versão 18h32 13/02

This commit is contained in:
Thaís Ferreira 2025-02-13 18:32:58 -03:00
parent d36d7f76d3
commit 923d6b0655
2 changed files with 341 additions and 132 deletions

View File

@ -16,30 +16,14 @@
:size="isCollapsed ? 40 : 36" :size="isCollapsed ? 40 : 36"
class="gradient-avatar" class="gradient-avatar"
> >
<v-icon size="24" color="white">mdi-security</v-icon> <v-icon size="22" color="white">mdi-security</v-icon>
</v-avatar> </v-avatar>
<span v-if="!isCollapsed" class="text-h6 ml-3 white--text font-weight-bold">TARS</span> <span v-if="!isCollapsed" class="text-h6 ml-3 white--text font-weight-bold">TARS</span>
</div> </div>
</div> </div>
<v-divider class="border-opacity-25"></v-divider> <v-divider class="border-opacity-25"></v-divider>
<!-- User Profile Section --> <!-- Navigation Menu -->
<div class="profile-section px-4 py-3" @click="goToUserProfile">
<div class="d-flex align-center" :class="{ 'justify-center': isCollapsed }">
<v-avatar color="primary" size="40">
<v-icon>mdi-account</v-icon>
</v-avatar>
<div v-if="!isCollapsed" class="ml-3">
<div class="text-subtitle-2 font-weight-medium">Admin User</div>
<div class="text-caption text-grey-lighten-1">Administrador</div>
</div>
</div>
</div>
<v-divider class="border-opacity-25"></v-divider>
<!-- Flattened Navigation Menu -->
<v-list nav class="px-2"> <v-list nav class="px-2">
<v-list-item <v-list-item
v-for="item in flattenedMenuItems" v-for="item in flattenedMenuItems"
@ -48,6 +32,7 @@
:value="item.name" :value="item.name"
rounded="lg" rounded="lg"
class="mb-1" class="mb-1"
@click="item.name === 'logout' ? handleLogout() : null"
> >
<template v-slot:prepend> <template v-slot:prepend>
<v-icon :size="22">{{ item.icon }}</v-icon> <v-icon :size="22">{{ item.icon }}</v-icon>
@ -85,32 +70,25 @@
</template> </template>
</v-navigation-drawer> </v-navigation-drawer>
<!-- Updated Top Bar --> <!-- Top Bar -->
<v-app-bar <v-app-bar
elevation="1" elevation="1"
height="64" height="64"
color="background" color="background"
> >
<v-spacer /> <v-spacer></v-spacer>
<!-- Admin User Profile --> <!-- Admin User Profile -->
<div class="d-flex align-center mr-2"> <div
class="d-flex align-center mr-2 profile-section pa-2 rounded"
style="cursor: pointer;"
@click="$router.push({ name: 'user-profile' })"
>
<v-avatar color="primary" size="32" class="mr-2"> <v-avatar color="primary" size="32" class="mr-2">
<v-icon>mdi-account</v-icon> <v-icon>mdi-account</v-icon>
</v-avatar> </v-avatar>
<span class="text-subtitle-2 font-weight-medium">Admin User</span> <span class="text-subtitle-2 font-weight-medium">{{ currentUser?.name || 'Admin User' }}</span>
</div> </div>
<!-- Logout Button -->
<v-btn
color="error"
variant="tonal"
@click="handleLogout"
prepend-icon="mdi-logout"
class="mr-2"
>
Sair
</v-btn>
</v-app-bar> </v-app-bar>
<!-- Main Content --> <!-- Main Content -->
@ -118,16 +96,34 @@
<v-container fluid class="pa-6"> <v-container fluid class="pa-6">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<component :is="Component" /> <component :is="Component"></component>
</transition> </transition>
</router-view> </router-view>
</v-container> </v-container>
</v-main> </v-main>
<!-- Status Snackbar -->
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="3000"
>
{{ snackbar.text }}
<template v-slot:actions>
<v-btn
color="white"
variant="text"
@click="snackbar.show = false"
>
Fechar
</v-btn>
</template>
</v-snackbar>
</v-app> </v-app>
</template> </template>
<script> <script>
import { mapActions } from 'vuex'; import { mapGetters } from 'vuex';
export default { export default {
name: 'AppLayout', name: 'AppLayout',
@ -136,6 +132,11 @@ export default {
return { return {
drawer: true, drawer: true,
isCollapsed: false, isCollapsed: false,
snackbar: {
show: false,
text: '',
color: 'success'
},
flattenedMenuItems: [ flattenedMenuItems: [
{ {
name: 'home', name: 'home',
@ -181,27 +182,40 @@ export default {
icon: 'mdi-account-group', icon: 'mdi-account-group',
label: 'Usuários' label: 'Usuários'
}, },
{
name: 'logout',
route: { name: 'login' },
icon: 'mdi-logout',
label: 'Sair',
}
] ]
}; };
}, },
computed: {
...mapGetters('auth', [
'isAuthenticated',
'currentUser'
])
},
methods: { methods: {
...mapActions('auth', ['logout']),
toggleDrawer() { toggleDrawer() {
this.isCollapsed = !this.isCollapsed; this.isCollapsed = !this.isCollapsed;
}, },
goToUserProfile() { showSnackbar(text, color = 'success') {
this.$router.push({ name: 'user-profile' }); this.snackbar.text = text;
this.snackbar.color = color;
this.snackbar.show = true;
}, },
async handleLogout() { async handleLogout() {
try { try {
await this.logout(); await this.$store.dispatch('auth/logout');
this.$router.push({ name: 'login' }); this.$router.push({ name: 'login' });
} catch (error) { } catch (error) {
console.error('Erro ao fazer logout:', error); this.showSnackbar('Erro ao fazer logout', 'error');
} }
} }
} }
@ -222,6 +236,10 @@ export default {
cursor: pointer; cursor: pointer;
} }
.profile-section:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.v-list-item--active { .v-list-item--active {
background-color: rgba(255, 255, 255, 0.1) !important; background-color: rgba(255, 255, 255, 0.1) !important;
} }

View File

@ -1,115 +1,306 @@
<template> <template>
<div class="testing-view"> <v-container>
<v-container> <!-- Filtros de Pesquisa -->
<v-row class="mb-4"> <v-row>
<!-- Filtro de Data/Hora --> <v-col cols="6">
<v-col cols="12" md="4"> <v-text-field
<v-text-field v-model="filters.model"
v-model="filters.date" label="Buscar por modelo"
label="Data/Hora" prepend-inner-icon="mdi-magnify"
type="datetime-local" variant="outlined"
></v-text-field> density="compact"
</v-col> @input="filterModels"
/>
</v-col>
<v-col cols="6">
<v-select
v-model="filters.status"
:items="statusOptions"
label="Status"
variant="outlined"
density="compact"
/>
</v-col>
</v-row>
<!-- Filtro de Câmera ID --> <!-- Botões de Ação -->
<v-col cols="12" md="4"> <v-row class="mb-4">
<v-text-field <v-col>
v-model="filters.cameraId" <v-btn
label="Câmera ID" color="primary"
clearable class="mr-2"
></v-text-field> @click="openTrainingDialog"
</v-col> >
<v-icon left>mdi-brain</v-icon>
Treinar
</v-btn>
<v-btn
color="success"
class="mr-2"
:disabled="!hasTrained"
@click="integrate"
>
<v-icon left>mdi-connection</v-icon>
Integrar
</v-btn>
<v-btn
color="info"
:disabled="!hasIntegrated"
@click="test"
>
<v-icon left>mdi-test-tube</v-icon>
Testar
</v-btn>
</v-col>
</v-row>
<!-- Campo de Pesquisa --> <!-- Tabela de Câmeras -->
<v-col cols="12" md="4"> <v-data-table
<v-text-field :headers="headers"
v-model="filters.search" :items="cameras"
label="Pesquisar" :search="filters.model"
append-icon="mdi-magnify" :custom-filter="customFilter"
clearable >
></v-text-field> <template #[`item.actions`]="{ item }">
</v-col> <v-icon
</v-row> size="small"
class="mr-2"
@click="editCamera(item)"
>
mdi-pencil
</v-icon>
<v-icon
size="small"
@click="confirmDelete(item)"
>
mdi-delete
</v-icon>
</template>
</v-data-table>
<!-- Botões de Treino e Integração --> <!-- Diálogo de Treinamento -->
<v-row class="mb-4"> <v-dialog v-model="dialogs.training" max-width="500px">
<v-col class="d-flex justify-end"> <v-card>
<v-btn color="primary" class="mr-2" @click="trainModels"> <v-card-title>Treinar Modelo</v-card-title>
Treinar Modelos <v-card-text>
</v-btn> <v-select
<v-btn color="success" @click="integrateModels"> v-model="selectedModel"
Integrar Modelos :items="modelOptions"
</v-btn> label="Selecione o modelo"
</v-col> required
</v-row> />
<v-progress-linear
<!-- Lista de Modelos de Teste --> v-if="training.inProgress"
<v-row> :value="training.progress"
<v-col> height="25"
<v-data-table
:headers="headers"
:items="models"
item-value="id"
class="elevation-1"
> >
<template #top> <template v-slot:default>
<v-toolbar flat> {{ training.progress }}%
<v-toolbar-title>Modelos de Teste</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
</template> </template>
<template #item_actions="{ item }"> </v-progress-linear>
<v-btn icon color="primary" @click="viewDetails(item)"> <v-alert
<v-icon>mdi-eye</v-icon> v-if="training.complete"
</v-btn> :type="training.success ? 'success' : 'error'"
</template> class="mt-4"
</v-data-table> >
</v-col> {{ training.message }}
</v-row> </v-alert>
</v-container> </v-card-text>
</div> <v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
text
@click="closeTrainingDialog"
>
Fechar
</v-btn>
<v-btn
color="primary"
:disabled="!selectedModel || training.inProgress"
@click="startTraining"
>
Iniciar Treinamento
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Diálogo de Confirmação de Exclusão -->
<v-dialog v-model="dialogs.delete" max-width="400px">
<v-card>
<v-card-title>Confirmar Exclusão</v-card-title>
<v-card-text>
Tem certeza que deseja excluir esta câmera?
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="error"
text
@click="deleteCamera"
>
Excluir
</v-btn>
<v-btn
color="primary"
text
@click="dialogs.delete = false"
>
Cancelar
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template> </template>
<script> <script>
export default { export default {
name: "TestingView",
data() { data() {
return { return {
filters: { filters: {
date: "", model: '',
cameraId: "", status: null
search: "",
}, },
headers: [ headers: [
{ text: "ID", value: "id" }, { title: 'Modelo', key: 'model' },
{ text: "Nome", value: "name" }, { title: 'Descrição', key: 'description' },
{ text: "Status", value: "status" }, { title: 'Tipo', key: 'type' },
{ text: "Ações", value: "actions", sortable: false }, { title: 'Status', key: 'status' },
{ title: 'Info', key: 'info'},
{ title: 'Vizualização', key: 'ip' },
{ title: 'Ações', key: 'actions', sortable: false }
], ],
models: [ cameras: [
{ id: 1, name: "Modelo A", status: "Pronto" }, {
{ id: 2, name: "Modelo B", status: "Em Execução" }, id: 1,
{ id: 3, name: "Modelo C", status: "Concluído" }, model: 'AXIS Q6128',
description: 'Câmera HD para ambientes internos',
type: 'HTTP',
status: 'Ativo',
ip: '192.168.1.100',
port: '8080'
},
{
id: 1,
model: 'Hikvision DS-2CD',
description: 'Câmera HD para ambientes internos',
type: 'HTTP',
status: 'Ativo',
ip: '192.168.1.100',
port: '8080'
},
{
id: 1,
model: 'AK-31548',
description: 'Câmera HD para ambientes internos',
type: 'RTSP',
status: 'Ativo',
ip: '192.168.1.100',
port: '8080'
},
// Adicione mais câmeras conforme necessário
], ],
}; statusOptions: ['Ativo', 'Inativo', 'Em Manutenção'],
typeOptions: ['HTTP','RTSP'],
modelOptions: ['Modelo AXIS Q6128', 'Modelo Hikvision DS-2CD', 'Modelo AK-31548'],
selectedModel: null,
selectedCamera: null,
dialogs: {
training: false,
delete: false
},
training: {
inProgress: false,
progress: 0,
complete: false,
success: false,
message: ''
},
hasTrained: false,
hasIntegrated: false
}
}, },
methods: { methods: {
trainModels() { customFilter(value, search, item) {
alert("Iniciando treinamento dos modelos..."); if (search == null || search === '') return true
const modelMatch = item.model.toLowerCase().includes(search.toLowerCase())
const statusMatch = !this.filters.status || item.status === this.filters.status
return modelMatch && statusMatch
}, },
integrateModels() {
alert("Integrando modelos..."); openTrainingDialog() {
this.training = {
inProgress: false,
progress: 0,
complete: false,
success: false,
message: ''
}
this.dialogs.training = true
}, },
viewDetails(item) {
alert(`Visualizando detalhes do modelo: ${item.name}`); closeTrainingDialog() {
this.dialogs.training = false
this.selectedModel = null
}, },
},
}; async startTraining() {
this.training.inProgress = true
this.training.progress = 0
// Simulação do progresso de treinamento
for (let i = 0; i <= 100; i += 10) {
await new Promise(resolve => setTimeout(resolve, 500))
this.training.progress = i
}
this.training.inProgress = false
this.training.complete = true
this.training.success = true
this.training.message = 'Treinamento concluído com sucesso!'
this.hasTrained = true
},
integrate() {
// Lógica de integração
this.hasIntegrated = true
// Exibir mensagem de sucesso
},
test() {
// Lógica de teste
// Exibir resultados do teste
},
editCamera(camera) {
this.selectedCamera = camera
// Implementar lógica de edição
},
confirmDelete(camera) {
this.selectedCamera = camera
this.dialogs.delete = true
},
deleteCamera() {
const index = this.cameras.findIndex(c => c.id === this.selectedCamera.id)
if (index > -1) {
this.cameras.splice(index, 1)
}
this.dialogs.delete = false
this.selectedCamera = null
// Exibir mensagem de sucesso
}
}
}
</script> </script>
<style scoped> <style scoped>
.testing-view { .v-data-table {
background: #f9f9f9; width: 100%;
min-height: 100vh;
} }
</style> </style>