// Data models shared with the Go backend. // CanvasLocal / CanvasRemote are aliases kept for node components migrated // from the original prototype; they equal Local / Remote. export type CanvasLocal = Local; export type CanvasRemote = Remote; // CanvasLink mirrors the store.Link JSON shape used by the canvas editor. export type CanvasLink = Link; export interface Local { name: string; ip: string; port: number; protocol: string; // tcp | udp | http | https // localOnly: forward stays on this node (loopback kept), no cluster. localOnly?: boolean; // M1 advanced transport knobs (optional; empty/zero = frpc defaults) useEncryption?: boolean; useCompression?: boolean; bandwidthLimit?: string; poolCount?: number; metadatas?: Record; annotations?: Record; // M2 HTTP/HTTPS routing (http/https only) customDomains?: string; // comma-separated subdomain?: string; locations?: string[]; hostHeaderRewrite?: string; httpHeaders?: Record; basicAuthUser?: string; basicAuthPassword?: string; // M3 load balancing group + health check lbGroup?: string; lbGroupKey?: string; healthCheckType?: string; // "" | tcp | http healthCheckPath?: string; healthCheckTimeout?: number; healthCheckMaxFailed?: number; healthCheckInterval?: number; } export interface Remote { name: string; ip: string; port: number; // frps connect port token?: string; url?: string; enabled: boolean; // M1 transport section transportProtocol?: string; // tcp | quic | kcp | websocket transportTls?: boolean; transportPool?: number; transportTlsServerName?: string; // M2: frps vhostHTTPPort vhostHttpPort?: number; // M3: local frpc admin API (webServer) adminAddr?: string; adminPort?: number; adminUser?: string; adminPassword?: string; } export interface Link { id?: number; local: string; remote: string; remotePort: number; offsetX?: number; offsetY?: number; // group: management label for one-click group start/stop on the forwards // page (unrelated to frps load-balancing lbGroup on Local). group?: string; // disabled: per-forward stop flag. A disabled forward is omitted from the // frpc config (local) / left out of the ring topology (cluster), so stop is // per-card and survives canvas re-saves. disabled?: boolean; } export interface CanvasData { locals: Local[]; remotes: Remote[]; links: Link[]; } export interface Settings { autoStartProfiles: boolean; restartOnExit: boolean; restartIntervalSeconds: number; binaryPath?: string; } export interface ProcessStatus { state: string; pid?: number; startTime?: number; restartCount: number; exitCode?: number; err?: string; } export interface ProxyState { name: string; type: string; status: string; // new | wait start | start error | running | check failed | closed err?: string; local_addr?: string; remote_addr?: string; } export interface StatusProfile { name: string; enabled: boolean; process: ProcessStatus; hasProcess: boolean; forwards: { service: string; remotePort: number; localPort?: number }[]; // connState: derived frps connection state (we do not control remote frps) connState?: string; // connected | connecting | failed | not_started | disabled // M3: admin API state adminEnabled?: boolean; proxyStates?: ProxyState[]; groupCount?: number; } export interface StatusResp { version: string; workDir: string; settings: Settings; services: Local[]; remotes: Remote[]; binaryPath: string; profiles: StatusProfile[]; localStatus: LocalStatus[]; // forwards: the forward-centric list rendered as cards on the status page. forwards: ForwardStatus[]; // selfId: this node's ring ID (== SelfAddr), to tell whether a cluster // forward's owner is this node (worker runs here) or a peer. selfId: string; } // ForwardStatus is one forward (link) in the forward-centric status view. // kind distinguishes 本地转发 (localOnly) from 远程转发 (cluster-distributed). export interface ForwardStatus { local: string; remote: string; remotePort: number; localOnly: boolean; kind: "local" | "remote"; ownerId?: string; // remote = topology owner; local = selfId active: boolean; // remote = in topology; local = local worker running disabled: boolean; // per-forward stop flag (true = stopped) group?: string; localIp?: string; localPort?: number; localProto?: string; } export interface LocalTargetStatus { remote: string; remotePort: number; workerState: string; } export interface LocalStatus { local: Local; targets: LocalTargetStatus[]; } export interface BinaryStatus { binaryPath: string; version?: string; } export interface InstallResult { path: string; version: string; } // M6: cluster node registry + binary cache. export interface ClusterNode { addr: string; cache?: string[]; version?: string; } export interface CacheEntry { version: string; path: string; size: number; modTime: number; } export interface ClusterNodesResp { nodes: ClusterNode[]; } export interface CacheResp { cache: CacheEntry[]; removed?: string[]; } // M6 token-ring snapshot (GET /cluster/ring). export interface RingNode { id: string; addr: string; isLeader?: boolean; alive: boolean; load: { memPct: number; netPct: number; forwards?: number }; version?: string; cache?: string[]; lastSeen?: number; } export interface RingTaskInfo { id: string; revoke?: boolean; removeNode?: string; // node-removal command (target rides the token to self-remove) local: { name: string }; remote: { name: string }; link: { remotePort?: number }; } export interface RingTopoEntry { taskId: string; ownerId: string; active: boolean; local: { name: string }; remote: { name: string }; link: { remotePort?: number }; } export interface RingLogEntry { seq: number; node: string; kind: string; at?: number; data?: Record | null; } export interface RingSnapshot { selfId: string; leaderId: string; // 当前 leader(持首令牌 + 判丢失 + 被上家监控) cycle: number; // 周期号(leader 每收令牌回 +1) lastSync: number; // 上次同步时间 (unix sec) roundDelayMs?: number; nodes: RingNode[]; pending: RingTaskInfo[]; topology: RingTopoEntry[]; log: RingLogEntry[]; // nodeKey: this node's cluster admission key. Newcomers must present it to // join via this node (handleClusterJoin verifies). Shown on the cluster page // for the operator to copy; omitted for non-cluster (standalone) nodes. nodeKey?: string; } // ---- Auth / accounts / API keys (M7) ---- export type AccessLevel = 'read' | 'write' | 'admin' | 'superadmin'; // MeResp is the authenticated principal (GET /me). The frontend uses level to // gate the UI: viewer (= auditors) sees read + export controls only. export interface MeResp { type: 'user' | 'service' | 'key'; name: string; level: AccessLevel; userId?: number; } export interface User { id: number; username: string; role: 'admin' | 'viewer' | 'superadmin'; enabled: boolean; system?: boolean; // flag-synced built-in account (UI read-only) createdAt: number; lastLoginAt: number; } export interface ApiKey { id: number; userId: number; prefix: string; label: string; scope: AccessLevel; createdAt: number; lastUsedAt: number; expiresAt?: number; } // ApiKeyCreated is the one-time response from POST /apikeys: the plaintext key // is shown exactly once and never retrievable again. export interface ApiKeyCreated { id: number; userId: number; label: string; scope: AccessLevel; prefix: string; createdAt: number; key: string; } export interface CanvasExportEnvelope { _type: 'webui4frpc-canvas'; version: number; exportedAt: number; exporter: string; canvas: CanvasData; } // Worker-log bundle (GET /cluster/logs/export): one entry per alive ring node. export interface WorkerLogEntry { name: string; remote: string; state: string; lines: string; } export interface NodeLogsResp { node: string; workers: WorkerLogEntry[]; } export interface ClusterWorkerLogNode { id: string; addr: string; ok: boolean; error?: string; logs?: NodeLogsResp; } export interface WorkerLogBundle { _type: 'webui4frpc-worker-logs'; version: number; exportedAt: number; requester: string; nodes: ClusterWorkerLogNode[]; }