dsh-plugin-container 0.0.0-stage → 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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +341 -2
  3. package/cordis.patch.yml +3 -0
  4. package/docs/ai-chat.png +0 -0
  5. package/docs/ai-new-session.png +0 -0
  6. package/docs/apple-container.png +0 -0
  7. package/docs/attach.png +0 -0
  8. package/docs/docker-console.png +0 -0
  9. package/docs/docker-overview.png +0 -0
  10. package/docs/docker-volumes.png +0 -0
  11. package/docs/image-architecture.png +0 -0
  12. package/docs/package-install.png +0 -0
  13. package/docs/provider-switch.png +0 -0
  14. package/docs/run-container.png +0 -0
  15. package/index.js +138 -0
  16. package/lib/client.js +3499 -0
  17. package/package.json +62 -4
  18. package/src/client/api.js +94 -0
  19. package/src/client/attach.jsx +133 -0
  20. package/src/client/components.jsx +301 -0
  21. package/src/client/hooks.js +161 -0
  22. package/src/client/i18n.js +496 -0
  23. package/src/client/icons.jsx +219 -0
  24. package/src/client/index.jsx +76 -0
  25. package/src/client/page.jsx +755 -0
  26. package/src/client/run-container.jsx +187 -0
  27. package/src/client/runs.js +182 -0
  28. package/src/client/store.js +91 -0
  29. package/src/client/styles.js +363 -0
  30. package/src/client/tabs/compose.jsx +94 -0
  31. package/src/client/tabs/console.jsx +211 -0
  32. package/src/client/tabs/containers.jsx +143 -0
  33. package/src/client/tabs/images.jsx +127 -0
  34. package/src/client/tabs/networks.jsx +91 -0
  35. package/src/client/tabs/overview.jsx +266 -0
  36. package/src/client/tabs/volumes.jsx +76 -0
  37. package/src/host/apple-cli.js +307 -0
  38. package/src/host/apple.js +471 -0
  39. package/src/host/attach.js +243 -0
  40. package/src/host/commands.js +235 -0
  41. package/src/host/docker-cli.js +405 -0
  42. package/src/host/jobs.js +245 -0
  43. package/src/host/parsers.js +484 -0
  44. package/src/host/runtime.js +285 -0
  45. package/src/host/service.js +832 -0
  46. package/src/host/tools.js +198 -0
  47. package/src/host/util.js +97 -0
@@ -0,0 +1,363 @@
1
+ /** Theme-aware styles for the Docker panel. */
2
+ const STYLE_TAG = 'dsh-plugin-container/styles.css'
3
+
4
+ const CSS = `
5
+ .dsh-docker-page {
6
+ box-sizing: border-box;
7
+ height: 100%;
8
+ min-height: 0;
9
+ display: flex;
10
+ flex-direction: column;
11
+ color: var(--dsw-alias-label-primary, #17181c);
12
+ background: var(--dsw-alias-bg-base, #fff);
13
+ font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
14
+ font-size: 13px;
15
+ line-height: 1.45;
16
+ }
17
+ .dsh-docker-page *,
18
+ .dsh-docker-page *::before,
19
+ .dsh-docker-page *::after { box-sizing: border-box; }
20
+ [data-platform=darwin] .dsh-docker-page { padding-top: var(--dsh-frame-top-clearance, 0px); }
21
+
22
+ .dsh-docker-header {
23
+ flex: none;
24
+ display: flex;
25
+ align-items: flex-start;
26
+ justify-content: space-between;
27
+ gap: 18px;
28
+ padding: 22px clamp(20px, 3.5vw, 40px) 14px;
29
+ border-bottom: 1px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08));
30
+ }
31
+ .dsh-docker-brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
32
+ .dsh-docker-mark {
33
+ width: 38px; height: 38px; flex: none;
34
+ display: grid; place-items: center;
35
+ border-radius: 11px;
36
+ color: var(--dsw-alias-brand-primary, #4d6bfe);
37
+ background: var(--dsw-alias-bg-layer-2, #f4f5f8);
38
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08));
39
+ }
40
+ .dsh-docker-heading { min-width: 0; }
41
+ .dsh-docker-title { margin: 0; font-size: 19px; line-height: 26px; font-weight: 600; letter-spacing: -.01em; }
42
+ .dsh-docker-subtitle { margin: 2px 0 0; color: var(--dsw-alias-label-secondary, #60636c); font-size: 12px; line-height: 18px; }
43
+ .dsh-docker-header-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
44
+ .dsh-docker-headmeta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
45
+
46
+ .dsh-docker-status {
47
+ display: inline-flex; align-items: center; gap: 6px;
48
+ height: 26px; padding: 0 10px; border-radius: 999px;
49
+ font-size: 12px; line-height: 1; white-space: nowrap;
50
+ color: var(--dsw-alias-label-secondary, #60636c);
51
+ background: var(--dsw-alias-bg-layer-2, #f4f5f8);
52
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08));
53
+ }
54
+ .dsh-docker-status[data-state=running] { color: var(--dsw-alias-state-success-primary, #168a4b); background: var(--dsw-alias-state-success-tertiary, rgba(22,138,75,.1)); }
55
+ .dsh-docker-status[data-state=stopped] { color: var(--dsw-alias-label-secondary, #60636c); }
56
+ .dsh-docker-status[data-state=starting] { color: var(--dsw-alias-state-warn-primary, #a35f00); background: var(--dsw-alias-state-warn-tertiary, rgba(163,95,0,.1)); }
57
+ .dsh-docker-status[data-state=error] { color: var(--dsw-alias-label-error, #d33c3c); background: var(--dsw-alias-state-error-primary, rgba(211,60,60,.08)); }
58
+ .dsh-docker-status[data-state=remote] { color: var(--dsw-alias-state-business-primary, #3156c8); }
59
+ .dsh-docker-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
60
+ .dsh-docker-status[data-state=running] .dsh-docker-dot { box-shadow: 0 0 0 3px var(--dsw-alias-state-success-tertiary, rgba(22,138,75,.12)); }
61
+
62
+ .dsh-docker-btn {
63
+ appearance: none; border: 0; cursor: pointer;
64
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
65
+ height: 30px; padding: 0 12px; border-radius: 7px;
66
+ font: inherit; font-size: 12.5px; font-weight: 500; white-space: nowrap;
67
+ color: var(--dsw-alias-label-primary, #17181c);
68
+ background: var(--dsw-alias-bg-layer-2, #f4f5f8);
69
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08));
70
+ transition: background .12s ease, border-color .12s ease, opacity .12s ease;
71
+ }
72
+ .dsh-docker-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.045)); }
73
+ .dsh-docker-btn:active { background: var(--dsw-alias-interactive-bg-active, rgba(0,0,0,.08)); }
74
+ .dsh-docker-btn:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, #4d6bfe); outline-offset: 2px; }
75
+ .dsh-docker-btn[disabled] { opacity: .45; cursor: not-allowed; }
76
+ .dsh-docker-btn.dsh-docker-primary {
77
+ color: var(--dsw-alias-label-primary-foreground, #fff);
78
+ background: var(--dsw-alias-button-primary-fill, #3156c8);
79
+ border-color: transparent;
80
+ }
81
+ .dsh-docker-btn.dsh-docker-primary:hover { background: var(--dsw-alias-button-primary-hover, #2848ad); }
82
+ .dsh-docker-btn.dsh-docker-danger { color: var(--dsw-alias-label-error, #d33c3c); }
83
+ .dsh-docker-btn.dsh-docker-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(211,60,60,.09)); }
84
+ .dsh-docker-btn.dsh-docker-danger-solid {
85
+ color: #fff; background: var(--dsw-alias-label-error, #d33c3c); border-color: transparent;
86
+ }
87
+ .dsh-docker-btn.dsh-docker-ghost { background: transparent; border-color: transparent; }
88
+ .dsh-docker-btn.dsh-docker-ghost:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.045)); }
89
+ .dsh-docker-btn.dsh-docker-sm { height: 26px; padding: 0 9px; font-size: 12px; border-radius: 6px; }
90
+ .dsh-docker-btn.dsh-docker-lg { height: 34px; padding: 0 14px; font-size: 13px; }
91
+ .dsh-docker-icon-btn {
92
+ appearance: none; border: .5px solid transparent; background: transparent; cursor: pointer;
93
+ width: 30px; height: 30px; border-radius: 7px; padding: 0;
94
+ display: inline-grid; place-items: center;
95
+ color: var(--dsw-alias-label-secondary, #60636c);
96
+ }
97
+ .dsh-docker-icon-btn:hover { color: var(--dsw-alias-label-primary, #17181c); background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.045)); }
98
+ .dsh-docker-icon-btn:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, #4d6bfe); outline-offset: 2px; }
99
+ .dsh-docker-icon-btn[disabled] { opacity: .4; cursor: not-allowed; }
100
+ .dsh-docker-icon-btn.dsh-docker-sm { width: 26px; height: 26px; }
101
+
102
+ .dsh-docker-select, .dsh-docker-input {
103
+ appearance: none; -webkit-appearance: none;
104
+ height: 30px; min-width: 0; padding: 0 9px; border-radius: 7px;
105
+ color: var(--dsw-alias-label-primary, #17181c);
106
+ background: var(--dsw-alias-bg-layer-1, #fff);
107
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.14));
108
+ font: inherit; font-size: 12.5px;
109
+ }
110
+ .dsh-docker-select { padding-right: 26px; background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%); background-position: calc(100% - 13px) 13px, calc(100% - 9px) 13px; background-size: 4px 4px, 4px 4px; background-repeat: no-repeat; }
111
+ .dsh-docker-select:focus, .dsh-docker-input:focus { outline: 2px solid var(--dsw-focus-ring-color, #4d6bfe); outline-offset: 1px; border-color: transparent; }
112
+ .dsh-docker-input::placeholder, .dsh-docker-select:disabled { color: var(--dsw-alias-label-tertiary, #8a8d96); }
113
+ .dsh-docker-input[disabled] { opacity: .6; }
114
+
115
+ .dsh-docker-banner {
116
+ flex: none; margin: 12px clamp(20px, 3.5vw, 40px) 0;
117
+ display: flex; align-items: flex-start; gap: 11px;
118
+ padding: 12px 14px; border-radius: 10px;
119
+ background: var(--dsw-alias-bg-layer-1, #fff);
120
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.1));
121
+ }
122
+ .dsh-docker-banner[data-tone=warn] { background: var(--dsw-alias-state-warn-tertiary, rgba(163,95,0,.07)); border-color: transparent; }
123
+ .dsh-docker-banner[data-tone=error] { background: var(--dsw-alias-bg-layer-1, #fff); border-color: var(--dsw-alias-label-error, #d33c3c); }
124
+ .dsh-docker-banner[data-tone=info] { background: var(--dsw-alias-bg-layer-2, #f7f8fa); }
125
+ .dsh-docker-banner-icon { flex: none; margin-top: 1px; color: var(--dsw-alias-label-secondary, #60636c); }
126
+ .dsh-docker-banner[data-tone=warn] .dsh-docker-banner-icon { color: var(--dsw-alias-state-warn-primary, #a35f00); }
127
+ .dsh-docker-banner[data-tone=error] .dsh-docker-banner-icon { color: var(--dsw-alias-label-error, #d33c3c); }
128
+ .dsh-docker-banner-main { min-width: 0; flex: 1; }
129
+ .dsh-docker-banner-title { font-weight: 600; font-size: 12.5px; }
130
+ .dsh-docker-banner-text { margin-top: 2px; color: var(--dsw-alias-label-secondary, #60636c); font-size: 12px; overflow-wrap: anywhere; }
131
+ .dsh-docker-banner-actions { flex: none; display: flex; align-items: center; gap: 6px; }
132
+
133
+ .dsh-docker-tabs {
134
+ flex: none; display: flex; align-items: center; gap: 2px;
135
+ padding: 8px clamp(20px, 3.5vw, 40px) 0;
136
+ overflow-x: auto;
137
+ border-bottom: 1px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08));
138
+ }
139
+ .dsh-docker-tab {
140
+ appearance: none; border: 0; background: transparent; cursor: pointer;
141
+ display: inline-flex; align-items: center; gap: 6px;
142
+ padding: 8px 10px 9px; margin-bottom: -1px;
143
+ color: var(--dsw-alias-label-secondary, #60636c);
144
+ font: inherit; font-size: 12.5px; white-space: nowrap;
145
+ border-bottom: 2px solid transparent;
146
+ }
147
+ .dsh-docker-tab:hover { color: var(--dsw-alias-label-primary, #17181c); }
148
+ .dsh-docker-tab[aria-selected=true] { color: var(--dsw-alias-label-primary, #17181c); border-bottom-color: var(--dsw-alias-brand-primary, #3156c8); font-weight: 600; }
149
+ .dsh-docker-tab:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, #4d6bfe); outline-offset: -2px; border-radius: 4px; }
150
+ .dsh-docker-tab-count {
151
+ min-width: 17px; height: 17px; padding: 0 5px; border-radius: 9px;
152
+ display: inline-grid; place-items: center;
153
+ font-size: 10.5px; font-weight: 500;
154
+ color: var(--dsw-alias-label-secondary, #60636c);
155
+ background: var(--dsw-alias-bg-layer-2, #f1f2f4);
156
+ }
157
+ .dsh-docker-tab[aria-selected=true] .dsh-docker-tab-count { color: var(--dsw-alias-brand-primary, #3156c8); background: var(--dsw-alias-bg-layer-2, #eef1fb); }
158
+
159
+ .dsh-docker-body { flex: 1; min-height: 0; overflow: auto; padding: 20px clamp(20px, 3.5vw, 40px) 36px; }
160
+ .dsh-docker-body.dsh-docker-body-fill { overflow: hidden; padding-bottom: 20px; display: flex; flex-direction: column; }
161
+ .dsh-docker-stack { display: flex; flex-direction: column; gap: 16px; }
162
+ .dsh-docker-grid-2 { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(260px, 1fr); gap: 16px; align-items: start; }
163
+ .dsh-docker-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
164
+
165
+ .dsh-docker-card {
166
+ min-width: 0; border-radius: 11px;
167
+ background: var(--dsw-alias-bg-layer-1, #fff);
168
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.1));
169
+ box-shadow: var(--dsw-elevation-soft, none);
170
+ }
171
+ .dsh-docker-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 14px 16px 0; }
172
+ .dsh-docker-card-title { margin: 0; font-size: 13px; font-weight: 600; }
173
+ .dsh-docker-card-sub { margin: 3px 0 0; color: var(--dsw-alias-label-tertiary, #8a8d96); font-size: 11.5px; }
174
+ .dsh-docker-card-body { padding: 14px 16px; }
175
+ .dsh-docker-card-body.dsh-docker-flush { padding: 0; }
176
+
177
+ .dsh-docker-stats { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 10px; }
178
+ .dsh-docker-stat {
179
+ min-width: 0; padding: 13px 14px; border-radius: 10px;
180
+ background: var(--dsw-alias-bg-layer-1, #fff);
181
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.1));
182
+ }
183
+ .dsh-docker-stat-label { display: flex; align-items: center; gap: 6px; color: var(--dsw-alias-label-tertiary, #8a8d96); font-size: 11px; white-space: nowrap; }
184
+ .dsh-docker-stat-value { margin-top: 6px; font-size: 21px; line-height: 25px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
185
+ .dsh-docker-stat-detail { margin-top: 3px; color: var(--dsw-alias-label-secondary, #60636c); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
186
+
187
+ .dsh-docker-runtime-head { display: flex; align-items: center; gap: 9px; }
188
+ .dsh-docker-runtime-name { font-weight: 600; font-size: 14px; }
189
+ .dsh-docker-runtime-tag { margin-left: 2px; }
190
+ .dsh-docker-kv { display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: 7px 12px; margin-top: 12px; font-size: 12px; }
191
+ .dsh-docker-kv-key { color: var(--dsw-alias-label-tertiary, #8a8d96); white-space: nowrap; }
192
+ .dsh-docker-kv-value { min-width: 0; color: var(--dsw-alias-label-primary, #17181c); overflow-wrap: anywhere; }
193
+ .dsh-docker-middle { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
194
+
195
+ .dsh-docker-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
196
+ .dsh-docker-toolbar-left, .dsh-docker-toolbar-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
197
+ .dsh-docker-search { position: relative; display: flex; align-items: center; }
198
+ .dsh-docker-search .dsh-docker-input { padding-left: 28px; width: 220px; max-width: 100%; }
199
+ .dsh-docker-search-icon { position: absolute; left: 9px; color: var(--dsw-alias-label-tertiary, #8a8d96); pointer-events: none; }
200
+ .dsh-docker-seg { display: inline-flex; padding: 2px; gap: 2px; border-radius: 7px; background: var(--dsw-alias-bg-layer-2, #f1f2f4); }
201
+ .dsh-docker-seg button { appearance: none; border: 0; cursor: pointer; height: 24px; padding: 0 9px; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary, #60636c); font: inherit; font-size: 12px; }
202
+ .dsh-docker-seg button[aria-pressed=true] { background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #17181c); box-shadow: var(--dsw-elevation-soft, 0 1px 2px rgba(0,0,0,.06)); }
203
+ .dsh-docker-provider-switch { flex: 0 0 auto; }
204
+ .dsh-docker-provider-switch button { height: 30px; padding: 0 10px; font-size: 12px; }
205
+ .dsh-docker-provider-switch button:disabled { cursor: not-allowed; opacity: .55; }
206
+
207
+ .dsh-docker-run-form { display: flex; flex-direction: column; gap: 12px; }
208
+ .dsh-docker-image-picker { display: flex; align-items: center; gap: 6px; }
209
+ .dsh-docker-image-picker .dsh-docker-select { flex: 1; min-width: 0; }
210
+ .dsh-docker-run-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
211
+ .dsh-docker-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
212
+ .dsh-docker-field > span { font-size: 11.5px; color: var(--dsw-alias-label-secondary, #60636c); }
213
+ .dsh-docker-textarea { height: auto; min-height: 72px; padding: 8px 10px; resize: vertical; font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); line-height: 1.5; }
214
+ .dsh-docker-form-error { color: var(--dsw-alias-label-error, #d33c3c); font-size: 12px; }
215
+
216
+
217
+ .dsh-docker-table-wrap { overflow: auto; border-radius: 0 0 11px 11px; }
218
+ .dsh-docker-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; }
219
+ .dsh-docker-table th {
220
+ position: sticky; top: 0; z-index: 1;
221
+ text-align: left; font-weight: 500; color: var(--dsw-alias-label-tertiary, #8a8d96);
222
+ padding: 9px 12px; background: var(--dsw-alias-bg-layer-1, #fff);
223
+ border-bottom: 1px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08));
224
+ white-space: nowrap;
225
+ }
226
+ .dsh-docker-table td { padding: 10px 12px; vertical-align: middle; border-bottom: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.07)); }
227
+ .dsh-docker-table tbody tr:hover td { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.025)); }
228
+ .dsh-docker-table tbody tr:last-child td { border-bottom: 0; }
229
+ .dsh-docker-table .dsh-docker-col-actions { width: 1%; white-space: nowrap; text-align: right; }
230
+ .dsh-docker-table .dsh-docker-col-num { text-align: right; font-variant-numeric: tabular-nums; }
231
+ .dsh-docker-row-title { font-weight: 500; color: var(--dsw-alias-label-primary, #17181c); }
232
+ .dsh-docker-row-actions { display: inline-flex; align-items: center; gap: 2px; justify-content: flex-end; }
233
+ .dsh-docker-mono { font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 11.5px; }
234
+ .dsh-docker-muted { color: var(--dsw-alias-label-tertiary, #8a8d96); }
235
+ .dsh-docker-truncate { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
236
+ .dsh-docker-chips { display: flex; flex-wrap: wrap; gap: 4px; }
237
+ .dsh-docker-chip {
238
+ display: inline-flex; align-items: center; gap: 4px;
239
+ max-width: 240px; padding: 1px 6px; border-radius: 4px;
240
+ color: var(--dsw-alias-label-secondary, #60636c);
241
+ background: var(--dsw-alias-bg-layer-2, #f4f5f8);
242
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.07));
243
+ font-size: 10.5px; line-height: 17px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
244
+ }
245
+ .dsh-docker-chip.dsh-docker-chip-code { font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); }
246
+ .dsh-docker-state { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; font-size: 11.5px; }
247
+ .dsh-docker-state .dsh-docker-dot { width: 6px; height: 6px; }
248
+ .dsh-docker-state[data-state=running] { color: var(--dsw-alias-state-success-primary, #168a4b); }
249
+ .dsh-docker-state[data-state=paused], .dsh-docker-state[data-state=restarting] { color: var(--dsw-alias-state-warn-primary, #a35f00); }
250
+ .dsh-docker-state[data-state=exited], .dsh-docker-state[data-state=dead] { color: var(--dsw-alias-label-tertiary, #8a8d96); }
251
+ .dsh-docker-state[data-state=dead] { color: var(--dsw-alias-label-error, #d33c3c); }
252
+
253
+ .dsh-docker-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 8px; padding: 52px 20px; }
254
+ .dsh-docker-empty-icon { width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center; color: var(--dsw-alias-label-tertiary, #8a8d96); background: var(--dsw-alias-bg-layer-2, #f4f5f8); }
255
+ .dsh-docker-empty-title { font-size: 13px; font-weight: 600; }
256
+ .dsh-docker-empty-text { max-width: 420px; color: var(--dsw-alias-label-tertiary, #8a8d96); font-size: 12px; line-height: 19px; }
257
+ .dsh-docker-error { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px; padding: 44px 20px; }
258
+ .dsh-docker-error-title { font-weight: 600; color: var(--dsw-alias-label-error, #d33c3c); font-size: 13px; }
259
+ .dsh-docker-error-text { max-width: 560px; color: var(--dsw-alias-label-secondary, #60636c); font-size: 12px; overflow-wrap: anywhere; }
260
+
261
+ .dsh-docker-skeleton { display: flex; flex-direction: column; gap: 9px; padding: 16px; }
262
+ .dsh-docker-skel-line { height: 12px; border-radius: 5px; background: linear-gradient(90deg, var(--dsw-alias-bg-layer-2, #f1f2f4), var(--dsw-alias-bg-layer-3, #e7e9ee), var(--dsw-alias-bg-layer-2, #f1f2f4)); background-size: 200% 100%; animation: dsh-docker-shimmer 1.2s ease-in-out infinite; }
263
+ .dsh-docker-skel-line:nth-child(2) { width: 80%; }
264
+ .dsh-docker-skel-line:nth-child(3) { width: 64%; }
265
+
266
+ .dsh-docker-modal-backdrop {
267
+ position: fixed; inset: 0; z-index: 1000;
268
+ display: grid; place-items: center; padding: 24px;
269
+ background: rgba(12, 14, 20, .32);
270
+ backdrop-filter: blur(2px);
271
+ }
272
+ .dsh-docker-modal {
273
+ width: min(560px, 100%); max-height: min(78vh, 720px);
274
+ display: flex; flex-direction: column;
275
+ border-radius: 13px; overflow: hidden;
276
+ background: var(--dsw-alias-bg-layer-1, #fff);
277
+ border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.12));
278
+ box-shadow: var(--dsw-elevation-prominent, 0 20px 60px rgba(0,0,0,.22));
279
+ }
280
+ .dsh-docker-modal.dsh-docker-modal-wide { width: min(820px, 100%); }
281
+ .dsh-docker-modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 16px 18px 12px; border-bottom: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08)); }
282
+ .dsh-docker-modal-title { margin: 0; font-size: 14px; font-weight: 600; }
283
+ .dsh-docker-modal-sub { margin: 3px 0 0; color: var(--dsw-alias-label-tertiary, #8a8d96); font-size: 11.5px; }
284
+ .dsh-docker-modal-body { min-height: 0; overflow: auto; padding: 16px 18px; }
285
+ .dsh-docker-modal-foot { display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding: 12px 18px; border-top: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08)); background: var(--dsw-alias-bg-layer-1, #fff); }
286
+ .dsh-docker-modal-danger { color: var(--dsw-alias-label-error, #d33c3c); background: var(--dsw-alias-bg-layer-2, #fdf3f3); border-radius: 8px; padding: 10px 12px; font-size: 12px; }
287
+ .dsh-docker-json { margin: 0; max-height: 54vh; overflow: auto; white-space: pre; tab-size: 2; font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); font-size: 11.5px; line-height: 1.55; padding: 12px; border-radius: 8px; background: var(--dsw-alias-markdown-code-block, #f6f7f9); border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08)); }
288
+
289
+ .dsh-docker-toast-wrap { position: fixed; right: 18px; bottom: 18px; z-index: 1100; display: flex; flex-direction: column; gap: 8px; max-width: min(420px, calc(100vw - 36px)); }
290
+ .dsh-docker-toast { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; border-radius: 9px; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-primary, #17181c); background: var(--dsw-alias-toast-bg, #fff); box-shadow: var(--dsw-elevation-prominent, 0 8px 30px rgba(0,0,0,.18)); border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.1)); animation: dsh-docker-toast-in .16s ease-out; }
291
+ .dsh-docker-toast[data-tone=error] { color: var(--dsw-alias-label-error, #d33c3c); }
292
+ .dsh-docker-toast[data-tone=success] { color: var(--dsw-alias-state-success-primary, #168a4b); }
293
+ .dsh-docker-toast-main { flex: 1; min-width: 0; overflow-wrap: anywhere; }
294
+
295
+ .dsh-docker-console { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 12px; }
296
+ .dsh-docker-console-form { flex: none; display: flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: 11px; background: var(--dsw-alias-bg-layer-1, #fff); border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.1)); }
297
+ .dsh-docker-console-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
298
+ .dsh-docker-console-prompt { flex: none; color: var(--dsw-alias-label-tertiary, #8a8d96); font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); font-size: 12px; }
299
+ .dsh-docker-console-input { flex: 1; height: 34px; font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); font-size: 12.5px; }
300
+ .dsh-docker-console-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
301
+ .dsh-docker-console-cwd { flex: 1; min-width: 160px; max-width: 420px; height: 28px; font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); font-size: 11.5px; }
302
+ .dsh-docker-console-hint { color: var(--dsw-alias-label-tertiary, #8a8d96); font-size: 11px; }
303
+ .dsh-docker-console-output { flex: 1; min-height: 0; display: flex; flex-direction: column; border-radius: 11px; overflow: hidden; background: var(--dsw-alias-markdown-code-block, #f6f7f9); border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.1)); }
304
+ .dsh-docker-output-head { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 12px; border-bottom: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08)); background: var(--dsw-alias-markdown-code-block-banner, rgba(0,0,0,.025)); }
305
+ .dsh-docker-output-title { display: flex; align-items: center; gap: 7px; min-width: 0; font-size: 11.5px; color: var(--dsw-alias-label-secondary, #60636c); }
306
+ .dsh-docker-output-cmd { font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); color: var(--dsw-alias-label-primary, #17181c); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
307
+ .dsh-docker-output-actions { display: flex; align-items: center; gap: 4px; flex: none; }
308
+ .dsh-docker-output-pre { flex: 1; min-height: 120px; margin: 0; padding: 12px 14px; overflow: auto; white-space: pre-wrap; word-break: break-word; tab-size: 2; font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); font-size: 11.5px; line-height: 1.6; color: var(--dsw-alias-label-primary, #17181c); }
309
+ .dsh-docker-output-pre.dsh-docker-no-output { display: grid; place-items: center; color: var(--dsw-alias-label-tertiary, #8a8d96); font-family: var(--dsw-font-family, sans-serif); font-size: 12px; }
310
+ .dsh-docker-run-status { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 8px 12px; border-top: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.08)); color: var(--dsw-alias-label-secondary, #60636c); font-size: 11.5px; }
311
+ .dsh-docker-run-status .dsh-docker-dot { background: var(--dsw-alias-state-success-primary, #168a4b); }
312
+ .dsh-docker-run-status[data-status=failed] .dsh-docker-dot, .dsh-docker-run-status[data-status=timed-out] .dsh-docker-dot { background: var(--dsw-alias-label-error, #d33c3c); }
313
+ .dsh-docker-run-status[data-status=cancelled] .dsh-docker-dot { background: var(--dsw-alias-label-tertiary, #8a8d96); }
314
+ .dsh-docker-run-status[data-status=exited] .dsh-docker-dot { background: var(--dsw-alias-label-tertiary, #8a8d96); }
315
+ .dsh-docker-attach-status { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 2px 0 8px; }
316
+ .dsh-docker-attach-screen { height: 390px; overflow: auto; border-radius: 8px; border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.12)); background: #111318; color: #e8eaf0; padding: 10px 12px; outline: none; cursor: text; }
317
+ .dsh-docker-attach-screen:focus { border-color: var(--dsw-focus-ring-color, #4d6bfe); box-shadow: 0 0 0 1px var(--dsw-focus-ring-color, #4d6bfe); }
318
+ .dsh-docker-attach-pre { margin: 0; padding: 0; white-space: pre-wrap; word-break: break-word; font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); font-size: 12px; line-height: 1.5; }
319
+ .dsh-docker-attach-pre.dsh-docker-no-output { color: rgba(232,234,240,.58); font-family: var(--dsw-font-family, sans-serif); font-size: 12px; }
320
+ .dsh-docker-attach-error { color: #ff9b9b; }
321
+ .dsh-docker-attach-loading { display: flex; align-items: center; gap: 8px; color: rgba(232,234,240,.72); font-size: 12px; padding: 4px; }
322
+
323
+ .dsh-docker-kbd { display: inline-flex; align-items: center; height: 18px; padding: 0 5px; border-radius: 4px; border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.14)); color: var(--dsw-alias-label-tertiary, #8a8d96); font-size: 10px; font-family: var(--dsw-font-family, sans-serif); }
324
+
325
+ .dsh-docker-chip-row { display: flex; flex-wrap: wrap; gap: 6px; }
326
+ .dsh-docker-quick { appearance: none; cursor: pointer; border: .5px solid var(--dsw-alias-border-l3, rgba(0,0,0,.1)); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-secondary, #60636c); border-radius: 6px; padding: 4px 8px; font: inherit; font-size: 11px; font-family: var(--dsw-font-markdown-code-font-family, ui-monospace, Menlo, monospace); }
327
+ .dsh-docker-quick:hover { color: var(--dsw-alias-label-primary, #17181c); background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.04)); }
328
+ .dsh-docker-divider { height: 1px; background: var(--dsw-alias-border-l3, rgba(0,0,0,.08)); margin: 12px 0; }
329
+ .dsh-docker-link { color: var(--dsw-alias-link, #3156c8); text-decoration: none; }
330
+ .dsh-docker-link:hover { text-decoration: underline; }
331
+
332
+ .dsh-docker-spinner { display: inline-block; border-radius: 50%; border: 1.6px solid currentColor; border-right-color: transparent; animation: dsh-docker-spin .75s linear infinite; flex: none; }
333
+ @keyframes dsh-docker-spin { to { transform: rotate(360deg); } }
334
+ @keyframes dsh-docker-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
335
+ @keyframes dsh-docker-toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
336
+
337
+ @media (max-width: 1180px) {
338
+ .dsh-docker-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
339
+ .dsh-docker-grid-2 { grid-template-columns: minmax(0, 1fr); }
340
+ }
341
+ @media (max-width: 760px) {
342
+ .dsh-docker-header { flex-direction: column; align-items: stretch; }
343
+ .dsh-docker-header-actions, .dsh-docker-headmeta { justify-content: flex-start; }
344
+ .dsh-docker-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
345
+ .dsh-docker-grid-3 { grid-template-columns: minmax(0, 1fr); }
346
+ .dsh-docker-search .dsh-docker-input { width: 100%; }
347
+ .dsh-docker-search { flex: 1; }
348
+ .dsh-docker-truncate { max-width: 200px; }
349
+ }
350
+ @media (prefers-reduced-motion: reduce) {
351
+ .dsh-docker-spinner, .dsh-docker-skel-line, .dsh-docker-toast { animation: none; }
352
+ }
353
+ `
354
+
355
+ export function injectStyles() {
356
+ if (typeof document === 'undefined') return
357
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(STYLE_TAG)}]`) !== null) return
358
+ const tag = document.createElement('style')
359
+ tag.dataset.plugin = 'dsh-plugin-container'
360
+ tag.dataset.pluginCss = STYLE_TAG
361
+ tag.textContent = CSS
362
+ document.head.appendChild(tag)
363
+ }
@@ -0,0 +1,94 @@
1
+ /** Compose projects are read-only; the command tab owns lifecycle actions. */
2
+ import { useMemo, useState } from 'react'
3
+
4
+ import { Button, CopyButton, EmptyState, ErrorState, LoadingSkeleton, SearchInput, displayValue } from '../components.jsx'
5
+ import { useDockerList } from '../hooks.js'
6
+ import { IconAlert, IconCompose } from '../icons.jsx'
7
+
8
+ function configList(value) {
9
+ if (typeof value !== 'string' || value.trim() === '') return []
10
+ return value.split(',').map((entry) => entry.trim()).filter(Boolean)
11
+ }
12
+
13
+ function composeCommand(project) {
14
+ const files = configList(project.configFiles)
15
+ return files.length === 0 ? null : `docker compose -f ${JSON.stringify(files[0])}`
16
+ }
17
+
18
+ export default function ComposeTab({ t, api, provider, context, status, refreshToken }) {
19
+ const [search, setSearch] = useState('')
20
+ const enabled = status?.daemon?.state === 'running'
21
+ const list = useDockerList(api, provider, 'compose', context, { refreshToken, enabled })
22
+ const items = list.data?.items ?? []
23
+ const filtered = useMemo(() => {
24
+ const needle = search.trim().toLowerCase()
25
+ return needle === '' ? items : items.filter((item) => `${item.name} ${item.status} ${item.configFiles}`.toLowerCase().includes(needle))
26
+ }, [items, search])
27
+
28
+ return (
29
+ <div className="dsh-docker-stack">
30
+ <div className="dsh-docker-toolbar">
31
+ <div className="dsh-docker-toolbar-left">
32
+ <SearchInput value={search} onChange={setSearch} placeholder={t('search')} label={t('search')} />
33
+ <span className="dsh-docker-muted">{t('composeReadOnly')}</span>
34
+ </div>
35
+ <div className="dsh-docker-toolbar-right">
36
+ <span className="dsh-docker-muted">{filtered.length} / {items.length}</span>
37
+ <Button size="sm" onClick={list.reload} disabled={list.loading}>{t('refresh')}</Button>
38
+ </div>
39
+ </div>
40
+ {!enabled && items.length === 0 ? (
41
+ <EmptyState icon={<IconAlert size={20} />} title={t('daemonStopped')} text={t('daemonStoppedBody')} />
42
+ ) : list.loading && list.data === null ? (
43
+ <LoadingSkeleton rows={5} />
44
+ ) : list.error !== null && list.data === null ? (
45
+ <ErrorState title={t('listFailed')} text={list.error.message} retryLabel={t('retry')} onRetry={list.reload} />
46
+ ) : filtered.length === 0 ? (
47
+ <EmptyState icon={<IconCompose size={20} />} title={search === '' ? t('emptyCompose') : t('emptySearch')} text={search === '' ? t('composeReadOnly') : search} />
48
+ ) : (
49
+ <section className="dsh-docker-card">
50
+ <div className="dsh-docker-table-wrap">
51
+ <table className="dsh-docker-table">
52
+ <thead>
53
+ <tr>
54
+ <th>{t('project')}</th>
55
+ <th>{t('status')}</th>
56
+ <th>{t('configFiles')}</th>
57
+ <th className="dsh-docker-col-actions">{t('actions')}</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ {filtered.map((item) => {
62
+ const base = composeCommand(item)
63
+ return (
64
+ <tr key={item.name}>
65
+ <td className="dsh-docker-row-title">{item.name}</td>
66
+ <td>{displayValue(item.status)}</td>
67
+ <td>
68
+ <div className="dsh-docker-chips">
69
+ {configList(item.configFiles).map((file) => (
70
+ <span key={file} className="dsh-docker-chip dsh-docker-chip-code" title={file}>{file}</span>
71
+ ))}
72
+ </div>
73
+ </td>
74
+ <td className="dsh-docker-col-actions">
75
+ {base !== null ? (
76
+ <span className="dsh-docker-row-actions">
77
+ <CopyButton text={`${base} up -d`} label="up -d" copiedLabel={t('copied')} className="dsh-docker-btn dsh-docker-sm" />
78
+ <CopyButton text={`${base} down`} label="down" copiedLabel={t('copied')} className="dsh-docker-btn dsh-docker-sm" />
79
+ </span>
80
+ ) : (
81
+ <span className="dsh-docker-muted">—</span>
82
+ )}
83
+ </td>
84
+ </tr>
85
+ )
86
+ })}
87
+ </tbody>
88
+ </table>
89
+ </div>
90
+ </section>
91
+ )}
92
+ </div>
93
+ )
94
+ }