diff --git a/app/(main)/settings/page.tsx b/app/(main)/settings/page.tsx
index c7f870df..55b94647 100644
--- a/app/(main)/settings/page.tsx
+++ b/app/(main)/settings/page.tsx
@@ -1,17 +1,15 @@
"use client"
-import { useCallback, useEffect, useState } from "react"
+import { useCallback, useState } from "react"
import Link from "next/link"
import { PageHeader } from "@/components/page-header"
-import { FormToggle } from "@/components/form-kit"
+import { useTheme } from "@/components/theme-provider"
import { FileImportDialog } from "@/components/file-import-dialog"
import { EvoBgpSettingsSection } from "@/components/settings/evobgp-settings-section"
import { OpsPanel } from "@/components/ops-panel"
-import { Frame, FramePanel } from "@/components/reui/frame"
import { Button, buttonVariants } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { useDataSource } from "@/lib/data-source"
-import { DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS } from "@/lib/route-optimizer-data"
import {
AlertDialog,
AlertDialogAction,
@@ -24,35 +22,26 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import {
- SaveIcon, TrashIcon, PlusIcon, CheckIcon, CopyIcon,
+ CheckIcon,
AlertCircleIcon,
- RefreshCwIcon, LoaderCircleIcon,
+ LoaderCircleIcon,
DownloadIcon, UploadIcon, XIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { downloadSystemDatabaseBackup, restoreSystemDatabaseBackup } from "@/shared/api/system-database"
import { toast } from "sonner"
-interface ApiKey { id: string; name: string; prefix: string; created: string; last: string; scopes: string[] }
-
-
-const INIT_API_KEYS: ApiKey[] = [
- { id: "k1", name: "Monitoring Script", prefix: "rl_live_mU9k2…", created: "12 дн назад", last: "2м назад", scopes: ["servers:read", "probes:read"] },
- { id: "k2", name: "CI/CD deploy hook", prefix: "rl_live_vB3p8…", created: "1 мес назад", last: "вчера", scopes: ["filters:write", "domains:write"] },
- { id: "k3", name: "Grafana datasource", prefix: "rl_live_xK7r1…", created: "2 мес назад", last: "сейчас", scopes: ["traffic:read", "servers:read"] },
-]
-
const SECTIONS_NAV = ["Общие", "EvoBGP", "Уведомления", "API-ключи", "Безопасность"] as const
type NavSection = typeof SECTIONS_NAV[number]
function SettingRow({ label, description, children }: { label: string; description?: string; children: React.ReactNode }) {
return (
-
+
{label}
{description &&
{description}
}
-
{children}
+
{children}
)
}
@@ -103,66 +92,13 @@ function DatabaseRestoreConfirm({
export default function SettingsPage() {
const [section, setSection] = useState
("Общие")
- const [saved, setSaved] = useState(false)
- const [copied, setCopied] = useState(null)
-
- // data source
- const { mode, setMode, backendUrl, setBackendUrl, backendUrlLocked, mockModeAvailable, backendStatus, checkBackend } = useDataSource()
- const [urlDraft, setUrlDraft] = useState(backendUrl)
-
- useEffect(() => {
- setUrlDraft(backendUrl)
- }, [backendUrl])
-
- const commitBackendUrl = useCallback(() => {
- const normalized = urlDraft.trim().replace(/\/$/, "")
- setUrlDraft(normalized)
- setBackendUrl(normalized)
- void checkBackend()
- }, [urlDraft, setBackendUrl, checkBackend])
- // general
- const [lang, setLang] = useState("ru")
- const [theme, setTheme] = useState("system")
- const [timezone, setTimezone] = useState("Europe/Moscow")
- const [refreshSec, setRefreshSec] = useState("30")
- const [probeTimeout, setProbeTimeout] = useState("5")
+ const { mode, setMode, backendUrl, backendUrlLocked, mockModeAvailable, backendStatus, checkBackend } = useDataSource()
+ const { theme, setTheme } = useTheme()
const [dbBackupBusy, setDbBackupBusy] = useState(false)
const [dbRestoreBusy, setDbRestoreBusy] = useState(false)
const [dbRestoreFile, setDbRestoreFile] = useState(null)
const [dbRestoreDialogOpen, setDbRestoreDialogOpen] = useState(false)
- // notifications
- const [notifEmail, setNotifEmail] = useState(true)
- const [notifSlack, setNotifSlack] = useState(false)
- const [notifWh, setNotifWh] = useState(true)
- const [notifDegr, setNotifDegr] = useState(true)
- const [notifOffline, setNotifOffline] = useState(true)
- const [notifBgp, setNotifBgp] = useState(true)
- const [notifBackup, setNotifBackup] = useState(false)
-
- // api keys
- const [apiKeys, setApiKeys] = useState(INIT_API_KEYS)
-
- // security
- const [mfa, setMfa] = useState(true)
- const [sessMin, setSessMin] = useState("480")
- const [ipAllow, setIpAllow] = useState("10.0.0.0/8\n192.168.0.0/16")
- const [auditLog, setAuditLog] = useState(true)
-
- const handleCopy = (text: string) => {
- navigator.clipboard.writeText(text).catch(() => {})
- setCopied(text); setTimeout(() => setCopied(null), 1500)
- toast.success("Скопировано в буфер обмена")
- }
-
- const markSaved = useCallback(() => {
- setSaved(true)
- toast.success("Настройки сохранены")
- setTimeout(() => setSaved(false), 2000)
- }, [])
-
- const handleSave = markSaved
-
const systemDbAvailable = mode === "live" && backendStatus === true
const handleSystemDatabaseBackup = useCallback(async () => {
@@ -201,7 +137,6 @@ export default function SettingsPage() {
const renderContent = () => {
- const ra = DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS
// ── Общие ──
if (section === "Общие") return (
@@ -212,18 +147,18 @@ export default function SettingsPage() {
title="Источник данных"
description={
mockModeAvailable
- ? "Переключите между живыми данными от бекенда и суррогатными моками из lib/data.ts"
- : "В продакшен-образе доступны только живые данные от бекенда"
+ ? "Выберите живые или демонстрационные данные. Режим применяется сразу в этом браузере."
+ : "Источник живых данных задан конфигурацией приложения."
}
contentClassName="divide-y px-5"
>
{/* mode toggle */}
{mockModeAvailable ? (
-
+
{([["mock", "Моковые"], ["live", "Живые"]] as const).map(([v, l], i) => (
-
)
if (section === "EvoBGP") return
- // ── Уведомления ──
- if (section === "Уведомления") return (
-
- )
-
- // ── API-ключи ──
- if (section === "API-ключи") return (
-
-
-
- {apiKeys.map(k => (
-
-
-
-
-
{k.name}
-
- {k.prefix}
- handleCopy(k.prefix)} className="text-muted-foreground/40 hover:text-muted-foreground transition-colors">
- {copied === k.prefix ? : }
-
-
-
- {k.scopes.map(s => (
- {s}
- ))}
-
-
-
-
Создан: {k.created}
-
Использован: {k.last}
-
- Переиздать
- setApiKeys(p => p.filter(x => x.id !== k.id))}>
-
-
-
-
-
-
-
- ))}
-
-
-
- {[
- ["GET", "/servers", "Список серверов"],
- ["GET", "/traffic", "Данные трафика"],
- ["GET", "/filters", "Фильтры маршрутов"],
- ["POST", "/filters", "Создать фильтр"],
- ["GET", "/probes", "Диагностические пробы"],
- ["POST", "/probes/run", "Запустить тест"],
- ].map(([method, path, desc], i) => (
-
- {method}
- {path}
- {desc}
-
- ))}
-
-
-
- )
-
- // ── Безопасность ──
- if (section === "Безопасность") return (
-
-
-
-
-
-
- setSessMin(e.target.value)} />
-
-
-
-
-
-
-
-
-
-
Опасная зона} contentClassName="px-4 pb-4">
-
-
-
Сбросить все настройки
-
Удалит пользовательские параметры и вернёт заводские значения
-
-
- Сбросить
-
-
-
-
- )
+ if (section === "Уведомления") return Открыть оповещения
+ if (section === "API-ключи") return Создание и управление отдельными API-ключами в приложении пока недоступны.
+ return Состояние MFA, ограничений IP и настроек аудита в этом разделе не получено. Изменение этих параметров здесь недоступно.
Управление пользователями
}
return (
- {saved ? : }
- {saved ? "Сохранено!" : "Сохранить"}
- }
/>
@@ -604,7 +314,7 @@ export default function SettingsPage() {
{/* nav */}