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
package/ui/pages/tasks.html
CHANGED
|
@@ -250,36 +250,134 @@ html.dark {
|
|
|
250
250
|
<main class="flex-1 overflow-y-auto p-6" style="background-color: var(--pw-bg-elevated);">
|
|
251
251
|
|
|
252
252
|
<!-- ===== Filter Bar ===== -->
|
|
253
|
-
<div class="
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
<
|
|
257
|
-
<
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
253
|
+
<div class="rounded-lg mb-5 p-4" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
254
|
+
<div class="flex flex-wrap items-end gap-3">
|
|
255
|
+
<!-- Repo Path Filter -->
|
|
256
|
+
<div class="flex flex-col gap-1">
|
|
257
|
+
<label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="仓库" data-en="Repo">仓库</label>
|
|
258
|
+
<div class="relative">
|
|
259
|
+
<select id="repoFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 180px;">
|
|
260
|
+
<option value="" data-zh="全部仓库" data-en="All repos">全部仓库</option>
|
|
261
|
+
</select>
|
|
262
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
263
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
264
|
+
</svg>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<!-- Status Filter -->
|
|
269
|
+
<div class="flex flex-col gap-1">
|
|
270
|
+
<label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="状态" data-en="Status">状态</label>
|
|
271
|
+
<div class="relative">
|
|
272
|
+
<select id="statusFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 140px;">
|
|
273
|
+
<option value="" data-zh="全部状态" data-en="All Status">全部状态</option>
|
|
274
|
+
<option value="running">Running</option>
|
|
275
|
+
<option value="pending">Pending</option>
|
|
276
|
+
<option value="done">Done</option>
|
|
277
|
+
<option value="failed">Failed</option>
|
|
278
|
+
<option value="failed_verification">Failed Verification</option>
|
|
279
|
+
<option value="failed_policy_violation">Failed Policy Violation</option>
|
|
280
|
+
<option value="failed_scope_violation">Failed Scope Violation</option>
|
|
281
|
+
<option value="needs_fix">Needs Fix</option>
|
|
282
|
+
<option value="ready_for_review">Ready For Review</option>
|
|
283
|
+
<option value="done_by_agent">Done By Agent</option>
|
|
284
|
+
</select>
|
|
285
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
286
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
287
|
+
</svg>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<!-- Acceptance Status Filter -->
|
|
292
|
+
<div class="flex flex-col gap-1">
|
|
293
|
+
<label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="验收" data-en="Acceptance">验收</label>
|
|
294
|
+
<div class="relative">
|
|
295
|
+
<select id="acceptanceFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 160px;">
|
|
296
|
+
<option value="" data-zh="全部验收" data-en="All Acceptance">全部验收</option>
|
|
297
|
+
<option value="ready_for_review">Ready For Review</option>
|
|
298
|
+
<option value="done_by_agent">Done By Agent</option>
|
|
299
|
+
<option value="needs_fix">Needs Fix</option>
|
|
300
|
+
<option value="failed_verification">Failed Verification</option>
|
|
301
|
+
<option value="failed_policy_violation">Failed Policy Violation</option>
|
|
302
|
+
<option value="failed_scope_violation">Failed Scope Violation</option>
|
|
303
|
+
</select>
|
|
304
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
305
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
306
|
+
</svg>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<!-- Warning Type Filter -->
|
|
311
|
+
<div class="flex flex-col gap-1">
|
|
312
|
+
<label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="警告类型" data-en="Warning Type">警告类型</label>
|
|
313
|
+
<div class="relative">
|
|
314
|
+
<select id="warningTypeFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 180px;">
|
|
315
|
+
<option value="" data-zh="全部警告" data-en="All Warnings">全部警告</option>
|
|
316
|
+
<option value="unrecorded_command_execution">Unrecorded Command Execution</option>
|
|
317
|
+
<option value="artifact_hygiene">Artifact Hygiene</option>
|
|
318
|
+
<option value="scope_changes">Scope Changes</option>
|
|
319
|
+
<option value="release_publish_claim">Release Publish Claim</option>
|
|
320
|
+
<option value="manual_verification_required">Manual Verification Required</option>
|
|
321
|
+
<option value="stale_task">Stale Task</option>
|
|
322
|
+
<option value="failed_verification">Failed Verification</option>
|
|
323
|
+
</select>
|
|
324
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
325
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
326
|
+
</svg>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<!-- Agent Filter -->
|
|
331
|
+
<div class="flex flex-col gap-1">
|
|
332
|
+
<label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="Agent" data-en="Agent">Agent</label>
|
|
333
|
+
<div class="relative">
|
|
334
|
+
<select id="agentFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 140px;">
|
|
335
|
+
<option value="" data-zh="全部 Agent" data-en="All Agents">全部 Agent</option>
|
|
336
|
+
</select>
|
|
337
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
338
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
339
|
+
</svg>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<!-- Date From -->
|
|
344
|
+
<div class="flex flex-col gap-1">
|
|
345
|
+
<label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="更新从" data-en="Updated From">更新从</label>
|
|
346
|
+
<input id="dateFromFilter" type="date" class="px-3 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); color-scheme: dark;" />
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<!-- Date To -->
|
|
350
|
+
<div class="flex flex-col gap-1">
|
|
351
|
+
<label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="更新至" data-en="Updated To">更新至</label>
|
|
352
|
+
<input id="dateToFilter" type="date" class="px-3 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); color-scheme: dark;" />
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<!-- Action Buttons -->
|
|
356
|
+
<div class="flex items-center gap-2">
|
|
357
|
+
<button id="applyFiltersBtn" class="flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-xs font-semibold whitespace-nowrap transition-colors" style="background-color: var(--pw-accent); color: var(--pw-text-inverse); font-family: var(--pw-font-sans);">
|
|
358
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>
|
|
359
|
+
<span data-zh="应用筛选" data-en="Apply Filters">应用筛选</span>
|
|
360
|
+
</button>
|
|
361
|
+
<button id="clearFiltersBtn" class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold whitespace-nowrap 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);">
|
|
362
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
363
|
+
<span data-zh="清空筛选" data-en="Clear Filters">清空筛选</span>
|
|
364
|
+
</button>
|
|
365
|
+
</div>
|
|
366
|
+
|
|
367
|
+
<!-- Refresh + Count -->
|
|
368
|
+
<button id="refreshBtn" class="flex items-center gap-2 px-4 py-1.5 rounded-lg text-xs font-semibold whitespace-nowrap transition-colors ml-auto" style="background-color: var(--pw-bg-elevated); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);">
|
|
369
|
+
<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">
|
|
370
|
+
<polyline points="23 4 23 10 17 10"/>
|
|
371
|
+
<polyline points="1 20 1 14 7 14"/>
|
|
372
|
+
<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"/>
|
|
373
|
+
</svg>
|
|
374
|
+
<span data-zh="刷新" data-en="Refresh">刷新</span>
|
|
375
|
+
</button>
|
|
376
|
+
</div>
|
|
377
|
+
<div class="flex items-center justify-between mt-3 pt-3" style="border-top: 1px solid var(--pw-border-subtle);">
|
|
378
|
+
<div id="filterStatusText" class="text-xs" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);">—</div>
|
|
379
|
+
<div id="countSummary" class="text-xs" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);">--</div>
|
|
266
380
|
</div>
|
|
267
|
-
|
|
268
|
-
<!-- Refresh Button -->
|
|
269
|
-
<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);">
|
|
270
|
-
<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">
|
|
271
|
-
<polyline points="23 4 23 10 17 10"/>
|
|
272
|
-
<polyline points="1 20 1 14 7 14"/>
|
|
273
|
-
<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"/>
|
|
274
|
-
</svg>
|
|
275
|
-
<span data-zh="刷新" data-en="Refresh">刷新</span>
|
|
276
|
-
</button>
|
|
277
|
-
|
|
278
|
-
<!-- Spacer -->
|
|
279
|
-
<div class="flex-1"></div>
|
|
280
|
-
|
|
281
|
-
<!-- Count Summary -->
|
|
282
|
-
<div id="countSummary" class="text-xs" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);">--</div>
|
|
283
381
|
</div>
|
|
284
382
|
|
|
285
383
|
<!-- ===== Task Table (border-led card) ===== -->
|
|
@@ -324,16 +422,18 @@ html.dark {
|
|
|
324
422
|
|
|
325
423
|
<!-- Table -->
|
|
326
424
|
<div id="tableWrapper" class="hidden overflow-x-auto">
|
|
327
|
-
<table class="w-full text-left" style="min-width:
|
|
425
|
+
<table class="w-full text-left" style="min-width: 1200px;">
|
|
328
426
|
<thead>
|
|
329
427
|
<tr style="border-bottom: 1px solid var(--pw-border);">
|
|
330
428
|
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="任务ID" data-en="Task ID">任务ID</th>
|
|
331
429
|
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="标题" data-en="Title">标题</th>
|
|
332
430
|
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);">Agent</th>
|
|
333
431
|
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="状态" data-en="Status">状态</th>
|
|
432
|
+
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="验收" data-en="Acceptance">验收</th>
|
|
433
|
+
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="验证" data-en="Verification">验证</th>
|
|
334
434
|
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="阶段" data-en="Phase">阶段</th>
|
|
335
435
|
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="创建时间" data-en="Created">创建时间</th>
|
|
336
|
-
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="
|
|
436
|
+
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="下一步" data-en="Next Action">下一步</th>
|
|
337
437
|
<th class="px-4 py-3 text-xs font-semibold whitespace-nowrap text-right" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="操作" data-en="Actions">操作</th>
|
|
338
438
|
</tr>
|
|
339
439
|
</thead>
|
|
@@ -344,6 +444,17 @@ html.dark {
|
|
|
344
444
|
</div>
|
|
345
445
|
</div>
|
|
346
446
|
|
|
447
|
+
<!-- ===== SAFE MODAL ===== -->
|
|
448
|
+
<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);">
|
|
449
|
+
<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);">
|
|
450
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
451
|
+
<h3 id="safe-modal-title" class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">—</h3>
|
|
452
|
+
<button id="safe-modal-close" class="text-lg leading-none" style="color: var(--pw-text-tertiary);" type="button">×</button>
|
|
453
|
+
</div>
|
|
454
|
+
<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>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
347
458
|
</main>
|
|
348
459
|
</div>
|
|
349
460
|
|
|
@@ -418,6 +529,35 @@ html.dark {
|
|
|
418
529
|
background-color: var(--pw-bg-active) !important;
|
|
419
530
|
color: var(--pw-text-primary) !important;
|
|
420
531
|
}
|
|
532
|
+
/* Quick action icon buttons */
|
|
533
|
+
.pw-quick-btn {
|
|
534
|
+
display: inline-flex;
|
|
535
|
+
align-items: center;
|
|
536
|
+
justify-content: center;
|
|
537
|
+
width: 24px;
|
|
538
|
+
height: 24px;
|
|
539
|
+
border-radius: 4px;
|
|
540
|
+
border: 1px solid var(--pw-border-subtle);
|
|
541
|
+
background-color: var(--pw-bg-hover);
|
|
542
|
+
color: var(--pw-text-secondary);
|
|
543
|
+
cursor: pointer;
|
|
544
|
+
transition: background-color var(--pw-transition-fast), color var(--pw-transition-fast);
|
|
545
|
+
text-decoration: none;
|
|
546
|
+
vertical-align: middle;
|
|
547
|
+
}
|
|
548
|
+
.pw-quick-btn:hover {
|
|
549
|
+
background-color: var(--pw-bg-active);
|
|
550
|
+
color: var(--pw-text-primary);
|
|
551
|
+
filter: none;
|
|
552
|
+
}
|
|
553
|
+
/* Acceptance status badge - more prominent than status */
|
|
554
|
+
.pw-acceptance-badge {
|
|
555
|
+
font-size: var(--pw-text-sm);
|
|
556
|
+
font-weight: var(--pw-weight-bold);
|
|
557
|
+
padding: 2px 8px;
|
|
558
|
+
border-width: 1px;
|
|
559
|
+
border-style: solid;
|
|
560
|
+
}
|
|
421
561
|
</style>
|
|
422
562
|
|
|
423
563
|
<script>
|
|
@@ -434,6 +574,11 @@ html.dark {
|
|
|
434
574
|
done: { color: 'success', text: '已完成' },
|
|
435
575
|
failed: { color: 'error', text: '失败' },
|
|
436
576
|
failed_scope_violation: { color: 'error', text: '范围违规' },
|
|
577
|
+
failed_policy_violation: { color: 'error', text: '策略违规' },
|
|
578
|
+
failed_verification: { color: 'error', text: '验证失败' },
|
|
579
|
+
needs_fix: { color: 'warning', text: '需修复' },
|
|
580
|
+
ready_for_review: { color: 'info', text: '待审核' },
|
|
581
|
+
done_by_agent: { color: 'success', text: 'Agent 完成' },
|
|
437
582
|
canceled: { color: 'muted', text: '已取消' },
|
|
438
583
|
timeout: { color: 'muted', text: '超时' }
|
|
439
584
|
};
|
|
@@ -448,7 +593,18 @@ html.dark {
|
|
|
448
593
|
};
|
|
449
594
|
|
|
450
595
|
var allTasks = [];
|
|
451
|
-
|
|
596
|
+
// currentFilters holds the active filter values for all dimensions.
|
|
597
|
+
// repo_path / status / acceptance_status / agent are sent to the backend.
|
|
598
|
+
// warning_type / date_from / date_to are applied client-side.
|
|
599
|
+
var currentFilters = {
|
|
600
|
+
repo_path: '',
|
|
601
|
+
status: '',
|
|
602
|
+
acceptance_status: '',
|
|
603
|
+
agent: '',
|
|
604
|
+
warning_type: '',
|
|
605
|
+
date_from: '',
|
|
606
|
+
date_to: ''
|
|
607
|
+
};
|
|
452
608
|
|
|
453
609
|
// ---- DOM helpers ----
|
|
454
610
|
function $(id) { return document.getElementById(id); }
|
|
@@ -488,6 +644,124 @@ html.dark {
|
|
|
488
644
|
return PHASE_LABELS[phase] || phase || '—';
|
|
489
645
|
}
|
|
490
646
|
|
|
647
|
+
function shortId(id) {
|
|
648
|
+
if (!id) return '—';
|
|
649
|
+
var s = String(id);
|
|
650
|
+
return s.length > 22 ? s.slice(0, 10) + '…' + s.slice(-8) : s;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ---- Safe summary + clipboard helpers ----
|
|
654
|
+
async function fetchJson(url) {
|
|
655
|
+
var res = await fetch(url);
|
|
656
|
+
if (!res.ok) throw new Error('HTTP ' + res.status + ' · ' + url);
|
|
657
|
+
return res.json();
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function showToast(kind, title, msg) {
|
|
661
|
+
var container = document.getElementById('toast-container');
|
|
662
|
+
if (!container) {
|
|
663
|
+
container = document.createElement('div');
|
|
664
|
+
container.id = 'toast-container';
|
|
665
|
+
container.className = 'fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm pointer-events-none';
|
|
666
|
+
document.body.appendChild(container);
|
|
667
|
+
}
|
|
668
|
+
var el = document.createElement('div');
|
|
669
|
+
var borderColors = {
|
|
670
|
+
success: 'var(--pw-state-success)',
|
|
671
|
+
error: 'var(--pw-state-error)',
|
|
672
|
+
warning: 'var(--pw-state-warning)',
|
|
673
|
+
info: 'var(--pw-state-info)'
|
|
674
|
+
};
|
|
675
|
+
el.className = 'pointer-events-auto flex items-start gap-2 px-3 py-2 rounded-lg text-xs';
|
|
676
|
+
el.style.cssText = 'background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); border-left: 3px solid ' + (borderColors[kind] || borderColors.info) + '; color: var(--pw-text-primary); box-shadow: var(--pw-shadow-float); max-width: 320px;';
|
|
677
|
+
el.innerHTML = '<div class="min-w-0 flex-1"><div style="font-weight:600;">' + escapeHtml(title || '') + '</div>' + (msg ? '<div style="color: var(--pw-text-secondary);">' + escapeHtml(msg) + '</div>' : '') + '</div>';
|
|
678
|
+
container.appendChild(el);
|
|
679
|
+
setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 4000);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function copyToClipboard(text, label) {
|
|
683
|
+
if (!text) return;
|
|
684
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
685
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
686
|
+
showToast('success', '已复制', (label || '内容') + '已复制到剪贴板');
|
|
687
|
+
}).catch(function () {
|
|
688
|
+
showToast('error', '复制失败', '请手动复制');
|
|
689
|
+
});
|
|
690
|
+
} else {
|
|
691
|
+
showToast('warning', '复制不支持', '浏览器不支持剪贴板 API');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function showSafeModal(title, data) {
|
|
696
|
+
var modal = $('safe-modal');
|
|
697
|
+
var titleEl = $('safe-modal-title');
|
|
698
|
+
var bodyEl = $('safe-modal-body');
|
|
699
|
+
if (!modal || !titleEl || !bodyEl) return;
|
|
700
|
+
titleEl.textContent = title || '—';
|
|
701
|
+
var content;
|
|
702
|
+
if (data && typeof data === 'object') {
|
|
703
|
+
content = JSON.stringify(data, null, 2);
|
|
704
|
+
} else {
|
|
705
|
+
content = String(data || '');
|
|
706
|
+
}
|
|
707
|
+
bodyEl.textContent = content;
|
|
708
|
+
modal.classList.remove('hidden');
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function closeSafeModal() {
|
|
712
|
+
var modal = $('safe-modal');
|
|
713
|
+
if (modal) modal.classList.add('hidden');
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function fetchSafeSummary(taskId, kind, title) {
|
|
717
|
+
if (!taskId) return;
|
|
718
|
+
try {
|
|
719
|
+
var data = await fetchJson('/api/tasks/' + encodeURIComponent(taskId) + '/' + kind);
|
|
720
|
+
showSafeModal(title, data);
|
|
721
|
+
} catch (err) {
|
|
722
|
+
showToast('error', '加载失败', title + ': ' + err.message);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ---- Status priority ----
|
|
727
|
+
function primaryStatus(task) {
|
|
728
|
+
if (!task) return { key: '', label: '—', color: 'muted' };
|
|
729
|
+
var priority = [
|
|
730
|
+
'failed_policy_violation',
|
|
731
|
+
'failed_scope_violation',
|
|
732
|
+
'failed_verification',
|
|
733
|
+
'needs_fix',
|
|
734
|
+
'ready_for_review',
|
|
735
|
+
'done_by_agent',
|
|
736
|
+
'running',
|
|
737
|
+
'pending'
|
|
738
|
+
];
|
|
739
|
+
var status = task.status || '';
|
|
740
|
+
var acceptance = task.acceptance_status || '';
|
|
741
|
+
var statusIdx = priority.indexOf(status);
|
|
742
|
+
var acceptanceIdx = priority.indexOf(acceptance);
|
|
743
|
+
var chosen = '';
|
|
744
|
+
if (statusIdx >= 0 && acceptanceIdx >= 0) {
|
|
745
|
+
chosen = statusIdx <= acceptanceIdx ? status : acceptance;
|
|
746
|
+
} else if (statusIdx >= 0) {
|
|
747
|
+
chosen = status;
|
|
748
|
+
} else if (acceptanceIdx >= 0) {
|
|
749
|
+
chosen = acceptance;
|
|
750
|
+
} else {
|
|
751
|
+
chosen = acceptance || status || '';
|
|
752
|
+
}
|
|
753
|
+
var meta = getStatusMeta(chosen || status);
|
|
754
|
+
return { key: chosen, label: meta.text, color: meta.color };
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function verificationHeadline(t) {
|
|
758
|
+
if (!t) return '—';
|
|
759
|
+
if (t.verification && t.verification.headline) return t.verification.headline;
|
|
760
|
+
if (t.safe_result && t.safe_result.verification && t.safe_result.verification.headline) return t.safe_result.verification.headline;
|
|
761
|
+
if (t.verification_headline) return t.verification_headline;
|
|
762
|
+
return '—';
|
|
763
|
+
}
|
|
764
|
+
|
|
491
765
|
// ---- State rendering ----
|
|
492
766
|
function showLoading() {
|
|
493
767
|
show($('loadingState'));
|
|
@@ -541,52 +815,156 @@ html.dark {
|
|
|
541
815
|
|
|
542
816
|
// ---- Row rendering ----
|
|
543
817
|
function renderRow(task, idx) {
|
|
818
|
+
var taskId = task.task_id || '';
|
|
544
819
|
var meta = getStatusMeta(task.status);
|
|
545
820
|
var style = badgeStyle(meta.color);
|
|
546
821
|
var pulseClass = (task.status === 'running') ? ' pw-pulse-badge' : '';
|
|
547
|
-
var truncatedId = truncate(
|
|
822
|
+
var truncatedId = truncate(taskId, 12);
|
|
548
823
|
var createdAt = formatTime(task.created_at);
|
|
549
824
|
var phaseLabel = getPhaseLabel(task.phase);
|
|
550
825
|
var altBg = (idx % 2 === 1) ? ' background-color: var(--pw-bg-hover);' : '';
|
|
826
|
+
var detailHref = '/pages/task-detail.html?id=' + encodeURIComponent(taskId);
|
|
827
|
+
|
|
828
|
+
// Skip status badge if redundant with acceptance (done + done_by_agent)
|
|
829
|
+
var skipStatusBadge = (task.status === 'done' && task.acceptance_status === 'done_by_agent');
|
|
830
|
+
|
|
831
|
+
// Acceptance badge (more prominent)
|
|
832
|
+
var accCell;
|
|
833
|
+
if (task.acceptance_status) {
|
|
834
|
+
var accMeta = getStatusMeta(task.acceptance_status);
|
|
835
|
+
var accStyle = badgeStyle(accMeta.color);
|
|
836
|
+
accCell = '<span class="inline-flex items-center gap-1.5 pw-acceptance-badge rounded-md whitespace-nowrap" style="' + accStyle + ' font-family: var(--pw-font-sans); border-color: ' + getAccentColor(accMeta.color) + ';">'
|
|
837
|
+
+ '<span class="shrink-0 w-1.5 h-1.5 rounded-full" style="background-color: currentColor;"></span>'
|
|
838
|
+
+ escapeHtml(accMeta.text)
|
|
839
|
+
+ '</span>';
|
|
840
|
+
} else {
|
|
841
|
+
accCell = '<span style="color: var(--pw-text-tertiary);">—</span>';
|
|
842
|
+
}
|
|
551
843
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
844
|
+
// Verification headline
|
|
845
|
+
var headline = verificationHeadline(task);
|
|
846
|
+
var verifyCell = '<span class="inline-block truncate" style="max-width: 180px; color: var(--pw-text-secondary); font-family: var(--pw-font-sans);" title="' + escapeAttr(headline) + '">' + escapeHtml(truncate(headline, 30)) + '</span>';
|
|
847
|
+
|
|
848
|
+
// Next action cell
|
|
849
|
+
var nextActionCell;
|
|
850
|
+
if (task.next_action) {
|
|
851
|
+
nextActionCell = '<span class="inline-block truncate" style="max-width: 160px; color: var(--pw-text-secondary); font-family: var(--pw-font-sans);" title="' + escapeAttr(task.next_action) + '">' + escapeHtml(truncate(task.next_action, 28)) + '</span>';
|
|
852
|
+
} else if (task.pending_reason) {
|
|
853
|
+
nextActionCell = '<span class="inline-block truncate" style="max-width: 160px; color: var(--pw-state-warning); font-family: var(--pw-font-sans);" title="' + escapeAttr(task.pending_reason) + '">' + escapeHtml(truncate(task.pending_reason, 28)) + '</span>';
|
|
558
854
|
} else {
|
|
559
|
-
|
|
855
|
+
nextActionCell = '<span style="color: var(--pw-text-tertiary);">—</span>';
|
|
560
856
|
}
|
|
561
857
|
|
|
562
|
-
|
|
858
|
+
// Status cell (skip badge if redundant)
|
|
859
|
+
var statusCell;
|
|
860
|
+
if (skipStatusBadge) {
|
|
861
|
+
statusCell = '<span style="color: var(--pw-text-tertiary);">—</span>';
|
|
862
|
+
} else {
|
|
863
|
+
statusCell = '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium whitespace-nowrap' + pulseClass + '" style="' + style + ' font-family: var(--pw-font-sans);" data-status="' + escapeAttr(task.status || '') + '">'
|
|
864
|
+
+ '<span class="shrink-0 w-1.5 h-1.5 rounded-full" style="background-color: currentColor;"></span>'
|
|
865
|
+
+ escapeHtml(meta.text)
|
|
866
|
+
+ '</span>';
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Quick action buttons
|
|
870
|
+
var quickActions =
|
|
871
|
+
'<button class="pw-quick-btn" data-safe-action="safe-result" data-task-id="' + escapeAttr(taskId) + '" title="safe result" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></button>'
|
|
872
|
+
+ '<button class="pw-quick-btn" data-safe-action="safe-audit" data-task-id="' + escapeAttr(taskId) + '" title="safe audit" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></button>'
|
|
873
|
+
+ '<button class="pw-quick-btn" data-safe-action="safe-test-summary" data-task-id="' + escapeAttr(taskId) + '" title="safe test summary" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></button>'
|
|
874
|
+
+ '<button class="pw-quick-btn" data-safe-action="safe-diff-summary" data-task-id="' + escapeAttr(taskId) + '" title="safe diff summary" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg></button>'
|
|
875
|
+
+ '<button class="pw-quick-btn" data-copy-id="' + escapeAttr(taskId) + '" title="复制 task_id" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
|
|
876
|
+
+ '<a href="' + detailHref + '" class="pw-quick-btn" title="查看详情"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></a>';
|
|
563
877
|
|
|
564
878
|
return ''
|
|
565
879
|
+ '<tr class="transition-colors" style="border-bottom: 1px solid var(--pw-border-subtle);' + altBg + '">'
|
|
566
|
-
+ '<td class="px-4 py-3
|
|
567
|
-
+
|
|
568
|
-
+
|
|
569
|
-
+
|
|
880
|
+
+ '<td class="px-4 py-3 whitespace-nowrap">'
|
|
881
|
+
+ '<div class="flex items-center gap-1">'
|
|
882
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + escapeAttr(taskId) + '">' + escapeHtml(truncatedId) + '</span>'
|
|
883
|
+
+ '<button class="pw-quick-btn" data-copy-id="' + escapeAttr(taskId) + '" title="复制 task_id" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
|
|
884
|
+
+ '</div>'
|
|
570
885
|
+ '</td>'
|
|
886
|
+
+ '<td class="px-4 py-3 text-sm" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans); max-width: 280px;">' + escapeHtml(task.title || '—') + '</td>'
|
|
571
887
|
+ '<td class="px-4 py-3">'
|
|
572
|
-
+ '<span class="inline-flex items-center
|
|
573
|
-
+ '<span class="shrink-0 w-1.5 h-1.5 rounded-full" style="background-color: currentColor;"></span>'
|
|
574
|
-
+ escapeHtml(meta.text)
|
|
575
|
-
+ '</span>'
|
|
888
|
+
+ '<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium whitespace-nowrap" style="' + agentBadgeStyle(task.agent) + ' font-family: var(--pw-font-mono);">' + escapeHtml(task.agent || '—') + '</span>'
|
|
576
889
|
+ '</td>'
|
|
890
|
+
+ '<td class="px-4 py-3">' + statusCell + '</td>'
|
|
891
|
+
+ '<td class="px-4 py-3">' + accCell + '</td>'
|
|
892
|
+
+ '<td class="px-4 py-3 text-xs">' + verifyCell + '</td>'
|
|
577
893
|
+ '<td class="px-4 py-3 text-xs whitespace-nowrap" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);" data-phase="' + escapeAttr(task.phase || '') + '">' + escapeHtml(phaseLabel) + '</td>'
|
|
578
894
|
+ '<td class="px-4 py-3 text-xs whitespace-nowrap" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + escapeAttr(task.created_at || '') + '">' + escapeHtml(createdAt) + '</td>'
|
|
579
|
-
+ '<td class="px-4 py-3 text-xs" style="font-family: var(--pw-font-sans);">' +
|
|
895
|
+
+ '<td class="px-4 py-3 text-xs" style="font-family: var(--pw-font-sans);">' + nextActionCell + '</td>'
|
|
580
896
|
+ '<td class="px-4 py-3 text-right">'
|
|
581
|
-
+ '<
|
|
897
|
+
+ '<div class="inline-flex items-center gap-1">' + quickActions + '</div>'
|
|
582
898
|
+ '</td>'
|
|
583
899
|
+ '</tr>';
|
|
584
900
|
}
|
|
585
901
|
|
|
902
|
+
function getAccentColor(color) {
|
|
903
|
+
switch (color) {
|
|
904
|
+
case 'success': return 'var(--pw-state-success)';
|
|
905
|
+
case 'info': return 'var(--pw-state-info)';
|
|
906
|
+
case 'warning': return 'var(--pw-state-warning)';
|
|
907
|
+
case 'error': return 'var(--pw-state-error)';
|
|
908
|
+
default: return 'var(--pw-text-secondary)';
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// ---- Client-side filter helpers ----
|
|
913
|
+
// Extracts warning-type-related signals from a task for client-side filtering.
|
|
914
|
+
function taskMatchesWarningType(task, warningType) {
|
|
915
|
+
if (!warningType) return true;
|
|
916
|
+
if (!task) return false;
|
|
917
|
+
// Check warnings array (most precise)
|
|
918
|
+
if (Array.isArray(task.warnings)) {
|
|
919
|
+
for (var i = 0; i < task.warnings.length; i++) {
|
|
920
|
+
var w = task.warnings[i] || {};
|
|
921
|
+
if (w.type === warningType || w.code === warningType || w.kind === warningType) return true;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
// Check stale_task flag
|
|
925
|
+
if (warningType === 'stale_task' && task.is_stale === true) return true;
|
|
926
|
+
// Check failed_verification mapping
|
|
927
|
+
if (warningType === 'failed_verification' &&
|
|
928
|
+
(task.status === 'failed_verification' || task.acceptance_status === 'failed_verification')) return true;
|
|
929
|
+
// Check status / acceptance_status containing the warning type
|
|
930
|
+
if (task.status === warningType || task.acceptance_status === warningType) return true;
|
|
931
|
+
// Check verification headline (loose contains for manual_verification_required, scope_changes, etc.)
|
|
932
|
+
var headline = verificationHeadline(task);
|
|
933
|
+
if (headline && headline.toLowerCase().indexOf(warningType.replace(/_/g, ' ')) >= 0) return true;
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function taskMatchesDateRange(task, dateFrom, dateTo) {
|
|
938
|
+
if (!dateFrom && !dateTo) return true;
|
|
939
|
+
if (!task) return false;
|
|
940
|
+
var ts = task.updated_at || task.created_at;
|
|
941
|
+
if (!ts) return false;
|
|
942
|
+
var d = new Date(ts);
|
|
943
|
+
if (isNaN(d.getTime())) return false;
|
|
944
|
+
if (dateFrom) {
|
|
945
|
+
var from = new Date(dateFrom + 'T00:00:00');
|
|
946
|
+
if (d < from) return false;
|
|
947
|
+
}
|
|
948
|
+
if (dateTo) {
|
|
949
|
+
var to = new Date(dateTo + 'T23:59:59');
|
|
950
|
+
if (d > to) return false;
|
|
951
|
+
}
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function applyClientSideFilters(tasks) {
|
|
956
|
+
var out = tasks.slice();
|
|
957
|
+
if (currentFilters.warning_type) {
|
|
958
|
+
out = out.filter(function (t) { return taskMatchesWarningType(t, currentFilters.warning_type); });
|
|
959
|
+
}
|
|
960
|
+
if (currentFilters.date_from || currentFilters.date_to) {
|
|
961
|
+
out = out.filter(function (t) { return taskMatchesDateRange(t, currentFilters.date_from, currentFilters.date_to); });
|
|
962
|
+
}
|
|
963
|
+
return out;
|
|
964
|
+
}
|
|
965
|
+
|
|
586
966
|
function renderTasks() {
|
|
587
|
-
var filtered =
|
|
588
|
-
? allTasks.filter(function (t) { return t && t.status === currentFilter; })
|
|
589
|
-
: allTasks.slice();
|
|
967
|
+
var filtered = applyClientSideFilters(allTasks);
|
|
590
968
|
|
|
591
969
|
// Sort by created_at desc (newest first). Falls back gracefully if missing.
|
|
592
970
|
filtered.sort(function (a, b) {
|
|
@@ -606,13 +984,16 @@ html.dark {
|
|
|
606
984
|
}
|
|
607
985
|
$('taskTableBody').innerHTML = html;
|
|
608
986
|
showTable();
|
|
987
|
+
wireQuickActions();
|
|
609
988
|
}
|
|
610
989
|
|
|
611
990
|
// Count summary
|
|
612
|
-
|
|
991
|
+
var hasFilters = currentFilters.repo_path || currentFilters.status || currentFilters.acceptance_status ||
|
|
992
|
+
currentFilters.agent || currentFilters.warning_type || currentFilters.date_from || currentFilters.date_to;
|
|
993
|
+
if (hasFilters) {
|
|
613
994
|
$('countSummary').textContent = '显示 ' + filtered.length + ' / ' + allTasks.length + ' 条';
|
|
614
995
|
} else {
|
|
615
|
-
$('countSummary').textContent = '共 ' +
|
|
996
|
+
$('countSummary').textContent = '共 ' + filtered.length + ' 条';
|
|
616
997
|
}
|
|
617
998
|
}
|
|
618
999
|
|
|
@@ -634,6 +1015,64 @@ html.dark {
|
|
|
634
1015
|
el.innerHTML = '<span class="shrink-0 w-1.5 h-1.5 rounded-full" style="background-color: ' + dotColor + ';"></span><span>Watcher: ' + escapeHtml(status) + '</span>';
|
|
635
1016
|
}
|
|
636
1017
|
|
|
1018
|
+
// ---- Filter query building ----
|
|
1019
|
+
// Builds a query string from the backend-supported filter dimensions.
|
|
1020
|
+
function buildFilterQueryString() {
|
|
1021
|
+
var params = [];
|
|
1022
|
+
if (currentFilters.repo_path) {
|
|
1023
|
+
params.push('repo_path=' + encodeURIComponent(currentFilters.repo_path));
|
|
1024
|
+
}
|
|
1025
|
+
if (currentFilters.status) {
|
|
1026
|
+
params.push('status=' + encodeURIComponent(currentFilters.status));
|
|
1027
|
+
}
|
|
1028
|
+
if (currentFilters.acceptance_status) {
|
|
1029
|
+
params.push('acceptance_status=' + encodeURIComponent(currentFilters.acceptance_status));
|
|
1030
|
+
}
|
|
1031
|
+
if (currentFilters.agent) {
|
|
1032
|
+
params.push('agent=' + encodeURIComponent(currentFilters.agent));
|
|
1033
|
+
}
|
|
1034
|
+
return params.length ? ('?' + params.join('&')) : '';
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Reads current dropdown/input values into currentFilters.
|
|
1038
|
+
function readFiltersFromUI() {
|
|
1039
|
+
currentFilters.repo_path = $('repoFilter') ? $('repoFilter').value : '';
|
|
1040
|
+
currentFilters.status = $('statusFilter') ? $('statusFilter').value : '';
|
|
1041
|
+
currentFilters.acceptance_status = $('acceptanceFilter') ? $('acceptanceFilter').value : '';
|
|
1042
|
+
currentFilters.agent = $('agentFilter') ? $('agentFilter').value : '';
|
|
1043
|
+
currentFilters.warning_type = $('warningTypeFilter') ? $('warningTypeFilter').value : '';
|
|
1044
|
+
currentFilters.date_from = $('dateFromFilter') ? $('dateFromFilter').value : '';
|
|
1045
|
+
currentFilters.date_to = $('dateToFilter') ? $('dateToFilter').value : '';
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Resets all filter UI inputs to empty.
|
|
1049
|
+
function clearFilterUI() {
|
|
1050
|
+
var ids = ['repoFilter', 'statusFilter', 'acceptanceFilter', 'agentFilter', 'warningTypeFilter', 'dateFromFilter', 'dateToFilter'];
|
|
1051
|
+
ids.forEach(function (id) {
|
|
1052
|
+
var el = $(id);
|
|
1053
|
+
if (el) el.value = '';
|
|
1054
|
+
});
|
|
1055
|
+
currentFilters = {
|
|
1056
|
+
repo_path: '', status: '', acceptance_status: '', agent: '',
|
|
1057
|
+
warning_type: '', date_from: '', date_to: ''
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Updates the small status text describing active filters.
|
|
1062
|
+
function updateFilterStatusText() {
|
|
1063
|
+
var el = $('filterStatusText');
|
|
1064
|
+
if (!el) return;
|
|
1065
|
+
var parts = [];
|
|
1066
|
+
if (currentFilters.repo_path) parts.push('repo: ' + currentFilters.repo_path);
|
|
1067
|
+
if (currentFilters.status) parts.push('status: ' + currentFilters.status);
|
|
1068
|
+
if (currentFilters.acceptance_status) parts.push('acceptance: ' + currentFilters.acceptance_status);
|
|
1069
|
+
if (currentFilters.agent) parts.push('agent: ' + currentFilters.agent);
|
|
1070
|
+
if (currentFilters.warning_type) parts.push('warning: ' + currentFilters.warning_type);
|
|
1071
|
+
if (currentFilters.date_from) parts.push('from: ' + currentFilters.date_from);
|
|
1072
|
+
if (currentFilters.date_to) parts.push('to: ' + currentFilters.date_to);
|
|
1073
|
+
el.textContent = parts.length ? ('已应用筛选 · ' + parts.join(' · ')) : '无筛选';
|
|
1074
|
+
}
|
|
1075
|
+
|
|
637
1076
|
// ---- API call ----
|
|
638
1077
|
function setRefreshing(isRefreshing) {
|
|
639
1078
|
var icon = $('refreshIcon');
|
|
@@ -648,7 +1087,8 @@ html.dark {
|
|
|
648
1087
|
function fetchTasks() {
|
|
649
1088
|
showLoading();
|
|
650
1089
|
setRefreshing(true);
|
|
651
|
-
|
|
1090
|
+
var url = API_URL + buildFilterQueryString();
|
|
1091
|
+
fetch(url, {
|
|
652
1092
|
method: 'GET',
|
|
653
1093
|
headers: { 'Accept': 'application/json' },
|
|
654
1094
|
cache: 'no-store'
|
|
@@ -665,6 +1105,7 @@ html.dark {
|
|
|
665
1105
|
allTasks = (data && Array.isArray(data.tasks)) ? data.tasks : [];
|
|
666
1106
|
renderWatcher(data && data.watcher);
|
|
667
1107
|
renderTasks();
|
|
1108
|
+
updateFilterStatusText();
|
|
668
1109
|
})
|
|
669
1110
|
.catch(function (err) {
|
|
670
1111
|
var msg = (err && err.message) ? err.message : '未知错误';
|
|
@@ -679,10 +1120,62 @@ html.dark {
|
|
|
679
1120
|
});
|
|
680
1121
|
}
|
|
681
1122
|
|
|
1123
|
+
// ---- Dropdown population ----
|
|
1124
|
+
async function loadRepos() {
|
|
1125
|
+
var select = $('repoFilter');
|
|
1126
|
+
if (!select) return;
|
|
1127
|
+
try {
|
|
1128
|
+
var data = await fetchJson('/api/workspace/repos');
|
|
1129
|
+
var repos = Array.isArray(data.repos) ? data.repos : [];
|
|
1130
|
+
var html = '<option value="" data-zh="全部仓库" data-en="All repos">全部仓库</option>';
|
|
1131
|
+
repos.forEach(function (r) {
|
|
1132
|
+
var label = r.name + (r.has_package_json && r.version ? ' (v' + r.version + ')' : '');
|
|
1133
|
+
var val = r.path || r.name;
|
|
1134
|
+
html += '<option value="' + escapeAttr(val) + '">' + escapeHtml(label) + '</option>';
|
|
1135
|
+
});
|
|
1136
|
+
select.innerHTML = html;
|
|
1137
|
+
} catch (err) {
|
|
1138
|
+
// keep default option
|
|
1139
|
+
console.error('loadRepos failed:', err);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
async function loadAgents() {
|
|
1144
|
+
var select = $('agentFilter');
|
|
1145
|
+
if (!select) return;
|
|
1146
|
+
try {
|
|
1147
|
+
var data = await fetchJson('/api/status');
|
|
1148
|
+
var agents = (data && Array.isArray(data.agents)) ? data.agents : [];
|
|
1149
|
+
var html = '<option value="" data-zh="全部 Agent" data-en="All Agents">全部 Agent</option>';
|
|
1150
|
+
agents.forEach(function (a) {
|
|
1151
|
+
if (a && a.name) {
|
|
1152
|
+
html += '<option value="' + escapeAttr(a.name) + '">' + escapeHtml(a.name) + (a.available ? '' : ' (unavailable)') + '</option>';
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
1155
|
+
select.innerHTML = html;
|
|
1156
|
+
} catch (err) {
|
|
1157
|
+
// keep default option
|
|
1158
|
+
console.error('loadAgents failed:', err);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
682
1162
|
// ---- Event wiring ----
|
|
1163
|
+
// Status filter: apply immediately on change (keeps original UX)
|
|
683
1164
|
$('statusFilter').addEventListener('change', function (e) {
|
|
684
|
-
|
|
685
|
-
|
|
1165
|
+
readFiltersFromUI();
|
|
1166
|
+
fetchTasks();
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// Apply Filters button: reads all filter values and refetches
|
|
1170
|
+
$('applyFiltersBtn').addEventListener('click', function () {
|
|
1171
|
+
readFiltersFromUI();
|
|
1172
|
+
fetchTasks();
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
// Clear Filters button: resets all filters and refetches
|
|
1176
|
+
$('clearFiltersBtn').addEventListener('click', function () {
|
|
1177
|
+
clearFilterUI();
|
|
1178
|
+
fetchTasks();
|
|
686
1179
|
});
|
|
687
1180
|
|
|
688
1181
|
$('refreshBtn').addEventListener('click', fetchTasks);
|
|
@@ -692,8 +1185,52 @@ html.dark {
|
|
|
692
1185
|
retryBtn.addEventListener('click', fetchTasks);
|
|
693
1186
|
}
|
|
694
1187
|
|
|
695
|
-
//
|
|
696
|
-
|
|
1188
|
+
// Safe modal close handlers
|
|
1189
|
+
var modalClose = $('safe-modal-close');
|
|
1190
|
+
if (modalClose) {
|
|
1191
|
+
modalClose.addEventListener('click', function () { closeSafeModal(); });
|
|
1192
|
+
}
|
|
1193
|
+
var safeModal = $('safe-modal');
|
|
1194
|
+
if (safeModal) {
|
|
1195
|
+
safeModal.addEventListener('click', function (e) {
|
|
1196
|
+
if (e.target === safeModal) closeSafeModal();
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// ---- Quick action button wiring (called after render) ----
|
|
1201
|
+
var SAFE_TITLE_MAP = {
|
|
1202
|
+
'safe-result': 'Safe Result',
|
|
1203
|
+
'safe-audit': 'Safe Audit',
|
|
1204
|
+
'safe-test-summary': 'Safe Test Summary',
|
|
1205
|
+
'safe-diff-summary': 'Safe Diff Summary'
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
function wireQuickActions() {
|
|
1209
|
+
var tbody = $('taskTableBody');
|
|
1210
|
+
if (!tbody) return;
|
|
1211
|
+
tbody.querySelectorAll('[data-safe-action]').forEach(function (btn) {
|
|
1212
|
+
btn.addEventListener('click', function (e) {
|
|
1213
|
+
e.preventDefault();
|
|
1214
|
+
e.stopPropagation();
|
|
1215
|
+
var action = btn.getAttribute('data-safe-action');
|
|
1216
|
+
var tid = btn.getAttribute('data-task-id');
|
|
1217
|
+
fetchSafeSummary(tid, action, SAFE_TITLE_MAP[action] || action);
|
|
1218
|
+
});
|
|
1219
|
+
});
|
|
1220
|
+
tbody.querySelectorAll('[data-copy-id]').forEach(function (btn) {
|
|
1221
|
+
btn.addEventListener('click', function (e) {
|
|
1222
|
+
e.preventDefault();
|
|
1223
|
+
e.stopPropagation();
|
|
1224
|
+
copyToClipboard(btn.getAttribute('data-copy-id'), 'task_id');
|
|
1225
|
+
});
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Initial load: populate filter dropdowns, then fetch tasks
|
|
1230
|
+
// Dropdowns load in parallel (best-effort); tasks fetch runs regardless.
|
|
1231
|
+
Promise.allSettled([loadRepos(), loadAgents()]).finally(function () {
|
|
1232
|
+
fetchTasks();
|
|
1233
|
+
});
|
|
697
1234
|
})();
|
|
698
1235
|
</script>
|
|
699
1236
|
|