@hyzyn/dsh-docker 0.6.4 → 0.7.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.
@@ -0,0 +1,2180 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * @hyzyn/dsh-docker — 面板样式表。
4
+ *
5
+ * 设计约定(改样式前先读这段):
6
+ * - 颜色/边框/阴影一律走 DSH 官方 token(--dsw-*),皮肤切换自动跟随;
7
+ * - 几何(圆角 / 控件高度 / 间距)统一用 --dk-* 令牌,禁止在规则里写魔法数字;
8
+ * - 三档层级:base(面板底)→ layer-2(栏 / 卡片底)→ layer-3(行 / 输入底);
9
+ * - 交互元素必须有 hover / active / :focus-visible / :disabled 四态;
10
+ * - 状态色只用在徽章与进度条上,正文保持中性,避免彩色噪音。
11
+ *
12
+ * 由 scripts/build-client.mjs 以 text loader 内联进 client.js。
13
+ */
14
+
15
+ /* ============================ 设计令牌 ============================ */
16
+
17
+ /*
18
+ * 令牌声明在 :where(html, body) 上——不能只放 :root:DSH 的明暗主题挂在
19
+ * body[data-ds-dark-theme] 上覆盖 --dsw-*,而 var() 在「声明它的元素」上就
20
+ * 完成替换;只在 :root 声明会永远拿到浅色值。
21
+ */
22
+ :where(html, body) {
23
+ --dk-r-xs: 4px;
24
+ --dk-r-sm: 6px;
25
+ --dk-r-md: 8px;
26
+ --dk-r-lg: 10px;
27
+ --dk-r-xl: 12px;
28
+ --dk-r-pill: 999px;
29
+ --dk-h-sm: 24px;
30
+ --dk-h-md: 28px;
31
+ --dk-h-lg: 32px;
32
+ --dk-gap-xs: 4px;
33
+ --dk-gap-sm: 6px;
34
+ --dk-gap-md: 8px;
35
+ --dk-gap-lg: 12px;
36
+ --dk-gap-xl: 16px;
37
+ /* 设置卡片标题行内边距:对齐 DSH 内置卡片 / 其他插件卡片(pM_pluginCard、tt_card) */
38
+ --dk-pad-cardHead: 14px 16px;
39
+ --dk-mono: "SF Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
40
+ --dk-ease: cubic-bezier(.2, .8, .2, 1);
41
+ --dk-dur: .16s;
42
+ --dk-accent: var(--dsw-alias-state-business-primary, #4d6bfe);
43
+ --dk-danger: var(--dsw-alias-state-error-primary, #d1242f);
44
+ --dk-success: var(--dsw-alias-state-success-primary, #1a7f37);
45
+ --dk-warn: var(--dsw-alias-state-warn-primary, #bf8700);
46
+ --dk-border: var(--dsw-alias-border-l1, rgba(127, 127, 127, .22));
47
+ --dk-border-strong: var(--dsw-alias-border-l2, rgba(127, 127, 127, .32));
48
+ --dk-hover: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, .1));
49
+ --dk-hover-solid: var(--dsw-alias-interactive-bg-hover-solid, rgba(127, 127, 127, .16));
50
+ --dk-label: var(--dsw-alias-label-primary, #1a1a1a);
51
+ --dk-label-2: var(--dsw-alias-label-secondary, #555);
52
+ --dk-label-dimmed: var(--dsw-alias-label-dimmed, var(--dk-label-3));
53
+ --dk-label-3: var(--dsw-alias-label-tertiary, #888);
54
+ --dk-surface: var(--dsw-alias-bg-base, #fff);
55
+ --dk-surface-solid: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-base, #fff));
56
+ --dk-surface-2: var(--dsw-alias-bg-layer-2, #f2f2f2);
57
+ --dk-surface-3: var(--dsw-alias-bg-layer-3, #e8e8e8);
58
+ --dk-input: var(--dsw-specific-input-major, var(--dsw-alias-bg-base, #fff));
59
+ --dk-shadow: 0 16px 40px -12px rgba(0, 0, 0, .32), var(--dsw-shadow-lv3, 0 4px 12px rgba(0, 0, 0, .12));
60
+ --dk-shadow-lg: 0 32px 72px -24px rgba(0, 0, 0, .48), 0 2px 8px rgba(0, 0, 0, .16);
61
+ --dk-ring: 0 0 0 2px color-mix(in srgb, var(--dk-accent) 42%, transparent);
62
+ --dk-log-bg: #0b0e14;
63
+ --dk-log-fg: #d7dce5;
64
+ }
65
+
66
+ /* ============================ 通用 ============================ */
67
+
68
+ .dk_backdrop ::selection,
69
+ .dk_card ::selection {
70
+ background: color-mix(in srgb, var(--dk-accent) 32%, transparent);
71
+ }
72
+
73
+ .dk_backdrop *,
74
+ .dk_card *,
75
+ [data-dsh-docker-entry] {
76
+ scrollbar-width: thin;
77
+ scrollbar-color: var(--dk-border-strong) transparent;
78
+ }
79
+
80
+ .dk_backdrop *::-webkit-scrollbar,
81
+ .dk_card *::-webkit-scrollbar {
82
+ width: 10px;
83
+ height: 10px;
84
+ }
85
+
86
+ .dk_backdrop *::-webkit-scrollbar-thumb,
87
+ .dk_card *::-webkit-scrollbar-thumb {
88
+ background: var(--dk-border-strong);
89
+ border: 3px solid transparent;
90
+ border-radius: var(--dk-r-pill);
91
+ background-clip: content-box;
92
+ }
93
+
94
+ /* ============================ 侧边栏入口 ============================ */
95
+
96
+ [data-dsh-docker-entry] {
97
+ position: relative;
98
+ /* 与 tty 侧边栏入口同因:宿主无全局 box-sizing reset,width:100% + 左右 padding
99
+ 在 content-box 下会比容器宽 24px,多出的右侧会被侧边栏裁掉(hover 底色缺一角)。 */
100
+ box-sizing: border-box;
101
+ display: flex;
102
+ align-items: center;
103
+ gap: var(--dk-gap-md);
104
+ width: 100%;
105
+ min-width: 0;
106
+ height: var(--dk-h-lg);
107
+ padding: 0 var(--dk-gap-lg);
108
+ border-radius: var(--dk-r-md);
109
+ color: var(--dk-label-2);
110
+ font-size: 13px;
111
+ white-space: nowrap;
112
+ cursor: pointer;
113
+ user-select: none;
114
+ transition: background var(--dk-dur) var(--dk-ease), color var(--dk-dur) var(--dk-ease);
115
+ }
116
+
117
+ [data-dsh-docker-entry]:hover {
118
+ background: var(--dk-hover);
119
+ color: var(--dk-label);
120
+ }
121
+
122
+ [data-dsh-docker-entry]:focus-visible {
123
+ outline: none;
124
+ box-shadow: var(--dk-ring);
125
+ }
126
+
127
+ .dk_entryIcon {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ width: 18px;
132
+ height: 18px;
133
+ color: currentColor;
134
+ }
135
+
136
+ .dk_entryLabel {
137
+ overflow: hidden;
138
+ text-overflow: ellipsis;
139
+ white-space: nowrap;
140
+ }
141
+
142
+ /* 侧边栏折叠(DSH 外壳在祖先元素上打 data-sidebar-collapsed):只留图标,
143
+ 与 tty 的终端入口同形——否则标签会从窄栏里溢出一条竖条 */
144
+ [data-sidebar-collapsed] [data-dsh-docker-entry] {
145
+ justify-content: center;
146
+ width: 100%;
147
+ /* 折叠 rail 里宿主的行是 36×36,跟齐(与 tty 入口一致) */
148
+ height: 36px;
149
+ padding: 0;
150
+ margin: 0;
151
+ }
152
+
153
+ [data-sidebar-collapsed] .dk_entryLabel {
154
+ display: none;
155
+ }
156
+
157
+ [data-sidebar-collapsed] .dk_entryBadge {
158
+ margin-left: 0;
159
+ padding-left: 0;
160
+ }
161
+
162
+ .dk_entryBadge {
163
+ margin-left: auto;
164
+ display: inline-flex;
165
+ align-items: center;
166
+ gap: var(--dk-gap-xs);
167
+ font-size: 11px;
168
+ color: var(--dk-label-3);
169
+ font-variant-numeric: tabular-nums;
170
+ }
171
+
172
+ .dk_entryDot {
173
+ width: 6px;
174
+ height: 6px;
175
+ border-radius: var(--dk-r-pill);
176
+ background: var(--dk-label-3);
177
+ }
178
+
179
+ .dk_entryDot[data-state="ok"] { background: var(--dk-success); }
180
+ .dk_entryDot[data-state="error"] { background: var(--dk-danger); }
181
+ .dk_entryDot[data-state="loading"] { background: var(--dk-warn); }
182
+
183
+ /* ============================ 弹窗骨架 ============================ */
184
+
185
+ .dk_backdrop {
186
+ position: fixed;
187
+ inset: 0;
188
+ z-index: 2140;
189
+ display: flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ padding: clamp(8px, 3vh, 32px);
193
+ background: color-mix(in srgb, #000 42%, transparent);
194
+ backdrop-filter: blur(2px);
195
+ animation: dk_fade var(--dk-dur) var(--dk-ease);
196
+ }
197
+
198
+ @keyframes dk_fade {
199
+ from { opacity: 0; }
200
+ to { opacity: 1; }
201
+ }
202
+
203
+ .dk_panel {
204
+ display: flex;
205
+ flex-direction: column;
206
+ width: min(1180px, 100%);
207
+ height: min(820px, 100%);
208
+ min-height: 420px;
209
+ overflow: hidden;
210
+ border: 1px solid var(--dk-border-strong);
211
+ border-radius: var(--dk-r-xl);
212
+ background: var(--dk-surface-solid);
213
+ color: var(--dk-label);
214
+ box-shadow: var(--dk-shadow-lg);
215
+ font-size: 13px;
216
+ line-height: 1.5;
217
+ }
218
+
219
+ /*
220
+ * 嵌入式承载:面板长在别人的栏里,撑满宿主即可——外框 / 圆角 / 阴影 / 最小高度都是
221
+ * 「独立弹窗」的装饰,挂在别人栏里要全去掉。
222
+ * .dk_panelDock —— tty 面板的右侧挂载位(0.3.0)
223
+ * .dk_panelTab —— 会话右侧栏的标签(S1)
224
+ */
225
+ .dk_panelDock,
226
+ .dk_panelTab {
227
+ width: 100%;
228
+ height: 100%;
229
+ min-height: 0;
230
+ border: none;
231
+ border-radius: 0;
232
+ box-shadow: none;
233
+ }
234
+
235
+ /* ---- 交互式终端抽屉(ttyTerminal.mount 就地嵌入,0.15.0) ---- */
236
+ .dk_drawer {
237
+ position: relative; /* 顶部拖拽条的定位基准 */
238
+ flex: 0 0 auto;
239
+ display: flex;
240
+ flex-direction: column;
241
+ height: min(46%, 380px);
242
+ min-height: 180px;
243
+ border-top: 1px solid var(--dk-border-strong);
244
+ background: var(--dk-surface-2);
245
+ animation: dk_drawerIn var(--dk-dur) var(--dk-ease);
246
+ }
247
+
248
+ /* 拖拽条:贴着抽屉上沿,鼠标进入才显形(常态不打扰终端内容) */
249
+ .dk_drawerResize {
250
+ position: absolute;
251
+ top: 0;
252
+ left: 0;
253
+ right: 0;
254
+ z-index: 2;
255
+ height: 5px;
256
+ cursor: ns-resize;
257
+ }
258
+
259
+ .dk_drawerResize:hover { background: color-mix(in srgb, var(--dk-accent) 55%, transparent); }
260
+
261
+ /*
262
+ * 折叠:只把抽屉压到标题栏高度(正文隐藏),**不卸载**挂载点——tty 那边的
263
+ * xterm 与 PTY 会话照旧跑着,展开即原样回来。
264
+ */
265
+ .dk_drawer[data-collapsed="1"] {
266
+ height: auto; /* 只剩标题栏(正文 display:none),别写死高度:header 是内容盒 + 内边距 */
267
+ min-height: 0;
268
+ }
269
+
270
+ .dk_drawer[data-collapsed="1"] .dk_drawerResize,
271
+ .dk_drawer[data-collapsed="1"] .dk_drawerBody { display: none; }
272
+
273
+ .dk_drawerFold .dk_iconGlyph { transition: transform var(--dk-dur) var(--dk-ease); }
274
+ .dk_drawer[data-collapsed="1"] .dk_drawerFold .dk_iconGlyph { transform: rotate(180deg); }
275
+
276
+ @keyframes dk_drawerIn {
277
+ from { opacity: 0; transform: translateY(8px); }
278
+ to { opacity: 1; transform: none; }
279
+ }
280
+
281
+ .dk_drawerHead {
282
+ display: flex;
283
+ align-items: center;
284
+ gap: var(--dk-gap-md);
285
+ flex: 0 0 auto;
286
+ min-height: 34px;
287
+ padding: var(--dk-gap-xs) var(--dk-gap-lg);
288
+ border-bottom: 1px solid var(--dk-border);
289
+ }
290
+
291
+ .dk_drawerIcon {
292
+ display: inline-flex;
293
+ flex: none;
294
+ color: var(--dk-accent);
295
+ }
296
+
297
+ .dk_drawerTitle {
298
+ min-width: 0;
299
+ font-size: 12px;
300
+ font-weight: 600;
301
+ white-space: nowrap;
302
+ overflow: hidden;
303
+ text-overflow: ellipsis;
304
+ }
305
+
306
+ .dk_drawerHint {
307
+ flex: none;
308
+ color: var(--dk-label-3);
309
+ font-size: 11px;
310
+ white-space: nowrap;
311
+ }
312
+
313
+ /* 挂载点:tty 会往里塞一个绝对定位的 .tt_term(复用终端面板同一套样式) */
314
+ .dk_drawerBody {
315
+ position: relative;
316
+ flex: 1 1 auto;
317
+ min-height: 0;
318
+ background: var(--dk-log-bg);
319
+ }
320
+
321
+ .dk_header {
322
+ display: flex;
323
+ align-items: center;
324
+ gap: var(--dk-gap-md);
325
+ flex: 0 0 auto;
326
+ flex-wrap: wrap;
327
+ min-height: 44px;
328
+ padding: var(--dk-gap-sm) var(--dk-gap-lg);
329
+ border-bottom: 1px solid var(--dk-border);
330
+ background: var(--dk-surface-2);
331
+ }
332
+
333
+ /* 详情头:返回 + 容器名 + 状态 + 目标主机 +(日志页工具条)+ 关闭 */
334
+ .dk_headerDetail { gap: var(--dk-gap-sm); }
335
+
336
+ .dk_logTools {
337
+ display: flex;
338
+ align-items: center;
339
+ gap: var(--dk-gap-sm);
340
+ flex-wrap: wrap;
341
+ }
342
+
343
+ .dk_titleIcon {
344
+ display: inline-flex;
345
+ align-items: center;
346
+ color: var(--dk-accent);
347
+ }
348
+
349
+ .dk_title {
350
+ font-size: 13px;
351
+ font-weight: 600;
352
+ letter-spacing: .2px;
353
+ }
354
+
355
+ .dk_headerSpacer { flex: 1 1 auto; }
356
+
357
+ .dk_iconBtn {
358
+ display: inline-flex;
359
+ align-items: center;
360
+ justify-content: center;
361
+ width: var(--dk-h-md);
362
+ height: var(--dk-h-md);
363
+ padding: 0;
364
+ border: 1px solid transparent;
365
+ border-radius: var(--dk-r-sm);
366
+ background: transparent;
367
+ color: var(--dk-label-2);
368
+ cursor: pointer;
369
+ transition: background var(--dk-dur) var(--dk-ease), color var(--dk-dur) var(--dk-ease), opacity var(--dk-dur) var(--dk-ease);
370
+ }
371
+
372
+ .dk_iconBtn:hover { background: var(--dk-hover); color: var(--dk-label); }
373
+ .dk_iconBtn:focus-visible { outline: none; box-shadow: var(--dk-ring); }
374
+ .dk_iconBtn:disabled { opacity: .45; cursor: not-allowed; }
375
+
376
+ /*
377
+ * 刷新中的图标自己转(data-spin 由各处的 refresh 按钮按 loading 打):
378
+ * 比在按钮旁边另挂一个独立 spinner 直观——用户刚点的就是这颗。
379
+ */
380
+ .dk_iconBtn[data-spin="1"] { color: var(--dk-accent); }
381
+ .dk_iconBtn[data-spin="1"] > span { animation: dk_spin .8s linear infinite; }
382
+
383
+ /* ============================ 工具栏 ============================ */
384
+
385
+ .dk_toolbar {
386
+ display: flex;
387
+ align-items: center;
388
+ gap: var(--dk-gap-md);
389
+ flex: 0 0 auto;
390
+ flex-wrap: wrap;
391
+ padding: var(--dk-gap-md) var(--dk-gap-lg);
392
+ border-bottom: 1px solid var(--dk-border);
393
+ }
394
+
395
+ /*
396
+ * dock 模式下没有面板头部,刷新 / 只读徽标挂到工具条末尾并靠右:
397
+ * margin-left:auto 在不换行时把它推到整行最右,换行时贴到最后一行的右端——
398
+ * 与「过滤 / 筛选控件从左往右排」区分开,避免刷新被当成工具条的第一个筛选项。
399
+ */
400
+ .dk_toolbarEnd {
401
+ display: inline-flex;
402
+ align-items: center;
403
+ gap: var(--dk-gap-sm);
404
+ margin-left: auto;
405
+ }
406
+
407
+ .dk_select,
408
+ .dk_input {
409
+ height: var(--dk-h-lg);
410
+ padding: 0 var(--dk-gap-md);
411
+ border: 1px solid var(--dk-border-strong);
412
+ border-radius: var(--dk-r-sm);
413
+ background: var(--dk-input);
414
+ color: var(--dk-label);
415
+ font-size: 13px;
416
+ font-family: inherit;
417
+ transition: border-color var(--dk-dur) var(--dk-ease), box-shadow var(--dk-dur) var(--dk-ease);
418
+ }
419
+
420
+ /*
421
+ * 输入框一律可收缩:以前这里是 min-width: 180px,遇到窄容器(设置面板 ~600px 时
422
+ * 每列只有 ~180px)会把列撑破、和隔壁字段叠在一起。宽度交给容器的栅格/弹性布局决定。
423
+ */
424
+ .dk_input { min-width: 0; }
425
+ .dk_select:focus-visible,
426
+ .dk_input:focus-visible {
427
+ outline: none;
428
+ border-color: var(--dk-accent);
429
+ box-shadow: var(--dk-ring);
430
+ }
431
+
432
+ /*
433
+ * 搜索框是工具条里唯一可伸缩的项,它的 min-width 决定了「放不下时先挤谁」:给到 160px 时
434
+ * 空隙不够,浏览器宁可把末尾那个开关单独换行,也不肯把搜索框压窄一点——宽面板里因此多出
435
+ * 一行只有一个复选框。120px 让它先瘦下来兜住这点差距(flex-grow 仍是 1:空间富余时它照旧
436
+ * 填满整行,视觉宽度不变)。
437
+ */
438
+ .dk_search { flex: 1 1 120px; min-width: 120px; }
439
+
440
+ /* 工具条里的结果计数(镜像:23 / 23 个镜像):别被压缩,数字等宽 */
441
+ .dk_searchCount { flex: none; font-variant-numeric: tabular-nums; }
442
+
443
+ .dk_seg {
444
+ display: inline-flex;
445
+ padding: 2px;
446
+ border: 1px solid var(--dk-border);
447
+ border-radius: var(--dk-r-md);
448
+ background: var(--dk-surface-2);
449
+ /*
450
+ * 分段从三段长到五段(容器/镜像/Compose/网络/卷)后,520px 的 dock 窄栏里可能放不下:
451
+ * 允许这一段自己横向滚动,而不是整个工具条换行或做二级菜单(切页是高频动作,
452
+ * 多一次点击不划算)。滚动条隐藏,靠两端的裁切给出「还有内容」的暗示。
453
+ */
454
+ max-width: 100%;
455
+ overflow-x: auto;
456
+ scrollbar-width: none;
457
+ }
458
+
459
+ .dk_seg::-webkit-scrollbar { display: none; }
460
+
461
+ .dk_segBtn {
462
+ /* 不参与压缩:否则窄栏里五段会被压成「容/镜/Co/网/卷」这种半截字 */
463
+ flex: none;
464
+ height: var(--dk-h-sm);
465
+ padding: 0 var(--dk-gap-md);
466
+ border: none;
467
+ border-radius: var(--dk-r-xs);
468
+ background: transparent;
469
+ color: var(--dk-label-2);
470
+ font-size: 12px;
471
+ font-family: inherit;
472
+ cursor: pointer;
473
+ transition: background var(--dk-dur) var(--dk-ease), color var(--dk-dur) var(--dk-ease);
474
+ }
475
+
476
+ .dk_segBtn:hover { color: var(--dk-label); }
477
+ /*
478
+ * 选中态**不能**用 --dk-surface-solid:它和分段条底色的 --dk-surface-2 解析到**同一个**宿主令牌
479
+ * (`--dsw-alias-bg-layer-2`),暗色下两者完全同色,只剩那层 1px 阴影可辨 —— 用户实测「暗色下选中
480
+ * 看不清」(浅色下能看见,靠的也只是阴影)。
481
+ *
482
+ * 改走**强调色淡染**:与 .dk_pillPick / .dk_pillFollow 的选中语言一致,靠色彩差而不是靠阴影,
483
+ * 两种主题下都可辨;文字仍用 --dk-label(不改强调色),避免 12px 小字撞对比度。
484
+ * 由 client-smoke 守「不得回到同底色」。
485
+ */
486
+ .dk_segBtn[data-on="1"] {
487
+ background: color-mix(in srgb, var(--dk-accent) 18%, transparent);
488
+ color: var(--dk-label);
489
+ font-weight: 600;
490
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .12);
491
+ }
492
+
493
+ .dk_check {
494
+ display: inline-flex;
495
+ align-items: center;
496
+ gap: var(--dk-gap-sm);
497
+ font-size: 12px;
498
+ color: var(--dk-label-2);
499
+ cursor: pointer;
500
+ user-select: none;
501
+ }
502
+
503
+ .dk_check input { accent-color: var(--dk-accent); }
504
+
505
+ /*
506
+ * 工具条末尾的两个开关(含已停止 / 自动刷新)成组:它们是一个「开关簇」,工具条在宽面板里
507
+ * 恰好卡在放得下与放不下的边界上——分开排时最后一个会被单独挤到第二行(一行只有一个复选
508
+ * 框)。成组后换行以组为单位,最坏情况也是一整组一起下去。
509
+ */
510
+ .dk_toolbarToggles {
511
+ display: inline-flex;
512
+ align-items: center;
513
+ gap: var(--dk-gap-md);
514
+ }
515
+
516
+ /* ============================ 按钮 ============================ */
517
+
518
+ .dk_btn {
519
+ display: inline-flex;
520
+ align-items: center;
521
+ justify-content: center;
522
+ gap: var(--dk-gap-sm);
523
+ height: var(--dk-h-md);
524
+ padding: 0 var(--dk-gap-lg);
525
+ border: 1px solid var(--dk-border-strong);
526
+ border-radius: var(--dk-r-sm);
527
+ background: var(--dk-surface-solid);
528
+ color: var(--dk-label);
529
+ font-size: 12px;
530
+ font-family: inherit;
531
+ cursor: pointer;
532
+ white-space: nowrap;
533
+ transition: background var(--dk-dur) var(--dk-ease), border-color var(--dk-dur) var(--dk-ease), color var(--dk-dur) var(--dk-ease);
534
+ }
535
+
536
+ .dk_btn:hover { background: var(--dk-hover); }
537
+ .dk_btn:active { background: var(--dk-hover-solid); }
538
+ .dk_btn:focus-visible { outline: none; box-shadow: var(--dk-ring); }
539
+ .dk_btn:disabled { opacity: .45; cursor: not-allowed; }
540
+
541
+ .dk_btnPrimary {
542
+ border-color: transparent;
543
+ background: var(--dk-accent);
544
+ color: #fff;
545
+ }
546
+
547
+ .dk_btnPrimary:hover { background: color-mix(in srgb, var(--dk-accent) 86%, #000); }
548
+
549
+ .dk_btnDanger { border-color: color-mix(in srgb, var(--dk-danger) 40%, transparent); color: var(--dk-danger); }
550
+ .dk_btnDanger:hover { background: color-mix(in srgb, var(--dk-danger) 12%, transparent); }
551
+
552
+ /* ============================ 主体 / 容器卡片 ============================ */
553
+
554
+ .dk_body {
555
+ display: flex;
556
+ flex: 1 1 auto;
557
+ min-height: 0;
558
+ /* 过渡浮层与顶部进度条的定位基准 */
559
+ position: relative;
560
+ }
561
+
562
+ .dk_main {
563
+ flex: 1 1 auto;
564
+ min-width: 0;
565
+ overflow: auto;
566
+ padding: var(--dk-gap-lg);
567
+ }
568
+
569
+ .dk_grid {
570
+ display: grid;
571
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
572
+ gap: var(--dk-gap-lg);
573
+ /* 切目标时数据整批换掉:淡入 + 轻微上移,让「换了一批内容」看得见 */
574
+ animation: dk_dataIn .2s var(--dk-ease) both;
575
+ }
576
+
577
+ /*
578
+ * 「正在切目标」:正文压暗 + 锁住指针(pointer-events: none 只挡内容,外层 .dk_main
579
+ * 仍可滚动)。保留可读是刻意的——比起白屏,用户更希望还能看着刚才那批卡片,
580
+ * 只是知道「这不是新目标的」、此时点不动。
581
+ */
582
+ /*
583
+ * 「正在切目标」的旧数据:82% + 轻度去色,而不是 42% 纯压暗。
584
+ * 语义差别——42% 读作「坏了 / 被禁用」,82% + 去色读作「这是另一批内容」,
585
+ * 与「正在换一批数据」是一回事;可读性也保住(还要能看清容器名)。
586
+ * pointer-events: none 是安全底线:不加锁时点「停止」会拿新目标当目标、
587
+ * 用旧列表的容器 ID 发命令。
588
+ */
589
+ .dk_body[data-stale="1"] .dk_main {
590
+ opacity: .82;
591
+ filter: saturate(.55);
592
+ pointer-events: none;
593
+ user-select: none;
594
+ }
595
+
596
+ .dk_body .dk_main {
597
+ transition: opacity var(--dk-dur) var(--dk-ease), filter var(--dk-dur) var(--dk-ease);
598
+ }
599
+
600
+ /*
601
+ * 正文顶部的过渡层:一条 2px 流光进度条(::before)+ 一枚居中的蓝色状态胶囊。
602
+ * 绝对定位 → 不占文档流,切换时首卡位置与滚动高度都不变;自身不吃指针事件(不挡滚动)。
603
+ */
604
+ .dk_switchOverlay {
605
+ position: absolute;
606
+ top: 0;
607
+ left: 0;
608
+ right: 0;
609
+ z-index: 4;
610
+ display: flex;
611
+ justify-content: center;
612
+ padding-top: var(--dk-gap-sm);
613
+ pointer-events: none;
614
+ }
615
+
616
+ /* 顶部 2px 不定长进度条:面板级「正在取数」信号 */
617
+ .dk_switchOverlay::before {
618
+ content: '';
619
+ position: absolute;
620
+ top: 0;
621
+ left: 0;
622
+ right: 0;
623
+ height: 2px;
624
+ background-image: linear-gradient(90deg, transparent, var(--dk-accent), transparent);
625
+ background-size: 35% 100%;
626
+ background-repeat: no-repeat;
627
+ animation: dk_indeterminate 1.15s linear infinite;
628
+ }
629
+
630
+ /* 状态胶囊:蓝色(强调色)表达「进行中」,底色是强调色掺入当前表面 */
631
+ .dk_switchPill {
632
+ display: flex;
633
+ align-items: center;
634
+ gap: var(--dk-gap-sm);
635
+ flex: none;
636
+ min-width: 0;
637
+ max-width: min(400px, 38vw);
638
+ padding: 4px var(--dk-gap-lg);
639
+ border: 1px solid color-mix(in srgb, var(--dk-accent) 42%, transparent);
640
+ border-radius: var(--dk-r-pill);
641
+ background: color-mix(in srgb, var(--dk-accent) 12%, var(--dk-surface-solid));
642
+ backdrop-filter: blur(6px);
643
+ box-shadow: 0 2px 10px rgba(0, 0, 0, .10);
644
+ font-size: 12px;
645
+ color: var(--dk-accent);
646
+ }
647
+
648
+ .dk_switchText {
649
+ display: flex;
650
+ align-items: baseline;
651
+ gap: var(--dk-gap-sm);
652
+ min-width: 0;
653
+ white-space: nowrap;
654
+ overflow: hidden;
655
+ text-overflow: ellipsis;
656
+ }
657
+
658
+ .dk_switchText strong { color: var(--dk-accent); font-weight: 600; }
659
+ /* 「·」与「当前显示:」稍微压淡一点,与两个目标名区分层次(仍在蓝色系内) */
660
+ .dk_switchDot,
661
+ .dk_switchSub { color: color-mix(in srgb, var(--dk-accent) 72%, var(--dk-label-2)); }
662
+ .dk_switchName { color: var(--dk-accent); }
663
+ .dk_spinSm { width: 10px; height: 10px; color: var(--dk-accent); }
664
+
665
+ /* 进入:8px 上滑 + 淡入,让「换了一批内容」有方向感(不是闪一下) */
666
+ @keyframes dk_dataIn {
667
+ from { opacity: 0; transform: translateY(8px); }
668
+ to { opacity: 1; transform: none; }
669
+ }
670
+
671
+ @keyframes dk_indeterminate {
672
+ from { background-position: -40% 0; }
673
+ to { background-position: 140% 0; }
674
+ }
675
+
676
+ @media (prefers-reduced-motion: reduce) {
677
+ .dk_grid { animation: none; }
678
+ .dk_switchOverlay::before { animation: none; background-position: 50% 0; }
679
+ .dk_body .dk_main { transition: none; }
680
+ }
681
+
682
+ .dk_card {
683
+ display: flex;
684
+ flex-direction: column;
685
+ gap: var(--dk-gap-sm);
686
+ padding: var(--dk-gap-lg);
687
+ border: 1px solid var(--dk-border);
688
+ border-radius: var(--dk-r-lg);
689
+ background: var(--dk-surface-solid);
690
+ cursor: pointer;
691
+ transition: border-color var(--dk-dur) var(--dk-ease), box-shadow var(--dk-dur) var(--dk-ease), transform var(--dk-dur) var(--dk-ease);
692
+ }
693
+
694
+ .dk_card:hover {
695
+ border-color: var(--dk-border-strong);
696
+ box-shadow: var(--dk-shadow);
697
+ }
698
+
699
+ .dk_card:focus-visible { outline: none; box-shadow: var(--dk-ring); }
700
+
701
+ .dk_card[data-selected="1"] {
702
+ border-color: var(--dk-accent);
703
+ box-shadow: var(--dk-ring);
704
+ }
705
+
706
+ .dk_cardHead {
707
+ display: flex;
708
+ align-items: center;
709
+ gap: var(--dk-gap-sm);
710
+ min-width: 0;
711
+ }
712
+
713
+ .dk_cardName {
714
+ flex: 1 1 auto;
715
+ min-width: 0;
716
+ font-size: 13px;
717
+ font-weight: 600;
718
+ overflow: hidden;
719
+ text-overflow: ellipsis;
720
+ white-space: nowrap;
721
+ }
722
+
723
+ .dk_cardRows {
724
+ display: flex;
725
+ flex-direction: column;
726
+ gap: var(--dk-gap-xs);
727
+ margin-top: var(--dk-gap-xs);
728
+ }
729
+
730
+ .dk_cardRow {
731
+ display: grid;
732
+ grid-template-columns: 44px 1fr;
733
+ gap: var(--dk-gap-md);
734
+ align-items: baseline;
735
+ font-size: 12px;
736
+ }
737
+
738
+ .dk_cardLabel {
739
+ color: var(--dk-label-3);
740
+ }
741
+
742
+ .dk_cardValue {
743
+ min-width: 0;
744
+ color: var(--dk-label-2);
745
+ font-family: var(--dk-mono);
746
+ overflow: hidden;
747
+ text-overflow: ellipsis;
748
+ white-space: nowrap;
749
+ }
750
+
751
+ .dk_actionBar {
752
+ display: flex;
753
+ gap: var(--dk-gap-xs);
754
+ margin-top: var(--dk-gap-sm);
755
+ padding-top: var(--dk-gap-sm);
756
+ border-top: 1px solid var(--dk-border);
757
+ }
758
+
759
+ .dk_actionBar .dk_iconBtn {
760
+ border: 1px solid var(--dk-border);
761
+ background: var(--dk-surface-2);
762
+ }
763
+
764
+ .dk_actionBar .dk_iconBtn:hover:not(:disabled) {
765
+ background: var(--dk-hover-solid);
766
+ color: var(--dk-label);
767
+ }
768
+
769
+ /* 「查看」与「变更」两组之间的竖线分隔 */
770
+ .dk_actionBarSep {
771
+ flex: none;
772
+ width: 1px;
773
+ margin: 4px var(--dk-gap-sm);
774
+ background: var(--dk-border-strong);
775
+ }
776
+
777
+ /* 删除是破坏性且不可恢复:在变更组内再额外留一点距离,别和「重启」挨着点错 */
778
+ .dk_actionBar .dk_iconBtnDanger {
779
+ margin-left: var(--dk-gap-xs);
780
+ }
781
+
782
+ .dk_iconBtnDanger { color: var(--dk-danger); }
783
+ .dk_iconBtnDanger:hover:not(:disabled) { background: color-mix(in srgb, var(--dk-danger) 14%, transparent); }
784
+
785
+ .dk_iconGlyph { display: inline-flex; align-items: center; }
786
+
787
+ /* ============================ 徽章 ============================ */
788
+
789
+ .dk_badge {
790
+ display: inline-flex;
791
+ align-items: center;
792
+ gap: var(--dk-gap-xs);
793
+ height: 20px;
794
+ padding: 0 var(--dk-gap-sm);
795
+ border-radius: var(--dk-r-pill);
796
+ border: 1px solid var(--dk-border-strong);
797
+ background: var(--dk-surface-2);
798
+ color: var(--dk-label-2);
799
+ font-size: 11px;
800
+ line-height: 1;
801
+ white-space: nowrap;
802
+ /* 启停后状态徽标会从「运行中」翻到「已停止」:给个过渡,别硬切 */
803
+ transition: background-color var(--dk-dur) var(--dk-ease), border-color var(--dk-dur) var(--dk-ease), color var(--dk-dur) var(--dk-ease);
804
+ }
805
+
806
+ .dk_badge::before {
807
+ content: '';
808
+ width: 6px;
809
+ height: 6px;
810
+ border-radius: var(--dk-r-pill);
811
+ background: currentColor;
812
+ opacity: .8;
813
+ }
814
+
815
+ .dk_badge[data-state="running"] { color: var(--dk-success); border-color: color-mix(in srgb, var(--dk-success) 36%, transparent); background: color-mix(in srgb, var(--dk-success) 10%, transparent); }
816
+ .dk_badge[data-state="exited"] { color: var(--dk-label-3); }
817
+ .dk_badge[data-state="paused"] { color: var(--dk-warn); border-color: color-mix(in srgb, var(--dk-warn) 36%, transparent); background: color-mix(in srgb, var(--dk-warn) 10%, transparent); }
818
+ .dk_badge[data-state="restarting"] { color: var(--dk-warn); }
819
+ .dk_badge[data-state="dead"] { color: var(--dk-danger); border-color: color-mix(in srgb, var(--dk-danger) 36%, transparent); background: color-mix(in srgb, var(--dk-danger) 10%, transparent); }
820
+ .dk_badge[data-state="unhealthy"] { color: var(--dk-danger); }
821
+ .dk_badge[data-state="created"] { color: var(--dk-label-2); }
822
+ .dk_badge[data-state="unknown"] { color: var(--dk-label-3); }
823
+
824
+ .dk_tag {
825
+ display: inline-flex;
826
+ align-items: center;
827
+ height: 18px;
828
+ padding: 0 var(--dk-gap-sm);
829
+ border-radius: var(--dk-r-xs);
830
+ background: var(--dk-surface-3);
831
+ color: var(--dk-label-2);
832
+ font-size: 11px;
833
+ }
834
+
835
+ /* 需关注原因徽标(总览异常表) */
836
+ .dk_reasons { display: inline-flex; flex-wrap: wrap; gap: 4px; }
837
+
838
+ .dk_reason {
839
+ display: inline-flex;
840
+ align-items: center;
841
+ height: 18px;
842
+ padding: 0 6px;
843
+ border-radius: var(--dk-r-xs);
844
+ background: var(--dk-surface-3);
845
+ color: var(--dk-label-2);
846
+ font-size: 11px;
847
+ white-space: nowrap;
848
+ }
849
+
850
+ .dk_reason[data-reason="oom"] { background: color-mix(in srgb, var(--dk-danger) 18%, transparent); color: var(--dk-danger); }
851
+ .dk_reason[data-reason="dead"] { background: color-mix(in srgb, var(--dk-danger) 14%, transparent); color: var(--dk-danger); }
852
+ .dk_reason[data-reason="unhealthy"] { background: color-mix(in srgb, var(--dk-danger) 12%, transparent); color: var(--dk-danger); }
853
+ .dk_reason[data-reason="restarting"] { background: color-mix(in srgb, var(--dk-warn) 16%, transparent); color: var(--dk-warn); }
854
+ .dk_reason[data-reason="exit-nonzero"] { background: color-mix(in srgb, var(--dk-warn) 12%, transparent); color: var(--dk-warn); }
855
+
856
+ /* ============================ 容器详情(整栏) ============================ */
857
+
858
+ .dk_detail {
859
+ display: flex;
860
+ flex-direction: column;
861
+ flex: 1 1 auto;
862
+ min-width: 0;
863
+ min-height: 0;
864
+ }
865
+
866
+
867
+ .dk_detailTitle {
868
+ min-width: 0;
869
+ font-size: 13px;
870
+ font-weight: 600;
871
+ overflow: hidden;
872
+ text-overflow: ellipsis;
873
+ white-space: nowrap;
874
+ }
875
+
876
+ .dk_detailSub {
877
+ flex: none;
878
+ color: var(--dk-label-3);
879
+ font-size: 11px;
880
+ font-family: var(--dk-mono);
881
+ }
882
+
883
+ .dk_tabs {
884
+ display: flex;
885
+ align-items: center;
886
+ gap: var(--dk-gap-xs);
887
+ flex: 0 0 auto;
888
+ flex-wrap: wrap; /* 窄面板下过滤行换行,而不是把右侧控件裁掉(D60) */
889
+ padding: 0 var(--dk-gap-lg);
890
+ border-bottom: 1px solid var(--dk-border);
891
+ }
892
+
893
+ .dk_filterBar {
894
+ position: relative;
895
+ display: flex;
896
+ align-items: center;
897
+ gap: var(--dk-gap-md);
898
+ flex: 1 1 auto;
899
+ flex-wrap: wrap; /* LINES + 三个 pill + 级别 + 两个导出 chip + 计数槽放不下时换行(D60) */
900
+ min-width: 0;
901
+ /* 右侧给计数留固定槽(92px ≈ 4 位数 + 「行匹配」),输入框宽度因此恒定 */
902
+ padding: var(--dk-gap-xs) 92px var(--dk-gap-xs) 0;
903
+ }
904
+
905
+ /**
906
+ * 同一条过滤行挂在两处,伸缩轴不同,必须分开对待:
907
+ * - 单容器日志:它在 `.dk_tabs`(横向行)里,`flex: 1 1 auto` 是「横向占满剩余宽度」,正确;
908
+ * - 聚合日志(ComposeLogs):它是 `.dk_logs`(纵向 flex)的直接子项,此时 `flex: 1 1 auto`
909
+ * 会变成**纵向撑高**——过滤到少量行时日志体基准高度变小、剩余空间出现,工具条与
910
+ * `.dk_logBody` 各分一半,整条工具条长成两百多像素的横条(输入框垂直居中、状态行被
911
+ * 顶到下面),也就是「输入文字后界面变形」。工具条是固定高度的一行,不该参与纵向伸缩。
912
+ */
913
+ .dk_logs > .dk_filterBar { flex: 0 0 auto; }
914
+
915
+ .dk_tab {
916
+ height: var(--dk-h-lg);
917
+ padding: 0 var(--dk-gap-md);
918
+ border: none;
919
+ border-bottom: 2px solid transparent;
920
+ background: transparent;
921
+ color: var(--dk-label-2);
922
+ font-size: 12px;
923
+ font-family: inherit;
924
+ cursor: pointer;
925
+ }
926
+
927
+ .dk_tab:hover { color: var(--dk-label); }
928
+ .dk_tab[data-on="1"] { color: var(--dk-label); border-bottom-color: var(--dk-accent); }
929
+
930
+ .dk_detailBody {
931
+ flex: 1 1 auto;
932
+ min-height: 0;
933
+ display: flex;
934
+ flex-direction: column;
935
+ overflow: auto;
936
+ padding: var(--dk-gap-lg);
937
+ }
938
+
939
+ /* ============================ 日志视图 ============================ */
940
+
941
+ .dk_logs {
942
+ position: relative; /* 「回到底部」浮层定位基准 */
943
+ display: flex;
944
+ flex-direction: column;
945
+ flex: 1 1 auto;
946
+ min-height: 0;
947
+ margin: calc(-1 * var(--dk-gap-lg));
948
+ }
949
+
950
+
951
+ .dk_toolLabel {
952
+ color: var(--dk-label-3);
953
+ font-size: 11px;
954
+ font-weight: 600;
955
+ letter-spacing: .4px;
956
+ }
957
+
958
+ .dk_selectSm { height: var(--dk-h-md); padding: 0 var(--dk-gap-sm); font-size: 12px; }
959
+
960
+ .dk_pill {
961
+ min-width: 44px;
962
+ height: var(--dk-h-md);
963
+ padding: 0 var(--dk-gap-md);
964
+ border: 1px solid var(--dk-border-strong);
965
+ border-radius: var(--dk-r-pill);
966
+ background: var(--dk-surface-2);
967
+ color: var(--dk-label-3);
968
+ font-size: 12px;
969
+ font-family: inherit;
970
+ cursor: pointer;
971
+ transition: background var(--dk-dur) var(--dk-ease), color var(--dk-dur) var(--dk-ease), border-color var(--dk-dur) var(--dk-ease);
972
+ }
973
+
974
+ .dk_pill:hover { background: var(--dk-hover); }
975
+ .dk_pill:focus-visible { outline: none; box-shadow: var(--dk-ring); }
976
+
977
+ .dk_pill[data-on="1"] {
978
+ border-color: color-mix(in srgb, var(--dk-success) 46%, transparent);
979
+ background: color-mix(in srgb, var(--dk-success) 16%, transparent);
980
+ color: var(--dk-success);
981
+ font-weight: 600;
982
+ }
983
+
984
+ /* 「聚合选择」激活态:这是「进入选择模式」而不是「某个开关已开」,用强调色 */
985
+ .dk_pillPick[data-on="1"] {
986
+ border-color: color-mix(in srgb, var(--dk-accent) 52%, transparent);
987
+ background: color-mix(in srgb, var(--dk-accent) 18%, transparent);
988
+ color: var(--dk-accent);
989
+ font-weight: 600;
990
+ }
991
+
992
+ /* FOLLOW 激活态用强调色(与 AUTO REFRESH 的成功绿区分开:实时流在推 vs 轮询开着) */
993
+ .dk_pillFollow[data-on="1"] {
994
+ border-color: color-mix(in srgb, var(--dk-accent) 52%, transparent);
995
+ background: color-mix(in srgb, var(--dk-accent) 18%, transparent);
996
+ color: var(--dk-accent);
997
+ }
998
+
999
+ .dk_pill:disabled {
1000
+ opacity: .45;
1001
+ cursor: not-allowed;
1002
+ }
1003
+
1004
+ .dk_pill:disabled:hover { background: var(--dk-surface-2); }
1005
+
1006
+ /* FOLLOW 连接状态行:小圆点 + 文案(连接错误只在这里表达,不弹横幅刷屏) */
1007
+ .dk_followState {
1008
+ display: flex;
1009
+ align-items: center;
1010
+ gap: var(--dk-gap-sm);
1011
+ flex: 0 0 auto;
1012
+ padding: var(--dk-gap-xs) var(--dk-gap-lg);
1013
+ border-bottom: 1px solid var(--dk-border);
1014
+ background: var(--dk-surface-2);
1015
+ color: var(--dk-label-3);
1016
+ font-size: 11px;
1017
+ }
1018
+
1019
+ .dk_followState::before {
1020
+ content: '';
1021
+ width: 6px;
1022
+ height: 6px;
1023
+ border-radius: 50%;
1024
+ background: var(--dk-label-3);
1025
+ }
1026
+
1027
+ .dk_followState[data-state="open"]::before { background: var(--dk-success); }
1028
+ .dk_followState[data-state="connecting"]::before,
1029
+ .dk_followState[data-state="reconnecting"]::before { background: var(--dk-warn); }
1030
+ .dk_followState[data-state="closed"]::before { background: var(--dk-danger); }
1031
+
1032
+ /* FOLLOW 暂停自动滚动时的「回到底部」浮层(不占布局,出现/消失不挤动日志) */
1033
+ .dk_backToBottom {
1034
+ position: absolute;
1035
+ right: var(--dk-gap-lg);
1036
+ bottom: var(--dk-gap-lg);
1037
+ z-index: 2;
1038
+ height: var(--dk-h-md);
1039
+ padding: 0 var(--dk-gap-md);
1040
+ border: 1px solid var(--dk-border-strong);
1041
+ border-radius: var(--dk-r-pill);
1042
+ background: var(--dk-surface-2);
1043
+ color: var(--dk-label);
1044
+ font-size: 11px;
1045
+ font-family: inherit;
1046
+ cursor: pointer;
1047
+ box-shadow: 0 2px 10px rgba(0, 0, 0, .28);
1048
+ }
1049
+
1050
+ .dk_backToBottom:hover { background: var(--dk-hover-solid); }
1051
+ .dk_backToBottom:focus-visible { outline: none; box-shadow: var(--dk-ring); }
1052
+
1053
+
1054
+ /* 过滤输入框常驻;清空按钮浮在框内右侧(绝对定位 → 出现/消失都不挤动布局) */
1055
+ .dk_filterWrap {
1056
+ position: relative;
1057
+ display: flex;
1058
+ flex: 1 1 200px;
1059
+ min-width: 0;
1060
+ /* 换行后唯一可缩的是输入框:给个下限,别在窄面板塌成 0 宽(D60) */
1061
+ min-width: 140px;
1062
+ }
1063
+
1064
+ .dk_filterInput {
1065
+ flex: 1 1 auto;
1066
+ height: var(--dk-h-md);
1067
+ min-width: 0;
1068
+ /* 常驻右侧内边距给清除按钮留位:内边距跟着内容变会改 flex 基准,反而挤动输入框 */
1069
+ padding-right: 28px;
1070
+ }
1071
+
1072
+ .dk_filterClear {
1073
+ position: absolute;
1074
+ top: 50%;
1075
+ right: 4px;
1076
+ transform: translateY(-50%);
1077
+ display: inline-flex;
1078
+ align-items: center;
1079
+ justify-content: center;
1080
+ width: 20px;
1081
+ height: 20px;
1082
+ padding: 0;
1083
+ border: none;
1084
+ border-radius: var(--dk-r-sm);
1085
+ background: transparent;
1086
+ color: var(--dk-label-3);
1087
+ cursor: pointer;
1088
+ }
1089
+
1090
+ .dk_filterClear:hover { background: var(--dk-hover); color: var(--dk-label); }
1091
+ .dk_filterClear:focus-visible { outline: none; box-shadow: var(--dk-ring); }
1092
+ /* 计数(「201 行」/「1 / 201 行匹配」)右对齐在固定槽里:文案变长也不会把输入框挤动 */
1093
+ .dk_filterCount {
1094
+ position: absolute;
1095
+ top: 50%;
1096
+ right: 0;
1097
+ transform: translateY(-50%);
1098
+ white-space: nowrap;
1099
+ color: var(--dk-label-3);
1100
+ font-size: 11px;
1101
+ font-variant-numeric: tabular-nums;
1102
+ }
1103
+
1104
+ .dk_logBody {
1105
+ flex: 1 1 auto;
1106
+ min-height: 0;
1107
+ overflow: auto;
1108
+ padding: var(--dk-gap-md) var(--dk-gap-lg);
1109
+ background: var(--dk-log-bg);
1110
+ color: var(--dk-log-fg);
1111
+ font-family: var(--dk-mono);
1112
+ font-size: 12px;
1113
+ line-height: 1.6;
1114
+ }
1115
+
1116
+ .dk_logLine {
1117
+ white-space: pre-wrap;
1118
+ word-break: break-word;
1119
+ /*
1120
+ * 这里**刻意不用** content-visibility / contain-intrinsic-size(D91)。
1121
+ *
1122
+ * D63 曾用 content-visibility 跳过视口外行的布局绘制,并用 contain-intrinsic-size
1123
+ * 给未渲染的行兜一个估算高度(19px);代价是 `scrollHeight` 从此变成**估算值** ——
1124
+ * 未渲染行按 19px 算,而折长的行实际 38px 起。FOLLOW 的整套贴底判定都建立在精确
1125
+ * 高度上:
1126
+ * body.scrollTop = body.scrollHeight (贴底)
1127
+ * scrollHeight - scrollTop - clientHeight < 24 (判断用户是否上滚)
1128
+ * 结果是贴底到不了底、自动跟随被误判成「用户上滚了」而静默停止、「回到底部」也回不到底 ——
1129
+ * FOLLOW 的全部价值(贴着看最新行)直接归零,单容器与聚合两条路径同时中招。
1130
+ *
1131
+ * 于是 D63 的性能优化只保留 `logStats` 的 useMemo(见 index.js 的 logStatsValue),
1132
+ * 恢复精确的 scrollHeight;真窗口化(DOM 节点数与缓冲行数解耦,不再依赖估算高度)
1133
+ * 见 DEFECTS.md 待办「日志渲染性能(D63 的根治)」,落地前不要在这里再开 content-visibility。
1134
+ */
1135
+ }
1136
+
1137
+ .dk_logLine mark {
1138
+ background: color-mix(in srgb, var(--dk-warn) 62%, transparent);
1139
+ color: inherit;
1140
+ border-radius: 2px;
1141
+ }
1142
+
1143
+ .dk_logLevel {
1144
+ margin-right: var(--dk-gap-sm);
1145
+ font-weight: 600;
1146
+ }
1147
+
1148
+ .dk_logLevel[data-level="TRACE"] { color: #8a93a5; }
1149
+ .dk_logLevel[data-level="DEBUG"] { color: #8a93a5; }
1150
+ .dk_logLevel[data-level="INFO"] { color: #6ea8fe; }
1151
+ .dk_logLevel[data-level="WARN"] { color: #e0a94a; }
1152
+ .dk_logLevel[data-level="ERROR"] { color: #ff6b6b; }
1153
+ .dk_logLevel[data-level="FATAL"] { color: #ff6b6b; font-weight: 700; }
1154
+
1155
+ .dk_logTs { margin-right: var(--dk-gap-sm); color: #7f8899; }
1156
+ .dk_logText { color: inherit; }
1157
+ .dk_logMore { color: #7f8899; font-style: italic; }
1158
+
1159
+ /* ------------------------------------------------------------------ *
1160
+ * 日志 → 会话桥:右键菜单 / 预览卡片 / 失败提示
1161
+ *
1162
+ * 三者都挂在 document.body 上,而不是面板的 React 树里:`.dk_logBody` 是
1163
+ * overflow:auto,内联渲染会跟着日志一起滚走;而宿主只向浏览器半体注入
1164
+ * react / react/jsx-runtime / react-dom/client,没有 react-dom,也就拿不到
1165
+ * createPortal。好在 `--dk-*` 令牌声明在 `:where(html, body)` 上,body 下的
1166
+ * 浮层自动继承,不必搬令牌。
1167
+ *
1168
+ * z-index 必须高于 `.dk_backdrop` 的 2140:模态形态下面板整个在 backdrop 里,
1169
+ * 菜单挂到 body 后若低于它就会被盖住、点不出来。
1170
+ * ------------------------------------------------------------------ */
1171
+
1172
+ .dk_menu {
1173
+ position: fixed;
1174
+ z-index: 2160;
1175
+ min-width: 220px;
1176
+ max-width: 320px;
1177
+ padding: var(--dk-gap-xs);
1178
+ border: 1px solid var(--dk-border-strong);
1179
+ border-radius: var(--dk-r-md);
1180
+ background: var(--dk-surface-solid);
1181
+ box-shadow: var(--dk-shadow);
1182
+ color: var(--dk-label);
1183
+ font-size: 12px;
1184
+ }
1185
+
1186
+ .dk_menuHead { padding: 6px 8px 2px; font-weight: 600; }
1187
+ .dk_menuSub {
1188
+ padding: 0 8px 6px;
1189
+ color: var(--dk-label-3);
1190
+ font-size: 11px;
1191
+ line-height: 1.5;
1192
+ word-break: break-all;
1193
+ }
1194
+
1195
+ .dk_menuItem {
1196
+ display: flex;
1197
+ flex-direction: column;
1198
+ gap: 2px;
1199
+ width: 100%;
1200
+ padding: 6px 8px;
1201
+ border: none;
1202
+ border-radius: var(--dk-r-sm);
1203
+ background: transparent;
1204
+ color: inherit;
1205
+ font: inherit;
1206
+ text-align: left;
1207
+ cursor: pointer;
1208
+ }
1209
+
1210
+ .dk_menuItem:hover:not(:disabled) { background: var(--dk-hover); }
1211
+ .dk_menuItem:focus-visible { outline: none; box-shadow: var(--dk-ring); }
1212
+ .dk_menuItem:disabled { cursor: not-allowed; }
1213
+ .dk_menuItem:disabled .dk_menuItemLabel { color: var(--dk-label-3); }
1214
+ .dk_menuItemHint { color: var(--dk-label-3); font-size: 11px; }
1215
+
1216
+ .dk_menuNote {
1217
+ margin-top: var(--dk-gap-xs);
1218
+ padding: 6px 8px 2px;
1219
+ border-top: 1px solid var(--dk-border);
1220
+ color: var(--dk-label-3);
1221
+ font-size: 11px;
1222
+ line-height: 1.5;
1223
+ }
1224
+
1225
+ .dk_askToast {
1226
+ position: fixed;
1227
+ right: 16px;
1228
+ bottom: 16px;
1229
+ z-index: 2180;
1230
+ max-width: 420px;
1231
+ padding: 10px 14px;
1232
+ border: 1px solid color-mix(in srgb, var(--dk-danger) 40%, transparent);
1233
+ border-radius: var(--dk-r-md);
1234
+ background: var(--dk-surface-solid);
1235
+ box-shadow: var(--dk-shadow);
1236
+ color: var(--dk-danger);
1237
+ font-size: 12px;
1238
+ line-height: 1.6;
1239
+ }
1240
+
1241
+ /* 成功回执:同一枚 toast 换配色(默认那套是「失败」的红) */
1242
+ .dk_askToast[data-kind="ok"] {
1243
+ border-color: color-mix(in srgb, var(--dk-success) 42%, transparent);
1244
+ color: var(--dk-success);
1245
+ }
1246
+
1247
+ /* 键值行 */
1248
+ .dk_kv {
1249
+ display: grid;
1250
+ grid-template-columns: 92px 1fr;
1251
+ gap: var(--dk-gap-xs) var(--dk-gap-md);
1252
+ font-size: 12px;
1253
+ }
1254
+
1255
+ .dk_kvKey { color: var(--dk-label-3); }
1256
+ /* pre-line(D59):多行值(多挂载 / 多网络 / 多标签)保留换行,仍可自动折行——
1257
+ 默认 normal 会把 '\n' 折叠成空格,多个条目挤成一行难以分辨边界 */
1258
+ .dk_kvVal { color: var(--dk-label); word-break: break-all; white-space: pre-line; }
1259
+ .dk_kvValMono { font-family: var(--dk-mono); }
1260
+
1261
+ /* 日志 */
1262
+ .dk_logBar {
1263
+ display: flex;
1264
+ align-items: center;
1265
+ gap: var(--dk-gap-sm);
1266
+ flex-wrap: wrap;
1267
+ margin-bottom: var(--dk-gap-sm);
1268
+ }
1269
+
1270
+ .dk_logBar .dk_input { height: var(--dk-h-md); min-width: 0; }
1271
+
1272
+ .dk_logBox {
1273
+ margin: 0;
1274
+ padding: var(--dk-gap-md);
1275
+ border-radius: var(--dk-r-md);
1276
+ background: var(--dk-log-bg);
1277
+ color: var(--dk-log-fg);
1278
+ font-family: var(--dk-mono);
1279
+ font-size: 12px;
1280
+ line-height: 1.55;
1281
+ white-space: pre-wrap;
1282
+ word-break: break-word;
1283
+ max-height: none;
1284
+ }
1285
+
1286
+ .dk_logBox mark {
1287
+ background: color-mix(in srgb, var(--dk-warn) 55%, transparent);
1288
+ color: inherit;
1289
+ border-radius: 2px;
1290
+ }
1291
+
1292
+ /* 统计 */
1293
+ .dk_stats {
1294
+ width: 100%;
1295
+ border-collapse: collapse;
1296
+ font-size: 12px;
1297
+ }
1298
+
1299
+ .dk_stats th,
1300
+ .dk_stats td {
1301
+ padding: var(--dk-gap-sm) var(--dk-gap-md);
1302
+ border-bottom: 1px solid var(--dk-border);
1303
+ text-align: left;
1304
+ white-space: nowrap;
1305
+ }
1306
+
1307
+ .dk_stats th {
1308
+ color: var(--dk-label-3);
1309
+ font-weight: 500;
1310
+ }
1311
+
1312
+ .dk_stats td.dk_num { font-family: var(--dk-mono); text-align: right; }
1313
+
1314
+ .dk_bar {
1315
+ position: relative;
1316
+ width: 72px;
1317
+ height: 6px;
1318
+ border-radius: var(--dk-r-pill);
1319
+ background: var(--dk-surface-3);
1320
+ overflow: hidden;
1321
+ }
1322
+
1323
+ .dk_barFill {
1324
+ position: absolute;
1325
+ inset: 0 auto 0 0;
1326
+ border-radius: var(--dk-r-pill);
1327
+ background: var(--dk-accent);
1328
+ transition: width var(--dk-dur) var(--dk-ease);
1329
+ }
1330
+
1331
+ .dk_barFill[data-warn="1"] { background: var(--dk-warn); }
1332
+ .dk_barFill[data-danger="1"] { background: var(--dk-danger); }
1333
+
1334
+ /* ============================ 镜像表 ============================ */
1335
+
1336
+ .dk_tableWrap { overflow: auto; }
1337
+
1338
+ /*
1339
+ * 镜像页:搜索框已经在工具条(固定区),这里再让表体自己滚、表头钉住——
1340
+ * 长镜像列表滚到底时列名(镜像 / 大小 / 创建 / ID)仍然看得见。
1341
+ */
1342
+ .dk_mainImages {
1343
+ display: flex;
1344
+ flex-direction: column;
1345
+ /* auto 而不是 hidden:横幅 / 空态在矮面板里仍可滚,不会被裁掉 */
1346
+ overflow: auto;
1347
+ }
1348
+
1349
+ .dk_imagesView {
1350
+ display: flex;
1351
+ flex-direction: column;
1352
+ flex: 1 1 auto;
1353
+ min-height: 0;
1354
+ }
1355
+
1356
+ .dk_mainImages .dk_tableWrap {
1357
+ flex: 1 1 auto;
1358
+ min-height: 0;
1359
+ }
1360
+
1361
+ .dk_images thead th {
1362
+ position: sticky;
1363
+ top: 0;
1364
+ z-index: 1;
1365
+ background: var(--dk-surface-solid);
1366
+ }
1367
+
1368
+ .dk_images {
1369
+ width: 100%;
1370
+ border-collapse: collapse;
1371
+ font-size: 12px;
1372
+ }
1373
+
1374
+ .dk_images th,
1375
+ .dk_images td {
1376
+ padding: var(--dk-gap-sm) var(--dk-gap-md);
1377
+ border-bottom: 1px solid var(--dk-border);
1378
+ text-align: left;
1379
+ white-space: nowrap;
1380
+ }
1381
+
1382
+ .dk_images th { color: var(--dk-label-3); font-weight: 500; }
1383
+ .dk_images td.dk_mono { font-family: var(--dk-mono); }
1384
+ .dk_images tbody tr:hover { background: var(--dk-hover); }
1385
+
1386
+ /* ============================ 临时多选聚合 ============================ */
1387
+
1388
+ /*
1389
+ * 勾选框:纯 CSS 画方框 + 勾(用真实 <input type="checkbox"> 会带进浏览器原生
1390
+ * 尺寸/对齐差异,还要处理只读态)。未勾选时把勾的字色设成透明,形状仍然占位,
1391
+ * 所以勾选/取消不会让卡片头部横向抖动。
1392
+ */
1393
+ .dk_pick {
1394
+ flex: none;
1395
+ display: inline-flex;
1396
+ align-items: center;
1397
+ justify-content: center;
1398
+ width: 16px;
1399
+ height: 16px;
1400
+ border: 1px solid var(--dk-border-strong);
1401
+ border-radius: var(--dk-r-xs);
1402
+ background: var(--dk-input);
1403
+ color: transparent;
1404
+ font-size: 11px;
1405
+ line-height: 1;
1406
+ transition: background var(--dk-dur) var(--dk-ease), border-color var(--dk-dur) var(--dk-ease), color var(--dk-dur) var(--dk-ease);
1407
+ }
1408
+
1409
+ .dk_pick::after { content: '✓'; }
1410
+ .dk_pick[data-on="1"] { background: var(--dk-accent); border-color: var(--dk-accent); color: #fff; }
1411
+
1412
+ /* 选择态:整张卡片就是勾选开关,动作条收起(否则一边多选一边能点到启停删) */
1413
+ .dk_card[data-pick="1"] .dk_actionBar { display: none; }
1414
+ .dk_card[data-picked="1"] { border-color: var(--dk-accent); box-shadow: var(--dk-ring); }
1415
+
1416
+ /* 聚合操作条:夹在工具条与正文之间的一条横栏,计数靠左、按钮靠右 */
1417
+ /* 选择态操作条:第一行是计数 + 动作,第二行是「按条件选中」chips */
1418
+ .dk_pickBar {
1419
+ display: flex;
1420
+ flex-direction: column;
1421
+ gap: var(--dk-gap-sm);
1422
+ flex: 0 0 auto;
1423
+ padding: var(--dk-gap-sm) var(--dk-gap-lg);
1424
+ border-bottom: 1px solid var(--dk-border);
1425
+ background: var(--dk-surface-2);
1426
+ }
1427
+
1428
+ .dk_pickRow {
1429
+ display: flex;
1430
+ align-items: center;
1431
+ gap: var(--dk-gap-md);
1432
+ flex-wrap: wrap;
1433
+ }
1434
+
1435
+ .dk_pickPresets {
1436
+ display: flex;
1437
+ align-items: center;
1438
+ gap: var(--dk-gap-sm);
1439
+ flex-wrap: wrap;
1440
+ }
1441
+
1442
+ .dk_pickPresetsLabel {
1443
+ font-size: 11px;
1444
+ color: var(--dk-label-3);
1445
+ }
1446
+
1447
+ /* 条件 chip:与 pill 同族的轻量按钮(一行放得下 5~6 个) */
1448
+ .dk_chip {
1449
+ height: var(--dk-h-sm);
1450
+ padding: 0 var(--dk-gap-md);
1451
+ border: 1px solid var(--dk-border-strong);
1452
+ border-radius: var(--dk-r-pill);
1453
+ background: var(--dk-surface-solid);
1454
+ color: var(--dk-label-2);
1455
+ font-size: 11px;
1456
+ font-family: inherit;
1457
+ font-variant-numeric: tabular-nums;
1458
+ cursor: pointer;
1459
+ transition: background var(--dk-dur) var(--dk-ease), color var(--dk-dur) var(--dk-ease), border-color var(--dk-dur) var(--dk-ease);
1460
+ }
1461
+
1462
+ .dk_chip:hover { background: var(--dk-hover); color: var(--dk-label); border-color: var(--dk-accent); }
1463
+ .dk_chip:focus-visible { outline: none; box-shadow: var(--dk-ring); }
1464
+ .dk_chipQuiet { border-color: var(--dk-border); color: var(--dk-label-3); }
1465
+ .dk_pickNotice { margin-left: auto; }
1466
+
1467
+ .dk_pickCount {
1468
+ font-size: 12px;
1469
+ font-weight: 600;
1470
+ color: var(--dk-label);
1471
+ font-variant-numeric: tabular-nums;
1472
+ }
1473
+
1474
+ /* 提示在窄栏里可以换行,但不要撑破行 */
1475
+ .dk_pickHint { min-width: 0; }
1476
+
1477
+ /* ============================ 事件活动条 ============================ */
1478
+
1479
+ /*
1480
+ * 活动条:贴在容器列表头部的一条窄栏。默认展开、可折叠——折叠只收起明细,
1481
+ * 事件流照常跑(它不是暂停开关),所以折叠态也要留着标题与状态点。
1482
+ */
1483
+ .dk_activity {
1484
+ flex: 0 0 auto;
1485
+ margin-bottom: var(--dk-gap-lg);
1486
+ border: 1px solid var(--dk-border);
1487
+ border-radius: var(--dk-r-md);
1488
+ background: var(--dk-surface-2);
1489
+ overflow: hidden;
1490
+ }
1491
+
1492
+ .dk_activityHead {
1493
+ display: flex;
1494
+ align-items: center;
1495
+ gap: var(--dk-gap-md);
1496
+ width: 100%;
1497
+ min-width: 0;
1498
+ padding: var(--dk-gap-sm) var(--dk-gap-lg);
1499
+ border: none;
1500
+ background: transparent;
1501
+ color: var(--dk-label);
1502
+ font-family: inherit;
1503
+ font-size: 12px;
1504
+ text-align: left;
1505
+ cursor: pointer;
1506
+ }
1507
+
1508
+ .dk_activityHead:hover { background: var(--dk-hover); }
1509
+ .dk_activityHead:focus-visible { outline: none; box-shadow: var(--dk-ring); }
1510
+ .dk_activityTitle { flex: none; font-weight: 600; letter-spacing: .3px; }
1511
+
1512
+ /* 状态点沿用 .dk_followState 的语义色,但内联在标题行里、不占整行也没有下边框 */
1513
+ .dk_activityState {
1514
+ display: inline-flex;
1515
+ align-items: center;
1516
+ gap: var(--dk-gap-sm);
1517
+ flex: none;
1518
+ color: var(--dk-label-3);
1519
+ font-size: 11px;
1520
+ }
1521
+
1522
+ .dk_activityState::before {
1523
+ content: '';
1524
+ width: 6px;
1525
+ height: 6px;
1526
+ border-radius: 50%;
1527
+ background: var(--dk-label-3);
1528
+ }
1529
+
1530
+ .dk_activityState[data-state="open"]::before { background: var(--dk-success); }
1531
+ .dk_activityState[data-state="connecting"]::before,
1532
+ .dk_activityState[data-state="reconnecting"]::before { background: var(--dk-warn); }
1533
+ .dk_activityState[data-state="closed"]::before,
1534
+ .dk_activityState[data-state="unsupported"]::before { background: var(--dk-danger); }
1535
+
1536
+ .dk_activityChevron {
1537
+ flex: none;
1538
+ display: inline-flex;
1539
+ color: var(--dk-label-3);
1540
+ transition: transform var(--dk-dur) var(--dk-ease);
1541
+ }
1542
+
1543
+ .dk_activity[data-open="1"] .dk_activityChevron { transform: rotate(180deg); }
1544
+
1545
+ .dk_activityList {
1546
+ display: flex;
1547
+ flex-wrap: wrap;
1548
+ gap: var(--dk-gap-sm);
1549
+ padding: 0 var(--dk-gap-lg) var(--dk-gap-md);
1550
+ }
1551
+
1552
+ .dk_activityEmpty {
1553
+ padding: 0 var(--dk-gap-lg) var(--dk-gap-md);
1554
+ color: var(--dk-label-3);
1555
+ font-size: 11px;
1556
+ }
1557
+
1558
+ .dk_activityItem {
1559
+ display: inline-flex;
1560
+ align-items: center;
1561
+ gap: var(--dk-gap-sm);
1562
+ max-width: 100%;
1563
+ padding: 2px var(--dk-gap-sm);
1564
+ border: 1px solid var(--dk-border);
1565
+ border-radius: var(--dk-r-xs);
1566
+ background: var(--dk-surface-solid);
1567
+ font-size: 11px;
1568
+ }
1569
+
1570
+ .dk_activityTime {
1571
+ color: var(--dk-label-3);
1572
+ font-family: var(--dk-mono);
1573
+ font-variant-numeric: tabular-nums;
1574
+ }
1575
+
1576
+ .dk_activityName {
1577
+ max-width: 200px;
1578
+ color: var(--dk-label);
1579
+ font-family: var(--dk-mono);
1580
+ overflow: hidden;
1581
+ text-overflow: ellipsis;
1582
+ white-space: nowrap;
1583
+ }
1584
+
1585
+ .dk_activityAction { color: var(--dk-label-2); }
1586
+
1587
+ /* 动作着色:start 是「起来了」,die / oom / kill 是「出事了」,health 只陈述状态 */
1588
+ .dk_activityItem[data-action="start"] .dk_activityAction { color: var(--dk-success); font-weight: 600; }
1589
+ .dk_activityItem[data-action="die"] .dk_activityAction,
1590
+ .dk_activityItem[data-action="oom"] .dk_activityAction,
1591
+ .dk_activityItem[data-action="kill"] .dk_activityAction { color: var(--dk-danger); font-weight: 600; }
1592
+ .dk_activityItem[data-action="stop"] .dk_activityAction,
1593
+ .dk_activityItem[data-action="destroy"] .dk_activityAction { color: var(--dk-warn); }
1594
+ .dk_activityItem[data-action="health_status"] .dk_activityAction { color: var(--dk-label-2); font-style: italic; }
1595
+
1596
+ /* ==================== 变更操作的「执行中」态 ==================== */
1597
+
1598
+ /*
1599
+ * 启停删在飞的时候(docker stop / rm 要等容器真的退出,秒级到十几秒):
1600
+ * 1. 正在跑的那个按钮把图标换成转圈(见 IconAction 的 busy);
1601
+ * 2. 同一张卡片的其它变更按钮置灰压暗,直观表示「这一组先别动」;
1602
+ * 3. 卡片描边走强调色,扫列表时能看出哪几张正在处理。
1603
+ * 置灰按钮本身有 opacity .45,busy 的那个要顶掉它,否则转圈也跟着变淡。
1604
+ */
1605
+ .dk_iconBtn[data-busy="1"] { color: var(--dk-accent); }
1606
+ .dk_iconBtn[data-busy="1"]:disabled { opacity: 1; cursor: progress; }
1607
+ .dk_card[data-pending="1"] .dk_actionBar .dk_iconBtn:not([data-busy="1"]) { opacity: .4; }
1608
+ .dk_card[data-pending="1"] { border-color: color-mix(in srgb, var(--dk-accent) 45%, transparent); }
1609
+
1610
+ /* 确认框里的「执行中…」:转圈 + 文案并排,不撑破按钮高度 */
1611
+ .dk_confirmBusy { display: inline-flex; align-items: center; gap: var(--dk-gap-sm); }
1612
+
1613
+ /* 忙碌的确认键同样要顶掉 .dk_btn:disabled 的 .45,否则「执行中…」看不清(取消键保持置灰) */
1614
+ .dk_confirm[data-busy="1"] .dk_btnDanger { opacity: 1; cursor: progress; }
1615
+
1616
+ /* ==================== 网络 / 卷列表 ==================== */
1617
+
1618
+ /*
1619
+ * 可点击的表格行(网络 / 卷列表:行 = 进详情的入口)。
1620
+ * 只给 cursor + hover 提示,不加下划线——表格里满屏下划线会更难看。
1621
+ */
1622
+ .dk_rowClickable { cursor: pointer; }
1623
+ .dk_rowClickable:hover { background: var(--dk-hover); }
1624
+
1625
+ /* 挂载点列:路径很长,撑宽表格会把别的列挤没,这里限宽 + 省略号,title 里给全量 */
1626
+ .dk_pathCell { max-width: 420px; overflow: hidden; text-overflow: ellipsis; }
1627
+
1628
+ /* ====================== 多目标总览(只读) ========================= */
1629
+
1630
+ /*
1631
+ * 一屏看全部目标:上面一行计数卡,下面异常表。卡片行不滚(目标数是配置出来的,
1632
+ * 通常个位数),异常表自己滚、表头吸顶——与镜像 / 网络 / 卷页同款,靠 .dk_mainImages
1633
+ * 里那条 `.dk_tableWrap { flex: 1 }` 生效,所以正文外层必须是 flex 列的 .dk_imagesView。
1634
+ */
1635
+ .dk_ovCards {
1636
+ display: grid;
1637
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
1638
+ gap: var(--dk-gap-md);
1639
+ margin-bottom: var(--dk-gap-lg);
1640
+ }
1641
+
1642
+ .dk_ovCard {
1643
+ display: flex;
1644
+ flex-direction: column;
1645
+ gap: var(--dk-gap-sm);
1646
+ padding: var(--dk-gap-md) var(--dk-gap-lg);
1647
+ border: 1px solid var(--dk-border);
1648
+ border-radius: var(--dk-r-md);
1649
+ background: var(--dk-surface-solid);
1650
+ color: inherit;
1651
+ font: inherit;
1652
+ text-align: left;
1653
+ cursor: pointer;
1654
+ transition: border-color var(--dk-dur) var(--dk-ease), box-shadow var(--dk-dur) var(--dk-ease);
1655
+ }
1656
+
1657
+ .dk_ovCard:hover { border-color: var(--dk-border-strong); box-shadow: var(--dk-shadow); }
1658
+ .dk_ovCard:focus-visible { outline: none; box-shadow: var(--dk-ring); }
1659
+
1660
+ /* 不可达的卡要在一屏里第一眼被挑出来:危险色描边 + 淡底,和「数字都正常」的卡拉开 */
1661
+ .dk_ovCard[data-state="error"] {
1662
+ border-color: color-mix(in srgb, var(--dk-danger) 40%, transparent);
1663
+ background: color-mix(in srgb, var(--dk-danger) 8%, transparent);
1664
+ }
1665
+
1666
+ .dk_ovCardHead {
1667
+ display: flex;
1668
+ align-items: center;
1669
+ gap: var(--dk-gap-sm);
1670
+ min-width: 0;
1671
+ }
1672
+
1673
+ .dk_ovCardName {
1674
+ flex: 1 1 auto;
1675
+ min-width: 0;
1676
+ font-size: 13px;
1677
+ font-weight: 600;
1678
+ overflow: hidden;
1679
+ text-overflow: ellipsis;
1680
+ white-space: nowrap;
1681
+ }
1682
+
1683
+ /*
1684
+ * 计数区:**两列网格 + 固定宽度的数字槽**。
1685
+ * 原来是 flex-wrap:四项换行后每项宽度随内容变(「21 运行中」比「0 不健康」宽),
1686
+ * 第二列就跟着内容左右错开,看起来「文字没对齐」。
1687
+ * - 网格保证同一列的起点一致(两行两列对齐);
1688
+ * - 数字槽固定宽 + 右对齐 + tabular-nums,保证「21」与「0」之后的标签也在同一 x
1689
+ * (12px 容器字号下 2.8ch ≈ 34px,够放三位数;真超过时标签右移一位,可接受)。
1690
+ */
1691
+ .dk_ovCardCounts {
1692
+ display: grid;
1693
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1694
+ gap: var(--dk-gap-sm) var(--dk-gap-lg);
1695
+ }
1696
+
1697
+ .dk_ovCount {
1698
+ display: grid;
1699
+ grid-template-columns: 2.8ch auto;
1700
+ align-items: baseline;
1701
+ gap: var(--dk-gap-xs);
1702
+ min-width: 0;
1703
+ font-size: 12px;
1704
+ color: var(--dk-label-3);
1705
+ }
1706
+
1707
+ .dk_ovCountLabel { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1708
+
1709
+ .dk_ovCountValue {
1710
+ font-size: 16px;
1711
+ font-weight: 600;
1712
+ font-variant-numeric: tabular-nums;
1713
+ text-align: right;
1714
+ color: var(--dk-label);
1715
+ }
1716
+
1717
+ .dk_ovCount[data-state="running"] .dk_ovCountValue { color: var(--dk-success); }
1718
+ .dk_ovCount[data-state="unhealthy"] .dk_ovCountValue { color: var(--dk-danger); }
1719
+ /*
1720
+ * 还没答完的卡:只给读取态,不出 0/0/0(那会被读成「这台机器没有容器」,而它只是还没
1721
+ * 回答——SSH 目标不可达时要等 readyTimeout 20s)。整卡压暗一点,与已有结果区分开。
1722
+ */
1723
+ .dk_ovCard[data-state="loading"] { opacity: .7; }
1724
+
1725
+ .dk_ovCardLoading {
1726
+ display: flex;
1727
+ align-items: center;
1728
+ gap: var(--dk-gap-sm);
1729
+ font-size: 12px;
1730
+ color: var(--dk-label-3);
1731
+ }
1732
+
1733
+ /* 不健康为 0 时数字即结论,不该继续用危险色喊人 */
1734
+ .dk_ovCount[data-state="unhealthy"][data-zero="1"] .dk_ovCountValue { color: var(--dk-label-3); }
1735
+
1736
+ .dk_ovCardError {
1737
+ display: flex;
1738
+ align-items: center;
1739
+ gap: var(--dk-gap-sm);
1740
+ min-width: 0;
1741
+ font-size: 12px;
1742
+ }
1743
+
1744
+ /* SSH 报错是一长串:卡片里只留一行,全量在 title 里 */
1745
+ .dk_ovCardErrorText {
1746
+ flex: 1 1 auto;
1747
+ min-width: 0;
1748
+ color: var(--dk-label-3);
1749
+ overflow: hidden;
1750
+ text-overflow: ellipsis;
1751
+ white-space: nowrap;
1752
+ }
1753
+
1754
+ .dk_ovSection {
1755
+ margin: 0 0 var(--dk-gap-sm);
1756
+ font-size: 12px;
1757
+ font-weight: 600;
1758
+ color: var(--dk-label-2);
1759
+ }
1760
+
1761
+ /* 「一切正常」/「读取中」贴在卡片行下面,不需要满屏 48px 的留白 */
1762
+ .dk_ovEmpty { padding: 24px var(--dk-gap-lg); }
1763
+
1764
+ /* ============================ 空态 / 提示 ============================ */
1765
+
1766
+ .dk_empty {
1767
+ display: flex;
1768
+ flex-direction: column;
1769
+ align-items: center;
1770
+ justify-content: center;
1771
+ gap: var(--dk-gap-md);
1772
+ padding: 48px var(--dk-gap-lg);
1773
+ color: var(--dk-label-3);
1774
+ text-align: center;
1775
+ }
1776
+
1777
+ .dk_emptyTitle { font-size: 13px; color: var(--dk-label-2); }
1778
+ .dk_emptyHint { font-size: 12px; max-width: 420px; line-height: 1.6; }
1779
+
1780
+ .dk_banner {
1781
+ display: flex;
1782
+ align-items: flex-start;
1783
+ gap: var(--dk-gap-sm);
1784
+ margin: 0 0 var(--dk-gap-lg);
1785
+ padding: var(--dk-gap-md) var(--dk-gap-lg);
1786
+ border: 1px solid color-mix(in srgb, var(--dk-danger) 32%, transparent);
1787
+ border-radius: var(--dk-r-md);
1788
+ background: color-mix(in srgb, var(--dk-danger) 8%, transparent);
1789
+ color: var(--dk-label);
1790
+ font-size: 12px;
1791
+ line-height: 1.6;
1792
+ }
1793
+
1794
+ .dk_banner[data-kind="warn"] {
1795
+ border-color: color-mix(in srgb, var(--dk-warn) 32%, transparent);
1796
+ background: color-mix(in srgb, var(--dk-warn) 8%, transparent);
1797
+ }
1798
+
1799
+ .dk_banner[data-kind="info"] {
1800
+ border-color: var(--dk-border-strong);
1801
+ background: var(--dk-surface-2);
1802
+ }
1803
+
1804
+ .dk_bannerIcon { flex: 0 0 auto; margin-top: 1px; color: currentColor; }
1805
+ .dk_bannerBody { flex: 1 1 auto; min-width: 0; }
1806
+ .dk_bannerAction { flex: 0 0 auto; display: inline-flex; align-items: center; }
1807
+
1808
+ /* ============================ 确认框 ============================ */
1809
+
1810
+ .dk_confirmBackdrop {
1811
+ position: absolute;
1812
+ inset: 0;
1813
+ z-index: 5;
1814
+ display: flex;
1815
+ align-items: center;
1816
+ justify-content: center;
1817
+ padding: var(--dk-gap-lg);
1818
+ background: color-mix(in srgb, #000 40%, transparent);
1819
+ }
1820
+
1821
+ .dk_confirm {
1822
+ width: min(420px, 100%);
1823
+ padding: var(--dk-gap-xl);
1824
+ border: 1px solid var(--dk-border-strong);
1825
+ border-radius: var(--dk-r-lg);
1826
+ background: var(--dk-surface-solid);
1827
+ box-shadow: var(--dk-shadow-lg);
1828
+ }
1829
+
1830
+ .dk_confirmTitle { font-size: 13px; font-weight: 600; margin-bottom: var(--dk-gap-md); }
1831
+ .dk_confirmText { font-size: 12px; color: var(--dk-label-2); line-height: 1.7; margin-bottom: var(--dk-gap-lg); }
1832
+ .dk_confirmActions { display: flex; justify-content: flex-end; gap: var(--dk-gap-md); }
1833
+
1834
+ /* ============================ 设置卡片 ============================ */
1835
+
1836
+ /*
1837
+ * 外框与 DSH 内置卡片 / 其他插件卡片(pM_pluginCard、tt_card)同一套度量:
1838
+ * border-l2 + bg-layer-3(展开时 layer-2)、12px 圆角、标题行 14/16、
1839
+ * 标题 14px/600、描述 12px/label-secondary。
1840
+ * 之前是内联的 10px 圆角 + 更浅的边框 + 13px 标题,在设置列表里一眼就是「另一套」。
1841
+ */
1842
+ /* DSH ≥0.1.6 的插件配置页只取表单本体,页面自己画标题与卡片外壳,这里不再套一层卡片。 */
1843
+ .dk_pageHost { display: block; }
1844
+
1845
+ .dk_settingsCard {
1846
+ list-style: none;
1847
+ border: 1px solid var(--dk-border-strong);
1848
+ border-radius: var(--dk-r-xl);
1849
+ background: var(--dk-surface-3);
1850
+ transition: border-color var(--dk-dur) var(--dk-ease), background var(--dk-dur) var(--dk-ease);
1851
+ }
1852
+
1853
+ .dk_settingsCard:hover { border-color: var(--dk-label-dimmed); }
1854
+ .dk_settingsCardOpen { background: var(--dk-surface-2); border-color: var(--dk-label-dimmed); }
1855
+
1856
+ .dk_settingsHead {
1857
+ display: flex;
1858
+ align-items: center;
1859
+ gap: var(--dk-gap-lg);
1860
+ width: 100%;
1861
+ padding: var(--dk-pad-cardHead);
1862
+ border: 0;
1863
+ border-radius: var(--dk-r-xl);
1864
+ background: transparent;
1865
+ color: inherit;
1866
+ font: inherit;
1867
+ text-align: left;
1868
+ cursor: pointer;
1869
+ }
1870
+
1871
+ .dk_settingsHead:focus-visible { outline: none; box-shadow: var(--dk-ring); }
1872
+ .dk_settingsHeadText { display: flex; flex-direction: column; gap: var(--dk-gap-xs); flex: 1; min-width: 0; }
1873
+ .dk_settingsName { color: var(--dk-label); font-size: 14px; font-weight: 600; line-height: 1.4; }
1874
+ /* dsh-plugin-kit 套件徽标(约定见 @hyzyn/dsh-kit README:全部 kit 设置卡片共用) */
1875
+ .dshkit_badge {
1876
+ flex: none;
1877
+ margin-left: auto;
1878
+ padding: 1px 8px;
1879
+ border-radius: 999px;
1880
+ font-size: 11px;
1881
+ font-weight: 600;
1882
+ line-height: 16px;
1883
+ letter-spacing: 0.02em;
1884
+ color: var(--dk-label-2, var(--dsw-alias-label-dimmed));
1885
+ background: var(--dk-bg-2, var(--dsw-alias-bg-layer-2));
1886
+ border: 1px solid var(--dk-border, var(--dsw-alias-border-l1));
1887
+ }
1888
+ .dk_settingsDesc { color: var(--dk-label-2); font-size: 12px; line-height: 1.5; }
1889
+
1890
+ .dk_settingsChevron {
1891
+ flex: none;
1892
+ display: inline-flex;
1893
+ color: var(--dk-label-3);
1894
+ transition: transform var(--dk-dur) var(--dk-ease);
1895
+ }
1896
+
1897
+ .dk_settingsCardOpen .dk_settingsChevron { transform: rotate(180deg); }
1898
+
1899
+ .dk_settingsBody {
1900
+ display: flex;
1901
+ flex-direction: column;
1902
+ gap: var(--dk-gap-lg);
1903
+ padding: 2px var(--dk-gap-xl) var(--dk-gap-xl);
1904
+ color: var(--dk-label);
1905
+ font-size: 13px;
1906
+ }
1907
+
1908
+ .dk_cardSection {
1909
+ margin-top: var(--dk-gap-xs);
1910
+ padding-bottom: var(--dk-gap-xs);
1911
+ border-bottom: 1px solid var(--dk-border);
1912
+ color: var(--dk-label-3);
1913
+ font-size: 12px;
1914
+ font-weight: 600;
1915
+ letter-spacing: .3px;
1916
+ }
1917
+
1918
+ .dk_field {
1919
+ display: flex;
1920
+ flex-direction: column;
1921
+ gap: var(--dk-gap-sm);
1922
+ min-width: 0;
1923
+ }
1924
+
1925
+ /*
1926
+ * 标签行高必须锁死。
1927
+ * 中文走 PingFang SC 回退字体,行框比纯拉丁文本高 ~2.5px(16.5 vs 14);栅格是
1928
+ * align-items:start,同一行里「docker CLI」和「统计刷新间隔(秒)」的输入框就会
1929
+ * 差 2.5px 错开,基线也高低不齐。给死行高后两种文本的行框等高,输入框顶边对齐。
1930
+ * 别删成 line-height: normal —— 一删就退回字体度量决定行框。
1931
+ */
1932
+ .dk_label { font-size: 12px; line-height: 1.4; color: var(--dk-label-2); }
1933
+ .dk_hint { font-size: 11px; color: var(--dk-label-3); line-height: 1.6; }
1934
+ .dk_row { display: flex; align-items: center; gap: var(--dk-gap-md); flex-wrap: wrap; }
1935
+
1936
+ /*
1937
+ * 设置卡片的字段栅格:**等宽** 3 列(窄屏 2 列)。
1938
+ * 不要再给单个字段跨列——第一行跨 2 列、第二行单列,行与行之间宽度不一致,
1939
+ * 看起来比不对齐还乱(v0.1.22 的教训)。
1940
+ */
1941
+ .dk_fieldGrid {
1942
+ display: grid;
1943
+ /* auto-fit + minmax:列宽跟着**容器**走,而不是视口 —— 设置面板本身可能是窄的。
1944
+ 卡片 ~966px → 3 列;~570px → 2 列;再窄 → 1 列,永远不会叠 */
1945
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
1946
+ gap: var(--dk-gap-lg) var(--dk-gap-xl);
1947
+ align-items: start;
1948
+ }
1949
+
1950
+ .dk_fieldGrid .dk_input,
1951
+ .dk_fieldGrid .dk_select { min-width: 0; }
1952
+
1953
+ /* 数字输入去掉浏览器原生上下箭头:各平台样式不一,还会吃掉右内边距 */
1954
+ .dk_input[type="number"] {
1955
+ -moz-appearance: textfield;
1956
+ appearance: textfield;
1957
+ font-variant-numeric: tabular-nums;
1958
+ }
1959
+
1960
+ .dk_input[type="number"]::-webkit-outer-spin-button,
1961
+ .dk_input[type="number"]::-webkit-inner-spin-button {
1962
+ -webkit-appearance: none;
1963
+ margin: 0;
1964
+ }
1965
+
1966
+ /*
1967
+ * 目标行与内联 SSH 字段共用同一套列宽模板(1fr · 96px · 1fr · 150px),
1968
+ * 内联块整宽铺开 → host/port/username/auth 正好落在目标名/类型/连接簿/操作列下方。
1969
+ */
1970
+ .dk_targetRow {
1971
+ display: grid;
1972
+ grid-template-columns: minmax(0, 1fr) 96px minmax(0, 1fr) minmax(0, 150px);
1973
+ gap: var(--dk-gap-sm);
1974
+ align-items: center;
1975
+ padding: var(--dk-gap-sm);
1976
+ border: 1px solid var(--dk-border);
1977
+ border-radius: var(--dk-r-md);
1978
+ background: var(--dk-surface-2);
1979
+ }
1980
+
1981
+ /* 删除按钮靠右,不跟着列宽被拉长 */
1982
+ .dk_targetRow > .dk_btnDanger { justify-self: end; }
1983
+
1984
+ .dk_targetInline {
1985
+ display: grid;
1986
+ grid-column: 1 / -1;
1987
+ grid-template-columns: minmax(0, 1fr) 96px minmax(0, 1fr) minmax(0, 150px);
1988
+ gap: var(--dk-gap-sm);
1989
+ align-items: center;
1990
+ }
1991
+
1992
+ /* 凭据占前三列,复选框固定第 4 列(与上面的 auth 下拉上下对齐) */
1993
+ .dk_targetInline > .dk_credential { grid-column: 1 / span 3; }
1994
+ .dk_targetInline > .dk_check { grid-row: 2; grid-column: 4; }
1995
+
1996
+ .dk_targetRow .dk_input,
1997
+ .dk_targetRow .dk_select {
1998
+ height: var(--dk-h-md);
1999
+ min-width: 0;
2000
+ }
2001
+
2002
+ .dk_targetHead {
2003
+ display: flex;
2004
+ align-items: center;
2005
+ justify-content: space-between;
2006
+ gap: var(--dk-gap-md);
2007
+ }
2008
+
2009
+ .dk_msg { font-size: 12px; }
2010
+ .dk_msg[data-kind="ok"] { color: var(--dk-success); }
2011
+ .dk_msg[data-kind="error"] { color: var(--dk-danger); }
2012
+
2013
+ .dk_spin {
2014
+ display: inline-block;
2015
+ width: 13px;
2016
+ height: 13px;
2017
+ border: 2px solid var(--dk-border-strong);
2018
+ border-top-color: var(--dk-accent);
2019
+ border-radius: 50%;
2020
+ animation: dk_spin .7s linear infinite;
2021
+ }
2022
+
2023
+ @keyframes dk_spin { to { transform: rotate(360deg); } }
2024
+
2025
+ .dk_srOnly {
2026
+ position: absolute;
2027
+ width: 1px;
2028
+ height: 1px;
2029
+ padding: 0;
2030
+ margin: -1px;
2031
+ overflow: hidden;
2032
+ clip: rect(0 0 0 0);
2033
+ white-space: nowrap;
2034
+ border: 0;
2035
+ }
2036
+
2037
+ /* 窄屏:抽屉改为覆盖整栏 */
2038
+ @media (max-width: 860px) {
2039
+ .dk_body { flex-direction: column; }
2040
+ .dk_targetRow,
2041
+ .dk_targetInline { grid-template-columns: minmax(0, 1fr) 80px; }
2042
+ .dk_targetInline > .dk_credential { grid-column: 1 / -1; }
2043
+ .dk_targetInline > .dk_check { grid-row: auto; grid-column: 2; }
2044
+ .dk_cardRow { grid-template-columns: 40px 1fr; }
2045
+ .dk_projectRow { grid-template-columns: 96px 1fr auto; }
2046
+ .dk_projectImage,
2047
+ .dk_projectPorts { display: none; }
2048
+ }
2049
+
2050
+ /* ============================ 镜像详情 ============================ */
2051
+
2052
+ /* 镜像表最后一列(操作):宽度收到内容、按钮右对齐,不跟数据列抢宽度 */
2053
+ .dk_colActions { width: 1%; white-space: nowrap; text-align: right; }
2054
+ .dk_rowActions { display: inline-flex; align-items: center; gap: var(--dk-gap-xs); }
2055
+
2056
+ .dk_layerList {
2057
+ display: flex;
2058
+ flex-direction: column;
2059
+ gap: 2px;
2060
+ max-height: 220px;
2061
+ overflow: auto;
2062
+ }
2063
+
2064
+ .dk_layerItem {
2065
+ display: flex;
2066
+ align-items: center;
2067
+ gap: var(--dk-gap-md);
2068
+ font-size: 12px;
2069
+ color: var(--dk-label-2);
2070
+ }
2071
+
2072
+ .dk_layerIndex {
2073
+ flex: none;
2074
+ width: 36px;
2075
+ color: var(--dk-label-3);
2076
+ font-variant-numeric: tabular-nums;
2077
+ text-align: right;
2078
+ }
2079
+
2080
+ .dk_layerId { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2081
+
2082
+ .dk_labelList { display: flex; flex-direction: column; gap: 2px; font-size: 12px; }
2083
+ .dk_labelItem { display: grid; grid-template-columns: minmax(0, 240px) 1fr; gap: var(--dk-gap-md); }
2084
+ .dk_labelKey { color: var(--dk-label-3); font-family: var(--dk-mono); overflow-wrap: anywhere; }
2085
+ .dk_labelVal { color: var(--dk-label); font-family: var(--dk-mono); overflow-wrap: anywhere; }
2086
+
2087
+ .dk_historyTable td { vertical-align: top; }
2088
+ .dk_historyCmd { max-width: 440px; overflow: hidden; text-overflow: ellipsis; }
2089
+
2090
+ /* ============================ 镜像拉取进度 ============================ */
2091
+
2092
+ .dk_pullBody { gap: var(--dk-gap-md); }
2093
+
2094
+ .dk_pullBox {
2095
+ flex: 1 1 auto;
2096
+ min-height: 180px;
2097
+ margin-top: var(--dk-gap-md);
2098
+ padding: var(--dk-gap-md);
2099
+ border: 1px solid var(--dk-border);
2100
+ border-radius: var(--dk-r-md);
2101
+ background: var(--dk-log-bg);
2102
+ color: var(--dk-log-fg);
2103
+ font-family: var(--dk-mono);
2104
+ font-size: 12px;
2105
+ line-height: 1.5;
2106
+ overflow: auto;
2107
+ white-space: pre-wrap;
2108
+ word-break: break-word;
2109
+ }
2110
+
2111
+ .dk_pullLine { min-height: 1.5em; }
2112
+ /* 逐层状态行:层键由 data-key 标出,给层状态一点色彩区分 */
2113
+ .dk_pullLine[data-key] { color: color-mix(in srgb, var(--dk-log-fg) 82%, var(--dk-accent)); }
2114
+
2115
+ /* ============================ 统计实时跟随 ============================ */
2116
+
2117
+ .dk_statsView {
2118
+ display: flex;
2119
+ flex-direction: column;
2120
+ gap: var(--dk-gap-md);
2121
+ flex: 1 1 auto;
2122
+ min-height: 0;
2123
+ }
2124
+
2125
+ .dk_statsBar { display: flex; align-items: center; gap: var(--dk-gap-sm); flex-wrap: wrap; }
2126
+ .dk_trend { display: inline-flex; align-items: center; gap: var(--dk-gap-sm); }
2127
+
2128
+ .dk_spark {
2129
+ display: inline-flex;
2130
+ align-items: center;
2131
+ justify-content: center;
2132
+ width: 96px;
2133
+ height: 22px;
2134
+ color: var(--dk-accent);
2135
+ }
2136
+
2137
+ .dk_spark svg { display: block; width: 96px; height: 22px; overflow: visible; }
2138
+ .dk_spark[data-alert="1"] { color: var(--dk-danger); }
2139
+ .dk_sparkEmpty { font-size: 11px; color: var(--dk-label-3); }
2140
+
2141
+ /* ============================ Compose 项目 ============================ */
2142
+
2143
+ .dk_projects { display: flex; flex-direction: column; gap: var(--dk-gap-lg); }
2144
+
2145
+ .dk_project {
2146
+ border: 1px solid var(--dk-border);
2147
+ border-radius: var(--dk-r-md);
2148
+ background: var(--dk-surface-2);
2149
+ overflow: hidden;
2150
+ cursor: pointer;
2151
+ transition: border-color var(--dk-dur) var(--dk-ease), box-shadow var(--dk-dur) var(--dk-ease);
2152
+ }
2153
+
2154
+ .dk_project:hover { border-color: var(--dk-border-strong); }
2155
+ .dk_project:focus-visible { outline: none; box-shadow: var(--dk-ring); }
2156
+ .dk_projectHead { display: flex; align-items: center; gap: var(--dk-gap-md); padding: var(--dk-gap-md) var(--dk-gap-lg); }
2157
+ .dk_projectIcon { display: inline-flex; align-items: center; color: var(--dk-accent); }
2158
+ .dk_projectName { font-size: 13px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2159
+ .dk_projectRows { display: flex; flex-direction: column; border-top: 1px solid var(--dk-border); }
2160
+
2161
+ .dk_projectRow {
2162
+ display: grid;
2163
+ grid-template-columns: 120px minmax(120px, 1.1fr) auto minmax(120px, 1.2fr) minmax(80px, 1fr);
2164
+ align-items: center;
2165
+ gap: var(--dk-gap-md);
2166
+ padding: var(--dk-gap-sm) var(--dk-gap-lg);
2167
+ font-size: 12px;
2168
+ border-top: 1px solid var(--dk-border);
2169
+ }
2170
+
2171
+ .dk_projectRow:first-child { border-top: none; }
2172
+ .dk_projectSvc { color: var(--dk-label-2); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2173
+ .dk_projectContainer { font-family: var(--dk-mono); color: var(--dk-label); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2174
+ .dk_projectImage { font-family: var(--dk-mono); color: var(--dk-label-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2175
+ .dk_projectPorts { color: var(--dk-label-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2176
+
2177
+ .dk_composeTable td { vertical-align: middle; }
2178
+
2179
+ /* 聚合日志的 [service] 前缀(同一份日志着色,只多一个来源标签) */
2180
+ .dk_logSvc { margin-right: 6px; color: var(--dk-accent); font-weight: 600; }