fix(operation-trace): correct capture scope, broaden coverage, polish panel

- Capture scope: treat the ``audit`` bucket (viewing the log / tracing
  settings) as a read-level observation instead of recording it from the
  mutation level. At "mutations only" the log no longer fills with GET page
  views; views are recorded only at the read level.
- Coverage: classify plugin lifecycle (install / upgrade / config / page),
  skills, knowledge bases and MCP servers, with dedicated actions and
  resource types, so those operations are traced instead of falling into the
  generic resource bucket. Route rules now support multi-fragment AND
  matching and per-verb read/write actions.
- Panel: move "Download logs" next to "Refresh" in the toolbar.
- Verification: count hash mismatches and broken links separately instead of
  collapsing both into one "tampered" signal.
- i18n: add the new action labels plus the split verification labels in all
  eight locales.
This commit is contained in:
TyperBody
2026-09-26 02:26:48 +08:00
parent 78baabde96
commit 48d92ad612
11 changed files with 261 additions and 51 deletions
@@ -323,6 +323,19 @@ export default function OperationTracePanel({
)}
{t('operationTrace.refresh')}
</Button>
<Button
size="sm"
variant="outline"
onClick={() => void downloadLogs()}
disabled={exporting}
>
{exporting ? (
<Loader2 className="size-3.5 animate-spin" />
) : (
<Download className="size-3.5" />
)}
{t('operationTrace.export')}
</Button>
</PanelToolbar>
<PanelBody className="space-y-6">
@@ -429,13 +442,22 @@ export default function OperationTracePanel({
{t('operationTrace.records')}
</h3>
<Badge variant="secondary">{total}</Badge>
{(page?.tampered_count ?? 0) > 0 && (
<Badge variant="destructive">
{t('operationTrace.tamperedCount', {
count: page?.tampered_count ?? 0,
})}
</Badge>
)}
{(page?.tampered_count ?? 0) > 0 &&
(page?.integrity_failed_count ?? 0) > 0 && (
<Badge variant="destructive">
{t('operationTrace.integrityFailedCount', {
count: page?.integrity_failed_count ?? 0,
})}
</Badge>
)}
{(page?.tampered_count ?? 0) > 0 &&
(page?.chain_failed_count ?? 0) > 0 && (
<Badge variant="destructive">
{t('operationTrace.chainFailedCount', {
count: page?.chain_failed_count ?? 0,
})}
</Badge>
)}
</div>
<div className="ml-auto flex flex-wrap items-center gap-2">
<Select
@@ -490,19 +512,6 @@ export default function OperationTracePanel({
))}
</SelectContent>
</Select>
<Button
size="sm"
variant="outline"
onClick={() => void downloadLogs()}
disabled={exporting}
>
{exporting ? (
<Loader2 className="size-3.5 animate-spin" />
) : (
<Download className="size-3.5" />
)}
{t('operationTrace.export')}
</Button>
<Select
value={query.actor ?? ALL_VALUE}
onValueChange={(value) =>
@@ -76,6 +76,10 @@ export interface OperationLogPage {
offset: number;
/** How many records on this page failed hash or chain verification. */
tampered_count: number;
/** Records whose stored hash no longer matches their content. */
integrity_failed_count: number;
/** Records whose predecessor link is broken. */
chain_failed_count: number;
}
export interface OperationLogFilters {