first commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import type { AiProviderKind, AiProviderResponse, AiProviderStatus, AiRelayMode } from "../api/types";
|
||||
|
||||
export type AiProviderFilter = AiProviderStatus | "all";
|
||||
export type AiProviderViewState = "api" | "local" | "saving" | "error";
|
||||
|
||||
export interface AiProviderFormState {
|
||||
id: string;
|
||||
name: string;
|
||||
kind: AiProviderKind;
|
||||
baseUrl: string;
|
||||
apiKeyRef: string;
|
||||
modelsText: string;
|
||||
defaultModel: string;
|
||||
relayMode: AiRelayMode;
|
||||
timeoutMs: string;
|
||||
redactionPolicy: string;
|
||||
}
|
||||
|
||||
export interface AiProviderMetrics {
|
||||
total: number;
|
||||
active: number;
|
||||
disabled: number;
|
||||
models: number;
|
||||
}
|
||||
|
||||
export interface AiProviderActionState {
|
||||
providerId: string;
|
||||
label: string;
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export function emptyAiProviderForm(): AiProviderFormState {
|
||||
return {
|
||||
id: "",
|
||||
name: "",
|
||||
kind: "openai-compatible",
|
||||
baseUrl: "https://api.example.test/v1",
|
||||
apiKeyRef: "secret://providers/",
|
||||
modelsText: "gpt-4.1-mini",
|
||||
defaultModel: "gpt-4.1-mini",
|
||||
relayMode: "direct",
|
||||
timeoutMs: "30000",
|
||||
redactionPolicy: "default"
|
||||
};
|
||||
}
|
||||
|
||||
export function aiProviderToForm(provider: AiProviderResponse): AiProviderFormState {
|
||||
return {
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
kind: provider.kind,
|
||||
baseUrl: provider.baseUrl,
|
||||
apiKeyRef: provider.apiKeyRef,
|
||||
modelsText: provider.models.join(", "),
|
||||
defaultModel: provider.defaultModel ?? "",
|
||||
relayMode: provider.relayMode,
|
||||
timeoutMs: String(provider.timeoutMs),
|
||||
redactionPolicy: provider.redactionPolicy
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeAiProviders(providers: AiProviderResponse[]): AiProviderMetrics {
|
||||
return providers.reduce<AiProviderMetrics>(
|
||||
(metrics, provider) => ({
|
||||
total: metrics.total + 1,
|
||||
active: metrics.active + (provider.status === "active" ? 1 : 0),
|
||||
disabled: metrics.disabled + (provider.status === "disabled" ? 1 : 0),
|
||||
models: metrics.models + provider.models.length
|
||||
}),
|
||||
{ total: 0, active: 0, disabled: 0, models: 0 }
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { ComponentType } from "react";
|
||||
|
||||
import type { UserProfileUpdateRequest, UserThemePreferenceRequest, UserThemePreferenceResponse } from "../api/types";
|
||||
import type { CurrentUserView, WorkspaceCapability } from "./workspace";
|
||||
import type { OperationTracker } from "../stores/operations";
|
||||
|
||||
export type PageId = "home" | "servers" | "serverDetail" | "plugins" | "users" | "aiProviders" | "maintenance" | "profileSettings";
|
||||
|
||||
export interface PageParams {
|
||||
serverId?: string;
|
||||
}
|
||||
|
||||
export interface PageRoute {
|
||||
id: PageId;
|
||||
label: string;
|
||||
path: string;
|
||||
hash: string;
|
||||
description: string;
|
||||
requiredCapability: WorkspaceCapability;
|
||||
showInNav: boolean;
|
||||
}
|
||||
|
||||
export interface PageComponentProps {
|
||||
session: CurrentUserView;
|
||||
params: PageParams;
|
||||
operations: OperationTracker;
|
||||
onNavigate: (pageId: PageId, params?: PageParams) => void;
|
||||
onLogout: () => Promise<void>;
|
||||
onProfileSave: (request: UserProfileUpdateRequest) => Promise<CurrentUserView>;
|
||||
onThemePreferenceSave: (request: UserThemePreferenceRequest) => Promise<UserThemePreferenceResponse>;
|
||||
}
|
||||
|
||||
export type PageComponent = ComponentType<PageComponentProps>;
|
||||
|
||||
export interface PageMetric {
|
||||
label: string;
|
||||
value: string;
|
||||
tone: "neutral" | "success" | "warning";
|
||||
}
|
||||
|
||||
export interface ShellSummaryItem {
|
||||
label: string;
|
||||
value: string;
|
||||
detail: string;
|
||||
tone: PageMetric["tone"];
|
||||
}
|
||||
|
||||
export interface ServerInstanceView {
|
||||
id: string;
|
||||
name: string;
|
||||
plugin: string;
|
||||
runEndpoint: string;
|
||||
state: "draft" | "installing" | "ready" | "running" | "stopped" | "failed" | "deleted";
|
||||
pendingJobs: number;
|
||||
latestLog: string;
|
||||
}
|
||||
|
||||
export interface PluginCatalogItem {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
serverType: string;
|
||||
status: "installed" | "disabled" | "invalid" | "updating";
|
||||
permissions: string[];
|
||||
bridgeActions: string[];
|
||||
validation: string;
|
||||
}
|
||||
|
||||
export interface UserAccessView {
|
||||
id: string;
|
||||
displayName: string;
|
||||
status: "active" | "disabled" | "pending";
|
||||
roles: string[];
|
||||
review: string;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# Page Contracts
|
||||
|
||||
## Shared Visual Contract
|
||||
|
||||
All first-party pages inherit the platform_web game-operations style with black-mecha default materials and a selectable magical-girl theme. Page implementations must use shared theme tokens and surface classes so 首页、服务器管理、插件市场、用户管理、AI 提供商管理、系统维护, server details, drawers, dialogs, logs, diffs, plugin controls, and operation history all feel like one console.
|
||||
|
||||
- Major surfaces remain transparent jelly/glass panels with visible background desktop, icy rim light, diamond borders, shine sweeps, and candy-color accents.
|
||||
- Built-in magical desktops and user-uploaded backgrounds render behind readable contrast surfaces.
|
||||
- Global theme ultimate motion is supplied by the shell-level background layer and lightweight global particle DOM layer, not by page-local fixed decoration elements. It must remain theme-specific and low-cost rather than a dense field of tiny rotating particles.
|
||||
- Page-specific work must not introduce opaque card islands, unrelated dark/light themes, marketing-style hero layouts, or one-off decorative systems.
|
||||
- Status, errors, warnings, destructive operations, LLM diff review, and operation/job feedback remain text/icon-visible and traceable.
|
||||
|
||||
## 平台概览(原首页)
|
||||
|
||||
Platform-administrator-only first screen. Shows online/offline server counts, abnormal instance count, run endpoint health, game type distribution, CPU/memory/disk load, LLM provider connectivity, and recent operational signals (faults, failed jobs, provider errors, audit events) that link to the relevant server, plugin, or AI provider context. Each module loads independently with scoped loading/empty/error states.
|
||||
|
||||
## 服务器管理
|
||||
|
||||
Default landing page for server owners and server administrators. Shows searchable/status-filterable server cards with online/offline state, player count, TPS, latency, CPU, memory, and disk usage; pending metric values render as stable placeholders. Provides create server workflow, refresh, and routed server detail pages. Empty states cover no servers, no matching filters, and role-scoped no-access.
|
||||
|
||||
- Create uses installed plugin and run endpoint selections, then renders the returned instance and install job through one visible operation lifecycle.
|
||||
- The page must not display raw run credentials, host paths, direct socket details, or AI provider keys.
|
||||
|
||||
## 服务器详情
|
||||
|
||||
Daily operations hub for one server. Status header shows online state, player count, TPS, latency, CPU/memory/disk, plus confirmed start/stop lifecycle actions. Sections: 概览 (live status cards, warnings), 日志 (level/keyword/time/source filters + log detail drawer with diagnostics), 配置 (edit with reviewable diff before any write job), 插件控制 (controls grouped by plugin, scoped to this server instance, confirmation + lifecycle feedback per action), AI 助手 (LLM suggestions produce recommendation/diff; write jobs require explicit diff confirmation; no raw AI keys reach the frontend), 操作历史 (operation/job IDs, status, timestamps, target, requester, error reasons).
|
||||
|
||||
## 插件市场
|
||||
|
||||
Shows available and installed game management plugins, versions, capabilities, and create-server entry points. Day-to-day plugin actions live in each server's detail page, not here.
|
||||
|
||||
## 用户管理
|
||||
|
||||
Shows users, roles, permissions, status, and audit entry points. Platform administrators only.
|
||||
|
||||
## AI 提供商管理
|
||||
|
||||
Shows configured model providers, base URL, model list, relay mode, status, and test actions. Raw API keys must never be displayed after save. Platform administrators only.
|
||||
|
||||
## 系统维护
|
||||
|
||||
Shows run endpoint health/capacity and audit events. Platform administrators only.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Plugin Page Bridge Contract
|
||||
|
||||
Plugin page runs with safe platform context.
|
||||
|
||||
## Host Context
|
||||
|
||||
- `pluginId`: installed plugin ID.
|
||||
- `routeKey`: current plugin route.
|
||||
- `serverInstanceId`: optional selected server instance.
|
||||
- `themeTokens`: safe theme tokens.
|
||||
- `permissions`: effective plugin permissions.
|
||||
|
||||
## Host APIs
|
||||
|
||||
- `api.request`: platform-scoped request only.
|
||||
- `jobs.dispatch`: authorized job dispatch.
|
||||
- `logs.query`: historical log query and analysis windows.
|
||||
- `artifacts.open`: artifact upload/download references.
|
||||
- `files.request`: scoped file operation requests.
|
||||
- `ai.invoke`: platform-mediated AI invocation.
|
||||
|
||||
## Forbidden
|
||||
|
||||
The host must not expose raw auth storage, AI keys, run credentials, host paths, or storage backend credentials.
|
||||
@@ -0,0 +1,70 @@
|
||||
export type PluginPermission =
|
||||
| "server.create"
|
||||
| "server.read"
|
||||
| "server.lifecycle"
|
||||
| "server.files.read"
|
||||
| "server.files.write"
|
||||
| "server.logs.read"
|
||||
| "server.artifacts.read"
|
||||
| "server.artifacts.write"
|
||||
| "ai.invoke";
|
||||
|
||||
export type PluginBridgeAction =
|
||||
| "server.instances.read"
|
||||
| "jobs.dispatch"
|
||||
| "logs.query"
|
||||
| "artifacts.open"
|
||||
| "files.request"
|
||||
| "ai.invoke";
|
||||
|
||||
export interface PluginPageContract {
|
||||
key: string;
|
||||
title: string;
|
||||
path: string;
|
||||
permissions?: PluginPermission[];
|
||||
bridgeActions?: PluginBridgeAction[];
|
||||
}
|
||||
|
||||
export interface PluginBridgeManifestContract {
|
||||
id: string;
|
||||
declaredPermissions: PluginPermission[];
|
||||
bridgeActions: PluginBridgeAction[];
|
||||
pages: PluginPageContract[];
|
||||
aiPurposes?: string[];
|
||||
}
|
||||
|
||||
export interface PluginBridgeThemeTokens {
|
||||
colorScheme: "light" | "dark";
|
||||
accentColor: string;
|
||||
}
|
||||
|
||||
export interface PluginBridgeHostContext {
|
||||
pluginId: string;
|
||||
routeKey: string;
|
||||
serverInstanceId?: string;
|
||||
themeTokens: PluginBridgeThemeTokens;
|
||||
permissions: PluginPermission[];
|
||||
bridgeActions: PluginBridgeAction[];
|
||||
aiPurposes: string[];
|
||||
}
|
||||
|
||||
export interface PluginBridgeExecuteEnvelope {
|
||||
requestId: string;
|
||||
action: PluginBridgeAction;
|
||||
aiPurpose?: string;
|
||||
payload?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface PluginBridgeSafeError {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: string[];
|
||||
}
|
||||
|
||||
export interface PluginBridgeExecutionResult {
|
||||
requestId: string;
|
||||
action: PluginBridgeAction;
|
||||
status: "ok" | "queued" | "denied" | "unsupported" | "cancelled" | "error" | string;
|
||||
result?: Record<string, string>;
|
||||
error?: PluginBridgeSafeError;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import type {
|
||||
GamePluginResponse,
|
||||
JobResponse,
|
||||
RunEndpointResponse,
|
||||
ServerInstanceResponse,
|
||||
ServerInstanceState
|
||||
} from "../api/types";
|
||||
|
||||
export type ServerWorkflowViewState = "api" | "local" | "saving";
|
||||
export type ServerLifecycleActionLabel = "create" | "start" | "stop" | "refresh";
|
||||
|
||||
export interface ServerCreateFormState {
|
||||
id: string;
|
||||
name: string;
|
||||
pluginId: string;
|
||||
runEndpointId: string;
|
||||
}
|
||||
|
||||
export interface ServerWorkflowActionState {
|
||||
label: ServerLifecycleActionLabel;
|
||||
serverInstanceId?: string;
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ServerManagementSummary {
|
||||
total: number;
|
||||
running: number;
|
||||
pendingJobs: number;
|
||||
failed: number;
|
||||
}
|
||||
|
||||
export const emptyServerCreateForm: ServerCreateFormState = {
|
||||
id: "",
|
||||
name: "",
|
||||
pluginId: "",
|
||||
runEndpointId: ""
|
||||
};
|
||||
|
||||
export function summarizeServerManagement(instances: ServerInstanceResponse[], jobs: JobResponse[]): ServerManagementSummary {
|
||||
return {
|
||||
total: instances.length,
|
||||
running: instances.filter((instance) => instance.state === "running").length,
|
||||
pendingJobs: jobs.filter((job) => isPendingJobState(job.state)).length,
|
||||
failed: instances.filter((instance) => instance.state === "failed").length
|
||||
};
|
||||
}
|
||||
|
||||
export function pendingJobsForServer(jobs: JobResponse[], serverInstanceId: string): JobResponse[] {
|
||||
return jobs.filter((job) => job.serverInstanceId === serverInstanceId && isPendingJobState(job.state));
|
||||
}
|
||||
|
||||
export function pluginLabel(plugin: GamePluginResponse | undefined, pluginId: string): string {
|
||||
if (!plugin) {
|
||||
return pluginId;
|
||||
}
|
||||
return plugin.serverDisplayName || plugin.name || plugin.id;
|
||||
}
|
||||
|
||||
export function endpointLabel(endpoint: RunEndpointResponse | undefined, runEndpointId: string): string {
|
||||
if (!endpoint) {
|
||||
return runEndpointId;
|
||||
}
|
||||
return endpoint.displayName || endpoint.id;
|
||||
}
|
||||
|
||||
export function canStartServer(state: ServerInstanceState): boolean {
|
||||
return state === "ready" || state === "stopped";
|
||||
}
|
||||
|
||||
export function canStopServer(state: ServerInstanceState): boolean {
|
||||
return state === "running";
|
||||
}
|
||||
|
||||
export function isPendingJobState(state: JobResponse["state"]): boolean {
|
||||
return state === "queued" || state === "accepted" || state === "running";
|
||||
}
|
||||
|
||||
export function defaultServerCreateForm(plugins: GamePluginResponse[], endpoints: RunEndpointResponse[]): ServerCreateFormState {
|
||||
return {
|
||||
...emptyServerCreateForm,
|
||||
pluginId: plugins[0]?.id ?? "",
|
||||
runEndpointId: endpoints[0]?.id ?? ""
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { PluginCatalogItem, ServerInstanceView, ShellSummaryItem, UserAccessView } from "./page";
|
||||
|
||||
export const shellSummary: ShellSummaryItem[] = [
|
||||
{ label: "服务器实例", value: "2", detail: "1 个运行中,1 个待配置", tone: "success" },
|
||||
{ label: "运行节点", value: "1", detail: "本地执行器在线", tone: "success" },
|
||||
{ label: "插件", value: "2", detail: "桥接能力已就绪", tone: "success" },
|
||||
{ label: "访问审核", value: "1", detail: "待确认角色变更", tone: "warning" }
|
||||
];
|
||||
|
||||
export const serverInstances: ServerInstanceView[] = [
|
||||
{
|
||||
id: "server-example-1",
|
||||
name: "Example Survival #1",
|
||||
plugin: "game.example",
|
||||
runEndpoint: "run-local",
|
||||
state: "running",
|
||||
pendingJobs: 0,
|
||||
latestLog: "stdout seq 1842 acknowledged"
|
||||
},
|
||||
{
|
||||
id: "server-example-2",
|
||||
name: "Example Creative #2",
|
||||
plugin: "game.example",
|
||||
runEndpoint: "run-local",
|
||||
state: "draft",
|
||||
pendingJobs: 1,
|
||||
latestLog: "waiting for create workflow"
|
||||
}
|
||||
];
|
||||
|
||||
export const pluginCatalog: PluginCatalogItem[] = [
|
||||
{
|
||||
id: "game.example",
|
||||
name: "Example Server",
|
||||
version: "0.1.0",
|
||||
serverType: "example",
|
||||
status: "installed",
|
||||
permissions: ["server.read", "server.lifecycle", "server.logs.read", "ai.invoke"],
|
||||
bridgeActions: ["server.instances.read", "logs.query", "ai.invoke"],
|
||||
validation: "manifest validated"
|
||||
},
|
||||
{
|
||||
id: "game.scum",
|
||||
name: "SCUM Server",
|
||||
version: "planned",
|
||||
serverType: "scum",
|
||||
status: "disabled",
|
||||
permissions: ["server.read"],
|
||||
bridgeActions: ["server.instances.read"],
|
||||
validation: "pending proof plugin"
|
||||
}
|
||||
];
|
||||
|
||||
export const userAccess: UserAccessView[] = [
|
||||
{
|
||||
id: "user-admin",
|
||||
displayName: "Operator",
|
||||
status: "active",
|
||||
roles: ["admin", "operator"],
|
||||
review: "baseline owner"
|
||||
},
|
||||
{
|
||||
id: "user-review",
|
||||
displayName: "Plugin Reviewer",
|
||||
status: "pending",
|
||||
roles: ["plugin-reviewer"],
|
||||
review: "needs approval"
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,192 @@
|
||||
import type { JobResponse, ServerInstanceResponse, ServerMetricsResponse, UserContactProfile, UserStatus, UserThemePreferenceResponse } from "../api/types";
|
||||
|
||||
export type WorkspaceRole = "platformAdmin" | "serverOwner" | "serverAdmin";
|
||||
|
||||
export type WorkspaceCapability =
|
||||
| "profile.settings.manage"
|
||||
| "platform.overview.read"
|
||||
| "servers.read"
|
||||
| "servers.manage"
|
||||
| "plugins.market.read"
|
||||
| "users.manage"
|
||||
| "aiProviders.manage"
|
||||
| "system.maintenance";
|
||||
|
||||
export interface CurrentUserView {
|
||||
id: string;
|
||||
displayName: string;
|
||||
email?: string;
|
||||
status: UserStatus;
|
||||
roles: WorkspaceRole[];
|
||||
capabilities: WorkspaceCapability[];
|
||||
profile: UserContactProfile;
|
||||
themePreference?: UserThemePreferenceResponse;
|
||||
source: "api" | "local";
|
||||
}
|
||||
|
||||
const roleCapabilities: Record<WorkspaceRole, WorkspaceCapability[]> = {
|
||||
platformAdmin: [
|
||||
"profile.settings.manage",
|
||||
"platform.overview.read",
|
||||
"servers.read",
|
||||
"servers.manage",
|
||||
"plugins.market.read",
|
||||
"users.manage",
|
||||
"aiProviders.manage",
|
||||
"system.maintenance"
|
||||
],
|
||||
serverOwner: ["profile.settings.manage", "servers.read", "servers.manage"],
|
||||
serverAdmin: ["profile.settings.manage", "servers.read", "servers.manage"]
|
||||
};
|
||||
|
||||
export function capabilitiesForRoles(roles: WorkspaceRole[]): WorkspaceCapability[] {
|
||||
const set = new Set<WorkspaceCapability>();
|
||||
for (const role of roles) {
|
||||
for (const capability of roleCapabilities[role] ?? []) {
|
||||
set.add(capability);
|
||||
}
|
||||
}
|
||||
return [...set];
|
||||
}
|
||||
|
||||
export function isPlatformAdmin(user: CurrentUserView): boolean {
|
||||
return user.roles.includes("platformAdmin");
|
||||
}
|
||||
|
||||
export function rolesFromBackendRoles(roles: string[]): WorkspaceRole[] {
|
||||
const mapped = new Set<WorkspaceRole>();
|
||||
for (const role of roles) {
|
||||
const normalized = role.toLowerCase();
|
||||
if (normalized === "admin" || normalized === "platform-admin" || normalized === "platformadmin") {
|
||||
mapped.add("platformAdmin");
|
||||
}
|
||||
if (normalized === "server-owner" || normalized === "owner") {
|
||||
mapped.add("serverOwner");
|
||||
}
|
||||
if (normalized === "server-admin" || normalized === "operator") {
|
||||
mapped.add("serverAdmin");
|
||||
}
|
||||
}
|
||||
return mapped.size > 0 ? [...mapped] : ["serverAdmin"];
|
||||
}
|
||||
|
||||
export type OperationStatus = "pending" | "succeeded" | "failed";
|
||||
|
||||
export interface OperationRecord {
|
||||
id: string;
|
||||
intent: string;
|
||||
targetKind: "server" | "plugin" | "config" | "llm" | "platform";
|
||||
targetId: string;
|
||||
requester: string;
|
||||
status: OperationStatus;
|
||||
jobId?: string;
|
||||
jobState?: JobResponse["state"];
|
||||
message?: string;
|
||||
errorReason?: string;
|
||||
diagnosticId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ServerCardView {
|
||||
instance: ServerInstanceResponse;
|
||||
metrics?: ServerMetricsResponse;
|
||||
pendingJobs: number;
|
||||
}
|
||||
|
||||
export type ServerStatusFilter = "all" | "online" | "offline" | "attention";
|
||||
|
||||
export function serverIsOnline(state: ServerInstanceResponse["state"]): boolean {
|
||||
return state === "running";
|
||||
}
|
||||
|
||||
export function serverNeedsAttention(state: ServerInstanceResponse["state"]): boolean {
|
||||
return state === "failed" || state === "installing";
|
||||
}
|
||||
|
||||
export function filterServerCards(cards: ServerCardView[], keyword: string, status: ServerStatusFilter): ServerCardView[] {
|
||||
const query = keyword.trim().toLowerCase();
|
||||
return cards.filter((card) => {
|
||||
if (query && !`${card.instance.name} ${card.instance.id} ${card.instance.pluginId}`.toLowerCase().includes(query)) {
|
||||
return false;
|
||||
}
|
||||
if (status === "online") {
|
||||
return serverIsOnline(card.instance.state);
|
||||
}
|
||||
if (status === "offline") {
|
||||
return !serverIsOnline(card.instance.state);
|
||||
}
|
||||
if (status === "attention") {
|
||||
return serverNeedsAttention(card.instance.state);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export interface GameTypeDistributionEntry {
|
||||
serverType: string;
|
||||
label: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface PlatformOverviewSignal {
|
||||
id: string;
|
||||
kind: "log" | "fault" | "plugin" | "aiProvider" | "job";
|
||||
summary: string;
|
||||
detail: string;
|
||||
targetPage: "servers" | "plugins" | "aiProviders" | "maintenance";
|
||||
targetId?: string;
|
||||
tone: "info" | "warning" | "error";
|
||||
at: string;
|
||||
}
|
||||
|
||||
export type ServerDetailSection = "overview" | "logs" | "config" | "plugins" | "llm" | "history";
|
||||
|
||||
export const serverDetailSections: Array<{ id: ServerDetailSection; label: string }> = [
|
||||
{ id: "overview", label: "概览" },
|
||||
{ id: "logs", label: "日志" },
|
||||
{ id: "config", label: "配置" },
|
||||
{ id: "plugins", label: "插件控制" },
|
||||
{ id: "llm", label: "AI 助手" },
|
||||
{ id: "history", label: "操作历史" }
|
||||
];
|
||||
|
||||
export interface PluginControlDescriptor {
|
||||
key: string;
|
||||
label: string;
|
||||
description: string;
|
||||
capability: string;
|
||||
lifecycleAction?: "start" | "stop";
|
||||
dangerous: boolean;
|
||||
}
|
||||
|
||||
export interface PluginControlGroupView {
|
||||
pluginId: string;
|
||||
pluginName: string;
|
||||
version: string;
|
||||
status: string;
|
||||
controls: PluginControlDescriptor[];
|
||||
}
|
||||
|
||||
export interface DiffLine {
|
||||
kind: "same" | "added" | "removed";
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface ConfigDiffView {
|
||||
serverInstanceId: string;
|
||||
configVersion?: number;
|
||||
key?: string;
|
||||
source?: string;
|
||||
summary: string;
|
||||
lines: DiffLine[];
|
||||
nextContent: string;
|
||||
proposedContentInputRef?: string;
|
||||
}
|
||||
|
||||
export interface LlmSuggestionView {
|
||||
serverInstanceId: string;
|
||||
source: "api" | "local";
|
||||
recommendation: string;
|
||||
diff?: ConfigDiffView;
|
||||
}
|
||||
Reference in New Issue
Block a user