import { servers } from "@/lib/data" import type { InterfaceType, PermLevel, AppUserRole as Role } from "@mmapp/contracts/users" export type { InterfaceType, PermLevel, Role } export interface SectionPerm { section: string; level: PermLevel } export interface ServerPerm { serverId: string; level: PermLevel } export interface InterfaceBinding { id: string userId: string serverId: string serverName: string serverSite: string serverCountry: string interfaceName: string interfaceType: InterfaceType peerPublicKey?: string peerName?: string comment: string } export interface CatalogPeer { publicKey: string name: string comment: string allowedIps: string[] latestHandshake?: string boundUserId: string | null boundUserLogin: string | null } export interface CatalogIface { name: string type: InterfaceType running: boolean disabled: boolean boundUserId: string | null boundUserLogin: string | null peers?: CatalogPeer[] peersError?: string } export interface AppUserForm { name: string login: string email: string role: Role active: boolean sections: SectionPerm[] servers: ServerPerm[] bindings: InterfaceBinding[] } export interface AppUser { id: string name: string login: string email: string role: Role last: string avatar: string active: boolean sections: SectionPerm[] servers: ServerPerm[] bindings: InterfaceBinding[] } export interface UserServerOption { id: string name: string host: string site: string country: string status?: "online" | "offline" | "degraded" | null } export const IFACE_TYPE_LABEL: Record = { ether: "Ethernet", gre: "GRE", wg: "WireGuard", other: "Прочие", } export const IFACE_TYPE_ORDER: InterfaceType[] = ["ether", "gre", "wg", "other"] export const ROLE_LABEL: Record = { admin: "Администратор", operator: "Оператор", viewer: "Наблюдатель", } export const ROLE_COLOR: Record = { admin: "bg-violet-500/10 text-violet-600 dark:text-violet-400 border border-violet-500/20", operator: "bg-sky-500/10 text-sky-600 dark:text-sky-400 border border-sky-500/20", viewer: "bg-muted text-muted-foreground border border-border", } export const PERM_OPTS: { v: PermLevel; label: string }[] = [ { v: "none", label: "Нет" }, { v: "read", label: "Просмотр" }, { v: "write", label: "Управление" }, ] export const PERM_COLOR: Record = { none: "bg-muted text-muted-foreground", read: "bg-sky-500/10 text-sky-600 dark:text-sky-400", write: "bg-success/10 text-success", } export const SECTION_GROUP_DEFS: { group: string; items: string[] }[] = [ { group: "Обзор", items: ["Дашборд", "Трафик", "Карта сети", "Мониторинг"] }, { group: "Данные", items: ["Домены", "IP-диапазоны", "ASN", "Communities"] }, { group: "Управление", items: ["Серверы", "Фильтры", "Firewall", "GRE-туннели", "Бэкапы"] }, { group: "Инструменты", items: ["Оптимизатор маршрутов", "OSPF", "Диагностика GRE", "Терминал"] }, { group: "Система", items: ["Оповещения", "Сбор данных", "Пользователи", "Настройки"] }, ] export const ALL_SECTIONS = SECTION_GROUP_DEFS.flatMap((g) => g.items) export function defaultSections(role: Role): SectionPerm[] { return ALL_SECTIONS.map((section) => { let level: PermLevel = "none" if (role === "admin") level = "write" else if (role === "operator") { level = ["Серверы", "Пользователи", "Фильтры", "Firewall", "GRE-туннели", "Бэкапы", "Диагностика GRE", "Оптимизатор маршрутов", "OSPF"].includes(section) ? "write" : "read" } else if (role === "viewer") { level = ["Настройки", "Терминал"].includes(section) ? "none" : "read" } return { section, level } }) } export function defaultServers(role: Role, serverList: { id: string }[] = servers): ServerPerm[] { return serverList.map((s) => ({ serverId: s.id, level: role === "admin" ? "write" : "read", })) } export function userInitials(name: string): string { return name.trim().split(/\s+/).map((p) => p[0] ?? "").slice(0, 2).join("").toUpperCase() || "??" } export const MOCK_IFACE_CATALOG: Record = { srv1: [ { name: "ether1", type: "ether", running: true, disabled: false, boundUserId: "u1", boundUserLogin: "a.korotaev@company.io" }, { name: "ether2", type: "ether", running: true, disabled: false, boundUserId: null, boundUserLogin: null }, { name: "gre-office-msk", type: "gre", running: true, disabled: false, boundUserId: "u1", boundUserLogin: "a.korotaev@company.io" }, { name: "gre-datacenter", type: "gre", running: true, disabled: false, boundUserId: "u1", boundUserLogin: "a.korotaev@company.io" }, { name: "gre-spb-branch", type: "gre", running: true, disabled: false, boundUserId: "u2", boundUserLogin: "d.fedorov@company.io" }, { name: "gre-retail-01", type: "gre", running: false, disabled: false, boundUserId: "u4", boundUserLogin: "i.petrov@company.io" }, { name: "wg-msk-spb", type: "wg", running: true, disabled: false, boundUserId: null, boundUserLogin: null, peers: [ { publicKey: "mockPeerKeyAAAA0123456789", name: "phone-ak", comment: "", allowedIps: ["10.8.0.2/32"], boundUserId: "u1", boundUserLogin: "a.korotaev@company.io" }, { publicKey: "mockPeerKeyBBBB0123456789", name: "laptop-ak", comment: "", allowedIps: ["10.8.0.3/32"], boundUserId: null, boundUserLogin: null, latestHandshake: "12s" }, ], }, ], srv7: [ { name: "ether1", type: "ether", running: true, disabled: false, boundUserId: null, boundUserLogin: null }, { name: "gre-office-msk", type: "gre", running: true, disabled: false, boundUserId: "u1", boundUserLogin: "a.korotaev@company.io" }, { name: "gre-warehouse", type: "gre", running: false, disabled: false, boundUserId: "u1", boundUserLogin: "a.korotaev@company.io" }, { name: "gre-spb-branch", type: "gre", running: true, disabled: false, boundUserId: "u2", boundUserLogin: "d.fedorov@company.io" }, { name: "wg-lab", type: "wg", running: true, disabled: false, boundUserId: null, boundUserLogin: null, peers: [ { publicKey: "mockPeerKeyLABB0123456789", name: "lab-peer", comment: "", allowedIps: ["10.9.0.2/32"], boundUserId: null, boundUserLogin: null }, ], }, ], } export function bindingDiffKey(b: Pick): string { return `${b.serverId}::${b.interfaceName}::${b.peerPublicKey ?? ""}` } export function bindingTitle(b: Pick): string { if (b.interfaceType === "wg" && (b.peerName || b.peerPublicKey)) { return `${b.peerName || "peer"} · ${b.interfaceName}` } return b.interfaceName } function bind( id: string, userId: string, serverId: string, interfaceName: string, interfaceType: InterfaceType, comment: string, peer?: { publicKey: string; name: string }, ): InterfaceBinding { const srv = servers.find((s) => s.id === serverId) return { id, userId, serverId, serverName: srv?.name ?? serverId, serverSite: srv?.site ?? "—", serverCountry: srv?.country ?? "UN", interfaceName, interfaceType, peerPublicKey: peer?.publicKey, peerName: peer?.name, comment, } } export const INIT_USERS: AppUser[] = [ { id: "u1", name: "Александр Коротаев", login: "a.korotaev@company.io", email: "a.korotaev@company.io", role: "admin", last: "сейчас", avatar: "АК", active: true, sections: defaultSections("admin"), servers: defaultServers("admin"), bindings: [ bind("b1", "u1", "srv1", "ether1", "ether", "Uplink MSK"), bind("b2", "u1", "srv1", "gre-office-msk", "gre", "Офис MSK"), bind("b3", "u1", "srv1", "gre-datacenter", "gre", "ЦОД"), bind("b4", "u1", "srv1", "wg-msk-spb", "wg", "Overlay SPB", { publicKey: "mockPeerKeyAAAA0123456789", name: "phone-ak" }), bind("b5", "u1", "srv7", "gre-office-msk", "gre", "Офис LAB"), bind("b6", "u1", "srv7", "gre-warehouse", "gre", "Склад"), ], }, { id: "u2", name: "Дмитрий Фёдоров", login: "d.fedorov@company.io", email: "d.fedorov@company.io", role: "operator", last: "2ч назад", avatar: "ДФ", active: true, sections: defaultSections("operator"), servers: defaultServers("operator"), bindings: [ bind("b7", "u2", "srv1", "gre-spb-branch", "gre", "Филиал SPB"), bind("b8", "u2", "srv7", "gre-spb-branch", "gre", "Филиал LAB"), ], }, { id: "u3", name: "Мария Соколова", login: "m.sokolova@company.io", email: "m.sokolova@company.io", role: "viewer", last: "вчера", avatar: "МС", active: true, sections: defaultSections("viewer"), servers: defaultServers("viewer"), bindings: [], }, { id: "u4", name: "Игорь Петров", login: "i.petrov@company.io", email: "i.petrov@company.io", role: "operator", last: "3 дн назад", avatar: "ИП", active: false, sections: defaultSections("operator"), servers: defaultServers("operator"), bindings: [ bind("b9", "u4", "srv1", "gre-retail-01", "gre", "Магазин #1"), ], }, ] export function catalogForServer(serverId: string, users: AppUser[]): CatalogIface[] { const base = MOCK_IFACE_CATALOG[serverId] ?? [] return base.map((iface) => { if (iface.type === "wg") { const peers = (iface.peers ?? []).map((peer) => { const owner = users.find((u) => u.bindings.some((b) => b.serverId === serverId && b.interfaceName === iface.name && (b.peerPublicKey ?? "") === peer.publicKey, ), ) if (!owner) return { ...peer, boundUserId: null, boundUserLogin: null } return { ...peer, boundUserId: owner.id, boundUserLogin: owner.email || owner.login } }) const legacy = users.find((u) => u.bindings.some((b) => b.serverId === serverId && b.interfaceName === iface.name && !(b.peerPublicKey ?? ""), ), ) return { ...iface, boundUserId: legacy?.id ?? null, boundUserLogin: legacy ? (legacy.email || legacy.login) : null, peers, } } const owner = users.find((u) => u.bindings.some((b) => b.serverId === serverId && b.interfaceName === iface.name && !(b.peerPublicKey ?? "")), ) if (!owner) return { ...iface, boundUserId: null, boundUserLogin: null } return { ...iface, boundUserId: owner.id, boundUserLogin: owner.email || owner.login } }) } export function groupBindingsByServer(bindings: InterfaceBinding[]): Array<{ serverId: string serverName: string serverSite: string serverCountry: string types: Array<{ type: InterfaceType; items: InterfaceBinding[] }> }> { const byServer = new Map() for (const b of bindings) { const arr = byServer.get(b.serverId) ?? [] arr.push(b) byServer.set(b.serverId, arr) } return [...byServer.entries()].map(([serverId, items]) => { const first = items[0] const types = IFACE_TYPE_ORDER .map((type) => ({ type, items: items.filter((i) => i.interfaceType === type) })) .filter((g) => g.items.length > 0) return { serverId, serverName: first?.serverName ?? serverId, serverSite: first?.serverSite ?? "—", serverCountry: first?.serverCountry ?? "UN", types, } }) } export type UserAccessWriteKind = "full" | "none" | "partial" export interface UserAccessSummary { sectionsGranted: number sectionsTotal: number serversGranted: number serversTotal: number writeKind: UserAccessWriteKind writeSections: string[] } export function summarizeUserAccess( user: Pick, serversTotal: number, sectionsTotal = ALL_SECTIONS.length, ): UserAccessSummary { const isAdmin = user.role === "admin" const sectionsGranted = isAdmin ? sectionsTotal : user.sections.filter((s) => s.level !== "none").length const serversGranted = isAdmin ? serversTotal : user.servers.filter((s) => s.level !== "none").length const writeSections = isAdmin ? [] : user.sections.filter((s) => s.level === "write").map((s) => s.section) const writeKind: UserAccessWriteKind = isAdmin ? "full" : writeSections.length === 0 ? "none" : "partial" return { sectionsGranted, sectionsTotal, serversGranted, serversTotal, writeKind, writeSections, } }