@exagone313/dsh-podman 0.2.0-rc.2
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/LICENSE +21 -0
- package/README.md +41 -0
- package/README.zh.md +41 -0
- package/cordis.patch.yml +17 -0
- package/dist/approval-reasons.d.ts +129 -0
- package/dist/approval-reasons.js +253 -0
- package/dist/approval-reasons.test.d.ts +1 -0
- package/dist/approval-reasons.test.js +115 -0
- package/dist/approval.d.ts +29 -0
- package/dist/approval.js +412 -0
- package/dist/approval.test.d.ts +1 -0
- package/dist/approval.test.js +462 -0
- package/dist/card-route.d.ts +15 -0
- package/dist/card-route.js +478 -0
- package/dist/card-route.test.d.ts +1 -0
- package/dist/card-route.test.js +181 -0
- package/dist/card-test-support.d.ts +15 -0
- package/dist/card-test-support.js +160 -0
- package/dist/client/ContainerCard.d.ts +6 -0
- package/dist/client/ContainerCard.js +79 -0
- package/dist/client/card-client.d.ts +6 -0
- package/dist/client/card-client.js +47 -0
- package/dist/client/card-protocol.d.ts +101 -0
- package/dist/client/card-protocol.js +10 -0
- package/dist/client/container-card-caches.d.ts +10 -0
- package/dist/client/container-card-caches.js +31 -0
- package/dist/client/container-card-controller.d.ts +107 -0
- package/dist/client/container-card-controller.js +207 -0
- package/dist/client/container-card-create-modal.d.ts +24 -0
- package/dist/client/container-card-create-modal.js +109 -0
- package/dist/client/container-card-directory-styles.d.ts +5 -0
- package/dist/client/container-card-directory-styles.js +246 -0
- package/dist/client/container-card-directory.d.ts +12 -0
- package/dist/client/container-card-directory.js +144 -0
- package/dist/client/container-card-editors.d.ts +30 -0
- package/dist/client/container-card-editors.js +116 -0
- package/dist/client/container-card-images.d.ts +37 -0
- package/dist/client/container-card-images.js +35 -0
- package/dist/client/container-card-paths.d.ts +20 -0
- package/dist/client/container-card-paths.js +88 -0
- package/dist/client/container-card-row.d.ts +30 -0
- package/dist/client/container-card-row.js +54 -0
- package/dist/client/container-card-secrets.d.ts +21 -0
- package/dist/client/container-card-secrets.js +71 -0
- package/dist/client/container-card-shared.d.ts +55 -0
- package/dist/client/container-card-shared.js +152 -0
- package/dist/client/container-card-styles.d.ts +21 -0
- package/dist/client/container-card-styles.js +143 -0
- package/dist/client/container-card-volumes.d.ts +12 -0
- package/dist/client/container-card-volumes.js +37 -0
- package/dist/client/container-card-workspace.d.ts +31 -0
- package/dist/client/container-card-workspace.js +24 -0
- package/dist/client/directory-picker.d.ts +12 -0
- package/dist/client/directory-picker.js +30 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +4419 -0
- package/dist/client/locales.d.ts +9 -0
- package/dist/client/locales.js +370 -0
- package/dist/client/read-only-approval.d.ts +8 -0
- package/dist/client/read-only-approval.js +63 -0
- package/dist/client/slot-contract.d.ts +12 -0
- package/dist/client/slot-contract.js +4 -0
- package/dist/client/terminal-styles.d.ts +5 -0
- package/dist/client/terminal-styles.js +31 -0
- package/dist/client/tool-views.d.ts +8 -0
- package/dist/client/tool-views.js +241 -0
- package/dist/containers.test.d.ts +1 -0
- package/dist/containers.test.js +443 -0
- package/dist/daemons.test.d.ts +1 -0
- package/dist/daemons.test.js +153 -0
- package/dist/fs-provider.d.ts +23 -0
- package/dist/fs-provider.js +184 -0
- package/dist/fs-provider.test.d.ts +1 -0
- package/dist/fs-provider.test.js +260 -0
- package/dist/generated/version.d.ts +2 -0
- package/dist/generated/version.js +6 -0
- package/dist/grpc/proto/dshctl/v1/control.proto +133 -0
- package/dist/grpc/proto/dshguest/v1/guest.proto +79 -0
- package/dist/grpc/runtime-client.d.ts +5 -0
- package/dist/grpc/runtime-client.js +43 -0
- package/dist/guest-rpc.d.ts +62 -0
- package/dist/guest-rpc.js +391 -0
- package/dist/images.test.d.ts +1 -0
- package/dist/images.test.js +79 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +140 -0
- package/dist/misc.test.d.ts +1 -0
- package/dist/misc.test.js +141 -0
- package/dist/mount-enums.d.ts +22 -0
- package/dist/mount-enums.js +81 -0
- package/dist/mount-input.d.ts +14 -0
- package/dist/mount-input.js +64 -0
- package/dist/mounts.test.d.ts +1 -0
- package/dist/mounts.test.js +408 -0
- package/dist/output-reader.d.ts +34 -0
- package/dist/output-reader.js +87 -0
- package/dist/paths.test.d.ts +1 -0
- package/dist/paths.test.js +74 -0
- package/dist/preferences.d.ts +6 -0
- package/dist/preferences.js +27 -0
- package/dist/project-path.d.ts +6 -0
- package/dist/project-path.js +73 -0
- package/dist/project-path.test.d.ts +1 -0
- package/dist/project-path.test.js +52 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.js +149 -0
- package/dist/public.d.ts +4 -0
- package/dist/public.js +63 -0
- package/dist/read-only-shell.d.ts +14 -0
- package/dist/read-only-shell.js +150 -0
- package/dist/read-only-shell.test.d.ts +1 -0
- package/dist/read-only-shell.test.js +127 -0
- package/dist/secrets.test.d.ts +1 -0
- package/dist/secrets.test.js +181 -0
- package/dist/settings-commands.test.d.ts +1 -0
- package/dist/settings-commands.test.js +549 -0
- package/dist/settings-create.test.d.ts +1 -0
- package/dist/settings-create.test.js +394 -0
- package/dist/settings-mounts.test.d.ts +1 -0
- package/dist/settings-mounts.test.js +467 -0
- package/dist/settings-paths.test.d.ts +1 -0
- package/dist/settings-paths.test.js +65 -0
- package/dist/settings-schema.d.ts +14 -0
- package/dist/settings-schema.js +14 -0
- package/dist/settings-workspaces.test.d.ts +1 -0
- package/dist/settings-workspaces.test.js +265 -0
- package/dist/spill-store.d.ts +17 -0
- package/dist/spill-store.js +54 -0
- package/dist/spill-store.test.d.ts +1 -0
- package/dist/spill-store.test.js +59 -0
- package/dist/subprocess.d.ts +9 -0
- package/dist/subprocess.js +369 -0
- package/dist/subprocess.test.d.ts +1 -0
- package/dist/subprocess.test.js +234 -0
- package/dist/test-support.d.ts +328 -0
- package/dist/test-support.js +437 -0
- package/dist/tool-defs.d.ts +26 -0
- package/dist/tool-defs.js +188 -0
- package/dist/tool-handlers.d.ts +2 -0
- package/dist/tool-handlers.js +546 -0
- package/dist/tool-params.d.ts +803 -0
- package/dist/tool-params.js +494 -0
- package/dist/tool-schemas.d.ts +2 -0
- package/dist/tool-schemas.js +5 -0
- package/dist/tool-views.d.ts +2 -0
- package/dist/tool-views.js +56 -0
- package/dist/views.test.d.ts +1 -0
- package/dist/views.test.js +23 -0
- package/dist/volumes.test.d.ts +1 -0
- package/dist/volumes.test.js +40 -0
- package/dist/workspace-binding.d.ts +40 -0
- package/dist/workspace-binding.js +275 -0
- package/dist/workspace-binding.test.d.ts +1 -0
- package/dist/workspace-binding.test.js +337 -0
- package/docs/architecture.md +173 -0
- package/docs/architecture.zh.md +129 -0
- package/docs/configuration.md +160 -0
- package/docs/configuration.zh.md +147 -0
- package/docs/development.md +142 -0
- package/docs/development.zh.md +135 -0
- package/docs/install-dsh-and-dsh-podman.md +165 -0
- package/docs/install-dsh-and-dsh-podman.zh.md +145 -0
- package/docs/usage.md +393 -0
- package/docs/usage.zh.md +315 -0
- package/package.json +119 -0
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
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
|
+
import { useId, useState } from "react";
|
|
9
|
+
export function SecretRow(props) {
|
|
10
|
+
const { t, name, busy, writable, onSet, onRemove } = props;
|
|
11
|
+
const [content, setContent] = useState("");
|
|
12
|
+
const canSave = content !== "" && !busy;
|
|
13
|
+
const submit = () => {
|
|
14
|
+
if (content === "")
|
|
15
|
+
return;
|
|
16
|
+
onSet(name, content);
|
|
17
|
+
setContent("");
|
|
18
|
+
};
|
|
19
|
+
return (_jsxs("div", { style: {
|
|
20
|
+
display: "flex",
|
|
21
|
+
flexWrap: "wrap",
|
|
22
|
+
alignItems: "center",
|
|
23
|
+
gap: "8px",
|
|
24
|
+
}, children: [_jsx("code", { style: {
|
|
25
|
+
...greyId,
|
|
26
|
+
flex: 1,
|
|
27
|
+
fontSize: "13px",
|
|
28
|
+
color: "var(--dsw-alias-label-primary)",
|
|
29
|
+
}, children: name }), _jsx(Input, { type: "password", value: content, disabled: !writable || busy, placeholder: t("setSecret"), onChange: (event) => setContent(event.target.value), onKeyDown: (event) => {
|
|
30
|
+
if (event.key === "Enter")
|
|
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) })] }));
|
|
33
|
+
}
|
|
34
|
+
export function SecretsSection(props) {
|
|
35
|
+
const { t, secrets, busy, writable, onCreate, onRemove, onSet } = props;
|
|
36
|
+
const [open, setOpen] = useState(false);
|
|
37
|
+
const [openSecret, setOpenSecret] = useState(null);
|
|
38
|
+
const [name, setName] = useState("");
|
|
39
|
+
const [length, setLength] = useState("32");
|
|
40
|
+
const [charset, setCharset] = useState("alphanumeric");
|
|
41
|
+
const secretNameId = useId();
|
|
42
|
+
const secretLengthId = useId();
|
|
43
|
+
const secretCharsetId = useId();
|
|
44
|
+
const canCreate = namePattern.test(name);
|
|
45
|
+
const submit = () => {
|
|
46
|
+
if (!namePattern.test(name))
|
|
47
|
+
return;
|
|
48
|
+
const parsedLength = parseInt(length, 10);
|
|
49
|
+
const normalized = length.trim() === "" || Number.isNaN(parsedLength) || parsedLength < 1
|
|
50
|
+
? undefined
|
|
51
|
+
: parsedLength;
|
|
52
|
+
onCreate(name.trim(), normalized, charset);
|
|
53
|
+
setName("");
|
|
54
|
+
setLength("32");
|
|
55
|
+
setCharset("alphanumeric");
|
|
56
|
+
setOpen(false);
|
|
57
|
+
};
|
|
58
|
+
return (_jsxs("section", { children: [_jsxs("div", { style: {
|
|
59
|
+
display: "flex",
|
|
60
|
+
flexWrap: "wrap",
|
|
61
|
+
alignItems: "center",
|
|
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: {
|
|
64
|
+
display: "flex",
|
|
65
|
+
flexDirection: "column",
|
|
66
|
+
gap: "12px",
|
|
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
|
+
if (event.key === "Enter")
|
|
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" })] }) })] }) })] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type MountInput, type ProjectMountView } from "./container-card-controller.js";
|
|
2
|
+
import { type ContainerPluginKey } from "./locales.js";
|
|
3
|
+
import { type StateDotState } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
4
|
+
import { type ReactNode } from "react";
|
|
5
|
+
export declare function CardChevron({ open }: {
|
|
6
|
+
open: boolean;
|
|
7
|
+
}): ReactNode;
|
|
8
|
+
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
|
+
export declare function Field(props: {
|
|
20
|
+
label: string;
|
|
21
|
+
htmlFor: string;
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}): ReactNode;
|
|
24
|
+
export declare const namePattern: RegExp;
|
|
25
|
+
export declare const imageIdPattern: RegExp;
|
|
26
|
+
export declare const sanitizeName: (raw: string) => string;
|
|
27
|
+
export declare const sanitizeImageId: (raw: string) => string;
|
|
28
|
+
export declare const mountKindShort: (kind: string) => string;
|
|
29
|
+
export declare const mountModeShort: (mode: string) => string;
|
|
30
|
+
export declare const mountViewToInput: (mount: ProjectMountView) => MountInput;
|
|
31
|
+
export declare const mountLabel: (t: (key: ContainerPluginKey) => string, mount: MountInput, withMode?: boolean) => string;
|
|
32
|
+
export declare const emptyMount: (kind?: string) => MountInput;
|
|
33
|
+
export declare function ConfirmButton(props: {
|
|
34
|
+
t: (key: ContainerPluginKey) => string;
|
|
35
|
+
label: string;
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
ariaLabel?: string;
|
|
40
|
+
onConfirm: () => void;
|
|
41
|
+
}): ReactNode;
|
|
42
|
+
export declare function Chip(props: {
|
|
43
|
+
t: (key: ContainerPluginKey) => string;
|
|
44
|
+
label: string;
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
onRemove?: () => void;
|
|
47
|
+
}): ReactNode;
|
|
48
|
+
export declare function TagInput(props: {
|
|
49
|
+
t: (key: ContainerPluginKey) => string;
|
|
50
|
+
value: readonly string[];
|
|
51
|
+
onChange: (tags: string[]) => void;
|
|
52
|
+
placeholder?: string;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
id?: string;
|
|
55
|
+
}): ReactNode;
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
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
|
+
}
|
|
16
|
+
export function containerStateDot(status) {
|
|
17
|
+
return status === "running" ? "done" : "error";
|
|
18
|
+
}
|
|
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
|
+
export function Field(props) {
|
|
32
|
+
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
|
+
}
|
|
34
|
+
export const namePattern = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
|
|
35
|
+
export const imageIdPattern = /^[a-zA-Z0-9_][a-zA-Z0-9_.\-/:]{0,127}$/;
|
|
36
|
+
export const sanitizeName = (raw) => raw.replace(/[^a-zA-Z0-9_.-]/g, "");
|
|
37
|
+
export const sanitizeImageId = (raw) => raw.replace(/[^a-zA-Z0-9_.\-/:]/g, "");
|
|
38
|
+
export const mountKindShort = (kind) => {
|
|
39
|
+
if (kind === "MOUNT_KIND_TMPFS")
|
|
40
|
+
return "tmpfs";
|
|
41
|
+
if (kind === "MOUNT_KIND_VOLUME")
|
|
42
|
+
return "volume";
|
|
43
|
+
if (kind === "MOUNT_KIND_SECRET")
|
|
44
|
+
return "secret";
|
|
45
|
+
return "project";
|
|
46
|
+
};
|
|
47
|
+
export const mountModeShort = (mode) => mode === "MOUNT_MODE_READ_ONLY" ? "read_only" : "read_write";
|
|
48
|
+
export const mountViewToInput = (mount) => ({
|
|
49
|
+
kind: mountKindShort(mount.kind),
|
|
50
|
+
project: mount.projectName,
|
|
51
|
+
destination: mount.destination,
|
|
52
|
+
mode: mountModeShort(mount.mode),
|
|
53
|
+
volume: mount.volume,
|
|
54
|
+
secret: mount.secret,
|
|
55
|
+
});
|
|
56
|
+
export const mountLabel = (t, mount, withMode = true) => {
|
|
57
|
+
if (mount.kind === "tmpfs") {
|
|
58
|
+
return `tmpfs${mount.destination !== "" ? ` at ${mount.destination}` : ""}`;
|
|
59
|
+
}
|
|
60
|
+
if (mount.kind === "volume") {
|
|
61
|
+
return `volume ${mount.volume}${mount.destination !== "" ? ` → ${mount.destination}` : ""}`;
|
|
62
|
+
}
|
|
63
|
+
if (mount.kind === "secret") {
|
|
64
|
+
return `secret ${mount.secret}${mount.destination !== "" ? ` → ${mount.destination}` : ""}`;
|
|
65
|
+
}
|
|
66
|
+
const mode = withMode ? (mount.mode === "read_only" ? " (ro)" : " (rw)") : "";
|
|
67
|
+
return `${mount.project}${mount.destination !== "" ? ` → ${mount.destination}` : ""}${mode}`;
|
|
68
|
+
};
|
|
69
|
+
// Adding a mount defaults to read-only, so granting write access is always a
|
|
70
|
+
// deliberate choice. tmpfs is scratch space and must be writable.
|
|
71
|
+
export const emptyMount = (kind = "project") => ({
|
|
72
|
+
kind,
|
|
73
|
+
project: "",
|
|
74
|
+
destination: "",
|
|
75
|
+
mode: kind === "tmpfs" ? "read_write" : "read_only",
|
|
76
|
+
volume: "",
|
|
77
|
+
secret: "",
|
|
78
|
+
});
|
|
79
|
+
export function ConfirmButton(props) {
|
|
80
|
+
const { t, label, title, description, disabled, ariaLabel, onConfirm } = props;
|
|
81
|
+
const [open, setOpen] = useState(false);
|
|
82
|
+
const confirm = () => {
|
|
83
|
+
setOpen(false);
|
|
84
|
+
onConfirm();
|
|
85
|
+
};
|
|
86
|
+
return (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", disabled: disabled, "aria-label": ariaLabel, onClick: () => setOpen(true), children: label }), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: title, 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", onClick: confirm, children: t("confirm") })] }), children: _jsx("p", { style: { ...hint, margin: 0 }, children: description }) })] }));
|
|
87
|
+
}
|
|
88
|
+
export function Chip(props) {
|
|
89
|
+
const { t, label, disabled, onRemove } = props;
|
|
90
|
+
return (_jsxs("span", { style: {
|
|
91
|
+
display: "inline-flex",
|
|
92
|
+
alignItems: "center",
|
|
93
|
+
gap: "2px",
|
|
94
|
+
height: "24px",
|
|
95
|
+
padding: onRemove === undefined ? "0 8px" : "0 4px 0 8px",
|
|
96
|
+
borderRadius: "999px",
|
|
97
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
98
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
99
|
+
fontSize: "12px",
|
|
100
|
+
fontFamily: "var(--dsw-alias-font-mono, ui-monospace, monospace)",
|
|
101
|
+
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: {
|
|
103
|
+
padding: 0,
|
|
104
|
+
minWidth: "18px",
|
|
105
|
+
height: "18px",
|
|
106
|
+
lineHeight: 1,
|
|
107
|
+
fontSize: "13px",
|
|
108
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
109
|
+
}, children: "\u00D7" }))] }));
|
|
110
|
+
}
|
|
111
|
+
export function TagInput(props) {
|
|
112
|
+
const { t, value, onChange, placeholder, disabled, id } = props;
|
|
113
|
+
const [draft, setDraft] = useState("");
|
|
114
|
+
const commit = (raw) => {
|
|
115
|
+
const tag = raw.trim();
|
|
116
|
+
if (tag === "" || value.includes(tag))
|
|
117
|
+
return;
|
|
118
|
+
onChange([...value, tag]);
|
|
119
|
+
};
|
|
120
|
+
return (_jsxs("div", { style: {
|
|
121
|
+
display: "flex",
|
|
122
|
+
flexWrap: "wrap",
|
|
123
|
+
alignItems: "center",
|
|
124
|
+
gap: "6px",
|
|
125
|
+
}, children: [value.map((tag) => (_jsx(Chip, { t: t, label: tag, disabled: disabled, onRemove: () => onChange(value.filter((item) => item !== tag)) }, tag))), _jsx(Input, { id: id, value: draft, disabled: disabled, placeholder: placeholder, onChange: (event) => setDraft(event.target.value), onKeyDown: (event) => {
|
|
126
|
+
if (event.key === " " || event.key === "," || event.key === "Enter") {
|
|
127
|
+
if (event.key === " " || event.key === ",")
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
commit(draft);
|
|
130
|
+
setDraft("");
|
|
131
|
+
}
|
|
132
|
+
else if (event.key === "Backspace" && draft === "") {
|
|
133
|
+
const last = value[value.length - 1];
|
|
134
|
+
if (last !== undefined)
|
|
135
|
+
onChange(value.slice(0, -1));
|
|
136
|
+
}
|
|
137
|
+
}, onPaste: (event) => {
|
|
138
|
+
const text = event.clipboardData.getData("text");
|
|
139
|
+
if (text === "")
|
|
140
|
+
return;
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
const tokens = text.split(/[\s,]+/);
|
|
143
|
+
const next = [...value];
|
|
144
|
+
for (const token of tokens) {
|
|
145
|
+
const tag = token.trim();
|
|
146
|
+
if (tag !== "" && !next.includes(tag))
|
|
147
|
+
next.push(tag);
|
|
148
|
+
}
|
|
149
|
+
if (next.length !== value.length)
|
|
150
|
+
onChange(next);
|
|
151
|
+
}, style: { flex: 1, minWidth: "160px" } })] }));
|
|
152
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
export declare const sectionTitle: React.CSSProperties;
|
|
10
|
+
export declare const banner: React.CSSProperties;
|
|
11
|
+
export declare const greyId: React.CSSProperties;
|
|
12
|
+
export declare const hint: React.CSSProperties;
|
|
13
|
+
export declare const wsBody: React.CSSProperties;
|
|
14
|
+
export declare const containerRow: React.CSSProperties;
|
|
15
|
+
export declare const containerHeader: React.CSSProperties;
|
|
16
|
+
export declare const actions: React.CSSProperties;
|
|
17
|
+
export declare const footerRow: React.CSSProperties;
|
|
18
|
+
export declare const tableStyle: React.CSSProperties;
|
|
19
|
+
export declare const thStyle: React.CSSProperties;
|
|
20
|
+
export declare const tdStyle: React.CSSProperties;
|
|
21
|
+
export declare const imageSelect: React.CSSProperties;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
|
+
//
|
|
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
|
+
export const sectionTitle = {
|
|
57
|
+
fontWeight: 600,
|
|
58
|
+
margin: "16px 0 8px",
|
|
59
|
+
fontSize: "13px",
|
|
60
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
61
|
+
textTransform: "uppercase",
|
|
62
|
+
letterSpacing: "0.04em",
|
|
63
|
+
};
|
|
64
|
+
export const banner = {
|
|
65
|
+
padding: "6px 10px",
|
|
66
|
+
borderRadius: "6px",
|
|
67
|
+
border: "1px solid rgba(210,153,34,0.6)",
|
|
68
|
+
margin: "12px 0",
|
|
69
|
+
fontSize: "13px",
|
|
70
|
+
};
|
|
71
|
+
export const greyId = {
|
|
72
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
73
|
+
fontSize: "12px",
|
|
74
|
+
fontFamily: "var(--dsw-alias-font-mono, ui-monospace, monospace)",
|
|
75
|
+
};
|
|
76
|
+
export const hint = {
|
|
77
|
+
fontSize: "13px",
|
|
78
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
79
|
+
margin: "8px 0",
|
|
80
|
+
};
|
|
81
|
+
export const wsBody = {
|
|
82
|
+
display: "flex",
|
|
83
|
+
flexDirection: "column",
|
|
84
|
+
gap: "8px",
|
|
85
|
+
padding: "10px 0 12px",
|
|
86
|
+
};
|
|
87
|
+
export const containerRow = {
|
|
88
|
+
display: "flex",
|
|
89
|
+
flexDirection: "column",
|
|
90
|
+
gap: "8px",
|
|
91
|
+
padding: "10px 12px",
|
|
92
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
93
|
+
borderRadius: "10px",
|
|
94
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
95
|
+
};
|
|
96
|
+
export const containerHeader = {
|
|
97
|
+
display: "flex",
|
|
98
|
+
flexWrap: "wrap",
|
|
99
|
+
alignItems: "center",
|
|
100
|
+
gap: "8px",
|
|
101
|
+
};
|
|
102
|
+
export const actions = {
|
|
103
|
+
display: "flex",
|
|
104
|
+
flexWrap: "wrap",
|
|
105
|
+
gap: "8px",
|
|
106
|
+
alignItems: "center",
|
|
107
|
+
};
|
|
108
|
+
export const footerRow = {
|
|
109
|
+
display: "flex",
|
|
110
|
+
justifyContent: "flex-end",
|
|
111
|
+
paddingTop: "12px",
|
|
112
|
+
marginTop: "12px",
|
|
113
|
+
borderTop: "1px solid var(--dsw-alias-border-l2)",
|
|
114
|
+
};
|
|
115
|
+
export const tableStyle = {
|
|
116
|
+
width: "100%",
|
|
117
|
+
borderCollapse: "collapse",
|
|
118
|
+
fontSize: "13px",
|
|
119
|
+
};
|
|
120
|
+
export const thStyle = {
|
|
121
|
+
textAlign: "left",
|
|
122
|
+
padding: "6px 10px",
|
|
123
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2)",
|
|
124
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
125
|
+
fontWeight: 600,
|
|
126
|
+
whiteSpace: "nowrap",
|
|
127
|
+
};
|
|
128
|
+
export const tdStyle = {
|
|
129
|
+
padding: "6px 10px",
|
|
130
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2)",
|
|
131
|
+
color: "var(--dsw-alias-label-primary)",
|
|
132
|
+
verticalAlign: "top",
|
|
133
|
+
};
|
|
134
|
+
export const imageSelect = {
|
|
135
|
+
appearance: "none",
|
|
136
|
+
padding: "4px 8px",
|
|
137
|
+
borderRadius: "8px",
|
|
138
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
139
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
140
|
+
color: "inherit",
|
|
141
|
+
fontSize: "12px",
|
|
142
|
+
maxWidth: "160px",
|
|
143
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ContainerPluginKey } from "./locales.js";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export declare function VolumesSection(props: {
|
|
4
|
+
t: (key: ContainerPluginKey) => string;
|
|
5
|
+
volumes: readonly {
|
|
6
|
+
name: string;
|
|
7
|
+
}[];
|
|
8
|
+
busy: boolean;
|
|
9
|
+
writable: boolean;
|
|
10
|
+
onCreate: (name: string) => void;
|
|
11
|
+
onRemove: (name: string) => void;
|
|
12
|
+
}): ReactNode;
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
import { ConfirmButton, Field, namePattern, sanitizeName } from "./container-card-shared.js";
|
|
6
|
+
import { hint, sectionTitle, wsBody } from "./container-card-styles.js";
|
|
7
|
+
import { Button, DisclosureRow, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
8
|
+
import { useId, useState } from "react";
|
|
9
|
+
export function VolumesSection(props) {
|
|
10
|
+
const { t, volumes, busy, writable, onCreate, onRemove } = props;
|
|
11
|
+
const [open, setOpen] = useState(false);
|
|
12
|
+
const [openVolume, setOpenVolume] = useState(null);
|
|
13
|
+
const [name, setName] = useState("");
|
|
14
|
+
const volumeId = useId();
|
|
15
|
+
const canCreate = namePattern.test(name);
|
|
16
|
+
const submit = () => {
|
|
17
|
+
if (!namePattern.test(name))
|
|
18
|
+
return;
|
|
19
|
+
onCreate(name.trim());
|
|
20
|
+
setName("");
|
|
21
|
+
setOpen(false);
|
|
22
|
+
};
|
|
23
|
+
return (_jsxs("section", { children: [_jsxs("div", { style: {
|
|
24
|
+
display: "flex",
|
|
25
|
+
flexWrap: "wrap",
|
|
26
|
+
alignItems: "center",
|
|
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: {
|
|
29
|
+
display: "flex",
|
|
30
|
+
flexWrap: "wrap",
|
|
31
|
+
alignItems: "center",
|
|
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) => {
|
|
34
|
+
if (event.key === "Enter")
|
|
35
|
+
submit();
|
|
36
|
+
} }), name !== "" && !namePattern.test(name) ? (_jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })) : null] }) })] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ContainerCreateConfig, type ContainerView, type ImageView, type MountInput, type WorkspaceView } from "./container-card-controller.js";
|
|
2
|
+
import { type DirectoryPickerFace } from "./directory-picker.js";
|
|
3
|
+
import { type ContainerPluginKey } from "./locales.js";
|
|
4
|
+
import { type ReactNode } from "react";
|
|
5
|
+
export declare function WorkspaceSection(props: {
|
|
6
|
+
t: (key: ContainerPluginKey) => string;
|
|
7
|
+
workspace: WorkspaceView;
|
|
8
|
+
containers: readonly ContainerView[];
|
|
9
|
+
images: readonly ImageView[];
|
|
10
|
+
volumes: readonly {
|
|
11
|
+
name: string;
|
|
12
|
+
}[];
|
|
13
|
+
secrets: readonly {
|
|
14
|
+
name: string;
|
|
15
|
+
}[];
|
|
16
|
+
busy: boolean;
|
|
17
|
+
defaultImage: string;
|
|
18
|
+
projectsRoot: string;
|
|
19
|
+
directoryPicker?: DirectoryPickerFace;
|
|
20
|
+
onRemove: (workspace: string) => void;
|
|
21
|
+
onRemoveWorkspace: (workspace: string) => void;
|
|
22
|
+
onRecreate: (workspace: string, image: string, env?: Record<string, string>) => void;
|
|
23
|
+
onCreate: (workspace: WorkspaceView, config?: ContainerCreateConfig) => void;
|
|
24
|
+
onStartContainer: (workspace: WorkspaceView, container: string, config?: ContainerCreateConfig) => void;
|
|
25
|
+
onAddContainerMount: (workspace: string, container: string, mount: MountInput) => void;
|
|
26
|
+
onRemoveContainerMount: (workspace: string, container: string, mount: MountInput) => void;
|
|
27
|
+
onUpdateContainerMount: (workspace: string, container: string, mount: MountInput) => void;
|
|
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;
|
|
31
|
+
}): ReactNode;
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
import { ContainerRow, CreateContainerModal } from "./container-card-row.js";
|
|
6
|
+
import { ConfirmButton, containerStateDot } from "./container-card-shared.js";
|
|
7
|
+
import { greyId, hint, wsBody } from "./container-card-styles.js";
|
|
8
|
+
import { Button, DisclosureRow, StateDot } from "@deepseek-ai/dsh-client-ui-primitives";
|
|
9
|
+
import { useState } from "react";
|
|
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;
|
|
12
|
+
const [open, setOpen] = useState(false);
|
|
13
|
+
const [createOpen, setCreateOpen] = useState(null);
|
|
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) => {
|
|
16
|
+
if (createOpen === "named") {
|
|
17
|
+
onStartContainer(workspace, container, config);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
onCreate(workspace, config);
|
|
21
|
+
}
|
|
22
|
+
setCreateOpen(null);
|
|
23
|
+
} })] }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DirectoryListing } from "@deepseek-ai/dsh-api-workspace-controller/types";
|
|
2
|
+
export interface DirectoryPickerFace {
|
|
3
|
+
list(path: string, signal?: AbortSignal): Promise<DirectoryListing>;
|
|
4
|
+
}
|
|
5
|
+
export declare class DirectoryPickerError extends Error {
|
|
6
|
+
readonly code: string;
|
|
7
|
+
constructor(failure: {
|
|
8
|
+
code?: unknown;
|
|
9
|
+
message?: unknown;
|
|
10
|
+
} | undefined);
|
|
11
|
+
}
|
|
12
|
+
export declare function buildDirectoryPicker(remote: any): DirectoryPickerFace | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
// A listing failure, carrying the harness's stable code so the dialog can name
|
|
5
|
+
// an unavailable backend apart from an unreadable directory.
|
|
6
|
+
export class DirectoryPickerError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(failure) {
|
|
9
|
+
super(typeof failure?.message === "string" && failure.message !== ""
|
|
10
|
+
? failure.message
|
|
11
|
+
: "directory listing failed");
|
|
12
|
+
this.code = typeof failure?.code === "string" ? failure.code : "";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// buildDirectoryPicker adapts the injected Remote namespace to the face, or
|
|
16
|
+
// returns undefined when this deployment mounts no directory picker (the card
|
|
17
|
+
// then hides the browse affordance and the path stays a plain input).
|
|
18
|
+
export function buildDirectoryPicker(remote) {
|
|
19
|
+
const picker = remote?.directoryPicker;
|
|
20
|
+
if (picker === undefined || typeof picker.list !== "function")
|
|
21
|
+
return undefined;
|
|
22
|
+
return {
|
|
23
|
+
async list(path, signal) {
|
|
24
|
+
const result = await picker.list(path, signal);
|
|
25
|
+
if (result?.ok === false)
|
|
26
|
+
throw new DirectoryPickerError(result.error);
|
|
27
|
+
return result.value;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Context as ClientContext } from "@deepseek-ai/cordis";
|
|
2
|
+
export type { ContainerCardProps } from "./ContainerCard.js";
|
|
3
|
+
export declare const name = "podman";
|
|
4
|
+
export declare const inject: string[];
|
|
5
|
+
export declare function apply(ctx: ClientContext): void;
|