feat(traffic, users): enhance interface and peer management features
Docker images / prepare-release (push) Successful in 7s
Docker images / backend-image (push) Successful in 1m37s
Docker images / frontend-image (push) Successful in 2m44s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 40s
Docker images / publish-release (push) Successful in 10s

Added support for managing peer information in interface bindings, including new fields for peerPublicKey and peerName in the BoundIfaceTraffic interface. Updated the database schema to include these fields in user_interface_bindings and traffic_samples tables. Enhanced the UI components to display peer details alongside interface names, improving user experience and clarity in the traffic management system. Updated relevant functions and services to handle peer-specific logic, ensuring robust integration across the application.
This commit is contained in:
Denozordec
2026-09-06 20:45:37 +07:00
parent fc161506e7
commit 5884bd8873
19 changed files with 749 additions and 134 deletions
+78 -13
View File
@@ -15,9 +15,32 @@ export interface InterfaceBinding {
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
@@ -43,15 +66,6 @@ export interface AppUser {
bindings: InterfaceBinding[]
}
export interface CatalogIface {
name: string
type: InterfaceType
running: boolean
disabled: boolean
boundUserId: string | null
boundUserLogin: string | null
}
export interface UserServerOption {
id: string
name: string
@@ -138,17 +152,39 @@ export const MOCK_IFACE_CATALOG: Record<string, CatalogIface[]> = {
{ name: "gre-datacenter", type: "gre", running: true, disabled: false, boundUserId: "u1", boundUserLogin: "[email protected]" },
{ name: "gre-spb-branch", type: "gre", running: true, disabled: false, boundUserId: "u2", boundUserLogin: "[email protected]" },
{ name: "gre-retail-01", type: "gre", running: false, disabled: false, boundUserId: "u4", boundUserLogin: "[email protected]" },
{ name: "wg-msk-spb", type: "wg", running: true, disabled: false, boundUserId: "u1", boundUserLogin: "[email protected]" },
{
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: "[email protected]" },
{ 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: "[email protected]" },
{ name: "gre-warehouse", type: "gre", running: false, disabled: false, boundUserId: "u1", boundUserLogin: "[email protected]" },
{ name: "gre-spb-branch", type: "gre", running: true, disabled: false, boundUserId: "u2", boundUserLogin: "[email protected]" },
{ name: "wg-lab", type: "wg", running: true, disabled: false, boundUserId: null, boundUserLogin: null },
{
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<InterfaceBinding, "serverId" | "interfaceName" | "peerPublicKey">): string {
return `${b.serverId}::${b.interfaceName}::${b.peerPublicKey ?? ""}`
}
export function bindingTitle(b: Pick<InterfaceBinding, "interfaceName" | "interfaceType" | "peerName" | "peerPublicKey">): string {
if (b.interfaceType === "wg" && (b.peerName || b.peerPublicKey)) {
return `${b.peerName || "peer"} · ${b.interfaceName}`
}
return b.interfaceName
}
function bind(
id: string,
userId: string,
@@ -156,6 +192,7 @@ function bind(
interfaceName: string,
interfaceType: InterfaceType,
comment: string,
peer?: { publicKey: string; name: string },
): InterfaceBinding {
const srv = servers.find((s) => s.id === serverId)
return {
@@ -167,6 +204,8 @@ function bind(
serverCountry: srv?.country ?? "UN",
interfaceName,
interfaceType,
peerPublicKey: peer?.publicKey,
peerName: peer?.name,
comment,
}
}
@@ -180,7 +219,7 @@ export const INIT_USERS: AppUser[] = [
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"),
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", "Склад"),
],
@@ -213,8 +252,34 @@ export const INIT_USERS: AppUser[] = [
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),
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 }