@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,97 @@
1
+ /**
2
+ * Drift view: shows the global drift reconciliation report
3
+ * from GET /api/v2/bricks/drift.
4
+ *
5
+ * Displays: total_bricks, drifted count, actions_taken, errors,
6
+ * last_reconcile_at, reconcile_count, and a table of drifted items.
7
+ */
8
+
9
+ import React from "react";
10
+ import type { DriftReportResponse } from "../../stores/zones-store.js";
11
+
12
+ interface DriftViewProps {
13
+ readonly drift: DriftReportResponse | null;
14
+ readonly loading: boolean;
15
+ }
16
+
17
+ function formatEpoch(epoch: number | null): string {
18
+ if (epoch === null) return "never";
19
+ try {
20
+ return new Date(epoch * 1000).toLocaleString();
21
+ } catch {
22
+ return String(epoch);
23
+ }
24
+ }
25
+
26
+ export function DriftView({ drift, loading }: DriftViewProps): React.ReactNode {
27
+ if (loading) {
28
+ return (
29
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
30
+ <text>Loading drift report...</text>
31
+ </box>
32
+ );
33
+ }
34
+
35
+ if (!drift) {
36
+ return (
37
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
38
+ <text>No drift data available</text>
39
+ </box>
40
+ );
41
+ }
42
+
43
+ const hasDrifts = drift.drifts.length > 0;
44
+
45
+ return (
46
+ <scrollbox height="100%" width="100%">
47
+ <box height={1} width="100%">
48
+ <text>--- Drift Reconciliation Report ---</text>
49
+ </box>
50
+ <box height={1} width="100%">
51
+ <text>{`Total bricks: ${drift.total_bricks}`}</text>
52
+ </box>
53
+ <box height={1} width="100%">
54
+ <text>{`Drifted: ${drift.drifted}`}</text>
55
+ </box>
56
+ <box height={1} width="100%">
57
+ <text>{`Actions taken: ${drift.actions_taken}`}</text>
58
+ </box>
59
+ <box height={1} width="100%">
60
+ <text>{`Errors: ${drift.errors}`}</text>
61
+ </box>
62
+ <box height={1} width="100%">
63
+ <text>{`Reconcile count: ${drift.reconcile_count}`}</text>
64
+ </box>
65
+ <box height={1} width="100%">
66
+ <text>{`Last reconcile: ${formatEpoch(drift.last_reconcile_at)}`}</text>
67
+ </box>
68
+
69
+ {hasDrifts && (
70
+ <>
71
+ <box height={1} width="100%" marginTop={1}>
72
+ <text>--- Drifted Items ---</text>
73
+ </box>
74
+ <box height={1} width="100%">
75
+ <text>{" BRICK NAME SPEC STATE ACTUAL STATE ACTION DETAIL"}</text>
76
+ </box>
77
+ <box height={1} width="100%">
78
+ <text>{" ----------------- ----------- ------------ ----------- -------------------------"}</text>
79
+ </box>
80
+ {drift.drifts.map((item, i) => (
81
+ <box key={`drift-${i}`} height={1} width="100%">
82
+ <text>
83
+ {` ${item.brick_name.padEnd(17)} ${item.spec_state.padEnd(11)} ${item.actual_state.padEnd(12)} ${item.action.padEnd(11)} ${item.detail}`}
84
+ </text>
85
+ </box>
86
+ ))}
87
+ </>
88
+ )}
89
+
90
+ {!hasDrifts && (
91
+ <box height={1} width="100%" marginTop={1}>
92
+ <text>No drift detected - all bricks in spec.</text>
93
+ </box>
94
+ )}
95
+ </scrollbox>
96
+ );
97
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * MCP Mounts sub-tab for the Zones panel.
3
+ * Displays mounted MCP servers with selection highlighting and status icons.
4
+ */
5
+
6
+ import React from "react";
7
+ import type { McpMount } from "../../stores/mcp-store.js";
8
+
9
+ interface McpMountsTabProps {
10
+ readonly mounts: readonly McpMount[];
11
+ readonly selectedIndex: number;
12
+ readonly loading: boolean;
13
+ }
14
+
15
+ export function McpMountsTab({
16
+ mounts,
17
+ selectedIndex,
18
+ loading,
19
+ }: McpMountsTabProps): React.ReactNode {
20
+ if (loading) return <text>Loading MCP mounts...</text>;
21
+ if (mounts.length === 0)
22
+ return <text>No MCP servers mounted. Press 'n' to mount one.</text>;
23
+
24
+ return (
25
+ <scrollbox height="100%" width="100%">
26
+ {mounts.map((mount, i) => {
27
+ const isSelected = i === selectedIndex;
28
+ const prefix = isSelected ? "> " : " ";
29
+ const statusIcon = mount.mounted ? "\u25CF" : "\u25CB";
30
+ return (
31
+ <box key={mount.name} height={1} width="100%">
32
+ <text>{`${prefix}${statusIcon} ${mount.name} ${mount.transport} ${mount.tool_count} tools ${mount.last_sync ?? "never synced"}`}</text>
33
+ </box>
34
+ );
35
+ })}
36
+ </scrollbox>
37
+ );
38
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Memories sub-tab for the Zones panel.
3
+ * Displays registered memory directories with selection highlighting.
4
+ */
5
+
6
+ import React from "react";
7
+ import type { MemoryInfo } from "../../stores/workspace-store.js";
8
+
9
+ interface MemoriesTabProps {
10
+ readonly memories: readonly MemoryInfo[];
11
+ readonly selectedIndex: number;
12
+ readonly loading: boolean;
13
+ }
14
+
15
+ export function MemoriesTab({
16
+ memories,
17
+ selectedIndex,
18
+ loading,
19
+ }: MemoriesTabProps): React.ReactNode {
20
+ if (loading) return <text>Loading memories...</text>;
21
+ if (memories.length === 0)
22
+ return <text>No memory directories registered. Press 'n' to register one.</text>;
23
+
24
+ return (
25
+ <scrollbox height="100%" width="100%">
26
+ {memories.map((mem, i) => {
27
+ const isSelected = i === selectedIndex;
28
+ const prefix = isSelected ? "> " : " ";
29
+ return (
30
+ <box key={mem.path} height={1} width="100%">
31
+ <text>{`${prefix}${mem.name} ${mem.path} ${mem.scope} ${mem.created_by ?? ""}`}</text>
32
+ </box>
33
+ );
34
+ })}
35
+ </scrollbox>
36
+ );
37
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Reindex status sub-view for the Zones panel.
3
+ * Trigger and monitor index rebuild operations.
4
+ * Issue #2930.
5
+ */
6
+
7
+ import React, { useState } from "react";
8
+ import { useApi } from "../../shared/hooks/use-api.js";
9
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
10
+
11
+ interface ReindexResult {
12
+ readonly target: string;
13
+ readonly total: number;
14
+ readonly processed: number;
15
+ readonly errors: number;
16
+ readonly lastSequence: number;
17
+ readonly dryRun: boolean;
18
+ }
19
+
20
+ export function ReindexStatus(): React.ReactNode {
21
+ const client = useApi();
22
+ const [result, setResult] = useState<ReindexResult | null>(null);
23
+ const [loading, setLoading] = useState(false);
24
+ const [error, setError] = useState<string | null>(null);
25
+
26
+ useKeyboard({
27
+ d: () => {
28
+ void triggerReindex("search", true);
29
+ },
30
+ s: () => {
31
+ void triggerReindex("search", false);
32
+ },
33
+ v: () => {
34
+ void triggerReindex("versions", false);
35
+ },
36
+ });
37
+
38
+ const triggerReindex = async (target: string, dryRun: boolean): Promise<void> => {
39
+ if (!client) return;
40
+ setLoading(true);
41
+ setError(null);
42
+ try {
43
+ const res = await client.post<ReindexResult>("/api/v2/admin/reindex", {
44
+ target,
45
+ dryRun,
46
+ });
47
+ setResult(res);
48
+ } catch (err) {
49
+ setError(err instanceof Error ? err.message : "Reindex failed");
50
+ } finally {
51
+ setLoading(false);
52
+ }
53
+ };
54
+
55
+ return (
56
+ <box flexDirection="column" height="100%" width="100%">
57
+ <text>{"─── Reindex Operations ───"}</text>
58
+ <text> </text>
59
+ <text>{" Remote targets: search | versions"}</text>
60
+ <text>{" (semantic requires local CLI: nexus reindex --target semantic)"}</text>
61
+ <text>{" Press 'd' dry-run search, 's' reindex search, 'v' reindex versions"}</text>
62
+ <text> </text>
63
+
64
+ {loading && <text>{" Reindex in progress..."}</text>}
65
+
66
+ {error && <text>{` Error: ${error}`}</text>}
67
+
68
+ {result && (
69
+ <box flexDirection="column">
70
+ <text>{` Last Reindex ${result.dryRun ? "(dry run)" : ""}`}</text>
71
+ <text>{` Target: ${result.target}`}</text>
72
+ <text>{` Total: ${result.total} MCL records`}</text>
73
+ <text>{` Processed: ${result.processed}`}</text>
74
+ <text>{` Errors: ${result.errors}`}</text>
75
+ <text>{` Last seq: ${result.lastSequence}`}</text>
76
+ </box>
77
+ )}
78
+
79
+ {!result && !loading && !error && (
80
+ <text>{" No reindex operations performed in this session."}</text>
81
+ )}
82
+ </box>
83
+ );
84
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Workspaces sub-tab for the Zones panel.
3
+ * Displays registered workspace directories with selection highlighting.
4
+ */
5
+
6
+ import React from "react";
7
+ import type { WorkspaceInfo } from "../../stores/workspace-store.js";
8
+
9
+ interface WorkspacesTabProps {
10
+ readonly workspaces: readonly WorkspaceInfo[];
11
+ readonly selectedIndex: number;
12
+ readonly loading: boolean;
13
+ }
14
+
15
+ export function WorkspacesTab({
16
+ workspaces,
17
+ selectedIndex,
18
+ loading,
19
+ }: WorkspacesTabProps): React.ReactNode {
20
+ if (loading) return <text>Loading workspaces...</text>;
21
+ if (workspaces.length === 0)
22
+ return <text>No workspaces registered. Press 'n' to register one.</text>;
23
+
24
+ return (
25
+ <scrollbox height="100%" width="100%">
26
+ {workspaces.map((ws, i) => {
27
+ const isSelected = i === selectedIndex;
28
+ const prefix = isSelected ? "> " : " ";
29
+ return (
30
+ <box key={ws.path} height={1} width="100%">
31
+ <text>{`${prefix}${ws.name} ${ws.path} ${ws.scope} ${ws.created_by ?? ""}`}</text>
32
+ </box>
33
+ );
34
+ })}
35
+ </scrollbox>
36
+ );
37
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Zone list view: shows zones from GET /api/zones.
3
+ *
4
+ * Displays: zone_id, name, domain, phase, is_active, created_at.
5
+ */
6
+
7
+ import React from "react";
8
+ import type { ZoneResponse } from "../../stores/zones-store.js";
9
+ import { EmptyState } from "../../shared/components/empty-state.js";
10
+
11
+ interface ZoneListProps {
12
+ readonly zones: readonly ZoneResponse[];
13
+ readonly selectedIndex: number;
14
+ readonly loading: boolean;
15
+ }
16
+
17
+ function formatTimestamp(ts: string): string {
18
+ try {
19
+ return new Date(ts).toLocaleString();
20
+ } catch {
21
+ return ts;
22
+ }
23
+ }
24
+
25
+ function truncate(value: string, maxLen: number): string {
26
+ if (value.length <= maxLen) return value;
27
+ return `${value.slice(0, maxLen - 2)}..`;
28
+ }
29
+
30
+ export function ZoneList({
31
+ zones,
32
+ selectedIndex,
33
+ loading,
34
+ }: ZoneListProps): React.ReactNode {
35
+ if (loading) {
36
+ return (
37
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
38
+ <text>Loading zones...</text>
39
+ </box>
40
+ );
41
+ }
42
+
43
+ if (zones.length === 0) {
44
+ return <EmptyState message="No zones found." hint="Press n to create a zone." />;
45
+ }
46
+
47
+ return (
48
+ <scrollbox height="100%" width="100%">
49
+ {/* Header */}
50
+ <box height={1} width="100%">
51
+ <text>{" ZONE ID NAME DOMAIN PHASE ACTIVE CREATED"}</text>
52
+ </box>
53
+ <box height={1} width="100%">
54
+ <text>{" ----------------- --------------- --------------- -------- ------ -------------------------"}</text>
55
+ </box>
56
+
57
+ {/* Rows */}
58
+ {zones.map((zone, i) => {
59
+ const isSelected = i === selectedIndex;
60
+ const prefix = isSelected ? "> " : " ";
61
+ const activeLabel = zone.is_active ? "yes" : "no";
62
+
63
+ return (
64
+ <box key={zone.zone_id} height={1} width="100%">
65
+ <text>
66
+ {`${prefix}${truncate(zone.zone_id, 17).padEnd(17)} ${truncate(zone.name, 15).padEnd(15)} ${truncate(zone.domain ?? "-", 15).padEnd(15)} ${zone.phase.padEnd(8)} ${activeLabel.padEnd(6)} ${formatTimestamp(zone.created_at)}`}
67
+ </text>
68
+ </box>
69
+ );
70
+ })}
71
+ </scrollbox>
72
+ );
73
+ }