@runloop/rl-cli 1.2.0 → 1.3.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 +28 -8
- package/dist/commands/blueprint/list.js +97 -28
- package/dist/commands/blueprint/prune.js +7 -19
- package/dist/commands/devbox/create.js +3 -0
- package/dist/commands/devbox/list.js +44 -65
- package/dist/commands/menu.js +2 -1
- package/dist/commands/network-policy/create.js +27 -0
- package/dist/commands/network-policy/delete.js +21 -0
- package/dist/commands/network-policy/get.js +15 -0
- package/dist/commands/network-policy/list.js +494 -0
- package/dist/commands/object/list.js +516 -24
- package/dist/commands/snapshot/list.js +90 -29
- package/dist/components/Banner.js +109 -8
- package/dist/components/ConfirmationPrompt.js +45 -0
- package/dist/components/DevboxActionsMenu.js +42 -6
- package/dist/components/DevboxCard.js +1 -1
- package/dist/components/DevboxCreatePage.js +95 -81
- package/dist/components/DevboxDetailPage.js +218 -272
- package/dist/components/LogsViewer.js +8 -1
- package/dist/components/MainMenu.js +35 -4
- package/dist/components/NavigationTips.js +24 -0
- package/dist/components/NetworkPolicyCreatePage.js +264 -0
- package/dist/components/OperationsMenu.js +9 -1
- package/dist/components/ResourceActionsMenu.js +5 -1
- package/dist/components/ResourceDetailPage.js +204 -0
- package/dist/components/ResourceListView.js +19 -2
- package/dist/components/StatusBadge.js +2 -2
- package/dist/components/Table.js +6 -8
- package/dist/components/form/FormActionButton.js +7 -0
- package/dist/components/form/FormField.js +7 -0
- package/dist/components/form/FormListManager.js +112 -0
- package/dist/components/form/FormSelect.js +34 -0
- package/dist/components/form/FormTextInput.js +8 -0
- package/dist/components/form/index.js +8 -0
- package/dist/hooks/useViewportHeight.js +38 -20
- package/dist/router/Router.js +23 -1
- package/dist/screens/BlueprintDetailScreen.js +337 -0
- package/dist/screens/MenuScreen.js +6 -0
- package/dist/screens/NetworkPolicyCreateScreen.js +7 -0
- package/dist/screens/NetworkPolicyDetailScreen.js +247 -0
- package/dist/screens/NetworkPolicyListScreen.js +7 -0
- package/dist/screens/ObjectDetailScreen.js +377 -0
- package/dist/screens/ObjectListScreen.js +7 -0
- package/dist/screens/SnapshotDetailScreen.js +208 -0
- package/dist/services/blueprintService.js +30 -11
- package/dist/services/networkPolicyService.js +108 -0
- package/dist/services/objectService.js +101 -0
- package/dist/services/snapshotService.js +39 -3
- package/dist/store/blueprintStore.js +4 -10
- package/dist/store/index.js +1 -0
- package/dist/store/networkPolicyStore.js +83 -0
- package/dist/store/objectStore.js +92 -0
- package/dist/store/snapshotStore.js +4 -8
- package/dist/utils/commands.js +47 -0
- package/package.json +2 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete network policy command
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from "../../utils/client.js";
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
6
|
+
export async function deleteNetworkPolicy(id, options = {}) {
|
|
7
|
+
try {
|
|
8
|
+
const client = getClient();
|
|
9
|
+
await client.networkPolicies.delete(id);
|
|
10
|
+
// Default: just output the ID for easy scripting
|
|
11
|
+
if (!options.output || options.output === "text") {
|
|
12
|
+
console.log(id);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
output({ id, status: "deleted" }, { format: options.output, defaultFormat: "json" });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
outputError("Failed to delete network policy", error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get network policy details command
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from "../../utils/client.js";
|
|
5
|
+
import { output, outputError } from "../../utils/output.js";
|
|
6
|
+
export async function getNetworkPolicy(options) {
|
|
7
|
+
try {
|
|
8
|
+
const client = getClient();
|
|
9
|
+
const policy = await client.networkPolicies.retrieve(options.id);
|
|
10
|
+
output(policy, { format: options.output, defaultFormat: "json" });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
outputError("Failed to get network policy", error);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput, useApp } from "ink";
|
|
4
|
+
import figures from "figures";
|
|
5
|
+
import { getClient } from "../../utils/client.js";
|
|
6
|
+
import { Header } from "../../components/Header.js";
|
|
7
|
+
import { SpinnerComponent } from "../../components/Spinner.js";
|
|
8
|
+
import { ErrorMessage } from "../../components/ErrorMessage.js";
|
|
9
|
+
import { SuccessMessage } from "../../components/SuccessMessage.js";
|
|
10
|
+
import { Breadcrumb } from "../../components/Breadcrumb.js";
|
|
11
|
+
import { NavigationTips } from "../../components/NavigationTips.js";
|
|
12
|
+
import { Table, createTextColumn } from "../../components/Table.js";
|
|
13
|
+
import { ActionsPopup } from "../../components/ActionsPopup.js";
|
|
14
|
+
import { formatTimeAgo } from "../../components/ResourceListView.js";
|
|
15
|
+
import { output, outputError } from "../../utils/output.js";
|
|
16
|
+
import { colors } from "../../utils/theme.js";
|
|
17
|
+
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
|
|
18
|
+
import { useExitOnCtrlC } from "../../hooks/useExitOnCtrlC.js";
|
|
19
|
+
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
|
|
20
|
+
import { useNavigation } from "../../store/navigationStore.js";
|
|
21
|
+
import { NetworkPolicyCreatePage } from "../../components/NetworkPolicyCreatePage.js";
|
|
22
|
+
import { ConfirmationPrompt } from "../../components/ConfirmationPrompt.js";
|
|
23
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
24
|
+
/**
|
|
25
|
+
* Get a display label for the egress policy type
|
|
26
|
+
*/
|
|
27
|
+
function getEgressTypeLabel(egress) {
|
|
28
|
+
if (egress.allow_all) {
|
|
29
|
+
return "Allow All";
|
|
30
|
+
}
|
|
31
|
+
if (egress.allowed_hostnames.length === 0) {
|
|
32
|
+
return "Deny All";
|
|
33
|
+
}
|
|
34
|
+
return `Custom (${egress.allowed_hostnames.length})`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get color for egress type
|
|
38
|
+
*/
|
|
39
|
+
function getEgressTypeColor(egress) {
|
|
40
|
+
if (egress.allow_all) {
|
|
41
|
+
return colors.success;
|
|
42
|
+
}
|
|
43
|
+
if (egress.allowed_hostnames.length === 0) {
|
|
44
|
+
return colors.error;
|
|
45
|
+
}
|
|
46
|
+
return colors.warning;
|
|
47
|
+
}
|
|
48
|
+
const ListNetworkPoliciesUI = ({ onBack, onExit, }) => {
|
|
49
|
+
const { exit: inkExit } = useApp();
|
|
50
|
+
const { navigate } = useNavigation();
|
|
51
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
52
|
+
const [showPopup, setShowPopup] = React.useState(false);
|
|
53
|
+
const [selectedOperation, setSelectedOperation] = React.useState(0);
|
|
54
|
+
const [selectedPolicy, setSelectedPolicy] = React.useState(null);
|
|
55
|
+
const [executingOperation, setExecutingOperation] = React.useState(null);
|
|
56
|
+
const [operationResult, setOperationResult] = React.useState(null);
|
|
57
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
58
|
+
const [operationLoading, setOperationLoading] = React.useState(false);
|
|
59
|
+
const [showCreatePolicy, setShowCreatePolicy] = React.useState(false);
|
|
60
|
+
const [showEditPolicy, setShowEditPolicy] = React.useState(false);
|
|
61
|
+
const [editingPolicy, setEditingPolicy] = React.useState(null);
|
|
62
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
63
|
+
// Calculate overhead for viewport height
|
|
64
|
+
const overhead = 13;
|
|
65
|
+
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
66
|
+
overhead,
|
|
67
|
+
minHeight: 5,
|
|
68
|
+
});
|
|
69
|
+
const PAGE_SIZE = viewportHeight;
|
|
70
|
+
// All width constants
|
|
71
|
+
const fixedWidth = 6; // border + padding
|
|
72
|
+
const idWidth = 25;
|
|
73
|
+
const egressWidth = 15;
|
|
74
|
+
const timeWidth = 20;
|
|
75
|
+
const showDescription = terminalWidth >= 100;
|
|
76
|
+
const descriptionWidth = Math.max(20, terminalWidth >= 140 ? 40 : 25);
|
|
77
|
+
// Name width uses remaining space after fixed columns
|
|
78
|
+
const baseWidth = fixedWidth + idWidth + egressWidth + timeWidth;
|
|
79
|
+
const optionalWidth = showDescription ? descriptionWidth : 0;
|
|
80
|
+
const remainingWidth = terminalWidth - baseWidth - optionalWidth;
|
|
81
|
+
const nameWidth = Math.min(80, Math.max(15, remainingWidth));
|
|
82
|
+
// Fetch function for pagination hook
|
|
83
|
+
const fetchPage = React.useCallback(async (params) => {
|
|
84
|
+
const client = getClient();
|
|
85
|
+
const pagePolicies = [];
|
|
86
|
+
// Build query params
|
|
87
|
+
const queryParams = {
|
|
88
|
+
limit: params.limit,
|
|
89
|
+
};
|
|
90
|
+
if (params.startingAt) {
|
|
91
|
+
queryParams.starting_after = params.startingAt;
|
|
92
|
+
}
|
|
93
|
+
// Fetch ONE page only
|
|
94
|
+
const page = (await client.networkPolicies.list(queryParams));
|
|
95
|
+
// Extract data and create defensive copies
|
|
96
|
+
if (page.network_policies && Array.isArray(page.network_policies)) {
|
|
97
|
+
page.network_policies.forEach((p) => {
|
|
98
|
+
pagePolicies.push({
|
|
99
|
+
id: p.id,
|
|
100
|
+
name: p.name,
|
|
101
|
+
description: p.description,
|
|
102
|
+
create_time_ms: p.create_time_ms,
|
|
103
|
+
update_time_ms: p.update_time_ms,
|
|
104
|
+
egress: {
|
|
105
|
+
allow_all: p.egress.allow_all,
|
|
106
|
+
allow_devbox_to_devbox: p.egress.allow_devbox_to_devbox,
|
|
107
|
+
allowed_hostnames: [...(p.egress.allowed_hostnames || [])],
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const result = {
|
|
113
|
+
items: pagePolicies,
|
|
114
|
+
hasMore: page.has_more || false,
|
|
115
|
+
totalCount: page.total_count || pagePolicies.length,
|
|
116
|
+
};
|
|
117
|
+
return result;
|
|
118
|
+
}, []);
|
|
119
|
+
// Use the shared pagination hook
|
|
120
|
+
const { items: policies, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
|
|
121
|
+
fetchPage,
|
|
122
|
+
pageSize: PAGE_SIZE,
|
|
123
|
+
getItemId: (policy) => policy.id,
|
|
124
|
+
pollInterval: 5000,
|
|
125
|
+
pollingEnabled: !showPopup &&
|
|
126
|
+
!executingOperation &&
|
|
127
|
+
!showCreatePolicy &&
|
|
128
|
+
!showEditPolicy &&
|
|
129
|
+
!showDeleteConfirm,
|
|
130
|
+
deps: [PAGE_SIZE],
|
|
131
|
+
});
|
|
132
|
+
// Operations for a specific network policy (shown in popup)
|
|
133
|
+
const operations = React.useMemo(() => [
|
|
134
|
+
{
|
|
135
|
+
key: "view_details",
|
|
136
|
+
label: "View Details",
|
|
137
|
+
color: colors.primary,
|
|
138
|
+
icon: figures.pointer,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: "edit",
|
|
142
|
+
label: "Edit Network Policy",
|
|
143
|
+
color: colors.warning,
|
|
144
|
+
icon: figures.pointer,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: "delete",
|
|
148
|
+
label: "Delete Network Policy",
|
|
149
|
+
color: colors.error,
|
|
150
|
+
icon: figures.cross,
|
|
151
|
+
},
|
|
152
|
+
], []);
|
|
153
|
+
// Build columns
|
|
154
|
+
const columns = React.useMemo(() => [
|
|
155
|
+
createTextColumn("id", "ID", (policy) => policy.id, {
|
|
156
|
+
width: idWidth + 1,
|
|
157
|
+
color: colors.idColor,
|
|
158
|
+
dimColor: false,
|
|
159
|
+
bold: false,
|
|
160
|
+
}),
|
|
161
|
+
createTextColumn("name", "Name", (policy) => policy.name || "", {
|
|
162
|
+
width: nameWidth,
|
|
163
|
+
}),
|
|
164
|
+
...(showDescription
|
|
165
|
+
? [
|
|
166
|
+
createTextColumn("description", "Description", (policy) => policy.description || "", {
|
|
167
|
+
width: descriptionWidth,
|
|
168
|
+
color: colors.textDim,
|
|
169
|
+
dimColor: false,
|
|
170
|
+
bold: false,
|
|
171
|
+
}),
|
|
172
|
+
]
|
|
173
|
+
: []),
|
|
174
|
+
{
|
|
175
|
+
key: "egress",
|
|
176
|
+
label: "Egress",
|
|
177
|
+
width: egressWidth,
|
|
178
|
+
render: (policy, _index, isSelected) => {
|
|
179
|
+
const label = getEgressTypeLabel(policy.egress);
|
|
180
|
+
const color = getEgressTypeColor(policy.egress);
|
|
181
|
+
const safeWidth = Math.max(1, egressWidth);
|
|
182
|
+
const truncated = label.slice(0, safeWidth);
|
|
183
|
+
const padded = truncated.padEnd(safeWidth, " ");
|
|
184
|
+
return (_jsx(Text, { color: isSelected ? "white" : color, bold: true, dimColor: false, inverse: isSelected, wrap: "truncate", children: padded }));
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
createTextColumn("created", "Created", (policy) => policy.create_time_ms ? formatTimeAgo(policy.create_time_ms) : "", {
|
|
188
|
+
width: timeWidth,
|
|
189
|
+
color: colors.textDim,
|
|
190
|
+
dimColor: false,
|
|
191
|
+
bold: false,
|
|
192
|
+
}),
|
|
193
|
+
], [
|
|
194
|
+
idWidth,
|
|
195
|
+
nameWidth,
|
|
196
|
+
descriptionWidth,
|
|
197
|
+
egressWidth,
|
|
198
|
+
timeWidth,
|
|
199
|
+
showDescription,
|
|
200
|
+
]);
|
|
201
|
+
// Handle Ctrl+C to exit
|
|
202
|
+
useExitOnCtrlC();
|
|
203
|
+
// Ensure selected index is within bounds
|
|
204
|
+
React.useEffect(() => {
|
|
205
|
+
if (policies.length > 0 && selectedIndex >= policies.length) {
|
|
206
|
+
setSelectedIndex(Math.max(0, policies.length - 1));
|
|
207
|
+
}
|
|
208
|
+
}, [policies.length, selectedIndex]);
|
|
209
|
+
const selectedPolicyItem = policies[selectedIndex];
|
|
210
|
+
// Calculate pagination info for display
|
|
211
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
|
|
212
|
+
const startIndex = currentPage * PAGE_SIZE;
|
|
213
|
+
const endIndex = startIndex + policies.length;
|
|
214
|
+
const executeOperation = async (policy, operationKey) => {
|
|
215
|
+
const client = getClient();
|
|
216
|
+
if (!policy)
|
|
217
|
+
return;
|
|
218
|
+
try {
|
|
219
|
+
setOperationLoading(true);
|
|
220
|
+
switch (operationKey) {
|
|
221
|
+
case "delete":
|
|
222
|
+
await client.networkPolicies.delete(policy.id);
|
|
223
|
+
setOperationResult(`Network policy "${policy.name}" deleted successfully`);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
setOperationError(err);
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
setOperationLoading(false);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
useInput((input, key) => {
|
|
235
|
+
// Handle operation result display
|
|
236
|
+
if (operationResult || operationError) {
|
|
237
|
+
if (input === "q" || key.escape || key.return) {
|
|
238
|
+
const wasDelete = executingOperation === "delete";
|
|
239
|
+
const hadError = operationError !== null;
|
|
240
|
+
setOperationResult(null);
|
|
241
|
+
setOperationError(null);
|
|
242
|
+
setExecutingOperation(null);
|
|
243
|
+
setSelectedPolicy(null);
|
|
244
|
+
// Refresh the list after delete to show updated data
|
|
245
|
+
if (wasDelete && !hadError) {
|
|
246
|
+
setTimeout(() => refresh(), 0);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Handle create policy screen
|
|
252
|
+
if (showCreatePolicy) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Handle edit policy screen
|
|
256
|
+
if (showEditPolicy) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// Handle popup navigation
|
|
260
|
+
if (showPopup) {
|
|
261
|
+
if (key.upArrow && selectedOperation > 0) {
|
|
262
|
+
setSelectedOperation(selectedOperation - 1);
|
|
263
|
+
}
|
|
264
|
+
else if (key.downArrow && selectedOperation < operations.length - 1) {
|
|
265
|
+
setSelectedOperation(selectedOperation + 1);
|
|
266
|
+
}
|
|
267
|
+
else if (key.return) {
|
|
268
|
+
setShowPopup(false);
|
|
269
|
+
const operationKey = operations[selectedOperation].key;
|
|
270
|
+
if (operationKey === "create") {
|
|
271
|
+
setShowCreatePolicy(true);
|
|
272
|
+
}
|
|
273
|
+
else if (operationKey === "view_details") {
|
|
274
|
+
navigate("network-policy-detail", {
|
|
275
|
+
networkPolicyId: selectedPolicyItem.id,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
else if (operationKey === "edit") {
|
|
279
|
+
// Show edit form
|
|
280
|
+
setEditingPolicy(selectedPolicyItem);
|
|
281
|
+
setShowEditPolicy(true);
|
|
282
|
+
}
|
|
283
|
+
else if (operationKey === "delete") {
|
|
284
|
+
// Show delete confirmation
|
|
285
|
+
setSelectedPolicy(selectedPolicyItem);
|
|
286
|
+
setShowDeleteConfirm(true);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
setSelectedPolicy(selectedPolicyItem);
|
|
290
|
+
setExecutingOperation(operationKey);
|
|
291
|
+
// Execute immediately with values passed directly
|
|
292
|
+
executeOperation(selectedPolicyItem, operationKey);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else if (input === "c") {
|
|
296
|
+
// Create hotkey
|
|
297
|
+
setShowPopup(false);
|
|
298
|
+
setShowCreatePolicy(true);
|
|
299
|
+
}
|
|
300
|
+
else if (input === "v" && selectedPolicyItem) {
|
|
301
|
+
// View details hotkey
|
|
302
|
+
setShowPopup(false);
|
|
303
|
+
navigate("network-policy-detail", {
|
|
304
|
+
networkPolicyId: selectedPolicyItem.id,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else if (input === "e" && selectedPolicyItem) {
|
|
308
|
+
// Edit hotkey
|
|
309
|
+
setShowPopup(false);
|
|
310
|
+
setEditingPolicy(selectedPolicyItem);
|
|
311
|
+
setShowEditPolicy(true);
|
|
312
|
+
}
|
|
313
|
+
else if (key.escape || input === "q") {
|
|
314
|
+
setShowPopup(false);
|
|
315
|
+
setSelectedOperation(0);
|
|
316
|
+
}
|
|
317
|
+
else if (input === "d") {
|
|
318
|
+
// Delete hotkey - show confirmation
|
|
319
|
+
setShowPopup(false);
|
|
320
|
+
setSelectedPolicy(selectedPolicyItem);
|
|
321
|
+
setShowDeleteConfirm(true);
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const pagePolicies = policies.length;
|
|
326
|
+
// Handle list view navigation
|
|
327
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
328
|
+
setSelectedIndex(selectedIndex - 1);
|
|
329
|
+
}
|
|
330
|
+
else if (key.downArrow && selectedIndex < pagePolicies - 1) {
|
|
331
|
+
setSelectedIndex(selectedIndex + 1);
|
|
332
|
+
}
|
|
333
|
+
else if ((input === "n" || key.rightArrow) &&
|
|
334
|
+
!loading &&
|
|
335
|
+
!navigating &&
|
|
336
|
+
hasMore) {
|
|
337
|
+
nextPage();
|
|
338
|
+
setSelectedIndex(0);
|
|
339
|
+
}
|
|
340
|
+
else if ((input === "p" || key.leftArrow) &&
|
|
341
|
+
!loading &&
|
|
342
|
+
!navigating &&
|
|
343
|
+
hasPrev) {
|
|
344
|
+
prevPage();
|
|
345
|
+
setSelectedIndex(0);
|
|
346
|
+
}
|
|
347
|
+
else if (key.return && selectedPolicyItem) {
|
|
348
|
+
// Enter key navigates to detail view
|
|
349
|
+
navigate("network-policy-detail", {
|
|
350
|
+
networkPolicyId: selectedPolicyItem.id,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
else if (input === "a") {
|
|
354
|
+
setShowPopup(true);
|
|
355
|
+
setSelectedOperation(0);
|
|
356
|
+
}
|
|
357
|
+
else if (input === "c") {
|
|
358
|
+
// Create shortcut
|
|
359
|
+
setShowCreatePolicy(true);
|
|
360
|
+
}
|
|
361
|
+
else if (input === "e" && selectedPolicyItem) {
|
|
362
|
+
// Edit shortcut
|
|
363
|
+
setEditingPolicy(selectedPolicyItem);
|
|
364
|
+
setShowEditPolicy(true);
|
|
365
|
+
}
|
|
366
|
+
else if (key.escape) {
|
|
367
|
+
if (onBack) {
|
|
368
|
+
onBack();
|
|
369
|
+
}
|
|
370
|
+
else if (onExit) {
|
|
371
|
+
onExit();
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
inkExit();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
// Delete confirmation
|
|
379
|
+
if (showDeleteConfirm && selectedPolicy) {
|
|
380
|
+
return (_jsx(ConfirmationPrompt, { title: "Delete Network Policy", message: `Are you sure you want to delete "${selectedPolicy.name}"?`, details: "This action cannot be undone. Any devboxes using this policy will lose their network restrictions.", breadcrumbItems: [
|
|
381
|
+
{ label: "Network Policies" },
|
|
382
|
+
{ label: selectedPolicy.name || selectedPolicy.id },
|
|
383
|
+
{ label: "Delete", active: true },
|
|
384
|
+
], onConfirm: () => {
|
|
385
|
+
setShowDeleteConfirm(false);
|
|
386
|
+
setExecutingOperation("delete");
|
|
387
|
+
executeOperation(selectedPolicy, "delete");
|
|
388
|
+
}, onCancel: () => {
|
|
389
|
+
setShowDeleteConfirm(false);
|
|
390
|
+
setSelectedPolicy(null);
|
|
391
|
+
} }));
|
|
392
|
+
}
|
|
393
|
+
// Operation result display
|
|
394
|
+
if (operationResult || operationError) {
|
|
395
|
+
const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
|
|
396
|
+
"Operation";
|
|
397
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
398
|
+
{ label: "Network Policies" },
|
|
399
|
+
{
|
|
400
|
+
label: selectedPolicy?.name || selectedPolicy?.id || "Policy",
|
|
401
|
+
},
|
|
402
|
+
{ label: operationLabel, active: true },
|
|
403
|
+
] }), _jsx(Header, { title: "Operation Result" }), operationResult && _jsx(SuccessMessage, { message: operationResult }), operationError && (_jsx(ErrorMessage, { message: "Operation failed", error: operationError })), _jsx(NavigationTips, { tips: [{ key: "Enter/q/esc", label: "Continue" }] })] }));
|
|
404
|
+
}
|
|
405
|
+
// Operation loading state
|
|
406
|
+
if (operationLoading && selectedPolicy) {
|
|
407
|
+
const operationLabel = operations.find((o) => o.key === executingOperation)?.label ||
|
|
408
|
+
"Operation";
|
|
409
|
+
const messages = {
|
|
410
|
+
delete: "Deleting network policy...",
|
|
411
|
+
};
|
|
412
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
413
|
+
{ label: "Network Policies" },
|
|
414
|
+
{ label: selectedPolicy.name || selectedPolicy.id },
|
|
415
|
+
{ label: operationLabel, active: true },
|
|
416
|
+
] }), _jsx(Header, { title: "Executing Operation" }), _jsx(SpinnerComponent, { message: messages[executingOperation] || "Please wait..." })] }));
|
|
417
|
+
}
|
|
418
|
+
// Create policy screen
|
|
419
|
+
if (showCreatePolicy) {
|
|
420
|
+
return (_jsx(NetworkPolicyCreatePage, { onBack: () => setShowCreatePolicy(false), onCreate: (policy) => {
|
|
421
|
+
setShowCreatePolicy(false);
|
|
422
|
+
navigate("network-policy-detail", { networkPolicyId: policy.id });
|
|
423
|
+
} }));
|
|
424
|
+
}
|
|
425
|
+
// Edit policy screen
|
|
426
|
+
if (showEditPolicy && editingPolicy) {
|
|
427
|
+
return (_jsx(NetworkPolicyCreatePage, { onBack: () => {
|
|
428
|
+
setShowEditPolicy(false);
|
|
429
|
+
setEditingPolicy(null);
|
|
430
|
+
}, onCreate: () => {
|
|
431
|
+
setShowEditPolicy(false);
|
|
432
|
+
setEditingPolicy(null);
|
|
433
|
+
// Refresh the list to show updated data
|
|
434
|
+
setTimeout(() => refresh(), 0);
|
|
435
|
+
}, initialPolicy: editingPolicy }));
|
|
436
|
+
}
|
|
437
|
+
// Loading state
|
|
438
|
+
if (loading && policies.length === 0) {
|
|
439
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), _jsx(SpinnerComponent, { message: "Loading network policies..." })] }));
|
|
440
|
+
}
|
|
441
|
+
// Error state
|
|
442
|
+
if (error) {
|
|
443
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), _jsx(ErrorMessage, { message: "Failed to list network policies", error: error })] }));
|
|
444
|
+
}
|
|
445
|
+
// Main list view
|
|
446
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [{ label: "Network Policies", active: true }] }), !showPopup && (_jsx(Table, { data: policies, keyExtractor: (policy) => policy.id, selectedIndex: selectedIndex, title: `network_policies[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No network policies found. Press [c] to create one."] }) })), !showPopup && (_jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " ", totalCount] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "total"] }), totalPages > 1 && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), navigating ? (_jsxs(Text, { color: colors.warning, children: [figures.pointer, " Loading page ", currentPage + 1, "..."] })) : (_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Page ", currentPage + 1, " of ", totalPages] }))] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Showing ", startIndex + 1, "-", endIndex, " of ", totalCount] })] })), showPopup && selectedPolicyItem && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedPolicyItem, operations: operations.map((op) => ({
|
|
447
|
+
key: op.key,
|
|
448
|
+
label: op.label,
|
|
449
|
+
color: op.color,
|
|
450
|
+
icon: op.icon,
|
|
451
|
+
shortcut: op.key === "create"
|
|
452
|
+
? "c"
|
|
453
|
+
: op.key === "view_details"
|
|
454
|
+
? "v"
|
|
455
|
+
: op.key === "edit"
|
|
456
|
+
? "e"
|
|
457
|
+
: op.key === "delete"
|
|
458
|
+
? "d"
|
|
459
|
+
: "",
|
|
460
|
+
})), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
461
|
+
{
|
|
462
|
+
icon: `${figures.arrowLeft}${figures.arrowRight}`,
|
|
463
|
+
label: "Page",
|
|
464
|
+
condition: hasMore || hasPrev,
|
|
465
|
+
},
|
|
466
|
+
{ key: "Enter", label: "Details" },
|
|
467
|
+
{ key: "c", label: "Create" },
|
|
468
|
+
{ key: "e", label: "Edit" },
|
|
469
|
+
{ key: "a", label: "Actions" },
|
|
470
|
+
{ key: "Esc", label: "Back" },
|
|
471
|
+
] })] }));
|
|
472
|
+
};
|
|
473
|
+
// Export the UI component for use in the main menu
|
|
474
|
+
export { ListNetworkPoliciesUI };
|
|
475
|
+
export async function listNetworkPolicies(options = {}) {
|
|
476
|
+
try {
|
|
477
|
+
const client = getClient();
|
|
478
|
+
// Build query params
|
|
479
|
+
const queryParams = {
|
|
480
|
+
limit: DEFAULT_PAGE_SIZE,
|
|
481
|
+
};
|
|
482
|
+
if (options.name) {
|
|
483
|
+
queryParams.name = options.name;
|
|
484
|
+
}
|
|
485
|
+
// Fetch network policies
|
|
486
|
+
const page = (await client.networkPolicies.list(queryParams));
|
|
487
|
+
// Extract network policies array
|
|
488
|
+
const networkPolicies = page.network_policies || [];
|
|
489
|
+
output(networkPolicies, { format: options.output, defaultFormat: "json" });
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
outputError("Failed to list network policies", error);
|
|
493
|
+
}
|
|
494
|
+
}
|