fix: migrate code queue control plane to k3s

This commit is contained in:
Codex
2026-05-16 12:01:09 +00:00
parent 83ed99f659
commit 111ec9f1eb
40 changed files with 419 additions and 366 deletions
File diff suppressed because one or more lines are too long
+34 -34
View File
@@ -6291,11 +6291,11 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
background: rgba(255,255,255,0.3);
}
.v3s-page {
.k3s-page {
display: grid;
gap: 14px;
}
.v3s-hero-panel {
.k3s-hero-panel {
position: relative;
overflow: hidden;
border-color: rgba(215, 161, 58, 0.34);
@@ -6304,14 +6304,14 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
radial-gradient(circle at 12% 84%, rgba(78, 183, 168, 0.13), transparent 34%),
linear-gradient(135deg, rgba(255,255,255,0.045), rgba(255,255,255,0.012));
}
.v3s-hero {
.k3s-hero {
display: grid;
grid-template-columns: 128px minmax(0, 1fr);
align-items: center;
gap: 18px;
margin-bottom: 16px;
}
.v3s-orb {
.k3s-orb {
position: relative;
display: grid;
place-items: center;
@@ -6328,20 +6328,20 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
color: var(--accent);
font-weight: 800;
}
.v3s-orb::before,
.v3s-orb::after {
.k3s-orb::before,
.k3s-orb::after {
content: "";
position: absolute;
inset: 12px;
border: 1px dashed rgba(78,183,168,0.38);
border-radius: inherit;
}
.v3s-orb::after {
.k3s-orb::after {
inset: 28px;
border-style: solid;
border-color: rgba(255,255,255,0.12);
}
.v3s-hero-copy h2 {
.k3s-hero-copy h2 {
max-width: 860px;
margin: 0 0 8px;
color: var(--text);
@@ -6350,14 +6350,14 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
letter-spacing: 0.08em;
text-transform: none;
}
.v3s-route-strip {
.k3s-route-strip {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 12px;
}
.v3s-route-strip span {
.k3s-route-strip span {
padding: 5px 8px;
border: 1px solid rgba(215,161,58,0.5);
border-radius: 999px;
@@ -6367,7 +6367,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
font-weight: 800;
letter-spacing: 0.16em;
}
.v3s-route-strip code {
.k3s-route-strip code {
max-width: 100%;
padding: 5px 8px;
border: 1px solid var(--line-soft);
@@ -6377,79 +6377,79 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
overflow-wrap: anywhere;
white-space: normal;
}
.v3s-control-plane-grid {
.k3s-control-plane-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
margin-top: 12px;
}
.v3s-control-plane-card {
.k3s-control-plane-card {
min-height: 96px;
padding: 12px;
border: 1px solid rgba(255,255,255,0.1);
background: linear-gradient(135deg, rgba(0,0,0,0.22), rgba(78,183,168,0.07));
}
.v3s-control-plane-card span {
.k3s-control-plane-card span {
color: var(--muted);
font-size: 10px;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.v3s-control-plane-card strong {
.k3s-control-plane-card strong {
display: block;
margin-top: 8px;
color: var(--text);
font-size: 17px;
letter-spacing: 0.08em;
}
.v3s-control-plane-card p {
.k3s-control-plane-card p {
margin: 8px 0 0;
color: var(--muted);
font-size: 12px;
line-height: 1.45;
overflow-wrap: anywhere;
}
.v3s-service-panel {
.k3s-service-panel {
border-color: rgba(78,183,168,0.25);
}
.v3s-service-summary {
.k3s-service-summary {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
margin-bottom: 12px;
}
.v3s-service-summary > div,
.v3s-instance-card {
.k3s-service-summary > div,
.k3s-instance-card {
border: 1px solid var(--line-soft);
background: rgba(0,0,0,0.18);
}
.v3s-service-summary > div {
.k3s-service-summary > div {
display: grid;
gap: 6px;
min-height: 66px;
padding: 10px;
}
.v3s-service-summary span,
.v3s-kv dt {
.k3s-service-summary span,
.k3s-kv dt {
color: var(--muted);
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.v3s-service-summary strong {
.k3s-service-summary strong {
color: var(--text);
font-size: 18px;
}
.v3s-instance-grid {
.k3s-instance-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 10px;
}
.v3s-instance-card {
.k3s-instance-card {
padding: 12px;
border-left: 3px solid rgba(78,183,168,0.7);
}
.v3s-instance-role {
.k3s-instance-role {
display: flex;
align-items: center;
justify-content: space-between;
@@ -6459,34 +6459,34 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); }
font-size: 11px;
letter-spacing: 0.15em;
}
.v3s-kv {
.k3s-kv {
display: grid;
grid-template-columns: 76px minmax(0, 1fr);
gap: 6px 10px;
margin: 0;
}
.v3s-kv dd {
.k3s-kv dd {
min-width: 0;
margin: 0;
color: var(--text);
overflow-wrap: anywhere;
}
.v3s-kv code {
.k3s-kv code {
white-space: normal;
}
@media (max-width: 780px) {
.v3s-hero {
.k3s-hero {
grid-template-columns: 1fr;
}
.v3s-orb {
.k3s-orb {
width: 96px;
height: 96px;
}
.v3s-service-summary {
.k3s-service-summary {
grid-template-columns: 1fr 1fr;
}
.v3s-control-plane-grid {
.k3s-control-plane-grid {
grid-template-columns: 1fr;
}
}
+12 -12
View File
@@ -14,7 +14,7 @@ import { PipelinePage } from "./pipeline";
import { ProjectManagerPage } from "./project-manager";
import { TodoNotePage } from "./todo-note";
import { TopStatusBar } from "./top-status";
import { V3sCtlPage } from "./v3sctl";
import { K3sCtlPage } from "./k3sctl";
import { LoadingTitle } from "./loading-indicator";
import { errorMessage, requestJson } from "./unidesk-error";
import { UniDeskErrorBanner } from "./unidesk-error-banner";
@@ -46,21 +46,21 @@ const fastCodeQueueService = {
name: "Code Queue",
providerId: "D601",
description: "Code Queue",
repository: { containerName: "v3s:code-queue" },
repository: { containerName: "k3s:code-queue" },
backend: {
nodeBaseUrl: "v3s://code-queue",
nodeBindHost: "v3s://unidesk/code-queue",
nodeBaseUrl: "k3s://code-queue",
nodeBindHost: "k3s://unidesk/code-queue",
nodePort: 4222,
proxyMode: "v3sctl-adapter-http",
proxyMode: "k3sctl-adapter-http",
public: false,
},
deployment: {
mode: "v3sctl-managed",
adapterServiceId: "v3sctl-adapter",
v3sServiceId: "code-queue",
mode: "k3sctl-managed",
adapterServiceId: "k3sctl-adapter",
k3sServiceId: "code-queue",
},
runtime: {
orchestrator: "v3sctl",
orchestrator: "k3sctl",
providerStatus: "loading",
providerName: "D601",
},
@@ -1649,7 +1649,7 @@ function MicroserviceCatalogPage({ microservices, onRaw, onNavigate }: AnyRecord
service.id === "claudeqq" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "claudeqq"), "data-testid": "open-claudeqq-button" }, "打开") : null,
service.id === "baidu-netdisk" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "baidu-netdisk"), "data-testid": "open-baidu-netdisk-button" }, "打开") : null,
service.id === "oa-event-flow" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "oa-event-flow"), "data-testid": "open-oa-event-flow-button" }, "打开") : null,
service.id === "v3sctl-adapter" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "v3sctl"), "data-testid": "open-v3sctl-button" }, "打开") : null,
service.id === "k3sctl-adapter" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "k3sctl"), "data-testid": "open-k3sctl-button" }, "打开") : null,
service.id === "code-queue" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "code-queue"), "data-testid": "open-code-queue-button" }, "打开") : null,
service.id === "mdtodo" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "mdtodo"), "data-testid": "open-mdtodo-button" }, "打开") : null,
service.id === "project-manager" ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "project-manager"), "data-testid": "open-project-manager-button" }, "打开") : null,
@@ -2157,7 +2157,7 @@ function WorkArea({ activeModule, activeTab, data, session, refresh, onRaw, onNa
if (activeModule === "apps" && activeTab === "baidu-netdisk") return h(BaiduNetdiskPage, { microservices: data.microservices, onRaw, apiBaseUrl: cfg.apiBaseUrl });
if (activeModule === "apps" && activeTab === "filebrowser") return h(FileBrowserPage, { microservices: data.microservices, onRaw, apiBaseUrl: cfg.apiBaseUrl });
if (activeModule === "apps" && activeTab === "oa-event-flow") return h(OaEventFlowPage, { microservices: data.microservices, onRaw, apiBaseUrl: cfg.apiBaseUrl });
if (activeModule === "apps" && activeTab === "v3sctl") return h(V3sCtlPage, { microservices: data.microservices, onRaw, apiBaseUrl: cfg.apiBaseUrl, onNavigate });
if (activeModule === "apps" && activeTab === "k3sctl") return h(K3sCtlPage, { microservices: data.microservices, onRaw, apiBaseUrl: cfg.apiBaseUrl, onNavigate });
if (activeModule === "apps" && activeTab === "code-queue") return h(CodeQueuePage, { microservices: data.microservices, onRaw, apiBaseUrl: cfg.apiBaseUrl, initialTasksData: initialCodeQueueOverview });
if (activeModule === "apps" && activeTab === "mdtodo") return h(MdtodoPage, { microservices: data.microservices, onRaw, apiBaseUrl: cfg.apiBaseUrl });
if (activeModule === "apps" && activeTab === "project-manager") return h(ProjectManagerPage, { microservices: data.microservices, onRaw, apiBaseUrl: cfg.apiBaseUrl });
@@ -2188,7 +2188,7 @@ function Shell({ session, onLogout }: AnyRecord) {
: microservices;
const effectiveData = effectiveMicroservices === microservices ? data : { ...data, microservices: effectiveMicroservices };
const activeService = activeModule === "apps"
? effectiveMicroservices.find((service: any) => String(service?.id || "") === (activeTab === "v3sctl" ? "v3sctl-adapter" : activeTab))
? effectiveMicroservices.find((service: any) => String(service?.id || "") === (activeTab === "k3sctl" ? "k3sctl-adapter" : activeTab))
: null;
const activeServiceRuntime = activeService ? microserviceRuntime(activeService) : {};
const activeTabTitle = module.tabs.find((tab: any) => tab.id === activeTab)?.label || activeTab;
@@ -30,22 +30,22 @@ const standaloneCodeQueueService = {
id: "code-queue",
name: "Code Queue",
providerId: "D601",
description: "Code Queue 独立入口,使用 v3sctl-adapter 单一路径访问 active service。",
repository: { containerName: "v3s:code-queue" },
description: "Code Queue 独立入口,使用 k3sctl-adapter 单一路径访问 active service。",
repository: { containerName: "k3s:code-queue" },
backend: {
nodeBaseUrl: "v3s://code-queue",
nodeBindHost: "v3s://unidesk/code-queue",
nodeBaseUrl: "k3s://code-queue",
nodeBindHost: "k3s://unidesk/code-queue",
nodePort: 4222,
proxyMode: "v3sctl-adapter-http",
proxyMode: "k3sctl-adapter-http",
public: false,
},
deployment: {
mode: "v3sctl-managed",
adapterServiceId: "v3sctl-adapter",
v3sServiceId: "code-queue",
mode: "k3sctl-managed",
adapterServiceId: "k3sctl-adapter",
k3sServiceId: "code-queue",
},
runtime: {
orchestrator: "v3sctl",
orchestrator: "k3sctl",
providerStatus: "online",
providerName: "D601",
},
@@ -57,11 +57,11 @@ function microserviceRuntime(service: any): AnyRecord {
}
function proxy(apiBaseUrl: string, path: string): string {
return `${apiBaseUrl}/microservices/v3sctl-adapter/proxy${path}`;
return `${apiBaseUrl}/microservices/k3sctl-adapter/proxy${path}`;
}
function findAdapter(microservices: any[]): any | null {
return microservices.find((service) => String(service?.id || "") === "v3sctl-adapter") || null;
return microservices.find((service) => String(service?.id || "") === "k3sctl-adapter") || null;
}
function instanceTone(instance: any): string {
@@ -90,16 +90,16 @@ function primaryInstance(services: any[]): string {
}
function renderInstanceCard(instance: any) {
return h("article", { key: instance?.id || instance?.nodeId, className: "v3s-instance-card" },
return h("article", { key: instance?.id || instance?.nodeId, className: "k3s-instance-card" },
h("div", { className: "node-card-head" },
h("strong", null, instance?.nodeId || instance?.id || "--"),
h(StatusBadge, { status: instanceTone(instance) }, instance?.healthy ? "HEALTHY" : "DEGRADED"),
),
h("div", { className: "v3s-instance-role" },
h("div", { className: "k3s-instance-role" },
h("span", null, String(instance?.role || "worker").toUpperCase()),
h("code", null, instance?.id || "--"),
),
h("dl", { className: "v3s-kv" },
h("dl", { className: "k3s-kv" },
h("dt", null, "Base URL"), h("dd", null, h("code", null, instance?.baseUrl || "--")),
h("dt", null, "Proxy"), h("dd", null, instance?.proxyMode || "--"),
h("dt", null, "Health"), h("dd", null, `${instance?.upstreamStatus ?? "--"} / ${instance?.status || "unknown"}`),
@@ -114,23 +114,23 @@ function renderManagedService(service: any, onRaw: any) {
return h(Panel, {
key: service?.id || "service",
title: service?.id || "managed-service",
eyebrow: `${service?.namespace || "unidesk"} / v3s managed service`,
className: "v3s-service-panel",
actions: h(RawButton, { title: `v3s service ${service?.id || ""}`, data: service, onOpen: onRaw, testId: `raw-v3s-service-${service?.id || "unknown"}` }),
eyebrow: `${service?.namespace || "unidesk"} / k3s managed service`,
className: "k3s-service-panel",
actions: h(RawButton, { title: `k3s service ${service?.id || ""}`, data: service, onOpen: onRaw, testId: `raw-k3s-service-${service?.id || "unknown"}` }),
},
h("div", { className: "v3s-service-summary" },
h("div", { className: "k3s-service-summary" },
h("div", null, h("span", null, "状态"), h(StatusBadge, { status: serviceTone(service) }, service?.status || "unknown")),
h("div", null, h("span", null, "Active"), h("strong", null, service?.activeInstanceId || "--")),
h("div", null, h("span", null, "Single Writer"), h("strong", null, fmtBool(service?.singleWriter))),
h("div", null, h("span", null, "Active Health"), h("strong", null, active?.upstreamStatus ?? "--")),
),
instances.length === 0
? h(EmptyState, { title: "暂无 v3s 实例", text: "adapter 没有返回该服务的 endpoint 列表" })
: h("div", { className: "v3s-instance-grid" }, instances.map(renderInstanceCard)),
? h(EmptyState, { title: "暂无 k3s 实例", text: "adapter 没有返回该服务的 endpoint 列表" })
: h("div", { className: "k3s-instance-grid" }, instances.map(renderInstanceCard)),
);
}
export function V3sCtlPage({ microservices, onRaw, apiBaseUrl, onNavigate }: AnyRecord) {
export function K3sCtlPage({ microservices, onRaw, apiBaseUrl, onNavigate }: AnyRecord) {
const adapter = findAdapter(Array.isArray(microservices) ? microservices : []);
const runtime = microserviceRuntime(adapter);
const [state, setState] = useState({ loading: false, error: "", data: null as any, refreshedAt: null as Date | null });
@@ -141,7 +141,7 @@ export function V3sCtlPage({ microservices, onRaw, apiBaseUrl, onNavigate }: Any
const data = await requestJson(proxy(apiBaseUrl, "/api/control-plane"));
setState({ loading: false, error: "", data, refreshedAt: new Date() });
} catch (err) {
setState((previous: any) => ({ ...previous, loading: false, error: errorMessage(err, "加载 v3s 控制平面失败") }));
setState((previous: any) => ({ ...previous, loading: false, error: errorMessage(err, "加载 k3s 控制平面失败") }));
}
}
@@ -157,50 +157,50 @@ export function V3sCtlPage({ microservices, onRaw, apiBaseUrl, onNavigate }: Any
const kubeProxy = objectRecord(state.data?.kubeApiProxy);
const manifestPaths = asArray(state.data?.manifestPaths).map((item) => String(item));
if (!adapter) return h(EmptyState, { title: "v3sctl-adapter 未登记", text: "请在 config.json 的 microservices 中登记 id=v3sctl-adapter,并通过该微服务连接 v3s 控制平面。" });
if (!adapter) return h(EmptyState, { title: "k3sctl-adapter 未登记", text: "请在 config.json 的 microservices 中登记 id=k3sctl-adapter,并通过该微服务连接 k3s 控制平面。" });
return h("div", { className: "v3s-page", "data-testid": "v3sctl-page" },
return h("div", { className: "k3s-page", "data-testid": "k3sctl-page" },
h(Panel, {
title: "V3S Control Plane",
eyebrow: "Managed by v3sctl-adapter",
className: "v3s-hero-panel",
title: "K3S Control Plane",
eyebrow: "Managed by k3sctl-adapter",
className: "k3s-hero-panel",
loading: state.loading,
actions: h(React.Fragment, null,
h("button", { type: "button", className: "ghost-btn", onClick: load, disabled: state.loading, "data-testid": "v3s-refresh-button" }, state.loading ? "刷新中" : "刷新"),
onNavigate ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "code-queue"), "data-testid": "v3s-open-code-queue" }, "打开 Code Queue") : null,
h(RawButton, { title: "v3sctl-adapter microservice", data: adapter, onOpen: onRaw, testId: "raw-v3s-adapter" }),
h("button", { type: "button", className: "ghost-btn", onClick: load, disabled: state.loading, "data-testid": "k3s-refresh-button" }, state.loading ? "刷新中" : "刷新"),
onNavigate ? h("button", { type: "button", className: "ghost-btn", onClick: () => onNavigate("apps", "code-queue"), "data-testid": "k3s-open-code-queue" }, "打开 Code Queue") : null,
h(RawButton, { title: "k3sctl-adapter microservice", data: adapter, onOpen: onRaw, testId: "raw-k3s-adapter" }),
),
},
h("div", { className: "v3s-hero" },
h("div", { className: "v3s-orb", "aria-hidden": "true" }, h("span", null, "v3s")),
h("div", { className: "v3s-hero-copy" },
h("div", { className: "k3s-hero" },
h("div", { className: "k3s-orb", "aria-hidden": "true" }, h("span", null, "k3s")),
h("div", { className: "k3s-hero-copy" },
h("p", { className: "eyebrow" }, "D601 control plane / D518 managed node"),
h("h2", null, "UniDesk 只管理 adapter;业务微服务交给 v3s 标准服务路由"),
h("p", { className: "muted paragraph" }, "Code Queue 的前端/API 请求进入 v3sctl-adapter,再由 adapter 转发到 v3s active service。provider-gateway 只用于维护 adapter 和节点诊断,不再直接管理 Code Queue 容器。"),
h("div", { className: "v3s-route-strip" },
h("h2", null, "UniDesk 只管理 adapter;业务微服务交给 k3s 标准服务路由"),
h("p", { className: "muted paragraph" }, "Code Queue 的前端/API 请求进入 k3sctl-adapter,再由 adapter 转发到 k3s active service。provider-gateway 只用于维护 adapter 和节点诊断,不再直接管理 Code Queue 容器。"),
h("div", { className: "k3s-route-strip" },
h("span", null, "NO FALLBACK"),
h("code", null, state.data?.runtimePath || "frontend -> backend-core -> v3sctl-adapter"),
h("code", null, state.data?.runtimePath || "frontend -> backend-core -> k3sctl-adapter"),
),
),
),
h("div", { className: "metric-grid" },
h(MetricCard, { label: "控制面", value: state.data?.clusterId || "D601", hint: `adapter ${runtime.providerStatus || "unknown"}`, tone: runtime.providerStatus === "online" ? "ok" : "warn" }),
h(MetricCard, { label: "代管服务", value: services.length, hint: `${healthyServices}/${services.length || 0} healthy`, tone: healthyServices === services.length && services.length > 0 ? "ok" : "warn" }),
h(MetricCard, { label: "节点", value: nodes.join(" / ") || "--", hint: "expected v3s nodes" }),
h(MetricCard, { label: "节点", value: nodes.join(" / ") || "--", hint: "expected k3s nodes" }),
h(MetricCard, { label: "实例", value: `${healthyInstances}/${totalInstances}`, hint: `active ${primary}`, tone: healthyInstances === totalInstances && totalInstances > 0 ? "ok" : "warn" }),
),
h("div", { className: "v3s-control-plane-grid" },
h("article", { className: "v3s-control-plane-card" },
h("div", { className: "k3s-control-plane-grid" },
h("article", { className: "k3s-control-plane-card" },
h("span", null, "service proxy"),
h("strong", null, kubeProxy.configured === true ? "K8S API PROXY" : "PROXY DEGRADED"),
h("p", null, kubeProxy.configured === true ? `${kubeProxy.mode || "kubernetes-api-service-proxy"} via ${kubeProxy.connectHost || "--"}` : "adapter 必须通过 k8s API service proxy 访问业务服务,不回退到业务容器直连。"),
),
h("article", { className: "v3s-control-plane-card" },
h("article", { className: "k3s-control-plane-card" },
h("span", null, "manifests"),
h("strong", null, manifestPaths.length || "--"),
h("p", null, manifestPaths.join(" / ") || "未配置 manifest"),
),
h("article", { className: "v3s-control-plane-card" },
h("article", { className: "k3s-control-plane-card" },
h("span", null, "cluster snapshot"),
h("strong", null, kubectl.enabled === true ? (kubectl.ok === true ? "KUBECTL OK" : "KUBECTL DEGRADED") : "API ONLY"),
h("p", null, kubectl.enabled === true ? `nodes ${kubectl.nodeCount ?? "--"}` : "控制面页面以 adapter 返回的 k8s service proxy 状态为准;kubectl 只作为可选快照。"),
@@ -210,7 +210,7 @@ export function V3sCtlPage({ microservices, onRaw, apiBaseUrl, onNavigate }: Any
state.refreshedAt ? h("p", { className: "muted paragraph" }, `最近刷新 ${fmtClock(state.refreshedAt)}`) : null,
),
services.length === 0
? h(Panel, { title: "代管服务", eyebrow: "v3s services", loading: state.loading }, h(EmptyState, { title: "暂无 v3s 服务", text: "等待 v3sctl-adapter 返回 /api/servicesCode Queue 切换后这里应显示 D601 和 D518 两个实例。" }))
? h(Panel, { title: "代管服务", eyebrow: "k3s services", loading: state.loading }, h(EmptyState, { title: "暂无 k3s 服务", text: "等待 k3sctl-adapter 返回 /api/servicesCode Queue 切换后这里应显示 D601 和 D518 两个实例。" }))
: services.map((service) => renderManagedService(service, onRaw)),
);
}
+1 -1
View File
@@ -70,7 +70,7 @@ export const MODULES: UniDeskModuleDefinition[] = [
{ id: "baidu-netdisk", label: "Baidu Netdisk" },
{ id: "filebrowser", label: "File Browser" },
{ id: "oa-event-flow", label: "OA Event Flow" },
{ id: "v3sctl", label: "V3S Control" },
{ id: "k3sctl", label: "K3S Control" },
{ id: "code-queue", label: "Code Queue" },
{ id: "mdtodo", label: "MDTODO" },
{ id: "project-manager", label: "Project Manager" },