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

View File

@ -1,115 +1,306 @@
<template>
<div class="testing-view">
<v-container>
<v-row class="mb-4">
<!-- Filtro de Data/Hora -->
<v-col cols="12" md="4">
<v-text-field
v-model="filters.date"
label="Data/Hora"
type="datetime-local"
></v-text-field>
</v-col>
<v-container>
<!-- Filtros de Pesquisa -->
<v-row>
<v-col cols="6">
<v-text-field
v-model="filters.model"
label="Buscar por modelo"
prepend-inner-icon="mdi-magnify"
variant="outlined"
density="compact"
@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 -->
<v-col cols="12" md="4">
<v-text-field
v-model="filters.cameraId"
label="Câmera ID"
clearable
></v-text-field>
</v-col>
<!-- Botões de Ação -->
<v-row class="mb-4">
<v-col>
<v-btn
color="primary"
class="mr-2"
@click="openTrainingDialog"
>
<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 -->
<v-col cols="12" md="4">
<v-text-field
v-model="filters.search"
label="Pesquisar"
append-icon="mdi-magnify"
clearable
></v-text-field>
</v-col>
</v-row>
<!-- Tabela de Câmeras -->
<v-data-table
:headers="headers"
:items="cameras"
:search="filters.model"
:custom-filter="customFilter"
>
<template #[`item.actions`]="{ item }">
<v-icon
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 -->
<v-row class="mb-4">
<v-col class="d-flex justify-end">
<v-btn color="primary" class="mr-2" @click="trainModels">
Treinar Modelos
</v-btn>
<v-btn color="success" @click="integrateModels">
Integrar Modelos
</v-btn>
</v-col>
</v-row>
<!-- Lista de Modelos de Teste -->
<v-row>
<v-col>
<v-data-table
:headers="headers"
:items="models"
item-value="id"
class="elevation-1"
<!-- Diálogo de Treinamento -->
<v-dialog v-model="dialogs.training" max-width="500px">
<v-card>
<v-card-title>Treinar Modelo</v-card-title>
<v-card-text>
<v-select
v-model="selectedModel"
:items="modelOptions"
label="Selecione o modelo"
required
/>
<v-progress-linear
v-if="training.inProgress"
:value="training.progress"
height="25"
>
<template #top>
<v-toolbar flat>
<v-toolbar-title>Modelos de Teste</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<template v-slot:default>
{{ training.progress }}%
</template>
<template #item_actions="{ item }">
<v-btn icon color="primary" @click="viewDetails(item)">
<v-icon>mdi-eye</v-icon>
</v-btn>
</template>
</v-data-table>
</v-col>
</v-row>
</v-container>
</div>
</v-progress-linear>
<v-alert
v-if="training.complete"
:type="training.success ? 'success' : 'error'"
class="mt-4"
>
{{ training.message }}
</v-alert>
</v-card-text>
<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>
<script>
export default {
name: "TestingView",
data() {
return {
filters: {
date: "",
cameraId: "",
search: "",
model: '',
status: null
},
headers: [
{ text: "ID", value: "id" },
{ text: "Nome", value: "name" },
{ text: "Status", value: "status" },
{ text: "Ações", value: "actions", sortable: false },
{ title: 'Modelo', key: 'model' },
{ title: 'Descrição', key: 'description' },
{ title: 'Tipo', key: 'type' },
{ title: 'Status', key: 'status' },
{ title: 'Info', key: 'info'},
{ title: 'Vizualização', key: 'ip' },
{ title: 'Ações', key: 'actions', sortable: false }
],
models: [
{ id: 1, name: "Modelo A", status: "Pronto" },
{ id: 2, name: "Modelo B", status: "Em Execução" },
{ id: 3, name: "Modelo C", status: "Concluído" },
cameras: [
{
id: 1,
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: {
trainModels() {
alert("Iniciando treinamento dos modelos...");
customFilter(value, search, item) {
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>
<style scoped>
.testing-view {
background: #f9f9f9;
min-height: 100vh;
.v-data-table {
width: 100%;
}
</style>
</style>