@nexus-ai-fs/tui 0.9.18
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 +30 -0
- package/package.json +48 -0
- package/src/app.tsx +349 -0
- package/src/index.tsx +137 -0
- package/src/opentui-env.d.ts +61 -0
- package/src/panels/access/access-panel.tsx +597 -0
- package/src/panels/access/alert-list.tsx +77 -0
- package/src/panels/access/constraint-creator.tsx +128 -0
- package/src/panels/access/constraint-list.tsx +72 -0
- package/src/panels/access/credential-list.tsx +68 -0
- package/src/panels/access/delegation-chain-view.tsx +110 -0
- package/src/panels/access/delegation-completer.tsx +120 -0
- package/src/panels/access/delegation-creator.tsx +237 -0
- package/src/panels/access/delegation-list.tsx +74 -0
- package/src/panels/access/fraud-score-view.tsx +94 -0
- package/src/panels/access/manifest-creator.tsx +167 -0
- package/src/panels/access/manifest-list.tsx +105 -0
- package/src/panels/access/namespace-config-view.tsx +525 -0
- package/src/panels/access/permission-checker.tsx +231 -0
- package/src/panels/agents/agent-status-view.tsx +196 -0
- package/src/panels/agents/agents-panel.tsx +493 -0
- package/src/panels/agents/delegation-list.tsx +154 -0
- package/src/panels/agents/inbox-view.tsx +96 -0
- package/src/panels/agents/trajectories-tab.tsx +40 -0
- package/src/panels/api-console/api-console-panel.tsx +189 -0
- package/src/panels/api-console/codegen-viewer.tsx +36 -0
- package/src/panels/api-console/codegen.ts +112 -0
- package/src/panels/api-console/endpoint-list.tsx +57 -0
- package/src/panels/api-console/request-builder.tsx +69 -0
- package/src/panels/api-console/response-viewer.tsx +54 -0
- package/src/panels/connectors/available-tab.tsx +357 -0
- package/src/panels/connectors/connector-row.tsx +121 -0
- package/src/panels/connectors/connectors-panel.tsx +88 -0
- package/src/panels/connectors/error-parser.ts +116 -0
- package/src/panels/connectors/mounted-tab.tsx +179 -0
- package/src/panels/connectors/skills-tab.tsx +235 -0
- package/src/panels/connectors/template-generator.ts +211 -0
- package/src/panels/connectors/write-tab.tsx +514 -0
- package/src/panels/events/audit-tab.tsx +69 -0
- package/src/panels/events/audit-trail.tsx +75 -0
- package/src/panels/events/connector-detail.tsx +49 -0
- package/src/panels/events/connector-list.tsx +73 -0
- package/src/panels/events/connectors-tab.tsx +92 -0
- package/src/panels/events/event-replay.tsx +80 -0
- package/src/panels/events/events-panel.tsx +414 -0
- package/src/panels/events/events-tab.tsx +212 -0
- package/src/panels/events/lock-list.tsx +54 -0
- package/src/panels/events/locks-tab.tsx +103 -0
- package/src/panels/events/mcl-replay.tsx +77 -0
- package/src/panels/events/mcl-tab.tsx +83 -0
- package/src/panels/events/operations-tab-wrapper.tsx +62 -0
- package/src/panels/events/operations-tab.tsx +41 -0
- package/src/panels/events/replay-tab.tsx +76 -0
- package/src/panels/events/secrets-audit.tsx +64 -0
- package/src/panels/events/secrets-tab.tsx +75 -0
- package/src/panels/events/subscription-list.tsx +54 -0
- package/src/panels/events/subscriptions-tab.tsx +82 -0
- package/src/panels/files/file-aspects.tsx +93 -0
- package/src/panels/files/file-editor.tsx +160 -0
- package/src/panels/files/file-explorer-keybindings.ts +468 -0
- package/src/panels/files/file-explorer-panel.tsx +545 -0
- package/src/panels/files/file-lineage.tsx +163 -0
- package/src/panels/files/file-list-item.tsx +28 -0
- package/src/panels/files/file-metadata.tsx +62 -0
- package/src/panels/files/file-preview.tsx +108 -0
- package/src/panels/files/file-schema.tsx +89 -0
- package/src/panels/files/file-tree-node.tsx +44 -0
- package/src/panels/files/file-tree.tsx +169 -0
- package/src/panels/files/share-links-tab.tsx +33 -0
- package/src/panels/files/uploads-tab.tsx +45 -0
- package/src/panels/payments/approval-list.tsx +83 -0
- package/src/panels/payments/balance-card.tsx +43 -0
- package/src/panels/payments/budget-card.tsx +70 -0
- package/src/panels/payments/payments-panel.tsx +451 -0
- package/src/panels/payments/policy-list.tsx +64 -0
- package/src/panels/payments/reservation-list.tsx +78 -0
- package/src/panels/payments/transaction-list.tsx +103 -0
- package/src/panels/payments/transfer-form.tsx +109 -0
- package/src/panels/search/column-search.tsx +79 -0
- package/src/panels/search/knowledge-view.tsx +100 -0
- package/src/panels/search/memory-list.tsx +197 -0
- package/src/panels/search/playbook-list.tsx +77 -0
- package/src/panels/search/rlm-answer-view.tsx +105 -0
- package/src/panels/search/search-panel.tsx +405 -0
- package/src/panels/search/search-results.tsx +116 -0
- package/src/panels/stack/stack-panel.tsx +474 -0
- package/src/panels/versions/conflicts-tab.tsx +59 -0
- package/src/panels/versions/entry-detail.tsx +89 -0
- package/src/panels/versions/transaction-actions.tsx +34 -0
- package/src/panels/versions/transaction-list.tsx +90 -0
- package/src/panels/versions/versions-panel.tsx +276 -0
- package/src/panels/workflows/execution-list.tsx +102 -0
- package/src/panels/workflows/scheduler-view.tsx +135 -0
- package/src/panels/workflows/workflow-list.tsx +88 -0
- package/src/panels/workflows/workflows-panel.tsx +295 -0
- package/src/panels/zones/brick-detail.tsx +136 -0
- package/src/panels/zones/brick-list.tsx +56 -0
- package/src/panels/zones/cache-tab.tsx +118 -0
- package/src/panels/zones/drift-view.tsx +97 -0
- package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
- package/src/panels/zones/memories-tab.tsx +37 -0
- package/src/panels/zones/reindex-status.tsx +84 -0
- package/src/panels/zones/workspaces-tab.tsx +37 -0
- package/src/panels/zones/zone-list.tsx +73 -0
- package/src/panels/zones/zones-panel.tsx +559 -0
- package/src/services/command-runner.ts +303 -0
- package/src/shared/accessibility-announcements.ts +44 -0
- package/src/shared/action-registry.ts +466 -0
- package/src/shared/brick-states.ts +91 -0
- package/src/shared/command-palette.ts +35 -0
- package/src/shared/components/announcement-bar.tsx +30 -0
- package/src/shared/components/app-confirm-dialog.tsx +29 -0
- package/src/shared/components/breadcrumb.tsx +21 -0
- package/src/shared/components/brick-gate.tsx +60 -0
- package/src/shared/components/command-output.tsx +95 -0
- package/src/shared/components/command-palette.tsx +97 -0
- package/src/shared/components/confirm-dialog.tsx +61 -0
- package/src/shared/components/diff-viewer.tsx +219 -0
- package/src/shared/components/empty-state.tsx +36 -0
- package/src/shared/components/error-bar.tsx +60 -0
- package/src/shared/components/error-boundary.tsx +53 -0
- package/src/shared/components/help-overlay.tsx +99 -0
- package/src/shared/components/identity-switcher.tsx +168 -0
- package/src/shared/components/loading-indicator.tsx +40 -0
- package/src/shared/components/pagination-bar.tsx +68 -0
- package/src/shared/components/pre-connection-screen.tsx +398 -0
- package/src/shared/components/scroll-indicator.tsx +46 -0
- package/src/shared/components/side-nav-utils.ts +68 -0
- package/src/shared/components/side-nav.tsx +287 -0
- package/src/shared/components/spinner.tsx +26 -0
- package/src/shared/components/status-bar.tsx +117 -0
- package/src/shared/components/styled-text.tsx +72 -0
- package/src/shared/components/sub-tab-bar-utils.ts +100 -0
- package/src/shared/components/sub-tab-bar.tsx +40 -0
- package/src/shared/components/tab-bar-utils.ts +36 -0
- package/src/shared/components/tab-bar.tsx +50 -0
- package/src/shared/components/text-input.tsx +73 -0
- package/src/shared/components/tooltip.tsx +53 -0
- package/src/shared/components/virtual-list.tsx +93 -0
- package/src/shared/components/welcome-screen.tsx +111 -0
- package/src/shared/hooks/use-api.ts +10 -0
- package/src/shared/hooks/use-brick-available.ts +42 -0
- package/src/shared/hooks/use-confirm.ts +66 -0
- package/src/shared/hooks/use-connection-state.ts +67 -0
- package/src/shared/hooks/use-copy.ts +31 -0
- package/src/shared/hooks/use-fresh-server.ts +62 -0
- package/src/shared/hooks/use-keyboard.ts +58 -0
- package/src/shared/hooks/use-list-navigation.ts +106 -0
- package/src/shared/hooks/use-swr.ts +117 -0
- package/src/shared/hooks/use-tab-fallback.ts +32 -0
- package/src/shared/hooks/use-text-input.ts +113 -0
- package/src/shared/hooks/use-visible-tabs.ts +61 -0
- package/src/shared/lib/circular-buffer.ts +82 -0
- package/src/shared/lib/clipboard.ts +14 -0
- package/src/shared/nav-items.ts +73 -0
- package/src/shared/navigation.ts +110 -0
- package/src/shared/status-breadcrumb.ts +74 -0
- package/src/shared/syntax-style.ts +3 -0
- package/src/shared/tab-visibility.ts +15 -0
- package/src/shared/text-style.ts +23 -0
- package/src/shared/theme.ts +179 -0
- package/src/shared/utils/format-size.ts +20 -0
- package/src/shared/utils/format-text.ts +10 -0
- package/src/shared/utils/format-time.ts +72 -0
- package/src/shared/utils/lru-cache.ts +75 -0
- package/src/stores/access-store-types.ts +154 -0
- package/src/stores/access-store.ts +674 -0
- package/src/stores/agents-store.ts +404 -0
- package/src/stores/announcement-store.ts +46 -0
- package/src/stores/api-console-store.ts +476 -0
- package/src/stores/connectors-store.ts +434 -0
- package/src/stores/create-api-action.ts +140 -0
- package/src/stores/delegation-store.ts +300 -0
- package/src/stores/error-store.ts +102 -0
- package/src/stores/events-store.ts +163 -0
- package/src/stores/files-store.ts +630 -0
- package/src/stores/first-run-store.ts +34 -0
- package/src/stores/global-store.ts +255 -0
- package/src/stores/infra-store.ts +461 -0
- package/src/stores/knowledge-store.ts +358 -0
- package/src/stores/lineage-store.ts +126 -0
- package/src/stores/mcp-store.ts +147 -0
- package/src/stores/payments-store.ts +545 -0
- package/src/stores/search-store-types.ts +155 -0
- package/src/stores/search-store.ts +656 -0
- package/src/stores/share-link-store.ts +151 -0
- package/src/stores/stack-store.ts +352 -0
- package/src/stores/ui-store.ts +161 -0
- package/src/stores/upload-store.ts +131 -0
- package/src/stores/versions-store.ts +355 -0
- package/src/stores/workflows-store.ts +402 -0
- package/src/stores/workspace-store.ts +185 -0
- package/src/stores/zones-store.ts +378 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locks tab: distributed lock list with acquire/release/extend actions.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from events-panel.tsx (Issue 2A).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useEffect } from "react";
|
|
8
|
+
import { useInfraStore } from "../../stores/infra-store.js";
|
|
9
|
+
import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
|
|
10
|
+
import { listNavigationBindings } from "../../shared/hooks/use-list-navigation.js";
|
|
11
|
+
import { useTextInput } from "../../shared/hooks/use-text-input.js";
|
|
12
|
+
import { useConfirmStore } from "../../shared/hooks/use-confirm.js";
|
|
13
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
14
|
+
import { LockList } from "./lock-list.js";
|
|
15
|
+
|
|
16
|
+
interface LocksTabProps {
|
|
17
|
+
readonly tabBindings: Readonly<Record<string, () => void>>;
|
|
18
|
+
readonly overlayActive: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function LocksTab({ tabBindings, overlayActive }: LocksTabProps): React.ReactNode {
|
|
22
|
+
const client = useApi();
|
|
23
|
+
const confirm = useConfirmStore((s) => s.confirm);
|
|
24
|
+
|
|
25
|
+
const locks = useInfraStore((s) => s.locks);
|
|
26
|
+
const locksLoading = useInfraStore((s) => s.locksLoading);
|
|
27
|
+
const selectedLockIndex = useInfraStore((s) => s.selectedLockIndex);
|
|
28
|
+
const setSelectedLockIndex = useInfraStore((s) => s.setSelectedLockIndex);
|
|
29
|
+
const fetchLocks = useInfraStore((s) => s.fetchLocks);
|
|
30
|
+
const acquireLock = useInfraStore((s) => s.acquireLock);
|
|
31
|
+
const releaseLock = useInfraStore((s) => s.releaseLock);
|
|
32
|
+
const extendLock = useInfraStore((s) => s.extendLock);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (client) fetchLocks(client);
|
|
36
|
+
}, [client, fetchLocks]);
|
|
37
|
+
|
|
38
|
+
const acquireInput = useTextInput({
|
|
39
|
+
onSubmit: (val) => {
|
|
40
|
+
if (val && client) acquireLock(val, "mutex", 60, client);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const listNav = listNavigationBindings({
|
|
45
|
+
getIndex: () => selectedLockIndex,
|
|
46
|
+
setIndex: (i) => setSelectedLockIndex(i),
|
|
47
|
+
getLength: () => locks.length,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
useKeyboard(
|
|
51
|
+
overlayActive
|
|
52
|
+
? {}
|
|
53
|
+
: acquireInput.active
|
|
54
|
+
? acquireInput.inputBindings
|
|
55
|
+
: {
|
|
56
|
+
...listNav,
|
|
57
|
+
...tabBindings,
|
|
58
|
+
n: () => acquireInput.activate(""),
|
|
59
|
+
d: async () => {
|
|
60
|
+
if (client) {
|
|
61
|
+
const lock = locks[selectedLockIndex];
|
|
62
|
+
if (lock) {
|
|
63
|
+
const ok = await confirm("Release lock?", "Release this lock. Other waiters may acquire it.");
|
|
64
|
+
if (!ok) return;
|
|
65
|
+
releaseLock(lock.resource, lock.lock_id, client);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
e: () => {
|
|
70
|
+
if (client) {
|
|
71
|
+
const lock = locks[selectedLockIndex];
|
|
72
|
+
if (lock) extendLock(lock.resource, lock.lock_id, 60, client);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
r: () => { if (client) fetchLocks(client); },
|
|
76
|
+
},
|
|
77
|
+
overlayActive ? undefined : acquireInput.active ? acquireInput.onUnhandled : undefined,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
82
|
+
{acquireInput.active && (
|
|
83
|
+
<box height={1} width="100%">
|
|
84
|
+
<text>{`Acquire lock path: ${acquireInput.buffer}\u2588`}</text>
|
|
85
|
+
</box>
|
|
86
|
+
)}
|
|
87
|
+
<box flexGrow={1} width="100%" borderStyle="single">
|
|
88
|
+
<LockList
|
|
89
|
+
locks={locks}
|
|
90
|
+
selectedIndex={selectedLockIndex}
|
|
91
|
+
loading={locksLoading}
|
|
92
|
+
/>
|
|
93
|
+
</box>
|
|
94
|
+
<box height={1} width="100%">
|
|
95
|
+
<text>
|
|
96
|
+
{acquireInput.active
|
|
97
|
+
? "Type path, Enter:acquire, Escape:cancel"
|
|
98
|
+
: "j/k:navigate n:acquire d:release e:extend r:refresh Tab:switch tab"}
|
|
99
|
+
</text>
|
|
100
|
+
</box>
|
|
101
|
+
</box>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCL (Metadata Change Log) replay sub-view.
|
|
3
|
+
* Shows structured change log entries from the operation log.
|
|
4
|
+
* Supports client-side filtering by entity URN and aspect name.
|
|
5
|
+
* Issue #2930.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useEffect } from "react";
|
|
9
|
+
import { useKnowledgeStore } from "../../stores/knowledge-store.js";
|
|
10
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
11
|
+
|
|
12
|
+
export interface MclReplayProps {
|
|
13
|
+
/** Optional URN filter string (substring match). */
|
|
14
|
+
readonly urnFilter?: string;
|
|
15
|
+
/** Optional aspect name filter string (substring match). */
|
|
16
|
+
readonly aspectFilter?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function MclReplay(props: MclReplayProps): React.ReactNode {
|
|
20
|
+
const client = useApi();
|
|
21
|
+
const entries = useKnowledgeStore((s) => s.replayEntries);
|
|
22
|
+
const loading = useKnowledgeStore((s) => s.replayLoading);
|
|
23
|
+
const hasMore = useKnowledgeStore((s) => s.replayHasMore);
|
|
24
|
+
const fetchReplay = useKnowledgeStore((s) => s.fetchReplay);
|
|
25
|
+
const clearReplay = useKnowledgeStore((s) => s.clearReplay);
|
|
26
|
+
const error = useKnowledgeStore((s) => s.error);
|
|
27
|
+
|
|
28
|
+
const urnFilter = props.urnFilter ?? "";
|
|
29
|
+
const aspectFilter = props.aspectFilter ?? "";
|
|
30
|
+
|
|
31
|
+
// Re-fetch from server when filters change (or on initial mount)
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (client) {
|
|
34
|
+
clearReplay();
|
|
35
|
+
void fetchReplay(client, 0, 200, urnFilter || undefined, aspectFilter || undefined);
|
|
36
|
+
}
|
|
37
|
+
}, [client, urnFilter, aspectFilter, fetchReplay, clearReplay]);
|
|
38
|
+
|
|
39
|
+
// Apply filters client-side
|
|
40
|
+
const filtered = entries.filter((e) => {
|
|
41
|
+
if (urnFilter && !e.entityUrn.includes(urnFilter)) return false;
|
|
42
|
+
if (aspectFilter && !e.aspectName.includes(aspectFilter)) return false;
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (loading && entries.length === 0) {
|
|
47
|
+
return <text>Loading MCL entries...</text>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (error) {
|
|
51
|
+
return <text>{`Error: ${error}`}</text>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (entries.length === 0) {
|
|
55
|
+
return <text>No MCL records found</text>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<box flexDirection="column" height="100%" width="100%">
|
|
60
|
+
{/* Filter bar */}
|
|
61
|
+
<box height={1} width="100%">
|
|
62
|
+
<text>
|
|
63
|
+
{` Filters: URN=${urnFilter || "*"} Aspect=${aspectFilter || "*"} (${filtered.length}/${entries.length} shown)`}
|
|
64
|
+
</text>
|
|
65
|
+
</box>
|
|
66
|
+
<text>
|
|
67
|
+
{" Seq Change Aspect Entity URN"}
|
|
68
|
+
</text>
|
|
69
|
+
{filtered.slice(0, 100).map((e) => (
|
|
70
|
+
<text key={e.sequenceNumber}>
|
|
71
|
+
{` ${String(e.sequenceNumber).padStart(5)} ${e.changeType.padEnd(12)} ${e.aspectName.padEnd(20)} ${e.entityUrn}`}
|
|
72
|
+
</text>
|
|
73
|
+
))}
|
|
74
|
+
{hasMore && <text>{" ... more records available"}</text>}
|
|
75
|
+
</box>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCL (MetaCatalog Language) replay tab with URN/aspect filtering.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from events-panel.tsx (Issue 2A).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useEffect } from "react";
|
|
8
|
+
import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
|
|
9
|
+
import { useTextInput } from "../../shared/hooks/use-text-input.js";
|
|
10
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
11
|
+
import { useKnowledgeStore } from "../../stores/knowledge-store.js";
|
|
12
|
+
import { MclReplay } from "./mcl-replay.js";
|
|
13
|
+
|
|
14
|
+
interface MclTabProps {
|
|
15
|
+
readonly tabBindings: Readonly<Record<string, () => void>>;
|
|
16
|
+
readonly overlayActive: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function MclTab({ tabBindings, overlayActive }: MclTabProps): React.ReactNode {
|
|
20
|
+
const client = useApi();
|
|
21
|
+
const [urnFilter, setUrnFilter] = useState("");
|
|
22
|
+
const [aspectFilter, setAspectFilter] = useState("");
|
|
23
|
+
|
|
24
|
+
const fetchReplay = useKnowledgeStore((s) => s.fetchReplay);
|
|
25
|
+
const clearReplay = useKnowledgeStore((s) => s.clearReplay);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (client) void fetchReplay(client, 0, 50);
|
|
29
|
+
}, [client, fetchReplay]);
|
|
30
|
+
|
|
31
|
+
const urnInput = useTextInput({
|
|
32
|
+
onSubmit: (val) => setUrnFilter(val),
|
|
33
|
+
});
|
|
34
|
+
const aspectInput = useTextInput({
|
|
35
|
+
onSubmit: (val) => setAspectFilter(val),
|
|
36
|
+
});
|
|
37
|
+
const anyInputActive = urnInput.active || aspectInput.active;
|
|
38
|
+
|
|
39
|
+
useKeyboard(
|
|
40
|
+
overlayActive
|
|
41
|
+
? {}
|
|
42
|
+
: anyInputActive
|
|
43
|
+
? (urnInput.active ? urnInput.inputBindings : aspectInput.inputBindings)
|
|
44
|
+
: {
|
|
45
|
+
...tabBindings,
|
|
46
|
+
u: () => urnInput.activate(urnFilter),
|
|
47
|
+
n: () => aspectInput.activate(aspectFilter),
|
|
48
|
+
r: () => {
|
|
49
|
+
if (client) {
|
|
50
|
+
clearReplay();
|
|
51
|
+
void fetchReplay(client, 0, 50);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
overlayActive ? undefined : anyInputActive
|
|
56
|
+
? (urnInput.active ? urnInput.onUnhandled : aspectInput.onUnhandled)
|
|
57
|
+
: undefined,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
62
|
+
<box height={1} width="100%">
|
|
63
|
+
<text>
|
|
64
|
+
{urnInput.active
|
|
65
|
+
? `Filter URN: ${urnInput.buffer}\u2588`
|
|
66
|
+
: aspectInput.active
|
|
67
|
+
? `Filter aspect: ${aspectInput.buffer}\u2588`
|
|
68
|
+
: `Filter: URN=${urnFilter || "*"} aspect=${aspectFilter || "*"}`}
|
|
69
|
+
</text>
|
|
70
|
+
</box>
|
|
71
|
+
<box flexGrow={1} width="100%" borderStyle="single">
|
|
72
|
+
<MclReplay urnFilter={urnFilter} aspectFilter={aspectFilter} />
|
|
73
|
+
</box>
|
|
74
|
+
<box height={1} width="100%">
|
|
75
|
+
<text>
|
|
76
|
+
{anyInputActive
|
|
77
|
+
? "Type value, Enter:apply, Escape:cancel"
|
|
78
|
+
: "u:filter URN n:filter aspect r:refresh Tab:switch tab"}
|
|
79
|
+
</text>
|
|
80
|
+
</box>
|
|
81
|
+
</box>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operations tab wrapper: adds keybindings around OperationsTab render.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from events-panel.tsx (Issue 2A).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useEffect } from "react";
|
|
8
|
+
import { useInfraStore } from "../../stores/infra-store.js";
|
|
9
|
+
import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
|
|
10
|
+
import { listNavigationBindings } from "../../shared/hooks/use-list-navigation.js";
|
|
11
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
12
|
+
import { OperationsTab } from "./operations-tab.js";
|
|
13
|
+
|
|
14
|
+
interface OperationsTabWrapperProps {
|
|
15
|
+
readonly tabBindings: Readonly<Record<string, () => void>>;
|
|
16
|
+
readonly overlayActive: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function OperationsTabWrapper({ tabBindings, overlayActive }: OperationsTabWrapperProps): React.ReactNode {
|
|
20
|
+
const client = useApi();
|
|
21
|
+
|
|
22
|
+
const operations = useInfraStore((s) => s.operations);
|
|
23
|
+
const operationsLoading = useInfraStore((s) => s.operationsLoading);
|
|
24
|
+
const selectedOperationIndex = useInfraStore((s) => s.selectedOperationIndex);
|
|
25
|
+
const setSelectedOperationIndex = useInfraStore((s) => s.setSelectedOperationIndex);
|
|
26
|
+
const fetchOperations = useInfraStore((s) => s.fetchOperations);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (client) fetchOperations(client);
|
|
30
|
+
}, [client, fetchOperations]);
|
|
31
|
+
|
|
32
|
+
const listNav = listNavigationBindings({
|
|
33
|
+
getIndex: () => selectedOperationIndex,
|
|
34
|
+
setIndex: (i) => setSelectedOperationIndex(i),
|
|
35
|
+
getLength: () => operations.length,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
useKeyboard(
|
|
39
|
+
overlayActive
|
|
40
|
+
? {}
|
|
41
|
+
: {
|
|
42
|
+
...listNav,
|
|
43
|
+
...tabBindings,
|
|
44
|
+
r: () => { if (client) fetchOperations(client); },
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
50
|
+
<box flexGrow={1} width="100%" borderStyle="single">
|
|
51
|
+
<OperationsTab
|
|
52
|
+
operations={operations}
|
|
53
|
+
selectedIndex={selectedOperationIndex}
|
|
54
|
+
loading={operationsLoading}
|
|
55
|
+
/>
|
|
56
|
+
</box>
|
|
57
|
+
<box height={1} width="100%">
|
|
58
|
+
<text>{"j/k:navigate r:refresh Tab:switch tab"}</text>
|
|
59
|
+
</box>
|
|
60
|
+
</box>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operations tab: simple list of operations.
|
|
3
|
+
*
|
|
4
|
+
* Displays operation_id, agent_id, type, status, and started_at.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import type { OperationItem } from "../../stores/infra-store.js";
|
|
9
|
+
|
|
10
|
+
interface OperationsTabProps {
|
|
11
|
+
readonly operations: readonly OperationItem[];
|
|
12
|
+
readonly selectedIndex: number;
|
|
13
|
+
readonly loading: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function OperationsTab({ operations, selectedIndex, loading }: OperationsTabProps): React.ReactNode {
|
|
17
|
+
if (loading) return <text>Loading operations...</text>;
|
|
18
|
+
if (operations.length === 0) return <text>No operations found.</text>;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<scrollbox height="100%" width="100%">
|
|
22
|
+
{/* Header */}
|
|
23
|
+
<box height={1} width="100%">
|
|
24
|
+
<text>{" OPERATION_ID AGENT_ID TYPE STATUS STARTED"}</text>
|
|
25
|
+
</box>
|
|
26
|
+
|
|
27
|
+
{operations.map((op, i) => {
|
|
28
|
+
const isSelected = i === selectedIndex;
|
|
29
|
+
const prefix = isSelected ? "> " : " ";
|
|
30
|
+
const opShort = op.operation_id.slice(0, 16) + "...";
|
|
31
|
+
const agentShort = op.agent_id ? op.agent_id.slice(0, 14) : "n/a";
|
|
32
|
+
const started = op.started_at ? op.started_at.slice(0, 19) : "n/a";
|
|
33
|
+
return (
|
|
34
|
+
<box key={op.operation_id} height={1} width="100%">
|
|
35
|
+
<text>{`${prefix}${opShort} ${agentShort.padEnd(16)} ${op.type.padEnd(10)} ${op.status.padEnd(10)} ${started}`}</text>
|
|
36
|
+
</box>
|
|
37
|
+
);
|
|
38
|
+
})}
|
|
39
|
+
</scrollbox>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event replay tab with event type filtering.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from events-panel.tsx (Issue 2A).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useEffect } from "react";
|
|
8
|
+
import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
|
|
9
|
+
import { useTextInput } from "../../shared/hooks/use-text-input.js";
|
|
10
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
11
|
+
import { useKnowledgeStore } from "../../stores/knowledge-store.js";
|
|
12
|
+
import { EventReplay } from "./event-replay.js";
|
|
13
|
+
|
|
14
|
+
interface ReplayTabProps {
|
|
15
|
+
readonly tabBindings: Readonly<Record<string, () => void>>;
|
|
16
|
+
readonly overlayActive: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ReplayTab({ tabBindings, overlayActive }: ReplayTabProps): React.ReactNode {
|
|
20
|
+
const client = useApi();
|
|
21
|
+
const [typeFilter, setTypeFilter] = useState("");
|
|
22
|
+
|
|
23
|
+
const fetchEventReplay = useKnowledgeStore((s) => s.fetchEventReplay);
|
|
24
|
+
const clearEventReplay = useKnowledgeStore((s) => s.clearEventReplay);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (client) void fetchEventReplay({}, client);
|
|
28
|
+
}, [client, fetchEventReplay]);
|
|
29
|
+
|
|
30
|
+
const filterInput = useTextInput({
|
|
31
|
+
onSubmit: (val) => {
|
|
32
|
+
setTypeFilter(val);
|
|
33
|
+
if (client) void fetchEventReplay({ event_types: val || undefined }, client);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
useKeyboard(
|
|
38
|
+
overlayActive
|
|
39
|
+
? {}
|
|
40
|
+
: filterInput.active
|
|
41
|
+
? filterInput.inputBindings
|
|
42
|
+
: {
|
|
43
|
+
...tabBindings,
|
|
44
|
+
f: () => filterInput.activate(typeFilter),
|
|
45
|
+
r: () => {
|
|
46
|
+
if (client) {
|
|
47
|
+
clearEventReplay();
|
|
48
|
+
void fetchEventReplay({ event_types: typeFilter || undefined }, client);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
overlayActive ? undefined : filterInput.active ? filterInput.onUnhandled : undefined,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
57
|
+
<box height={1} width="100%">
|
|
58
|
+
<text>
|
|
59
|
+
{filterInput.active
|
|
60
|
+
? `Filter event type: ${filterInput.buffer}\u2588`
|
|
61
|
+
: `Filter: event_type=${typeFilter || "*"}`}
|
|
62
|
+
</text>
|
|
63
|
+
</box>
|
|
64
|
+
<box flexGrow={1} width="100%" borderStyle="single">
|
|
65
|
+
<EventReplay typeFilter={typeFilter} />
|
|
66
|
+
</box>
|
|
67
|
+
<box height={1} width="100%">
|
|
68
|
+
<text>
|
|
69
|
+
{filterInput.active
|
|
70
|
+
? "Type value, Enter:apply, Escape:cancel"
|
|
71
|
+
: "f:filter event type r:refresh Tab:switch tab"}
|
|
72
|
+
</text>
|
|
73
|
+
</box>
|
|
74
|
+
</box>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets audit log view: shows audit trail of secret access and modifications.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { SecretAuditEntry } from "../../stores/infra-store.js";
|
|
7
|
+
import { Spinner } from "../../shared/components/spinner.js";
|
|
8
|
+
import { textStyle } from "../../shared/text-style.js";
|
|
9
|
+
import { formatTimestamp } from "../../shared/utils/format-time.js";
|
|
10
|
+
|
|
11
|
+
export function SecretsAudit({
|
|
12
|
+
entries,
|
|
13
|
+
loading,
|
|
14
|
+
filter,
|
|
15
|
+
}: {
|
|
16
|
+
readonly entries: readonly SecretAuditEntry[];
|
|
17
|
+
readonly loading: boolean;
|
|
18
|
+
readonly filter?: string;
|
|
19
|
+
}): React.ReactNode {
|
|
20
|
+
if (loading) {
|
|
21
|
+
return <Spinner label="Loading secrets audit..." />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (entries.length === 0) {
|
|
25
|
+
return <text>No audit entries</text>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const needle = (filter ?? "").toLowerCase();
|
|
29
|
+
const filtered = needle
|
|
30
|
+
? entries.filter((e) => {
|
|
31
|
+
const haystack = `${e.event_type} ${e.actor_id} ${e.details ?? ""}`.toLowerCase();
|
|
32
|
+
return haystack.includes(needle);
|
|
33
|
+
})
|
|
34
|
+
: entries;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<scrollbox height="100%" width="100%">
|
|
38
|
+
{/* Count indicator */}
|
|
39
|
+
{needle ? (
|
|
40
|
+
<box height={1} width="100%">
|
|
41
|
+
<text style={textStyle({ dim: true })}>{`${filtered.length} of ${entries.length} entries`}</text>
|
|
42
|
+
</box>
|
|
43
|
+
) : null}
|
|
44
|
+
|
|
45
|
+
{/* Header */}
|
|
46
|
+
<box height={1} width="100%">
|
|
47
|
+
<text>{" Event Type Actor Zone Time"}</text>
|
|
48
|
+
</box>
|
|
49
|
+
|
|
50
|
+
{filtered.map((entry) => {
|
|
51
|
+
const eventType = entry.event_type.padEnd(14).slice(0, 14);
|
|
52
|
+
const actor = entry.actor_id.padEnd(20).slice(0, 20);
|
|
53
|
+
const zone = entry.zone_id.padEnd(20).slice(0, 20);
|
|
54
|
+
const time = formatTimestamp(entry.created_at);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<box key={entry.id} height={1} width="100%">
|
|
58
|
+
<text>{` ${eventType} ${actor} ${zone} ${time}`}</text>
|
|
59
|
+
</box>
|
|
60
|
+
);
|
|
61
|
+
})}
|
|
62
|
+
</scrollbox>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets audit tab: secrets access log with filtering.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from events-panel.tsx (Issue 2A).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useEffect } from "react";
|
|
8
|
+
import { useInfraStore } from "../../stores/infra-store.js";
|
|
9
|
+
import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
|
|
10
|
+
import { useTextInput } from "../../shared/hooks/use-text-input.js";
|
|
11
|
+
import { useApi } from "../../shared/hooks/use-api.js";
|
|
12
|
+
import { SecretsAudit } from "./secrets-audit.js";
|
|
13
|
+
|
|
14
|
+
interface SecretsTabProps {
|
|
15
|
+
readonly tabBindings: Readonly<Record<string, () => void>>;
|
|
16
|
+
readonly overlayActive: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function SecretsTab({ tabBindings, overlayActive }: SecretsTabProps): React.ReactNode {
|
|
20
|
+
const client = useApi();
|
|
21
|
+
const [secretsFilter, setSecretsFilter] = useState("");
|
|
22
|
+
|
|
23
|
+
const secretAuditEntries = useInfraStore((s) => s.secretAuditEntries);
|
|
24
|
+
const secretsLoading = useInfraStore((s) => s.secretsLoading);
|
|
25
|
+
const fetchSecretAudit = useInfraStore((s) => s.fetchSecretAudit);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (client) fetchSecretAudit(client);
|
|
29
|
+
}, [client, fetchSecretAudit]);
|
|
30
|
+
|
|
31
|
+
const filterInput = useTextInput({
|
|
32
|
+
onSubmit: (val) => setSecretsFilter(val),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
useKeyboard(
|
|
36
|
+
overlayActive
|
|
37
|
+
? {}
|
|
38
|
+
: filterInput.active
|
|
39
|
+
? filterInput.inputBindings
|
|
40
|
+
: {
|
|
41
|
+
...tabBindings,
|
|
42
|
+
"/": () => filterInput.activate(secretsFilter),
|
|
43
|
+
r: () => { if (client) fetchSecretAudit(client); },
|
|
44
|
+
},
|
|
45
|
+
overlayActive ? undefined : filterInput.active ? filterInput.onUnhandled : undefined,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<box height="100%" width="100%" flexDirection="column">
|
|
50
|
+
<box height={1} width="100%">
|
|
51
|
+
<text>
|
|
52
|
+
{filterInput.active
|
|
53
|
+
? `Filter: ${filterInput.buffer}\u2588`
|
|
54
|
+
: secretsFilter
|
|
55
|
+
? `Filter: ${secretsFilter}`
|
|
56
|
+
: ""}
|
|
57
|
+
</text>
|
|
58
|
+
</box>
|
|
59
|
+
<box flexGrow={1} width="100%" borderStyle="single">
|
|
60
|
+
<SecretsAudit
|
|
61
|
+
entries={secretAuditEntries}
|
|
62
|
+
loading={secretsLoading}
|
|
63
|
+
filter={secretsFilter}
|
|
64
|
+
/>
|
|
65
|
+
</box>
|
|
66
|
+
<box height={1} width="100%">
|
|
67
|
+
<text>
|
|
68
|
+
{filterInput.active
|
|
69
|
+
? "Type filter, Enter:apply, Escape:cancel"
|
|
70
|
+
: "/:filter r:refresh Tab:switch tab"}
|
|
71
|
+
</text>
|
|
72
|
+
</box>
|
|
73
|
+
</box>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription list view: shows event subscriptions with status and trigger info.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { Subscription } from "../../stores/infra-store.js";
|
|
7
|
+
import { Spinner } from "../../shared/components/spinner.js";
|
|
8
|
+
|
|
9
|
+
const STATUS_BADGE: Record<string, string> = {
|
|
10
|
+
active: "[ON]",
|
|
11
|
+
paused: "[||]",
|
|
12
|
+
failed: "[!!]",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function SubscriptionList({
|
|
16
|
+
subscriptions,
|
|
17
|
+
selectedIndex,
|
|
18
|
+
loading,
|
|
19
|
+
}: {
|
|
20
|
+
readonly subscriptions: readonly Subscription[];
|
|
21
|
+
readonly selectedIndex: number;
|
|
22
|
+
readonly loading: boolean;
|
|
23
|
+
}): React.ReactNode {
|
|
24
|
+
if (loading) {
|
|
25
|
+
return <Spinner label="Loading subscriptions..." />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (subscriptions.length === 0) {
|
|
29
|
+
return <text>No subscriptions configured</text>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<scrollbox height="100%" width="100%">
|
|
34
|
+
{/* Header */}
|
|
35
|
+
<box height={1} width="100%">
|
|
36
|
+
<text>{" Status Event Type Endpoint Triggers"}</text>
|
|
37
|
+
</box>
|
|
38
|
+
|
|
39
|
+
{subscriptions.map((sub, i) => {
|
|
40
|
+
const prefix = i === selectedIndex ? "> " : " ";
|
|
41
|
+
const badge = STATUS_BADGE[sub.status] ?? "[??]";
|
|
42
|
+
const eventType = sub.event_type.padEnd(20).slice(0, 20);
|
|
43
|
+
const endpoint = sub.endpoint.padEnd(25).slice(0, 25);
|
|
44
|
+
const triggers = String(sub.trigger_count).padStart(8);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<box key={sub.subscription_id} height={1} width="100%">
|
|
48
|
+
<text>{`${prefix}${badge} ${eventType} ${endpoint} ${triggers}`}</text>
|
|
49
|
+
</box>
|
|
50
|
+
);
|
|
51
|
+
})}
|
|
52
|
+
</scrollbox>
|
|
53
|
+
);
|
|
54
|
+
}
|