@exagone313/dsh-podman 0.2.0-rc.3 → 0.2.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.
Files changed (191) hide show
  1. package/README.md +14 -2
  2. package/README.zh.md +14 -2
  3. package/cordis.patch.yml +58 -0
  4. package/dist/approval-reasons.d.ts +3 -4
  5. package/dist/approval-reasons.js +40 -49
  6. package/dist/approval-reasons.test.js +43 -15
  7. package/dist/approval.js +27 -10
  8. package/dist/approval.test.js +165 -34
  9. package/dist/card-route.d.ts +3 -2
  10. package/dist/card-route.js +154 -42
  11. package/dist/card-route.test.js +85 -43
  12. package/dist/card-test-support.js +39 -66
  13. package/dist/client/ContainerCard.d.ts +1 -1
  14. package/dist/client/ContainerCard.js +92 -50
  15. package/dist/client/card-protocol.d.ts +6 -3
  16. package/dist/client/container-card-caches.js +11 -6
  17. package/dist/client/container-card-controller.d.ts +16 -18
  18. package/dist/client/container-card-controller.js +92 -40
  19. package/dist/client/container-card-create-modal.d.ts +2 -2
  20. package/dist/client/container-card-create-modal.js +13 -5
  21. package/dist/client/container-card-default-env.d.ts +10 -0
  22. package/dist/client/container-card-default-env.js +69 -0
  23. package/dist/client/container-card-directory.d.ts +2 -2
  24. package/dist/client/container-card-directory.js +12 -4
  25. package/dist/client/container-card-editors.d.ts +3 -3
  26. package/dist/client/container-card-editors.js +104 -32
  27. package/dist/client/container-card-images.d.ts +4 -4
  28. package/dist/client/container-card-images.js +19 -6
  29. package/dist/client/container-card-paths.d.ts +3 -3
  30. package/dist/client/container-card-paths.js +18 -11
  31. package/dist/client/container-card-row.d.ts +10 -6
  32. package/dist/client/container-card-row.js +82 -13
  33. package/dist/client/container-card-secrets.d.ts +3 -3
  34. package/dist/client/container-card-secrets.js +8 -6
  35. package/dist/client/container-card-shared.d.ts +4 -17
  36. package/dist/client/container-card-shared.js +9 -25
  37. package/dist/client/container-card-styles.d.ts +2 -8
  38. package/dist/client/container-card-styles.js +14 -52
  39. package/dist/client/container-card-volumes.d.ts +2 -2
  40. package/dist/client/container-card-volumes.js +9 -5
  41. package/dist/client/container-card-workspace.d.ts +8 -6
  42. package/dist/client/container-card-workspace.js +9 -3
  43. package/dist/client/directory-picker.js +2 -1
  44. package/dist/client/index.js +13141 -2036
  45. package/dist/client/locales.d.ts +6 -1
  46. package/dist/client/locales.js +106 -38
  47. package/dist/client/podman-terminal-guide.d.ts +11 -0
  48. package/dist/client/podman-terminal-guide.js +208 -0
  49. package/dist/client/podman-terminal-title.d.ts +7 -0
  50. package/dist/client/podman-terminal-title.js +30 -0
  51. package/dist/client/podman-terminal.d.ts +26 -0
  52. package/dist/client/podman-terminal.js +412 -0
  53. package/dist/client/read-only-approval.d.ts +2 -2
  54. package/dist/client/read-only-approval.js +13 -3
  55. package/dist/client/slot-contract.d.ts +0 -11
  56. package/dist/client/terminal-preference.d.ts +26 -0
  57. package/dist/client/terminal-preference.js +67 -0
  58. package/dist/client/terminal-protocol.d.ts +85 -0
  59. package/dist/client/terminal-protocol.js +14 -0
  60. package/dist/client/terminal-tab.d.ts +60 -0
  61. package/dist/client/terminal-tab.js +35 -0
  62. package/dist/client/terminal-targets.d.ts +33 -0
  63. package/dist/client/terminal-targets.js +65 -0
  64. package/dist/client/terminal-titles.d.ts +23 -0
  65. package/dist/client/terminal-titles.js +52 -0
  66. package/dist/client/terminal-transport.d.ts +63 -0
  67. package/dist/client/terminal-transport.js +207 -0
  68. package/dist/client/tool-views.js +215 -49
  69. package/dist/container-env.d.ts +11 -0
  70. package/dist/container-env.js +96 -0
  71. package/dist/container-env.test.d.ts +1 -0
  72. package/dist/container-env.test.js +93 -0
  73. package/dist/containers.test.js +196 -18
  74. package/dist/daemons.test.js +18 -4
  75. package/dist/dsh-version.d.ts +1 -0
  76. package/dist/dsh-version.js +46 -0
  77. package/dist/env-rows.d.ts +14 -0
  78. package/dist/env-rows.js +52 -0
  79. package/dist/env-rows.test.d.ts +1 -0
  80. package/dist/env-rows.test.js +43 -0
  81. package/dist/fs-provider.d.ts +1 -0
  82. package/dist/fs-provider.js +26 -12
  83. package/dist/fs-provider.test.js +51 -10
  84. package/dist/generated/version.d.ts +2 -2
  85. package/dist/generated/version.js +2 -2
  86. package/dist/grpc/proto/dshctl/v1/control.proto +11 -1
  87. package/dist/grpc/proto/dshguest/v1/guest.proto +5 -0
  88. package/dist/grpc/runtime-client.d.ts +3 -1
  89. package/dist/grpc/runtime-client.js +90 -17
  90. package/dist/guest-rpc.d.ts +15 -19
  91. package/dist/guest-rpc.js +289 -70
  92. package/dist/guest-rpc.test.d.ts +1 -0
  93. package/dist/guest-rpc.test.js +241 -0
  94. package/dist/guest-terminal.d.ts +30 -0
  95. package/dist/guest-terminal.js +196 -0
  96. package/dist/images.test.js +10 -3
  97. package/dist/index.d.ts +11 -16
  98. package/dist/index.js +42 -36
  99. package/dist/locales.test.d.ts +1 -0
  100. package/dist/locales.test.js +34 -0
  101. package/dist/misc.test.js +38 -10
  102. package/dist/mount-enums.js +2 -1
  103. package/dist/mount-input.d.ts +2 -0
  104. package/dist/mount-input.js +38 -22
  105. package/dist/mounts.test.js +81 -11
  106. package/dist/naming.test.d.ts +1 -0
  107. package/dist/naming.test.js +28 -0
  108. package/dist/output-reader.js +36 -4
  109. package/dist/package-deps.test.d.ts +1 -0
  110. package/dist/package-deps.test.js +49 -0
  111. package/dist/paths.test.js +4 -2
  112. package/dist/plugin-meta.test.d.ts +1 -0
  113. package/dist/plugin-meta.test.js +29 -0
  114. package/dist/project-path.js +33 -7
  115. package/dist/project-path.test.js +14 -0
  116. package/dist/prompts.d.ts +0 -4
  117. package/dist/prompts.js +15 -84
  118. package/dist/read-only-shell.js +3 -1
  119. package/dist/read-only-shell.test.js +80 -25
  120. package/dist/runtime-client.test.d.ts +1 -0
  121. package/dist/runtime-client.test.js +53 -0
  122. package/dist/secrets.test.js +21 -6
  123. package/dist/settings-commands.test.js +61 -14
  124. package/dist/settings-create.test.js +32 -10
  125. package/dist/settings-default-env.test.d.ts +1 -0
  126. package/dist/settings-default-env.test.js +175 -0
  127. package/dist/settings-mounts.test.js +46 -7
  128. package/dist/settings-schema.d.ts +6 -11
  129. package/dist/settings-schema.js +4 -8
  130. package/dist/settings-schema.test.d.ts +1 -0
  131. package/dist/settings-schema.test.js +35 -0
  132. package/dist/settings-workspaces.test.js +38 -63
  133. package/dist/spill-store.d.ts +1 -0
  134. package/dist/spill-store.js +17 -4
  135. package/dist/spill-store.test.js +31 -8
  136. package/dist/subprocess.d.ts +4 -0
  137. package/dist/subprocess.js +148 -162
  138. package/dist/subprocess.test.js +118 -8
  139. package/dist/terminal-preference.test.d.ts +1 -0
  140. package/dist/terminal-preference.test.js +62 -0
  141. package/dist/terminal-route.d.ts +10 -0
  142. package/dist/terminal-route.js +270 -0
  143. package/dist/terminal-route.test.d.ts +1 -0
  144. package/dist/terminal-route.test.js +265 -0
  145. package/dist/terminal-sessions.d.ts +69 -0
  146. package/dist/terminal-sessions.js +252 -0
  147. package/dist/terminal-shells.d.ts +22 -0
  148. package/dist/terminal-shells.js +99 -0
  149. package/dist/terminal-tab.test.d.ts +1 -0
  150. package/dist/terminal-tab.test.js +58 -0
  151. package/dist/terminal-targets.test.d.ts +1 -0
  152. package/dist/terminal-targets.test.js +43 -0
  153. package/dist/terminal-titles.test.d.ts +1 -0
  154. package/dist/terminal-titles.test.js +36 -0
  155. package/dist/test-support.d.ts +35 -5
  156. package/dist/test-support.js +91 -29
  157. package/dist/tool-defs.js +19 -4
  158. package/dist/tool-handlers.js +80 -28
  159. package/dist/tool-params.js +19 -5
  160. package/dist/version-compat.d.ts +4 -0
  161. package/dist/version-compat.js +29 -0
  162. package/dist/version-compat.test.d.ts +1 -0
  163. package/dist/version-compat.test.js +27 -0
  164. package/dist/volumes.test.js +1 -1
  165. package/dist/workspace-binding.d.ts +19 -10
  166. package/dist/workspace-binding.js +252 -78
  167. package/dist/workspace-binding.test.js +202 -27
  168. package/docs/architecture.md +71 -20
  169. package/docs/architecture.zh.md +62 -12
  170. package/docs/configuration.md +61 -20
  171. package/docs/configuration.zh.md +53 -19
  172. package/docs/development-prompt.md +72 -0
  173. package/docs/development-prompt.zh.md +69 -0
  174. package/docs/development.md +184 -29
  175. package/docs/development.zh.md +165 -26
  176. package/docs/install-dsh-and-dsh-podman.md +24 -21
  177. package/docs/install-dsh-and-dsh-podman.zh.md +20 -21
  178. package/docs/uninstall.md +83 -0
  179. package/docs/uninstall.zh.md +79 -0
  180. package/docs/update.md +120 -0
  181. package/docs/update.zh.md +114 -0
  182. package/docs/usage.md +108 -61
  183. package/docs/usage.zh.md +76 -38
  184. package/locale/en.json +6 -0
  185. package/locale/zh.json +6 -0
  186. package/package.json +58 -25
  187. package/quadlet/dsh-podman-orchestrator.container +46 -0
  188. package/quadlet/dsh.container +35 -0
  189. package/LICENSE.pkg +0 -17103
  190. package/dist/preferences.d.ts +0 -6
  191. package/dist/preferences.js +0 -27
@@ -1,14 +1,62 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { EnvEditor, MountsEditor } from "./container-card-editors.js";
3
3
  import { PathsEditor } from "./container-card-paths.js";
4
- import { ConfirmButton, mountViewToInput } from "./container-card-shared.js";
4
+ import { ConfirmButton, mountKindShort, mountViewToInput, } from "./container-card-shared.js";
5
5
  import { actions, containerHeader, containerRow, greyId, imageSelect, tableStyle, tdStyle, thStyle, wsBody, } from "./container-card-styles.js";
6
- import { Button, DisclosureRow, Input, Pill } from "@deepseek-ai/dsh-client-ui-primitives";
7
- import { useState } from "react";
6
+ import { Button, DisclosureRow, Input, Pill, } from "@deepseek-ai/dsh-client-ui-primitives";
7
+ import { useEffect, useState } from "react";
8
+ // The order the summary table lists mount kinds in.
9
+ const MOUNT_ROWS = [
10
+ { key: "projects", kind: "project" },
11
+ { key: "volumesTitle", kind: "volume" },
12
+ { key: "mountTmpfs", kind: "tmpfs" },
13
+ { key: "secretsTitle", kind: "secret" },
14
+ ];
15
+ // One summary row per mount kind present on a container, so a volume, tmpfs or
16
+ // secret never contributes an empty project name to the Projects line. A mount
17
+ // whose identifying field is empty is skipped, and a kind with no mount left
18
+ // gets no row at all.
19
+ export function mountSummaries(mounts) {
20
+ const buckets = new Map(MOUNT_ROWS.map(({ kind }) => [kind, []]));
21
+ for (const mount of mounts) {
22
+ const kind = mountKindShort(mount.kind);
23
+ const values = buckets.get(kind);
24
+ if (values === undefined)
25
+ continue;
26
+ const identifier = kind === "project"
27
+ ? mount.projectName
28
+ : kind === "volume"
29
+ ? mount.volume
30
+ : kind === "secret"
31
+ ? mount.secret
32
+ : mount.destination;
33
+ if (identifier === "")
34
+ continue;
35
+ values.push(kind === "tmpfs" || mount.destination === ""
36
+ ? identifier
37
+ : `${identifier} → ${mount.destination}`);
38
+ }
39
+ return MOUNT_ROWS.flatMap(({ key, kind }) => {
40
+ const values = buckets.get(kind) ?? [];
41
+ return values.length === 0 ? [] : [{ key, values }];
42
+ });
43
+ }
8
44
  export function ContainerRow(props) {
9
45
  const { t, container, images, volumes, secrets, busy, onRemove, onRecreate, onAddContainerMount, onRemoveContainerMount, onUpdateContainerMount, onSetContainerPaths, onAddContainerSecret, onRemoveContainerSecret, projectName, projectsRoot, directoryPicker, } = props;
10
46
  const [selected, setSelected] = useState(container.imageId);
11
47
  const [env, setEnv] = useState(container.env);
48
+ // The row is keyed by container name, so it survives a snapshot refresh.
49
+ // Re-sync the image draft when the live image changes, or the select would
50
+ // keep offering and re-sending the previous one.
51
+ useEffect(() => {
52
+ setSelected(container.imageId);
53
+ }, [container.imageId]);
54
+ // Env is a fresh object on every snapshot; key the resync on its content so
55
+ // an unchanged refresh does not clobber an in-progress edit.
56
+ const serverEnv = JSON.stringify(container.env);
57
+ useEffect(() => {
58
+ setEnv(container.env);
59
+ }, [serverEnv]);
12
60
  const [envOpen, setEnvOpen] = useState(false);
13
61
  const [mountOpen, setMountOpen] = useState(false);
14
62
  const [pathOpen, setPathOpen] = useState(false);
@@ -16,21 +64,36 @@ export function ContainerRow(props) {
16
64
  const [attachSecret, setAttachSecret] = useState("");
17
65
  const [attachVar, setAttachVar] = useState("");
18
66
  const enabled = container.workspaceSlug !== "" && !busy;
19
- const projects = container.mounts
20
- .map((mount) => mount.projectName)
21
- .join(", ");
67
+ const mountRows = mountSummaries(container.mounts);
22
68
  const envEntries = Object.entries(container.env);
23
69
  const secretEntries = Object.entries(container.secretEnv);
24
70
  const attach = () => {
25
71
  const envVar = attachVar.trim();
26
72
  if (envVar === "" || attachSecret === "")
27
73
  return;
28
- onAddContainerSecret(container.workspaceSlug, envVar, attachSecret);
74
+ onAddContainerSecret(container.workspaceSlug, container.containerName, envVar, attachSecret);
29
75
  setAttachVar("");
30
76
  };
31
- return (_jsxs("div", { style: containerRow, children: [_jsxs("div", { style: containerHeader, children: [_jsx("strong", { style: { color: "var(--dsw-alias-label-primary)" }, children: container.containerName }), _jsx(Pill, { children: container.status })] }), _jsx("table", { style: tableStyle, children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("image") }), _jsx("td", { style: tdStyle, children: container.imageId })] }), _jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("created") }), _jsx("td", { style: tdStyle, children: container.createdAt })] }), projects !== "" ? (_jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("projects") }), _jsx("td", { style: tdStyle, children: projects })] })) : null, envEntries.length > 0 ? (_jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("env") }), _jsx("td", { style: tdStyle, children: envEntries.map(([key]) => key).join(", ") })] })) : null, secretEntries.length > 0 ? (_jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("secretEnv") }), _jsx("td", { style: tdStyle, children: secretEntries
32
- .map(([envVar, secretName]) => `${envVar}=${secretName}`)
33
- .join(", ") })] })) : null] }) }), _jsxs("div", { style: actions, children: [_jsx(ConfirmButton, { t: t, label: t("remove"), title: t("confirmTitle"), description: t("confirmRemoveContainer"), disabled: !enabled, onConfirm: () => onRemove(container.workspaceSlug) }), _jsx(ConfirmButton, { t: t, label: t("recreate"), title: t("confirmTitle"), description: t("confirmRecreate"), disabled: !enabled, onConfirm: () => onRecreate(container.workspaceSlug, "", env) }), _jsxs("select", { style: imageSelect, value: selected, disabled: !enabled, onChange: (event) => setSelected(event.target.value), "aria-label": t("recreateWithImage"), children: [container.imageId === "" ? (_jsx("option", { value: "", children: t("none") })) : null, images.map((image) => (_jsx("option", { value: image.imageId, children: image.imageId }, image.imageId)))] }), _jsx(ConfirmButton, { t: t, label: t("recreateWithImage"), title: t("confirmTitle"), description: t("confirmRecreate"), disabled: !enabled || selected === "", onConfirm: () => onRecreate(container.workspaceSlug, selected, env) })] }), _jsx(DisclosureRow, { icon: _jsx("span", {}), title: t("envTitle"), open: envOpen, expandable: true, onToggle: () => setEnvOpen(!envOpen), children: _jsx("div", { style: wsBody, children: _jsx(EnvEditor, { t: t, env: env, busy: busy, onChange: setEnv }) }) }), _jsx(DisclosureRow, { icon: _jsx("span", {}), title: t("mountsTitle"), open: mountOpen, expandable: true, onToggle: () => setMountOpen(!mountOpen), children: _jsx("div", { style: wsBody, children: _jsx(MountsEditor, { t: t, mounts: container.mounts.map(mountViewToInput), volumes: volumes, secrets: secrets, busy: busy, enabled: enabled, projectsRoot: projectsRoot, directoryPicker: directoryPicker, confirmRemove: true, primaryProject: container.containerName === "default" ? projectName : "", onUpdate: (mount) => onUpdateContainerMount(container.workspaceSlug, container.containerName, mount), onRemove: (mount) => onRemoveContainerMount(container.workspaceSlug, container.containerName, mount), onAdd: (mount) => onAddContainerMount(container.workspaceSlug, container.containerName, mount) }) }) }), _jsx(DisclosureRow, { icon: _jsx("span", {}), title: t("pathsTitle"), open: pathOpen, expandable: true, onToggle: () => setPathOpen(!pathOpen), children: _jsx("div", { style: wsBody, children: _jsx(PathsEditor, { t: t, paths: container.paths, busy: busy, enabled: enabled, onApply: (paths) => onSetContainerPaths(container.workspaceSlug, container.containerName, paths) }) }) }), _jsx(DisclosureRow, { icon: _jsx("span", {}), title: t("containerSecretsTitle"), open: secretOpen, expandable: true, onToggle: () => setSecretOpen(!secretOpen), children: _jsxs("div", { style: wsBody, children: [secretEntries.map(([envVar, secretName]) => (_jsxs("div", { style: {
77
+ return (_jsxs("div", { style: containerRow, children: [_jsxs("div", { style: containerHeader, children: [_jsx("strong", { style: { color: "var(--dsw-alias-label-primary)" }, children: container.containerName }), _jsx(Pill, { children: container.status })] }), _jsx("table", { style: tableStyle, children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("image") }), _jsx("td", { style: tdStyle, children: container.imageId })] }), _jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("created") }), _jsx("td", { style: tdStyle, children: container.createdAt })] }), mountRows.map(({ key, values }) => (_jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t(key) }), _jsx("td", { style: tdStyle, children: values.join(", ") })] }, key))), envEntries.length > 0
78
+ ? (_jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("env") }), _jsx("td", { style: tdStyle, children: envEntries.map(([key]) => key).join(", ") })] }))
79
+ : null, secretEntries.length > 0
80
+ ? (_jsxs("tr", { children: [_jsx("th", { style: thStyle, scope: "row", children: t("secretEnv") }), _jsx("td", { style: tdStyle, children: secretEntries
81
+ .map(([envVar, secretName]) => `${envVar}=${secretName}`)
82
+ .join(", ") })] }))
83
+ : null] }) }), _jsxs("div", { style: actions, children: [_jsx(ConfirmButton, { t: t, label: t("remove"), title: t("confirmTitle"), description: t("confirmRemoveContainer", {
84
+ workspace: projectName,
85
+ container: container.containerName,
86
+ }), disabled: !enabled, onConfirm: () => onRemove(container.workspaceSlug, container.containerName) }), _jsx(ConfirmButton, { t: t, label: t("recreate"), title: t("confirmTitle"), description: t("confirmRecreate", {
87
+ workspace: projectName,
88
+ container: container.containerName,
89
+ }), disabled: !enabled, onConfirm: () => onRecreate(container.workspaceSlug, container.containerName, "", env) }), _jsxs("select", { style: imageSelect, value: selected, disabled: !enabled, onChange: (event) => setSelected(event.target.value), "aria-label": t("recreateWithImage"), children: [container.imageId === ""
90
+ ? _jsx("option", { value: "", children: t("none") })
91
+ : null, images.map((image) => (_jsx("option", { value: image.imageId, children: image.imageId }, image.imageId)))] }), _jsx(ConfirmButton, { t: t, label: t("recreateWithImage"), title: t("confirmTitle"), description: t("confirmRecreate", {
92
+ workspace: projectName,
93
+ container: container.containerName,
94
+ }), disabled: !enabled || selected === "", onConfirm: () => onRecreate(container.workspaceSlug, container.containerName, selected, env) })] }), _jsx(DisclosureRow, { icon: _jsx("span", {}), title: t("envTitle"), open: envOpen, expandable: true, onToggle: () => setEnvOpen(!envOpen), children: _jsx("div", { style: wsBody, children: _jsx(EnvEditor, { t: t, env: env, busy: busy, onChange: setEnv }) }) }), _jsx(DisclosureRow, { icon: _jsx("span", {}), title: t("mountsTitle"), open: mountOpen, expandable: true, onToggle: () => setMountOpen(!mountOpen), children: _jsx("div", { style: wsBody, children: _jsx(MountsEditor, { t: t, mounts: container.mounts.map(mountViewToInput), volumes: volumes, secrets: secrets, busy: busy, enabled: enabled, projectsRoot: projectsRoot, directoryPicker: directoryPicker, confirmRemove: true, primaryProject: container.containerName === "default"
95
+ ? projectName
96
+ : "", onUpdate: (mount) => onUpdateContainerMount(container.workspaceSlug, container.containerName, mount), onRemove: (mount) => onRemoveContainerMount(container.workspaceSlug, container.containerName, mount), onAdd: (mount) => onAddContainerMount(container.workspaceSlug, container.containerName, mount) }) }) }), _jsx(DisclosureRow, { icon: _jsx("span", {}), title: t("pathsTitle"), open: pathOpen, expandable: true, onToggle: () => setPathOpen(!pathOpen), children: _jsx("div", { style: wsBody, children: _jsx(PathsEditor, { t: t, paths: container.paths, busy: busy, enabled: enabled, onApply: (paths) => onSetContainerPaths(container.workspaceSlug, container.containerName, paths) }) }) }), _jsx(DisclosureRow, { icon: _jsx("span", {}), title: t("containerSecretsTitle"), open: secretOpen, expandable: true, onToggle: () => setSecretOpen(!secretOpen), children: _jsxs("div", { style: wsBody, children: [secretEntries.map(([envVar, secretName]) => (_jsxs("div", { style: {
34
97
  display: "flex",
35
98
  flexWrap: "wrap",
36
99
  alignItems: "center",
@@ -40,15 +103,21 @@ export function ContainerRow(props) {
40
103
  flex: 1,
41
104
  fontSize: "13px",
42
105
  color: "var(--dsw-alias-label-primary)",
43
- }, children: [envVar, "=", secretName] }), _jsx(ConfirmButton, { t: t, label: t("detachSecret"), title: t("confirmTitle"), description: t("confirmDetachSecret"), disabled: busy, onConfirm: () => onRemoveContainerSecret(container.workspaceSlug, envVar) })] }, envVar))), _jsxs("div", { style: {
106
+ }, children: [envVar, "=", secretName] }), _jsx(ConfirmButton, { t: t, label: t("detachSecret"), title: t("confirmTitle"), description: t("confirmDetachSecret", {
107
+ env: envVar,
108
+ secret: secretName,
109
+ }), disabled: busy, onConfirm: () => onRemoveContainerSecret(container.workspaceSlug, container.containerName, envVar) })] }, envVar))), _jsxs("div", { style: {
44
110
  display: "flex",
45
111
  flexWrap: "wrap",
46
112
  alignItems: "center",
47
113
  gap: "8px",
48
114
  paddingTop: "8px",
49
- }, children: [_jsxs("select", { style: imageSelect, value: attachSecret, disabled: !enabled, onChange: (event) => setAttachSecret(event.target.value), "aria-label": t("attachSecret"), children: [secrets.length === 0 ? (_jsx("option", { value: "", children: t("none") })) : null, secrets.map((secret) => (_jsx("option", { value: secret.name, children: secret.name }, secret.name)))] }), _jsx(Input, { value: attachVar, disabled: !enabled, placeholder: t("secretEnvName"), onChange: (event) => setAttachVar(event.target.value), onKeyDown: (event) => {
115
+ }, children: [_jsxs("select", { style: imageSelect, value: attachSecret, disabled: !enabled, onChange: (event) => setAttachSecret(event.target.value), "aria-label": t("attachSecret"), children: [secrets.length === 0
116
+ ? _jsx("option", { value: "", children: t("none") })
117
+ : null, secrets.map((secret) => (_jsx("option", { value: secret.name, children: secret.name }, secret.name)))] }), _jsx(Input, { value: attachVar, disabled: !enabled, placeholder: t("secretEnvName"), onChange: (event) => setAttachVar(event.target.value), onKeyDown: (event) => {
50
118
  if (event.key === "Enter")
51
119
  attach();
52
- }, style: { width: "200px" } }), _jsx(Button, { variant: "outline", size: "sm", disabled: !enabled || attachVar.trim() === "" || attachSecret === "", onClick: attach, children: t("attachSecret") })] })] }) })] }));
120
+ }, style: { width: "200px" } }), _jsx(Button, { variant: "outline", size: "sm", disabled: !enabled || attachVar.trim() === "" ||
121
+ attachSecret === "", onClick: attach, children: t("attachSecret") })] })] }) })] }));
53
122
  }
54
123
  export { CreateContainerModal } from "./container-card-create-modal.js";
@@ -1,7 +1,7 @@
1
- import { type ContainerPluginKey } from "./locales.js";
1
+ import type { Translate } from "./locales.js";
2
2
  import { type ReactNode } from "react";
3
3
  export declare function SecretRow(props: {
4
- t: (key: ContainerPluginKey) => string;
4
+ t: Translate;
5
5
  name: string;
6
6
  busy: boolean;
7
7
  writable: boolean;
@@ -9,7 +9,7 @@ export declare function SecretRow(props: {
9
9
  onRemove: (name: string) => void;
10
10
  }): ReactNode;
11
11
  export declare function SecretsSection(props: {
12
- t: (key: ContainerPluginKey) => string;
12
+ t: Translate;
13
13
  secrets: readonly {
14
14
  name: string;
15
15
  }[];
@@ -2,9 +2,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  // SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
3
  //
4
4
  // SPDX-License-Identifier: MIT
5
- import { ConfirmButton, Field, namePattern, sanitizeName } from "./container-card-shared.js";
6
- import { greyId, hint, imageSelect, sectionTitle, wsBody } from "./container-card-styles.js";
7
- import { Button, DisclosureRow, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
5
+ import { ConfirmButton, Field, namePattern, sanitizeName, } from "./container-card-shared.js";
6
+ import { greyId, hint, imageSelect, sectionTitle, wsBody, } from "./container-card-styles.js";
7
+ import { Button, DisclosureRow, Input, Modal, } from "@deepseek-ai/dsh-client-ui-primitives";
8
8
  import { useId, useState } from "react";
9
9
  export function SecretRow(props) {
10
10
  const { t, name, busy, writable, onSet, onRemove } = props;
@@ -29,7 +29,7 @@ export function SecretRow(props) {
29
29
  }, children: name }), _jsx(Input, { type: "password", value: content, disabled: !writable || busy, placeholder: t("setSecret"), onChange: (event) => setContent(event.target.value), onKeyDown: (event) => {
30
30
  if (event.key === "Enter")
31
31
  submit();
32
- }, style: { width: "200px" } }), _jsx(ConfirmButton, { t: t, label: t("setSecret"), title: t("confirmTitle"), description: t("confirmSetSecret"), disabled: !writable || !canSave, onConfirm: submit }), _jsx(ConfirmButton, { t: t, label: t("removeSecret"), title: t("confirmTitle"), description: t("confirmRemoveSecret"), disabled: busy, onConfirm: () => onRemove(name) })] }));
32
+ }, style: { width: "200px" } }), _jsx(ConfirmButton, { t: t, label: t("setSecret"), title: t("confirmTitle"), description: t("confirmSetSecret", { secret: name }), disabled: !writable || !canSave, onConfirm: submit }), _jsx(ConfirmButton, { t: t, label: t("removeSecret"), title: t("confirmTitle"), description: t("confirmRemoveSecret", { secret: name }), disabled: !writable || busy, onConfirm: () => onRemove(name) })] }));
33
33
  }
34
34
  export function SecretsSection(props) {
35
35
  const { t, secrets, busy, writable, onCreate, onRemove, onSet } = props;
@@ -60,12 +60,14 @@ export function SecretsSection(props) {
60
60
  flexWrap: "wrap",
61
61
  alignItems: "center",
62
62
  gap: "8px",
63
- }, children: [_jsx("div", { style: { ...sectionTitle, flex: 1 }, children: t("secretsTitle") }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || busy, onClick: () => setOpen(true), children: t("createSecret") })] }), secrets.length === 0 ? (_jsx("p", { style: hint, children: t("none") })) : (secrets.map((secret) => (_jsx(DisclosureRow, { icon: _jsx("span", {}), title: secret.name, open: openSecret === secret.name, expandable: true, onToggle: () => setOpenSecret(openSecret === secret.name ? null : secret.name), children: _jsx("div", { style: wsBody, children: _jsx(SecretRow, { t: t, name: secret.name, busy: busy, writable: writable, onSet: onSet, onRemove: onRemove }) }) }, secret.name)))), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: t("secretsTitle"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: !canCreate, onClick: submit, children: t("createSecret") })] }), children: _jsxs("div", { style: {
63
+ }, children: [_jsx("div", { style: { ...sectionTitle, flex: 1 }, children: t("secretsTitle") }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || busy, onClick: () => setOpen(true), children: t("createSecret") })] }), secrets.length === 0 ? _jsx("p", { style: hint, children: t("none") }) : (secrets.map((secret) => (_jsx(DisclosureRow, { icon: _jsx("span", {}), title: secret.name, open: openSecret === secret.name, expandable: true, onToggle: () => setOpenSecret(openSecret === secret.name ? null : secret.name), children: _jsx("div", { style: wsBody, children: _jsx(SecretRow, { t: t, name: secret.name, busy: busy, writable: writable, onSet: onSet, onRemove: onRemove }) }) }, secret.name)))), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: t("secretsTitle"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: !canCreate, onClick: submit, children: t("createSecret") })] }), children: _jsxs("div", { style: {
64
64
  display: "flex",
65
65
  flexDirection: "column",
66
66
  gap: "12px",
67
67
  }, children: [_jsxs(Field, { label: t("secretName"), htmlFor: secretNameId, children: [_jsx(Input, { id: secretNameId, value: name, disabled: !writable || busy, autoFocus: true, onChange: (event) => setName(sanitizeName(event.target.value)), onKeyDown: (event) => {
68
68
  if (event.key === "Enter")
69
69
  submit();
70
- } }), name !== "" && !namePattern.test(name) ? (_jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })) : null] }), _jsx(Field, { label: t("secretLength"), htmlFor: secretLengthId, children: _jsx(Input, { id: secretLengthId, type: "number", min: 1, max: 1024, value: length, disabled: !writable || busy, onChange: (event) => setLength(event.target.value) }) }), _jsx(Field, { label: t("secretCharset"), htmlFor: secretCharsetId, children: _jsxs("select", { id: secretCharsetId, style: imageSelect, value: charset, disabled: !writable || busy, onChange: (event) => setCharset(event.target.value), children: [_jsx("option", { value: "alphanumeric", children: "alphanumeric" }), _jsx("option", { value: "hex", children: "hex" }), _jsx("option", { value: "base64url", children: "base64url" })] }) })] }) })] }));
70
+ } }), name !== "" && !namePattern.test(name)
71
+ ? _jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })
72
+ : null] }), _jsx(Field, { label: t("secretLength"), htmlFor: secretLengthId, children: _jsx(Input, { id: secretLengthId, type: "number", min: 1, max: 1024, value: length, disabled: !writable || busy, onChange: (event) => setLength(event.target.value) }) }), _jsx(Field, { label: t("secretCharset"), htmlFor: secretCharsetId, children: _jsxs("select", { id: secretCharsetId, style: imageSelect, value: charset, disabled: !writable || busy, onChange: (event) => setCharset(event.target.value), children: [_jsx("option", { value: "alphanumeric", children: "alphanumeric" }), _jsx("option", { value: "hex", children: "hex" }), _jsx("option", { value: "base64url", children: "base64url" })] }) })] }) })] }));
71
73
  }
@@ -1,21 +1,8 @@
1
1
  import { type MountInput, type ProjectMountView } from "./container-card-controller.js";
2
- import { type ContainerPluginKey } from "./locales.js";
2
+ import { type ContainerPluginKey, type Translate } from "./locales.js";
3
3
  import { type StateDotState } from "@deepseek-ai/dsh-client-ui-primitives";
4
4
  import { type ReactNode } from "react";
5
- export declare function CardChevron({ open }: {
6
- open: boolean;
7
- }): ReactNode;
8
5
  export declare function containerStateDot(status: string): StateDotState;
9
- export declare function ConfigField(props: {
10
- t: (key: ContainerPluginKey) => string;
11
- label: string;
12
- value: string;
13
- current: string;
14
- writable: boolean;
15
- onChange: (text: string) => void;
16
- onSave: () => void;
17
- onDiscard: () => void;
18
- }): ReactNode;
19
6
  export declare function Field(props: {
20
7
  label: string;
21
8
  htmlFor: string;
@@ -31,7 +18,7 @@ export declare const mountViewToInput: (mount: ProjectMountView) => MountInput;
31
18
  export declare const mountLabel: (t: (key: ContainerPluginKey) => string, mount: MountInput, withMode?: boolean) => string;
32
19
  export declare const emptyMount: (kind?: string) => MountInput;
33
20
  export declare function ConfirmButton(props: {
34
- t: (key: ContainerPluginKey) => string;
21
+ t: Translate;
35
22
  label: string;
36
23
  title: string;
37
24
  description: string;
@@ -40,13 +27,13 @@ export declare function ConfirmButton(props: {
40
27
  onConfirm: () => void;
41
28
  }): ReactNode;
42
29
  export declare function Chip(props: {
43
- t: (key: ContainerPluginKey) => string;
30
+ t: Translate;
44
31
  label: string;
45
32
  disabled?: boolean;
46
33
  onRemove?: () => void;
47
34
  }): ReactNode;
48
35
  export declare function TagInput(props: {
49
- t: (key: ContainerPluginKey) => string;
36
+ t: Translate;
50
37
  value: readonly string[];
51
38
  onChange: (tags: string[]) => void;
52
39
  placeholder?: string;
@@ -3,31 +3,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
3
3
  //
4
4
  // SPDX-License-Identifier: MIT
5
5
  import { hint } from "./container-card-styles.js";
6
- import { Button, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
7
- import { useId, useState } from "react";
8
- export function CardChevron({ open }) {
9
- return (_jsx("svg", { viewBox: "0 0 14 14", width: "14", height: "14", "aria-hidden": "true", style: {
10
- flex: "none",
11
- color: "var(--dsw-alias-label-tertiary)",
12
- transition: "transform .16s",
13
- transform: open ? "rotate(180deg)" : undefined,
14
- }, children: _jsx("path", { d: "M3 5l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
15
- }
6
+ import { Button, Input, Modal, } from "@deepseek-ai/dsh-client-ui-primitives";
7
+ import { useState } from "react";
16
8
  export function containerStateDot(status) {
17
9
  return status === "running" ? "done" : "error";
18
10
  }
19
- export function ConfigField(props) {
20
- const { t, label, value, current, writable, onChange, onSave, onDiscard } = props;
21
- const id = useId();
22
- const dirty = value !== current;
23
- return (_jsxs("div", { style: {
24
- display: "flex",
25
- flexWrap: "wrap",
26
- alignItems: "center",
27
- gap: "8px",
28
- padding: "8px 0",
29
- }, children: [_jsx("label", { htmlFor: id, style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary)", minWidth: "110px" }, children: label }), _jsx(Input, { id: id, value: value, disabled: !writable, onChange: (event) => onChange(event.target.value), style: { width: "200px" } }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || !dirty, onClick: onSave, children: t("save") }), _jsx(Button, { variant: "ghost", size: "sm", disabled: !dirty, onClick: onDiscard, children: t("discard") })] }));
30
- }
31
11
  export function Field(props) {
32
12
  return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "4px" }, children: [_jsx("label", { htmlFor: props.htmlFor, style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary)" }, children: props.label }), props.children] }));
33
13
  }
@@ -55,7 +35,7 @@ export const mountViewToInput = (mount) => ({
55
35
  });
56
36
  export const mountLabel = (t, mount, withMode = true) => {
57
37
  if (mount.kind === "tmpfs") {
58
- return `tmpfs${mount.destination !== "" ? ` at ${mount.destination}` : ""}`;
38
+ return `${t("mountTmpfs")}${mount.destination !== "" ? ` ${t("mountAt")} ${mount.destination}` : ""}`;
59
39
  }
60
40
  if (mount.kind === "volume") {
61
41
  return `volume ${mount.volume}${mount.destination !== "" ? ` → ${mount.destination}` : ""}`;
@@ -63,7 +43,11 @@ export const mountLabel = (t, mount, withMode = true) => {
63
43
  if (mount.kind === "secret") {
64
44
  return `secret ${mount.secret}${mount.destination !== "" ? ` → ${mount.destination}` : ""}`;
65
45
  }
66
- const mode = withMode ? (mount.mode === "read_only" ? " (ro)" : " (rw)") : "";
46
+ const mode = withMode
47
+ ? ` ${t(mount.mode === "read_only"
48
+ ? "mountReadOnlySuffix"
49
+ : "mountReadWriteSuffix")}`
50
+ : "";
67
51
  return `${mount.project}${mount.destination !== "" ? ` → ${mount.destination}` : ""}${mode}`;
68
52
  };
69
53
  // Adding a mount defaults to read-only, so granting write access is always a
@@ -99,7 +83,7 @@ export function Chip(props) {
99
83
  fontSize: "12px",
100
84
  fontFamily: "var(--dsw-alias-font-mono, ui-monospace, monospace)",
101
85
  color: "var(--dsw-alias-label-primary)",
102
- }, children: [label, onRemove === undefined ? null : (_jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("removeTag"), disabled: disabled, onClick: onRemove, style: {
86
+ }, children: [label, onRemove === undefined ? null : (_jsx(Button, { variant: "ghost", size: "sm", "aria-label": `${t("removeTag")} ${label}`, disabled: disabled, onClick: onRemove, style: {
103
87
  padding: 0,
104
88
  minWidth: "18px",
105
89
  height: "18px",
@@ -1,15 +1,8 @@
1
- export declare const cardStyle: React.CSSProperties;
2
- export declare const cardOpenStyle: React.CSSProperties;
3
- export declare const cardHoverStyle: React.CSSProperties;
4
- export declare const cardHeaderStyle: React.CSSProperties;
5
- export declare const cardHeadTextStyle: React.CSSProperties;
6
- export declare const cardNameStyle: React.CSSProperties;
7
- export declare const cardDescriptionStyle: React.CSSProperties;
8
- export declare const cardBodyStyle: React.CSSProperties;
9
1
  export declare const sectionTitle: React.CSSProperties;
10
2
  export declare const banner: React.CSSProperties;
11
3
  export declare const greyId: React.CSSProperties;
12
4
  export declare const hint: React.CSSProperties;
5
+ export declare const fieldLabel: React.CSSProperties;
13
6
  export declare const wsBody: React.CSSProperties;
14
7
  export declare const containerRow: React.CSSProperties;
15
8
  export declare const containerHeader: React.CSSProperties;
@@ -19,3 +12,4 @@ export declare const tableStyle: React.CSSProperties;
19
12
  export declare const thStyle: React.CSSProperties;
20
13
  export declare const tdStyle: React.CSSProperties;
21
14
  export declare const imageSelect: React.CSSProperties;
15
+ export declare const fieldSelect: React.CSSProperties;
@@ -1,58 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
2
2
  //
3
3
  // SPDX-License-Identifier: MIT
4
- export const cardStyle = {
5
- listStyle: "none",
6
- border: "1px solid var(--dsw-alias-border-l2)",
7
- borderRadius: "12px",
8
- background: "var(--dsw-alias-bg-layer-3)",
9
- transition: "border-color .16s, background .16s",
10
- };
11
- export const cardOpenStyle = {
12
- background: "var(--dsw-alias-bg-layer-2)",
13
- borderColor: "var(--dsw-alias-label-dimmed)",
14
- };
15
- export const cardHoverStyle = {
16
- borderColor: "var(--dsw-alias-label-dimmed)",
17
- };
18
- export const cardHeaderStyle = {
19
- width: "100%",
20
- appearance: "none",
21
- border: 0,
22
- background: "none",
23
- font: "inherit",
24
- color: "inherit",
25
- textAlign: "left",
26
- cursor: "pointer",
27
- display: "flex",
28
- alignItems: "center",
29
- gap: "12px",
30
- padding: "14px 16px",
31
- borderRadius: "12px",
32
- };
33
- export const cardHeadTextStyle = {
34
- flex: 1,
35
- minWidth: 0,
36
- display: "flex",
37
- flexDirection: "column",
38
- gap: "4px",
39
- };
40
- export const cardNameStyle = {
41
- fontSize: "15px",
42
- fontWeight: 600,
43
- lineHeight: 1.4,
44
- color: "var(--dsw-alias-label-primary)",
45
- };
46
- export const cardDescriptionStyle = {
47
- fontSize: "13px",
48
- lineHeight: 1.5,
49
- color: "var(--dsw-alias-label-tertiary)",
50
- };
51
- export const cardBodyStyle = {
52
- borderTop: "1px solid var(--dsw-alias-border-l2)",
53
- margin: "0 16px",
54
- paddingBottom: "8px",
55
- };
56
4
  export const sectionTitle = {
57
5
  fontWeight: 600,
58
6
  margin: "16px 0 8px",
@@ -78,6 +26,14 @@ export const hint = {
78
26
  color: "var(--dsw-alias-label-tertiary)",
79
27
  margin: "8px 0",
80
28
  };
29
+ export const fieldLabel = {
30
+ fontSize: "12px",
31
+ color: "var(--dsw-alias-label-tertiary)",
32
+ whiteSpace: "nowrap",
33
+ // Hugs the control when the label fills a grid cell; an auto-width flex item
34
+ // (the tab body's toolbar) is unaffected.
35
+ textAlign: "right",
36
+ };
81
37
  export const wsBody = {
82
38
  display: "flex",
83
39
  flexDirection: "column",
@@ -141,3 +97,9 @@ export const imageSelect = {
141
97
  fontSize: "12px",
142
98
  maxWidth: "160px",
143
99
  };
100
+ // A select that fills its label's row where fields are stacked; the narrow
101
+ // guide card uses it so the container and shell controls share one width.
102
+ export const fieldSelect = {
103
+ ...imageSelect,
104
+ width: "100%",
105
+ };
@@ -1,7 +1,7 @@
1
- import { type ContainerPluginKey } from "./locales.js";
1
+ import type { Translate } from "./locales.js";
2
2
  import { type ReactNode } from "react";
3
3
  export declare function VolumesSection(props: {
4
- t: (key: ContainerPluginKey) => string;
4
+ t: Translate;
5
5
  volumes: readonly {
6
6
  name: string;
7
7
  }[];
@@ -2,9 +2,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  // SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
3
  //
4
4
  // SPDX-License-Identifier: MIT
5
- import { ConfirmButton, Field, namePattern, sanitizeName } from "./container-card-shared.js";
5
+ import { ConfirmButton, Field, namePattern, sanitizeName, } from "./container-card-shared.js";
6
6
  import { hint, sectionTitle, wsBody } from "./container-card-styles.js";
7
- import { Button, DisclosureRow, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
7
+ import { Button, DisclosureRow, Input, Modal, } from "@deepseek-ai/dsh-client-ui-primitives";
8
8
  import { useId, useState } from "react";
9
9
  export function VolumesSection(props) {
10
10
  const { t, volumes, busy, writable, onCreate, onRemove } = props;
@@ -25,13 +25,17 @@ export function VolumesSection(props) {
25
25
  flexWrap: "wrap",
26
26
  alignItems: "center",
27
27
  gap: "8px",
28
- }, children: [_jsx("div", { style: { ...sectionTitle, flex: 1 }, children: t("volumesTitle") }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || busy, onClick: () => setOpen(true), children: t("createVolume") })] }), volumes.length === 0 ? (_jsx("p", { style: hint, children: t("none") })) : (volumes.map((volume) => (_jsx(DisclosureRow, { icon: _jsx("span", {}), title: volume.name, open: openVolume === volume.name, expandable: true, onToggle: () => setOpenVolume(openVolume === volume.name ? null : volume.name), children: _jsx("div", { style: wsBody, children: _jsx("div", { style: {
28
+ }, children: [_jsx("div", { style: { ...sectionTitle, flex: 1 }, children: t("volumesTitle") }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || busy, onClick: () => setOpen(true), children: t("createVolume") })] }), volumes.length === 0 ? _jsx("p", { style: hint, children: t("none") }) : (volumes.map((volume) => (_jsx(DisclosureRow, { icon: _jsx("span", {}), title: volume.name, open: openVolume === volume.name, expandable: true, onToggle: () => setOpenVolume(openVolume === volume.name ? null : volume.name), children: _jsx("div", { style: wsBody, children: _jsx("div", { style: {
29
29
  display: "flex",
30
30
  flexWrap: "wrap",
31
31
  alignItems: "center",
32
32
  gap: "8px",
33
- }, children: _jsx(ConfirmButton, { t: t, label: t("removeVolume"), title: t("confirmTitle"), description: t("confirmRemoveVolume"), disabled: busy, onConfirm: () => onRemove(volume.name) }) }) }) }, volume.name)))), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: t("volumesTitle"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: !canCreate, onClick: submit, children: t("createVolume") })] }), children: _jsxs(Field, { label: t("volumeName"), htmlFor: volumeId, children: [_jsx(Input, { id: volumeId, value: name, disabled: !writable || busy, autoFocus: true, onChange: (event) => setName(sanitizeName(event.target.value)), onKeyDown: (event) => {
33
+ }, children: _jsx(ConfirmButton, { t: t, label: t("removeVolume"), title: t("confirmTitle"), description: t("confirmRemoveVolume", {
34
+ volume: volume.name,
35
+ }), disabled: busy, onConfirm: () => onRemove(volume.name) }) }) }) }, volume.name)))), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: t("volumesTitle"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: !canCreate, onClick: submit, children: t("createVolume") })] }), children: _jsxs(Field, { label: t("volumeName"), htmlFor: volumeId, children: [_jsx(Input, { id: volumeId, value: name, disabled: !writable || busy, autoFocus: true, onChange: (event) => setName(sanitizeName(event.target.value)), onKeyDown: (event) => {
34
36
  if (event.key === "Enter")
35
37
  submit();
36
- } }), name !== "" && !namePattern.test(name) ? (_jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })) : null] }) })] }));
38
+ } }), name !== "" && !namePattern.test(name)
39
+ ? _jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })
40
+ : null] }) })] }));
37
41
  }
@@ -1,9 +1,9 @@
1
1
  import { type ContainerCreateConfig, type ContainerView, type ImageView, type MountInput, type WorkspaceView } from "./container-card-controller.js";
2
2
  import { type DirectoryPickerFace } from "./directory-picker.js";
3
- import { type ContainerPluginKey } from "./locales.js";
3
+ import type { Translate } from "./locales.js";
4
4
  import { type ReactNode } from "react";
5
5
  export declare function WorkspaceSection(props: {
6
- t: (key: ContainerPluginKey) => string;
6
+ t: Translate;
7
7
  workspace: WorkspaceView;
8
8
  containers: readonly ContainerView[];
9
9
  images: readonly ImageView[];
@@ -17,15 +17,17 @@ export declare function WorkspaceSection(props: {
17
17
  defaultImage: string;
18
18
  projectsRoot: string;
19
19
  directoryPicker?: DirectoryPickerFace;
20
- onRemove: (workspace: string) => void;
20
+ onRemove: (workspace: string, container: string) => void;
21
21
  onRemoveWorkspace: (workspace: string) => void;
22
- onRecreate: (workspace: string, image: string, env?: Record<string, string>) => void;
22
+ onRecreate: (workspace: string, container: string, image: string, env?: Record<string, string>) => void;
23
23
  onCreate: (workspace: WorkspaceView, config?: ContainerCreateConfig) => void;
24
24
  onStartContainer: (workspace: WorkspaceView, container: string, config?: ContainerCreateConfig) => void;
25
25
  onAddContainerMount: (workspace: string, container: string, mount: MountInput) => void;
26
26
  onRemoveContainerMount: (workspace: string, container: string, mount: MountInput) => void;
27
27
  onUpdateContainerMount: (workspace: string, container: string, mount: MountInput) => void;
28
28
  onSetContainerPaths: (workspace: string, container: string, paths: readonly string[]) => void;
29
- onAddContainerSecret: (workspace: string, envVar: string, secret: string) => void;
30
- onRemoveContainerSecret: (workspace: string, envVar: string) => void;
29
+ onAddContainerSecret: (workspace: string, container: string, envVar: string, secret: string) => void;
30
+ onRemoveContainerSecret: (workspace: string, container: string, envVar: string) => void;
31
+ onSyncDefaults: (workspace: string) => void;
32
+ defaultEnvCount: number;
31
33
  }): ReactNode;
@@ -5,14 +5,20 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
5
5
  import { ContainerRow, CreateContainerModal } from "./container-card-row.js";
6
6
  import { ConfirmButton, containerStateDot } from "./container-card-shared.js";
7
7
  import { greyId, hint, wsBody } from "./container-card-styles.js";
8
- import { Button, DisclosureRow, StateDot } from "@deepseek-ai/dsh-client-ui-primitives";
8
+ import { Button, DisclosureRow, StateDot, } from "@deepseek-ai/dsh-client-ui-primitives";
9
9
  import { useState } from "react";
10
10
  export function WorkspaceSection(props) {
11
- const { t, workspace, containers, images, volumes, secrets, busy, defaultImage, projectsRoot, directoryPicker, onRemove, onRemoveWorkspace, onRecreate, onCreate, onStartContainer, onAddContainerMount, onRemoveContainerMount, onUpdateContainerMount, onSetContainerPaths, onAddContainerSecret, onRemoveContainerSecret, } = props;
11
+ const { t, workspace, containers, images, volumes, secrets, busy, defaultImage, projectsRoot, directoryPicker, onRemove, onRemoveWorkspace, onRecreate, onCreate, onStartContainer, onAddContainerMount, onRemoveContainerMount, onUpdateContainerMount, onSetContainerPaths, onAddContainerSecret, onRemoveContainerSecret, onSyncDefaults, defaultEnvCount, } = props;
12
12
  const [open, setOpen] = useState(false);
13
13
  const [createOpen, setCreateOpen] = useState(null);
14
14
  const hasContainer = containers.length > 0;
15
- return (_jsxs(DisclosureRow, { icon: hasContainer ? (_jsx(StateDot, { state: containerStateDot(containers[0]?.status ?? "") })) : (_jsx("span", {})), title: workspace.projectName, open: open, expandable: true, onToggle: () => setOpen(!open), children: [_jsxs("div", { style: wsBody, children: [_jsx("code", { style: greyId, children: workspace.workspaceSlug }), !hasContainer ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsx("p", { style: { ...hint, margin: 0 }, children: t("noContainers") }), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => setCreateOpen("default"), children: t("createContainer") }) })] })) : (_jsxs(_Fragment, { children: [containers.map((container) => (_jsx(ContainerRow, { t: t, container: container, images: images, volumes: volumes, secrets: secrets, busy: busy, onRemove: onRemove, onRecreate: onRecreate, onAddContainerMount: onAddContainerMount, onRemoveContainerMount: onRemoveContainerMount, onUpdateContainerMount: onUpdateContainerMount, onSetContainerPaths: onSetContainerPaths, projectName: workspace.projectName, projectsRoot: projectsRoot, directoryPicker: directoryPicker, onAddContainerSecret: onAddContainerSecret, onRemoveContainerSecret: onRemoveContainerSecret }, container.containerName))), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => setCreateOpen("named"), children: t("addContainer") }) })] })), _jsx("div", { children: _jsx(ConfirmButton, { t: t, label: t("removePod"), title: t("confirmTitle"), description: t("confirmRemovePod"), disabled: busy, onConfirm: () => onRemoveWorkspace(workspace.workspaceSlug) }) })] }), _jsx(CreateContainerModal, { t: t, workspace: workspace, images: images, volumes: volumes, secrets: secrets, busy: busy, defaultImage: defaultImage, projectsRoot: projectsRoot, directoryPicker: directoryPicker, named: createOpen === "named", existing: containers.map((container) => container.containerName), open: createOpen !== null, onClose: () => setCreateOpen(null), onCreate: (container, config) => {
15
+ return (_jsxs(DisclosureRow, { icon: hasContainer
16
+ ? _jsx(StateDot, { state: containerStateDot(containers[0]?.status ?? "") })
17
+ : _jsx("span", {}), title: workspace.projectName, open: open, expandable: true, onToggle: () => setOpen(!open), children: [_jsxs("div", { style: wsBody, children: [_jsx("code", { style: greyId, children: workspace.workspaceSlug }), !hasContainer
18
+ ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsx("p", { style: { ...hint, margin: 0 }, children: t("noContainers") }), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => setCreateOpen("default"), children: t("createContainer") }) })] }))
19
+ : (_jsxs(_Fragment, { children: [containers.map((container) => (_jsx(ContainerRow, { t: t, container: container, images: images, volumes: volumes, secrets: secrets, busy: busy, onRemove: onRemove, onRecreate: onRecreate, onAddContainerMount: onAddContainerMount, onRemoveContainerMount: onRemoveContainerMount, onUpdateContainerMount: onUpdateContainerMount, onSetContainerPaths: onSetContainerPaths, projectName: workspace.projectName, projectsRoot: projectsRoot, directoryPicker: directoryPicker, onAddContainerSecret: onAddContainerSecret, onRemoveContainerSecret: onRemoveContainerSecret }, container.containerName))), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => setCreateOpen("named"), children: t("addContainer") }) })] })), _jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: [_jsx(ConfirmButton, { t: t, label: t("applyDefaults"), title: t("confirmTitle"), description: t("confirmApplyDefaults"), disabled: busy || defaultEnvCount === 0, onConfirm: () => onSyncDefaults(workspace.workspaceSlug) }), _jsx(ConfirmButton, { t: t, label: t("removePod"), title: t("confirmTitle"), description: t("confirmRemovePod", {
20
+ workspace: workspace.projectName,
21
+ }), disabled: busy, onConfirm: () => onRemoveWorkspace(workspace.workspaceSlug) })] })] }), _jsx(CreateContainerModal, { t: t, workspace: workspace, images: images, volumes: volumes, secrets: secrets, busy: busy, defaultImage: defaultImage, projectsRoot: projectsRoot, directoryPicker: directoryPicker, named: createOpen === "named", existing: containers.map((container) => container.containerName), open: createOpen !== null, onClose: () => setCreateOpen(null), onCreate: (container, config) => {
16
22
  if (createOpen === "named") {
17
23
  onStartContainer(workspace, container, config);
18
24
  }
@@ -17,8 +17,9 @@ export class DirectoryPickerError extends Error {
17
17
  // then hides the browse affordance and the path stays a plain input).
18
18
  export function buildDirectoryPicker(remote) {
19
19
  const picker = remote?.directoryPicker;
20
- if (picker === undefined || typeof picker.list !== "function")
20
+ if (picker === undefined || typeof picker.list !== "function") {
21
21
  return undefined;
22
+ }
22
23
  return {
23
24
  async list(path, signal) {
24
25
  const result = await picker.list(path, signal);