@exagone313/dsh-podman 0.2.0-rc.4 → 0.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 +12 -0
- package/README.zh.md +14 -2
- package/cordis.patch.yml +58 -0
- package/dist/approval-reasons.d.ts +3 -4
- package/dist/approval-reasons.js +40 -49
- package/dist/approval-reasons.test.js +43 -15
- package/dist/approval.js +27 -10
- package/dist/approval.test.js +165 -34
- package/dist/card-route.d.ts +3 -2
- package/dist/card-route.js +103 -35
- package/dist/card-route.test.js +60 -43
- package/dist/card-test-support.js +39 -66
- package/dist/client/ContainerCard.d.ts +1 -1
- package/dist/client/ContainerCard.js +76 -70
- package/dist/client/card-protocol.d.ts +3 -3
- package/dist/client/container-card-caches.js +11 -6
- package/dist/client/container-card-controller.d.ts +13 -18
- package/dist/client/container-card-controller.js +86 -40
- package/dist/client/container-card-create-modal.d.ts +2 -2
- package/dist/client/container-card-create-modal.js +13 -5
- package/dist/client/container-card-default-env.d.ts +10 -0
- package/dist/client/container-card-default-env.js +69 -0
- package/dist/client/container-card-directory.d.ts +2 -2
- package/dist/client/container-card-directory.js +12 -4
- package/dist/client/container-card-editors.d.ts +3 -3
- package/dist/client/container-card-editors.js +104 -32
- package/dist/client/container-card-images.d.ts +4 -4
- package/dist/client/container-card-images.js +19 -6
- package/dist/client/container-card-paths.d.ts +3 -3
- package/dist/client/container-card-paths.js +18 -11
- package/dist/client/container-card-row.d.ts +10 -6
- package/dist/client/container-card-row.js +82 -13
- package/dist/client/container-card-secrets.d.ts +3 -3
- package/dist/client/container-card-secrets.js +8 -6
- package/dist/client/container-card-shared.d.ts +4 -17
- package/dist/client/container-card-shared.js +9 -25
- package/dist/client/container-card-styles.d.ts +2 -8
- package/dist/client/container-card-styles.js +14 -52
- package/dist/client/container-card-volumes.d.ts +2 -2
- package/dist/client/container-card-volumes.js +9 -5
- package/dist/client/container-card-workspace.d.ts +8 -6
- package/dist/client/container-card-workspace.js +9 -3
- package/dist/client/directory-picker.js +2 -1
- package/dist/client/index.js +13109 -2117
- package/dist/client/locales.d.ts +6 -1
- package/dist/client/locales.js +88 -38
- package/dist/client/podman-terminal-guide.d.ts +11 -0
- package/dist/client/podman-terminal-guide.js +208 -0
- package/dist/client/podman-terminal-title.d.ts +7 -0
- package/dist/client/podman-terminal-title.js +30 -0
- package/dist/client/podman-terminal.d.ts +26 -0
- package/dist/client/podman-terminal.js +412 -0
- package/dist/client/read-only-approval.d.ts +2 -2
- package/dist/client/read-only-approval.js +13 -3
- package/dist/client/slot-contract.d.ts +0 -11
- package/dist/client/terminal-preference.d.ts +26 -0
- package/dist/client/terminal-preference.js +67 -0
- package/dist/client/terminal-protocol.d.ts +85 -0
- package/dist/client/terminal-protocol.js +14 -0
- package/dist/client/terminal-tab.d.ts +60 -0
- package/dist/client/terminal-tab.js +35 -0
- package/dist/client/terminal-targets.d.ts +33 -0
- package/dist/client/terminal-targets.js +65 -0
- package/dist/client/terminal-titles.d.ts +23 -0
- package/dist/client/terminal-titles.js +52 -0
- package/dist/client/terminal-transport.d.ts +63 -0
- package/dist/client/terminal-transport.js +207 -0
- package/dist/client/tool-views.js +215 -49
- package/dist/container-env.d.ts +11 -0
- package/dist/container-env.js +96 -0
- package/dist/container-env.test.d.ts +1 -0
- package/dist/container-env.test.js +93 -0
- package/dist/containers.test.js +196 -18
- package/dist/daemons.test.js +18 -4
- package/dist/dsh-version.js +2 -1
- package/dist/env-rows.d.ts +14 -0
- package/dist/env-rows.js +52 -0
- package/dist/env-rows.test.d.ts +1 -0
- package/dist/env-rows.test.js +43 -0
- package/dist/fs-provider.d.ts +1 -0
- package/dist/fs-provider.js +26 -12
- package/dist/fs-provider.test.js +51 -10
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +2 -2
- package/dist/grpc/proto/dshctl/v1/control.proto +8 -1
- package/dist/grpc/proto/dshguest/v1/guest.proto +5 -0
- package/dist/grpc/runtime-client.d.ts +3 -1
- package/dist/grpc/runtime-client.js +90 -17
- package/dist/guest-rpc.d.ts +15 -19
- package/dist/guest-rpc.js +289 -70
- package/dist/guest-rpc.test.d.ts +1 -0
- package/dist/guest-rpc.test.js +241 -0
- package/dist/guest-terminal.d.ts +30 -0
- package/dist/guest-terminal.js +196 -0
- package/dist/images.test.js +10 -3
- package/dist/index.d.ts +11 -16
- package/dist/index.js +42 -36
- package/dist/locales.test.d.ts +1 -0
- package/dist/locales.test.js +34 -0
- package/dist/misc.test.js +38 -10
- package/dist/mount-enums.js +2 -1
- package/dist/mount-input.d.ts +2 -0
- package/dist/mount-input.js +38 -22
- package/dist/mounts.test.js +81 -11
- package/dist/naming.test.d.ts +1 -0
- package/dist/naming.test.js +28 -0
- package/dist/output-reader.js +36 -4
- package/dist/package-deps.test.d.ts +1 -0
- package/dist/package-deps.test.js +49 -0
- package/dist/paths.test.js +4 -2
- package/dist/plugin-meta.test.d.ts +1 -0
- package/dist/plugin-meta.test.js +29 -0
- package/dist/project-path.js +33 -7
- package/dist/project-path.test.js +14 -0
- package/dist/prompts.d.ts +0 -4
- package/dist/prompts.js +15 -84
- package/dist/read-only-shell.js +3 -1
- package/dist/read-only-shell.test.js +80 -25
- package/dist/runtime-client.test.d.ts +1 -0
- package/dist/runtime-client.test.js +53 -0
- package/dist/secrets.test.js +21 -6
- package/dist/settings-commands.test.js +61 -14
- package/dist/settings-create.test.js +32 -10
- package/dist/settings-default-env.test.d.ts +1 -0
- package/dist/settings-default-env.test.js +175 -0
- package/dist/settings-mounts.test.js +46 -7
- package/dist/settings-schema.d.ts +6 -11
- package/dist/settings-schema.js +4 -8
- package/dist/settings-schema.test.d.ts +1 -0
- package/dist/settings-schema.test.js +35 -0
- package/dist/settings-workspaces.test.js +36 -63
- package/dist/spill-store.d.ts +1 -0
- package/dist/spill-store.js +17 -4
- package/dist/spill-store.test.js +31 -8
- package/dist/subprocess.d.ts +4 -0
- package/dist/subprocess.js +148 -162
- package/dist/subprocess.test.js +118 -8
- package/dist/terminal-preference.test.d.ts +1 -0
- package/dist/terminal-preference.test.js +62 -0
- package/dist/terminal-route.d.ts +10 -0
- package/dist/terminal-route.js +270 -0
- package/dist/terminal-route.test.d.ts +1 -0
- package/dist/terminal-route.test.js +265 -0
- package/dist/terminal-sessions.d.ts +69 -0
- package/dist/terminal-sessions.js +252 -0
- package/dist/terminal-shells.d.ts +22 -0
- package/dist/terminal-shells.js +99 -0
- package/dist/terminal-tab.test.d.ts +1 -0
- package/dist/terminal-tab.test.js +58 -0
- package/dist/terminal-targets.test.d.ts +1 -0
- package/dist/terminal-targets.test.js +43 -0
- package/dist/terminal-titles.test.d.ts +1 -0
- package/dist/terminal-titles.test.js +36 -0
- package/dist/test-support.d.ts +35 -5
- package/dist/test-support.js +91 -29
- package/dist/tool-defs.js +19 -4
- package/dist/tool-handlers.js +80 -28
- package/dist/tool-params.js +19 -5
- package/dist/volumes.test.js +1 -1
- package/dist/workspace-binding.d.ts +18 -9
- package/dist/workspace-binding.js +244 -77
- package/dist/workspace-binding.test.js +189 -25
- package/docs/architecture.md +62 -21
- package/docs/architecture.zh.md +57 -13
- package/docs/configuration.md +61 -20
- package/docs/configuration.zh.md +53 -19
- package/docs/development-prompt.md +72 -0
- package/docs/development-prompt.zh.md +69 -0
- package/docs/development.md +141 -21
- package/docs/development.zh.md +125 -17
- package/docs/install-dsh-and-dsh-podman.md +21 -8
- package/docs/install-dsh-and-dsh-podman.zh.md +18 -8
- package/docs/uninstall.md +83 -0
- package/docs/uninstall.zh.md +79 -0
- package/docs/update.md +120 -0
- package/docs/update.zh.md +114 -0
- package/docs/usage.md +108 -61
- package/docs/usage.zh.md +76 -38
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +59 -26
- package/quadlet/dsh-podman-orchestrator.container +46 -0
- package/quadlet/dsh.container +35 -0
- package/LICENSE.pkg +0 -17103
- package/dist/preferences.d.ts +0 -6
- package/dist/preferences.js +0 -27
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type SnapshotStore } from "@deepseek-ai/dsh-client-store";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ConfigForm, ConfigFormSnapshot } from "@deepseek-ai/dsh-client-ui-settings/client";
|
|
3
3
|
import type { CacheView, ContainerView, ImageView, MountInput, SecretView, VolumeView, WorkspaceView } from "./card-protocol.js";
|
|
4
4
|
import { type CardClient } from "./card-client.js";
|
|
5
5
|
import { type DirectoryPickerFace } from "./directory-picker.js";
|
|
6
6
|
export declare const CONTAINER_NS = "podman";
|
|
7
7
|
export interface ContainerSettings {
|
|
8
8
|
defaultImage: string;
|
|
9
|
-
socketsRoot: string;
|
|
10
9
|
uiLocale?: string;
|
|
10
|
+
containerEnv?: Record<string, string>;
|
|
11
11
|
}
|
|
12
12
|
export interface ProjectMountView {
|
|
13
13
|
projectName: string;
|
|
@@ -36,9 +36,7 @@ export interface CardState {
|
|
|
36
36
|
orchestratorVersion: string;
|
|
37
37
|
versionState: "ok" | "minor-mismatch" | "major-mismatch";
|
|
38
38
|
defaultImage: string;
|
|
39
|
-
|
|
40
|
-
socketsRoot: string;
|
|
41
|
-
socketsRootDraft: string;
|
|
39
|
+
containerEnv: Record<string, string>;
|
|
42
40
|
projectsRoot: string;
|
|
43
41
|
directoryPicker?: DirectoryPickerFace;
|
|
44
42
|
workspaces: readonly WorkspaceView[];
|
|
@@ -53,10 +51,10 @@ export interface ContainerCardFace {
|
|
|
53
51
|
containerCard: SnapshotStore<CardState>;
|
|
54
52
|
};
|
|
55
53
|
reload: () => void;
|
|
56
|
-
remove: (workspace: string) => void;
|
|
54
|
+
remove: (workspace: string, container: string) => void;
|
|
57
55
|
cleanCaches: (mode: string) => void;
|
|
58
56
|
removeWorkspace: (workspace: string) => void;
|
|
59
|
-
recreate: (workspace: string, image: string, env?: Record<string, string>) => void;
|
|
57
|
+
recreate: (workspace: string, container: string, image: string, env?: Record<string, string>) => void;
|
|
60
58
|
createContainer: (workspace: WorkspaceView, config?: ContainerCreateConfig) => void;
|
|
61
59
|
startContainer: (workspace: WorkspaceView, container: string, config?: ContainerCreateConfig) => void;
|
|
62
60
|
addContainerMount: (workspace: string, container: string, mount: MountInput) => void;
|
|
@@ -75,14 +73,10 @@ export interface ContainerCardFace {
|
|
|
75
73
|
createSecret: (name: string, length?: number, charset?: string) => void;
|
|
76
74
|
removeSecret: (name: string) => void;
|
|
77
75
|
setSecret: (name: string, value: string) => void;
|
|
78
|
-
addContainerSecret: (workspace: string, envVar: string, secret: string) => void;
|
|
79
|
-
removeContainerSecret: (workspace: string, envVar: string) => void;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
discardDefaultImage: () => void;
|
|
83
|
-
editSocketsRoot: (text: string) => void;
|
|
84
|
-
saveSocketsRoot: () => void;
|
|
85
|
-
discardSocketsRoot: () => void;
|
|
76
|
+
addContainerSecret: (workspace: string, container: string, envVar: string, secret: string) => void;
|
|
77
|
+
removeContainerSecret: (workspace: string, container: string, envVar: string) => void;
|
|
78
|
+
saveContainerEnv: (env: Record<string, string>) => void;
|
|
79
|
+
syncDefaultEnv: (workspace: string) => void;
|
|
86
80
|
}
|
|
87
81
|
export declare class ContainerCardController {
|
|
88
82
|
private readonly scope;
|
|
@@ -90,10 +84,11 @@ export declare class ContainerCardController {
|
|
|
90
84
|
private readonly drafts;
|
|
91
85
|
private readonly client;
|
|
92
86
|
private snapshot;
|
|
93
|
-
private
|
|
87
|
+
private pending;
|
|
88
|
+
private generation;
|
|
94
89
|
private notice;
|
|
95
90
|
private directoryPicker;
|
|
96
|
-
constructor(scope:
|
|
91
|
+
constructor(scope: ConfigForm<ContainerSettings>, client?: CardClient);
|
|
97
92
|
private draft;
|
|
98
93
|
private project;
|
|
99
94
|
private publish;
|
|
@@ -107,4 +102,4 @@ export declare class ContainerCardController {
|
|
|
107
102
|
inject(): ContainerCardFace;
|
|
108
103
|
private defaultImage;
|
|
109
104
|
}
|
|
110
|
-
export type {
|
|
105
|
+
export type { ConfigFormSnapshot };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
|
-
import { createSnapshotStore } from "@deepseek-ai/dsh-client-store";
|
|
4
|
+
import { createSnapshotStore, } from "@deepseek-ai/dsh-client-store";
|
|
5
5
|
import { createCardClient } from "./card-client.js";
|
|
6
6
|
export const CONTAINER_NS = "podman";
|
|
7
7
|
const EMPTY_SNAPSHOT = {
|
|
@@ -27,7 +27,10 @@ export class ContainerCardController {
|
|
|
27
27
|
drafts = new Map();
|
|
28
28
|
client;
|
|
29
29
|
snapshot = EMPTY_SNAPSHOT;
|
|
30
|
-
busy
|
|
30
|
+
// Counts in-flight commands, so overlapping ones cannot clear the busy state
|
|
31
|
+
// early; generation makes a superseded response drop its stale snapshot.
|
|
32
|
+
pending = 0;
|
|
33
|
+
generation = 0;
|
|
31
34
|
notice = "";
|
|
32
35
|
directoryPicker;
|
|
33
36
|
constructor(scope, client = createCardClient()) {
|
|
@@ -48,7 +51,7 @@ export class ContainerCardController {
|
|
|
48
51
|
return {
|
|
49
52
|
available: snapshot.status === "ready" && value !== undefined,
|
|
50
53
|
writable: snapshot.writable,
|
|
51
|
-
busy: this.
|
|
54
|
+
busy: this.pending > 0,
|
|
52
55
|
notice: this.notice,
|
|
53
56
|
version: this.snapshot.version,
|
|
54
57
|
commit: this.snapshot.commit,
|
|
@@ -56,9 +59,7 @@ export class ContainerCardController {
|
|
|
56
59
|
orchestratorVersion: this.snapshot.orchestratorVersion,
|
|
57
60
|
versionState: this.snapshot.versionState,
|
|
58
61
|
defaultImage: value?.defaultImage ?? "",
|
|
59
|
-
|
|
60
|
-
socketsRoot: value?.socketsRoot ?? "",
|
|
61
|
-
socketsRootDraft: this.draft("socketsRoot", value?.socketsRoot ?? ""),
|
|
62
|
+
containerEnv: value?.containerEnv ?? {},
|
|
62
63
|
projectsRoot: this.snapshot.projectsRoot,
|
|
63
64
|
...(this.directoryPicker === undefined
|
|
64
65
|
? {}
|
|
@@ -92,43 +93,62 @@ export class ContainerCardController {
|
|
|
92
93
|
env: extra.env ?? {},
|
|
93
94
|
container: extra.container ?? "",
|
|
94
95
|
secret: extra.secret ?? "",
|
|
95
|
-
|
|
96
|
+
secretEnvName: extra.secretEnvName ?? "",
|
|
96
97
|
length: extra.length ?? 0,
|
|
97
98
|
charset: extra.charset ?? "",
|
|
98
99
|
packages: extra.packages ?? [],
|
|
99
|
-
|
|
100
|
+
secretEnv: extra.secretEnv ?? {},
|
|
100
101
|
cacheMode: extra.cacheMode ?? "",
|
|
101
102
|
mount: extra.mount ?? null,
|
|
102
103
|
});
|
|
103
104
|
}
|
|
104
105
|
async dispatch(request) {
|
|
105
|
-
|
|
106
|
+
const generation = ++this.generation;
|
|
107
|
+
this.pending++;
|
|
106
108
|
this.notice = "";
|
|
107
109
|
this.publish();
|
|
108
110
|
try {
|
|
109
111
|
const result = await this.client.command(request);
|
|
112
|
+
const snapshot = await this.client.snapshot();
|
|
113
|
+
// A reload (or another command) started after this one wins: applying
|
|
114
|
+
// this response would overwrite the fresher state with a stale snapshot.
|
|
115
|
+
if (generation !== this.generation)
|
|
116
|
+
return;
|
|
110
117
|
this.notice = result.notice ?? "";
|
|
111
|
-
this.snapshot =
|
|
118
|
+
this.snapshot = snapshot;
|
|
112
119
|
}
|
|
113
120
|
catch (error) {
|
|
114
|
-
|
|
121
|
+
if (generation === this.generation) {
|
|
122
|
+
this.notice = error instanceof Error ? error.message : String(error);
|
|
123
|
+
}
|
|
115
124
|
}
|
|
116
125
|
finally {
|
|
117
|
-
this.
|
|
126
|
+
this.pending--;
|
|
118
127
|
this.publish();
|
|
119
128
|
}
|
|
120
129
|
}
|
|
121
130
|
async reload() {
|
|
131
|
+
const generation = ++this.generation;
|
|
132
|
+
this.pending++;
|
|
133
|
+
this.publish();
|
|
122
134
|
try {
|
|
123
|
-
|
|
135
|
+
const snapshot = await this.client.snapshot();
|
|
136
|
+
if (generation !== this.generation)
|
|
137
|
+
return;
|
|
138
|
+
this.snapshot = snapshot;
|
|
124
139
|
// A refresh clears the previous action's notice, so reopening the
|
|
125
140
|
// settings does not replay feedback from an earlier session.
|
|
126
141
|
this.notice = "";
|
|
127
142
|
}
|
|
128
143
|
catch (error) {
|
|
129
|
-
|
|
144
|
+
if (generation === this.generation) {
|
|
145
|
+
this.notice = error instanceof Error ? error.message : String(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
this.pending--;
|
|
150
|
+
this.publish();
|
|
130
151
|
}
|
|
131
|
-
this.publish();
|
|
132
152
|
}
|
|
133
153
|
edit(field, text) {
|
|
134
154
|
this.drafts.set(field, text);
|
|
@@ -150,27 +170,55 @@ export class ContainerCardController {
|
|
|
150
170
|
reload: () => {
|
|
151
171
|
void this.reload();
|
|
152
172
|
},
|
|
153
|
-
remove: (workspace) => this.command("remove", workspace, ""),
|
|
173
|
+
remove: (workspace, container) => this.command("remove", workspace, "", { container }),
|
|
154
174
|
cleanCaches: (mode) => this.command("cache_clean", "", "", { cacheMode: mode }),
|
|
155
175
|
removeWorkspace: (workspace) => this.command("workspace_remove", workspace, ""),
|
|
156
|
-
recreate: (workspace, image, env) => this.command("recreate", workspace, image, {
|
|
176
|
+
recreate: (workspace, container, image, env) => this.command("recreate", workspace, image, {
|
|
177
|
+
container,
|
|
178
|
+
...(env ? { env } : {}),
|
|
179
|
+
}),
|
|
157
180
|
createContainer: (workspace, config) => this.command("create", workspace.workspaceSlug, config?.image ?? (workspace.imageId || this.defaultImage() || ""), {
|
|
158
181
|
projectName: workspace.projectName,
|
|
159
|
-
...(config?.mounts && config.mounts.length > 0
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
...(config?.
|
|
182
|
+
...(config?.mounts && config.mounts.length > 0
|
|
183
|
+
? { mounts: config.mounts }
|
|
184
|
+
: {}),
|
|
185
|
+
...(config?.env && Object.keys(config.env).length > 0
|
|
186
|
+
? { env: config.env }
|
|
187
|
+
: {}),
|
|
188
|
+
...(config?.paths && config.paths.length > 0
|
|
189
|
+
? { paths: config.paths }
|
|
190
|
+
: {}),
|
|
191
|
+
...(config?.secretEnv && Object.keys(config.secretEnv).length > 0
|
|
192
|
+
? { secretEnv: config.secretEnv }
|
|
193
|
+
: {}),
|
|
163
194
|
}),
|
|
164
195
|
startContainer: (workspace, container, config) => this.command("create", workspace.workspaceSlug, config?.image ?? (workspace.imageId || this.defaultImage() || ""), {
|
|
165
196
|
container,
|
|
166
|
-
...(config?.mounts && config.mounts.length > 0
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
...(config?.
|
|
197
|
+
...(config?.mounts && config.mounts.length > 0
|
|
198
|
+
? { mounts: config.mounts }
|
|
199
|
+
: {}),
|
|
200
|
+
...(config?.env && Object.keys(config.env).length > 0
|
|
201
|
+
? { env: config.env }
|
|
202
|
+
: {}),
|
|
203
|
+
...(config?.paths && config.paths.length > 0
|
|
204
|
+
? { paths: config.paths }
|
|
205
|
+
: {}),
|
|
206
|
+
...(config?.secretEnv && Object.keys(config.secretEnv).length > 0
|
|
207
|
+
? { secretEnv: config.secretEnv }
|
|
208
|
+
: {}),
|
|
209
|
+
}),
|
|
210
|
+
addContainerMount: (workspace, container, mount) => this.command("container_mount_add", workspace, "", {
|
|
211
|
+
container,
|
|
212
|
+
mount,
|
|
213
|
+
}),
|
|
214
|
+
removeContainerMount: (workspace, container, mount) => this.command("container_mount_remove", workspace, "", {
|
|
215
|
+
container,
|
|
216
|
+
mount,
|
|
217
|
+
}),
|
|
218
|
+
updateContainerMount: (workspace, container, mount) => this.command("container_mount_update", workspace, "", {
|
|
219
|
+
container,
|
|
220
|
+
mount,
|
|
170
221
|
}),
|
|
171
|
-
addContainerMount: (workspace, container, mount) => this.command("container_mount_add", workspace, "", { container, mount }),
|
|
172
|
-
removeContainerMount: (workspace, container, mount) => this.command("container_mount_remove", workspace, "", { container, mount }),
|
|
173
|
-
updateContainerMount: (workspace, container, mount) => this.command("container_mount_update", workspace, "", { container, mount }),
|
|
174
222
|
setContainerPaths: (workspace, container, paths) => this.command("container_path_set", workspace, "", { container, paths }),
|
|
175
223
|
createVolume: (name) => this.command("volume_create", name, ""),
|
|
176
224
|
removeVolume: (name) => this.command("volume_remove", name, ""),
|
|
@@ -190,21 +238,19 @@ export class ContainerCardController {
|
|
|
190
238
|
}),
|
|
191
239
|
removeSecret: (name) => this.command("secret_remove", name, ""),
|
|
192
240
|
setSecret: (name, value) => this.command("secret_set", name, "", { value }),
|
|
193
|
-
addContainerSecret: (workspace, envVar, secret) => this.command("container_secret_add", workspace, "", {
|
|
194
|
-
container
|
|
195
|
-
|
|
241
|
+
addContainerSecret: (workspace, container, envVar, secret) => this.command("container_secret_add", workspace, "", {
|
|
242
|
+
container,
|
|
243
|
+
secretEnvName: envVar,
|
|
196
244
|
secret,
|
|
197
245
|
}),
|
|
198
|
-
removeContainerSecret: (workspace, envVar) => this.command("container_secret_remove", workspace, "", {
|
|
199
|
-
container
|
|
200
|
-
|
|
246
|
+
removeContainerSecret: (workspace, container, envVar) => this.command("container_secret_remove", workspace, "", {
|
|
247
|
+
container,
|
|
248
|
+
secretEnvName: envVar,
|
|
201
249
|
}),
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
saveSocketsRoot: () => this.save("socketsRoot"),
|
|
207
|
-
discardSocketsRoot: () => this.discard("socketsRoot"),
|
|
250
|
+
saveContainerEnv: (env) => {
|
|
251
|
+
void this.scope.set("containerEnv", env);
|
|
252
|
+
},
|
|
253
|
+
syncDefaultEnv: (workspace) => this.command("default_env_sync", workspace, ""),
|
|
208
254
|
};
|
|
209
255
|
}
|
|
210
256
|
defaultImage() {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type ContainerCreateConfig, type ImageView, type WorkspaceView } from "./container-card-controller.js";
|
|
2
2
|
import { type DirectoryPickerFace } from "./directory-picker.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { Translate } from "./locales.js";
|
|
4
4
|
import { type ReactNode } from "react";
|
|
5
5
|
export declare function CreateContainerModal(props: {
|
|
6
|
-
t:
|
|
6
|
+
t: Translate;
|
|
7
7
|
workspace: WorkspaceView;
|
|
8
8
|
images: readonly ImageView[];
|
|
9
9
|
volumes: readonly {
|
|
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { EnvEditor, MountsEditor } from "./container-card-editors.js";
|
|
3
3
|
import { PathsList } from "./container-card-paths.js";
|
|
4
4
|
import { Field, namePattern, sanitizeName } from "./container-card-shared.js";
|
|
5
|
-
import { greyId, hint, imageSelect, sectionTitle } from "./container-card-styles.js";
|
|
5
|
+
import { greyId, hint, imageSelect, sectionTitle, } from "./container-card-styles.js";
|
|
6
6
|
import { Button, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
7
7
|
import { useEffect, useId, useState } from "react";
|
|
8
8
|
export function CreateContainerModal(props) {
|
|
@@ -81,9 +81,15 @@ export function CreateContainerModal(props) {
|
|
|
81
81
|
display: "flex",
|
|
82
82
|
flexDirection: "column",
|
|
83
83
|
gap: "12px",
|
|
84
|
-
}, children: [_jsx(Field, { label: t("image"), htmlFor: imageLabel, children: _jsxs("select", { id: imageLabel, style: imageSelect, value: image, disabled: busy, onChange: (event) => setImage(event.target.value), children: [candidates.length === 0
|
|
85
|
-
? t("
|
|
86
|
-
:
|
|
84
|
+
}, children: [_jsx(Field, { label: t("image"), htmlFor: imageLabel, children: _jsxs("select", { id: imageLabel, style: imageSelect, value: image, disabled: busy, onChange: (event) => setImage(event.target.value), children: [candidates.length === 0
|
|
85
|
+
? _jsx("option", { value: "", children: t("none") })
|
|
86
|
+
: null, candidates.map((imageId) => (_jsx("option", { value: imageId, children: imageId }, imageId)))] }) }), named
|
|
87
|
+
? (_jsxs(Field, { label: t("containerName"), htmlFor: nameLabel, children: [_jsx(Input, { id: nameLabel, value: name, disabled: busy, onChange: (event) => setName(sanitizeName(event.target.value)) }), name !== "" && !validName
|
|
88
|
+
? (_jsx("p", { style: { ...hint, margin: 0 }, children: nameTaken
|
|
89
|
+
? t("containerNameTaken")
|
|
90
|
+
: t("invalidContainerName") }))
|
|
91
|
+
: null] }))
|
|
92
|
+
: null, _jsx("div", { style: sectionTitle, children: t("envTitle") }), _jsx(EnvEditor, { t: t, env: env, busy: busy, onChange: setEnv }), _jsx("div", { style: sectionTitle, children: t("mountsTitle") }), _jsx(MountsEditor, { t: t, mounts: mounts, volumes: volumes, secrets: secrets, busy: busy, enabled: !busy, projectsRoot: projectsRoot, directoryPicker: directoryPicker, modeControl: "select", onAdd: (mount) => setMounts([...mounts, mount]), onRemove: (mount) => setMounts(mounts.filter((item) => item !== mount)), onUpdate: (updated, index) => setMounts(mounts.map((item, at) => (at === index ? updated : item))) }), _jsx("div", { style: sectionTitle, children: t("pathsTitle") }), _jsx(PathsList, { t: t, entries: paths, busy: busy, onChange: setPaths }), _jsx("div", { style: sectionTitle, children: t("containerSecretsTitle") }), Object.entries(secretEnv).map(([envVar, secretName]) => (_jsxs("div", { style: {
|
|
87
93
|
display: "flex",
|
|
88
94
|
flexWrap: "wrap",
|
|
89
95
|
alignItems: "center",
|
|
@@ -102,7 +108,9 @@ export function CreateContainerModal(props) {
|
|
|
102
108
|
flexWrap: "wrap",
|
|
103
109
|
alignItems: "center",
|
|
104
110
|
gap: "8px",
|
|
105
|
-
}, children: [_jsxs("select", { style: imageSelect, value: attachSecret, disabled: busy, onChange: (event) => setAttachSecret(event.target.value), "aria-label": t("attachSecret"), children: [secrets.length === 0
|
|
111
|
+
}, children: [_jsxs("select", { style: imageSelect, value: attachSecret, disabled: busy, onChange: (event) => setAttachSecret(event.target.value), "aria-label": t("attachSecret"), children: [secrets.length === 0
|
|
112
|
+
? _jsx("option", { value: "", children: t("none") })
|
|
113
|
+
: null, secrets.map((secret) => (_jsx("option", { value: secret.name, children: secret.name }, secret.name)))] }), _jsx(Input, { value: attachVar, disabled: busy, placeholder: t("secretEnvName"), onChange: (event) => setAttachVar(event.target.value), onKeyDown: (event) => {
|
|
106
114
|
if (event.key === "Enter")
|
|
107
115
|
attach();
|
|
108
116
|
}, style: { width: "200px" } }), _jsx(Button, { variant: "outline", size: "sm", disabled: busy || attachVar.trim() === "" || attachSecret === "", onClick: attach, children: t("attachSecret") })] })] }) }));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Translate } from "./locales.js";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export declare function DefaultEnvironmentSection(props: {
|
|
4
|
+
t: Translate;
|
|
5
|
+
env: Record<string, string>;
|
|
6
|
+
busy: boolean;
|
|
7
|
+
writable: boolean;
|
|
8
|
+
onSave: (env: Record<string, string>) => void;
|
|
9
|
+
onSync: (workspace: string) => void;
|
|
10
|
+
}): ReactNode;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
3
|
+
//
|
|
4
|
+
// SPDX-License-Identifier: MIT
|
|
5
|
+
// The default container environment: the map seeded into every new container,
|
|
6
|
+
// edited with the same EnvEditor a container row uses. The setting is
|
|
7
|
+
// persisted, so the draft is committed with Save, and the git shortcut fills
|
|
8
|
+
// the four identity variables in that same draft.
|
|
9
|
+
import { EnvEditor } from "./container-card-editors.js";
|
|
10
|
+
import { ConfirmButton, Field } from "./container-card-shared.js";
|
|
11
|
+
import { hint, sectionTitle, wsBody } from "./container-card-styles.js";
|
|
12
|
+
import { commitIdentityAction, readGitIdentity, setGitIdentity, } from "../container-env.js";
|
|
13
|
+
import { Button, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
14
|
+
import { useEffect, useId, useState } from "react";
|
|
15
|
+
export function DefaultEnvironmentSection(props) {
|
|
16
|
+
const { t, env, busy, writable, onSave, onSync } = props;
|
|
17
|
+
const [draft, setDraft] = useState(env);
|
|
18
|
+
const [gitOpen, setGitOpen] = useState(false);
|
|
19
|
+
const [gitName, setGitName] = useState("");
|
|
20
|
+
const [gitEmail, setGitEmail] = useState("");
|
|
21
|
+
const nameId = useId();
|
|
22
|
+
const emailId = useId();
|
|
23
|
+
// An external settings change resyncs the draft; an unrelated refresh does
|
|
24
|
+
// not, so an edit in progress is never clobbered. Same rule as a container
|
|
25
|
+
// row's environment editor.
|
|
26
|
+
const serverEnv = JSON.stringify(env);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setDraft(env);
|
|
29
|
+
}, [serverEnv]);
|
|
30
|
+
const dirty = JSON.stringify(draft) !== serverEnv;
|
|
31
|
+
const disabled = busy || !writable;
|
|
32
|
+
// The identity popup applies into the draft; when the draft's only drift from
|
|
33
|
+
// the saved environment is inside the identity keys, it commits straight, so
|
|
34
|
+
// a name/email edit is not left unsaved behind an easy-to-miss Save click.
|
|
35
|
+
// When the popup restores the saved identity exactly, its pending change is
|
|
36
|
+
// discarded and the editor falls back to the persisted defaults, instead of
|
|
37
|
+
// keeping the stale identity (a no-op save would not resync the draft, since
|
|
38
|
+
// the server value did not change).
|
|
39
|
+
const commitIdentity = (next) => {
|
|
40
|
+
const action = commitIdentityAction(env, draft, next);
|
|
41
|
+
if (action === "save") {
|
|
42
|
+
onSave(next);
|
|
43
|
+
}
|
|
44
|
+
else if (action === "discard") {
|
|
45
|
+
setDraft(env);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
setDraft(next);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const openGit = () => {
|
|
52
|
+
const identity = readGitIdentity(draft);
|
|
53
|
+
setGitName(identity.name);
|
|
54
|
+
setGitEmail(identity.email);
|
|
55
|
+
setGitOpen(true);
|
|
56
|
+
};
|
|
57
|
+
return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsx("div", { style: sectionTitle, children: t("defaultEnvironment") }), _jsx("div", { style: wsBody, children: _jsx(EnvEditor, { t: t, env: draft, busy: disabled, onChange: setDraft }) }), _jsxs("div", { style: {
|
|
58
|
+
display: "flex",
|
|
59
|
+
flexWrap: "wrap",
|
|
60
|
+
alignItems: "center",
|
|
61
|
+
gap: "8px",
|
|
62
|
+
}, children: [_jsx(Button, { variant: "outline", size: "sm", disabled: disabled, onClick: openGit, children: t("gitIdentity") }), _jsx(Button, { variant: "primary", size: "sm", disabled: disabled || !dirty, onClick: () => onSave(draft), children: t("save") }), _jsx(Button, { variant: "ghost", size: "sm", disabled: busy || !dirty, onClick: () => setDraft(env), children: t("discard") })] }), _jsx("div", { children: _jsx(ConfirmButton, { t: t, label: t("applyDefaults"), title: t("confirmTitle"), description: t("confirmApplyDefaults"), disabled: disabled || dirty || Object.keys(env).length === 0, onConfirm: () => onSync("") }) }), _jsx("p", { style: { ...hint, margin: 0 }, children: t("defaultEnvironmentHint") }), _jsx(Modal, { open: gitOpen, onClose: () => setGitOpen(false), title: t("gitIdentity"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", disabled: busy || readGitIdentity(draft).name === "", onClick: () => {
|
|
63
|
+
commitIdentity(setGitIdentity(draft, "", ""));
|
|
64
|
+
setGitOpen(false);
|
|
65
|
+
}, children: t("clear") }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setGitOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: busy || gitName === "" || gitEmail === "", onClick: () => {
|
|
66
|
+
commitIdentity(setGitIdentity(draft, gitName, gitEmail));
|
|
67
|
+
setGitOpen(false);
|
|
68
|
+
}, children: t("apply") })] }), children: _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [_jsx(Field, { label: t("gitName"), htmlFor: nameId, children: _jsx(Input, { id: nameId, value: gitName, disabled: busy, onChange: (event) => setGitName(event.target.value) }) }), _jsx(Field, { label: t("gitEmail"), htmlFor: emailId, children: _jsx(Input, { id: emailId, value: gitEmail, disabled: busy, onChange: (event) => setGitEmail(event.target.value) }) }), _jsx("p", { style: { ...hint, margin: 0 }, children: t("gitIdentityHint") })] }) })] }));
|
|
69
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type DirectoryPickerFace } from "./directory-picker.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { Translate } from "./locales.js";
|
|
3
3
|
import { type ReactNode } from "react";
|
|
4
4
|
export declare function DirectoryPickerModal(props: {
|
|
5
|
-
t:
|
|
5
|
+
t: Translate;
|
|
6
6
|
open: boolean;
|
|
7
7
|
root: string;
|
|
8
8
|
start: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { DIRECTORY_CLASS } from "./container-card-directory-styles.js";
|
|
3
|
-
import { confineToRoot, crumbLabel, rootCrumbs
|
|
4
|
-
import { Button,
|
|
3
|
+
import { confineToRoot, crumbLabel, rootCrumbs } from "../project-path.js";
|
|
4
|
+
import { Button, IconCheckOutlineRegular, IconChevronRightOutlineRegular, IconFolderCloseRegular, IconFolderOpenRegular, Modal, } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
6
|
// How long a listing may take before the browser admits it is still loading.
|
|
7
7
|
const SLOW_SCAN_DELAY_MS = 300;
|
|
@@ -128,13 +128,21 @@ export function DirectoryPickerModal(props) {
|
|
|
128
128
|
const crumbs = rootCrumbs(root, path);
|
|
129
129
|
const visible = (entries) => entries.filter((entry) => showHidden || !entry.hidden);
|
|
130
130
|
const truncated = level?.truncated === true || child?.truncated === true;
|
|
131
|
-
return (_jsx(Modal, { open: open, onClose: onClose, title: t("browseTitle"), className: DIRECTORY_CLASS, headless: true, children: _jsxs("div", { className: "dsh-podman-directory-scope", children: [_jsxs("div", { className: "dsh-podman-directory-header", children: [_jsx("h2", { className: "dsh-podman-directory-title", children: t("browseTitle") }), _jsx("div", { className: "dsh-podman-directory-crumbbar", children: _jsxs("nav", { ref: trailRef, className: "dsh-podman-directory-crumbs", "aria-label": t("browseTitle"), children: [_jsx("span", { className: "dsh-podman-directory-crumbseat", children: _jsx("button", { type: "button", className: "dsh-podman-directory-crumb", disabled: busy, onClick: () => navigate(root), children: crumbLabel(root) }) }), crumbs.map((crumb) => (_jsxs("span", { className: "dsh-podman-directory-crumbseat", children: [_jsx(
|
|
131
|
+
return (_jsx(Modal, { open: open, onClose: onClose, title: t("browseTitle"), className: DIRECTORY_CLASS, headless: true, children: _jsxs("div", { className: "dsh-podman-directory-scope", children: [_jsxs("div", { className: "dsh-podman-directory-header", children: [_jsx("h2", { className: "dsh-podman-directory-title", children: t("browseTitle") }), _jsx("div", { className: "dsh-podman-directory-crumbbar", children: _jsxs("nav", { ref: trailRef, className: "dsh-podman-directory-crumbs", "aria-label": t("browseTitle"), children: [_jsx("span", { className: "dsh-podman-directory-crumbseat", children: _jsx("button", { type: "button", className: "dsh-podman-directory-crumb", disabled: busy, onClick: () => navigate(root), children: crumbLabel(root) }) }), crumbs.map((crumb) => (_jsxs("span", { className: "dsh-podman-directory-crumbseat", children: [_jsx(IconChevronRightOutlineRegular, { size: 12, className: "dsh-podman-directory-crumbchevron" }), _jsx("button", { type: "button", className: "dsh-podman-directory-crumb", disabled: busy, onClick: () => navigate(crumb), children: crumbLabel(crumb) })] }, crumb)))] }) })] }), _jsxs("div", { className: "dsh-podman-directory-content", children: [_jsxs("div", { className: "dsh-podman-directory-miller", children: [_jsx("ul", { className: "dsh-podman-directory-column", children: (level?.entries ?? []).length === 0 ? null : (visible(level?.entries ?? []).map((entry) => (_jsx(DirectoryRow, { entry: entry, selected: selected?.path === entry.path, busy: busy, onPick: select }, entry.path)))) }), selected === null ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: "dsh-podman-directory-divider" }), _jsx("ul", { className: "dsh-podman-directory-column", children: visible(child?.entries ?? []).map((entry) => (_jsx(DirectoryRow, { entry: entry, selected: false, busy: busy, onPick: advance }, entry.path))) })] }))] }), error === ""
|
|
132
|
+
? null
|
|
133
|
+
: (_jsx("p", { className: "dsh-podman-directory-error", role: "alert", children: error })), error === "" && truncated
|
|
134
|
+
? (_jsx("p", { className: "dsh-podman-directory-status", role: "status", children: t("browseTruncated") }))
|
|
135
|
+
: null, slow
|
|
136
|
+
? (_jsx("span", { className: "dsh-podman-directory-loading", role: "status", children: t("browseLoading") }))
|
|
137
|
+
: null] }), _jsxs("div", { className: "dsh-podman-directory-footer", children: [_jsxs("button", { type: "button", className: "dsh-podman-directory-toggle", "aria-pressed": showHidden, disabled: busy, onClick: () => setShowHidden((previous) => !previous), children: [t("browseHidden"), showHidden ? _jsx(IconCheckOutlineRegular, { size: 14 }) : null] }), _jsx("span", { className: "dsh-podman-directory-gap" }), _jsx(Button, { variant: "outline", className: "dsh-podman-directory-action", disabled: busy, onClick: onClose, children: t("cancel") }), _jsx(Button, { variant: "primary", className: "dsh-podman-directory-action", disabled: busy || error !== "", onClick: () => onSelect(selected?.path ?? path), children: t("browseSelect") })] })] }) }));
|
|
132
138
|
}
|
|
133
139
|
function DirectoryRow(props) {
|
|
134
140
|
const { entry, selected, busy, onPick } = props;
|
|
135
141
|
return (_jsx("li", { className: "dsh-podman-directory-seat", children: _jsxs("button", { type: "button", className: selected
|
|
136
142
|
? "dsh-podman-directory-row dsh-podman-directory-row-selected"
|
|
137
|
-
: "dsh-podman-directory-row", "aria-current": selected ? true : undefined, disabled: busy, onClick: () => onPick(entry), children: [selected
|
|
143
|
+
: "dsh-podman-directory-row", "aria-current": selected ? true : undefined, disabled: busy, onClick: () => onPick(entry), children: [selected
|
|
144
|
+
? (_jsx(IconFolderOpenRegular, { size: 16, className: "dsh-podman-directory-rowicon-selected" }))
|
|
145
|
+
: (_jsx(IconFolderCloseRegular, { size: 16, className: "dsh-podman-directory-rowicon" })), _jsx("span", { className: "dsh-podman-directory-rowname", children: entry.name }), _jsx(IconChevronRightOutlineRegular, { size: 12, className: "dsh-podman-directory-rowchevron" })] }) }));
|
|
138
146
|
}
|
|
139
147
|
function toLevel(listing) {
|
|
140
148
|
return {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { type MountInput } from "./container-card-controller.js";
|
|
2
2
|
import { type DirectoryPickerFace } from "./directory-picker.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { Translate } from "./locales.js";
|
|
4
4
|
import { type ReactNode } from "react";
|
|
5
5
|
export declare function EnvEditor(props: {
|
|
6
|
-
t:
|
|
6
|
+
t: Translate;
|
|
7
7
|
env: Record<string, string>;
|
|
8
8
|
busy: boolean;
|
|
9
9
|
onChange: (env: Record<string, string>) => void;
|
|
10
10
|
}): ReactNode;
|
|
11
11
|
export declare function MountsEditor(props: {
|
|
12
|
-
t:
|
|
12
|
+
t: Translate;
|
|
13
13
|
mounts: readonly MountInput[];
|
|
14
14
|
volumes: readonly {
|
|
15
15
|
name: string;
|