Files
dip-ids/frontend/layouts/default.vue
T
2026-04-12 22:26:26 +04:00

117 lines
3.8 KiB
Vue

<template>
<div class="min-h-screen bg-gray-950 text-gray-100 flex">
<!-- Sidebar -->
<aside class="w-60 shrink-0 bg-gray-900 border-r border-gray-800 flex flex-col">
<!-- Logo -->
<div class="px-5 py-5 border-b border-gray-800">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-600 flex items-center justify-center shrink-0">
<UIcon name="i-heroicons-shield-exclamation" class="text-white w-5 h-5" />
</div>
<div>
<p class="text-sm font-bold text-white leading-tight">DIP-IDS</p>
<p class="text-[10px] text-gray-400 leading-tight">Network IDS</p>
</div>
</div>
</div>
<!-- Navigation -->
<nav class="flex-1 px-3 py-4 space-y-0.5">
<NuxtLink
v-for="item in navigation"
:key="item.to"
:to="item.to"
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors"
:class="isActive(item.to)
? 'bg-blue-600/20 text-blue-400 font-medium'
: 'text-gray-400 hover:text-gray-200 hover:bg-gray-800'"
>
<UIcon :name="item.icon" class="w-4.5 h-4.5 shrink-0" />
{{ item.label }}
</NuxtLink>
</nav>
<!-- Status indicator -->
<div class="px-4 py-4 border-t border-gray-800">
<div class="flex items-center gap-2">
<span
class="w-2 h-2 rounded-full shrink-0"
:class="captureRunning ? 'bg-green-400 animate-pulse' : 'bg-gray-600'"
/>
<span class="text-xs text-gray-400">
{{ captureRunning ? 'Захват активен' : 'Захват остановлен' }}
</span>
</div>
</div>
</aside>
<!-- Main content -->
<div class="flex-1 flex flex-col min-w-0">
<!-- Top bar -->
<header class="h-14 border-b border-gray-800 bg-gray-900/50 flex items-center px-6 gap-4 shrink-0">
<h1 class="text-sm font-semibold text-gray-200 flex-1">{{ pageTitle }}</h1>
<UButton
v-if="captureRunning"
color="red"
variant="soft"
size="xs"
icon="i-heroicons-stop-circle"
@click="stopCapture"
>
Остановить захват
</UButton>
<UButton
color="blue"
variant="soft"
size="xs"
icon="i-heroicons-document-arrow-down"
@click="downloadReport"
>
PDF отчёт
</UButton>
</header>
<!-- Page content -->
<main class="flex-1 overflow-auto p-6">
<slot />
</main>
</div>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const { status, stopCapture } = useCaptureStatus()
const captureRunning = computed(() => status.value?.running ?? false)
const navigation = [
{ to: '/', label: 'Дашборд', icon: 'i-heroicons-chart-bar-square' },
{ to: '/alerts', label: 'Алерты', icon: 'i-heroicons-bell-alert' },
{ to: '/capture', label: 'Захват', icon: 'i-heroicons-radio' },
{ to: '/stats', label: 'Статистика', icon: 'i-heroicons-chart-pie' },
]
const pageTitles: Record<string, string> = {
'/': 'Обзор',
'/alerts': 'Журнал алертов',
'/capture': 'Управление захватом',
'/stats': 'Статистика трафика',
}
const pageTitle = computed(() => pageTitles[route.path] ?? 'DIP-IDS')
function isActive(to: string) {
return to === '/' ? route.path === '/' : route.path.startsWith(to)
}
function downloadReport() {
const a = document.createElement('a')
a.href = '/api/reports/generate'
a.download = `nids-report-${new Date().toISOString().slice(0, 10)}.pdf`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
</script>