patchwarden 1.1.0 → 1.5.1
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/README.en.md +83 -8
- package/README.md +78 -10
- package/dist/assessments/agentAssessor.d.ts +1 -1
- package/dist/assessments/agentAssessor.js +3 -3
- package/dist/assessments/assessmentStore.d.ts +1 -1
- package/dist/assessments/assessmentStore.js +2 -2
- package/dist/assessments/confirmCli.js +5 -4
- package/dist/config.d.ts +7 -0
- package/dist/config.js +34 -0
- package/dist/control/middleware/auth.d.ts +10 -0
- package/dist/control/middleware/auth.js +8 -0
- package/dist/control/middleware/static.d.ts +3 -0
- package/dist/control/middleware/static.js +65 -0
- package/dist/control/routes/audit.d.ts +15 -0
- package/dist/control/routes/audit.js +277 -0
- package/dist/control/routes/evidence.d.ts +4 -0
- package/dist/control/routes/evidence.js +96 -0
- package/dist/control/routes/lineage.d.ts +3 -0
- package/dist/control/routes/lineage.js +71 -0
- package/dist/control/routes/policy.d.ts +3 -0
- package/dist/control/routes/policy.js +81 -0
- package/dist/control/routes/process.d.ts +5 -0
- package/dist/control/routes/process.js +200 -0
- package/dist/control/routes/sessions.d.ts +22 -0
- package/dist/control/routes/sessions.js +224 -0
- package/dist/control/routes/status.d.ts +6 -0
- package/dist/control/routes/status.js +250 -0
- package/dist/control/routes/taskActions.d.ts +21 -0
- package/dist/control/routes/taskActions.js +233 -0
- package/dist/control/routes/tasks.d.ts +20 -0
- package/dist/control/routes/tasks.js +310 -0
- package/dist/control/routes/workspace.d.ts +15 -0
- package/dist/control/routes/workspace.js +193 -0
- package/dist/control/runtime.d.ts +91 -0
- package/dist/control/runtime.js +392 -0
- package/dist/control/server.d.ts +13 -0
- package/dist/control/server.js +479 -0
- package/dist/control/shared.d.ts +35 -0
- package/dist/control/shared.js +288 -0
- package/dist/controlCenter.d.ts +6 -0
- package/dist/controlCenter.js +7 -2001
- package/dist/direct/directGuards.js +30 -8
- package/dist/direct/directSessionStore.d.ts +2 -0
- package/dist/direct/directVerification.js +7 -0
- package/dist/doctor.d.ts +18 -1
- package/dist/doctor.js +579 -348
- package/dist/goal/goalReport.d.ts +54 -0
- package/dist/goal/goalReport.js +204 -0
- package/dist/goal/goalStatus.d.ts +6 -0
- package/dist/goal/specKitImport.d.ts +63 -0
- package/dist/goal/specKitImport.js +220 -0
- package/dist/goal/subgoalSync.js +2 -1
- package/dist/httpServer.js +15 -12
- package/dist/index.js +7 -4
- package/dist/logging.d.ts +7 -1
- package/dist/logging.js +8 -0
- package/dist/policy/projectPolicy.d.ts +55 -0
- package/dist/policy/projectPolicy.js +286 -0
- package/dist/runner/changeCapture.d.ts +3 -3
- package/dist/runner/changeCapture.js +63 -39
- package/dist/runner/cli.js +7 -6
- package/dist/runner/postTaskCleanup.js +26 -2
- package/dist/runner/runTask.js +245 -221
- package/dist/runner/simpleProcess.js +4 -4
- package/dist/runner/watch.js +17 -14
- package/dist/security/contentRedaction.d.ts +6 -0
- package/dist/security/contentRedaction.js +22 -0
- package/dist/smoke-test.js +261 -255
- package/dist/test/unit/apply-patch.test.d.ts +1 -0
- package/dist/test/unit/apply-patch.test.js +225 -0
- package/dist/test/unit/create-task.test.d.ts +1 -0
- package/dist/test/unit/create-task.test.js +197 -0
- package/dist/test/unit/direct-guards.test.js +124 -9
- package/dist/test/unit/evidence-pack.test.d.ts +1 -0
- package/dist/test/unit/evidence-pack.test.js +224 -0
- package/dist/test/unit/get-task-status.test.d.ts +1 -0
- package/dist/test/unit/get-task-status.test.js +174 -0
- package/dist/test/unit/get-task-summary.test.d.ts +1 -0
- package/dist/test/unit/get-task-summary.test.js +146 -0
- package/dist/test/unit/goal-report.test.d.ts +1 -0
- package/dist/test/unit/goal-report.test.js +159 -0
- package/dist/test/unit/goal-subgoal-task.test.js +6 -6
- package/dist/test/unit/goal-tools-registry.test.js +6 -4
- package/dist/test/unit/path-guard.test.js +24 -0
- package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
- package/dist/test/unit/project-policy-release-mode.test.js +125 -0
- package/dist/test/unit/run-task-loop.test.d.ts +1 -0
- package/dist/test/unit/run-task-loop.test.js +380 -0
- package/dist/test/unit/schema-drift-check.test.js +10 -9
- package/dist/test/unit/spec-kit-import.test.d.ts +1 -0
- package/dist/test/unit/spec-kit-import.test.js +341 -0
- package/dist/test/unit/wait-for-task.test.d.ts +1 -0
- package/dist/test/unit/wait-for-task.test.js +144 -0
- package/dist/tools/auditTask.d.ts +8 -63
- package/dist/tools/auditTask.js +12 -8
- package/dist/tools/createDirectSession.d.ts +1 -1
- package/dist/tools/createDirectSession.js +2 -2
- package/dist/tools/createTask.d.ts +2 -2
- package/dist/tools/createTask.js +4 -4
- package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
- package/dist/tools/dispatch/coreDispatch.js +282 -0
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +14 -0
- package/dist/tools/dispatch/diagnosticDispatch.js +78 -0
- package/dist/tools/dispatch/directDispatch.d.ts +8 -0
- package/dist/tools/dispatch/directDispatch.js +115 -0
- package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
- package/dist/tools/dispatch/goalDispatch.js +91 -0
- package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
- package/dist/tools/dispatch/releaseDispatch.js +45 -0
- package/dist/tools/dispatch/types.d.ts +23 -0
- package/dist/tools/dispatch/types.js +15 -0
- package/dist/tools/evidencePack.d.ts +45 -0
- package/dist/tools/evidencePack.js +375 -0
- package/dist/tools/finalizeDirectSession.d.ts +1 -1
- package/dist/tools/finalizeDirectSession.js +3 -3
- package/dist/tools/goalSubgoalTask.d.ts +1 -1
- package/dist/tools/goalSubgoalTask.js +2 -2
- package/dist/tools/healthCheck.js +3 -3
- package/dist/tools/recommendAgentForTask.d.ts +19 -0
- package/dist/tools/recommendAgentForTask.js +56 -0
- package/dist/tools/registry.d.ts +3 -3
- package/dist/tools/registry.js +333 -402
- package/dist/tools/releaseMode.d.ts +50 -0
- package/dist/tools/releaseMode.js +370 -0
- package/dist/tools/retryTask.d.ts +2 -2
- package/dist/tools/retryTask.js +2 -2
- package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
- package/dist/tools/runDirectVerificationBundle.js +64 -0
- package/dist/tools/runTaskLoop.d.ts +57 -0
- package/dist/tools/runTaskLoop.js +417 -0
- package/dist/tools/runVerification.d.ts +4 -0
- package/dist/tools/runVerification.js +4 -0
- package/dist/tools/safeViews.d.ts +8 -2
- package/dist/tools/safeViews.js +4 -2
- package/dist/tools/taskLineage.d.ts +91 -0
- package/dist/tools/taskLineage.js +175 -0
- package/dist/tools/toolCatalog.d.ts +2 -2
- package/dist/tools/toolCatalog.js +6 -0
- package/dist/tools/toolRegistry.js +132 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/docs/CODE_WIKI.md +983 -0
- package/docs/agentseal-integration.md +150 -0
- package/docs/architecture.md +63 -0
- package/docs/assets/patchwarden-oss-demo.gif +0 -0
- package/docs/chatgpt-usage.md +31 -0
- package/docs/control-center/README.md +9 -0
- package/docs/dashboard-overview.md +86 -0
- package/docs/demo.md +8 -0
- package/docs/direct-session-workflow.md +98 -0
- package/docs/evidence-pack-schema.md +215 -0
- package/docs/execution-plan-2026-07-09.md +315 -0
- package/docs/lineage-evidence-pack-workflow.md +127 -0
- package/docs/mcp-inspector-testing.md +200 -0
- package/docs/open-source-application.md +162 -0
- package/docs/opencode-worker.md +151 -0
- package/docs/openhands-worker.md +181 -0
- package/docs/release-evidence.md +72 -0
- package/docs/roadmap-execution-and-acceptance.md +365 -0
- package/docs/spec-kit-integration.md +131 -0
- package/docs/task-safe-review-workflow.md +98 -0
- package/docs/threat-model.md +79 -0
- package/docs/user-feedback.md +40 -0
- package/docs/why-patchwarden.md +110 -0
- package/package.json +3 -3
- package/scripts/checks/control-center-smoke.js +443 -0
- package/scripts/checks/control-smoke.js +2 -2
- package/scripts/checks/lifecycle-smoke.js +29 -23
- package/scripts/checks/mcp-manifest-check.js +12 -0
- package/scripts/checks/mcp-smoke.js +33 -7
- package/scripts/checks/watcher-supervisor-smoke.js +1 -1
- package/scripts/generate-demo-gif.py +320 -0
- package/src/assessments/agentAssessor.ts +3 -3
- package/src/assessments/assessmentStore.ts +2 -2
- package/src/assessments/confirmCli.ts +5 -4
- package/src/config.ts +37 -0
- package/src/control/middleware/auth.ts +17 -0
- package/src/control/middleware/static.ts +71 -0
- package/src/control/routes/audit.ts +321 -0
- package/src/control/routes/evidence.ts +107 -0
- package/src/control/routes/lineage.ts +92 -0
- package/src/control/routes/policy.ts +81 -0
- package/src/control/routes/process.ts +204 -0
- package/src/control/routes/sessions.ts +251 -0
- package/src/control/routes/status.ts +325 -0
- package/src/control/routes/taskActions.ts +248 -0
- package/src/control/routes/tasks.ts +323 -0
- package/src/control/routes/workspace.ts +203 -0
- package/src/control/runtime.ts +472 -0
- package/src/control/server.ts +471 -0
- package/src/control/shared.ts +294 -0
- package/src/controlCenter.ts +7 -2150
- package/src/direct/directGuards.ts +28 -7
- package/src/direct/directSessionStore.ts +2 -0
- package/src/direct/directVerification.ts +7 -0
- package/src/doctor.ts +741 -481
- package/src/goal/goalReport.ts +271 -0
- package/src/goal/goalStatus.ts +6 -0
- package/src/goal/specKitImport.ts +355 -0
- package/src/goal/subgoalSync.ts +4 -2
- package/src/httpServer.ts +17 -14
- package/src/index.ts +7 -4
- package/src/logging.ts +10 -1
- package/src/policy/projectPolicy.ts +344 -0
- package/src/runner/changeCapture.ts +70 -42
- package/src/runner/cli.ts +7 -6
- package/src/runner/postTaskCleanup.ts +26 -2
- package/src/runner/runTask.ts +325 -223
- package/src/runner/simpleProcess.ts +4 -4
- package/src/runner/watch.ts +17 -14
- package/src/security/contentRedaction.ts +29 -0
- package/src/smoke-test.ts +254 -252
- package/src/test/unit/apply-patch.test.ts +293 -0
- package/src/test/unit/create-task.test.ts +255 -0
- package/src/test/unit/direct-guards.test.ts +178 -8
- package/src/test/unit/evidence-pack.test.ts +251 -0
- package/src/test/unit/get-task-status.test.ts +203 -0
- package/src/test/unit/get-task-summary.test.ts +173 -0
- package/src/test/unit/goal-report.test.ts +189 -0
- package/src/test/unit/goal-subgoal-task.test.ts +6 -6
- package/src/test/unit/goal-tools-registry.test.ts +7 -5
- package/src/test/unit/path-guard.test.ts +30 -0
- package/src/test/unit/project-policy-release-mode.test.ts +156 -0
- package/src/test/unit/run-task-loop.test.ts +425 -0
- package/src/test/unit/schema-drift-check.test.ts +11 -9
- package/src/test/unit/spec-kit-import.test.ts +429 -0
- package/src/test/unit/wait-for-task.test.ts +176 -0
- package/src/tools/auditTask.ts +99 -59
- package/src/tools/createDirectSession.ts +3 -3
- package/src/tools/createTask.ts +7 -7
- package/src/tools/dispatch/coreDispatch.ts +374 -0
- package/src/tools/dispatch/diagnosticDispatch.ts +101 -0
- package/src/tools/dispatch/directDispatch.ts +167 -0
- package/src/tools/dispatch/goalDispatch.ts +127 -0
- package/src/tools/dispatch/releaseDispatch.ts +65 -0
- package/src/tools/dispatch/types.ts +24 -0
- package/src/tools/evidencePack.ts +490 -0
- package/src/tools/finalizeDirectSession.ts +4 -4
- package/src/tools/goalSubgoalTask.ts +2 -2
- package/src/tools/healthCheck.ts +3 -3
- package/src/tools/listWorkspace.ts +71 -71
- package/src/tools/recommendAgentForTask.ts +79 -0
- package/src/tools/registry.ts +352 -509
- package/src/tools/releaseMode.ts +450 -0
- package/src/tools/retryTask.ts +2 -2
- package/src/tools/runDirectVerificationBundle.ts +98 -0
- package/src/tools/runTaskLoop.ts +526 -0
- package/src/tools/runVerification.ts +8 -0
- package/src/tools/safeViews.ts +4 -2
- package/src/tools/taskLineage.ts +300 -0
- package/src/tools/toolCatalog.ts +6 -0
- package/src/tools/toolRegistry.ts +132 -0
- package/src/version.ts +2 -2
- package/ui/pages/audit.html +192 -3
- package/ui/pages/dashboard.html +1070 -14
- package/ui/pages/direct-sessions.html +505 -53
- package/ui/pages/task-detail.html +456 -438
- package/ui/pages/tasks.html +598 -61
|
@@ -101,6 +101,76 @@ html.dark {
|
|
|
101
101
|
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
102
102
|
.pw-spin { animation: pw-spin 1s linear infinite; }
|
|
103
103
|
@keyframes pw-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
104
|
+
/* Toast notifications */
|
|
105
|
+
.pw-toast {
|
|
106
|
+
pointer-events: auto;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: flex-start;
|
|
109
|
+
gap: 8px;
|
|
110
|
+
padding: 10px 12px;
|
|
111
|
+
border-radius: 8px;
|
|
112
|
+
border: 1px solid var(--pw-border);
|
|
113
|
+
background-color: var(--pw-bg-surface);
|
|
114
|
+
box-shadow: var(--pw-shadow-float);
|
|
115
|
+
font-family: var(--pw-font-sans);
|
|
116
|
+
font-size: 12px;
|
|
117
|
+
color: var(--pw-text-primary);
|
|
118
|
+
animation: pw-toast-in 200ms ease;
|
|
119
|
+
max-width: 360px;
|
|
120
|
+
}
|
|
121
|
+
.pw-toast.pw-toast-success { border-left: 3px solid var(--pw-state-success); }
|
|
122
|
+
.pw-toast.pw-toast-warning { border-left: 3px solid var(--pw-state-warning); }
|
|
123
|
+
.pw-toast.pw-toast-error { border-left: 3px solid var(--pw-state-error); }
|
|
124
|
+
.pw-toast.pw-toast-info { border-left: 3px solid var(--pw-state-info); }
|
|
125
|
+
.pw-toast .pw-toast-title { font-weight: 600; margin-bottom: 2px; }
|
|
126
|
+
.pw-toast .pw-toast-msg { color: var(--pw-text-secondary); word-break: break-word; }
|
|
127
|
+
.pw-toast .pw-toast-close { margin-left: auto; cursor: pointer; color: var(--pw-text-tertiary); font-size: 14px; line-height: 1; padding: 0 2px; }
|
|
128
|
+
.pw-toast .pw-toast-close:hover { color: var(--pw-text-primary); }
|
|
129
|
+
@keyframes pw-toast-in {
|
|
130
|
+
from { opacity: 0; transform: translateX(20px); }
|
|
131
|
+
to { opacity: 1; transform: translateX(0); }
|
|
132
|
+
}
|
|
133
|
+
.pw-toast.pw-toast-leaving { animation: pw-toast-out 200ms ease forwards; }
|
|
134
|
+
@keyframes pw-toast-out {
|
|
135
|
+
to { opacity: 0; transform: translateX(20px); }
|
|
136
|
+
}
|
|
137
|
+
/* Quick action buttons */
|
|
138
|
+
.pw-quick-btn {
|
|
139
|
+
display: inline-flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
gap: 4px;
|
|
142
|
+
padding: 3px 8px;
|
|
143
|
+
border-radius: 4px;
|
|
144
|
+
border: 1px solid var(--pw-border-subtle);
|
|
145
|
+
background-color: var(--pw-bg-hover);
|
|
146
|
+
color: var(--pw-text-secondary);
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
transition: background-color var(--pw-transition-fast), color var(--pw-transition-fast);
|
|
149
|
+
text-decoration: none;
|
|
150
|
+
font-size: 11px;
|
|
151
|
+
font-family: var(--pw-font-sans);
|
|
152
|
+
white-space: nowrap;
|
|
153
|
+
}
|
|
154
|
+
.pw-quick-btn:hover {
|
|
155
|
+
background-color: var(--pw-bg-active);
|
|
156
|
+
color: var(--pw-text-primary);
|
|
157
|
+
}
|
|
158
|
+
.pw-quick-btn:disabled {
|
|
159
|
+
opacity: 0.5;
|
|
160
|
+
cursor: not-allowed;
|
|
161
|
+
}
|
|
162
|
+
.pw-quick-btn.pw-quick-danger:hover {
|
|
163
|
+
background-color: var(--pw-state-error-bg);
|
|
164
|
+
color: var(--pw-state-error);
|
|
165
|
+
border-color: var(--pw-state-error);
|
|
166
|
+
}
|
|
167
|
+
.pw-quick-btn.pw-quick-accent {
|
|
168
|
+
color: var(--pw-accent);
|
|
169
|
+
border-color: var(--pw-accent-muted);
|
|
170
|
+
}
|
|
171
|
+
.pw-quick-btn.pw-quick-accent:hover {
|
|
172
|
+
background-color: var(--pw-accent-muted);
|
|
173
|
+
}
|
|
104
174
|
</style>
|
|
105
175
|
</head>
|
|
106
176
|
<body class="flex h-screen overflow-hidden">
|
|
@@ -261,6 +331,20 @@ html.dark {
|
|
|
261
331
|
<!-- ===== Session Detail ===== -->
|
|
262
332
|
<div id="session-detail" class="hidden flex flex-col gap-6"></div>
|
|
263
333
|
|
|
334
|
+
<!-- ===== Toast Container ===== -->
|
|
335
|
+
<div id="toast-container" class="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm pointer-events-none"></div>
|
|
336
|
+
|
|
337
|
+
<!-- ===== SAFE MODAL ===== -->
|
|
338
|
+
<div id="safe-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4" style="background-color: rgba(0,0,0,0.6);">
|
|
339
|
+
<div class="rounded-lg w-full max-w-2xl max-h-[80vh] flex flex-col" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); box-shadow: var(--pw-shadow-modal);">
|
|
340
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
341
|
+
<h3 id="safe-modal-title" class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">—</h3>
|
|
342
|
+
<button id="safe-modal-close" class="text-lg leading-none" style="color: var(--pw-text-tertiary);" type="button">×</button>
|
|
343
|
+
</div>
|
|
344
|
+
<div id="safe-modal-body" class="flex-1 overflow-auto px-4 py-3 text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono); white-space: pre-wrap; word-break: break-all;"></div>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
264
348
|
</main>
|
|
265
349
|
</div>
|
|
266
350
|
|
|
@@ -288,6 +372,7 @@ html.dark {
|
|
|
288
372
|
'use strict';
|
|
289
373
|
|
|
290
374
|
var API_URL = '/api/direct-sessions';
|
|
375
|
+
var controlToken = null;
|
|
291
376
|
|
|
292
377
|
function $(id) { return document.getElementById(id); }
|
|
293
378
|
function hide(el) { if (el) el.classList.add('hidden'); }
|
|
@@ -316,6 +401,13 @@ html.dark {
|
|
|
316
401
|
+ ' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes()) + ':' + pad2(d.getSeconds());
|
|
317
402
|
}
|
|
318
403
|
|
|
404
|
+
function shortId(sid) {
|
|
405
|
+
if (!sid) return '—';
|
|
406
|
+
var s = String(sid);
|
|
407
|
+
if (s.length <= 20) return s;
|
|
408
|
+
return s.slice(0, 12) + '…' + s.slice(-6);
|
|
409
|
+
}
|
|
410
|
+
|
|
319
411
|
function showError(msg) {
|
|
320
412
|
var banner = $('error-banner');
|
|
321
413
|
var message = $('error-message');
|
|
@@ -377,7 +469,8 @@ html.dark {
|
|
|
377
469
|
success: { fg: 'var(--pw-state-success)', bg: 'var(--pw-state-success-bg)' },
|
|
378
470
|
warning: { fg: 'var(--pw-state-warning)', bg: 'var(--pw-state-warning-bg)' },
|
|
379
471
|
error: { fg: 'var(--pw-state-error)', bg: 'var(--pw-state-error-bg)' },
|
|
380
|
-
muted: { fg: 'var(--pw-text-tertiary)', bg: 'var(--pw-bg-hover)' }
|
|
472
|
+
muted: { fg: 'var(--pw-text-tertiary)', bg: 'var(--pw-bg-hover)' },
|
|
473
|
+
info: { fg: 'var(--pw-state-info)', bg: 'var(--pw-state-info-bg)' }
|
|
381
474
|
};
|
|
382
475
|
|
|
383
476
|
function badgeHtml(text, color) {
|
|
@@ -385,7 +478,131 @@ html.dark {
|
|
|
385
478
|
return '<span class="text-xs px-2 py-0.5 rounded-md whitespace-nowrap" style="color: ' + c.fg + '; background-color: ' + c.bg + ';">' + escapeHtml(text) + '</span>';
|
|
386
479
|
}
|
|
387
480
|
|
|
388
|
-
// ----------
|
|
481
|
+
// ---------- Toast notifications ----------
|
|
482
|
+
function showToast(kind, title, msg) {
|
|
483
|
+
var container = $('toast-container');
|
|
484
|
+
if (!container) return;
|
|
485
|
+
var el = document.createElement('div');
|
|
486
|
+
el.className = 'pw-toast pw-toast-' + (kind || 'info');
|
|
487
|
+
el.innerHTML =
|
|
488
|
+
'<div class="min-w-0 flex-1">'
|
|
489
|
+
+ '<div class="pw-toast-title">' + escapeHtml(title || '') + '</div>'
|
|
490
|
+
+ (msg ? '<div class="pw-toast-msg">' + escapeHtml(msg) + '</div>' : '')
|
|
491
|
+
+ '</div>'
|
|
492
|
+
+ '<span class="pw-toast-close" title="关闭">×</span>';
|
|
493
|
+
var closeBtn = el.querySelector('.pw-toast-close');
|
|
494
|
+
if (closeBtn) closeBtn.addEventListener('click', function () { dismissToast(el); });
|
|
495
|
+
container.appendChild(el);
|
|
496
|
+
setTimeout(function () { dismissToast(el); }, 8000);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function dismissToast(el) {
|
|
500
|
+
if (!el || el.classList.contains('pw-toast-leaving')) return;
|
|
501
|
+
el.classList.add('pw-toast-leaving');
|
|
502
|
+
setTimeout(function () {
|
|
503
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
504
|
+
}, 220);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ---------- Clipboard + Safe Modal ----------
|
|
508
|
+
function copyToClipboard(text, label) {
|
|
509
|
+
if (!text) return;
|
|
510
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
511
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
512
|
+
showToast('success', '已复制', (label || '内容') + '已复制到剪贴板');
|
|
513
|
+
}).catch(function () {
|
|
514
|
+
showToast('error', '复制失败', '请手动复制');
|
|
515
|
+
});
|
|
516
|
+
} else {
|
|
517
|
+
showToast('warning', '复制不支持', '浏览器不支持剪贴板 API');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function showSafeModal(title, data) {
|
|
522
|
+
var modal = $('safe-modal');
|
|
523
|
+
var titleEl = $('safe-modal-title');
|
|
524
|
+
var bodyEl = $('safe-modal-body');
|
|
525
|
+
if (!modal || !titleEl || !bodyEl) return;
|
|
526
|
+
titleEl.textContent = title || '—';
|
|
527
|
+
var content;
|
|
528
|
+
if (data && typeof data === 'object') {
|
|
529
|
+
content = JSON.stringify(data, null, 2);
|
|
530
|
+
} else {
|
|
531
|
+
content = String(data || '');
|
|
532
|
+
}
|
|
533
|
+
bodyEl.textContent = content;
|
|
534
|
+
modal.classList.remove('hidden');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function closeSafeModal() {
|
|
538
|
+
var modal = $('safe-modal');
|
|
539
|
+
if (modal) modal.classList.add('hidden');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ---------- Control token + POST actions ----------
|
|
543
|
+
async function loadToken() {
|
|
544
|
+
try {
|
|
545
|
+
const res = await fetch('/control-token.json');
|
|
546
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
547
|
+
const data = await res.json();
|
|
548
|
+
controlToken = data.token || null;
|
|
549
|
+
} catch (err) {
|
|
550
|
+
controlToken = null;
|
|
551
|
+
console.error('loadToken failed:', err);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function postAction(url) {
|
|
556
|
+
if (!controlToken) await loadToken();
|
|
557
|
+
const res = await fetch(url, {
|
|
558
|
+
method: 'POST',
|
|
559
|
+
headers: { 'X-PatchWarden-Control-Token': controlToken || '' }
|
|
560
|
+
});
|
|
561
|
+
let data = null;
|
|
562
|
+
try { data = await res.json(); } catch (_) { /* ignore */ }
|
|
563
|
+
if (!res.ok) {
|
|
564
|
+
const msg = (data && (data.error || data.reason || data.message)) || ('HTTP ' + res.status);
|
|
565
|
+
throw new Error(msg);
|
|
566
|
+
}
|
|
567
|
+
return data;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ---------- Session classification ----------
|
|
571
|
+
function classifySession(s) {
|
|
572
|
+
var nowMs = Date.now();
|
|
573
|
+
var expiresMs = s.expires_at ? Date.parse(s.expires_at) : NaN;
|
|
574
|
+
var expired = Number.isFinite(expiresMs) && expiresMs < nowMs;
|
|
575
|
+
if (expired) return 'expired';
|
|
576
|
+
if (s.audited) return 'audited';
|
|
577
|
+
if (s.finalized) return 'finalized';
|
|
578
|
+
return 'active';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function verificationStatusText(vs) {
|
|
582
|
+
if (!vs || typeof vs !== 'object') return '—';
|
|
583
|
+
// verification_summary is the last run object: { command, exit_code, passed, timed_out, ... }
|
|
584
|
+
if (vs.passed === true) return 'passed';
|
|
585
|
+
if (vs.timed_out === true) return 'timed_out';
|
|
586
|
+
if (vs.passed === false) return 'failed';
|
|
587
|
+
if (vs.exit_code != null) return String(vs.exit_code);
|
|
588
|
+
return '—';
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function verificationStatusColor(vs) {
|
|
592
|
+
if (!vs || typeof vs !== 'object') return 'muted';
|
|
593
|
+
if (vs.passed === true) return 'success';
|
|
594
|
+
if (vs.timed_out === true || vs.passed === false) return 'error';
|
|
595
|
+
return 'muted';
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ---------- Render: sessions list (grouped) ----------
|
|
599
|
+
var GROUP_LABELS = {
|
|
600
|
+
active: { zh: '进行中', en: 'Active', color: 'info' },
|
|
601
|
+
finalized: { zh: '已 Finalize', en: 'Finalized', color: 'success' },
|
|
602
|
+
audited: { zh: '已审计', en: 'Audited', color: 'success' },
|
|
603
|
+
expired: { zh: '已过期', en: 'Expired', color: 'warning' }
|
|
604
|
+
};
|
|
605
|
+
|
|
389
606
|
function renderSessions(sessions) {
|
|
390
607
|
var listEl = $('sessions-list');
|
|
391
608
|
listEl.innerHTML = '';
|
|
@@ -394,69 +611,292 @@ html.dark {
|
|
|
394
611
|
return;
|
|
395
612
|
}
|
|
396
613
|
|
|
397
|
-
var
|
|
614
|
+
var groups = { active: [], finalized: [], audited: [], expired: [] };
|
|
398
615
|
for (var i = 0; i < sessions.length; i++) {
|
|
399
616
|
var s = sessions[i] || {};
|
|
400
|
-
var
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
html +=
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
+ '<div class="flex items-center gap-2 mb-1">'
|
|
420
|
-
+ '<h3 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">' + escapeHtml(titleText) + '</h3>'
|
|
421
|
-
+ '</div>'
|
|
422
|
-
+ '<code class="text-xs break-all" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);">' + escapeHtml(sid) + '</code>'
|
|
423
|
-
+ '</div>'
|
|
424
|
-
+ '<div class="flex items-center gap-1.5 shrink-0 flex-wrap justify-end">'
|
|
425
|
-
+ stateBadge + decisionBadge
|
|
426
|
-
+ '</div>'
|
|
427
|
-
+ '</div>'
|
|
428
|
-
+ '<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-xs">'
|
|
429
|
-
+ '<div class="flex flex-col gap-0.5 min-w-0">'
|
|
430
|
-
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">repo_path</span>'
|
|
431
|
-
+ '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(s.repo_path || '—') + '</span>'
|
|
432
|
-
+ '</div>'
|
|
433
|
-
+ '<div class="flex flex-col gap-0.5 min-w-0">'
|
|
434
|
-
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">changed_files_total</span>'
|
|
435
|
-
+ '<span class="truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(changedTotal)) + '</span>'
|
|
436
|
-
+ '</div>'
|
|
437
|
-
+ '<div class="flex flex-col gap-0.5 min-w-0">'
|
|
438
|
-
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">created_at</span>'
|
|
439
|
-
+ '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(fmtTimeShort(s.created_at)) + '</span>'
|
|
440
|
-
+ '</div>'
|
|
441
|
-
+ '<div class="flex flex-col gap-0.5 min-w-0">'
|
|
442
|
-
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">expires_at</span>'
|
|
443
|
-
+ '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(fmtTimeShort(s.expires_at)) + '</span>'
|
|
444
|
-
+ '</div>'
|
|
445
|
-
+ '</div>'
|
|
446
|
-
+ '</div>';
|
|
617
|
+
var cls = classifySession(s);
|
|
618
|
+
groups[cls].push(s);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
var html = '';
|
|
622
|
+
// Active section (expanded)
|
|
623
|
+
if (groups.active.length > 0) {
|
|
624
|
+
html += renderGroupHeader('active', groups.active.length, false);
|
|
625
|
+
html += renderSessionCards(groups.active, false);
|
|
626
|
+
}
|
|
627
|
+
// Finalized section (expanded)
|
|
628
|
+
if (groups.finalized.length > 0) {
|
|
629
|
+
html += renderGroupHeader('finalized', groups.finalized.length, false);
|
|
630
|
+
html += renderSessionCards(groups.finalized, false);
|
|
631
|
+
}
|
|
632
|
+
// Audited section (expanded)
|
|
633
|
+
if (groups.audited.length > 0) {
|
|
634
|
+
html += renderGroupHeader('audited', groups.audited.length, false);
|
|
635
|
+
html += renderSessionCards(groups.audited, false);
|
|
447
636
|
}
|
|
637
|
+
// Expired section (collapsed via <details>)
|
|
638
|
+
if (groups.expired.length > 0) {
|
|
639
|
+
html += renderExpiredGroup(groups.expired);
|
|
640
|
+
}
|
|
641
|
+
|
|
448
642
|
listEl.innerHTML = html;
|
|
449
643
|
showList();
|
|
644
|
+
wireActionButtons();
|
|
645
|
+
wireSessionCards();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function renderGroupHeader(key, count, isExpired) {
|
|
649
|
+
var meta = GROUP_LABELS[key] || GROUP_LABELS.active;
|
|
650
|
+
var c = COLOR_VARS[meta.color] || COLOR_VARS.muted;
|
|
651
|
+
return '<div class="flex items-center gap-2 mt-2 mb-1 px-1">'
|
|
652
|
+
+ '<span class="w-2 h-2 rounded-full" style="background-color: ' + c.fg + ';"></span>'
|
|
653
|
+
+ '<h2 class="text-sm font-semibold" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">' + escapeHtml(meta.zh) + '</h2>'
|
|
654
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">(' + count + ')</span>'
|
|
655
|
+
+ '</div>';
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function renderExpiredGroup(sessions) {
|
|
659
|
+
var meta = GROUP_LABELS.expired;
|
|
660
|
+
var c = COLOR_VARS[meta.color] || COLOR_VARS.muted;
|
|
661
|
+
var headerHtml =
|
|
662
|
+
'<summary class="flex items-center gap-2 cursor-pointer px-3 py-2" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans;">'
|
|
663
|
+
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0"><polyline points="6 9 12 15 18 9"/></svg>'
|
|
664
|
+
+ '<span class="w-2 h-2 rounded-full" style="background-color: ' + c.fg + ';"></span>'
|
|
665
|
+
+ '<span class="text-sm font-semibold">' + escapeHtml(meta.zh) + '</span>'
|
|
666
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">(' + sessions.length + ')</span>'
|
|
667
|
+
+ '<span class="ml-auto text-xs" style="color: var(--pw-text-tertiary);">点击展开 / 折叠</span>'
|
|
668
|
+
+ '</summary>';
|
|
669
|
+
return '<details class="rounded-lg mt-2" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">'
|
|
670
|
+
+ headerHtml
|
|
671
|
+
+ '<div class="px-3 pb-3 pt-1 flex flex-col gap-3">'
|
|
672
|
+
+ renderSessionCards(sessions, true)
|
|
673
|
+
+ '</div>'
|
|
674
|
+
+ '</details>';
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function renderSessionCards(sessions, isExpired) {
|
|
678
|
+
var html = '';
|
|
679
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
680
|
+
html += renderSessionCard(sessions[i] || {}, isExpired);
|
|
681
|
+
}
|
|
682
|
+
return html;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function renderSessionCard(s, isExpired) {
|
|
686
|
+
var sid = s.session_id || '—';
|
|
687
|
+
var finalized = !!s.finalized;
|
|
688
|
+
var audited = !!s.audited;
|
|
689
|
+
var changedTotal = s.changed_files_total != null ? s.changed_files_total : '—';
|
|
690
|
+
var decision = s.audit_decision || null;
|
|
691
|
+
var decisionBadge = decision
|
|
692
|
+
? badgeHtml(decision, verdictColor(decision))
|
|
693
|
+
: '<span class="text-xs px-2 py-0.5 rounded-md" style="color: var(--pw-text-tertiary); background-color: var(--pw-bg-hover);">not audited</span>';
|
|
694
|
+
var stateBadge = finalized
|
|
695
|
+
? badgeHtml('finalized', 'success')
|
|
696
|
+
: (isExpired ? badgeHtml('expired', 'warning') : badgeHtml('open', 'warning'));
|
|
697
|
+
var auditedBadge = audited ? badgeHtml('audited', 'success') : '';
|
|
698
|
+
var titleText = s.title || s.repo_path || sid;
|
|
699
|
+
|
|
700
|
+
// Verification status summary
|
|
701
|
+
var vs = s.verification_summary || null;
|
|
702
|
+
var vsText = verificationStatusText(vs);
|
|
703
|
+
var vsColor = verificationStatusColor(vs);
|
|
704
|
+
var vsBadge = badgeHtml(vsText, vsColor);
|
|
705
|
+
|
|
706
|
+
// Quick action buttons
|
|
707
|
+
var actions = '';
|
|
708
|
+
// safe_direct_summary
|
|
709
|
+
actions += quickBtnHtml('summary', 'safe_direct_summary', sid, '查看安全摘要', false, false);
|
|
710
|
+
// run_direct_verification_bundle — informational button (links to tasks page)
|
|
711
|
+
actions += quickBtnHtml('verify-bundle', 'verify', sid, 'run_direct_verification_bundle:通过任务页创建验证任务(该操作需在任务页发起)', false, false);
|
|
712
|
+
// safe_finalize_direct_session (disabled if already finalized)
|
|
713
|
+
actions += quickBtnHtml('finalize', 'safe_finalize_direct_session', sid, 'Finalize 会话', finalized, true);
|
|
714
|
+
// safe_audit_direct_session (disabled if already audited)
|
|
715
|
+
actions += quickBtnHtml('audit', 'safe_audit_direct_session', sid, '审计会话', audited, true);
|
|
716
|
+
// copy session_id
|
|
717
|
+
actions += quickBtnHtml('copy', 'copy session_id', sid, '复制 session_id', false, false);
|
|
718
|
+
// hide (expired only)
|
|
719
|
+
if (isExpired) {
|
|
720
|
+
actions += quickBtnHtml('hide', 'hide', sid, '从列表隐藏', false, false, true);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return '<div class="rounded-lg p-4 transition-colors session-card" data-session-id="' + escapeHtml(sid) + '" '
|
|
724
|
+
+ 'onmouseover="this.style.backgroundColor=\'var(--pw-bg-hover)\'" '
|
|
725
|
+
+ 'onmouseout="this.style.backgroundColor=\'var(--pw-bg-surface)\'" '
|
|
726
|
+
+ 'style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); cursor: pointer;">'
|
|
727
|
+
+ '<div class="flex items-start justify-between gap-3 mb-3">'
|
|
728
|
+
+ '<div class="min-w-0 flex-1">'
|
|
729
|
+
+ '<div class="flex items-center gap-2 mb-1">'
|
|
730
|
+
+ '<h3 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">' + escapeHtml(titleText) + '</h3>'
|
|
731
|
+
+ '</div>'
|
|
732
|
+
+ '<div class="flex items-center gap-2 min-w-0">'
|
|
733
|
+
+ '<code class="text-xs truncate" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);" title="' + escapeHtml(sid) + '">' + escapeHtml(shortId(sid)) + '</code>'
|
|
734
|
+
+ '</div>'
|
|
735
|
+
+ '</div>'
|
|
736
|
+
+ '<div class="flex items-center gap-1.5 shrink-0 flex-wrap justify-end">'
|
|
737
|
+
+ stateBadge + auditedBadge + decisionBadge
|
|
738
|
+
+ '</div>'
|
|
739
|
+
+ '</div>'
|
|
740
|
+
+ '<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-xs mb-3">'
|
|
741
|
+
+ '<div class="flex flex-col gap-0.5 min-w-0">'
|
|
742
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">repo_path</span>'
|
|
743
|
+
+ '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + escapeHtml(s.repo_path || '') + '">' + escapeHtml(s.repo_path || '—') + '</span>'
|
|
744
|
+
+ '</div>'
|
|
745
|
+
+ '<div class="flex flex-col gap-0.5 min-w-0">'
|
|
746
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">changed_files_total</span>'
|
|
747
|
+
+ '<span class="truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(changedTotal)) + '</span>'
|
|
748
|
+
+ '</div>'
|
|
749
|
+
+ '<div class="flex flex-col gap-0.5 min-w-0">'
|
|
750
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">created_at</span>'
|
|
751
|
+
+ '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(fmtTimeShort(s.created_at)) + '</span>'
|
|
752
|
+
+ '</div>'
|
|
753
|
+
+ '<div class="flex flex-col gap-0.5 min-w-0">'
|
|
754
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">expires_at</span>'
|
|
755
|
+
+ '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(fmtTimeShort(s.expires_at)) + '</span>'
|
|
756
|
+
+ '</div>'
|
|
757
|
+
+ '</div>'
|
|
758
|
+
+ '<div class="flex items-center gap-2 mb-3 text-xs">'
|
|
759
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">verification:</span>'
|
|
760
|
+
+ vsBadge
|
|
761
|
+
+ '</div>'
|
|
762
|
+
+ '<div class="flex items-center gap-1.5 flex-wrap" onclick="event.stopPropagation();">'
|
|
763
|
+
+ actions
|
|
764
|
+
+ '</div>'
|
|
765
|
+
+ '</div>';
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function quickBtnHtml(action, label, sid, title, disabled, accent, danger) {
|
|
769
|
+
var cls = 'pw-quick-btn';
|
|
770
|
+
if (accent) cls += ' pw-quick-accent';
|
|
771
|
+
if (danger) cls += ' pw-quick-danger';
|
|
772
|
+
var disAttr = disabled ? ' disabled' : '';
|
|
773
|
+
return '<button type="button" class="' + cls + '" data-action="' + escapeHtml(action) + '" data-session-id="' + escapeHtml(sid) + '" title="' + escapeHtml(title + ' (' + label + ')') + '"' + disAttr + '>'
|
|
774
|
+
+ escapeHtml(label)
|
|
775
|
+
+ '</button>';
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// ---------- Action button wiring ----------
|
|
779
|
+
function wireActionButtons() {
|
|
780
|
+
var btns = document.querySelectorAll('#sessions-list .pw-quick-btn[data-action]');
|
|
781
|
+
for (var i = 0; i < btns.length; i++) {
|
|
782
|
+
var btn = btns[i];
|
|
783
|
+
// Avoid double-binding on re-render.
|
|
784
|
+
if (btn.__pwBound) continue;
|
|
785
|
+
btn.__pwBound = true;
|
|
786
|
+
btn.addEventListener('click', function (ev) {
|
|
787
|
+
ev.preventDefault();
|
|
788
|
+
ev.stopPropagation();
|
|
789
|
+
handleActionClick(ev.currentTarget);
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
}
|
|
450
793
|
|
|
451
|
-
|
|
794
|
+
function wireSessionCards() {
|
|
795
|
+
var cards = $('sessions-list').querySelectorAll('.session-card');
|
|
452
796
|
for (var j = 0; j < cards.length; j++) {
|
|
453
|
-
cards[j]
|
|
797
|
+
var card = cards[j];
|
|
798
|
+
if (card.__pwBound) continue;
|
|
799
|
+
card.__pwBound = true;
|
|
800
|
+
card.addEventListener('click', function (ev) {
|
|
801
|
+
// Ignore clicks that originated on a button/details summary.
|
|
802
|
+
if (ev.target.closest('.pw-quick-btn') || ev.target.closest('summary')) return;
|
|
454
803
|
var sid2 = ev.currentTarget.getAttribute('data-session-id');
|
|
455
804
|
if (sid2) loadSessionDetail(sid2);
|
|
456
805
|
});
|
|
457
806
|
}
|
|
458
807
|
}
|
|
459
808
|
|
|
809
|
+
function handleActionClick(btn) {
|
|
810
|
+
var action = btn.getAttribute('data-action');
|
|
811
|
+
var sid = btn.getAttribute('data-session-id');
|
|
812
|
+
if (!action || !sid) return;
|
|
813
|
+
if (action === 'summary') {
|
|
814
|
+
loadSafeSummary(sid);
|
|
815
|
+
} else if (action === 'finalize') {
|
|
816
|
+
finalizeSession(sid, btn);
|
|
817
|
+
} else if (action === 'audit') {
|
|
818
|
+
auditSession(sid, btn);
|
|
819
|
+
} else if (action === 'copy') {
|
|
820
|
+
copyToClipboard(sid, 'session_id');
|
|
821
|
+
} else if (action === 'hide') {
|
|
822
|
+
hideSession(sid, btn);
|
|
823
|
+
} else if (action === 'verify-bundle') {
|
|
824
|
+
// Informational: navigate to tasks page where verification tasks are created.
|
|
825
|
+
showToast('info', '跳转任务页', 'run_direct_verification_bundle 需在任务页发起');
|
|
826
|
+
setTimeout(function () { window.location.href = '/pages/tasks.html'; }, 600);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function loadSafeSummary(sessionId) {
|
|
831
|
+
var url = '/api/direct-sessions/' + encodeURIComponent(sessionId) + '/summary';
|
|
832
|
+
fetch(url, { method: 'GET', headers: { 'Accept': 'application/json' }, cache: 'no-store' })
|
|
833
|
+
.then(function (res) { return res.json(); })
|
|
834
|
+
.then(function (data) {
|
|
835
|
+
showSafeModal('safe_direct_summary · ' + shortId(sessionId), data);
|
|
836
|
+
})
|
|
837
|
+
.catch(function (err) {
|
|
838
|
+
showToast('error', '加载失败', err.message || '未知错误');
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function withBusy(btn, fn) {
|
|
843
|
+
var orig = btn.innerHTML;
|
|
844
|
+
btn.disabled = true;
|
|
845
|
+
btn.innerHTML = '<span class="inline-block w-3 h-3 align-middle border-2 border-current border-t-transparent rounded-full pw-spin"></span>';
|
|
846
|
+
Promise.resolve()
|
|
847
|
+
.then(fn)
|
|
848
|
+
.catch(function (err) {
|
|
849
|
+
showToast('error', '操作失败', err.message || '未知错误');
|
|
850
|
+
})
|
|
851
|
+
.finally(function () {
|
|
852
|
+
btn.disabled = false;
|
|
853
|
+
btn.innerHTML = orig;
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function finalizeSession(sessionId, btn) {
|
|
858
|
+
withBusy(btn, function () {
|
|
859
|
+
return postAction('/api/direct-sessions/' + encodeURIComponent(sessionId) + '/finalize')
|
|
860
|
+
.then(function (data) {
|
|
861
|
+
if (data && data.error) {
|
|
862
|
+
showToast('error', 'Finalize 失败', data.error);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
showToast('success', '已 Finalize', '会话 ' + shortId(sessionId) + ' 已完成 finalize');
|
|
866
|
+
loadSessions();
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function auditSession(sessionId, btn) {
|
|
872
|
+
withBusy(btn, function () {
|
|
873
|
+
return postAction('/api/direct-sessions/' + encodeURIComponent(sessionId) + '/audit')
|
|
874
|
+
.then(function (data) {
|
|
875
|
+
if (data && data.error) {
|
|
876
|
+
showToast('error', '审计失败', data.error);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
var decision = (data && data.decision) || '完成';
|
|
880
|
+
showToast('success', '审计完成', '会话 ' + shortId(sessionId) + ' 审计结果: ' + decision);
|
|
881
|
+
loadSessions();
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function hideSession(sessionId, btn) {
|
|
887
|
+
withBusy(btn, function () {
|
|
888
|
+
return postAction('/api/direct-sessions/' + encodeURIComponent(sessionId) + '/hide')
|
|
889
|
+
.then(function (data) {
|
|
890
|
+
if (data && data.error) {
|
|
891
|
+
showToast('error', '隐藏失败', data.error);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
showToast('success', '已隐藏', '会话 ' + shortId(sessionId) + ' 已从列表隐藏');
|
|
895
|
+
loadSessions();
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
|
|
460
900
|
// ---------- Render: session detail ----------
|
|
461
901
|
function renderSessionDetail(data) {
|
|
462
902
|
var el = $('session-detail');
|
|
@@ -473,7 +913,7 @@ html.dark {
|
|
|
473
913
|
var vs = s.verification_summary || null;
|
|
474
914
|
var vsHtml = '—';
|
|
475
915
|
if (vs && typeof vs === 'object') {
|
|
476
|
-
var vsStatus = vs.status || vs.verdict || '—';
|
|
916
|
+
var vsStatus = vs.status || vs.verdict || verificationStatusText(vs) || '—';
|
|
477
917
|
var vsCmds = Array.isArray(vs.commands) ? vs.commands.map(function (c) { return JSON.stringify(c); }).join(' ') : '';
|
|
478
918
|
vsHtml = '<span style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(vsStatus)) + '</span>'
|
|
479
919
|
+ (vsCmds ? '<div class="mt-1 text-xs break-all" style="color: var(--pw-text-tertiary);">' + escapeHtml(vsCmds) + '</div>' : '');
|
|
@@ -644,6 +1084,18 @@ html.dark {
|
|
|
644
1084
|
var retryBtn = $('retry-btn');
|
|
645
1085
|
if (retryBtn) retryBtn.addEventListener('click', loadSessions);
|
|
646
1086
|
|
|
1087
|
+
var modalClose = $('safe-modal-close');
|
|
1088
|
+
if (modalClose) modalClose.addEventListener('click', closeSafeModal);
|
|
1089
|
+
// Click on modal backdrop closes it.
|
|
1090
|
+
var safeModal = $('safe-modal');
|
|
1091
|
+
if (safeModal) safeModal.addEventListener('click', function (ev) {
|
|
1092
|
+
if (ev.target === safeModal) closeSafeModal();
|
|
1093
|
+
});
|
|
1094
|
+
// ESC closes modal.
|
|
1095
|
+
document.addEventListener('keydown', function (ev) {
|
|
1096
|
+
if (ev.key === 'Escape') closeSafeModal();
|
|
1097
|
+
});
|
|
1098
|
+
|
|
647
1099
|
// Initial load
|
|
648
1100
|
loadSessions();
|
|
649
1101
|
})();
|