@guanjinke/artifact-client 1.12.2 → 1.14.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 (42) hide show
  1. package/README.md +219 -217
  2. package/dist/cli.js +0 -0
  3. package/dist/environment/artifact-cache.d.ts +36 -0
  4. package/dist/environment/artifact-cache.d.ts.map +1 -0
  5. package/dist/environment/artifact-cache.js +131 -0
  6. package/dist/environment/artifact-cache.js.map +1 -0
  7. package/dist/environment/builder.d.ts.map +1 -1
  8. package/dist/environment/builder.js +10 -3
  9. package/dist/environment/builder.js.map +1 -1
  10. package/dist/environment/local-server.d.ts +12 -0
  11. package/dist/environment/local-server.d.ts.map +1 -0
  12. package/dist/environment/local-server.js +201 -0
  13. package/dist/environment/local-server.js.map +1 -0
  14. package/dist/environment/manager.d.ts.map +1 -1
  15. package/dist/environment/manager.js +7 -0
  16. package/dist/environment/manager.js.map +1 -1
  17. package/dist/environment/manifest.d.ts +56 -0
  18. package/dist/environment/manifest.d.ts.map +1 -0
  19. package/dist/environment/manifest.js +81 -0
  20. package/dist/environment/manifest.js.map +1 -0
  21. package/dist/environment/update.d.ts +1 -1
  22. package/dist/environment/update.d.ts.map +1 -1
  23. package/dist/environment/update.js +79 -42
  24. package/dist/environment/update.js.map +1 -1
  25. package/dist/providers/oss-provider.d.ts.map +1 -1
  26. package/dist/providers/oss-provider.js +28 -2
  27. package/dist/providers/oss-provider.js.map +1 -1
  28. package/dist/web/api.d.ts +5 -0
  29. package/dist/web/api.d.ts.map +1 -1
  30. package/dist/web/api.js +25 -0
  31. package/dist/web/api.js.map +1 -1
  32. package/dist/web/reveal.d.ts +25 -0
  33. package/dist/web/reveal.d.ts.map +1 -0
  34. package/dist/web/reveal.js +75 -0
  35. package/dist/web/reveal.js.map +1 -0
  36. package/dist/web/server.d.ts +2 -0
  37. package/dist/web/server.d.ts.map +1 -1
  38. package/dist/web/server.js +2 -0
  39. package/dist/web/server.js.map +1 -1
  40. package/dist/web/static/app.js +10 -10
  41. package/dist/web/static/styles.css +1619 -1596
  42. package/package.json +1 -1
@@ -1,1596 +1,1619 @@
1
- /* AFM Environment Manager — dark glassmorphism theme.
2
- Everything is class-driven: the page is served under a strict CSP that forbids
3
- inline <style> and style="" attributes. */
4
-
5
- :root {
6
- --bg: #05070d;
7
- --glass: rgba(255, 255, 255, .055);
8
- --glass-strong: rgba(255, 255, 255, .09);
9
- --border: rgba(255, 255, 255, .10);
10
- --border-strong: rgba(255, 255, 255, .18);
11
- --text: #eaeefb;
12
- --muted: #98a2bd;
13
- --accent: #4d6bfe;
14
- --accent-soft: rgba(77, 107, 254, .16);
15
- --cyan: #35d0e8;
16
- --ok: #3ddc97;
17
- --warn: #ffc24b;
18
- --err: #ff6b81;
19
- --radius: 18px;
20
- /* One height for every single-line control, so a <select> and an <input> in
21
- the same row cannot disagree. */
22
- --control-height: 38px;
23
- --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
24
- --panel: linear-gradient(160deg, var(--glass-strong), var(--glass));
25
- --panel-shadow: 0 18px 50px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .06);
26
- }
27
-
28
- *,
29
- *::before,
30
- *::after {
31
- box-sizing: border-box;
32
- }
33
-
34
- html,
35
- body {
36
- margin: 0;
37
- padding: 0;
38
- }
39
-
40
- body {
41
- min-height: 100vh;
42
- background:
43
- radial-gradient(900px 520px at 12% -8%, rgba(77, 107, 254, .30), transparent 60%),
44
- radial-gradient(800px 480px at 88% -4%, rgba(53, 208, 232, .20), transparent 55%),
45
- var(--bg);
46
- background-repeat: no-repeat;
47
- background-attachment: fixed;
48
- color: var(--text);
49
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
50
- font-size: 14px;
51
- line-height: 1.55;
52
- -webkit-font-smoothing: antialiased;
53
- text-rendering: optimizeLegibility;
54
- }
55
-
56
- h1,
57
- h2,
58
- h3 {
59
- line-height: 1.25;
60
- }
61
-
62
- p {
63
- margin: 0;
64
- }
65
-
66
- button {
67
- font: inherit;
68
- }
69
-
70
- code,
71
- .mono,
72
- pre {
73
- font-family: var(--mono);
74
- }
75
-
76
- a {
77
- color: var(--cyan);
78
- }
79
-
80
- ::selection {
81
- background: var(--accent-soft);
82
- }
83
-
84
- :focus-visible {
85
- outline: 2px solid rgba(77, 107, 254, .4);
86
- outline-offset: 2px;
87
- border-radius: 10px;
88
- }
89
-
90
- .sr-only {
91
- position: absolute;
92
- width: 1px;
93
- height: 1px;
94
- padding: 0;
95
- margin: -1px;
96
- overflow: hidden;
97
- clip: rect(0 0 0 0);
98
- white-space: nowrap;
99
- border: 0;
100
- }
101
-
102
- .muted { color: var(--muted); }
103
- .small { font-size: 12px; }
104
- .strong { font-weight: 650; }
105
- .err-text { color: var(--err); }
106
- .warn-text { color: var(--warn); }
107
- .danger-text { color: #ffd7dc; }
108
-
109
- /* ------------------------------------------------------------------ layout */
110
-
111
- .app {
112
- min-height: 100vh;
113
- max-width: 1440px;
114
- margin: 0 auto;
115
- padding: 26px 22px 40px;
116
- display: grid;
117
- gap: 18px;
118
- align-content: start;
119
- }
120
-
121
- .app-main {
122
- display: grid;
123
- gap: 18px;
124
- }
125
-
126
- .app-footer {
127
- display: flex;
128
- flex-wrap: wrap;
129
- gap: 8px 16px;
130
- justify-content: space-between;
131
- padding: 4px 2px;
132
- color: var(--muted);
133
- font-size: 11.5px;
134
- }
135
-
136
- .toolbar {
137
- display: flex;
138
- flex-wrap: wrap;
139
- align-items: center;
140
- gap: 12px;
141
- }
142
-
143
- /* ------------------------------------------------------------------ header */
144
-
145
- .topbar {
146
- display: grid;
147
- gap: 14px;
148
- padding: 18px 20px;
149
- background: var(--panel);
150
- backdrop-filter: blur(18px) saturate(140%);
151
- -webkit-backdrop-filter: blur(18px) saturate(140%);
152
- border: 1px solid var(--border);
153
- border-radius: var(--radius);
154
- box-shadow: var(--panel-shadow);
155
- }
156
-
157
- .brand {
158
- display: flex;
159
- align-items: center;
160
- gap: 12px;
161
- min-width: 0;
162
- }
163
-
164
- .brand-mark {
165
- flex: 0 0 auto;
166
- display: grid;
167
- place-items: center;
168
- width: 42px;
169
- height: 42px;
170
- border-radius: 13px;
171
- background: linear-gradient(135deg, rgba(77, 107, 254, .95), rgba(53, 208, 232, .8));
172
- color: #f8faff;
173
- font-weight: 700;
174
- font-size: 13px;
175
- letter-spacing: .04em;
176
- box-shadow: 0 10px 24px rgba(77, 107, 254, .35);
177
- }
178
-
179
- .brand-mark.big {
180
- width: 56px;
181
- height: 56px;
182
- border-radius: 17px;
183
- font-size: 15px;
184
- }
185
-
186
- .brand-name {
187
- margin: 0;
188
- font-size: 18px;
189
- font-weight: 680;
190
- letter-spacing: .01em;
191
- }
192
-
193
- .brand-sub {
194
- margin: 4px 0 0;
195
- color: var(--muted);
196
- font-size: 12.5px;
197
- }
198
-
199
- .topbar-meta {
200
- display: flex;
201
- flex-wrap: wrap;
202
- gap: 10px;
203
- }
204
-
205
- .meta-chip {
206
- display: grid;
207
- gap: 4px;
208
- min-width: 150px;
209
- padding: 8px 12px;
210
- background: rgba(255, 255, 255, .04);
211
- border: 1px solid var(--border);
212
- border-radius: 12px;
213
- font-size: 12px;
214
- }
215
-
216
- .meta-label {
217
- color: var(--muted);
218
- font-size: 11px;
219
- letter-spacing: .06em;
220
- text-transform: uppercase;
221
- }
222
-
223
- .meta-value {
224
- word-break: break-all;
225
- }
226
-
227
- .providers {
228
- display: flex;
229
- gap: 8px;
230
- }
231
-
232
- .provider-badge {
233
- display: inline-flex;
234
- align-items: center;
235
- gap: 6px;
236
- padding: 3px 10px;
237
- border: 1px solid var(--border);
238
- border-radius: 999px;
239
- font-size: 11.5px;
240
- letter-spacing: .06em;
241
- text-transform: uppercase;
242
- }
243
-
244
- .provider-badge .dot {
245
- width: 7px;
246
- height: 7px;
247
- border-radius: 50%;
248
- background: currentColor;
249
- box-shadow: 0 0 8px currentColor;
250
- }
251
-
252
- .provider-badge.on {
253
- color: var(--ok);
254
- border-color: rgba(61, 220, 151, .40);
255
- background: rgba(61, 220, 151, .12);
256
- }
257
-
258
- .provider-badge.off {
259
- color: var(--warn);
260
- border-color: rgba(255, 194, 75, .35);
261
- background: rgba(255, 194, 75, .10);
262
- }
263
-
264
- .provider-hint {
265
- grid-column: 1 / -1;
266
- margin: 0;
267
- padding: 10px 12px;
268
- color: #ffe0ab;
269
- background: rgba(255, 194, 75, .09);
270
- border: 1px solid rgba(255, 194, 75, .28);
271
- border-radius: 12px;
272
- font-size: 12.5px;
273
- line-height: 1.7;
274
- }
275
-
276
- .provider-hint .code {
277
- margin: 0 4px;
278
- }
279
-
280
- /* ----------------------------------------------------------------- banners */
281
-
282
- .banner {
283
- display: flex;
284
- align-items: center;
285
- justify-content: space-between;
286
- gap: 12px;
287
- padding: 12px 16px;
288
- background: var(--panel);
289
- backdrop-filter: blur(18px) saturate(140%);
290
- -webkit-backdrop-filter: blur(18px) saturate(140%);
291
- border: 1px solid var(--border);
292
- border-radius: 14px;
293
- box-shadow: 0 12px 34px rgba(0, 0, 0, .35);
294
- }
295
-
296
- .banner.error {
297
- border-color: rgba(255, 107, 129, .42);
298
- background: linear-gradient(160deg, rgba(255, 107, 129, .16), rgba(255, 107, 129, .07));
299
- }
300
-
301
- .banner.success {
302
- border-color: rgba(61, 220, 151, .40);
303
- background: linear-gradient(160deg, rgba(61, 220, 151, .14), rgba(61, 220, 151, .06));
304
- }
305
-
306
- .banner.info {
307
- border-color: rgba(77, 107, 254, .40);
308
- background: linear-gradient(160deg, rgba(77, 107, 254, .16), rgba(77, 107, 254, .06));
309
- }
310
-
311
- .banner-text {
312
- font-size: 13px;
313
- line-height: 1.6;
314
- }
315
-
316
- .banner-actions {
317
- display: flex;
318
- flex: 0 0 auto;
319
- gap: 8px;
320
- }
321
-
322
- /* ------------------------------------------------------------------ panels */
323
-
324
- .panel {
325
- display: grid;
326
- gap: 14px;
327
- padding: 18px 20px;
328
- background: var(--panel);
329
- backdrop-filter: blur(18px) saturate(140%);
330
- -webkit-backdrop-filter: blur(18px) saturate(140%);
331
- border: 1px solid var(--border);
332
- border-radius: var(--radius);
333
- box-shadow: var(--panel-shadow);
334
- }
335
-
336
- .panel-head {
337
- display: flex;
338
- align-items: center;
339
- gap: 10px;
340
- }
341
-
342
- .panel-title {
343
- margin: 0;
344
- font-size: 15px;
345
- font-weight: 650;
346
- }
347
-
348
- .count {
349
- padding: 1px 8px;
350
- color: var(--muted);
351
- background: rgba(255, 255, 255, .05);
352
- border: 1px solid var(--border);
353
- border-radius: 999px;
354
- font-size: 11.5px;
355
- }
356
-
357
- .panel-actions {
358
- display: flex;
359
- align-items: center;
360
- gap: 8px;
361
- margin-left: auto;
362
- }
363
-
364
- /* ----------------------------------------------------------------- buttons */
365
-
366
- .btn {
367
- display: inline-flex;
368
- align-items: center;
369
- justify-content: center;
370
- gap: 6px;
371
- padding: 8px 14px;
372
- color: var(--text);
373
- background: linear-gradient(160deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .045));
374
- border: 1px solid var(--border-strong);
375
- border-radius: 10px;
376
- font-size: 13px;
377
- font-weight: 500;
378
- cursor: pointer;
379
- transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease, background-color .16s ease;
380
- }
381
-
382
- .btn:hover:not(:disabled) {
383
- transform: translateY(-1px);
384
- border-color: rgba(255, 255, 255, .30);
385
- }
386
-
387
- .btn:active:not(:disabled) {
388
- transform: translateY(0);
389
- }
390
-
391
- .btn.primary {
392
- background: linear-gradient(135deg, var(--accent), #3f7ef7);
393
- border-color: rgba(155, 175, 255, .45);
394
- box-shadow: 0 10px 26px rgba(77, 107, 254, .35);
395
- }
396
-
397
- .btn.primary:hover:not(:disabled) {
398
- box-shadow: 0 14px 30px rgba(77, 107, 254, .45);
399
- }
400
-
401
- .btn.danger {
402
- color: #ffd9de;
403
- background: linear-gradient(160deg, rgba(255, 107, 129, .22), rgba(255, 107, 129, .12));
404
- border-color: rgba(255, 107, 129, .45);
405
- }
406
-
407
- .btn.ghost {
408
- background: transparent;
409
- border-color: var(--border);
410
- }
411
-
412
- .btn.small {
413
- padding: 6px 10px;
414
- border-radius: 9px;
415
- font-size: 12px;
416
- }
417
-
418
- .btn:disabled {
419
- opacity: .45;
420
- cursor: not-allowed;
421
- transform: none;
422
- box-shadow: none;
423
- }
424
-
425
- /* ------------------------------------------------------------------- pills */
426
-
427
- .pill {
428
- display: inline-flex;
429
- align-items: center;
430
- padding: 3px 10px;
431
- border: 1px solid var(--border-strong);
432
- border-radius: 999px;
433
- font-size: 10.5px;
434
- font-weight: 650;
435
- letter-spacing: .09em;
436
- text-transform: uppercase;
437
- white-space: nowrap;
438
- }
439
-
440
- .pill.s-ok,
441
- .pill.s-job-succeeded {
442
- color: var(--ok);
443
- background: rgba(61, 220, 151, .14);
444
- border-color: rgba(61, 220, 151, .38);
445
- }
446
-
447
- .pill.s-accent,
448
- .pill.s-job-running {
449
- color: #b9c6ff;
450
- background: var(--accent-soft);
451
- border-color: rgba(77, 107, 254, .45);
452
- }
453
-
454
- .pill.s-warn,
455
- .pill.s-job-queued {
456
- color: var(--warn);
457
- background: rgba(255, 194, 75, .14);
458
- border-color: rgba(255, 194, 75, .38);
459
- }
460
-
461
- .pill.s-warn-muted {
462
- color: var(--warn);
463
- background: rgba(255, 194, 75, .08);
464
- border-color: rgba(255, 194, 75, .22);
465
- }
466
-
467
- .pill.s-err,
468
- .pill.s-job-failed {
469
- color: var(--err);
470
- background: rgba(255, 107, 129, .14);
471
- border-color: rgba(255, 107, 129, .40);
472
- }
473
-
474
- .pill.s-muted {
475
- color: var(--muted);
476
- background: rgba(255, 255, 255, .05);
477
- border-color: var(--border-strong);
478
- }
479
-
480
- /* ---------------------------------------------------------- environments */
481
-
482
- /* One compact row per environment instead of a card grid: every row shows the
483
- same columns, so a long list stays scannable and uses the width. */
484
- .env-list {
485
- display: grid;
486
- gap: 0;
487
- margin: 0;
488
- padding: 0;
489
- list-style: none;
490
- }
491
-
492
- .env-row {
493
- display: grid;
494
- gap: 4px;
495
- padding: 12px 4px;
496
- border-top: 1px solid var(--border);
497
- transition: background-color .16s ease;
498
- }
499
-
500
- .env-row:first-child {
501
- border-top: none;
502
- }
503
-
504
- .env-row:hover {
505
- background: rgba(255, 255, 255, .028);
506
- }
507
-
508
- .env-row-main {
509
- display: grid;
510
- grid-template-columns: minmax(170px, 1fr) minmax(200px, auto) auto minmax(180px, 2fr) auto;
511
- align-items: center;
512
- gap: 10px 16px;
513
- }
514
-
515
- .env-id {
516
- display: flex;
517
- align-items: center;
518
- gap: 8px;
519
- min-width: 0;
520
- }
521
-
522
- .env-name {
523
- font-size: 14.5px;
524
- font-weight: 650;
525
- overflow: hidden;
526
- text-overflow: ellipsis;
527
- white-space: nowrap;
528
- }
529
-
530
- .env-site {
531
- color: var(--cyan);
532
- font-size: 12.5px;
533
- text-decoration: none;
534
- border-bottom: 1px dashed rgba(53, 208, 232, .35);
535
- white-space: nowrap;
536
- }
537
-
538
- .env-site:hover {
539
- color: #8ee7f5;
540
- border-bottom-color: rgba(142, 231, 245, .7);
541
- }
542
-
543
- .env-site.muted {
544
- color: var(--muted);
545
- border-bottom-color: var(--border);
546
- }
547
-
548
- .env-meta {
549
- color: var(--muted);
550
- font-size: 12px;
551
- white-space: nowrap;
552
- }
553
-
554
- .env-chips {
555
- min-width: 0;
556
- overflow: hidden;
557
- max-height: 22px;
558
- }
559
-
560
- .env-row-sub {
561
- display: flex;
562
- flex-wrap: wrap;
563
- align-items: center;
564
- gap: 6px 14px;
565
- min-width: 0;
566
- font-size: 11.5px;
567
- }
568
-
569
- .env-root {
570
- margin: 0;
571
- color: var(--muted);
572
- font-size: 11.5px;
573
- overflow: hidden;
574
- text-overflow: ellipsis;
575
- white-space: nowrap;
576
- max-width: 100%;
577
- }
578
-
579
- .env-problem {
580
- margin: 0;
581
- padding: 2px 8px;
582
- color: #ffd7dc;
583
- background: rgba(255, 107, 129, .10);
584
- border: 1px solid rgba(255, 107, 129, .30);
585
- border-radius: 8px;
586
- font-size: 11.5px;
587
- line-height: 1.6;
588
- word-break: break-word;
589
- }
590
-
591
- .env-actions {
592
- display: flex;
593
- flex-wrap: wrap;
594
- justify-content: flex-end;
595
- gap: 6px;
596
- }
597
-
598
- .card {
599
- background: var(--panel);
600
- backdrop-filter: blur(18px) saturate(140%);
601
- -webkit-backdrop-filter: blur(18px) saturate(140%);
602
- border: 1px solid var(--border);
603
- border-radius: var(--radius);
604
- box-shadow: var(--panel-shadow);
605
- transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
606
- }
607
-
608
- /* ------------------------------------------------------------------- chips */
609
-
610
- .chips {
611
- display: flex;
612
- flex-wrap: wrap;
613
- align-items: center;
614
- gap: 6px;
615
- }
616
-
617
- .chips.inline {
618
- display: inline-flex;
619
- }
620
-
621
- .chip-tag {
622
- display: inline-flex;
623
- align-items: center;
624
- padding: 2px 9px;
625
- color: var(--text);
626
- background: rgba(255, 255, 255, .06);
627
- border: 1px solid var(--border);
628
- border-radius: 999px;
629
- font-size: 11.5px;
630
- font-family: var(--mono);
631
- }
632
-
633
- .chip-tag.local {
634
- color: var(--cyan);
635
- background: rgba(53, 208, 232, .12);
636
- border-color: rgba(53, 208, 232, .35);
637
- letter-spacing: .04em;
638
- text-transform: uppercase;
639
- }
640
-
641
- .chip-tag.warn {
642
- color: var(--warn);
643
- background: rgba(255, 194, 75, .12);
644
- border-color: rgba(255, 194, 75, .35);
645
- }
646
-
647
- .chip-row {
648
- display: flex;
649
- flex-wrap: wrap;
650
- gap: 8px;
651
- margin-top: 2px;
652
- }
653
-
654
- .chip {
655
- padding: 4px 11px;
656
- color: var(--text);
657
- background: rgba(255, 255, 255, .06);
658
- border: 1px solid var(--border);
659
- border-radius: 999px;
660
- font-family: var(--mono);
661
- font-size: 12px;
662
- cursor: pointer;
663
- transition: transform .14s ease, border-color .14s ease, background-color .14s ease;
664
- }
665
-
666
- .chip:hover:not(:disabled) {
667
- transform: translateY(-1px);
668
- border-color: var(--border-strong);
669
- background: rgba(255, 255, 255, .10);
670
- }
671
-
672
- .chip.active,
673
- .chip.accent {
674
- color: #cdd7ff;
675
- background: var(--accent-soft);
676
- border-color: rgba(77, 107, 254, .48);
677
- }
678
-
679
- .chip:disabled {
680
- opacity: .4;
681
- cursor: not-allowed;
682
- }
683
-
684
- /* -------------------------------------------------------------------- jobs */
685
-
686
- .jobs {
687
- display: grid;
688
- gap: 12px;
689
- }
690
-
691
- .job {
692
- display: grid;
693
- gap: 10px;
694
- padding: 12px 14px;
695
- background: rgba(255, 255, 255, .035);
696
- border: 1px solid var(--border);
697
- border-radius: 14px;
698
- }
699
-
700
- .job.running {
701
- border-color: rgba(53, 208, 232, .34);
702
- }
703
-
704
- .job.queued {
705
- border-color: rgba(255, 194, 75, .28);
706
- }
707
-
708
- .job.failed {
709
- border-color: rgba(255, 107, 129, .32);
710
- }
711
-
712
- .job-head {
713
- display: flex;
714
- flex-wrap: wrap;
715
- align-items: center;
716
- gap: 8px 10px;
717
- font-size: 12.5px;
718
- }
719
-
720
- .job-kind {
721
- color: var(--muted);
722
- }
723
-
724
- .job-target {
725
- font-size: 13.5px;
726
- }
727
-
728
- .job-time {
729
- margin-left: auto;
730
- color: var(--muted);
731
- font-size: 11.5px;
732
- }
733
-
734
- .job-log {
735
- display: grid;
736
- gap: 4px;
737
- max-height: 220px;
738
- overflow-y: auto;
739
- padding: 10px 12px;
740
- background: rgba(0, 0, 0, .35);
741
- border: 1px solid var(--border);
742
- border-radius: 12px;
743
- font-size: 12px;
744
- }
745
-
746
- .log-line {
747
- display: grid;
748
- grid-template-columns: 66px minmax(0, 1fr);
749
- gap: 10px;
750
- margin: 0;
751
- align-items: start;
752
- }
753
-
754
- .log-time {
755
- color: rgba(152, 162, 189, .75);
756
- }
757
-
758
- .log-text {
759
- font-family: var(--mono);
760
- white-space: pre-wrap;
761
- word-break: break-word;
762
- }
763
-
764
- .job-error {
765
- margin: 0;
766
- padding: 8px 10px;
767
- color: #ffd7dc;
768
- background: rgba(255, 107, 129, .12);
769
- border: 1px solid rgba(255, 107, 129, .38);
770
- border-radius: 12px;
771
- font-size: 12.5px;
772
- line-height: 1.6;
773
- }
774
-
775
- .job-details summary {
776
- padding: 2px 0;
777
- color: var(--muted);
778
- font-size: 12.5px;
779
- cursor: pointer;
780
- }
781
-
782
- .job-details[open] summary {
783
- margin-bottom: 10px;
784
- }
785
-
786
- /* ----------------------------------------------------------------- results */
787
-
788
- .result {
789
- display: grid;
790
- gap: 8px;
791
- font-size: 12.5px;
792
- }
793
-
794
- .result-title {
795
- font-size: 12.5px;
796
- font-weight: 650;
797
- }
798
-
799
- .result-list {
800
- display: grid;
801
- gap: 6px;
802
- margin: 0;
803
- padding: 0;
804
- list-style: none;
805
- color: var(--muted);
806
- }
807
-
808
- .result-list li {
809
- display: flex;
810
- flex-wrap: wrap;
811
- align-items: baseline;
812
- gap: 8px;
813
- }
814
-
815
- .result-arrow {
816
- display: inline-flex;
817
- align-items: baseline;
818
- gap: 4px;
819
- }
820
-
821
- .result-line {
822
- color: var(--muted);
823
- }
824
-
825
- .warn-list {
826
- color: var(--warn);
827
- }
828
-
829
- .callout {
830
- padding: 10px 12px;
831
- border-radius: 12px;
832
- font-size: 12.5px;
833
- line-height: 1.6;
834
- }
835
-
836
- .callout.warn {
837
- color: #ffe0ab;
838
- background: rgba(255, 194, 75, .10);
839
- border: 1px solid rgba(255, 194, 75, .30);
840
- }
841
-
842
- .callout.info {
843
- color: #c9d4ff;
844
- background: var(--accent-soft);
845
- border: 1px solid rgba(77, 107, 254, .35);
846
- }
847
-
848
- .raw {
849
- font-size: 12px;
850
- color: var(--muted);
851
- }
852
-
853
- .raw summary {
854
- cursor: pointer;
855
- }
856
-
857
- .pre {
858
- max-height: 260px;
859
- overflow: auto;
860
- margin: 8px 0 0;
861
- padding: 10px;
862
- background: rgba(0, 0, 0, .35);
863
- border: 1px solid var(--border);
864
- border-radius: 10px;
865
- font-size: 11.5px;
866
- line-height: 1.5;
867
- white-space: pre-wrap;
868
- word-break: break-word;
869
- }
870
-
871
- /* ------------------------------------------------------------------- forms */
872
-
873
- .form {
874
- display: grid;
875
- gap: 16px;
876
- }
877
-
878
- .field {
879
- display: grid;
880
- gap: 6px;
881
- /* Never stretch a control to fill a row: a taller neighbour must not change
882
- the height of this field's input. */
883
- align-content: start;
884
- }
885
-
886
- /* A row of fields plus a note that belongs to the row as a whole. */
887
- .field-block {
888
- display: grid;
889
- gap: 6px;
890
- }
891
-
892
- .field-row {
893
- display: grid;
894
- grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
895
- gap: 14px;
896
- align-items: start;
897
- }
898
-
899
- .label {
900
- color: var(--muted);
901
- font-size: 12.5px;
902
- font-weight: 600;
903
- letter-spacing: .04em;
904
- }
905
-
906
- .helper {
907
- color: var(--muted);
908
- font-size: 12px;
909
- line-height: 1.6;
910
- }
911
-
912
- .hint {
913
- margin: 0;
914
- color: var(--muted);
915
- font-size: 12px;
916
- line-height: 1.6;
917
- word-break: break-all;
918
- }
919
-
920
- .fieldset {
921
- display: grid;
922
- gap: 10px;
923
- margin: 0;
924
- padding: 12px 14px;
925
- border: 1px solid var(--border);
926
- border-radius: 14px;
927
- }
928
-
929
- .input,
930
- .select,
931
- .textarea {
932
- width: 100%;
933
- padding: 10px 12px;
934
- color: var(--text);
935
- background: rgba(4, 7, 14, .55);
936
- border: 1px solid var(--border);
937
- border-radius: 12px;
938
- font-size: 13px;
939
- transition: border-color .16s ease, box-shadow .16s ease;
940
- }
941
-
942
- .input::placeholder,
943
- .textarea::placeholder {
944
- color: rgba(152, 162, 189, .65);
945
- }
946
-
947
- .input:focus,
948
- .select:focus,
949
- .textarea:focus {
950
- border-color: rgba(77, 107, 254, .65);
951
- box-shadow: 0 0 0 3px var(--accent-soft);
952
- outline: none;
953
- }
954
-
955
- .textarea {
956
- resize: vertical;
957
- min-height: 76px;
958
- line-height: 1.6;
959
- }
960
-
961
- /* A <select> is intrinsically a little taller than an <input>; pin both to the
962
- same height so a row of controls lines up exactly. `box-sizing: border-box`
963
- is global, so this includes the padding and the border. */
964
- .input,
965
- .select {
966
- height: var(--control-height);
967
- line-height: 1.25;
968
- }
969
-
970
- .select {
971
- appearance: none;
972
- background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
973
- background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
974
- background-size: 5px 5px, 5px 5px;
975
- background-repeat: no-repeat;
976
- }
977
-
978
- .checks,
979
- .radios {
980
- display: flex;
981
- flex-wrap: wrap;
982
- gap: 10px 18px;
983
- }
984
-
985
- .checkbox,
986
- .radio {
987
- display: inline-flex;
988
- align-items: center;
989
- gap: 8px;
990
- font-size: 13px;
991
- cursor: pointer;
992
- }
993
-
994
- .checkbox input,
995
- .radio input {
996
- width: 16px;
997
- height: 16px;
998
- accent-color: var(--accent);
999
- cursor: pointer;
1000
- }
1001
-
1002
- .radio.disabled {
1003
- opacity: .55;
1004
- cursor: not-allowed;
1005
- }
1006
-
1007
- .radio.disabled input {
1008
- cursor: not-allowed;
1009
- }
1010
-
1011
- .checkbox.solo {
1012
- padding: 10px 12px;
1013
- background: rgba(255, 255, 255, .03);
1014
- border: 1px solid var(--border);
1015
- border-radius: 12px;
1016
- }
1017
-
1018
- .form-error {
1019
- padding: 10px 12px;
1020
- color: #ffd7dc;
1021
- background: rgba(255, 107, 129, .12);
1022
- border: 1px solid rgba(255, 107, 129, .40);
1023
- border-radius: 12px;
1024
- font-size: 12.5px;
1025
- line-height: 1.6;
1026
- }
1027
-
1028
- .status-strip {
1029
- display: flex;
1030
- align-items: center;
1031
- gap: 10px;
1032
- padding: 8px 12px;
1033
- color: var(--muted);
1034
- background: rgba(255, 255, 255, .04);
1035
- border: 1px solid var(--border);
1036
- border-radius: 12px;
1037
- font-size: 12.5px;
1038
- }
1039
-
1040
- .spec-forms {
1041
- margin: 4px 0 0;
1042
- padding-left: 18px;
1043
- color: var(--muted);
1044
- font-size: 12px;
1045
- line-height: 1.75;
1046
- }
1047
-
1048
- .code {
1049
- padding: 1px 6px;
1050
- background: rgba(255, 255, 255, .07);
1051
- border: 1px solid var(--border);
1052
- border-radius: 6px;
1053
- font-size: 12px;
1054
- }
1055
-
1056
- /* ------------------------------------------------------------------ modals */
1057
-
1058
- .scrim {
1059
- position: fixed;
1060
- inset: 0;
1061
- z-index: 50;
1062
- display: grid;
1063
- place-items: center;
1064
- padding: 24px;
1065
- overflow-y: auto;
1066
- background: rgba(3, 5, 10, .62);
1067
- backdrop-filter: blur(6px);
1068
- -webkit-backdrop-filter: blur(6px);
1069
- }
1070
-
1071
- .modal {
1072
- display: flex;
1073
- flex-direction: column;
1074
- width: min(680px, 100%);
1075
- max-height: 88vh;
1076
- overflow: hidden;
1077
- background: linear-gradient(160deg, rgba(255, 255, 255, .105), rgba(255, 255, 255, .045));
1078
- backdrop-filter: blur(22px) saturate(150%);
1079
- -webkit-backdrop-filter: blur(22px) saturate(150%);
1080
- border: 1px solid var(--border-strong);
1081
- border-radius: 22px;
1082
- box-shadow: 0 30px 80px rgba(0, 0, 0, .60), inset 0 1px 0 rgba(255, 255, 255, .08);
1083
- }
1084
-
1085
- .modal-head {
1086
- display: flex;
1087
- align-items: center;
1088
- justify-content: space-between;
1089
- gap: 12px;
1090
- padding: 18px 22px;
1091
- border-bottom: 1px solid var(--border);
1092
- }
1093
-
1094
- .modal-title {
1095
- margin: 0;
1096
- font-size: 16px;
1097
- font-weight: 650;
1098
- word-break: break-all;
1099
- }
1100
-
1101
- .modal-body {
1102
- overflow-y: auto;
1103
- padding: 20px 22px 22px;
1104
- }
1105
-
1106
- .modal-foot {
1107
- display: flex;
1108
- justify-content: flex-end;
1109
- gap: 10px;
1110
- padding-top: 4px;
1111
- }
1112
-
1113
- /* ------------------------------------------------------------ empty/loading */
1114
-
1115
- .empty {
1116
- padding: 28px;
1117
- color: var(--muted);
1118
- border: 1px dashed var(--border);
1119
- border-radius: var(--radius);
1120
- text-align: center;
1121
- }
1122
-
1123
- .empty-title {
1124
- margin: 0 0 6px;
1125
- color: var(--text);
1126
- font-weight: 650;
1127
- }
1128
-
1129
- .auth-screen {
1130
- min-height: 100vh;
1131
- display: grid;
1132
- place-items: center;
1133
- padding: 24px;
1134
- }
1135
-
1136
- .auth-card {
1137
- display: grid;
1138
- gap: 14px;
1139
- justify-items: start;
1140
- max-width: 640px;
1141
- padding: 30px;
1142
- }
1143
-
1144
- .auth-title {
1145
- margin: 0;
1146
- font-size: 19px;
1147
- font-weight: 680;
1148
- }
1149
-
1150
- .auth-body {
1151
- display: grid;
1152
- gap: 12px;
1153
- color: var(--text);
1154
- font-size: 13.5px;
1155
- line-height: 1.75;
1156
- }
1157
-
1158
- .loading-bar {
1159
- width: 100%;
1160
- max-width: 260px;
1161
- height: 3px;
1162
- overflow: hidden;
1163
- background: rgba(255, 255, 255, .08);
1164
- border-radius: 999px;
1165
- }
1166
-
1167
- .loading-bar span {
1168
- display: block;
1169
- width: 40%;
1170
- height: 100%;
1171
- background: linear-gradient(90deg, var(--accent), var(--cyan));
1172
- border-radius: 999px;
1173
- animation: loading-slide 1.2s ease-in-out infinite;
1174
- }
1175
-
1176
- @keyframes loading-slide {
1177
- 0% { transform: translateX(-60%); }
1178
- 100% { transform: translateX(260%); }
1179
- }
1180
-
1181
- /* -------------------------------------------------------------- scrollbars */
1182
-
1183
- .job-log::-webkit-scrollbar,
1184
- .modal-body::-webkit-scrollbar,
1185
- .pre::-webkit-scrollbar,
1186
- .scrim::-webkit-scrollbar {
1187
- width: 10px;
1188
- height: 10px;
1189
- }
1190
-
1191
- .job-log::-webkit-scrollbar-thumb,
1192
- .modal-body::-webkit-scrollbar-thumb,
1193
- .pre::-webkit-scrollbar-thumb,
1194
- .scrim::-webkit-scrollbar-thumb {
1195
- background: rgba(255, 255, 255, .14);
1196
- background-clip: content-box;
1197
- border: 3px solid transparent;
1198
- border-radius: 999px;
1199
- }
1200
-
1201
- .job-log::-webkit-scrollbar-track,
1202
- .modal-body::-webkit-scrollbar-track,
1203
- .pre::-webkit-scrollbar-track,
1204
- .scrim::-webkit-scrollbar-track {
1205
- background: transparent;
1206
- }
1207
-
1208
- /* -------------------------------------------------------------- settings -- */
1209
-
1210
- .settings-block {
1211
- display: grid;
1212
- gap: 10px;
1213
- padding: 14px;
1214
- background: rgba(255, 255, 255, .02);
1215
- border: 1px solid var(--border);
1216
- border-radius: 14px;
1217
- }
1218
-
1219
- .settings-title {
1220
- margin: 0;
1221
- font-size: 13.5px;
1222
- font-weight: 650;
1223
- }
1224
-
1225
- .settings-current {
1226
- margin: 0;
1227
- color: var(--muted);
1228
- font-size: 12.5px;
1229
- word-break: break-all;
1230
- }
1231
-
1232
- .settings-row {
1233
- display: flex;
1234
- flex-wrap: wrap;
1235
- align-items: flex-end;
1236
- gap: 10px;
1237
- }
1238
-
1239
- .settings-row > .field {
1240
- flex: 1 1 200px;
1241
- }
1242
-
1243
- .settings-row > .input {
1244
- flex: 1 1 240px;
1245
- }
1246
-
1247
- .settings-provider {
1248
- display: grid;
1249
- gap: 10px;
1250
- padding-top: 12px;
1251
- border-top: 1px dashed var(--border);
1252
- }
1253
-
1254
- .settings-provider-head {
1255
- display: flex;
1256
- align-items: center;
1257
- flex-wrap: wrap;
1258
- gap: 10px;
1259
- }
1260
-
1261
- .settings-actions {
1262
- display: flex;
1263
- justify-content: flex-end;
1264
- gap: 8px;
1265
- }
1266
-
1267
- /* The header chips now carry a small action button. */
1268
- .meta-chip .btn.small {
1269
- justify-self: start;
1270
- padding: 3px 10px;
1271
- }
1272
-
1273
- .provider-hint .btn.small {
1274
- margin: 0 6px;
1275
- vertical-align: middle;
1276
- }
1277
-
1278
- /* --------------------------------------------------------------- responsive */
1279
-
1280
- @media (min-width: 861px) {
1281
- .topbar {
1282
- grid-template-columns: minmax(0, 1fr) auto;
1283
- align-items: center;
1284
- }
1285
- }
1286
-
1287
- @media (max-width: 860px) {
1288
- .app {
1289
- padding: 18px 14px 30px;
1290
- }
1291
-
1292
- /* One column per row: the link, modules and actions stack instead of
1293
- squeezing into a single line. */
1294
- .env-row-main {
1295
- grid-template-columns: minmax(0, 1fr);
1296
- align-items: start;
1297
- gap: 6px;
1298
- }
1299
-
1300
- .env-actions {
1301
- justify-content: flex-start;
1302
- }
1303
-
1304
- .env-chips {
1305
- max-height: none;
1306
- }
1307
-
1308
- .job-time {
1309
- margin-left: 0;
1310
- }
1311
-
1312
- .log-line {
1313
- grid-template-columns: 56px minmax(0, 1fr);
1314
- }
1315
-
1316
- .scrim {
1317
- padding: 14px;
1318
- }
1319
-
1320
- .modal {
1321
- max-height: 92vh;
1322
- }
1323
- }
1324
-
1325
- @media (prefers-reduced-motion: reduce) {
1326
- *,
1327
- *::before,
1328
- *::after {
1329
- animation-duration: .01ms !important;
1330
- animation-iteration-count: 1 !important;
1331
- transition-duration: .01ms !important;
1332
- }
1333
- }
1334
-
1335
- /* ------------------------------------------- environment configuration -- */
1336
-
1337
- /* The per-environment "完整配置" dialog (opened from the 配置 row button): the
1338
- summary grid, the manifest module table and the foldable Wyn.conf lists.
1339
- Everything is introduced here and prefixed `config-`, so the rest of the page
1340
- is untouched. */
1341
- .config-view {
1342
- display: grid;
1343
- gap: 14px;
1344
- }
1345
-
1346
- /* This dialog is a data view (a five-column module table and long paths), so it
1347
- gets a wider modal than the compact action dialogs. */
1348
- .modal.modal-wide {
1349
- width: min(1080px, 94vw);
1350
- }
1351
-
1352
- .config-table {
1353
- table-layout: fixed;
1354
- }
1355
-
1356
- /* 模块 · 版本 · 来源 · 部署目标 · 校验 */
1357
- .config-table th:nth-child(1), .config-table td:nth-child(1) { width: 20%; }
1358
- .config-table th:nth-child(2), .config-table td:nth-child(2) { width: 10%; }
1359
- .config-table th:nth-child(3), .config-table td:nth-child(3) { width: 18%; }
1360
- .config-table th:nth-child(4), .config-table td:nth-child(4) { width: 36%; }
1361
- .config-table th:nth-child(5), .config-table td:nth-child(5) { width: 16%; }
1362
-
1363
- .config-table td:nth-child(2) { white-space: nowrap; font-variant-numeric: tabular-nums; }
1364
- .config-table td:nth-child(4) { overflow-wrap: anywhere; }
1365
-
1366
- /* A path, artifact name or digest may be long: wrap it instead of clipping it. */
1367
- .config-sub, .config-field-value, .config-digest { overflow-wrap: anywhere; }
1368
- .config-digest { white-space: nowrap; }
1369
-
1370
- .config-block {
1371
- display: grid;
1372
- gap: 10px;
1373
- padding: 14px;
1374
- background: rgba(255, 255, 255, .02);
1375
- border: 1px solid var(--border);
1376
- border-radius: 14px;
1377
- }
1378
-
1379
- .config-title {
1380
- margin: 0;
1381
- font-size: 13.5px;
1382
- font-weight: 650;
1383
- }
1384
-
1385
- .config-grid {
1386
- display: grid;
1387
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1388
- gap: 8px 16px;
1389
- }
1390
-
1391
- .config-field {
1392
- display: grid;
1393
- grid-template-columns: 96px minmax(0, 1fr);
1394
- align-items: baseline;
1395
- gap: 8px;
1396
- min-width: 0;
1397
- font-size: 12.5px;
1398
- }
1399
-
1400
- .config-field-label {
1401
- color: var(--muted);
1402
- }
1403
-
1404
- .config-field-value {
1405
- min-width: 0;
1406
- word-break: break-all;
1407
- }
1408
-
1409
- .config-site {
1410
- color: var(--cyan);
1411
- }
1412
-
1413
- .config-site.muted {
1414
- color: var(--muted);
1415
- }
1416
-
1417
- .config-sublist {
1418
- display: grid;
1419
- gap: 6px;
1420
- }
1421
-
1422
- .config-sublist-label {
1423
- color: var(--muted);
1424
- font-size: 12px;
1425
- }
1426
-
1427
- /* A scroll box keeps a wide module table from stretching the dialog. */
1428
- .config-table-wrap {
1429
- overflow-x: auto;
1430
- border: 1px solid var(--border);
1431
- border-radius: 12px;
1432
- }
1433
-
1434
- .config-table {
1435
- width: 100%;
1436
- border-collapse: collapse;
1437
- font-size: 12.5px;
1438
- }
1439
-
1440
- .config-table th {
1441
- padding: 8px 10px;
1442
- color: var(--muted);
1443
- font-weight: 600;
1444
- text-align: left;
1445
- white-space: nowrap;
1446
- background: rgba(255, 255, 255, .03);
1447
- border-bottom: 1px solid var(--border);
1448
- }
1449
-
1450
- .config-table td {
1451
- padding: 8px 10px;
1452
- vertical-align: top;
1453
- border-bottom: 1px solid rgba(255, 255, 255, .06);
1454
- }
1455
-
1456
- .config-table tr:last-child td {
1457
- border-bottom: none;
1458
- }
1459
-
1460
- .config-module-cell {
1461
- display: grid;
1462
- gap: 4px;
1463
- min-width: 140px;
1464
- }
1465
-
1466
- .config-module-tags {
1467
- display: flex;
1468
- flex-wrap: wrap;
1469
- gap: 4px;
1470
- }
1471
-
1472
- /* A second, quieter line inside a cell (assembly, artifact path, override path). */
1473
- .config-sub {
1474
- display: block;
1475
- max-width: 280px;
1476
- color: var(--muted);
1477
- font-size: 11px;
1478
- line-height: 1.45;
1479
- word-break: break-all;
1480
- }
1481
-
1482
- .chip-tag.source-remote {
1483
- color: #c9d4ff;
1484
- border-color: rgba(77, 107, 254, .35);
1485
- }
1486
-
1487
- .chip-tag.source-local {
1488
- color: var(--cyan);
1489
- background: rgba(53, 208, 232, .12);
1490
- border-color: rgba(53, 208, 232, .35);
1491
- }
1492
-
1493
- /* A worker is a mapping of its own: the chip makes it stand out in the table. */
1494
- .chip-tag.source-worker {
1495
- color: var(--ok);
1496
- background: rgba(61, 220, 151, .12);
1497
- border-color: rgba(61, 220, 151, .35);
1498
- }
1499
-
1500
- /* One line per manifest mapping, directly under the module name. */
1501
- .config-mappings {
1502
- display: grid;
1503
- gap: 3px;
1504
- min-width: 0;
1505
- }
1506
-
1507
- .config-mappings-label {
1508
- color: var(--muted);
1509
- font-size: 11px;
1510
- }
1511
-
1512
- .config-mapping {
1513
- display: flex;
1514
- flex-wrap: wrap;
1515
- align-items: baseline;
1516
- gap: 4px;
1517
- min-width: 0;
1518
- }
1519
-
1520
- .config-mapping-text {
1521
- max-width: 300px;
1522
- font-size: 11.5px;
1523
- line-height: 1.45;
1524
- word-break: break-all;
1525
- }
1526
-
1527
- .config-sep {
1528
- color: var(--muted);
1529
- }
1530
-
1531
- .config-mappings-fold summary {
1532
- cursor: pointer;
1533
- }
1534
-
1535
- .config-mappings-fold .config-mappings {
1536
- margin-top: 6px;
1537
- }
1538
-
1539
- .config-digest {
1540
- color: var(--muted);
1541
- white-space: nowrap;
1542
- }
1543
-
1544
- .config-overrides,
1545
- .config-services,
1546
- .config-paths {
1547
- display: grid;
1548
- gap: 6px;
1549
- margin: 0;
1550
- padding: 0;
1551
- list-style: none;
1552
- }
1553
-
1554
- .config-override,
1555
- .config-service {
1556
- display: flex;
1557
- flex-wrap: wrap;
1558
- align-items: baseline;
1559
- gap: 8px;
1560
- min-width: 0;
1561
- }
1562
-
1563
- .config-override .config-sub {
1564
- max-width: 100%;
1565
- }
1566
-
1567
- .config-paths {
1568
- max-height: 200px;
1569
- overflow: auto;
1570
- margin-top: 8px;
1571
- padding: 10px;
1572
- background: rgba(0, 0, 0, .35);
1573
- border: 1px solid var(--border);
1574
- border-radius: 10px;
1575
- font-size: 11.5px;
1576
- }
1577
-
1578
- .config-paths li {
1579
- word-break: break-all;
1580
- }
1581
-
1582
- .config-collapse summary {
1583
- cursor: pointer;
1584
- }
1585
-
1586
- @media (max-width: 620px) {
1587
- /* One line per field: the label no longer squeezes the value column. */
1588
- .config-field {
1589
- grid-template-columns: minmax(0, 1fr);
1590
- gap: 2px;
1591
- }
1592
-
1593
- .config-sub {
1594
- max-width: none;
1595
- }
1596
- }
1
+ /* AFM Environment Manager — dark glassmorphism theme.
2
+ Everything is class-driven: the page is served under a strict CSP that forbids
3
+ inline <style> and style="" attributes. */
4
+
5
+ :root {
6
+ --bg: #05070d;
7
+ --glass: rgba(255, 255, 255, .055);
8
+ --glass-strong: rgba(255, 255, 255, .09);
9
+ --border: rgba(255, 255, 255, .10);
10
+ --border-strong: rgba(255, 255, 255, .18);
11
+ --text: #eaeefb;
12
+ --muted: #98a2bd;
13
+ --accent: #4d6bfe;
14
+ --accent-soft: rgba(77, 107, 254, .16);
15
+ --cyan: #35d0e8;
16
+ --ok: #3ddc97;
17
+ --warn: #ffc24b;
18
+ --err: #ff6b81;
19
+ --radius: 18px;
20
+ /* One height for every single-line control, so a <select> and an <input> in
21
+ the same row cannot disagree. */
22
+ --control-height: 38px;
23
+ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
24
+ --panel: linear-gradient(160deg, var(--glass-strong), var(--glass));
25
+ --panel-shadow: 0 18px 50px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .06);
26
+ }
27
+
28
+ *,
29
+ *::before,
30
+ *::after {
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ html,
35
+ body {
36
+ margin: 0;
37
+ padding: 0;
38
+ }
39
+
40
+ body {
41
+ min-height: 100vh;
42
+ background:
43
+ radial-gradient(900px 520px at 12% -8%, rgba(77, 107, 254, .30), transparent 60%),
44
+ radial-gradient(800px 480px at 88% -4%, rgba(53, 208, 232, .20), transparent 55%),
45
+ var(--bg);
46
+ background-repeat: no-repeat;
47
+ background-attachment: fixed;
48
+ color: var(--text);
49
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
50
+ font-size: 14px;
51
+ line-height: 1.55;
52
+ -webkit-font-smoothing: antialiased;
53
+ text-rendering: optimizeLegibility;
54
+ }
55
+
56
+ h1,
57
+ h2,
58
+ h3 {
59
+ line-height: 1.25;
60
+ }
61
+
62
+ p {
63
+ margin: 0;
64
+ }
65
+
66
+ button {
67
+ font: inherit;
68
+ }
69
+
70
+ code,
71
+ .mono,
72
+ pre {
73
+ font-family: var(--mono);
74
+ }
75
+
76
+ a {
77
+ color: var(--cyan);
78
+ }
79
+
80
+ ::selection {
81
+ background: var(--accent-soft);
82
+ }
83
+
84
+ :focus-visible {
85
+ outline: 2px solid rgba(77, 107, 254, .4);
86
+ outline-offset: 2px;
87
+ border-radius: 10px;
88
+ }
89
+
90
+ .sr-only {
91
+ position: absolute;
92
+ width: 1px;
93
+ height: 1px;
94
+ padding: 0;
95
+ margin: -1px;
96
+ overflow: hidden;
97
+ clip: rect(0 0 0 0);
98
+ white-space: nowrap;
99
+ border: 0;
100
+ }
101
+
102
+ .muted { color: var(--muted); }
103
+ .small { font-size: 12px; }
104
+ .strong { font-weight: 650; }
105
+ .err-text { color: var(--err); }
106
+ .warn-text { color: var(--warn); }
107
+ .danger-text { color: #ffd7dc; }
108
+
109
+ /* ------------------------------------------------------------------ layout */
110
+
111
+ .app {
112
+ min-height: 100vh;
113
+ max-width: 1440px;
114
+ margin: 0 auto;
115
+ padding: 26px 22px 40px;
116
+ display: grid;
117
+ gap: 18px;
118
+ align-content: start;
119
+ }
120
+
121
+ .app-main {
122
+ display: grid;
123
+ gap: 18px;
124
+ }
125
+
126
+ .app-footer {
127
+ display: flex;
128
+ flex-wrap: wrap;
129
+ gap: 8px 16px;
130
+ justify-content: space-between;
131
+ padding: 4px 2px;
132
+ color: var(--muted);
133
+ font-size: 11.5px;
134
+ }
135
+
136
+ .toolbar {
137
+ display: flex;
138
+ flex-wrap: wrap;
139
+ align-items: center;
140
+ gap: 12px;
141
+ }
142
+
143
+ /* ------------------------------------------------------------------ header */
144
+
145
+ .topbar {
146
+ display: grid;
147
+ gap: 14px;
148
+ padding: 18px 20px;
149
+ background: var(--panel);
150
+ backdrop-filter: blur(18px) saturate(140%);
151
+ -webkit-backdrop-filter: blur(18px) saturate(140%);
152
+ border: 1px solid var(--border);
153
+ border-radius: var(--radius);
154
+ box-shadow: var(--panel-shadow);
155
+ }
156
+
157
+ .brand {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 12px;
161
+ min-width: 0;
162
+ }
163
+
164
+ .brand-mark {
165
+ flex: 0 0 auto;
166
+ display: grid;
167
+ place-items: center;
168
+ width: 42px;
169
+ height: 42px;
170
+ border-radius: 13px;
171
+ background: linear-gradient(135deg, rgba(77, 107, 254, .95), rgba(53, 208, 232, .8));
172
+ color: #f8faff;
173
+ font-weight: 700;
174
+ font-size: 13px;
175
+ letter-spacing: .04em;
176
+ box-shadow: 0 10px 24px rgba(77, 107, 254, .35);
177
+ }
178
+
179
+ .brand-mark.big {
180
+ width: 56px;
181
+ height: 56px;
182
+ border-radius: 17px;
183
+ font-size: 15px;
184
+ }
185
+
186
+ .brand-name {
187
+ margin: 0;
188
+ font-size: 18px;
189
+ font-weight: 680;
190
+ letter-spacing: .01em;
191
+ }
192
+
193
+ .brand-sub {
194
+ margin: 4px 0 0;
195
+ color: var(--muted);
196
+ font-size: 12.5px;
197
+ }
198
+
199
+ .topbar-meta {
200
+ display: flex;
201
+ flex-wrap: wrap;
202
+ gap: 10px;
203
+ }
204
+
205
+ .meta-chip {
206
+ display: grid;
207
+ gap: 4px;
208
+ min-width: 150px;
209
+ padding: 8px 12px;
210
+ background: rgba(255, 255, 255, .04);
211
+ border: 1px solid var(--border);
212
+ border-radius: 12px;
213
+ font-size: 12px;
214
+ }
215
+
216
+ .meta-label {
217
+ color: var(--muted);
218
+ font-size: 11px;
219
+ letter-spacing: .06em;
220
+ text-transform: uppercase;
221
+ }
222
+
223
+ .meta-value {
224
+ word-break: break-all;
225
+ }
226
+
227
+ .providers {
228
+ display: flex;
229
+ gap: 8px;
230
+ }
231
+
232
+ .provider-badge {
233
+ display: inline-flex;
234
+ align-items: center;
235
+ gap: 6px;
236
+ padding: 3px 10px;
237
+ border: 1px solid var(--border);
238
+ border-radius: 999px;
239
+ font-size: 11.5px;
240
+ letter-spacing: .06em;
241
+ text-transform: uppercase;
242
+ }
243
+
244
+ .provider-badge .dot {
245
+ width: 7px;
246
+ height: 7px;
247
+ border-radius: 50%;
248
+ background: currentColor;
249
+ box-shadow: 0 0 8px currentColor;
250
+ }
251
+
252
+ .provider-badge.on {
253
+ color: var(--ok);
254
+ border-color: rgba(61, 220, 151, .40);
255
+ background: rgba(61, 220, 151, .12);
256
+ }
257
+
258
+ .provider-badge.off {
259
+ color: var(--warn);
260
+ border-color: rgba(255, 194, 75, .35);
261
+ background: rgba(255, 194, 75, .10);
262
+ }
263
+
264
+ .provider-hint {
265
+ grid-column: 1 / -1;
266
+ margin: 0;
267
+ padding: 10px 12px;
268
+ color: #ffe0ab;
269
+ background: rgba(255, 194, 75, .09);
270
+ border: 1px solid rgba(255, 194, 75, .28);
271
+ border-radius: 12px;
272
+ font-size: 12.5px;
273
+ line-height: 1.7;
274
+ }
275
+
276
+ .provider-hint .code {
277
+ margin: 0 4px;
278
+ }
279
+
280
+ /* ----------------------------------------------------------------- banners */
281
+
282
+ .banner {
283
+ display: flex;
284
+ align-items: center;
285
+ justify-content: space-between;
286
+ gap: 12px;
287
+ padding: 12px 16px;
288
+ background: var(--panel);
289
+ backdrop-filter: blur(18px) saturate(140%);
290
+ -webkit-backdrop-filter: blur(18px) saturate(140%);
291
+ border: 1px solid var(--border);
292
+ border-radius: 14px;
293
+ box-shadow: 0 12px 34px rgba(0, 0, 0, .35);
294
+ }
295
+
296
+ .banner.error {
297
+ border-color: rgba(255, 107, 129, .42);
298
+ background: linear-gradient(160deg, rgba(255, 107, 129, .16), rgba(255, 107, 129, .07));
299
+ }
300
+
301
+ .banner.success {
302
+ border-color: rgba(61, 220, 151, .40);
303
+ background: linear-gradient(160deg, rgba(61, 220, 151, .14), rgba(61, 220, 151, .06));
304
+ }
305
+
306
+ .banner.info {
307
+ border-color: rgba(77, 107, 254, .40);
308
+ background: linear-gradient(160deg, rgba(77, 107, 254, .16), rgba(77, 107, 254, .06));
309
+ }
310
+
311
+ .banner-text {
312
+ font-size: 13px;
313
+ line-height: 1.6;
314
+ }
315
+
316
+ .banner-actions {
317
+ display: flex;
318
+ flex: 0 0 auto;
319
+ gap: 8px;
320
+ }
321
+
322
+ /* ------------------------------------------------------------------ panels */
323
+
324
+ .panel {
325
+ display: grid;
326
+ gap: 14px;
327
+ padding: 18px 20px;
328
+ background: var(--panel);
329
+ backdrop-filter: blur(18px) saturate(140%);
330
+ -webkit-backdrop-filter: blur(18px) saturate(140%);
331
+ border: 1px solid var(--border);
332
+ border-radius: var(--radius);
333
+ box-shadow: var(--panel-shadow);
334
+ }
335
+
336
+ .panel-head {
337
+ display: flex;
338
+ align-items: center;
339
+ gap: 10px;
340
+ }
341
+
342
+ .panel-title {
343
+ margin: 0;
344
+ font-size: 15px;
345
+ font-weight: 650;
346
+ }
347
+
348
+ .count {
349
+ padding: 1px 8px;
350
+ color: var(--muted);
351
+ background: rgba(255, 255, 255, .05);
352
+ border: 1px solid var(--border);
353
+ border-radius: 999px;
354
+ font-size: 11.5px;
355
+ }
356
+
357
+ .panel-actions {
358
+ display: flex;
359
+ align-items: center;
360
+ gap: 8px;
361
+ margin-left: auto;
362
+ }
363
+
364
+ /* ----------------------------------------------------------------- buttons */
365
+
366
+ .btn {
367
+ display: inline-flex;
368
+ align-items: center;
369
+ justify-content: center;
370
+ gap: 6px;
371
+ padding: 8px 14px;
372
+ color: var(--text);
373
+ background: linear-gradient(160deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .045));
374
+ border: 1px solid var(--border-strong);
375
+ border-radius: 10px;
376
+ font-size: 13px;
377
+ font-weight: 500;
378
+ cursor: pointer;
379
+ transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease, background-color .16s ease;
380
+ }
381
+
382
+ .btn:hover:not(:disabled) {
383
+ transform: translateY(-1px);
384
+ border-color: rgba(255, 255, 255, .30);
385
+ }
386
+
387
+ .btn:active:not(:disabled) {
388
+ transform: translateY(0);
389
+ }
390
+
391
+ .btn.primary {
392
+ background: linear-gradient(135deg, var(--accent), #3f7ef7);
393
+ border-color: rgba(155, 175, 255, .45);
394
+ box-shadow: 0 10px 26px rgba(77, 107, 254, .35);
395
+ }
396
+
397
+ .btn.primary:hover:not(:disabled) {
398
+ box-shadow: 0 14px 30px rgba(77, 107, 254, .45);
399
+ }
400
+
401
+ .btn.danger {
402
+ color: #ffd9de;
403
+ background: linear-gradient(160deg, rgba(255, 107, 129, .22), rgba(255, 107, 129, .12));
404
+ border-color: rgba(255, 107, 129, .45);
405
+ }
406
+
407
+ .btn.ghost {
408
+ background: transparent;
409
+ border-color: var(--border);
410
+ }
411
+
412
+ .btn.small {
413
+ padding: 6px 10px;
414
+ border-radius: 9px;
415
+ font-size: 12px;
416
+ }
417
+
418
+ .btn.tiny {
419
+ padding: 2px 8px;
420
+ font-size: 11px;
421
+ line-height: 1.5;
422
+ border-radius: 8px;
423
+ }
424
+
425
+ .btn:disabled {
426
+ opacity: .45;
427
+ cursor: not-allowed;
428
+ transform: none;
429
+ box-shadow: none;
430
+ }
431
+
432
+ /* ------------------------------------------------------------------- pills */
433
+
434
+ .pill {
435
+ display: inline-flex;
436
+ align-items: center;
437
+ padding: 3px 10px;
438
+ border: 1px solid var(--border-strong);
439
+ border-radius: 999px;
440
+ font-size: 10.5px;
441
+ font-weight: 650;
442
+ letter-spacing: .09em;
443
+ text-transform: uppercase;
444
+ white-space: nowrap;
445
+ }
446
+
447
+ .pill.s-ok,
448
+ .pill.s-job-succeeded {
449
+ color: var(--ok);
450
+ background: rgba(61, 220, 151, .14);
451
+ border-color: rgba(61, 220, 151, .38);
452
+ }
453
+
454
+ .pill.s-accent,
455
+ .pill.s-job-running {
456
+ color: #b9c6ff;
457
+ background: var(--accent-soft);
458
+ border-color: rgba(77, 107, 254, .45);
459
+ }
460
+
461
+ .pill.s-warn,
462
+ .pill.s-job-queued {
463
+ color: var(--warn);
464
+ background: rgba(255, 194, 75, .14);
465
+ border-color: rgba(255, 194, 75, .38);
466
+ }
467
+
468
+ .pill.s-warn-muted {
469
+ color: var(--warn);
470
+ background: rgba(255, 194, 75, .08);
471
+ border-color: rgba(255, 194, 75, .22);
472
+ }
473
+
474
+ .pill.s-err,
475
+ .pill.s-job-failed {
476
+ color: var(--err);
477
+ background: rgba(255, 107, 129, .14);
478
+ border-color: rgba(255, 107, 129, .40);
479
+ }
480
+
481
+ .pill.s-muted {
482
+ color: var(--muted);
483
+ background: rgba(255, 255, 255, .05);
484
+ border-color: var(--border-strong);
485
+ }
486
+
487
+ /* ---------------------------------------------------------- environments */
488
+
489
+ /* One compact row per environment instead of a card grid: every row shows the
490
+ same columns, so a long list stays scannable and uses the width. */
491
+ .env-list {
492
+ display: grid;
493
+ gap: 0;
494
+ margin: 0;
495
+ padding: 0;
496
+ list-style: none;
497
+ }
498
+
499
+ .env-row {
500
+ display: grid;
501
+ gap: 4px;
502
+ padding: 12px 4px;
503
+ border-top: 1px solid var(--border);
504
+ transition: background-color .16s ease;
505
+ }
506
+
507
+ .env-row:first-child {
508
+ border-top: none;
509
+ }
510
+
511
+ .env-row:hover {
512
+ background: rgba(255, 255, 255, .028);
513
+ }
514
+
515
+ .env-row-main {
516
+ display: grid;
517
+ grid-template-columns: minmax(170px, 1fr) minmax(200px, auto) auto minmax(180px, 2fr) auto;
518
+ align-items: center;
519
+ gap: 10px 16px;
520
+ }
521
+
522
+ .env-id {
523
+ display: flex;
524
+ align-items: center;
525
+ gap: 8px;
526
+ min-width: 0;
527
+ }
528
+
529
+ .env-name {
530
+ font-size: 14.5px;
531
+ font-weight: 650;
532
+ overflow: hidden;
533
+ text-overflow: ellipsis;
534
+ white-space: nowrap;
535
+ }
536
+
537
+ .env-url {
538
+ display: inline-flex;
539
+ align-items: center;
540
+ gap: 6px;
541
+ min-width: 0;
542
+ }
543
+
544
+ .env-site {
545
+ color: var(--cyan);
546
+ font-size: 12.5px;
547
+ text-decoration: none;
548
+ border-bottom: 1px dashed rgba(53, 208, 232, .35);
549
+ white-space: nowrap;
550
+ }
551
+
552
+ .env-site:hover {
553
+ color: #8ee7f5;
554
+ border-bottom-color: rgba(142, 231, 245, .7);
555
+ }
556
+
557
+ .env-site.muted {
558
+ color: var(--muted);
559
+ border-bottom-color: var(--border);
560
+ }
561
+
562
+ .env-meta {
563
+ color: var(--muted);
564
+ font-size: 12px;
565
+ white-space: nowrap;
566
+ }
567
+
568
+ .env-chips {
569
+ min-width: 0;
570
+ overflow: hidden;
571
+ max-height: 22px;
572
+ }
573
+
574
+ .env-row-sub {
575
+ display: flex;
576
+ flex-wrap: wrap;
577
+ align-items: center;
578
+ gap: 6px 14px;
579
+ min-width: 0;
580
+ font-size: 11.5px;
581
+ }
582
+
583
+ .env-root {
584
+ margin: 0;
585
+ color: var(--muted);
586
+ font-size: 11.5px;
587
+ overflow: hidden;
588
+ text-overflow: ellipsis;
589
+ white-space: nowrap;
590
+ max-width: 100%;
591
+ }
592
+
593
+ .env-problem {
594
+ margin: 0;
595
+ padding: 2px 8px;
596
+ color: #ffd7dc;
597
+ background: rgba(255, 107, 129, .10);
598
+ border: 1px solid rgba(255, 107, 129, .30);
599
+ border-radius: 8px;
600
+ font-size: 11.5px;
601
+ line-height: 1.6;
602
+ word-break: break-word;
603
+ }
604
+
605
+ .env-actions {
606
+ display: flex;
607
+ flex-wrap: wrap;
608
+ justify-content: flex-end;
609
+ gap: 6px;
610
+ }
611
+
612
+ .card {
613
+ background: var(--panel);
614
+ backdrop-filter: blur(18px) saturate(140%);
615
+ -webkit-backdrop-filter: blur(18px) saturate(140%);
616
+ border: 1px solid var(--border);
617
+ border-radius: var(--radius);
618
+ box-shadow: var(--panel-shadow);
619
+ transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
620
+ }
621
+
622
+ /* ------------------------------------------------------------------- chips */
623
+
624
+ .chips {
625
+ display: flex;
626
+ flex-wrap: wrap;
627
+ align-items: center;
628
+ gap: 6px;
629
+ }
630
+
631
+ .chips.inline {
632
+ display: inline-flex;
633
+ }
634
+
635
+ .chip-tag {
636
+ display: inline-flex;
637
+ align-items: center;
638
+ padding: 2px 9px;
639
+ color: var(--text);
640
+ background: rgba(255, 255, 255, .06);
641
+ border: 1px solid var(--border);
642
+ border-radius: 999px;
643
+ font-size: 11.5px;
644
+ font-family: var(--mono);
645
+ }
646
+
647
+ .chip-tag.local {
648
+ color: var(--cyan);
649
+ background: rgba(53, 208, 232, .12);
650
+ border-color: rgba(53, 208, 232, .35);
651
+ letter-spacing: .04em;
652
+ text-transform: uppercase;
653
+ }
654
+
655
+ .chip-tag.warn {
656
+ color: var(--warn);
657
+ background: rgba(255, 194, 75, .12);
658
+ border-color: rgba(255, 194, 75, .35);
659
+ }
660
+
661
+ .chip-row {
662
+ display: flex;
663
+ flex-wrap: wrap;
664
+ gap: 8px;
665
+ margin-top: 2px;
666
+ }
667
+
668
+ .chip {
669
+ padding: 4px 11px;
670
+ color: var(--text);
671
+ background: rgba(255, 255, 255, .06);
672
+ border: 1px solid var(--border);
673
+ border-radius: 999px;
674
+ font-family: var(--mono);
675
+ font-size: 12px;
676
+ cursor: pointer;
677
+ transition: transform .14s ease, border-color .14s ease, background-color .14s ease;
678
+ }
679
+
680
+ .chip:hover:not(:disabled) {
681
+ transform: translateY(-1px);
682
+ border-color: var(--border-strong);
683
+ background: rgba(255, 255, 255, .10);
684
+ }
685
+
686
+ .chip.active,
687
+ .chip.accent {
688
+ color: #cdd7ff;
689
+ background: var(--accent-soft);
690
+ border-color: rgba(77, 107, 254, .48);
691
+ }
692
+
693
+ .chip:disabled {
694
+ opacity: .4;
695
+ cursor: not-allowed;
696
+ }
697
+
698
+ /* -------------------------------------------------------------------- jobs */
699
+
700
+ .jobs {
701
+ display: grid;
702
+ gap: 12px;
703
+ }
704
+
705
+ .job {
706
+ display: grid;
707
+ gap: 10px;
708
+ padding: 12px 14px;
709
+ background: rgba(255, 255, 255, .035);
710
+ border: 1px solid var(--border);
711
+ border-radius: 14px;
712
+ }
713
+
714
+ .job.running {
715
+ border-color: rgba(53, 208, 232, .34);
716
+ }
717
+
718
+ .job.queued {
719
+ border-color: rgba(255, 194, 75, .28);
720
+ }
721
+
722
+ .job.failed {
723
+ border-color: rgba(255, 107, 129, .32);
724
+ }
725
+
726
+ .job-head {
727
+ display: flex;
728
+ flex-wrap: wrap;
729
+ align-items: center;
730
+ gap: 8px 10px;
731
+ font-size: 12.5px;
732
+ }
733
+
734
+ .job-kind {
735
+ color: var(--muted);
736
+ }
737
+
738
+ .job-target {
739
+ font-size: 13.5px;
740
+ }
741
+
742
+ .job-time {
743
+ margin-left: auto;
744
+ color: var(--muted);
745
+ font-size: 11.5px;
746
+ }
747
+
748
+ .job-log {
749
+ display: grid;
750
+ gap: 4px;
751
+ max-height: 220px;
752
+ overflow-y: auto;
753
+ padding: 10px 12px;
754
+ background: rgba(0, 0, 0, .35);
755
+ border: 1px solid var(--border);
756
+ border-radius: 12px;
757
+ font-size: 12px;
758
+ }
759
+
760
+ .log-line {
761
+ display: grid;
762
+ grid-template-columns: 66px minmax(0, 1fr);
763
+ gap: 10px;
764
+ margin: 0;
765
+ align-items: start;
766
+ }
767
+
768
+ .log-time {
769
+ color: rgba(152, 162, 189, .75);
770
+ }
771
+
772
+ .log-text {
773
+ font-family: var(--mono);
774
+ white-space: pre-wrap;
775
+ word-break: break-word;
776
+ }
777
+
778
+ .job-error {
779
+ margin: 0;
780
+ padding: 8px 10px;
781
+ color: #ffd7dc;
782
+ background: rgba(255, 107, 129, .12);
783
+ border: 1px solid rgba(255, 107, 129, .38);
784
+ border-radius: 12px;
785
+ font-size: 12.5px;
786
+ line-height: 1.6;
787
+ }
788
+
789
+ .job-details summary {
790
+ padding: 2px 0;
791
+ color: var(--muted);
792
+ font-size: 12.5px;
793
+ cursor: pointer;
794
+ }
795
+
796
+ .job-details[open] summary {
797
+ margin-bottom: 10px;
798
+ }
799
+
800
+ /* ----------------------------------------------------------------- results */
801
+
802
+ .result {
803
+ display: grid;
804
+ gap: 8px;
805
+ font-size: 12.5px;
806
+ }
807
+
808
+ .result-title {
809
+ font-size: 12.5px;
810
+ font-weight: 650;
811
+ }
812
+
813
+ .result-list {
814
+ display: grid;
815
+ gap: 6px;
816
+ margin: 0;
817
+ padding: 0;
818
+ list-style: none;
819
+ color: var(--muted);
820
+ }
821
+
822
+ .result-list li {
823
+ display: flex;
824
+ flex-wrap: wrap;
825
+ align-items: baseline;
826
+ gap: 8px;
827
+ }
828
+
829
+ .result-arrow {
830
+ display: inline-flex;
831
+ align-items: baseline;
832
+ gap: 4px;
833
+ }
834
+
835
+ .result-line {
836
+ color: var(--muted);
837
+ }
838
+
839
+ .warn-list {
840
+ color: var(--warn);
841
+ }
842
+
843
+ .callout {
844
+ padding: 10px 12px;
845
+ border-radius: 12px;
846
+ font-size: 12.5px;
847
+ line-height: 1.6;
848
+ }
849
+
850
+ .callout.warn {
851
+ color: #ffe0ab;
852
+ background: rgba(255, 194, 75, .10);
853
+ border: 1px solid rgba(255, 194, 75, .30);
854
+ }
855
+
856
+ .callout.info {
857
+ color: #c9d4ff;
858
+ background: var(--accent-soft);
859
+ border: 1px solid rgba(77, 107, 254, .35);
860
+ }
861
+
862
+ .raw {
863
+ font-size: 12px;
864
+ color: var(--muted);
865
+ }
866
+
867
+ .raw summary {
868
+ cursor: pointer;
869
+ }
870
+
871
+ .pre {
872
+ max-height: 260px;
873
+ overflow: auto;
874
+ margin: 8px 0 0;
875
+ padding: 10px;
876
+ background: rgba(0, 0, 0, .35);
877
+ border: 1px solid var(--border);
878
+ border-radius: 10px;
879
+ font-size: 11.5px;
880
+ line-height: 1.5;
881
+ white-space: pre-wrap;
882
+ word-break: break-word;
883
+ }
884
+
885
+ /* ------------------------------------------------------------------- forms */
886
+
887
+ .form {
888
+ display: grid;
889
+ gap: 16px;
890
+ }
891
+
892
+ .field {
893
+ display: grid;
894
+ gap: 6px;
895
+ /* Never stretch a control to fill a row: a taller neighbour must not change
896
+ the height of this field's input. */
897
+ align-content: start;
898
+ }
899
+
900
+ /* A row of fields plus a note that belongs to the row as a whole. */
901
+ .field-block {
902
+ display: grid;
903
+ gap: 6px;
904
+ }
905
+
906
+ .field-row {
907
+ display: grid;
908
+ grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
909
+ gap: 14px;
910
+ align-items: start;
911
+ }
912
+
913
+ .label {
914
+ color: var(--muted);
915
+ font-size: 12.5px;
916
+ font-weight: 600;
917
+ letter-spacing: .04em;
918
+ }
919
+
920
+ .helper {
921
+ color: var(--muted);
922
+ font-size: 12px;
923
+ line-height: 1.6;
924
+ }
925
+
926
+ .hint {
927
+ margin: 0;
928
+ color: var(--muted);
929
+ font-size: 12px;
930
+ line-height: 1.6;
931
+ word-break: break-all;
932
+ }
933
+
934
+ .fieldset {
935
+ display: grid;
936
+ gap: 10px;
937
+ margin: 0;
938
+ padding: 12px 14px;
939
+ border: 1px solid var(--border);
940
+ border-radius: 14px;
941
+ }
942
+
943
+ .input,
944
+ .select,
945
+ .textarea {
946
+ width: 100%;
947
+ padding: 10px 12px;
948
+ color: var(--text);
949
+ background: rgba(4, 7, 14, .55);
950
+ border: 1px solid var(--border);
951
+ border-radius: 12px;
952
+ font-size: 13px;
953
+ transition: border-color .16s ease, box-shadow .16s ease;
954
+ }
955
+
956
+ .input::placeholder,
957
+ .textarea::placeholder {
958
+ color: rgba(152, 162, 189, .65);
959
+ }
960
+
961
+ .input:focus,
962
+ .select:focus,
963
+ .textarea:focus {
964
+ border-color: rgba(77, 107, 254, .65);
965
+ box-shadow: 0 0 0 3px var(--accent-soft);
966
+ outline: none;
967
+ }
968
+
969
+ .textarea {
970
+ resize: vertical;
971
+ min-height: 76px;
972
+ line-height: 1.6;
973
+ }
974
+
975
+ /* A <select> is intrinsically a little taller than an <input>; pin both to the
976
+ same height so a row of controls lines up exactly. `box-sizing: border-box`
977
+ is global, so this includes the padding and the border. */
978
+ .input,
979
+ .select {
980
+ height: var(--control-height);
981
+ line-height: 1.25;
982
+ }
983
+
984
+ .select {
985
+ appearance: none;
986
+ background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
987
+ background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
988
+ background-size: 5px 5px, 5px 5px;
989
+ background-repeat: no-repeat;
990
+ }
991
+
992
+ .checks,
993
+ .radios {
994
+ display: flex;
995
+ flex-wrap: wrap;
996
+ gap: 10px 18px;
997
+ }
998
+
999
+ .checkbox,
1000
+ .radio {
1001
+ display: inline-flex;
1002
+ align-items: center;
1003
+ gap: 8px;
1004
+ font-size: 13px;
1005
+ cursor: pointer;
1006
+ }
1007
+
1008
+ .checkbox input,
1009
+ .radio input {
1010
+ width: 16px;
1011
+ height: 16px;
1012
+ accent-color: var(--accent);
1013
+ cursor: pointer;
1014
+ }
1015
+
1016
+ .radio.disabled {
1017
+ opacity: .55;
1018
+ cursor: not-allowed;
1019
+ }
1020
+
1021
+ .radio.disabled input {
1022
+ cursor: not-allowed;
1023
+ }
1024
+
1025
+ .checkbox.solo {
1026
+ padding: 10px 12px;
1027
+ background: rgba(255, 255, 255, .03);
1028
+ border: 1px solid var(--border);
1029
+ border-radius: 12px;
1030
+ }
1031
+
1032
+ .form-error {
1033
+ padding: 10px 12px;
1034
+ color: #ffd7dc;
1035
+ background: rgba(255, 107, 129, .12);
1036
+ border: 1px solid rgba(255, 107, 129, .40);
1037
+ border-radius: 12px;
1038
+ font-size: 12.5px;
1039
+ line-height: 1.6;
1040
+ }
1041
+
1042
+ .status-strip {
1043
+ display: flex;
1044
+ align-items: center;
1045
+ gap: 10px;
1046
+ padding: 8px 12px;
1047
+ color: var(--muted);
1048
+ background: rgba(255, 255, 255, .04);
1049
+ border: 1px solid var(--border);
1050
+ border-radius: 12px;
1051
+ font-size: 12.5px;
1052
+ }
1053
+
1054
+ .spec-forms {
1055
+ margin: 4px 0 0;
1056
+ padding-left: 18px;
1057
+ color: var(--muted);
1058
+ font-size: 12px;
1059
+ line-height: 1.75;
1060
+ }
1061
+
1062
+ .code {
1063
+ padding: 1px 6px;
1064
+ background: rgba(255, 255, 255, .07);
1065
+ border: 1px solid var(--border);
1066
+ border-radius: 6px;
1067
+ font-size: 12px;
1068
+ }
1069
+
1070
+ /* ------------------------------------------------------------------ modals */
1071
+
1072
+ .scrim {
1073
+ position: fixed;
1074
+ inset: 0;
1075
+ z-index: 50;
1076
+ display: grid;
1077
+ place-items: center;
1078
+ padding: 24px;
1079
+ overflow-y: auto;
1080
+ background: rgba(3, 5, 10, .62);
1081
+ backdrop-filter: blur(6px);
1082
+ -webkit-backdrop-filter: blur(6px);
1083
+ }
1084
+
1085
+ .modal {
1086
+ display: flex;
1087
+ flex-direction: column;
1088
+ width: min(680px, 100%);
1089
+ max-height: 88vh;
1090
+ overflow: hidden;
1091
+ background: linear-gradient(160deg, rgba(255, 255, 255, .105), rgba(255, 255, 255, .045));
1092
+ backdrop-filter: blur(22px) saturate(150%);
1093
+ -webkit-backdrop-filter: blur(22px) saturate(150%);
1094
+ border: 1px solid var(--border-strong);
1095
+ border-radius: 22px;
1096
+ box-shadow: 0 30px 80px rgba(0, 0, 0, .60), inset 0 1px 0 rgba(255, 255, 255, .08);
1097
+ }
1098
+
1099
+ .modal-head {
1100
+ display: flex;
1101
+ align-items: center;
1102
+ justify-content: space-between;
1103
+ gap: 12px;
1104
+ padding: 18px 22px;
1105
+ border-bottom: 1px solid var(--border);
1106
+ }
1107
+
1108
+ .modal-title {
1109
+ margin: 0;
1110
+ font-size: 16px;
1111
+ font-weight: 650;
1112
+ word-break: break-all;
1113
+ }
1114
+
1115
+ .modal-body {
1116
+ overflow-y: auto;
1117
+ padding: 20px 22px 22px;
1118
+ }
1119
+
1120
+ .modal-foot {
1121
+ display: flex;
1122
+ justify-content: flex-end;
1123
+ gap: 10px;
1124
+ padding-top: 4px;
1125
+ }
1126
+
1127
+ /* ------------------------------------------------------------ empty/loading */
1128
+
1129
+ .empty {
1130
+ padding: 28px;
1131
+ color: var(--muted);
1132
+ border: 1px dashed var(--border);
1133
+ border-radius: var(--radius);
1134
+ text-align: center;
1135
+ }
1136
+
1137
+ .empty-title {
1138
+ margin: 0 0 6px;
1139
+ color: var(--text);
1140
+ font-weight: 650;
1141
+ }
1142
+
1143
+ .auth-screen {
1144
+ min-height: 100vh;
1145
+ display: grid;
1146
+ place-items: center;
1147
+ padding: 24px;
1148
+ }
1149
+
1150
+ .auth-card {
1151
+ display: grid;
1152
+ gap: 14px;
1153
+ justify-items: start;
1154
+ max-width: 640px;
1155
+ padding: 30px;
1156
+ }
1157
+
1158
+ .auth-title {
1159
+ margin: 0;
1160
+ font-size: 19px;
1161
+ font-weight: 680;
1162
+ }
1163
+
1164
+ .auth-body {
1165
+ display: grid;
1166
+ gap: 12px;
1167
+ color: var(--text);
1168
+ font-size: 13.5px;
1169
+ line-height: 1.75;
1170
+ }
1171
+
1172
+ .loading-bar {
1173
+ width: 100%;
1174
+ max-width: 260px;
1175
+ height: 3px;
1176
+ overflow: hidden;
1177
+ background: rgba(255, 255, 255, .08);
1178
+ border-radius: 999px;
1179
+ }
1180
+
1181
+ .loading-bar span {
1182
+ display: block;
1183
+ width: 40%;
1184
+ height: 100%;
1185
+ background: linear-gradient(90deg, var(--accent), var(--cyan));
1186
+ border-radius: 999px;
1187
+ animation: loading-slide 1.2s ease-in-out infinite;
1188
+ }
1189
+
1190
+ @keyframes loading-slide {
1191
+ 0% { transform: translateX(-60%); }
1192
+ 100% { transform: translateX(260%); }
1193
+ }
1194
+
1195
+ /* -------------------------------------------------------------- scrollbars */
1196
+
1197
+ .job-log::-webkit-scrollbar,
1198
+ .modal-body::-webkit-scrollbar,
1199
+ .pre::-webkit-scrollbar,
1200
+ .scrim::-webkit-scrollbar {
1201
+ width: 10px;
1202
+ height: 10px;
1203
+ }
1204
+
1205
+ .job-log::-webkit-scrollbar-thumb,
1206
+ .modal-body::-webkit-scrollbar-thumb,
1207
+ .pre::-webkit-scrollbar-thumb,
1208
+ .scrim::-webkit-scrollbar-thumb {
1209
+ background: rgba(255, 255, 255, .14);
1210
+ background-clip: content-box;
1211
+ border: 3px solid transparent;
1212
+ border-radius: 999px;
1213
+ }
1214
+
1215
+ .job-log::-webkit-scrollbar-track,
1216
+ .modal-body::-webkit-scrollbar-track,
1217
+ .pre::-webkit-scrollbar-track,
1218
+ .scrim::-webkit-scrollbar-track {
1219
+ background: transparent;
1220
+ }
1221
+
1222
+ /* -------------------------------------------------------------- settings -- */
1223
+
1224
+ .settings-block {
1225
+ display: grid;
1226
+ gap: 10px;
1227
+ padding: 14px;
1228
+ background: rgba(255, 255, 255, .02);
1229
+ border: 1px solid var(--border);
1230
+ border-radius: 14px;
1231
+ }
1232
+
1233
+ .settings-title {
1234
+ margin: 0;
1235
+ font-size: 13.5px;
1236
+ font-weight: 650;
1237
+ }
1238
+
1239
+ .settings-current {
1240
+ margin: 0;
1241
+ color: var(--muted);
1242
+ font-size: 12.5px;
1243
+ word-break: break-all;
1244
+ }
1245
+
1246
+ .settings-row {
1247
+ display: flex;
1248
+ flex-wrap: wrap;
1249
+ align-items: flex-end;
1250
+ gap: 10px;
1251
+ }
1252
+
1253
+ .settings-row > .field {
1254
+ flex: 1 1 200px;
1255
+ }
1256
+
1257
+ .settings-row > .input {
1258
+ flex: 1 1 240px;
1259
+ }
1260
+
1261
+ .settings-provider {
1262
+ display: grid;
1263
+ gap: 10px;
1264
+ padding-top: 12px;
1265
+ border-top: 1px dashed var(--border);
1266
+ }
1267
+
1268
+ .settings-provider-head {
1269
+ display: flex;
1270
+ align-items: center;
1271
+ flex-wrap: wrap;
1272
+ gap: 10px;
1273
+ }
1274
+
1275
+ .settings-actions {
1276
+ display: flex;
1277
+ justify-content: flex-end;
1278
+ gap: 8px;
1279
+ }
1280
+
1281
+ /* The header chips now carry a small action button. */
1282
+ .meta-chip .btn.small {
1283
+ justify-self: start;
1284
+ padding: 3px 10px;
1285
+ }
1286
+
1287
+ .provider-hint .btn.small {
1288
+ margin: 0 6px;
1289
+ vertical-align: middle;
1290
+ }
1291
+
1292
+ /* --------------------------------------------------------------- responsive */
1293
+
1294
+ @media (min-width: 861px) {
1295
+ .topbar {
1296
+ grid-template-columns: minmax(0, 1fr) auto;
1297
+ align-items: center;
1298
+ }
1299
+ }
1300
+
1301
+ @media (max-width: 860px) {
1302
+ .app {
1303
+ padding: 18px 14px 30px;
1304
+ }
1305
+
1306
+ /* One column per row: the link, modules and actions stack instead of
1307
+ squeezing into a single line. */
1308
+ .env-row-main {
1309
+ grid-template-columns: minmax(0, 1fr);
1310
+ align-items: start;
1311
+ gap: 6px;
1312
+ }
1313
+
1314
+ .env-actions {
1315
+ justify-content: flex-start;
1316
+ }
1317
+
1318
+ .env-chips {
1319
+ max-height: none;
1320
+ }
1321
+
1322
+ .job-time {
1323
+ margin-left: 0;
1324
+ }
1325
+
1326
+ .log-line {
1327
+ grid-template-columns: 56px minmax(0, 1fr);
1328
+ }
1329
+
1330
+ .scrim {
1331
+ padding: 14px;
1332
+ }
1333
+
1334
+ .modal {
1335
+ max-height: 92vh;
1336
+ }
1337
+ }
1338
+
1339
+ @media (prefers-reduced-motion: reduce) {
1340
+ *,
1341
+ *::before,
1342
+ *::after {
1343
+ animation-duration: .01ms !important;
1344
+ animation-iteration-count: 1 !important;
1345
+ transition-duration: .01ms !important;
1346
+ }
1347
+ }
1348
+
1349
+ /* ------------------------------------------- environment configuration -- */
1350
+
1351
+ /* The per-environment "完整配置" dialog (opened from the 配置 row button): the
1352
+ summary grid, the manifest module table and the foldable Wyn.conf lists.
1353
+ Everything is introduced here and prefixed `config-`, so the rest of the page
1354
+ is untouched. */
1355
+ .config-view {
1356
+ display: grid;
1357
+ gap: 14px;
1358
+ }
1359
+
1360
+ /* This dialog is a data view (a five-column module table and long paths), so it
1361
+ gets a wider modal than the compact action dialogs. */
1362
+ .modal.modal-wide {
1363
+ width: min(1080px, 94vw);
1364
+ }
1365
+
1366
+ .config-table {
1367
+ table-layout: fixed;
1368
+ }
1369
+
1370
+ /* 模块 · 版本 · 来源 · 部署目标 · 校验 */
1371
+ .config-table th:nth-child(1), .config-table td:nth-child(1) { width: 20%; }
1372
+ .config-table th:nth-child(2), .config-table td:nth-child(2) { width: 10%; }
1373
+ .config-table th:nth-child(3), .config-table td:nth-child(3) { width: 18%; }
1374
+ .config-table th:nth-child(4), .config-table td:nth-child(4) { width: 36%; }
1375
+ .config-table th:nth-child(5), .config-table td:nth-child(5) { width: 16%; }
1376
+
1377
+ .config-table td:nth-child(2) { white-space: nowrap; font-variant-numeric: tabular-nums; }
1378
+ .config-table td:nth-child(4) { overflow-wrap: anywhere; }
1379
+
1380
+ /* A path, artifact name or digest may be long: wrap it instead of clipping it. */
1381
+ .config-sub, .config-field-value, .config-digest { overflow-wrap: anywhere; }
1382
+ .config-digest { white-space: nowrap; }
1383
+
1384
+ .config-block {
1385
+ display: grid;
1386
+ gap: 10px;
1387
+ padding: 14px;
1388
+ background: rgba(255, 255, 255, .02);
1389
+ border: 1px solid var(--border);
1390
+ border-radius: 14px;
1391
+ }
1392
+
1393
+ .config-title {
1394
+ margin: 0;
1395
+ font-size: 13.5px;
1396
+ font-weight: 650;
1397
+ }
1398
+
1399
+ .config-grid {
1400
+ display: grid;
1401
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1402
+ gap: 8px 16px;
1403
+ }
1404
+
1405
+ .config-field {
1406
+ display: grid;
1407
+ grid-template-columns: 96px minmax(0, 1fr);
1408
+ align-items: baseline;
1409
+ gap: 8px;
1410
+ min-width: 0;
1411
+ font-size: 12.5px;
1412
+ }
1413
+
1414
+ .config-field-label {
1415
+ color: var(--muted);
1416
+ }
1417
+
1418
+ .config-field-value {
1419
+ min-width: 0;
1420
+ word-break: break-all;
1421
+ }
1422
+
1423
+ .config-site {
1424
+ color: var(--cyan);
1425
+ }
1426
+
1427
+ .config-site.muted {
1428
+ color: var(--muted);
1429
+ }
1430
+
1431
+ /* A value with its inline action button (copy the address, open the directory). */
1432
+ .config-inline {
1433
+ display: inline-flex;
1434
+ align-items: center;
1435
+ flex-wrap: wrap;
1436
+ gap: 8px;
1437
+ min-width: 0;
1438
+ }
1439
+
1440
+ .config-sublist {
1441
+ display: grid;
1442
+ gap: 6px;
1443
+ }
1444
+
1445
+ .config-sublist-label {
1446
+ color: var(--muted);
1447
+ font-size: 12px;
1448
+ }
1449
+
1450
+ /* A scroll box keeps a wide module table from stretching the dialog. */
1451
+ .config-table-wrap {
1452
+ overflow-x: auto;
1453
+ border: 1px solid var(--border);
1454
+ border-radius: 12px;
1455
+ }
1456
+
1457
+ .config-table {
1458
+ width: 100%;
1459
+ border-collapse: collapse;
1460
+ font-size: 12.5px;
1461
+ }
1462
+
1463
+ .config-table th {
1464
+ padding: 8px 10px;
1465
+ color: var(--muted);
1466
+ font-weight: 600;
1467
+ text-align: left;
1468
+ white-space: nowrap;
1469
+ background: rgba(255, 255, 255, .03);
1470
+ border-bottom: 1px solid var(--border);
1471
+ }
1472
+
1473
+ .config-table td {
1474
+ padding: 8px 10px;
1475
+ vertical-align: top;
1476
+ border-bottom: 1px solid rgba(255, 255, 255, .06);
1477
+ }
1478
+
1479
+ .config-table tr:last-child td {
1480
+ border-bottom: none;
1481
+ }
1482
+
1483
+ .config-module-cell {
1484
+ display: grid;
1485
+ gap: 4px;
1486
+ min-width: 140px;
1487
+ }
1488
+
1489
+ .config-module-tags {
1490
+ display: flex;
1491
+ flex-wrap: wrap;
1492
+ gap: 4px;
1493
+ }
1494
+
1495
+ /* A second, quieter line inside a cell (assembly, artifact path, override path). */
1496
+ .config-sub {
1497
+ display: block;
1498
+ max-width: 280px;
1499
+ color: var(--muted);
1500
+ font-size: 11px;
1501
+ line-height: 1.45;
1502
+ word-break: break-all;
1503
+ }
1504
+
1505
+ .chip-tag.source-remote {
1506
+ color: #c9d4ff;
1507
+ border-color: rgba(77, 107, 254, .35);
1508
+ }
1509
+
1510
+ .chip-tag.source-local {
1511
+ color: var(--cyan);
1512
+ background: rgba(53, 208, 232, .12);
1513
+ border-color: rgba(53, 208, 232, .35);
1514
+ }
1515
+
1516
+ /* A worker is a mapping of its own: the chip makes it stand out in the table. */
1517
+ .chip-tag.source-worker {
1518
+ color: var(--ok);
1519
+ background: rgba(61, 220, 151, .12);
1520
+ border-color: rgba(61, 220, 151, .35);
1521
+ }
1522
+
1523
+ /* One line per manifest mapping, directly under the module name. */
1524
+ .config-mappings {
1525
+ display: grid;
1526
+ gap: 3px;
1527
+ min-width: 0;
1528
+ }
1529
+
1530
+ .config-mappings-label {
1531
+ color: var(--muted);
1532
+ font-size: 11px;
1533
+ }
1534
+
1535
+ .config-mapping {
1536
+ display: flex;
1537
+ flex-wrap: wrap;
1538
+ align-items: baseline;
1539
+ gap: 4px;
1540
+ min-width: 0;
1541
+ }
1542
+
1543
+ .config-mapping-text {
1544
+ max-width: 300px;
1545
+ font-size: 11.5px;
1546
+ line-height: 1.45;
1547
+ word-break: break-all;
1548
+ }
1549
+
1550
+ .config-sep {
1551
+ color: var(--muted);
1552
+ }
1553
+
1554
+ .config-mappings-fold summary {
1555
+ cursor: pointer;
1556
+ }
1557
+
1558
+ .config-mappings-fold .config-mappings {
1559
+ margin-top: 6px;
1560
+ }
1561
+
1562
+ .config-digest {
1563
+ color: var(--muted);
1564
+ white-space: nowrap;
1565
+ }
1566
+
1567
+ .config-overrides,
1568
+ .config-services,
1569
+ .config-paths {
1570
+ display: grid;
1571
+ gap: 6px;
1572
+ margin: 0;
1573
+ padding: 0;
1574
+ list-style: none;
1575
+ }
1576
+
1577
+ .config-override,
1578
+ .config-service {
1579
+ display: flex;
1580
+ flex-wrap: wrap;
1581
+ align-items: baseline;
1582
+ gap: 8px;
1583
+ min-width: 0;
1584
+ }
1585
+
1586
+ .config-override .config-sub {
1587
+ max-width: 100%;
1588
+ }
1589
+
1590
+ .config-paths {
1591
+ max-height: 200px;
1592
+ overflow: auto;
1593
+ margin-top: 8px;
1594
+ padding: 10px;
1595
+ background: rgba(0, 0, 0, .35);
1596
+ border: 1px solid var(--border);
1597
+ border-radius: 10px;
1598
+ font-size: 11.5px;
1599
+ }
1600
+
1601
+ .config-paths li {
1602
+ word-break: break-all;
1603
+ }
1604
+
1605
+ .config-collapse summary {
1606
+ cursor: pointer;
1607
+ }
1608
+
1609
+ @media (max-width: 620px) {
1610
+ /* One line per field: the label no longer squeezes the value column. */
1611
+ .config-field {
1612
+ grid-template-columns: minmax(0, 1fr);
1613
+ gap: 2px;
1614
+ }
1615
+
1616
+ .config-sub {
1617
+ max-width: none;
1618
+ }
1619
+ }