Add API service layer, authentication store, and composables
- Implement API service modules for sensors, rooms, analytics, health, and auth - Add Pinia auth store for JWT token management and validation - Create Vue composables for API integration and state management - Update settings and AI optimization views for code style and connection URLs - Add test-websocket.html for local WebSocket testing
This commit is contained in:
119
src/components/common/AuthStatus.vue
Normal file
119
src/components/common/AuthStatus.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="auth-status" :class="authStatusClass">
|
||||
<div class="auth-status__indicator">
|
||||
<span class="auth-status__dot" :class="statusDotClass"></span>
|
||||
<span class="auth-status__text">{{ statusText }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="authStore.timeUntilExpiry" class="auth-status__expiry">
|
||||
Token expires in: {{ formatTimeUntilExpiry() }}
|
||||
</div>
|
||||
|
||||
<div v-if="authStore.error" class="auth-status__error">
|
||||
Auth Error: {{ authStore.error }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="!authStore.isAuthenticated"
|
||||
@click="handleReauth"
|
||||
:disabled="authStore.isLoading"
|
||||
class="auth-status__retry-btn"
|
||||
>
|
||||
{{ authStore.isLoading ? 'Authenticating...' : 'Retry Authentication' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const authStatusClass = computed(() => ({
|
||||
'auth-status--authenticated': authStore.isAuthenticated,
|
||||
'auth-status--error': !authStore.isAuthenticated || authStore.error,
|
||||
'auth-status--loading': authStore.isLoading
|
||||
}))
|
||||
|
||||
const statusDotClass = computed(() => ({
|
||||
'auth-status__dot--green': authStore.isAuthenticated && !authStore.error,
|
||||
'auth-status__dot--red': !authStore.isAuthenticated || authStore.error,
|
||||
'auth-status__dot--yellow': authStore.isLoading
|
||||
}))
|
||||
|
||||
const statusText = computed(() => {
|
||||
if (authStore.isLoading) return 'Authenticating...'
|
||||
if (authStore.isAuthenticated) return 'Authenticated'
|
||||
return 'Not authenticated'
|
||||
})
|
||||
|
||||
function formatTimeUntilExpiry(): string {
|
||||
const time = authStore.timeUntilExpiry
|
||||
if (!time) return 'Unknown'
|
||||
|
||||
if (time.hours > 0) {
|
||||
return `${time.hours}h ${time.minutes}m`
|
||||
} else {
|
||||
return `${time.minutes}m`
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReauth() {
|
||||
await authStore.generateToken()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.auth-status {
|
||||
@apply text-xs bg-gray-50 p-2 rounded border;
|
||||
|
||||
&--authenticated {
|
||||
@apply bg-green-50 border-green-200 text-green-800;
|
||||
}
|
||||
|
||||
&--error {
|
||||
@apply bg-red-50 border-red-200 text-red-800;
|
||||
}
|
||||
|
||||
&--loading {
|
||||
@apply bg-yellow-50 border-yellow-200 text-yellow-800;
|
||||
}
|
||||
|
||||
&__indicator {
|
||||
@apply flex items-center gap-1 mb-1;
|
||||
}
|
||||
|
||||
&__dot {
|
||||
@apply w-2 h-2 rounded-full;
|
||||
|
||||
&--green {
|
||||
@apply bg-green-500;
|
||||
}
|
||||
|
||||
&--red {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
|
||||
&--yellow {
|
||||
@apply bg-yellow-500;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
&__expiry {
|
||||
@apply text-gray-600 mb-1;
|
||||
}
|
||||
|
||||
&__error {
|
||||
@apply text-red-600 text-xs mb-2;
|
||||
}
|
||||
|
||||
&__retry-btn {
|
||||
@apply bg-blue-500 text-white px-2 py-1 rounded text-xs hover:bg-blue-600 disabled:opacity-50;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user