chore: synchronize pending app/backend updates and repository hygiene
Includes current frontend and backend work in progress and removes generated artifacts from tracking to keep the repository clean for дальнейшая разработка. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -61,7 +61,7 @@ const SECTION_GROUPS: { group: string; icon: React.ReactNode; items: string[] }[
|
||||
{ group: "Данные", icon: <EyeIcon className="size-3" />, items: ["Домены", "IP-диапазоны", "ASN", "Communities"] },
|
||||
{ group: "Управление", icon: <WrenchIcon className="size-3" />, items: ["Серверы", "Фильтры", "Firewall", "GRE-туннели", "Бэкапы"] },
|
||||
{ group: "Инструменты", icon: <ShieldIcon className="size-3" />, items: ["Оптимизатор маршрутов", "OSPF", "Диагностика GRE", "Терминал"] },
|
||||
{ group: "Система", icon: <ServerIcon className="size-3" />, items: ["Настройки"] },
|
||||
{ group: "Система", icon: <ServerIcon className="size-3" />, items: ["Оповещения", "Сбор данных", "Настройки"] },
|
||||
]
|
||||
|
||||
const ALL_SECTIONS = SECTION_GROUPS.flatMap(g => g.items)
|
||||
@@ -147,21 +147,9 @@ const PERM_COLOR: Record<PermLevel, string> = {
|
||||
write: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
|
||||
}
|
||||
|
||||
const SECTIONS_NAV = ["Общие", "Сбор данных", "EvoBGP", "Уведомления", "Пользователи", "API-ключи", "Безопасность"] as const
|
||||
const SECTIONS_NAV = ["Общие", "EvoBGP", "Уведомления", "Пользователи", "API-ключи", "Безопасность"] as const
|
||||
type NavSection = typeof SECTIONS_NAV[number]
|
||||
|
||||
interface CollectorSettingsDto {
|
||||
enabled: boolean
|
||||
intervalSec: number
|
||||
probeIntervalSec?: number
|
||||
speedIntervalSec?: number
|
||||
retentionDays: number
|
||||
lastCollectedAt: string | null
|
||||
lastDurationMs: number | null
|
||||
lastError: string | null
|
||||
collectorRunning?: boolean
|
||||
}
|
||||
|
||||
function makeApiFetch(backendUrl: string) {
|
||||
return async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const headers: HeadersInit = init?.body ? { "Content-Type": "application/json" } : {}
|
||||
@@ -864,17 +852,6 @@ export default function SettingsPage() {
|
||||
const [evoBusy, setEvoBusy] = useState<"test" | "refresh" | null>(null)
|
||||
const [showEvoKey, setShowEvoKey] = useState(false)
|
||||
|
||||
// collectors
|
||||
const [trafficCollector, setTrafficCollector] = useState<CollectorSettingsDto | null>(null)
|
||||
const [uptimeCollector, setUptimeCollector] = useState<CollectorSettingsDto | null>(null)
|
||||
const [trafficIntervalDraft, setTrafficIntervalDraft] = useState("30")
|
||||
const [trafficRetentionDraft, setTrafficRetentionDraft] = useState("14")
|
||||
const [uptimeIntervalDraft, setUptimeIntervalDraft] = useState("15")
|
||||
const [uptimeSpeedIntervalDraft, setUptimeSpeedIntervalDraft] = useState("60")
|
||||
const [uptimeRetentionDraft, setUptimeRetentionDraft] = useState("14")
|
||||
const [collectorBusy, setCollectorBusy] = useState<"traffic" | "uptime" | null>(null)
|
||||
const [collectorError, setCollectorError] = useState<string | null>(null)
|
||||
|
||||
// general
|
||||
const [lang, setLang] = useState("ru")
|
||||
const [theme, setTheme] = useState("system")
|
||||
@@ -927,31 +904,6 @@ export default function SettingsPage() {
|
||||
// total sub-users count for summary
|
||||
const totalSubUsers = users.reduce((s, u) => s + u.subUsers.length, 0)
|
||||
|
||||
const loadCollectors = useCallback(async () => {
|
||||
if (mode !== "live" || backendStatus !== true) return
|
||||
setCollectorError(null)
|
||||
try {
|
||||
const [traffic, uptime] = await Promise.all([
|
||||
apiFetch<CollectorSettingsDto>("/api/traffic/settings"),
|
||||
apiFetch<CollectorSettingsDto>("/api/uptime/settings"),
|
||||
])
|
||||
setTrafficCollector(traffic)
|
||||
setUptimeCollector(uptime)
|
||||
setTrafficIntervalDraft(String(traffic.intervalSec))
|
||||
setTrafficRetentionDraft(String(traffic.retentionDays))
|
||||
setUptimeIntervalDraft(String(uptime.probeIntervalSec ?? uptime.intervalSec))
|
||||
setUptimeSpeedIntervalDraft(String(uptime.speedIntervalSec ?? 60))
|
||||
setUptimeRetentionDraft(String(uptime.retentionDays))
|
||||
} catch (e) {
|
||||
setCollectorError(e instanceof Error ? e.message : "Не удалось загрузить настройки сборщиков")
|
||||
}
|
||||
}, [apiFetch, backendStatus, mode])
|
||||
|
||||
useEffect(() => {
|
||||
if (section !== "Сбор данных") return
|
||||
queueMicrotask(() => { void loadCollectors() })
|
||||
}, [section, loadCollectors])
|
||||
|
||||
useEffect(() => {
|
||||
if (section !== "EvoBGP") return
|
||||
if (mode === "live" && backendStatus === true) queueMicrotask(() => { void evo.loadSettings() })
|
||||
@@ -1178,145 +1130,6 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
)
|
||||
|
||||
// ── Сбор данных ──
|
||||
if (section === "Сбор данных") return (
|
||||
<div className="space-y-4">
|
||||
{(mode !== "live" || backendStatus !== true) && (
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-4 px-4">
|
||||
<p className="text-sm font-medium">Раздел доступен только в live-режиме</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Переключи `Режим данных` в `Живые` и проверь доступность бекенда в разделе `Общие`.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{mode === "live" && backendStatus === true && (
|
||||
<>
|
||||
{collectorError && (
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-4 px-4">
|
||||
<p className="text-xs text-destructive">{collectorError}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Сбор трафика</CardTitle>
|
||||
<CardDescription className="text-xs">Настройки для `/traffic`</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 px-5 pb-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm">Состояние</span>
|
||||
<div className="flex rounded-md border border-input overflow-hidden h-8">
|
||||
<button className={cn("px-3 text-xs", trafficCollector?.enabled ? "bg-emerald-600 text-white" : "text-muted-foreground hover:bg-muted")}
|
||||
disabled={collectorBusy === "traffic"}
|
||||
onClick={async () => {
|
||||
setCollectorBusy("traffic")
|
||||
try { await apiFetch("/api/traffic/settings", { method: "PUT", body: JSON.stringify({ enabled: true }) }); await loadCollectors() }
|
||||
finally { setCollectorBusy(null) }
|
||||
}}>Вкл</button>
|
||||
<button className={cn("px-3 text-xs border-l border-input", !trafficCollector?.enabled ? "bg-muted-foreground text-white" : "text-muted-foreground hover:bg-muted")}
|
||||
disabled={collectorBusy === "traffic"}
|
||||
onClick={async () => {
|
||||
setCollectorBusy("traffic")
|
||||
try { await apiFetch("/api/traffic/settings", { method: "PUT", body: JSON.stringify({ enabled: false }) }); await loadCollectors() }
|
||||
finally { setCollectorBusy(null) }
|
||||
}}>Выкл</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Input value={trafficIntervalDraft} onChange={(e) => setTrafficIntervalDraft(e.target.value)} className="h-8 text-sm" placeholder="Интервал (сек)" />
|
||||
<Input value={trafficRetentionDraft} onChange={(e) => setTrafficRetentionDraft(e.target.value)} className="h-8 text-sm" placeholder="Хранение (дней)" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm" variant="outline" disabled={collectorBusy === "traffic"} onClick={async () => {
|
||||
setCollectorBusy("traffic")
|
||||
try {
|
||||
await apiFetch("/api/traffic/settings", { method: "PUT", body: JSON.stringify({ intervalSec: Number.parseInt(trafficIntervalDraft, 10) || 30, retentionDays: Number.parseInt(trafficRetentionDraft, 10) || 14 }) })
|
||||
await loadCollectors()
|
||||
} finally { setCollectorBusy(null) }
|
||||
}}>Сохранить</Button>
|
||||
<Button size="sm" variant="outline" disabled={collectorBusy === "traffic"} onClick={async () => {
|
||||
setCollectorBusy("traffic")
|
||||
try { await apiFetch("/api/traffic/collect-now", { method: "POST" }); await loadCollectors() }
|
||||
finally { setCollectorBusy(null) }
|
||||
}}>Собрать сейчас</Button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<p>Последний сбор: {trafficCollector?.lastCollectedAt ? new Date(trafficCollector.lastCollectedAt).toLocaleString("ru-RU") : "—"}</p>
|
||||
<p>Длительность: {trafficCollector?.lastDurationMs != null ? `${trafficCollector.lastDurationMs} мс` : "—"}</p>
|
||||
<p className={cn(trafficCollector?.lastError ? "text-destructive" : "")}>{trafficCollector?.lastError ? `Ошибка: ${trafficCollector.lastError}` : "Ошибок нет"}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Сбор uptime</CardTitle>
|
||||
<CardDescription className="text-xs">Настройки для `/uptime`</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 px-5 pb-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm">Состояние</span>
|
||||
<div className="flex rounded-md border border-input overflow-hidden h-8">
|
||||
<button className={cn("px-3 text-xs", uptimeCollector?.enabled ? "bg-emerald-600 text-white" : "text-muted-foreground hover:bg-muted")}
|
||||
disabled={collectorBusy === "uptime"}
|
||||
onClick={async () => {
|
||||
setCollectorBusy("uptime")
|
||||
try { await apiFetch("/api/uptime/settings", { method: "PUT", body: JSON.stringify({ enabled: true }) }); await loadCollectors() }
|
||||
finally { setCollectorBusy(null) }
|
||||
}}>Вкл</button>
|
||||
<button className={cn("px-3 text-xs border-l border-input", !uptimeCollector?.enabled ? "bg-muted-foreground text-white" : "text-muted-foreground hover:bg-muted")}
|
||||
disabled={collectorBusy === "uptime"}
|
||||
onClick={async () => {
|
||||
setCollectorBusy("uptime")
|
||||
try { await apiFetch("/api/uptime/settings", { method: "PUT", body: JSON.stringify({ enabled: false }) }); await loadCollectors() }
|
||||
finally { setCollectorBusy(null) }
|
||||
}}>Выкл</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Input value={uptimeIntervalDraft} onChange={(e) => setUptimeIntervalDraft(e.target.value)} className="h-8 text-sm" placeholder="Интервал ping-проб (сек)" />
|
||||
<Input value={uptimeSpeedIntervalDraft} onChange={(e) => setUptimeSpeedIntervalDraft(e.target.value)} className="h-8 text-sm" placeholder="Интервал speed-проб (сек)" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
<Input value={uptimeRetentionDraft} onChange={(e) => setUptimeRetentionDraft(e.target.value)} className="h-8 text-sm" placeholder="Хранение (дней)" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm" variant="outline" disabled={collectorBusy === "uptime"} onClick={async () => {
|
||||
setCollectorBusy("uptime")
|
||||
try {
|
||||
await apiFetch("/api/uptime/settings", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
probeIntervalSec: Number.parseInt(uptimeIntervalDraft, 10) || 15,
|
||||
speedIntervalSec: Number.parseInt(uptimeSpeedIntervalDraft, 10) || 60,
|
||||
retentionDays: Number.parseInt(uptimeRetentionDraft, 10) || 14,
|
||||
}),
|
||||
})
|
||||
await loadCollectors()
|
||||
} finally { setCollectorBusy(null) }
|
||||
}}>Сохранить</Button>
|
||||
<Button size="sm" variant="outline" disabled={collectorBusy === "uptime"} onClick={async () => {
|
||||
setCollectorBusy("uptime")
|
||||
try { await apiFetch("/api/uptime/collect-now", { method: "POST" }); await loadCollectors() }
|
||||
finally { setCollectorBusy(null) }
|
||||
}}>Собрать сейчас</Button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<p>Последний сбор: {uptimeCollector?.lastCollectedAt ? new Date(uptimeCollector.lastCollectedAt).toLocaleString("ru-RU") : "—"}</p>
|
||||
<p>Длительность: {uptimeCollector?.lastDurationMs != null ? `${uptimeCollector.lastDurationMs} мс` : "—"}</p>
|
||||
<p className={cn(uptimeCollector?.lastError ? "text-destructive" : "")}>{uptimeCollector?.lastError ? `Ошибка: ${uptimeCollector.lastError}` : "Ошибок нет"}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
// ── EvoBGP ──
|
||||
if (section === "EvoBGP") return (
|
||||
<div className="space-y-4">
|
||||
|
||||
Reference in New Issue
Block a user