@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.
Files changed (193) hide show
  1. package/README.md +30 -0
  2. package/package.json +48 -0
  3. package/src/app.tsx +349 -0
  4. package/src/index.tsx +137 -0
  5. package/src/opentui-env.d.ts +61 -0
  6. package/src/panels/access/access-panel.tsx +597 -0
  7. package/src/panels/access/alert-list.tsx +77 -0
  8. package/src/panels/access/constraint-creator.tsx +128 -0
  9. package/src/panels/access/constraint-list.tsx +72 -0
  10. package/src/panels/access/credential-list.tsx +68 -0
  11. package/src/panels/access/delegation-chain-view.tsx +110 -0
  12. package/src/panels/access/delegation-completer.tsx +120 -0
  13. package/src/panels/access/delegation-creator.tsx +237 -0
  14. package/src/panels/access/delegation-list.tsx +74 -0
  15. package/src/panels/access/fraud-score-view.tsx +94 -0
  16. package/src/panels/access/manifest-creator.tsx +167 -0
  17. package/src/panels/access/manifest-list.tsx +105 -0
  18. package/src/panels/access/namespace-config-view.tsx +525 -0
  19. package/src/panels/access/permission-checker.tsx +231 -0
  20. package/src/panels/agents/agent-status-view.tsx +196 -0
  21. package/src/panels/agents/agents-panel.tsx +493 -0
  22. package/src/panels/agents/delegation-list.tsx +154 -0
  23. package/src/panels/agents/inbox-view.tsx +96 -0
  24. package/src/panels/agents/trajectories-tab.tsx +40 -0
  25. package/src/panels/api-console/api-console-panel.tsx +189 -0
  26. package/src/panels/api-console/codegen-viewer.tsx +36 -0
  27. package/src/panels/api-console/codegen.ts +112 -0
  28. package/src/panels/api-console/endpoint-list.tsx +57 -0
  29. package/src/panels/api-console/request-builder.tsx +69 -0
  30. package/src/panels/api-console/response-viewer.tsx +54 -0
  31. package/src/panels/connectors/available-tab.tsx +357 -0
  32. package/src/panels/connectors/connector-row.tsx +121 -0
  33. package/src/panels/connectors/connectors-panel.tsx +88 -0
  34. package/src/panels/connectors/error-parser.ts +116 -0
  35. package/src/panels/connectors/mounted-tab.tsx +179 -0
  36. package/src/panels/connectors/skills-tab.tsx +235 -0
  37. package/src/panels/connectors/template-generator.ts +211 -0
  38. package/src/panels/connectors/write-tab.tsx +514 -0
  39. package/src/panels/events/audit-tab.tsx +69 -0
  40. package/src/panels/events/audit-trail.tsx +75 -0
  41. package/src/panels/events/connector-detail.tsx +49 -0
  42. package/src/panels/events/connector-list.tsx +73 -0
  43. package/src/panels/events/connectors-tab.tsx +92 -0
  44. package/src/panels/events/event-replay.tsx +80 -0
  45. package/src/panels/events/events-panel.tsx +414 -0
  46. package/src/panels/events/events-tab.tsx +212 -0
  47. package/src/panels/events/lock-list.tsx +54 -0
  48. package/src/panels/events/locks-tab.tsx +103 -0
  49. package/src/panels/events/mcl-replay.tsx +77 -0
  50. package/src/panels/events/mcl-tab.tsx +83 -0
  51. package/src/panels/events/operations-tab-wrapper.tsx +62 -0
  52. package/src/panels/events/operations-tab.tsx +41 -0
  53. package/src/panels/events/replay-tab.tsx +76 -0
  54. package/src/panels/events/secrets-audit.tsx +64 -0
  55. package/src/panels/events/secrets-tab.tsx +75 -0
  56. package/src/panels/events/subscription-list.tsx +54 -0
  57. package/src/panels/events/subscriptions-tab.tsx +82 -0
  58. package/src/panels/files/file-aspects.tsx +93 -0
  59. package/src/panels/files/file-editor.tsx +160 -0
  60. package/src/panels/files/file-explorer-keybindings.ts +468 -0
  61. package/src/panels/files/file-explorer-panel.tsx +545 -0
  62. package/src/panels/files/file-lineage.tsx +163 -0
  63. package/src/panels/files/file-list-item.tsx +28 -0
  64. package/src/panels/files/file-metadata.tsx +62 -0
  65. package/src/panels/files/file-preview.tsx +108 -0
  66. package/src/panels/files/file-schema.tsx +89 -0
  67. package/src/panels/files/file-tree-node.tsx +44 -0
  68. package/src/panels/files/file-tree.tsx +169 -0
  69. package/src/panels/files/share-links-tab.tsx +33 -0
  70. package/src/panels/files/uploads-tab.tsx +45 -0
  71. package/src/panels/payments/approval-list.tsx +83 -0
  72. package/src/panels/payments/balance-card.tsx +43 -0
  73. package/src/panels/payments/budget-card.tsx +70 -0
  74. package/src/panels/payments/payments-panel.tsx +451 -0
  75. package/src/panels/payments/policy-list.tsx +64 -0
  76. package/src/panels/payments/reservation-list.tsx +78 -0
  77. package/src/panels/payments/transaction-list.tsx +103 -0
  78. package/src/panels/payments/transfer-form.tsx +109 -0
  79. package/src/panels/search/column-search.tsx +79 -0
  80. package/src/panels/search/knowledge-view.tsx +100 -0
  81. package/src/panels/search/memory-list.tsx +197 -0
  82. package/src/panels/search/playbook-list.tsx +77 -0
  83. package/src/panels/search/rlm-answer-view.tsx +105 -0
  84. package/src/panels/search/search-panel.tsx +405 -0
  85. package/src/panels/search/search-results.tsx +116 -0
  86. package/src/panels/stack/stack-panel.tsx +474 -0
  87. package/src/panels/versions/conflicts-tab.tsx +59 -0
  88. package/src/panels/versions/entry-detail.tsx +89 -0
  89. package/src/panels/versions/transaction-actions.tsx +34 -0
  90. package/src/panels/versions/transaction-list.tsx +90 -0
  91. package/src/panels/versions/versions-panel.tsx +276 -0
  92. package/src/panels/workflows/execution-list.tsx +102 -0
  93. package/src/panels/workflows/scheduler-view.tsx +135 -0
  94. package/src/panels/workflows/workflow-list.tsx +88 -0
  95. package/src/panels/workflows/workflows-panel.tsx +295 -0
  96. package/src/panels/zones/brick-detail.tsx +136 -0
  97. package/src/panels/zones/brick-list.tsx +56 -0
  98. package/src/panels/zones/cache-tab.tsx +118 -0
  99. package/src/panels/zones/drift-view.tsx +97 -0
  100. package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
  101. package/src/panels/zones/memories-tab.tsx +37 -0
  102. package/src/panels/zones/reindex-status.tsx +84 -0
  103. package/src/panels/zones/workspaces-tab.tsx +37 -0
  104. package/src/panels/zones/zone-list.tsx +73 -0
  105. package/src/panels/zones/zones-panel.tsx +559 -0
  106. package/src/services/command-runner.ts +303 -0
  107. package/src/shared/accessibility-announcements.ts +44 -0
  108. package/src/shared/action-registry.ts +466 -0
  109. package/src/shared/brick-states.ts +91 -0
  110. package/src/shared/command-palette.ts +35 -0
  111. package/src/shared/components/announcement-bar.tsx +30 -0
  112. package/src/shared/components/app-confirm-dialog.tsx +29 -0
  113. package/src/shared/components/breadcrumb.tsx +21 -0
  114. package/src/shared/components/brick-gate.tsx +60 -0
  115. package/src/shared/components/command-output.tsx +95 -0
  116. package/src/shared/components/command-palette.tsx +97 -0
  117. package/src/shared/components/confirm-dialog.tsx +61 -0
  118. package/src/shared/components/diff-viewer.tsx +219 -0
  119. package/src/shared/components/empty-state.tsx +36 -0
  120. package/src/shared/components/error-bar.tsx +60 -0
  121. package/src/shared/components/error-boundary.tsx +53 -0
  122. package/src/shared/components/help-overlay.tsx +99 -0
  123. package/src/shared/components/identity-switcher.tsx +168 -0
  124. package/src/shared/components/loading-indicator.tsx +40 -0
  125. package/src/shared/components/pagination-bar.tsx +68 -0
  126. package/src/shared/components/pre-connection-screen.tsx +398 -0
  127. package/src/shared/components/scroll-indicator.tsx +46 -0
  128. package/src/shared/components/side-nav-utils.ts +68 -0
  129. package/src/shared/components/side-nav.tsx +287 -0
  130. package/src/shared/components/spinner.tsx +26 -0
  131. package/src/shared/components/status-bar.tsx +117 -0
  132. package/src/shared/components/styled-text.tsx +72 -0
  133. package/src/shared/components/sub-tab-bar-utils.ts +100 -0
  134. package/src/shared/components/sub-tab-bar.tsx +40 -0
  135. package/src/shared/components/tab-bar-utils.ts +36 -0
  136. package/src/shared/components/tab-bar.tsx +50 -0
  137. package/src/shared/components/text-input.tsx +73 -0
  138. package/src/shared/components/tooltip.tsx +53 -0
  139. package/src/shared/components/virtual-list.tsx +93 -0
  140. package/src/shared/components/welcome-screen.tsx +111 -0
  141. package/src/shared/hooks/use-api.ts +10 -0
  142. package/src/shared/hooks/use-brick-available.ts +42 -0
  143. package/src/shared/hooks/use-confirm.ts +66 -0
  144. package/src/shared/hooks/use-connection-state.ts +67 -0
  145. package/src/shared/hooks/use-copy.ts +31 -0
  146. package/src/shared/hooks/use-fresh-server.ts +62 -0
  147. package/src/shared/hooks/use-keyboard.ts +58 -0
  148. package/src/shared/hooks/use-list-navigation.ts +106 -0
  149. package/src/shared/hooks/use-swr.ts +117 -0
  150. package/src/shared/hooks/use-tab-fallback.ts +32 -0
  151. package/src/shared/hooks/use-text-input.ts +113 -0
  152. package/src/shared/hooks/use-visible-tabs.ts +61 -0
  153. package/src/shared/lib/circular-buffer.ts +82 -0
  154. package/src/shared/lib/clipboard.ts +14 -0
  155. package/src/shared/nav-items.ts +73 -0
  156. package/src/shared/navigation.ts +110 -0
  157. package/src/shared/status-breadcrumb.ts +74 -0
  158. package/src/shared/syntax-style.ts +3 -0
  159. package/src/shared/tab-visibility.ts +15 -0
  160. package/src/shared/text-style.ts +23 -0
  161. package/src/shared/theme.ts +179 -0
  162. package/src/shared/utils/format-size.ts +20 -0
  163. package/src/shared/utils/format-text.ts +10 -0
  164. package/src/shared/utils/format-time.ts +72 -0
  165. package/src/shared/utils/lru-cache.ts +75 -0
  166. package/src/stores/access-store-types.ts +154 -0
  167. package/src/stores/access-store.ts +674 -0
  168. package/src/stores/agents-store.ts +404 -0
  169. package/src/stores/announcement-store.ts +46 -0
  170. package/src/stores/api-console-store.ts +476 -0
  171. package/src/stores/connectors-store.ts +434 -0
  172. package/src/stores/create-api-action.ts +140 -0
  173. package/src/stores/delegation-store.ts +300 -0
  174. package/src/stores/error-store.ts +102 -0
  175. package/src/stores/events-store.ts +163 -0
  176. package/src/stores/files-store.ts +630 -0
  177. package/src/stores/first-run-store.ts +34 -0
  178. package/src/stores/global-store.ts +255 -0
  179. package/src/stores/infra-store.ts +461 -0
  180. package/src/stores/knowledge-store.ts +358 -0
  181. package/src/stores/lineage-store.ts +126 -0
  182. package/src/stores/mcp-store.ts +147 -0
  183. package/src/stores/payments-store.ts +545 -0
  184. package/src/stores/search-store-types.ts +155 -0
  185. package/src/stores/search-store.ts +656 -0
  186. package/src/stores/share-link-store.ts +151 -0
  187. package/src/stores/stack-store.ts +352 -0
  188. package/src/stores/ui-store.ts +161 -0
  189. package/src/stores/upload-store.ts +131 -0
  190. package/src/stores/versions-store.ts +355 -0
  191. package/src/stores/workflows-store.ts +402 -0
  192. package/src/stores/workspace-store.ts +185 -0
  193. 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
+ }