@gmickel/gno 1.27.1 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -20
- package/assets/skill/SKILL.md +59 -1
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip.sha256 +1 -0
- package/browser-extension/dist/chunk-b2zm0jjd.js +50 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/browser-extension/dist/preview.html +1 -1
- package/browser-extension/dist/service-worker.js +6 -6
- package/package.json +2 -2
- package/spec/cli.md +82 -5
- package/spec/db/schema.sql +31 -0
- package/spec/mcp.md +71 -0
- package/spec/output-schemas/ask.schema.json +156 -1
- package/spec/output-schemas/browser-clip-preview.schema.json +30 -0
- package/spec/output-schemas/collection-egress-check.schema.json +91 -0
- package/spec/output-schemas/collection-egress-policy-set.schema.json +56 -0
- package/spec/output-schemas/collection-egress-policy.schema.json +41 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +183 -3
- package/spec/output-schemas/context-capsule-verification.schema.json +1 -1
- package/spec/output-schemas/egress-audit-management.schema.json +88 -0
- package/spec/output-schemas/get.schema.json +94 -0
- package/spec/output-schemas/mcp-http-error.schema.json +113 -2
- package/spec/output-schemas/mcp-job-status.schema.json +28 -0
- package/spec/output-schemas/multi-get.schema.json +128 -0
- package/spec/output-schemas/publish-artifact.schema.json +32 -0
- package/spec/output-schemas/record-import.schema.json +223 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
- package/spec/output-schemas/search-result.schema.json +94 -0
- package/spec/output-schemas/search-results.schema.json +155 -0
- package/spec/project-profile.schema.json +66 -0
- package/src/app/context-format.ts +1 -1
- package/src/app/context-runtime-contract.ts +16 -7
- package/src/cli/commands/ask.ts +7 -0
- package/src/cli/commands/collection/index.ts +5 -0
- package/src/cli/commands/collection/policy.ts +126 -0
- package/src/cli/commands/context-build.ts +5 -0
- package/src/cli/commands/daemon.ts +45 -3
- package/src/cli/commands/egress-audit.ts +68 -0
- package/src/cli/commands/embed.ts +2 -0
- package/src/cli/commands/get.ts +12 -2
- package/src/cli/commands/index-cmd.ts +13 -0
- package/src/cli/commands/multi-get.ts +9 -2
- package/src/cli/commands/query.ts +12 -0
- package/src/cli/commands/replay.ts +13 -3
- package/src/cli/commands/shared.ts +28 -0
- package/src/cli/commands/update.ts +5 -0
- package/src/cli/commands/vsearch.ts +3 -1
- package/src/cli/program.ts +127 -0
- package/src/config/index.ts +9 -0
- package/src/config/project-profile.ts +2 -0
- package/src/config/types.ts +66 -0
- package/src/converters/adapters/browser-export/adapter.ts +199 -0
- package/src/converters/adapters/browser-export/formats.ts +358 -0
- package/src/converters/adapters/email/adapter.ts +429 -0
- package/src/converters/adapters/email/html.ts +162 -0
- package/src/converters/adapters/email/mime.ts +454 -0
- package/src/converters/adapters/email/parameters.ts +77 -0
- package/src/converters/adapters/ical/adapter.ts +475 -0
- package/src/converters/adapters/ical/recurrence.ts +186 -0
- package/src/converters/adapters/jsonl/adapter.ts +238 -0
- package/src/converters/adapters/jsonl/config.ts +105 -0
- package/src/converters/adapters/shared/html-text.ts +165 -0
- package/src/converters/adapters/shared/record-utils.ts +79 -0
- package/src/converters/adapters/shared/utf8-lines.ts +141 -0
- package/src/converters/adapters/transcript/adapter.ts +295 -0
- package/src/converters/adapters/transcript/json.ts +184 -0
- package/src/converters/adapters/transcript/model.ts +171 -0
- package/src/converters/adapters/transcript/text.ts +58 -0
- package/src/converters/adapters/transcript/timed.ts +152 -0
- package/src/converters/index.ts +11 -1
- package/src/converters/mime.ts +8 -0
- package/src/converters/pipeline.ts +15 -1
- package/src/converters/registry.ts +37 -1
- package/src/converters/types.ts +136 -0
- package/src/core/browser-clip.ts +16 -1
- package/src/core/collection-egress-policy-projection.ts +28 -0
- package/src/core/collection-egress-policy-service.ts +443 -0
- package/src/core/collection-egress-policy-validation.ts +242 -0
- package/src/core/config-mutation.ts +31 -19
- package/src/core/context-capsule-schema.ts +198 -1
- package/src/core/context-capsule-validation.ts +4 -4
- package/src/core/context-capsule-verification.ts +5 -1
- package/src/core/context-capsule.ts +177 -112
- package/src/core/context-evidence.ts +93 -15
- package/src/core/destination-classifier.ts +402 -0
- package/src/core/document-capabilities.ts +12 -0
- package/src/core/egress-audit.ts +239 -0
- package/src/core/egress-authorization.ts +50 -0
- package/src/core/egress-enforcement.ts +264 -0
- package/src/core/egress-policy.ts +440 -0
- package/src/core/egress-provenance.ts +336 -0
- package/src/core/job-manager.ts +37 -0
- package/src/core/network-boundary-inventory.ts +261 -0
- package/src/core/project-profile-apply-state.ts +5 -0
- package/src/core/project-profile.ts +4 -0
- package/src/core/record-metadata.ts +49 -0
- package/src/core/retrieval-qrels.ts +6 -0
- package/src/core/retrieval-replay.ts +18 -4
- package/src/core/retrieval-trace-export.ts +23 -4
- package/src/core/retrieval-trace-management-helpers.ts +1 -0
- package/src/core/retrieval-trace-management-types.ts +3 -0
- package/src/core/retrieval-trace-management.ts +9 -1
- package/src/core/retrieval-trace-session.ts +94 -0
- package/src/core/retrieval-trace.ts +22 -0
- package/src/ingestion/record-adapter-canonical.ts +433 -0
- package/src/ingestion/record-adapter.ts +448 -0
- package/src/ingestion/record-container.ts +704 -0
- package/src/ingestion/record-path.ts +20 -0
- package/src/ingestion/record-sync.ts +70 -0
- package/src/ingestion/sync.ts +243 -36
- package/src/ingestion/types.ts +71 -1
- package/src/ingestion/walker.ts +31 -11
- package/src/llm/errors.ts +10 -0
- package/src/llm/http-inference.ts +175 -0
- package/src/llm/http-policy.ts +537 -0
- package/src/llm/httpEmbedding.ts +47 -28
- package/src/llm/httpGeneration.ts +31 -18
- package/src/llm/httpRerank.ts +30 -18
- package/src/llm/index.ts +1 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
- package/src/llm/pinned-http-connection.ts +177 -0
- package/src/mcp/context.ts +53 -1
- package/src/mcp/http-egress.ts +220 -0
- package/src/mcp/http-security.ts +32 -21
- package/src/mcp/http-session.ts +13 -2
- package/src/mcp/http-transport.ts +96 -6
- package/src/mcp/sync-egress.ts +24 -0
- package/src/mcp/tools/add-collection.ts +4 -0
- package/src/mcp/tools/ask.ts +1 -1
- package/src/mcp/tools/context.ts +9 -2
- package/src/mcp/tools/egress.ts +247 -0
- package/src/mcp/tools/embed.ts +1 -0
- package/src/mcp/tools/get.ts +10 -2
- package/src/mcp/tools/index-cmd.ts +7 -0
- package/src/mcp/tools/index.ts +98 -0
- package/src/mcp/tools/multi-get.ts +9 -2
- package/src/mcp/tools/query.ts +10 -0
- package/src/mcp/tools/remove-collection.ts +4 -0
- package/src/mcp/tools/sync.ts +6 -0
- package/src/mcp/tools/trace.ts +7 -1
- package/src/mcp/tools/vsearch.ts +1 -0
- package/src/mcp/tools/workspace-write.ts +2 -0
- package/src/pipeline/egress-lineage.ts +124 -0
- package/src/pipeline/filters.ts +1 -1
- package/src/pipeline/graph-retrieval.ts +7 -4
- package/src/pipeline/hybrid.ts +21 -4
- package/src/pipeline/result-context.ts +12 -4
- package/src/pipeline/search.ts +23 -4
- package/src/pipeline/types.ts +6 -0
- package/src/pipeline/vsearch.ts +67 -28
- package/src/publish/artifact.ts +31 -3
- package/src/publish/export-service.ts +25 -0
- package/src/sdk/client.ts +119 -2
- package/src/sdk/documents.ts +13 -5
- package/src/sdk/embed.ts +1 -0
- package/src/sdk/types.ts +33 -0
- package/src/serve/browse-tree.ts +4 -2
- package/src/serve/clipper-capture.ts +5 -1
- package/src/serve/closed-json.ts +61 -0
- package/src/serve/config-sync.ts +4 -1
- package/src/serve/context.ts +1 -0
- package/src/serve/doc-events.ts +110 -39
- package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
- package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
- package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
- package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Collections.tsx +3 -0
- package/src/serve/resident-request.ts +80 -3
- package/src/serve/resident-runtime.ts +63 -1
- package/src/serve/routes/api.ts +237 -65
- package/src/serve/routes/clipper.ts +64 -1
- package/src/serve/routes/mcp.ts +6 -0
- package/src/serve/routes/traces.ts +27 -4
- package/src/serve/server.ts +118 -7
- package/src/store/migrations/022-record-export-lineage.ts +42 -0
- package/src/store/migrations/023-collection-egress-policy.ts +61 -0
- package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
- package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
- package/src/store/migrations/index.ts +8 -0
- package/src/store/retrieval-trace-codec.ts +15 -0
- package/src/store/sqlite/adapter.ts +284 -14
- package/src/store/sqlite/change-journal-store.ts +41 -2
- package/src/store/sqlite/egress-audit-store.ts +485 -0
- package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
- package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
- package/src/store/sqlite/retrieval-trace-store.ts +104 -5
- package/src/store/types.ts +166 -1
- package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip.sha256 +0 -1
- package/browser-extension/dist/chunk-vn5f663b.js +0 -50
|
@@ -8,6 +8,12 @@ import {
|
|
|
8
8
|
import { useEffect, useMemo, useState } from "react";
|
|
9
9
|
|
|
10
10
|
import { apiFetch } from "../hooks/use-api";
|
|
11
|
+
import { CollectionEgressCheckPanel } from "./CollectionEgressCheckPanel";
|
|
12
|
+
import {
|
|
13
|
+
CollectionEgressPolicyEditor,
|
|
14
|
+
type CollectionEgressPolicy,
|
|
15
|
+
} from "./CollectionEgressPolicyEditor";
|
|
16
|
+
import { EgressAuditPanel } from "./EgressAuditPanel";
|
|
11
17
|
import { Button } from "./ui/button";
|
|
12
18
|
import {
|
|
13
19
|
Dialog,
|
|
@@ -50,6 +56,15 @@ export interface CollectionModelDetails {
|
|
|
50
56
|
name: string;
|
|
51
57
|
path: string;
|
|
52
58
|
pattern?: string;
|
|
59
|
+
egressPolicy?: {
|
|
60
|
+
schemaVersion: "1.0";
|
|
61
|
+
collection: string;
|
|
62
|
+
configuredPolicy: "local_only" | "lan" | "remote" | null;
|
|
63
|
+
effectivePolicy: "local_only" | "lan" | "remote";
|
|
64
|
+
source: "explicit" | "config_default";
|
|
65
|
+
revision: number;
|
|
66
|
+
version: string;
|
|
67
|
+
} | null;
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
interface UpdateCollectionResponse {
|
|
@@ -58,6 +73,7 @@ interface UpdateCollectionResponse {
|
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
interface CollectionModelDialogProps {
|
|
76
|
+
availableCollections?: readonly string[];
|
|
61
77
|
collection: CollectionModelDetails | null;
|
|
62
78
|
onOpenChange: (open: boolean) => void;
|
|
63
79
|
onSaved: () => void;
|
|
@@ -119,6 +135,7 @@ function collectionLooksCodeHeavy(collection: CollectionModelDetails): boolean {
|
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
export function CollectionModelDialog({
|
|
138
|
+
availableCollections = [],
|
|
122
139
|
collection,
|
|
123
140
|
onOpenChange,
|
|
124
141
|
onSaved,
|
|
@@ -132,6 +149,9 @@ export function CollectionModelDialog({
|
|
|
132
149
|
});
|
|
133
150
|
const [error, setError] = useState<string | null>(null);
|
|
134
151
|
const [saving, setSaving] = useState(false);
|
|
152
|
+
const [policyDraft, setPolicyDraft] =
|
|
153
|
+
useState<CollectionEgressPolicy>("local_only");
|
|
154
|
+
const [relaxationConfirmed, setRelaxationConfirmed] = useState(false);
|
|
135
155
|
const showCodeRecommendation =
|
|
136
156
|
collection !== null &&
|
|
137
157
|
collectionLooksCodeHeavy(collection) &&
|
|
@@ -150,6 +170,8 @@ export function CollectionModelDialog({
|
|
|
150
170
|
});
|
|
151
171
|
setError(null);
|
|
152
172
|
setSaving(false);
|
|
173
|
+
setPolicyDraft(collection.egressPolicy?.effectivePolicy ?? "local_only");
|
|
174
|
+
setRelaxationConfirmed(false);
|
|
153
175
|
}, [collection, open]);
|
|
154
176
|
|
|
155
177
|
const patch = useMemo(() => {
|
|
@@ -170,7 +192,13 @@ export function CollectionModelDialog({
|
|
|
170
192
|
return nextPatch;
|
|
171
193
|
}, [collection, draft]);
|
|
172
194
|
|
|
173
|
-
const
|
|
195
|
+
const originalPolicy =
|
|
196
|
+
collection?.egressPolicy?.effectivePolicy ?? "local_only";
|
|
197
|
+
const policyChanged = policyDraft !== originalPolicy;
|
|
198
|
+
const policyOrder = { local_only: 0, lan: 1, remote: 2 } as const;
|
|
199
|
+
const policyRelaxed =
|
|
200
|
+
policyChanged && policyOrder[policyDraft] > policyOrder[originalPolicy];
|
|
201
|
+
const hasChanges = Object.keys(patch).length > 0 || policyChanged;
|
|
174
202
|
const embedChanged = Object.hasOwn(patch, "embed");
|
|
175
203
|
|
|
176
204
|
const handleSave = async () => {
|
|
@@ -181,13 +209,46 @@ export function CollectionModelDialog({
|
|
|
181
209
|
setSaving(true);
|
|
182
210
|
setError(null);
|
|
183
211
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
212
|
+
if (policyChanged) {
|
|
213
|
+
const state = collection.egressPolicy;
|
|
214
|
+
const { error: policyError } = await apiFetch(
|
|
215
|
+
`/api/collections/${encodeURIComponent(collection.name)}/egress-policy`,
|
|
216
|
+
{
|
|
217
|
+
method: "PUT",
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
policy: policyDraft,
|
|
220
|
+
confirmation:
|
|
221
|
+
policyRelaxed && state && relaxationConfirmed
|
|
222
|
+
? {
|
|
223
|
+
collection: state.collection,
|
|
224
|
+
currentPolicy: state.effectivePolicy,
|
|
225
|
+
currentRevision: state.revision,
|
|
226
|
+
targetPolicy: policyDraft,
|
|
227
|
+
acknowledged: true,
|
|
228
|
+
}
|
|
229
|
+
: undefined,
|
|
230
|
+
}),
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
if (policyError) {
|
|
234
|
+
setSaving(false);
|
|
235
|
+
setError(policyError);
|
|
236
|
+
return;
|
|
189
237
|
}
|
|
190
|
-
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const requestError =
|
|
241
|
+
Object.keys(patch).length > 0
|
|
242
|
+
? (
|
|
243
|
+
await apiFetch<UpdateCollectionResponse>(
|
|
244
|
+
`/api/collections/${encodeURIComponent(collection.name)}`,
|
|
245
|
+
{
|
|
246
|
+
method: "PATCH",
|
|
247
|
+
body: JSON.stringify({ models: patch }),
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
).error
|
|
251
|
+
: null;
|
|
191
252
|
|
|
192
253
|
setSaving(false);
|
|
193
254
|
|
|
@@ -240,6 +301,27 @@ export function CollectionModelDialog({
|
|
|
240
301
|
{/* Scrollable model roles */}
|
|
241
302
|
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden">
|
|
242
303
|
<div className="divide-y divide-border/15">
|
|
304
|
+
<CollectionEgressPolicyEditor
|
|
305
|
+
confirmed={relaxationConfirmed}
|
|
306
|
+
onConfirmedChange={setRelaxationConfirmed}
|
|
307
|
+
onPolicyChange={(policy) => {
|
|
308
|
+
setPolicyDraft(policy);
|
|
309
|
+
setRelaxationConfirmed(false);
|
|
310
|
+
}}
|
|
311
|
+
policy={policyDraft}
|
|
312
|
+
relaxed={policyRelaxed}
|
|
313
|
+
revision={collection?.egressPolicy?.revision ?? 0}
|
|
314
|
+
source={collection?.egressPolicy?.source ?? "config_default"}
|
|
315
|
+
/>
|
|
316
|
+
{collection ? (
|
|
317
|
+
<CollectionEgressCheckPanel
|
|
318
|
+
availableCollections={availableCollections}
|
|
319
|
+
collection={collection.name}
|
|
320
|
+
effectivePolicy={originalPolicy}
|
|
321
|
+
source={collection.egressPolicy?.source ?? "config_default"}
|
|
322
|
+
/>
|
|
323
|
+
) : null}
|
|
324
|
+
<EgressAuditPanel />
|
|
243
325
|
{MODEL_ROLES.map((role) => {
|
|
244
326
|
const source = collection?.modelSources?.[role] ?? "preset";
|
|
245
327
|
const effectiveValue = collection?.effectiveModels?.[role] ?? "";
|
|
@@ -385,12 +467,14 @@ export function CollectionModelDialog({
|
|
|
385
467
|
</Button>
|
|
386
468
|
<Button
|
|
387
469
|
className="text-xs"
|
|
388
|
-
disabled={
|
|
470
|
+
disabled={
|
|
471
|
+
!hasChanges || saving || (policyRelaxed && !relaxationConfirmed)
|
|
472
|
+
}
|
|
389
473
|
onClick={() => void handleSave()}
|
|
390
474
|
size="sm"
|
|
391
475
|
>
|
|
392
476
|
{saving ? <Loader2Icon className="size-3.5 animate-spin" /> : null}
|
|
393
|
-
Save
|
|
477
|
+
Save collection settings
|
|
394
478
|
</Button>
|
|
395
479
|
</DialogFooter>
|
|
396
480
|
</DialogContent>
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChevronRightIcon,
|
|
3
|
+
Loader2Icon,
|
|
4
|
+
ReceiptTextIcon,
|
|
5
|
+
Trash2Icon,
|
|
6
|
+
} from "lucide-react";
|
|
7
|
+
import { useCallback, useEffect, useState } from "react";
|
|
8
|
+
|
|
9
|
+
import { apiFetch } from "../hooks/use-api";
|
|
10
|
+
import { Button } from "./ui/button";
|
|
11
|
+
|
|
12
|
+
interface AuditReceipt {
|
|
13
|
+
action: string;
|
|
14
|
+
auditId: string;
|
|
15
|
+
byteSize: number;
|
|
16
|
+
contentClass: string;
|
|
17
|
+
createdAtMs: number;
|
|
18
|
+
decision: "allow" | "deny";
|
|
19
|
+
destinationZone: string;
|
|
20
|
+
effectivePolicy: string;
|
|
21
|
+
reasonCode: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface AuditPage {
|
|
25
|
+
nextCursor: string | null;
|
|
26
|
+
receipts: AuditReceipt[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface AuditStatus {
|
|
30
|
+
bytes: number;
|
|
31
|
+
receipts: number;
|
|
32
|
+
retention: {
|
|
33
|
+
maxAgeDays: number;
|
|
34
|
+
maxBytes: number;
|
|
35
|
+
maxReceipts: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface CleanupResult {
|
|
40
|
+
auditId?: string;
|
|
41
|
+
checkpointedFrames: number;
|
|
42
|
+
deleted: number;
|
|
43
|
+
physicalCleanup: string;
|
|
44
|
+
remainingWalFrames: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function EgressAuditPanel() {
|
|
48
|
+
const [receipts, setReceipts] = useState<AuditReceipt[]>([]);
|
|
49
|
+
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
|
50
|
+
const [status, setStatus] = useState<AuditStatus | null>(null);
|
|
51
|
+
const [selected, setSelected] = useState<AuditReceipt | null>(null);
|
|
52
|
+
const [pendingDelete, setPendingDelete] = useState<string | null>(null);
|
|
53
|
+
const [purgeConfirmed, setPurgeConfirmed] = useState(false);
|
|
54
|
+
const [cleanup, setCleanup] = useState<CleanupResult | null>(null);
|
|
55
|
+
const [loading, setLoading] = useState(false);
|
|
56
|
+
const [error, setError] = useState<string | null>(null);
|
|
57
|
+
|
|
58
|
+
const loadStatus = useCallback(async () => {
|
|
59
|
+
const response = await apiFetch<AuditStatus>("/api/egress/audits/status");
|
|
60
|
+
if (response.data) setStatus(response.data);
|
|
61
|
+
else if (response.error) setError(response.error);
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
const loadPage = useCallback(async (cursor?: string) => {
|
|
65
|
+
setLoading(true);
|
|
66
|
+
setError(null);
|
|
67
|
+
const suffix = cursor
|
|
68
|
+
? `?limit=10&cursor=${encodeURIComponent(cursor)}`
|
|
69
|
+
: "?limit=10";
|
|
70
|
+
const response = await apiFetch<AuditPage>(`/api/egress/audits${suffix}`);
|
|
71
|
+
setLoading(false);
|
|
72
|
+
if (response.error || !response.data) {
|
|
73
|
+
setError(response.error ?? "Audit list failed");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const page = response.data;
|
|
77
|
+
setReceipts((current) =>
|
|
78
|
+
cursor ? [...current, ...page.receipts] : page.receipts
|
|
79
|
+
);
|
|
80
|
+
setNextCursor(page.nextCursor);
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
void Promise.all([loadPage(), loadStatus()]);
|
|
85
|
+
}, [loadPage, loadStatus]);
|
|
86
|
+
|
|
87
|
+
const show = async (auditId: string) => {
|
|
88
|
+
setError(null);
|
|
89
|
+
const response = await apiFetch<{ receipt: AuditReceipt }>(
|
|
90
|
+
`/api/egress/audits/${encodeURIComponent(auditId)}`
|
|
91
|
+
);
|
|
92
|
+
if (response.data) setSelected(response.data.receipt);
|
|
93
|
+
else setError(response.error ?? "Audit receipt unavailable");
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const remove = async (auditId: string) => {
|
|
97
|
+
setError(null);
|
|
98
|
+
const response = await apiFetch<CleanupResult>(
|
|
99
|
+
`/api/egress/audits/${encodeURIComponent(auditId)}`,
|
|
100
|
+
{ method: "DELETE" }
|
|
101
|
+
);
|
|
102
|
+
setPendingDelete(null);
|
|
103
|
+
if (response.error || !response.data) {
|
|
104
|
+
setError(response.error ?? "Audit deletion failed");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
setCleanup(response.data);
|
|
108
|
+
setSelected((current) => (current?.auditId === auditId ? null : current));
|
|
109
|
+
setReceipts((current) =>
|
|
110
|
+
current.filter((receipt) => receipt.auditId !== auditId)
|
|
111
|
+
);
|
|
112
|
+
await loadStatus();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const purge = async () => {
|
|
116
|
+
setError(null);
|
|
117
|
+
const response = await apiFetch<CleanupResult>("/api/egress/audits", {
|
|
118
|
+
method: "DELETE",
|
|
119
|
+
});
|
|
120
|
+
setPurgeConfirmed(false);
|
|
121
|
+
if (response.error || !response.data) {
|
|
122
|
+
setError(response.error ?? "Audit purge failed");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
setCleanup(response.data);
|
|
126
|
+
setReceipts([]);
|
|
127
|
+
setNextCursor(null);
|
|
128
|
+
setSelected(null);
|
|
129
|
+
await loadStatus();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<section
|
|
134
|
+
aria-labelledby="egress-audit-title"
|
|
135
|
+
className="space-y-3 border-border/15 border-t px-6 py-5"
|
|
136
|
+
>
|
|
137
|
+
<div className="flex items-start justify-between gap-4">
|
|
138
|
+
<div>
|
|
139
|
+
<h3
|
|
140
|
+
className="flex items-center gap-2 font-medium text-[13px]"
|
|
141
|
+
id="egress-audit-title"
|
|
142
|
+
>
|
|
143
|
+
<ReceiptTextIcon className="size-3.5 text-secondary/70" />
|
|
144
|
+
Local audit receipts
|
|
145
|
+
</h3>
|
|
146
|
+
<p className="mt-1 text-muted-foreground/50 text-xs">
|
|
147
|
+
Content-free decisions; newest first; retained locally.
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
<p className="font-mono text-[10px] text-muted-foreground/50">
|
|
151
|
+
{status
|
|
152
|
+
? `${status.receipts} receipts · ${status.bytes} bytes`
|
|
153
|
+
: "loading status"}
|
|
154
|
+
</p>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
{error ? (
|
|
158
|
+
<p className="text-destructive text-xs" role="alert">
|
|
159
|
+
{error}
|
|
160
|
+
</p>
|
|
161
|
+
) : null}
|
|
162
|
+
|
|
163
|
+
<div className="max-h-44 overflow-y-auto rounded-md border border-border/20">
|
|
164
|
+
{receipts.length === 0 && !loading ? (
|
|
165
|
+
<p className="p-3 text-muted-foreground/50 text-xs">
|
|
166
|
+
No audit receipts yet.
|
|
167
|
+
</p>
|
|
168
|
+
) : null}
|
|
169
|
+
{receipts.map((receipt) => (
|
|
170
|
+
<div
|
|
171
|
+
className="flex items-center gap-2 border-border/15 border-b px-3 py-2 last:border-0"
|
|
172
|
+
key={receipt.auditId}
|
|
173
|
+
>
|
|
174
|
+
<button
|
|
175
|
+
className="min-w-0 flex-1 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-secondary"
|
|
176
|
+
onClick={() => void show(receipt.auditId)}
|
|
177
|
+
type="button"
|
|
178
|
+
>
|
|
179
|
+
<span className="block truncate font-mono text-[10px]">
|
|
180
|
+
{receipt.decision} · {receipt.reasonCode}
|
|
181
|
+
</span>
|
|
182
|
+
<span className="block text-[10px] text-muted-foreground/45">
|
|
183
|
+
{receipt.action} / {receipt.destinationZone} /{" "}
|
|
184
|
+
{receipt.contentClass}
|
|
185
|
+
</span>
|
|
186
|
+
</button>
|
|
187
|
+
{pendingDelete === receipt.auditId ? (
|
|
188
|
+
<div className="flex gap-1">
|
|
189
|
+
<Button
|
|
190
|
+
onClick={() => void remove(receipt.auditId)}
|
|
191
|
+
size="sm"
|
|
192
|
+
type="button"
|
|
193
|
+
variant="destructive"
|
|
194
|
+
>
|
|
195
|
+
Confirm
|
|
196
|
+
</Button>
|
|
197
|
+
<Button
|
|
198
|
+
onClick={() => setPendingDelete(null)}
|
|
199
|
+
size="sm"
|
|
200
|
+
type="button"
|
|
201
|
+
variant="ghost"
|
|
202
|
+
>
|
|
203
|
+
Cancel
|
|
204
|
+
</Button>
|
|
205
|
+
</div>
|
|
206
|
+
) : (
|
|
207
|
+
<Button
|
|
208
|
+
aria-label="Delete audit receipt"
|
|
209
|
+
onClick={() => setPendingDelete(receipt.auditId)}
|
|
210
|
+
size="icon-sm"
|
|
211
|
+
type="button"
|
|
212
|
+
variant="ghost"
|
|
213
|
+
>
|
|
214
|
+
<Trash2Icon className="size-3" />
|
|
215
|
+
</Button>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
))}
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{nextCursor ? (
|
|
222
|
+
<Button
|
|
223
|
+
disabled={loading}
|
|
224
|
+
onClick={() => void loadPage(nextCursor)}
|
|
225
|
+
size="sm"
|
|
226
|
+
type="button"
|
|
227
|
+
variant="outline"
|
|
228
|
+
>
|
|
229
|
+
{loading ? <Loader2Icon className="size-3 animate-spin" /> : null}
|
|
230
|
+
Load older
|
|
231
|
+
<ChevronRightIcon className="size-3" />
|
|
232
|
+
</Button>
|
|
233
|
+
) : null}
|
|
234
|
+
|
|
235
|
+
{selected ? (
|
|
236
|
+
<output className="block rounded-md border border-border/20 bg-muted/10 p-3 font-mono text-[10px]">
|
|
237
|
+
<span className="block">{selected.auditId}</span>
|
|
238
|
+
<span className="block text-muted-foreground/60">
|
|
239
|
+
{selected.effectivePolicy} · {selected.reasonCode} ·{" "}
|
|
240
|
+
{selected.byteSize} bytes
|
|
241
|
+
</span>
|
|
242
|
+
</output>
|
|
243
|
+
) : null}
|
|
244
|
+
|
|
245
|
+
{cleanup ? (
|
|
246
|
+
<output
|
|
247
|
+
aria-live="polite"
|
|
248
|
+
className="block text-[10px] text-muted-foreground/60"
|
|
249
|
+
>
|
|
250
|
+
Deleted {cleanup.deleted}; cleanup {cleanup.physicalCleanup};
|
|
251
|
+
checkpointed {cleanup.checkpointedFrames}; WAL remaining{" "}
|
|
252
|
+
{cleanup.remainingWalFrames}.
|
|
253
|
+
</output>
|
|
254
|
+
) : null}
|
|
255
|
+
|
|
256
|
+
<div className="flex flex-wrap items-center gap-3 border-border/15 border-t pt-3">
|
|
257
|
+
<label className="flex items-center gap-2 text-xs">
|
|
258
|
+
<input
|
|
259
|
+
checked={purgeConfirmed}
|
|
260
|
+
onChange={(event) => setPurgeConfirmed(event.target.checked)}
|
|
261
|
+
type="checkbox"
|
|
262
|
+
/>
|
|
263
|
+
Confirm purge of all local audit receipts
|
|
264
|
+
</label>
|
|
265
|
+
<Button
|
|
266
|
+
className="ml-auto"
|
|
267
|
+
disabled={!purgeConfirmed}
|
|
268
|
+
onClick={() => void purge()}
|
|
269
|
+
size="sm"
|
|
270
|
+
type="button"
|
|
271
|
+
variant="destructive"
|
|
272
|
+
>
|
|
273
|
+
Purge audits
|
|
274
|
+
</Button>
|
|
275
|
+
</div>
|
|
276
|
+
</section>
|
|
277
|
+
);
|
|
278
|
+
}
|