patchwarden 0.6.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PatchWarden-Control-Tray.cmd +11 -0
- package/PatchWarden-Control.cmd +6 -0
- package/PatchWarden-Desktop.cmd +5 -0
- package/PatchWarden.cmd +1 -1
- package/README.en.md +112 -24
- package/README.md +36 -25
- package/Restart-PatchWarden-Control.cmd +6 -0
- package/Stop-PatchWarden.cmd +11 -0
- package/dist/agents/agentRouter.d.ts +40 -0
- package/dist/agents/agentRouter.js +95 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +6 -2
- package/dist/controlCenter.d.ts +14 -0
- package/dist/controlCenter.js +2015 -0
- package/dist/doctor.js +35 -4
- package/dist/goal/acceptanceEngine.d.ts +62 -0
- package/dist/goal/acceptanceEngine.js +103 -0
- package/dist/goal/acceptanceTemplate.d.ts +10 -0
- package/dist/goal/acceptanceTemplate.js +104 -0
- package/dist/goal/goalGraph.d.ts +58 -0
- package/dist/goal/goalGraph.js +189 -0
- package/dist/goal/goalProgress.d.ts +81 -0
- package/dist/goal/goalProgress.js +216 -0
- package/dist/goal/goalStatus.d.ts +60 -0
- package/dist/goal/goalStatus.js +137 -0
- package/dist/goal/goalStore.d.ts +79 -0
- package/dist/goal/goalStore.js +211 -0
- package/dist/goal/handoffExport.d.ts +34 -0
- package/dist/goal/handoffExport.js +183 -0
- package/dist/goal/subgoalSync.d.ts +40 -0
- package/dist/goal/subgoalSync.js +72 -0
- package/dist/goal/worktreeManager.d.ts +96 -0
- package/dist/goal/worktreeManager.js +292 -0
- package/dist/logging.d.ts +44 -0
- package/dist/logging.js +68 -0
- package/dist/release/releaseGate.d.ts +99 -0
- package/dist/release/releaseGate.js +475 -0
- package/dist/runner/postTaskCleanup.d.ts +13 -0
- package/dist/runner/postTaskCleanup.js +147 -0
- package/dist/runner/runTask.js +50 -10
- package/dist/security/discoveryTokenStore.d.ts +62 -0
- package/dist/security/discoveryTokenStore.js +100 -0
- package/dist/security/toolInvocationGuard.d.ts +35 -0
- package/dist/security/toolInvocationGuard.js +153 -0
- package/dist/smoke-test.js +18 -10
- package/dist/taskRuntime.d.ts +17 -0
- package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
- package/dist/test/unit/acceptance-engine.test.js +228 -0
- package/dist/test/unit/agent-router.test.d.ts +1 -0
- package/dist/test/unit/agent-router.test.js +287 -0
- package/dist/test/unit/audit-checks.test.d.ts +1 -0
- package/dist/test/unit/audit-checks.test.js +350 -0
- package/dist/test/unit/diagnose-task.test.d.ts +1 -0
- package/dist/test/unit/diagnose-task.test.js +457 -0
- package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
- package/dist/test/unit/discovery-token-store.test.js +139 -0
- package/dist/test/unit/goal-graph.test.d.ts +1 -0
- package/dist/test/unit/goal-graph.test.js +298 -0
- package/dist/test/unit/goal-progress.test.d.ts +1 -0
- package/dist/test/unit/goal-progress.test.js +381 -0
- package/dist/test/unit/goal-status.test.d.ts +1 -0
- package/dist/test/unit/goal-status.test.js +215 -0
- package/dist/test/unit/goal-store.test.d.ts +1 -0
- package/dist/test/unit/goal-store.test.js +253 -0
- package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
- package/dist/test/unit/goal-subgoal-task.test.js +55 -0
- package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
- package/dist/test/unit/goal-tools-registry.test.js +190 -0
- package/dist/test/unit/handoff-export.test.d.ts +1 -0
- package/dist/test/unit/handoff-export.test.js +263 -0
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
- package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
- package/dist/test/unit/logging.test.js +127 -5
- package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
- package/dist/test/unit/post-task-cleanup.test.js +48 -0
- package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
- package/dist/test/unit/reconcile-tasks.test.js +456 -0
- package/dist/test/unit/release-gate.test.d.ts +1 -0
- package/dist/test/unit/release-gate.test.js +242 -0
- package/dist/test/unit/safe-views.test.d.ts +1 -0
- package/dist/test/unit/safe-views.test.js +171 -0
- package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
- package/dist/test/unit/schema-drift-check.test.js +175 -0
- package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
- package/dist/test/unit/subgoal-sync.test.js +183 -0
- package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
- package/dist/test/unit/tool-invocation-guard.test.js +432 -0
- package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
- package/dist/test/unit/tool-usage-stats.test.js +300 -0
- package/dist/test/unit/toolSearch.test.d.ts +1 -0
- package/dist/test/unit/toolSearch.test.js +571 -0
- package/dist/test/unit/worktree-manager.test.d.ts +1 -0
- package/dist/test/unit/worktree-manager.test.js +176 -0
- package/dist/tools/auditTask.d.ts +103 -1
- package/dist/tools/auditTask.js +461 -8
- package/dist/tools/cancelTask.js +1 -1
- package/dist/tools/checkReleaseGate.d.ts +21 -0
- package/dist/tools/checkReleaseGate.js +22 -0
- package/dist/tools/createTask.d.ts +18 -2
- package/dist/tools/createTask.js +29 -1
- package/dist/tools/diagnoseTask.d.ts +45 -0
- package/dist/tools/diagnoseTask.js +347 -0
- package/dist/tools/discardWorktree.d.ts +24 -0
- package/dist/tools/discardWorktree.js +27 -0
- package/dist/tools/discoverTools.d.ts +11 -0
- package/dist/tools/discoverTools.js +39 -0
- package/dist/tools/explainTool.d.ts +11 -0
- package/dist/tools/explainTool.js +21 -0
- package/dist/tools/getTaskSummary.js +2 -1
- package/dist/tools/goalSubgoalTask.d.ts +51 -0
- package/dist/tools/goalSubgoalTask.js +110 -0
- package/dist/tools/healthCheck.d.ts +1 -0
- package/dist/tools/healthCheck.js +5 -1
- package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
- package/dist/tools/invokeDiscoveredTool.js +112 -0
- package/dist/tools/listTasks.d.ts +3 -1
- package/dist/tools/listTasks.js +14 -1
- package/dist/tools/mergeWorktree.d.ts +24 -0
- package/dist/tools/mergeWorktree.js +27 -0
- package/dist/tools/reconcileTasks.d.ts +41 -0
- package/dist/tools/reconcileTasks.js +352 -0
- package/dist/tools/registry.js +550 -0
- package/dist/tools/safeStatus.d.ts +31 -1
- package/dist/tools/safeStatus.js +43 -1
- package/dist/tools/safeViews.d.ts +256 -0
- package/dist/tools/safeViews.js +250 -0
- package/dist/tools/schemaDriftCheck.d.ts +46 -0
- package/dist/tools/schemaDriftCheck.js +80 -0
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +33 -11
- package/dist/tools/toolRegistry.d.ts +61 -0
- package/dist/tools/toolRegistry.js +724 -0
- package/dist/tools/toolSearch.d.ts +110 -0
- package/dist/tools/toolSearch.js +331 -0
- package/dist/tools/toolUsageStats.d.ts +41 -0
- package/dist/tools/toolUsageStats.js +116 -0
- package/dist/tools/waitForTask.js +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +29 -0
- package/dist/watcherStatus.js +92 -1
- package/docs/control-center/README.md +33 -0
- package/docs/control-center/control-center-daily-driver.md +211 -0
- package/docs/control-center/control-center-mvp.md +205 -0
- package/docs/control-center/control-center-phase2.md +159 -0
- package/docs/demo.md +3 -0
- package/docs/release-v0.6.4.md +45 -0
- package/examples/openai-tunnel/README.md +5 -5
- package/examples/openai-tunnel/tunnel-client.example.yaml +3 -3
- package/package.json +25 -16
- package/scripts/README.md +47 -0
- package/scripts/{brand-check.js → checks/brand-check.js} +2 -2
- package/scripts/checks/control-center-smoke.js +1098 -0
- package/scripts/{control-smoke.js → checks/control-smoke.js} +19 -5
- package/scripts/{doctor-smoke.js → checks/doctor-smoke.js} +2 -1
- package/scripts/{http-mcp-smoke.js → checks/http-mcp-smoke.js} +2 -2
- package/scripts/{lifecycle-smoke.js → checks/lifecycle-smoke.js} +25 -21
- package/scripts/{mcp-manifest-check.js → checks/mcp-manifest-check.js} +54 -7
- package/scripts/{mcp-smoke.js → checks/mcp-smoke.js} +40 -11
- package/scripts/{package-manifest-check.js → checks/package-manifest-check.js} +2 -1
- package/scripts/{tunnel-supervisor-smoke.js → checks/tunnel-supervisor-smoke.js} +2 -2
- package/scripts/{unit-tests.js → checks/unit-tests.js} +1 -1
- package/scripts/{watcher-supervisor-smoke.js → checks/watcher-supervisor-smoke.js} +13 -7
- package/scripts/control/control-center-tray.ps1 +281 -0
- package/scripts/{get-patchwarden-health.ps1 → control/get-patchwarden-health.ps1} +3 -3
- package/scripts/{manage-patchwarden.ps1 → control/manage-patchwarden.ps1} +30 -4
- package/scripts/control/restart-control-center.ps1 +173 -0
- package/scripts/{restart-patchwarden.ps1 → control/restart-patchwarden.ps1} +1 -1
- package/scripts/control/start-control-center.ps1 +263 -0
- package/scripts/{start-patchwarden-tunnel.ps1 → control/start-patchwarden-tunnel.ps1} +48 -6
- package/scripts/control/stop-patchwarden.ps1 +114 -0
- package/scripts/launchers/Check-PatchWarden-Health.cmd +1 -1
- package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +1 -1
- package/scripts/launchers/Restart-PatchWarden.cmd +1 -1
- package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +1 -1
- package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +1 -1
- package/scripts/{patchwarden-mcp-direct.cmd → mcp/patchwarden-mcp-direct.cmd} +1 -1
- package/scripts/{patchwarden-mcp-stdio.cmd → mcp/patchwarden-mcp-stdio.cmd} +1 -1
- package/scripts/{pack-clean.js → release/pack-clean.js} +9 -1
- package/src/agents/agentRouter.ts +149 -0
- package/src/config.ts +9 -3
- package/src/controlCenter.ts +2166 -0
- package/src/doctor.ts +40 -5
- package/src/goal/acceptanceEngine.ts +160 -0
- package/src/goal/acceptanceTemplate.ts +121 -0
- package/src/goal/goalGraph.ts +225 -0
- package/src/goal/goalProgress.ts +301 -0
- package/src/goal/goalStatus.ts +234 -0
- package/src/goal/goalStore.ts +306 -0
- package/src/goal/handoffExport.ts +211 -0
- package/src/goal/subgoalSync.ts +82 -0
- package/src/goal/worktreeManager.ts +404 -0
- package/src/logging.ts +91 -0
- package/src/release/releaseGate.ts +567 -0
- package/src/runner/postTaskCleanup.ts +154 -0
- package/src/runner/runTask.ts +49 -10
- package/src/security/discoveryTokenStore.ts +157 -0
- package/src/security/toolInvocationGuard.ts +251 -0
- package/src/smoke-test.ts +15 -7
- package/src/taskRuntime.ts +17 -0
- package/src/test/unit/acceptance-engine.test.ts +261 -0
- package/src/test/unit/agent-router.test.ts +342 -0
- package/src/test/unit/audit-checks.test.ts +567 -0
- package/src/test/unit/diagnose-task.test.ts +544 -0
- package/src/test/unit/discovery-token-store.test.ts +181 -0
- package/src/test/unit/goal-graph.test.ts +347 -0
- package/src/test/unit/goal-progress.test.ts +538 -0
- package/src/test/unit/goal-status.test.ts +270 -0
- package/src/test/unit/goal-store.test.ts +318 -0
- package/src/test/unit/goal-subgoal-task.test.ts +72 -0
- package/src/test/unit/goal-tools-registry.test.ts +243 -0
- package/src/test/unit/handoff-export.test.ts +295 -0
- package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
- package/src/test/unit/logging.test.ts +177 -5
- package/src/test/unit/post-task-cleanup.test.ts +53 -0
- package/src/test/unit/reconcile-tasks.test.ts +551 -0
- package/src/test/unit/release-gate.test.ts +314 -0
- package/src/test/unit/safe-views.test.ts +184 -0
- package/src/test/unit/schema-drift-check.test.ts +258 -0
- package/src/test/unit/subgoal-sync.test.ts +236 -0
- package/src/test/unit/tool-invocation-guard.test.ts +542 -0
- package/src/test/unit/tool-usage-stats.test.ts +384 -0
- package/src/test/unit/toolSearch.test.ts +756 -0
- package/src/test/unit/worktree-manager.test.ts +247 -0
- package/src/tools/auditTask.ts +831 -402
- package/src/tools/cancelTask.ts +1 -1
- package/src/tools/checkReleaseGate.ts +45 -0
- package/src/tools/createTask.ts +64 -6
- package/src/tools/diagnoseTask.ts +460 -0
- package/src/tools/discardWorktree.ts +42 -0
- package/src/tools/discoverTools.ts +51 -0
- package/src/tools/explainTool.ts +26 -0
- package/src/tools/getTaskSummary.ts +2 -1
- package/src/tools/goalSubgoalTask.ts +170 -0
- package/src/tools/healthCheck.ts +4 -1
- package/src/tools/invokeDiscoveredTool.ts +163 -0
- package/src/tools/listTasks.ts +16 -2
- package/src/tools/mergeWorktree.ts +42 -0
- package/src/tools/reconcileTasks.ts +464 -0
- package/src/tools/registry.ts +618 -1
- package/src/tools/safeStatus.ts +73 -2
- package/src/tools/safeViews.ts +271 -0
- package/src/tools/schemaDriftCheck.ts +120 -0
- package/src/tools/toolCatalog.ts +34 -11
- package/src/tools/toolRegistry.ts +786 -0
- package/src/tools/toolSearch.ts +464 -0
- package/src/tools/toolUsageStats.ts +151 -0
- package/src/tools/waitForTask.ts +1 -0
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +97 -1
- package/ui/colors_and_type.css +141 -0
- package/ui/pages/audit.html +743 -0
- package/ui/pages/dashboard.html +1154 -0
- package/ui/pages/direct-sessions.html +652 -0
- package/ui/pages/logs.html +502 -0
- package/ui/pages/task-detail.html +1229 -0
- package/ui/pages/tasks.html +702 -0
- package/ui/pages/workspace.html +947 -0
- package/ui/partials/project-shell.html +362 -0
- package/ui/vendor/lucide.js +12 -0
- package/ui/vendor/tailwindcss-browser.js +947 -0
|
@@ -0,0 +1,947 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title data-zh="工作区 - PatchWarden" data-en="Workspace - PatchWarden">工作区 - PatchWarden</title>
|
|
7
|
+
<style id="theme-vars">
|
|
8
|
+
/* PatchWarden Brand CSS — Dark Security Theme */
|
|
9
|
+
/* Prefix: pw */
|
|
10
|
+
|
|
11
|
+
:root {
|
|
12
|
+
/* ── Primary Palette ── */
|
|
13
|
+
--pw-bg: #0a0e14;
|
|
14
|
+
--pw-bg-elevated: #111820;
|
|
15
|
+
--pw-bg-surface: #161d27;
|
|
16
|
+
--pw-bg-hover: #1c2533;
|
|
17
|
+
--pw-bg-active: #222d3d;
|
|
18
|
+
--pw-border: #1e2a3a;
|
|
19
|
+
--pw-border-subtle: #172030;
|
|
20
|
+
--pw-border-focus: #2dd4a8;
|
|
21
|
+
|
|
22
|
+
/* ── Text ── */
|
|
23
|
+
--pw-text-primary: #e8edf4;
|
|
24
|
+
--pw-text-secondary: #8b99ad;
|
|
25
|
+
--pw-text-tertiary: #5a6a80;
|
|
26
|
+
--pw-text-inverse: #0a0e14;
|
|
27
|
+
--pw-text-accent: #2dd4a8;
|
|
28
|
+
|
|
29
|
+
/* ── Brand Accent — Security Green ── */
|
|
30
|
+
--pw-accent: #2dd4a8;
|
|
31
|
+
--pw-accent-hover: #34eabc;
|
|
32
|
+
--pw-accent-muted: rgba(45, 212, 168, 0.12);
|
|
33
|
+
--pw-accent-subtle: rgba(45, 212, 168, 0.06);
|
|
34
|
+
|
|
35
|
+
/* ── State Colors ── */
|
|
36
|
+
--pw-state-success: #22c55e;
|
|
37
|
+
--pw-state-success-bg: rgba(34, 197, 94, 0.1);
|
|
38
|
+
--pw-state-warning: #f59e0b;
|
|
39
|
+
--pw-state-warning-bg: rgba(245, 158, 11, 0.1);
|
|
40
|
+
--pw-state-error: #ef4444;
|
|
41
|
+
--pw-state-error-bg: rgba(239, 68, 68, 0.1);
|
|
42
|
+
--pw-state-info: #3b82f6;
|
|
43
|
+
--pw-state-info-bg: rgba(59, 130, 246, 0.1);
|
|
44
|
+
|
|
45
|
+
/* ── Radius ── */
|
|
46
|
+
--pw-radius-sm: 4px;
|
|
47
|
+
--pw-radius-md: 8px;
|
|
48
|
+
--pw-radius-lg: 12px;
|
|
49
|
+
--pw-radius-full: 9999px;
|
|
50
|
+
|
|
51
|
+
/* ── Shadows — static surfaces use border only; floating layers use subtle shadow ── */
|
|
52
|
+
--pw-shadow-float: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
53
|
+
--pw-shadow-modal: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
54
|
+
|
|
55
|
+
/* ── Typography ── */
|
|
56
|
+
--pw-font-sans: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
57
|
+
--pw-font-mono: 'JetBrains Mono', 'Cascadia Code', 'Consolas', monospace;
|
|
58
|
+
|
|
59
|
+
--pw-text-xs: 0.75rem;
|
|
60
|
+
--pw-text-sm: 0.8125rem;
|
|
61
|
+
--pw-text-base: 0.875rem;
|
|
62
|
+
--pw-text-lg: 1rem;
|
|
63
|
+
--pw-text-xl: 1.25rem;
|
|
64
|
+
--pw-text-2xl: 1.5rem;
|
|
65
|
+
--pw-text-3xl: 2rem;
|
|
66
|
+
|
|
67
|
+
--pw-leading-tight: 1.25;
|
|
68
|
+
--pw-leading-normal: 1.5;
|
|
69
|
+
--pw-leading-relaxed: 1.625;
|
|
70
|
+
|
|
71
|
+
--pw-weight-normal: 400;
|
|
72
|
+
--pw-weight-medium: 500;
|
|
73
|
+
--pw-weight-semibold: 600;
|
|
74
|
+
--pw-weight-bold: 700;
|
|
75
|
+
|
|
76
|
+
/* ── Spacing ── */
|
|
77
|
+
--pw-space-1: 4px;
|
|
78
|
+
--pw-space-2: 8px;
|
|
79
|
+
--pw-space-3: 12px;
|
|
80
|
+
--pw-space-4: 16px;
|
|
81
|
+
--pw-space-5: 20px;
|
|
82
|
+
--pw-space-6: 24px;
|
|
83
|
+
--pw-space-8: 32px;
|
|
84
|
+
--pw-space-10: 40px;
|
|
85
|
+
--pw-space-12: 48px;
|
|
86
|
+
|
|
87
|
+
/* ── Transitions ── */
|
|
88
|
+
--pw-transition-fast: 150ms ease;
|
|
89
|
+
--pw-transition-base: 200ms ease;
|
|
90
|
+
--pw-transition-slow: 300ms ease;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
html.dark {
|
|
94
|
+
--pw-bg: #0a0e14;
|
|
95
|
+
--pw-bg-elevated: #111820;
|
|
96
|
+
--pw-bg-surface: #161d27;
|
|
97
|
+
--pw-bg-hover: #1c2533;
|
|
98
|
+
--pw-bg-active: #222d3d;
|
|
99
|
+
--pw-border: #1e2a3a;
|
|
100
|
+
--pw-border-subtle: #172030;
|
|
101
|
+
--pw-text-primary: #e8edf4;
|
|
102
|
+
--pw-text-secondary: #8b99ad;
|
|
103
|
+
--pw-text-tertiary: #5a6a80;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
</style>
|
|
107
|
+
<script src="/vendor/tailwindcss-browser.js"></script>
|
|
108
|
+
<script src="/vendor/lucide.js"></script>
|
|
109
|
+
<style type="text/tailwindcss">
|
|
110
|
+
@theme inline {
|
|
111
|
+
--color-accent: var(--pw-accent);
|
|
112
|
+
--color-border: var(--pw-border);
|
|
113
|
+
}
|
|
114
|
+
@layer base {
|
|
115
|
+
body { background: var(--pw-background); color: var(--pw-text-primary); }
|
|
116
|
+
td, th { @apply break-words; word-break: break-all; word-break: auto-phrase; }
|
|
117
|
+
th { @apply whitespace-nowrap; }
|
|
118
|
+
}
|
|
119
|
+
</style>
|
|
120
|
+
<style>
|
|
121
|
+
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
122
|
+
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
123
|
+
[data-icon] {
|
|
124
|
+
display: inline-flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
justify-content: center;
|
|
127
|
+
-webkit-mask-size: contain;
|
|
128
|
+
mask-size: contain;
|
|
129
|
+
-webkit-mask-repeat: no-repeat;
|
|
130
|
+
mask-repeat: no-repeat;
|
|
131
|
+
-webkit-mask-position: center;
|
|
132
|
+
mask-position: center;
|
|
133
|
+
background-color: currentColor;
|
|
134
|
+
}
|
|
135
|
+
.pw-spin {
|
|
136
|
+
animation: pw-spin 0.9s linear infinite;
|
|
137
|
+
}
|
|
138
|
+
@keyframes pw-spin {
|
|
139
|
+
from { transform: rotate(0deg); }
|
|
140
|
+
to { transform: rotate(360deg); }
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
143
|
+
</head>
|
|
144
|
+
|
|
145
|
+
<body class="flex h-screen overflow-hidden">
|
|
146
|
+
|
|
147
|
+
<!-- ========== SIDEBAR ========== -->
|
|
148
|
+
<aside class="flex flex-col w-60 shrink-0 h-screen" style="background-color: var(--pw-bg); border-right: 1px solid var(--pw-border);">
|
|
149
|
+
|
|
150
|
+
<!-- Logo Area -->
|
|
151
|
+
<div class="flex items-center gap-3 px-5 py-5" style="border-bottom: 1px solid var(--pw-border-subtle);">
|
|
152
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--pw-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
153
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
|
154
|
+
<path d="M9 12l2 2 4-4"/>
|
|
155
|
+
</svg>
|
|
156
|
+
<span class="truncate text-lg font-semibold tracking-tight" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">PatchWarden</span>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<!-- Navigation -->
|
|
160
|
+
<nav class="flex-1 flex flex-col gap-1 px-3 pt-4 overflow-y-auto">
|
|
161
|
+
|
|
162
|
+
<a href="/pages/dashboard.html" data-nav-key="dashboard" data-dom-id="nav-dashboard" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
163
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
164
|
+
<rect x="3" y="3" width="7" height="7" rx="1"/>
|
|
165
|
+
<rect x="14" y="3" width="7" height="7" rx="1"/>
|
|
166
|
+
<rect x="3" y="14" width="7" height="7" rx="1"/>
|
|
167
|
+
<rect x="14" y="14" width="7" height="7" rx="1"/>
|
|
168
|
+
</svg>
|
|
169
|
+
<span class="truncate" data-zh="控制台" data-en="Dashboard">控制台</span>
|
|
170
|
+
</a>
|
|
171
|
+
|
|
172
|
+
<a href="/pages/tasks.html" data-nav-key="tasks" data-dom-id="nav-tasks" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
173
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
174
|
+
<path d="M9 11l3 3L22 4"/>
|
|
175
|
+
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
176
|
+
</svg>
|
|
177
|
+
<span class="truncate" data-zh="任务面板" data-en="Tasks">任务面板</span>
|
|
178
|
+
</a>
|
|
179
|
+
|
|
180
|
+
<a href="/pages/workspace.html" data-nav-key="workspace" data-active="true" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
181
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
182
|
+
<path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2z"/>
|
|
183
|
+
</svg>
|
|
184
|
+
<span class="truncate" data-zh="工作区" data-en="Workspace">工作区</span>
|
|
185
|
+
</a>
|
|
186
|
+
|
|
187
|
+
<a href="/pages/audit.html" data-nav-key="audit" data-dom-id="nav-audit" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
188
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
189
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
|
190
|
+
<polyline points="14 2 14 8 20 8"/>
|
|
191
|
+
<line x1="16" y1="13" x2="8" y2="13"/>
|
|
192
|
+
<line x1="16" y1="17" x2="8" y2="17"/>
|
|
193
|
+
<polyline points="10 9 9 9 8 9"/>
|
|
194
|
+
</svg>
|
|
195
|
+
<span class="truncate" data-zh="审计日志" data-en="Audit Log">审计日志</span>
|
|
196
|
+
</a>
|
|
197
|
+
|
|
198
|
+
<a href="/pages/direct-sessions.html" data-nav-key="direct-sessions" data-dom-id="nav-direct-sessions" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
199
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
200
|
+
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/>
|
|
201
|
+
<line x1="4" y1="22" x2="4" y2="15"/>
|
|
202
|
+
</svg>
|
|
203
|
+
<span class="truncate" data-zh="Direct 会话" data-en="Direct Sessions">Direct 会话</span>
|
|
204
|
+
</a>
|
|
205
|
+
|
|
206
|
+
<a href="/pages/logs.html" data-nav-key="logs" data-dom-id="nav-logs" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
207
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
208
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
|
209
|
+
<polyline points="14 2 14 8 20 8"/>
|
|
210
|
+
<line x1="16" y1="13" x2="8" y2="13"/>
|
|
211
|
+
<line x1="16" y1="17" x2="8" y2="17"/>
|
|
212
|
+
</svg>
|
|
213
|
+
<span class="truncate" data-zh="日志" data-en="Logs">日志</span>
|
|
214
|
+
</a>
|
|
215
|
+
|
|
216
|
+
</nav>
|
|
217
|
+
|
|
218
|
+
<!-- Bottom Section -->
|
|
219
|
+
<div class="px-5 py-4" style="border-top: 1px solid var(--pw-border-subtle);">
|
|
220
|
+
<div class="flex items-center gap-2 mb-3">
|
|
221
|
+
<span class="text-xs font-medium px-2 py-0.5 rounded-md whitespace-nowrap" style="color: var(--pw-text-tertiary); background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-mono);">Core v0.6.0</span>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="flex items-center gap-2">
|
|
224
|
+
<span class="shrink-0 w-2 h-2 rounded-full" style="background-color: var(--pw-state-success);"></span>
|
|
225
|
+
<span class="text-xs truncate" style="color: var(--pw-text-tertiary);">Connected</span>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
</aside>
|
|
230
|
+
|
|
231
|
+
<!-- ========== MAIN AREA ========== -->
|
|
232
|
+
<div class="flex flex-col flex-1 min-w-0 h-screen overflow-hidden">
|
|
233
|
+
|
|
234
|
+
<!-- Top Bar -->
|
|
235
|
+
<header class="flex items-center justify-between shrink-0 px-6 py-3" style="border-bottom: 1px solid var(--pw-border); background-color: var(--pw-bg);">
|
|
236
|
+
<h1 class="text-lg font-semibold truncate min-w-0" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans); text-wrap: balance; word-break: keep-all; overflow-wrap: break-word;" data-zh="工作区" data-en="Workspace">
|
|
237
|
+
工作区
|
|
238
|
+
</h1>
|
|
239
|
+
|
|
240
|
+
<div class="flex items-center gap-4 shrink-0">
|
|
241
|
+
<span id="lastUpdated" class="text-xs whitespace-nowrap hidden" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);"></span>
|
|
242
|
+
<button id="refreshBtn" class="flex items-center gap-2 px-4 py-1.5 rounded-lg text-xs font-semibold whitespace-nowrap transition-colors" style="background-color: var(--pw-bg-surface); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);">
|
|
243
|
+
<svg id="refreshIcon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
244
|
+
<polyline points="23 4 23 10 17 10"/>
|
|
245
|
+
<polyline points="1 20 1 14 7 14"/>
|
|
246
|
+
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
|
|
247
|
+
</svg>
|
|
248
|
+
<span data-zh="刷新" data-en="Refresh">刷新</span>
|
|
249
|
+
</button>
|
|
250
|
+
<div class="shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-xs font-semibold" style="background-color: var(--pw-accent-muted); color: var(--pw-accent); font-family: var(--pw-font-mono);">
|
|
251
|
+
PW
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</header>
|
|
255
|
+
|
|
256
|
+
<!-- Page Content Area -->
|
|
257
|
+
<main class="flex-1 overflow-y-auto p-6" style="background-color: var(--pw-bg-elevated);">
|
|
258
|
+
|
|
259
|
+
<!-- ===== Error Banner ===== -->
|
|
260
|
+
<div id="error-banner" class="hidden mb-5 rounded-lg px-4 py-3 flex items-start gap-3" style="background-color: var(--pw-state-error-bg); border: 1px solid var(--pw-state-error);">
|
|
261
|
+
<svg class="shrink-0 mt-0.5" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-state-error)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
262
|
+
<circle cx="12" cy="12" r="10"/>
|
|
263
|
+
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
264
|
+
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
265
|
+
</svg>
|
|
266
|
+
<div class="min-w-0 flex-1">
|
|
267
|
+
<p class="text-sm font-medium" style="color: var(--pw-state-error);" data-zh="加载失败" data-en="Load Failed">加载失败</p>
|
|
268
|
+
<p id="error-message" class="text-xs mt-1 break-words" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);"></p>
|
|
269
|
+
</div>
|
|
270
|
+
<button id="retry-btn" class="shrink-0 px-3 py-1 rounded-md text-xs font-medium transition-colors" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);" data-zh="重试" data-en="Retry">重试</button>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<!-- ===== Workspace Path Card ===== -->
|
|
274
|
+
<div class="mb-6 rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); border-left: 3px solid var(--pw-accent);">
|
|
275
|
+
<div class="flex items-center gap-2 px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
276
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
277
|
+
<path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2z"/>
|
|
278
|
+
</svg>
|
|
279
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);" data-zh="工作区路径" data-en="Workspace Root">工作区路径</h2>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="px-4 py-3 flex items-center gap-3 min-w-0">
|
|
282
|
+
<svg class="shrink-0" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
283
|
+
<polyline points="16 18 22 12 16 6"/>
|
|
284
|
+
<polyline points="8 6 2 12 8 18"/>
|
|
285
|
+
</svg>
|
|
286
|
+
<code id="workspace-root" class="text-sm truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);" data-zh="加载中..." data-en="Loading...">加载中...</code>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<!-- Two-column layout -->
|
|
291
|
+
<div class="grid grid-cols-1 lg:grid-cols-5 gap-6">
|
|
292
|
+
|
|
293
|
+
<!-- Left Column: Projects (~40%) -->
|
|
294
|
+
<div class="lg:col-span-2">
|
|
295
|
+
|
|
296
|
+
<!-- 项目列表 Card -->
|
|
297
|
+
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
298
|
+
<!-- Card Header -->
|
|
299
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
300
|
+
<div class="flex items-center gap-2">
|
|
301
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
302
|
+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
|
|
303
|
+
</svg>
|
|
304
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);" data-zh="项目列表" data-en="Projects">项目列表</h2>
|
|
305
|
+
</div>
|
|
306
|
+
<span id="projects-count" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);">--</span>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<!-- Project list body -->
|
|
310
|
+
<div id="projects-body" class="p-4" style="font-family: var(--pw-font-mono); font-size: var(--pw-text-sm);">
|
|
311
|
+
<!-- Loading state -->
|
|
312
|
+
<div id="projects-loading" class="py-10 text-center">
|
|
313
|
+
<svg class="pw-spin inline-block mb-2" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
314
|
+
<line x1="12" y1="2" x2="12" y2="6"/>
|
|
315
|
+
<line x1="12" y1="18" x2="12" y2="22"/>
|
|
316
|
+
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/>
|
|
317
|
+
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/>
|
|
318
|
+
<line x1="2" y1="12" x2="6" y2="12"/>
|
|
319
|
+
<line x1="18" y1="12" x2="22" y2="12"/>
|
|
320
|
+
</svg>
|
|
321
|
+
<p class="text-xs" style="color: var(--pw-text-tertiary);" data-zh="加载中..." data-en="Loading...">加载中...</p>
|
|
322
|
+
</div>
|
|
323
|
+
<!-- Empty state -->
|
|
324
|
+
<div id="projects-empty" class="hidden py-10 text-center">
|
|
325
|
+
<svg class="inline-block mb-2" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
326
|
+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
|
|
327
|
+
</svg>
|
|
328
|
+
<p class="text-xs" style="color: var(--pw-text-tertiary);" data-zh="暂无项目" data-en="No projects">暂无项目</p>
|
|
329
|
+
</div>
|
|
330
|
+
<!-- Rendered project list -->
|
|
331
|
+
<div id="projects-list" class="hidden flex flex-col gap-1"></div>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
<!-- Repo status panel (on-demand git status) -->
|
|
336
|
+
<div id="repo-status-panel" class="hidden mt-4 rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
337
|
+
<!-- populated by renderRepoStatusPanel -->
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<!-- Right Column: Config + Agents (~60%) -->
|
|
343
|
+
<div class="lg:col-span-3 flex flex-col gap-6">
|
|
344
|
+
|
|
345
|
+
<!-- 配置预览 Card -->
|
|
346
|
+
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
347
|
+
<!-- Card Header -->
|
|
348
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
349
|
+
<div class="flex items-center gap-2">
|
|
350
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
351
|
+
<polyline points="16 18 22 12 16 6"/>
|
|
352
|
+
<polyline points="8 6 2 12 8 18"/>
|
|
353
|
+
</svg>
|
|
354
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);" data-zh="配置预览" data-en="Config Preview">配置预览</h2>
|
|
355
|
+
</div>
|
|
356
|
+
<span class="text-xs px-2 py-0.5 rounded-md whitespace-nowrap" style="color: var(--pw-text-tertiary); background-color: var(--pw-bg-hover); font-family: var(--pw-font-mono);">patchwarden.config.json</span>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<!-- Config rows -->
|
|
360
|
+
<div id="config-body" class="p-4">
|
|
361
|
+
<!-- Loading -->
|
|
362
|
+
<div id="config-loading" class="py-6 text-center">
|
|
363
|
+
<svg class="pw-spin inline-block mb-2" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
364
|
+
<line x1="12" y1="2" x2="12" y2="6"/>
|
|
365
|
+
<line x1="12" y1="18" x2="12" y2="22"/>
|
|
366
|
+
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/>
|
|
367
|
+
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/>
|
|
368
|
+
<line x1="2" y1="12" x2="6" y2="12"/>
|
|
369
|
+
<line x1="18" y1="12" x2="22" y2="12"/>
|
|
370
|
+
</svg>
|
|
371
|
+
<p class="text-xs" style="color: var(--pw-text-tertiary);" data-zh="加载中..." data-en="Loading...">加载中...</p>
|
|
372
|
+
</div>
|
|
373
|
+
<div id="config-rows" class="hidden flex flex-col"></div>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<!-- Agent 注册 Card -->
|
|
378
|
+
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
379
|
+
<!-- Card Header -->
|
|
380
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
381
|
+
<div class="flex items-center gap-2">
|
|
382
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
383
|
+
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
|
|
384
|
+
<circle cx="9" cy="7" r="4"/>
|
|
385
|
+
<path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
|
|
386
|
+
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
|
387
|
+
</svg>
|
|
388
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);" data-zh="Agent 注册" data-en="Agent Registry">Agent 注册</h2>
|
|
389
|
+
</div>
|
|
390
|
+
<span id="agents-count" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);" data-zh="-- 个 Agent" data-en="-- Agents">-- 个 Agent</span>
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
<!-- Agent Cards Grid -->
|
|
394
|
+
<div class="p-4">
|
|
395
|
+
<!-- Loading -->
|
|
396
|
+
<div id="agents-loading" class="py-6 text-center">
|
|
397
|
+
<svg class="pw-spin inline-block mb-2" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
398
|
+
<line x1="12" y1="2" x2="12" y2="6"/>
|
|
399
|
+
<line x1="12" y1="18" x2="12" y2="22"/>
|
|
400
|
+
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/>
|
|
401
|
+
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/>
|
|
402
|
+
<line x1="2" y1="12" x2="6" y2="12"/>
|
|
403
|
+
<line x1="18" y1="12" x2="22" y2="12"/>
|
|
404
|
+
</svg>
|
|
405
|
+
<p class="text-xs" style="color: var(--pw-text-tertiary);" data-zh="加载中..." data-en="Loading...">加载中...</p>
|
|
406
|
+
</div>
|
|
407
|
+
<!-- Empty -->
|
|
408
|
+
<div id="agents-empty" class="hidden py-6 text-center">
|
|
409
|
+
<svg class="inline-block mb-2" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
410
|
+
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
|
|
411
|
+
<circle cx="9" cy="7" r="4"/>
|
|
412
|
+
</svg>
|
|
413
|
+
<p class="text-xs" style="color: var(--pw-text-tertiary);" data-zh="未注册任何 Agent" data-en="No agents registered">未注册任何 Agent</p>
|
|
414
|
+
</div>
|
|
415
|
+
<!-- Rendered agent grid -->
|
|
416
|
+
<div id="agents-grid" class="hidden grid grid-cols-1 md:grid-cols-2 gap-4"></div>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
<!-- 安全边界 Card (full width at bottom) -->
|
|
425
|
+
<div class="mt-6 rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
426
|
+
<!-- Card Header -->
|
|
427
|
+
<div class="flex items-center gap-2 px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
428
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
429
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
|
430
|
+
</svg>
|
|
431
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);" data-zh="安全边界" data-en="Security Boundaries">安全边界</h2>
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<!-- Security Items -->
|
|
435
|
+
<div class="p-4 grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
436
|
+
|
|
437
|
+
<!-- 工作区隔离 -->
|
|
438
|
+
<div class="flex items-start gap-3 p-3 rounded-lg" style="background-color: var(--pw-bg); border: 1px solid var(--pw-border-subtle);">
|
|
439
|
+
<div class="shrink-0 w-8 h-8 rounded-lg flex items-center justify-center" style="background-color: var(--pw-state-success-bg);">
|
|
440
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-state-success)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
441
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
442
|
+
</svg>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="min-w-0">
|
|
445
|
+
<h3 class="text-sm font-medium truncate" style="color: var(--pw-text-primary);" data-zh="工作区隔离" data-en="Workspace Isolation">工作区隔离</h3>
|
|
446
|
+
<p class="text-xs mt-1 truncate" style="color: var(--pw-text-tertiary);" data-zh="workspaceRoot 路径限制" data-en="workspaceRoot path restriction">workspaceRoot 路径限制</p>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
<!-- 命令白名单 -->
|
|
451
|
+
<div class="flex items-start gap-3 p-3 rounded-lg" style="background-color: var(--pw-bg); border: 1px solid var(--pw-border-subtle);">
|
|
452
|
+
<div class="shrink-0 w-8 h-8 rounded-lg flex items-center justify-center" style="background-color: var(--pw-state-success-bg);">
|
|
453
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-state-success)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
454
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
455
|
+
</svg>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="min-w-0">
|
|
458
|
+
<h3 class="text-sm font-medium truncate" style="color: var(--pw-text-primary);" data-zh="命令白名单" data-en="Command Allowlist">命令白名单</h3>
|
|
459
|
+
<p class="text-xs mt-1 truncate" style="color: var(--pw-text-tertiary);" data-zh="精确匹配 allowedTestCommands" data-en="Exact match allowedTestCommands">精确匹配 allowedTestCommands</p>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<!-- 敏感文件 -->
|
|
464
|
+
<div class="flex items-start gap-3 p-3 rounded-lg" style="background-color: var(--pw-bg); border: 1px solid var(--pw-border-subtle);">
|
|
465
|
+
<div class="shrink-0 w-8 h-8 rounded-lg flex items-center justify-center" style="background-color: var(--pw-state-success-bg);">
|
|
466
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-state-success)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
467
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
468
|
+
</svg>
|
|
469
|
+
</div>
|
|
470
|
+
<div class="min-w-0">
|
|
471
|
+
<h3 class="text-sm font-medium truncate" style="color: var(--pw-text-primary);" data-zh="敏感文件" data-en="Sensitive Files">敏感文件</h3>
|
|
472
|
+
<p class="text-xs mt-1 truncate" style="color: var(--pw-text-tertiary);" data-zh=".env / credentials 已屏蔽" data-en=".env / credentials blocked">.env / credentials 已屏蔽</p>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
<!-- Repo clean 状态(按需接口) -->
|
|
477
|
+
<div class="flex items-start gap-3 p-3 rounded-lg" style="background-color: var(--pw-bg); border: 1px solid var(--pw-border-subtle);">
|
|
478
|
+
<div class="shrink-0 w-8 h-8 rounded-lg flex items-center justify-center" style="background-color: var(--pw-state-success-bg);">
|
|
479
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-state-success)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
480
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
481
|
+
</svg>
|
|
482
|
+
</div>
|
|
483
|
+
<div class="min-w-0">
|
|
484
|
+
<h3 class="text-sm font-medium truncate" style="color: var(--pw-text-primary);" data-zh="Repo Clean 状态" data-en="Repo Clean Status">Repo Clean 状态</h3>
|
|
485
|
+
<p class="text-xs mt-1 truncate" style="color: var(--pw-text-tertiary);" data-zh="按需 git status(点击检查)" data-en="On-demand git status (click Check)">按需 git status(点击检查)</p>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
</main>
|
|
493
|
+
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<!-- Active nav item styling + sidebar hover states -->
|
|
497
|
+
<style>
|
|
498
|
+
/* Nav item base hover */
|
|
499
|
+
[data-nav-key] {
|
|
500
|
+
transition: background-color var(--pw-transition-fast), color var(--pw-transition-fast);
|
|
501
|
+
}
|
|
502
|
+
[data-nav-key]:hover {
|
|
503
|
+
background-color: var(--pw-bg-hover);
|
|
504
|
+
color: var(--pw-text-primary);
|
|
505
|
+
}
|
|
506
|
+
/* Active nav item */
|
|
507
|
+
[data-nav-key][data-active="true"] {
|
|
508
|
+
background-color: var(--pw-accent-subtle);
|
|
509
|
+
color: var(--pw-accent);
|
|
510
|
+
border-left: 3px solid var(--pw-accent);
|
|
511
|
+
padding-left: 9px;
|
|
512
|
+
}
|
|
513
|
+
/* Search input focus */
|
|
514
|
+
input[type="text"]:focus {
|
|
515
|
+
border-color: var(--pw-border-focus);
|
|
516
|
+
background-color: var(--pw-bg-hover);
|
|
517
|
+
}
|
|
518
|
+
input[type="text"]::placeholder {
|
|
519
|
+
color: var(--pw-text-tertiary);
|
|
520
|
+
}
|
|
521
|
+
</style>
|
|
522
|
+
|
|
523
|
+
<!-- Lucide init -->
|
|
524
|
+
<script>lucide.createIcons();</script>
|
|
525
|
+
|
|
526
|
+
<style>
|
|
527
|
+
@layer base {
|
|
528
|
+
body {
|
|
529
|
+
margin: 0;
|
|
530
|
+
padding: 0;
|
|
531
|
+
background-color: var(--pw-bg);
|
|
532
|
+
color: var(--pw-text-primary);
|
|
533
|
+
font-family: var(--pw-font-sans);
|
|
534
|
+
font-size: var(--pw-text-base);
|
|
535
|
+
line-height: var(--pw-leading-normal);
|
|
536
|
+
-webkit-font-smoothing: antialiased;
|
|
537
|
+
-moz-osx-font-smoothing: grayscale;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
</style>
|
|
541
|
+
|
|
542
|
+
<!-- Workspace data loader -->
|
|
543
|
+
<script>
|
|
544
|
+
(function () {
|
|
545
|
+
'use strict';
|
|
546
|
+
|
|
547
|
+
var API_URL = '/api/workspace';
|
|
548
|
+
|
|
549
|
+
// ---------- Utilities ----------
|
|
550
|
+
function $(id) { return document.getElementById(id); }
|
|
551
|
+
|
|
552
|
+
function escapeHtml(s) {
|
|
553
|
+
if (s === null || s === undefined) return '';
|
|
554
|
+
return String(s).replace(/[&<>"']/g, function (c) {
|
|
555
|
+
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function fmtTime(s) {
|
|
560
|
+
if (!s) return '—';
|
|
561
|
+
var d = new Date(s);
|
|
562
|
+
if (isNaN(d.getTime())) return escapeHtml(s);
|
|
563
|
+
return d.toLocaleString();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function hide(el) { if (el) el.classList.add('hidden'); }
|
|
567
|
+
function show(el) { if (el) el.classList.remove('hidden'); }
|
|
568
|
+
|
|
569
|
+
function showError(msg) {
|
|
570
|
+
var banner = $('error-banner');
|
|
571
|
+
var message = $('error-message');
|
|
572
|
+
if (message) message.textContent = msg || '未知错误';
|
|
573
|
+
if (banner) show(banner);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function clearError() {
|
|
577
|
+
var banner = $('error-banner');
|
|
578
|
+
if (banner) hide(banner);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function setRefreshing(isRefreshing) {
|
|
582
|
+
var icon = $('refreshIcon');
|
|
583
|
+
if (!icon) return;
|
|
584
|
+
if (isRefreshing) {
|
|
585
|
+
icon.classList.add('pw-spin');
|
|
586
|
+
} else {
|
|
587
|
+
icon.classList.remove('pw-spin');
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function setLastUpdated() {
|
|
592
|
+
var el = $('lastUpdated');
|
|
593
|
+
if (!el) return;
|
|
594
|
+
el.textContent = '更新于 ' + fmtTime(new Date().toISOString());
|
|
595
|
+
show(el);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ---------- Render: Workspace root ----------
|
|
599
|
+
function renderWorkspaceRoot(root) {
|
|
600
|
+
var el = $('workspace-root');
|
|
601
|
+
if (!el) return;
|
|
602
|
+
if (root) {
|
|
603
|
+
el.textContent = root;
|
|
604
|
+
el.removeAttribute('data-zh');
|
|
605
|
+
el.removeAttribute('data-en');
|
|
606
|
+
} else {
|
|
607
|
+
el.textContent = '未配置工作区根目录';
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// ---------- Render: Projects ----------
|
|
612
|
+
function renderProjects(directories) {
|
|
613
|
+
var countEl = $('projects-count');
|
|
614
|
+
var listEl = $('projects-list');
|
|
615
|
+
var loadingEl = $('projects-loading');
|
|
616
|
+
var emptyEl = $('projects-empty');
|
|
617
|
+
if (!listEl) return;
|
|
618
|
+
|
|
619
|
+
hide(loadingEl);
|
|
620
|
+
hide(emptyEl);
|
|
621
|
+
listEl.innerHTML = '';
|
|
622
|
+
|
|
623
|
+
var dirs = Array.isArray(directories) ? directories : [];
|
|
624
|
+
if (countEl) countEl.textContent = dirs.length + ' 个目录';
|
|
625
|
+
|
|
626
|
+
if (dirs.length === 0) {
|
|
627
|
+
show(emptyEl);
|
|
628
|
+
hide(listEl);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
var html = '';
|
|
633
|
+
for (var i = 0; i < dirs.length; i++) {
|
|
634
|
+
var name = dirs[i];
|
|
635
|
+
var isHidden = String(name).charAt(0) === '.';
|
|
636
|
+
var folderColor = isHidden ? 'var(--pw-accent)' : 'var(--pw-accent)';
|
|
637
|
+
var nameColor = isHidden ? 'var(--pw-text-secondary)' : 'var(--pw-text-primary)';
|
|
638
|
+
var safeName = escapeHtml(name);
|
|
639
|
+
html +=
|
|
640
|
+
'<div class="flex items-center gap-2 py-1.5 px-2 rounded transition-colors" style="color: ' + nameColor + ';" '
|
|
641
|
+
+ 'onmouseover="this.style.backgroundColor=\'var(--pw-bg-hover)\'" '
|
|
642
|
+
+ 'onmouseout="this.style.backgroundColor=\'transparent\'">'
|
|
643
|
+
+ '<svg class="shrink-0" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="' + folderColor + '" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'
|
|
644
|
+
+ '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>'
|
|
645
|
+
+ '</svg>'
|
|
646
|
+
+ '<span class="truncate flex-1 min-w-0" title="' + safeName + '">' + safeName + '/</span>'
|
|
647
|
+
+ '<button class="repo-check-btn shrink-0 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium transition-colors" '
|
|
648
|
+
+ 'data-repo="' + safeName + '" '
|
|
649
|
+
+ 'style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);" '
|
|
650
|
+
+ 'onmouseover="this.style.backgroundColor=\'var(--pw-bg-active)\'" '
|
|
651
|
+
+ 'onmouseout="this.style.backgroundColor=\'var(--pw-bg-hover)\'">'
|
|
652
|
+
+ '<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>'
|
|
653
|
+
+ '<span data-zh="检查" data-en="Check">检查</span>'
|
|
654
|
+
+ '</button>'
|
|
655
|
+
+ '</div>';
|
|
656
|
+
}
|
|
657
|
+
listEl.innerHTML = html;
|
|
658
|
+
show(listEl);
|
|
659
|
+
|
|
660
|
+
// Wire up the check buttons.
|
|
661
|
+
var buttons = listEl.querySelectorAll('.repo-check-btn');
|
|
662
|
+
for (var j = 0; j < buttons.length; j++) {
|
|
663
|
+
buttons[j].addEventListener('click', function (ev) {
|
|
664
|
+
ev.preventDefault();
|
|
665
|
+
var repo = ev.currentTarget.getAttribute('data-repo');
|
|
666
|
+
if (repo) checkRepoStatus(repo, ev.currentTarget);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// ---------- Repo status (on-demand) ----------
|
|
672
|
+
function checkRepoStatus(repo, btnEl) {
|
|
673
|
+
var panelEl = $('repo-status-panel');
|
|
674
|
+
if (!panelEl) return;
|
|
675
|
+
|
|
676
|
+
// Loading state on button.
|
|
677
|
+
var origHtml = btnEl ? btnEl.innerHTML : null;
|
|
678
|
+
if (btnEl) {
|
|
679
|
+
btnEl.disabled = true;
|
|
680
|
+
btnEl.innerHTML = '<span class="inline-block w-2.5 h-2.5 align-middle border-2 border-current border-t-transparent rounded-full pw-spin"></span>';
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
var url = '/api/workspace/' + encodeURIComponent(repo) + '/status';
|
|
684
|
+
fetch(url, { method: 'GET', headers: { 'Accept': 'application/json' }, cache: 'no-store' })
|
|
685
|
+
.then(function (res) {
|
|
686
|
+
return res.json().then(function (data) { return { ok: res.ok, status: res.status, data: data }; });
|
|
687
|
+
})
|
|
688
|
+
.then(function (r) {
|
|
689
|
+
renderRepoStatusPanel(repo, r);
|
|
690
|
+
})
|
|
691
|
+
.catch(function (err) {
|
|
692
|
+
renderRepoStatusPanel(repo, { ok: false, status: 0, data: { error: (err && err.message) || 'network error' } });
|
|
693
|
+
})
|
|
694
|
+
.finally(function () {
|
|
695
|
+
if (btnEl) {
|
|
696
|
+
btnEl.disabled = false;
|
|
697
|
+
btnEl.innerHTML = origHtml;
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function renderRepoStatusPanel(repo, r) {
|
|
703
|
+
var panelEl = $('repo-status-panel');
|
|
704
|
+
if (!panelEl) return;
|
|
705
|
+
show(panelEl);
|
|
706
|
+
|
|
707
|
+
var data = r.data || {};
|
|
708
|
+
if (!r.ok) {
|
|
709
|
+
var errMsg = data.error || ('HTTP ' + r.status);
|
|
710
|
+
panelEl.innerHTML =
|
|
711
|
+
'<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">'
|
|
712
|
+
+ '<div class="flex items-center gap-2 min-w-0">'
|
|
713
|
+
+ '<svg class="shrink-0" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-state-error)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'
|
|
714
|
+
+ '<h3 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(repo) + '</h3>'
|
|
715
|
+
+ '</div>'
|
|
716
|
+
+ '</div>'
|
|
717
|
+
+ '<div class="px-4 py-3 text-xs" style="color: var(--pw-state-error); font-family: var(--pw-font-mono);">' + escapeHtml(errMsg) + '</div>';
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
var isGit = data.is_git_repo !== false;
|
|
722
|
+
var isClean = !!data.is_clean;
|
|
723
|
+
var changed = data.changed_files_count != null ? data.changed_files_count : 0;
|
|
724
|
+
var untracked = data.untracked_count != null ? data.untracked_count : 0;
|
|
725
|
+
var modified = data.modified_count != null ? data.modified_count : 0;
|
|
726
|
+
var shortStatus = data.short_status || '';
|
|
727
|
+
|
|
728
|
+
var headColor = isGit ? (isClean ? 'var(--pw-state-success)' : 'var(--pw-state-warning)') : 'var(--pw-text-tertiary)';
|
|
729
|
+
var stateBadge = isGit
|
|
730
|
+
? (isClean
|
|
731
|
+
? '<span class="text-xs px-2 py-0.5 rounded-md" style="color: var(--pw-state-success); background-color: var(--pw-state-success-bg);">clean</span>'
|
|
732
|
+
: '<span class="text-xs px-2 py-0.5 rounded-md" style="color: var(--pw-state-warning); background-color: var(--pw-state-warning-bg);">dirty</span>')
|
|
733
|
+
: '<span class="text-xs px-2 py-0.5 rounded-md" style="color: var(--pw-text-tertiary); background-color: var(--pw-bg-hover);">not a git repo</span>';
|
|
734
|
+
|
|
735
|
+
var rowsHtml = '';
|
|
736
|
+
if (isGit) {
|
|
737
|
+
rowsHtml +=
|
|
738
|
+
'<div class="flex items-center justify-between gap-3 py-1.5" style="border-bottom: 1px solid var(--pw-border-subtle);">'
|
|
739
|
+
+ '<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);">changed_files_count</span>'
|
|
740
|
+
+ '<span class="text-xs text-right min-w-0 truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(changed)) + '</span>'
|
|
741
|
+
+ '</div>';
|
|
742
|
+
rowsHtml +=
|
|
743
|
+
'<div class="flex items-center justify-between gap-3 py-1.5" style="border-bottom: 1px solid var(--pw-border-subtle);">'
|
|
744
|
+
+ '<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);">modified_count</span>'
|
|
745
|
+
+ '<span class="text-xs text-right min-w-0 truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(modified)) + '</span>'
|
|
746
|
+
+ '</div>';
|
|
747
|
+
rowsHtml +=
|
|
748
|
+
'<div class="flex items-center justify-between gap-3 py-1.5" style="border-bottom: 1px solid var(--pw-border-subtle);">'
|
|
749
|
+
+ '<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);">untracked_count</span>'
|
|
750
|
+
+ '<span class="text-xs text-right min-w-0 truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(untracked)) + '</span>'
|
|
751
|
+
+ '</div>';
|
|
752
|
+
} else if (data.error) {
|
|
753
|
+
rowsHtml +=
|
|
754
|
+
'<div class="py-2 text-xs" style="color: var(--pw-state-error); font-family: var(--pw-font-mono);">' + escapeHtml(String(data.error)) + '</div>';
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
var shortStatusBlock = '';
|
|
758
|
+
if (isGit && shortStatus) {
|
|
759
|
+
shortStatusBlock =
|
|
760
|
+
'<div class="mt-2 rounded-md p-2 text-xs leading-relaxed overflow-auto" style="background-color: #0d1117; color: var(--pw-text-secondary); font-family: var(--pw-font-mono); max-height: 200px;">'
|
|
761
|
+
+ '<pre class="whitespace-pre-wrap break-all">' + escapeHtml(shortStatus) + '</pre>'
|
|
762
|
+
+ '</div>';
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
panelEl.innerHTML =
|
|
766
|
+
'<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">'
|
|
767
|
+
+ '<div class="flex items-center gap-2 min-w-0">'
|
|
768
|
+
+ '<svg class="shrink-0" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="' + headColor + '" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2z"/></svg>'
|
|
769
|
+
+ '<h3 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(repo) + '</h3>'
|
|
770
|
+
+ '</div>'
|
|
771
|
+
+ '<div class="flex items-center gap-2 shrink-0">'
|
|
772
|
+
+ stateBadge
|
|
773
|
+
+ '<button id="repo-status-close" class="px-2 py-0.5 rounded text-xs" style="background-color: var(--pw-bg-hover); color: var(--pw-text-tertiary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);" data-zh="关闭" data-en="Close">关闭</button>'
|
|
774
|
+
+ '</div>'
|
|
775
|
+
+ '</div>'
|
|
776
|
+
+ '<div class="px-4 py-2">' + rowsHtml + '</div>'
|
|
777
|
+
+ shortStatusBlock;
|
|
778
|
+
|
|
779
|
+
var closeBtn = $('repo-status-close');
|
|
780
|
+
if (closeBtn) closeBtn.addEventListener('click', function () { hide(panelEl); });
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// ---------- Render: Config ----------
|
|
784
|
+
function renderConfig(config) {
|
|
785
|
+
var rowsEl = $('config-rows');
|
|
786
|
+
var loadingEl = $('config-loading');
|
|
787
|
+
if (!rowsEl) return;
|
|
788
|
+
|
|
789
|
+
hide(loadingEl);
|
|
790
|
+
rowsEl.innerHTML = '';
|
|
791
|
+
|
|
792
|
+
if (!config) {
|
|
793
|
+
rowsEl.innerHTML =
|
|
794
|
+
'<div class="py-6 text-center">'
|
|
795
|
+
+ '<p class="text-xs" style="color: var(--pw-text-tertiary);">未加载到配置摘要</p>'
|
|
796
|
+
+ '</div>';
|
|
797
|
+
show(rowsEl);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
var profile = config.toolProfile != null ? String(config.toolProfile) : '—';
|
|
802
|
+
var testCount = config.allowedTestCommandsCount != null ? String(config.allowedTestCommandsCount) : '—';
|
|
803
|
+
var direct = config.enableDirectProfile;
|
|
804
|
+
var directHtml;
|
|
805
|
+
if (direct === true) {
|
|
806
|
+
directHtml = '<span style="color: var(--pw-state-success);">已启用</span>';
|
|
807
|
+
} else if (direct === false) {
|
|
808
|
+
directHtml = '<span style="color: var(--pw-text-tertiary);">未启用</span>';
|
|
809
|
+
} else {
|
|
810
|
+
directHtml = '<span style="color: var(--pw-text-tertiary);">—</span>';
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
var rows = [
|
|
814
|
+
{ label: 'toolProfile', valueHtml: '<span style="color: var(--pw-state-warning);">' + escapeHtml(profile) + '</span>' },
|
|
815
|
+
{ label: 'allowedTestCommandsCount', valueHtml: '<span style="color: var(--pw-text-primary);">' + escapeHtml(testCount) + '</span>' },
|
|
816
|
+
{ label: 'enableDirectProfile', valueHtml: directHtml }
|
|
817
|
+
];
|
|
818
|
+
|
|
819
|
+
var html = '';
|
|
820
|
+
for (var i = 0; i < rows.length; i++) {
|
|
821
|
+
var r = rows[i];
|
|
822
|
+
var last = i === rows.length - 1;
|
|
823
|
+
var borderStyle = last ? '' : ' style="border-bottom: 1px solid var(--pw-border-subtle);"';
|
|
824
|
+
html +=
|
|
825
|
+
'<div class="flex items-center justify-between gap-3 py-2"' + borderStyle + '>'
|
|
826
|
+
+ '<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);">' + escapeHtml(r.label) + '</span>'
|
|
827
|
+
+ '<span class="text-xs text-right min-w-0 truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + r.valueHtml + '</span>'
|
|
828
|
+
+ '</div>';
|
|
829
|
+
}
|
|
830
|
+
rowsEl.innerHTML = html;
|
|
831
|
+
show(rowsEl);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// ---------- Render: Agents ----------
|
|
835
|
+
function renderAgents(agents) {
|
|
836
|
+
var countEl = $('agents-count');
|
|
837
|
+
var gridEl = $('agents-grid');
|
|
838
|
+
var loadingEl = $('agents-loading');
|
|
839
|
+
var emptyEl = $('agents-empty');
|
|
840
|
+
if (!gridEl) return;
|
|
841
|
+
|
|
842
|
+
hide(loadingEl);
|
|
843
|
+
hide(emptyEl);
|
|
844
|
+
gridEl.innerHTML = '';
|
|
845
|
+
|
|
846
|
+
var list = Array.isArray(agents) ? agents : [];
|
|
847
|
+
if (countEl) countEl.textContent = list.length + ' 个 Agent';
|
|
848
|
+
|
|
849
|
+
if (list.length === 0) {
|
|
850
|
+
show(emptyEl);
|
|
851
|
+
hide(gridEl);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
var html = '';
|
|
856
|
+
for (var i = 0; i < list.length; i++) {
|
|
857
|
+
var a = list[i] || {};
|
|
858
|
+
var available = !!a.available;
|
|
859
|
+
var dotColor = available ? 'var(--pw-state-success)' : 'var(--pw-state-error)';
|
|
860
|
+
var badgeColor = available ? 'var(--pw-state-success)' : 'var(--pw-state-error)';
|
|
861
|
+
var badgeBg = available ? 'var(--pw-state-success-bg)' : 'var(--pw-state-error-bg)';
|
|
862
|
+
var badgeText = available ? '可用' : '不可用';
|
|
863
|
+
var reason = a.reason ? escapeHtml(a.reason) : '—';
|
|
864
|
+
var command = a.command ? escapeHtml(a.command) : '—';
|
|
865
|
+
var checkedAt = a.checked_at ? escapeHtml(fmtTime(a.checked_at)) : '—';
|
|
866
|
+
|
|
867
|
+
html +=
|
|
868
|
+
'<div class="rounded-lg p-4" style="background-color: var(--pw-bg); border: 1px solid var(--pw-border-subtle);">'
|
|
869
|
+
+ '<div class="flex items-center justify-between mb-3">'
|
|
870
|
+
+ '<div class="flex items-center gap-2 min-w-0">'
|
|
871
|
+
+ '<span class="shrink-0 w-2 h-2 rounded-full" style="background-color: ' + dotColor + ';"></span>'
|
|
872
|
+
+ '<h3 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(a.name || '—') + '</h3>'
|
|
873
|
+
+ '</div>'
|
|
874
|
+
+ '<span class="inline-flex items-center justify-center text-xs font-medium px-2 py-0.5 rounded-md whitespace-nowrap" style="color: ' + badgeColor + '; background-color: ' + badgeBg + ';">' + badgeText + '</span>'
|
|
875
|
+
+ '</div>'
|
|
876
|
+
+ '<div class="mb-3 px-3 py-2 rounded" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border-subtle);">'
|
|
877
|
+
+ '<div class="flex items-center gap-2">'
|
|
878
|
+
+ '<span class="text-xs shrink-0" style="color: var(--pw-text-tertiary);">command:</span>'
|
|
879
|
+
+ '<code class="text-xs block truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + command + '</code>'
|
|
880
|
+
+ '</div>'
|
|
881
|
+
+ '</div>'
|
|
882
|
+
+ '<div class="flex items-start justify-between gap-3">'
|
|
883
|
+
+ '<div class="min-w-0 flex-1">'
|
|
884
|
+
+ '<div class="text-xs" style="color: var(--pw-text-tertiary);">reason</div>'
|
|
885
|
+
+ '<div class="text-xs mt-0.5 break-words" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + reason + '">' + reason + '</div>'
|
|
886
|
+
+ '</div>'
|
|
887
|
+
+ '<div class="shrink-0 text-right">'
|
|
888
|
+
+ '<div class="text-xs" style="color: var(--pw-text-tertiary);">checked_at</div>'
|
|
889
|
+
+ '<div class="text-xs mt-0.5" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + checkedAt + '</div>'
|
|
890
|
+
+ '</div>'
|
|
891
|
+
+ '</div>'
|
|
892
|
+
+ '</div>';
|
|
893
|
+
}
|
|
894
|
+
gridEl.innerHTML = html;
|
|
895
|
+
show(gridEl);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// ---------- Network ----------
|
|
899
|
+
function loadWorkspace() {
|
|
900
|
+
clearError();
|
|
901
|
+
setRefreshing(true);
|
|
902
|
+
fetch(API_URL, {
|
|
903
|
+
method: 'GET',
|
|
904
|
+
headers: { 'Accept': 'application/json' },
|
|
905
|
+
cache: 'no-store'
|
|
906
|
+
})
|
|
907
|
+
.then(function (res) {
|
|
908
|
+
if (!res.ok) {
|
|
909
|
+
return res.text().then(function (body) {
|
|
910
|
+
throw new Error('HTTP ' + res.status + (body ? (': ' + body.slice(0, 200)) : ''));
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
return res.json();
|
|
914
|
+
})
|
|
915
|
+
.then(function (data) {
|
|
916
|
+
data = data || {};
|
|
917
|
+
renderWorkspaceRoot(data.workspace_root);
|
|
918
|
+
renderProjects(data.directories);
|
|
919
|
+
renderAgents(data.agents);
|
|
920
|
+
renderConfig(data.config);
|
|
921
|
+
setLastUpdated();
|
|
922
|
+
})
|
|
923
|
+
.catch(function (err) {
|
|
924
|
+
var msg = (err && err.message) ? err.message : '未知错误';
|
|
925
|
+
if (err && err.name === 'TypeError' && /fetch/i.test(msg)) {
|
|
926
|
+
msg = '无法连接到 ' + API_URL + '(请确认 PatchWarden 服务正在运行)';
|
|
927
|
+
}
|
|
928
|
+
showError(msg);
|
|
929
|
+
})
|
|
930
|
+
.finally(function () {
|
|
931
|
+
setRefreshing(false);
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// ---------- Event wiring ----------
|
|
936
|
+
var refreshBtn = $('refreshBtn');
|
|
937
|
+
if (refreshBtn) refreshBtn.addEventListener('click', loadWorkspace);
|
|
938
|
+
|
|
939
|
+
var retryBtn = $('retry-btn');
|
|
940
|
+
if (retryBtn) retryBtn.addEventListener('click', loadWorkspace);
|
|
941
|
+
|
|
942
|
+
// Initial load
|
|
943
|
+
loadWorkspace();
|
|
944
|
+
})();
|
|
945
|
+
</script>
|
|
946
|
+
</body>
|
|
947
|
+
</html>
|