@runloop/rl-cli 1.2.0 → 1.4.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 +29 -8
- package/dist/commands/blueprint/from-dockerfile.js +182 -0
- 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 +174 -168
- 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 +263 -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 +355 -0
- package/dist/screens/DevboxDetailScreen.js +4 -4
- 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 +65 -0
- package/package.json +2 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network Policy Service - Handles all network policy API calls
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from "../utils/client.js";
|
|
5
|
+
/**
|
|
6
|
+
* List network policies with pagination
|
|
7
|
+
*/
|
|
8
|
+
export async function listNetworkPolicies(options) {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
const queryParams = {
|
|
11
|
+
limit: options.limit,
|
|
12
|
+
};
|
|
13
|
+
if (options.startingAfter) {
|
|
14
|
+
queryParams.starting_after = options.startingAfter;
|
|
15
|
+
}
|
|
16
|
+
if (options.search) {
|
|
17
|
+
queryParams.name = options.search;
|
|
18
|
+
}
|
|
19
|
+
const pagePromise = client.networkPolicies.list(queryParams);
|
|
20
|
+
const page = (await pagePromise);
|
|
21
|
+
const networkPolicies = [];
|
|
22
|
+
if (page.network_policies && Array.isArray(page.network_policies)) {
|
|
23
|
+
page.network_policies.forEach((p) => {
|
|
24
|
+
// CRITICAL: Truncate all strings to prevent Yoga crashes
|
|
25
|
+
const MAX_ID_LENGTH = 100;
|
|
26
|
+
const MAX_NAME_LENGTH = 200;
|
|
27
|
+
const MAX_DESC_LENGTH = 500;
|
|
28
|
+
networkPolicies.push({
|
|
29
|
+
id: String(p.id || "").substring(0, MAX_ID_LENGTH),
|
|
30
|
+
name: String(p.name || "").substring(0, MAX_NAME_LENGTH),
|
|
31
|
+
description: p.description
|
|
32
|
+
? String(p.description).substring(0, MAX_DESC_LENGTH)
|
|
33
|
+
: undefined,
|
|
34
|
+
create_time_ms: p.create_time_ms,
|
|
35
|
+
update_time_ms: p.update_time_ms,
|
|
36
|
+
egress: {
|
|
37
|
+
allow_all: p.egress.allow_all,
|
|
38
|
+
allow_devbox_to_devbox: p.egress.allow_devbox_to_devbox,
|
|
39
|
+
allowed_hostnames: p.egress.allowed_hostnames || [],
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const result = {
|
|
45
|
+
networkPolicies,
|
|
46
|
+
totalCount: page.total_count || networkPolicies.length,
|
|
47
|
+
hasMore: page.has_more || false,
|
|
48
|
+
};
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get a single network policy by ID
|
|
53
|
+
*/
|
|
54
|
+
export async function getNetworkPolicy(id) {
|
|
55
|
+
const client = getClient();
|
|
56
|
+
const policy = await client.networkPolicies.retrieve(id);
|
|
57
|
+
return {
|
|
58
|
+
id: policy.id,
|
|
59
|
+
name: policy.name,
|
|
60
|
+
description: policy.description ?? undefined,
|
|
61
|
+
create_time_ms: policy.create_time_ms,
|
|
62
|
+
update_time_ms: policy.update_time_ms,
|
|
63
|
+
egress: {
|
|
64
|
+
allow_all: policy.egress.allow_all,
|
|
65
|
+
allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
|
|
66
|
+
allowed_hostnames: policy.egress.allowed_hostnames || [],
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Delete a network policy
|
|
72
|
+
*/
|
|
73
|
+
export async function deleteNetworkPolicy(id) {
|
|
74
|
+
const client = getClient();
|
|
75
|
+
await client.networkPolicies.delete(id);
|
|
76
|
+
}
|
|
77
|
+
export async function createNetworkPolicy(params) {
|
|
78
|
+
const client = getClient();
|
|
79
|
+
const policy = await client.networkPolicies.create(params);
|
|
80
|
+
return {
|
|
81
|
+
id: policy.id,
|
|
82
|
+
name: policy.name,
|
|
83
|
+
description: policy.description ?? undefined,
|
|
84
|
+
create_time_ms: policy.create_time_ms,
|
|
85
|
+
update_time_ms: policy.update_time_ms,
|
|
86
|
+
egress: {
|
|
87
|
+
allow_all: policy.egress.allow_all,
|
|
88
|
+
allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
|
|
89
|
+
allowed_hostnames: policy.egress.allowed_hostnames || [],
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export async function updateNetworkPolicy(id, params) {
|
|
94
|
+
const client = getClient();
|
|
95
|
+
const policy = await client.networkPolicies.update(id, params);
|
|
96
|
+
return {
|
|
97
|
+
id: policy.id,
|
|
98
|
+
name: policy.name,
|
|
99
|
+
description: policy.description ?? undefined,
|
|
100
|
+
create_time_ms: policy.create_time_ms,
|
|
101
|
+
update_time_ms: policy.update_time_ms,
|
|
102
|
+
egress: {
|
|
103
|
+
allow_all: policy.egress.allow_all,
|
|
104
|
+
allow_devbox_to_devbox: policy.egress.allow_devbox_to_devbox,
|
|
105
|
+
allowed_hostnames: policy.egress.allowed_hostnames || [],
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object Service - Handles all storage object API calls
|
|
3
|
+
*/
|
|
4
|
+
import { getClient } from "../utils/client.js";
|
|
5
|
+
/**
|
|
6
|
+
* List storage objects with pagination
|
|
7
|
+
*/
|
|
8
|
+
export async function listObjects(options) {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
const queryParams = {
|
|
11
|
+
limit: options.limit,
|
|
12
|
+
};
|
|
13
|
+
if (options.startingAfter) {
|
|
14
|
+
queryParams.starting_after = options.startingAfter;
|
|
15
|
+
}
|
|
16
|
+
if (options.name) {
|
|
17
|
+
queryParams.name = options.name;
|
|
18
|
+
}
|
|
19
|
+
if (options.contentType) {
|
|
20
|
+
queryParams.content_type = options.contentType;
|
|
21
|
+
}
|
|
22
|
+
if (options.state) {
|
|
23
|
+
queryParams.state = options.state;
|
|
24
|
+
}
|
|
25
|
+
if (options.isPublic !== undefined) {
|
|
26
|
+
queryParams.is_public = options.isPublic;
|
|
27
|
+
}
|
|
28
|
+
const result = await client.objects.list(queryParams);
|
|
29
|
+
const objects = [];
|
|
30
|
+
if (result.objects && Array.isArray(result.objects)) {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
result.objects.forEach((obj) => {
|
|
33
|
+
// CRITICAL: Truncate all strings to prevent Yoga crashes
|
|
34
|
+
const MAX_ID_LENGTH = 100;
|
|
35
|
+
const MAX_NAME_LENGTH = 200;
|
|
36
|
+
const MAX_CONTENT_TYPE_LENGTH = 100;
|
|
37
|
+
const MAX_STATE_LENGTH = 50;
|
|
38
|
+
objects.push({
|
|
39
|
+
id: String(obj.id || "").substring(0, MAX_ID_LENGTH),
|
|
40
|
+
name: String(obj.name || "").substring(0, MAX_NAME_LENGTH),
|
|
41
|
+
content_type: obj.content_type || "unspecified",
|
|
42
|
+
create_time_ms: obj.create_time_ms || 0,
|
|
43
|
+
state: obj.state || "UPLOADING",
|
|
44
|
+
size_bytes: obj.size_bytes,
|
|
45
|
+
delete_after_time_ms: obj.delete_after_time_ms,
|
|
46
|
+
// UI-specific extended fields
|
|
47
|
+
is_public: obj.is_public,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Access pagination properties from the result
|
|
52
|
+
const pageResult = result;
|
|
53
|
+
return {
|
|
54
|
+
objects,
|
|
55
|
+
totalCount: pageResult.total_count || objects.length,
|
|
56
|
+
hasMore: pageResult.has_more || false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get full object details by ID
|
|
61
|
+
*/
|
|
62
|
+
export async function getObject(id) {
|
|
63
|
+
const client = getClient();
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
|
+
const obj = await client.objects.retrieve(id);
|
|
66
|
+
return {
|
|
67
|
+
id: obj.id,
|
|
68
|
+
name: obj.name || "",
|
|
69
|
+
content_type: obj.content_type || "unspecified",
|
|
70
|
+
create_time_ms: obj.create_time_ms || 0,
|
|
71
|
+
state: obj.state || "UPLOADING",
|
|
72
|
+
size_bytes: obj.size_bytes,
|
|
73
|
+
delete_after_time_ms: obj.delete_after_time_ms,
|
|
74
|
+
// UI-specific extended fields
|
|
75
|
+
is_public: obj.is_public,
|
|
76
|
+
download_url: obj.download_url || undefined,
|
|
77
|
+
metadata: obj.metadata,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Delete an object
|
|
82
|
+
*/
|
|
83
|
+
export async function deleteObject(id) {
|
|
84
|
+
const client = getClient();
|
|
85
|
+
await client.objects.delete(id);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Format file size in human-readable format
|
|
89
|
+
*/
|
|
90
|
+
export function formatFileSize(bytes) {
|
|
91
|
+
if (bytes === undefined || bytes === null)
|
|
92
|
+
return "Unknown";
|
|
93
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
94
|
+
let size = bytes;
|
|
95
|
+
let unitIndex = 0;
|
|
96
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
97
|
+
size /= 1024;
|
|
98
|
+
unitIndex++;
|
|
99
|
+
}
|
|
100
|
+
return `${size.toFixed(unitIndex > 0 ? 2 : 0)} ${units[unitIndex]}`;
|
|
101
|
+
}
|
|
@@ -33,11 +33,14 @@ export async function listSnapshots(options) {
|
|
|
33
33
|
name: snapshotView.name
|
|
34
34
|
? String(snapshotView.name).substring(0, MAX_NAME_LENGTH)
|
|
35
35
|
: undefined,
|
|
36
|
+
create_time_ms: snapshotView.create_time_ms,
|
|
37
|
+
metadata: snapshotView.metadata || {},
|
|
38
|
+
source_devbox_id: String(snapshotView.source_devbox_id || "").substring(0, MAX_ID_LENGTH),
|
|
39
|
+
// UI-specific extended fields
|
|
36
40
|
devbox_id: String(snapshotView.source_devbox_id || "").substring(0, MAX_ID_LENGTH),
|
|
37
41
|
status: snapshotView.status
|
|
38
42
|
? String(snapshotView.status).substring(0, MAX_STATUS_LENGTH)
|
|
39
43
|
: "",
|
|
40
|
-
create_time_ms: snapshotView.create_time_ms,
|
|
41
44
|
});
|
|
42
45
|
});
|
|
43
46
|
}
|
|
@@ -56,6 +59,36 @@ export async function getSnapshotStatus(id) {
|
|
|
56
59
|
const status = await client.devboxes.diskSnapshots.queryStatus(id);
|
|
57
60
|
return status;
|
|
58
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Get full snapshot details by ID
|
|
64
|
+
*/
|
|
65
|
+
export async function getSnapshot(id) {
|
|
66
|
+
const client = getClient();
|
|
67
|
+
const statusResponse = await client.devboxes.diskSnapshots.queryStatus(id);
|
|
68
|
+
// The queryStatus returns a status wrapper with snapshot data inside
|
|
69
|
+
const snapshot = statusResponse.snapshot;
|
|
70
|
+
const operationStatus = statusResponse.status; // 'in_progress', 'error', 'complete', 'deleted'
|
|
71
|
+
if (!snapshot) {
|
|
72
|
+
// If no snapshot data yet, return minimal info based on operation status
|
|
73
|
+
return {
|
|
74
|
+
id: id,
|
|
75
|
+
create_time_ms: Date.now(),
|
|
76
|
+
metadata: {},
|
|
77
|
+
source_devbox_id: "",
|
|
78
|
+
status: operationStatus === "in_progress" ? "pending" : operationStatus,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
id: snapshot.id,
|
|
83
|
+
name: snapshot.name || undefined,
|
|
84
|
+
create_time_ms: snapshot.create_time_ms,
|
|
85
|
+
metadata: snapshot.metadata || {},
|
|
86
|
+
source_devbox_id: snapshot.source_devbox_id || "",
|
|
87
|
+
// UI-specific extended fields
|
|
88
|
+
devbox_id: snapshot.source_devbox_id || undefined,
|
|
89
|
+
status: operationStatus === "complete" ? "ready" : operationStatus,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
59
92
|
/**
|
|
60
93
|
* Create a snapshot
|
|
61
94
|
*/
|
|
@@ -67,9 +100,12 @@ export async function createSnapshot(devboxId, name) {
|
|
|
67
100
|
return {
|
|
68
101
|
id: snapshot.id,
|
|
69
102
|
name: snapshot.name || undefined,
|
|
70
|
-
devbox_id: snapshot.devbox_id || devboxId,
|
|
71
|
-
status: snapshot.status || "pending",
|
|
72
103
|
create_time_ms: snapshot.create_time_ms,
|
|
104
|
+
metadata: snapshot.metadata || {},
|
|
105
|
+
source_devbox_id: snapshot.source_devbox_id || devboxId,
|
|
106
|
+
// UI-specific extended fields
|
|
107
|
+
devbox_id: snapshot.source_devbox_id || devboxId,
|
|
108
|
+
status: "pending",
|
|
73
109
|
};
|
|
74
110
|
}
|
|
75
111
|
/**
|
|
@@ -38,16 +38,10 @@ export const useBlueprintStore = create((set, get) => ({
|
|
|
38
38
|
lastIdCache.delete(oldestKey);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
//
|
|
42
|
-
const plainData = data.map((
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
status: b.status,
|
|
46
|
-
create_time_ms: b.create_time_ms,
|
|
47
|
-
build_status: b.build_status,
|
|
48
|
-
architecture: b.architecture,
|
|
49
|
-
resources: b.resources,
|
|
50
|
-
}));
|
|
41
|
+
// Deep copy all fields to avoid SDK references
|
|
42
|
+
const plainData = data.map((d) => {
|
|
43
|
+
return JSON.parse(JSON.stringify(d));
|
|
44
|
+
});
|
|
51
45
|
pageCache.set(page, plainData);
|
|
52
46
|
lastIdCache.set(page, lastId);
|
|
53
47
|
set({});
|
package/dist/store/index.js
CHANGED
|
@@ -5,3 +5,4 @@ export { useNavigation, useNavigationStore, NavigationProvider, } from "./naviga
|
|
|
5
5
|
export { useDevboxStore } from "./devboxStore.js";
|
|
6
6
|
export { useBlueprintStore } from "./blueprintStore.js";
|
|
7
7
|
export { useSnapshotStore } from "./snapshotStore.js";
|
|
8
|
+
export { useObjectStore } from "./objectStore.js";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network Policy Store - Manages network policy list state, pagination, and caching
|
|
3
|
+
*/
|
|
4
|
+
import { create } from "zustand";
|
|
5
|
+
const MAX_CACHE_SIZE = 10;
|
|
6
|
+
export const useNetworkPolicyStore = create((set, get) => ({
|
|
7
|
+
networkPolicies: [],
|
|
8
|
+
loading: false,
|
|
9
|
+
initialLoading: true,
|
|
10
|
+
error: null,
|
|
11
|
+
currentPage: 0,
|
|
12
|
+
pageSize: 10,
|
|
13
|
+
totalCount: 0,
|
|
14
|
+
hasMore: false,
|
|
15
|
+
pageCache: new Map(),
|
|
16
|
+
lastIdCache: new Map(),
|
|
17
|
+
searchQuery: "",
|
|
18
|
+
selectedIndex: 0,
|
|
19
|
+
setNetworkPolicies: (policies) => set({ networkPolicies: policies }),
|
|
20
|
+
setLoading: (loading) => set({ loading }),
|
|
21
|
+
setInitialLoading: (loading) => set({ initialLoading: loading }),
|
|
22
|
+
setError: (error) => set({ error }),
|
|
23
|
+
setCurrentPage: (page) => set({ currentPage: page }),
|
|
24
|
+
setPageSize: (size) => set({ pageSize: size }),
|
|
25
|
+
setTotalCount: (count) => set({ totalCount: count }),
|
|
26
|
+
setHasMore: (hasMore) => set({ hasMore }),
|
|
27
|
+
setSearchQuery: (query) => set({ searchQuery: query }),
|
|
28
|
+
setSelectedIndex: (index) => set({ selectedIndex: index }),
|
|
29
|
+
cachePageData: (page, data, lastId) => {
|
|
30
|
+
const state = get();
|
|
31
|
+
const pageCache = state.pageCache;
|
|
32
|
+
const lastIdCache = state.lastIdCache;
|
|
33
|
+
// Aggressive LRU eviction
|
|
34
|
+
if (pageCache.size >= MAX_CACHE_SIZE) {
|
|
35
|
+
const oldestKey = pageCache.keys().next().value;
|
|
36
|
+
if (oldestKey !== undefined) {
|
|
37
|
+
pageCache.delete(oldestKey);
|
|
38
|
+
lastIdCache.delete(oldestKey);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Deep copy all fields to avoid SDK references
|
|
42
|
+
const plainData = data.map((d) => {
|
|
43
|
+
return JSON.parse(JSON.stringify(d));
|
|
44
|
+
});
|
|
45
|
+
pageCache.set(page, plainData);
|
|
46
|
+
lastIdCache.set(page, lastId);
|
|
47
|
+
set({});
|
|
48
|
+
},
|
|
49
|
+
getCachedPage: (page) => {
|
|
50
|
+
return get().pageCache.get(page);
|
|
51
|
+
},
|
|
52
|
+
clearCache: () => {
|
|
53
|
+
const state = get();
|
|
54
|
+
state.pageCache.clear();
|
|
55
|
+
state.lastIdCache.clear();
|
|
56
|
+
set({
|
|
57
|
+
pageCache: new Map(),
|
|
58
|
+
lastIdCache: new Map(),
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
clearAll: () => {
|
|
62
|
+
const state = get();
|
|
63
|
+
state.pageCache.clear();
|
|
64
|
+
state.lastIdCache.clear();
|
|
65
|
+
set({
|
|
66
|
+
networkPolicies: [],
|
|
67
|
+
loading: false,
|
|
68
|
+
initialLoading: true,
|
|
69
|
+
error: null,
|
|
70
|
+
currentPage: 0,
|
|
71
|
+
totalCount: 0,
|
|
72
|
+
hasMore: false,
|
|
73
|
+
pageCache: new Map(),
|
|
74
|
+
lastIdCache: new Map(),
|
|
75
|
+
searchQuery: "",
|
|
76
|
+
selectedIndex: 0,
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
getSelectedNetworkPolicy: () => {
|
|
80
|
+
const state = get();
|
|
81
|
+
return state.networkPolicies[state.selectedIndex];
|
|
82
|
+
},
|
|
83
|
+
}));
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object Store - Manages storage object list state, pagination, and caching
|
|
3
|
+
*/
|
|
4
|
+
import { create } from "zustand";
|
|
5
|
+
const MAX_CACHE_SIZE = 10;
|
|
6
|
+
export const useObjectStore = create((set, get) => ({
|
|
7
|
+
objects: [],
|
|
8
|
+
loading: false,
|
|
9
|
+
initialLoading: true,
|
|
10
|
+
error: null,
|
|
11
|
+
currentPage: 0,
|
|
12
|
+
pageSize: 10,
|
|
13
|
+
totalCount: 0,
|
|
14
|
+
hasMore: false,
|
|
15
|
+
pageCache: new Map(),
|
|
16
|
+
lastIdCache: new Map(),
|
|
17
|
+
nameFilter: undefined,
|
|
18
|
+
contentTypeFilter: undefined,
|
|
19
|
+
stateFilter: undefined,
|
|
20
|
+
isPublicFilter: undefined,
|
|
21
|
+
selectedIndex: 0,
|
|
22
|
+
setObjects: (objects) => set({ objects }),
|
|
23
|
+
setLoading: (loading) => set({ loading }),
|
|
24
|
+
setInitialLoading: (loading) => set({ initialLoading: loading }),
|
|
25
|
+
setError: (error) => set({ error }),
|
|
26
|
+
setCurrentPage: (page) => set({ currentPage: page }),
|
|
27
|
+
setPageSize: (size) => set({ pageSize: size }),
|
|
28
|
+
setTotalCount: (count) => set({ totalCount: count }),
|
|
29
|
+
setHasMore: (hasMore) => set({ hasMore }),
|
|
30
|
+
setNameFilter: (name) => set({ nameFilter: name }),
|
|
31
|
+
setContentTypeFilter: (contentType) => set({ contentTypeFilter: contentType }),
|
|
32
|
+
setStateFilter: (state) => set({ stateFilter: state }),
|
|
33
|
+
setIsPublicFilter: (isPublic) => set({ isPublicFilter: isPublic }),
|
|
34
|
+
setSelectedIndex: (index) => set({ selectedIndex: index }),
|
|
35
|
+
cachePageData: (page, data, lastId) => {
|
|
36
|
+
const state = get();
|
|
37
|
+
const pageCache = state.pageCache;
|
|
38
|
+
const lastIdCache = state.lastIdCache;
|
|
39
|
+
// Aggressive LRU eviction
|
|
40
|
+
if (pageCache.size >= MAX_CACHE_SIZE) {
|
|
41
|
+
const oldestKey = pageCache.keys().next().value;
|
|
42
|
+
if (oldestKey !== undefined) {
|
|
43
|
+
pageCache.delete(oldestKey);
|
|
44
|
+
lastIdCache.delete(oldestKey);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Deep copy all fields to avoid SDK references
|
|
48
|
+
const plainData = data.map((d) => {
|
|
49
|
+
return JSON.parse(JSON.stringify(d));
|
|
50
|
+
});
|
|
51
|
+
pageCache.set(page, plainData);
|
|
52
|
+
lastIdCache.set(page, lastId);
|
|
53
|
+
set({});
|
|
54
|
+
},
|
|
55
|
+
getCachedPage: (page) => {
|
|
56
|
+
return get().pageCache.get(page);
|
|
57
|
+
},
|
|
58
|
+
clearCache: () => {
|
|
59
|
+
const state = get();
|
|
60
|
+
state.pageCache.clear();
|
|
61
|
+
state.lastIdCache.clear();
|
|
62
|
+
set({
|
|
63
|
+
pageCache: new Map(),
|
|
64
|
+
lastIdCache: new Map(),
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
clearAll: () => {
|
|
68
|
+
const state = get();
|
|
69
|
+
state.pageCache.clear();
|
|
70
|
+
state.lastIdCache.clear();
|
|
71
|
+
set({
|
|
72
|
+
objects: [],
|
|
73
|
+
loading: false,
|
|
74
|
+
initialLoading: true,
|
|
75
|
+
error: null,
|
|
76
|
+
currentPage: 0,
|
|
77
|
+
totalCount: 0,
|
|
78
|
+
hasMore: false,
|
|
79
|
+
pageCache: new Map(),
|
|
80
|
+
lastIdCache: new Map(),
|
|
81
|
+
nameFilter: undefined,
|
|
82
|
+
contentTypeFilter: undefined,
|
|
83
|
+
stateFilter: undefined,
|
|
84
|
+
isPublicFilter: undefined,
|
|
85
|
+
selectedIndex: 0,
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
getSelectedObject: () => {
|
|
89
|
+
const state = get();
|
|
90
|
+
return state.objects[state.selectedIndex];
|
|
91
|
+
},
|
|
92
|
+
}));
|
|
@@ -38,14 +38,10 @@ export const useSnapshotStore = create((set, get) => ({
|
|
|
38
38
|
lastIdCache.delete(oldestKey);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
//
|
|
42
|
-
const plainData = data.map((
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
devbox_id: s.devbox_id,
|
|
46
|
-
status: s.status,
|
|
47
|
-
create_time_ms: s.create_time_ms,
|
|
48
|
-
}));
|
|
41
|
+
// Deep copy all fields to avoid SDK references
|
|
42
|
+
const plainData = data.map((d) => {
|
|
43
|
+
return JSON.parse(JSON.stringify(d));
|
|
44
|
+
});
|
|
49
45
|
pageCache.set(page, plainData);
|
|
50
46
|
lastIdCache.set(page, lastId);
|
|
51
47
|
set({});
|
package/dist/utils/commands.js
CHANGED
|
@@ -38,6 +38,7 @@ export function createProgram() {
|
|
|
38
38
|
.option("--available-ports <ports...>", "Available ports")
|
|
39
39
|
.option("--root", "Run as root")
|
|
40
40
|
.option("--user <user:uid>", "Run as this user (format: username:uid)")
|
|
41
|
+
.option("--network-policy <id>", "Network policy ID to apply")
|
|
41
42
|
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
42
43
|
.action(createDevbox);
|
|
43
44
|
devbox
|
|
@@ -311,6 +312,24 @@ export function createProgram() {
|
|
|
311
312
|
const { pruneBlueprints } = await import("../commands/blueprint/prune.js");
|
|
312
313
|
await pruneBlueprints(name, options);
|
|
313
314
|
});
|
|
315
|
+
blueprint
|
|
316
|
+
.command("from-dockerfile")
|
|
317
|
+
.description("Create a blueprint from a Dockerfile with build context support")
|
|
318
|
+
.requiredOption("--name <name>", "Blueprint name (required)")
|
|
319
|
+
.option("--build-context <path>", "Build context directory (default: current directory)")
|
|
320
|
+
.option("--dockerfile <path>", "Dockerfile path (default: Dockerfile in build context)")
|
|
321
|
+
.option("--system-setup-commands <commands...>", "System setup commands")
|
|
322
|
+
.option("--resources <size>", "Resource size (X_SMALL, SMALL, MEDIUM, LARGE, X_LARGE, XX_LARGE)")
|
|
323
|
+
.option("--architecture <arch>", "Architecture (arm64, x86_64)")
|
|
324
|
+
.option("--available-ports <ports...>", "Available ports")
|
|
325
|
+
.option("--root", "Run as root")
|
|
326
|
+
.option("--user <user:uid>", "Run as this user (format: username:uid)")
|
|
327
|
+
.option("--ttl <seconds>", "TTL in seconds for the build context object (default: 3600)")
|
|
328
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
329
|
+
.action(async (options) => {
|
|
330
|
+
const { createBlueprintFromDockerfile } = await import("../commands/blueprint/from-dockerfile.js");
|
|
331
|
+
await createBlueprintFromDockerfile(options);
|
|
332
|
+
});
|
|
314
333
|
// Object storage commands
|
|
315
334
|
const object = program
|
|
316
335
|
.command("object")
|
|
@@ -374,6 +393,52 @@ export function createProgram() {
|
|
|
374
393
|
const { deleteObject } = await import("../commands/object/delete.js");
|
|
375
394
|
await deleteObject({ id, ...options });
|
|
376
395
|
});
|
|
396
|
+
// Network policy commands
|
|
397
|
+
const networkPolicy = program
|
|
398
|
+
.command("network-policy")
|
|
399
|
+
.description("Manage network policies")
|
|
400
|
+
.alias("np");
|
|
401
|
+
networkPolicy
|
|
402
|
+
.command("list")
|
|
403
|
+
.description("List network policies")
|
|
404
|
+
.option("--limit <n>", "Max results", "20")
|
|
405
|
+
.option("--starting-after <id>", "Starting point for pagination")
|
|
406
|
+
.option("--name <name>", "Filter by name")
|
|
407
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
408
|
+
.action(async (options) => {
|
|
409
|
+
const { listNetworkPolicies } = await import("../commands/network-policy/list.js");
|
|
410
|
+
await listNetworkPolicies(options);
|
|
411
|
+
});
|
|
412
|
+
networkPolicy
|
|
413
|
+
.command("get <id>")
|
|
414
|
+
.description("Get network policy details")
|
|
415
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: json)")
|
|
416
|
+
.action(async (id, options) => {
|
|
417
|
+
const { getNetworkPolicy } = await import("../commands/network-policy/get.js");
|
|
418
|
+
await getNetworkPolicy({ id, ...options });
|
|
419
|
+
});
|
|
420
|
+
networkPolicy
|
|
421
|
+
.command("create")
|
|
422
|
+
.description("Create a new network policy")
|
|
423
|
+
.requiredOption("--name <name>", "Policy name (required)")
|
|
424
|
+
.option("--description <description>", "Policy description")
|
|
425
|
+
.option("--allow-all", "Allow all egress traffic")
|
|
426
|
+
.option("--allow-devbox-to-devbox", "Allow devbox-to-devbox communication")
|
|
427
|
+
.option("--allowed-hostnames <hostnames...>", "List of allowed hostnames for egress")
|
|
428
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
429
|
+
.action(async (options) => {
|
|
430
|
+
const { createNetworkPolicy } = await import("../commands/network-policy/create.js");
|
|
431
|
+
await createNetworkPolicy(options);
|
|
432
|
+
});
|
|
433
|
+
networkPolicy
|
|
434
|
+
.command("delete <id>")
|
|
435
|
+
.description("Delete a network policy")
|
|
436
|
+
.alias("rm")
|
|
437
|
+
.option("-o, --output [format]", "Output format: text|json|yaml (default: text)")
|
|
438
|
+
.action(async (id, options) => {
|
|
439
|
+
const { deleteNetworkPolicy } = await import("../commands/network-policy/delete.js");
|
|
440
|
+
await deleteNetworkPolicy(id, options);
|
|
441
|
+
});
|
|
377
442
|
// MCP server commands
|
|
378
443
|
const mcp = program
|
|
379
444
|
.command("mcp")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runloop/rl-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Beautiful CLI for the Runloop platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
70
|
-
"@runloop/api-client": "
|
|
70
|
+
"@runloop/api-client": "1.3.0",
|
|
71
71
|
"@types/express": "^5.0.3",
|
|
72
72
|
"chalk": "^5.3.0",
|
|
73
73
|
"commander": "^14.0.1",
|