78e5c90ed0
also small various features
136 lines
4.7 KiB
Vue
136 lines
4.7 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 indicators -->
|
|
<div class="px-4 py-4 border-t border-gray-800 space-y-2">
|
|
<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 class="flex items-center gap-2">
|
|
<span
|
|
class="w-2 h-2 rounded-full shrink-0"
|
|
:class="isOnline ? 'bg-green-400' : 'bg-red-500 animate-pulse'"
|
|
/>
|
|
<span class="text-xs" :class="isOnline ? 'text-gray-400' : 'text-red-400'">
|
|
{{ isOnline ? 'Бэкенд онлайн' : 'Бэкенд недоступен' }}
|
|
</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>
|
|
|
|
<!-- Offline banner -->
|
|
<div
|
|
v-if="!isOnline"
|
|
class="mx-6 mt-4 flex items-center gap-2 text-xs text-yellow-300 bg-yellow-950/50 border border-yellow-800/60 rounded-lg px-3 py-2 shrink-0"
|
|
>
|
|
<UIcon name="i-heroicons-signal-slash" class="w-4 h-4 shrink-0" />
|
|
Бэкенд недоступен — отображаются данные из БД
|
|
</div>
|
|
|
|
<!-- 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 { isOnline } = useBackendStatus()
|
|
|
|
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>
|