fix(ui): выровнять chrome Frame и убрать Card-оболочки
Docker images / prepare-release (push) Successful in 4s
Docker images / backend-image (push) Failing after 2m36s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 55s
Docker images / publish-release (push) Skipped
Docker images / prepare-release (push) Successful in 4s
Docker images / backend-image (push) Failing after 2m36s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 55s
Docker images / publish-release (push) Skipped
Подключить App Switcher, NavUser, OpsPanel и AlertDialog вместо Card-shell. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
import {
|
||||
ChartBarIcon,
|
||||
CloudIcon,
|
||||
GlobeIcon,
|
||||
LayoutDashboardIcon,
|
||||
ServerIcon,
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
import { formatAppVersionLabel, getAppVersion } from "@/lib/app-version"
|
||||
|
||||
export const CURRENT_APP_ID = "mm"
|
||||
|
||||
export type AppSwitcherIconName =
|
||||
| "server"
|
||||
| "cloud"
|
||||
| "globe"
|
||||
| "dashboard"
|
||||
| "chart"
|
||||
|
||||
export type AppSwitcherEntry = {
|
||||
id: string
|
||||
name: string
|
||||
subtitle?: string
|
||||
url: string
|
||||
icon: AppSwitcherIconName
|
||||
shortcut?: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export type AppSwitcherConfig = {
|
||||
menuLabel: string
|
||||
apps: AppSwitcherEntry[]
|
||||
}
|
||||
|
||||
export const APP_SWITCHER_ICONS: Record<AppSwitcherIconName, LucideIcon> = {
|
||||
server: ServerIcon,
|
||||
cloud: CloudIcon,
|
||||
globe: GlobeIcon,
|
||||
dashboard: LayoutDashboardIcon,
|
||||
chart: ChartBarIcon,
|
||||
}
|
||||
|
||||
function localAppUrl(): string {
|
||||
if (typeof window !== "undefined") return window.location.origin
|
||||
return process.env.NEXT_PUBLIC_APP_URL?.replace(/\/$/, "") || "http://localhost:3000"
|
||||
}
|
||||
|
||||
export function localMikrotikEntry(): AppSwitcherEntry {
|
||||
return {
|
||||
id: CURRENT_APP_ID,
|
||||
name: "MikrotikManager",
|
||||
subtitle: formatAppVersionLabel(getAppVersion()),
|
||||
url: localAppUrl(),
|
||||
icon: "dashboard",
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
/** Offline fallback when auth-portal is unreachable. */
|
||||
export const DEFAULT_APP_SWITCHER_CONFIG: AppSwitcherConfig = {
|
||||
menuLabel: "Приложения",
|
||||
apps: [
|
||||
localMikrotikEntry(),
|
||||
{
|
||||
id: "cfdm",
|
||||
name: "CF Domain Manager",
|
||||
subtitle: "Управление доменами",
|
||||
url: "https://cfdm.shnt.top",
|
||||
icon: "cloud",
|
||||
},
|
||||
{
|
||||
id: "vps",
|
||||
name: "VPS Tracker",
|
||||
subtitle: "Учёт виртуальных серверов",
|
||||
url: "https://vps.shnt.top",
|
||||
icon: "server",
|
||||
},
|
||||
{
|
||||
id: "bgp",
|
||||
name: "EvoBGP",
|
||||
subtitle: "BGP маршрутизация",
|
||||
url: "https://bgp.shnt.top",
|
||||
icon: "globe",
|
||||
},
|
||||
{
|
||||
id: "fw",
|
||||
name: "EvoFirewall",
|
||||
subtitle: "Firewall controller",
|
||||
url: "https://fw.shnt.top",
|
||||
icon: "server",
|
||||
},
|
||||
{
|
||||
id: "dns",
|
||||
name: "Technitium DNS",
|
||||
subtitle: "DNS-сервер",
|
||||
url: "https://dns.shnt.top",
|
||||
icon: "globe",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const ICON_NAMES = new Set<AppSwitcherIconName>([
|
||||
"server",
|
||||
"cloud",
|
||||
"globe",
|
||||
"dashboard",
|
||||
"chart",
|
||||
])
|
||||
|
||||
function asIcon(value: unknown): AppSwitcherIconName {
|
||||
return ICON_NAMES.has(value as AppSwitcherIconName)
|
||||
? (value as AppSwitcherIconName)
|
||||
: "server"
|
||||
}
|
||||
|
||||
export function parseAppSwitcherConfig(raw: unknown): AppSwitcherConfig | null {
|
||||
if (!raw || typeof raw !== "object") return null
|
||||
const data = raw as { menuLabel?: unknown; apps?: unknown }
|
||||
if (!Array.isArray(data.apps) || data.apps.length === 0) return null
|
||||
|
||||
const apps: AppSwitcherEntry[] = []
|
||||
for (const item of data.apps) {
|
||||
if (!item || typeof item !== "object") continue
|
||||
const row = item as Record<string, unknown>
|
||||
if (typeof row.id !== "string" || typeof row.name !== "string" || typeof row.url !== "string") {
|
||||
continue
|
||||
}
|
||||
if (row.enabled === false) continue
|
||||
apps.push({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
subtitle: typeof row.subtitle === "string" ? row.subtitle : undefined,
|
||||
url: row.url,
|
||||
icon: asIcon(row.icon),
|
||||
shortcut: typeof row.shortcut === "string" ? row.shortcut : undefined,
|
||||
enabled: true,
|
||||
})
|
||||
}
|
||||
if (apps.length === 0) return null
|
||||
return {
|
||||
menuLabel: typeof data.menuLabel === "string" && data.menuLabel ? data.menuLabel : "Приложения",
|
||||
apps,
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeWithLocalApp(config: AppSwitcherConfig): AppSwitcherConfig {
|
||||
const local = localMikrotikEntry()
|
||||
const rest = config.apps.filter((app) => app.id !== CURRENT_APP_ID)
|
||||
return { ...config, apps: [local, ...rest] }
|
||||
}
|
||||
|
||||
export function authPortalBaseUrl(): string | null {
|
||||
const raw = process.env.NEXT_PUBLIC_AUTH_PORTAL_URL?.trim()
|
||||
if (!raw) return null
|
||||
return raw.replace(/\/$/, "")
|
||||
}
|
||||
|
||||
export function getCurrentApp(
|
||||
config: AppSwitcherConfig = DEFAULT_APP_SWITCHER_CONFIG,
|
||||
): AppSwitcherEntry {
|
||||
return config.apps.find((app) => app.id === CURRENT_APP_ID) ?? localMikrotikEntry()
|
||||
}
|
||||
Reference in New Issue
Block a user