main
html 896 lines 34.4 KB
Raw
1 <html>
2 <head>
3 <title>Self Update</title>
4 <script type="module">
5 import { store } from "/components/settings/external/self-update-store.js";
6 </script>
7 </head>
8
9 <body>
10 <div x-data>
11 <template x-if="$store.selfUpdateStore">
12 <div
13 x-init="$store.selfUpdateStore.init()"
14 x-destroy="$store.selfUpdateStore.cleanup()"
15 class="self-update-modal"
16 >
17 <div
18 class="self-update-warning-banner self-update-major-version-banner"
19 x-show="$store.selfUpdateStore.hasMajorUpgrade"
20 >
21 <div class="self-update-major-version-banner-copy">
22 <div class="self-update-major-version-banner-title">
23 New major version available
24 </div>
25 <div x-text="$store.selfUpdateStore.majorUpgradeBannerMessage"></div>
26 </div>
27 <a
28 href="https://www.agent-zero.ai/p/docs/get-started/"
29 target="_blank"
30 rel="noreferrer"
31 >
32 Website installation guide
33 </a>
34 </div>
35
36 <div class="self-update-tabs">
37 <ul class="nav nav-tabs" id="self-update-tabs" role="tablist">
38 <li class="nav-item" role="presentation">
39 <button
40 class="nav-link"
41 :class="{ active: $store.selfUpdateStore.activeTab === 'quick' }"
42 id="self-update-quick-tab"
43 type="button"
44 role="tab"
45 aria-controls="self-update-quick"
46 :aria-selected="$store.selfUpdateStore.activeTab === 'quick'"
47 @click="$store.selfUpdateStore.setTab('quick')"
48 >
49 Quick
50 </button>
51 </li>
52 <li class="nav-item" role="presentation">
53 <button
54 class="nav-link"
55 :class="{ active: $store.selfUpdateStore.activeTab === 'advanced' }"
56 id="self-update-advanced-tab"
57 type="button"
58 role="tab"
59 aria-controls="self-update-advanced"
60 :aria-selected="$store.selfUpdateStore.activeTab === 'advanced'"
61 @click="$store.selfUpdateStore.setTab('advanced')"
62 >
63 Advanced
64 </button>
65 </li>
66 </ul>
67 </div>
68
69 <template x-if="$store.selfUpdateStore.activeTab === 'quick'">
70 <div id="self-update-quick" role="tabpanel" aria-labelledby="self-update-quick-tab">
71 <div class="self-update-quick-card">
72 <div class="self-update-quick-header">
73 <div
74 class="status-pill self-update-quick-status"
75 :class="$store.selfUpdateStore.quickStatusBadgeClass"
76 x-text="$store.selfUpdateStore.quickStatusLabel"
77 ></div>
78 </div>
79
80 <div
81 class="self-update-quick-copy self-update-quick-copy-prominent"
82 x-text="$store.selfUpdateStore.quickStatusMessage"
83 ></div>
84
85 <div class="self-update-quick-grid">
86 <div class="self-update-quick-version-card">
87 <div class="summary-label">Current</div>
88 <div class="summary-value" x-text="$store.selfUpdateStore.currentVersion"></div>
89 <div class="summary-meta">
90 <span x-text="$store.selfUpdateStore.formatReleaseTimestamp($store.selfUpdateStore.currentReleasedAt)"></span>
91 </div>
92 </div>
93
94 <div class="self-update-quick-arrow" :class="$store.selfUpdateStore.quickComparisonIconClass" aria-hidden="true">
95 <x-icon
96 :class="{ spinning: $store.selfUpdateStore.isCheckingStatus }"
97 :name="$store.selfUpdateStore.quickComparisonIcon"
98 ></x-icon>
99 </div>
100
101 <div class="self-update-quick-version-card self-update-quick-version-card-accent">
102 <div class="summary-label">Latest on main</div>
103 <div
104 class="summary-value"
105 x-text="$store.selfUpdateStore.mainBranchLatestVersion"
106 ></div>
107 <div class="summary-meta">
108 <span x-text="$store.selfUpdateStore.formatReleaseTimestamp($store.selfUpdateStore.mainBranchLatestReleasedAt)"></span>
109 </div>
110 </div>
111 </div>
112
113 <div class="self-update-quick-note">
114 Quick mode always targets <code>main</code> and uses the default backup settings.
115 Use Advanced for a different branch, manual version selection, or custom backup behavior.
116 </div>
117 </div>
118
119 <div
120 class="self-update-warning-banner"
121 x-show="$store.selfUpdateStore.quickMajorUpgradeNotice"
122 >
123 <span x-text="$store.selfUpdateStore.quickMajorUpgradeNotice"></span>
124 </div>
125 </div>
126 </template>
127
128 <template x-if="$store.selfUpdateStore.activeTab === 'advanced'">
129 <div
130 id="self-update-advanced"
131 role="tabpanel"
132 aria-labelledby="self-update-advanced-tab"
133 class="self-update-advanced-layout"
134 >
135 <div class="self-update-version-grid">
136 <div class="self-update-summary-card">
137 <div class="summary-label">Current version</div>
138 <div class="summary-value" x-text="$store.selfUpdateStore.currentVersion"></div>
139 <div class="summary-meta">
140 Commit
141 <code x-text="$store.selfUpdateStore.info?.current?.short_commit || 'unknown'"></code>
142 </div>
143 <div class="summary-meta" x-text="`Branch ${$store.selfUpdateStore.currentBranch || 'unknown'}`"></div>
144 <template x-if="$store.selfUpdateStore.info?.current?.describe && $store.selfUpdateStore.info?.current?.describe !== $store.selfUpdateStore.info?.current?.short_tag">
145 <div
146 class="summary-meta"
147 x-text="$store.selfUpdateStore.info?.current?.describe"
148 ></div>
149 </template>
150 </div>
151
152 <div class="self-update-summary-card">
153 <div class="summary-label">Latest version</div>
154 <div
155 class="summary-value"
156 x-text="$store.selfUpdateStore.info?.current_branch_latest?.display_version || 'Unavailable'"
157 ></div>
158 <template x-if="$store.selfUpdateStore.info?.current_branch_latest?.short_commit && $store.selfUpdateStore.info?.current_branch_latest?.branch !== 'main'">
159 <div class="summary-meta">
160 Commit
161 <code x-text="$store.selfUpdateStore.info?.current_branch_latest?.short_commit"></code>
162 </div>
163 </template>
164 <div
165 class="summary-meta"
166 x-text="`Branch ${$store.selfUpdateStore.info?.current_branch_latest?.branch || $store.selfUpdateStore.currentBranch || 'unknown'}`"
167 ></div>
168 <template x-if="$store.selfUpdateStore.info?.current_branch_latest?.describe && $store.selfUpdateStore.info?.current_branch_latest?.describe !== $store.selfUpdateStore.info?.current_branch_latest?.short_tag">
169 <div
170 class="summary-meta"
171 x-text="$store.selfUpdateStore.info?.current_branch_latest?.describe"
172 ></div>
173 </template>
174 <template x-if="$store.selfUpdateStore.info?.current_branch_latest?.supported === false">
175 <div class="summary-meta">
176 Latest official version is only tracked for <code>main</code>, <code>testing</code>,
177 and <code>development</code>.
178 </div>
179 </template>
180 </div>
181 </div>
182
183 <div class="self-update-summary-grid" x-show="$store.selfUpdateStore.info?.pending">
184 <div class="self-update-summary-card">
185 <div class="summary-label">Pending request</div>
186 <div
187 class="summary-value"
188 x-text="$store.selfUpdateStore.formatBranchTag($store.selfUpdateStore.info?.pending?.branch, $store.selfUpdateStore.info?.pending?.tag)"
189 ></div>
190 <div
191 class="summary-meta"
192 x-text="$store.selfUpdateStore.formatTimestamp($store.selfUpdateStore.info?.pending?.requested_at)"
193 ></div>
194 </div>
195 </div>
196
197 <template x-if="!$store.selfUpdateStore.isSupported">
198 <div class="self-update-warning">
199 Self-update is currently available only in dockerized Agent Zero deployments
200 that boot through <code>/exe/run_A0.sh</code>.
201 </div>
202 </template>
203
204 <div class="self-update-panel">
205 <button
206 type="button"
207 class="self-update-panel-toggle"
208 data-bs-toggle="collapse"
209 data-bs-target="#self-update-howto-collapse"
210 aria-expanded="false"
211 aria-controls="self-update-howto-collapse"
212 >
213 <span>How it works?</span>
214 <x-icon class="self-update-panel-toggle-icon" name="expand_more"></x-icon>
215 </button>
216 <div class="collapse" id="self-update-howto-collapse">
217 <div class="self-update-panel-body self-update-copy">
218 <p>
219 Agent Zero saves this request into
220 <code x-text="$store.selfUpdateStore.info?.paths?.update_file || '/exe/a0-self-update.yaml'"></code>,
221 restarts once, applies the requested branch and version target before the UI
222 starts again, then reloads this page when <code>/api/health</code> is healthy.
223 </p>
224 <p>
225 If the updated UI does not become healthy within 2 minutes, the bootstrap
226 manager in <code>/exe</code> restores the previous checkout and starts that
227 version again, so even an older downgraded <code>/a0</code> can be upgraded back
228 by creating the YAML file manually.
229 </p>
230 <p>
231 Version numbers use the format <code>vMAJOR.MINOR</code>. The first number is
232 the major release line and moving to a newer major version requires a newer
233 Docker image. The second number is the minor release line and those updates can
234 be applied through Agent Zero self-update inside the existing image.
235 </p>
236 <p>
237 On development branches you may also see versions like <code>v1.5+2</code>.
238 The <code>+2</code> means that checkout is based on <code>v1.5</code> plus two
239 additional commits after that release tag. This suffix is not used on
240 <code>main</code>.
241 </p>
242 </div>
243 </div>
244 </div>
245
246 <template x-if="$store.selfUpdateStore.info?.last_status">
247 <div class="self-update-panel">
248 <button
249 type="button"
250 class="self-update-panel-toggle"
251 data-bs-toggle="collapse"
252 data-bs-target="#self-update-last-attempt-collapse"
253 aria-expanded="false"
254 aria-controls="self-update-last-attempt-collapse"
255 >
256 <span>Last Attempt</span>
257 <span class="self-update-panel-toggle-trailing">
258 <span
259 class="status-pill self-update-header-status"
260 :class="$store.selfUpdateStore.getLastStatusBadgeClass($store.selfUpdateStore.info?.last_status?.status)"
261 x-text="$store.selfUpdateStore.getLastStatusLabel($store.selfUpdateStore.info?.last_status?.status)"
262 ></span>
263 <x-icon class="self-update-panel-toggle-icon" name="expand_more"></x-icon>
264 </span>
265 </button>
266 <div class="collapse" id="self-update-last-attempt-collapse">
267 <div class="self-update-panel-body">
268 <div class="status-message" x-text="$store.selfUpdateStore.info?.last_status?.message || ''"></div>
269 <div class="summary-meta">
270 Trigger:
271 <code x-text="$store.selfUpdateStore.info?.paths?.update_file || '/exe/a0-self-update.yaml'"></code>
272 </div>
273 <div class="summary-meta">
274 Log:
275 <code x-text="$store.selfUpdateStore.info?.paths?.log_file || '/exe/a0-self-update.log'"></code>
276 </div>
277 <div
278 class="summary-meta"
279 x-text="$store.selfUpdateStore.formatTimestamp($store.selfUpdateStore.info?.last_status?.finished_at)"
280 ></div>
281 <template x-if="$store.selfUpdateStore.info?.last_status?.backup_zip_path">
282 <div class="status-path">
283 Backup:
284 <code x-text="$store.selfUpdateStore.info?.last_status?.backup_zip_path"></code>
285 </div>
286 </template>
287 </div>
288 </div>
289 </div>
290 </template>
291
292 <template x-if="$store.selfUpdateStore.isSupported">
293 <div>
294 <div class="field">
295 <div class="field-label">
296 <div class="field-title">Target branch</div>
297 <div class="field-description">
298 Choose which official branch context should be used when resolving the requested tag.
299 </div>
300 </div>
301 <div class="field-control">
302 <select
303 x-model="$store.selfUpdateStore.form.branch"
304 x-effect="$nextTick(() => { $el.value = $store.selfUpdateStore.form.branch || 'main'; })"
305 @change="$store.selfUpdateStore.onBranchChanged()"
306 :disabled="$store.selfUpdateStore.isBusy"
307 >
308 <template x-for="branch in ($store.selfUpdateStore.info?.branches || [])" :key="branch.value">
309 <option :value="branch.value" x-text="branch.label"></option>
310 </template>
311 </select>
312 </div>
313 </div>
314
315 <div class="field">
316 <div class="field-label">
317 <div class="field-title">Version</div>
318 <div class="field-description">
319 Choose a preloaded version target from the
320 <a href="https://github.com/agent0ai/agent-zero" target="_blank" rel="noreferrer">Agent Zero repository</a>.
321 Only versions from the current major release line are listed here. Newer major lines require a Docker image update first.
322 </div>
323 <div class="field-description">
324 <code>latest</code> resolves to the newest tag on <code>main</code>, and to the current branch head on <code>testing</code> and <code>development</code>.
325 </div>
326 <template x-if="$store.selfUpdateStore.tagsError">
327 <div class="field-description">
328 Version lookup failed:
329 <span x-text="$store.selfUpdateStore.tagsError"></span>
330 </div>
331 </template>
332 </div>
333 <template x-if="$store.selfUpdateStore.higherMajorVersionMessage">
334 <div class="self-update-warning-banner">
335 <div x-text="$store.selfUpdateStore.higherMajorVersionMessage"></div>
336 <a
337 href="https://www.agent-zero.ai/p/docs/get-started/"
338 target="_blank"
339 rel="noreferrer"
340 >
341 Docker update guide
342 </a>
343 </div>
344 </template>
345 <div class="field-control">
346 <select
347 x-model="$store.selfUpdateStore.form.tag"
348 :disabled="$store.selfUpdateStore.isBusy || $store.selfUpdateStore.tagsLoading || !$store.selfUpdateStore.hasAvailableTags"
349 >
350 <option value="" x-text="$store.selfUpdateStore.versionSelectPlaceholder"></option>
351 <template x-for="tagOption in $store.selfUpdateStore.availableTagOptions" :key="tagOption.value">
352 <option :value="tagOption.value" x-text="tagOption.label"></option>
353 </template>
354 </select>
355 </div>
356 </div>
357
358 <div class="field">
359 <div class="field-label">
360 <div class="field-title">Back up <code>/a0/usr</code> first</div>
361 <div class="field-description">
362 Creates a zip backup before the release files are replaced.
363 </div>
364 </div>
365 <div class="field-control">
366 <label class="toggle">
367 <input
368 type="checkbox"
369 x-model="$store.selfUpdateStore.form.backup_usr"
370 :disabled="$store.selfUpdateStore.isBusy"
371 />
372 <span class="toggler"></span>
373 </label>
374 </div>
375 </div>
376
377 <div x-show="$store.selfUpdateStore.form.backup_usr">
378 <div class="field">
379 <div class="field-label">
380 <div class="field-title">Backup directory</div>
381 <div class="field-description">
382 Absolute or repo-relative path where the <code>usr</code> zip should be written.
383 </div>
384 </div>
385 <div class="field-control">
386 <input
387 type="text"
388 x-model="$store.selfUpdateStore.form.backup_path"
389 :disabled="$store.selfUpdateStore.isBusy"
390 />
391 </div>
392 </div>
393
394 <div class="field">
395 <div class="field-label">
396 <div class="field-title">Manual backup</div>
397 <div class="field-description">
398 Open the existing backup and restore modal if you want to create a backup before scheduling the update.
399 </div>
400 </div>
401 <div class="field-control">
402 <button
403 type="button"
404 class="btn btn-field"
405 @click="$store.selfUpdateStore.openManualBackupModal()"
406 :disabled="$store.selfUpdateStore.isBusy"
407 >
408 Manual backup
409 </button>
410 </div>
411 </div>
412
413 <div class="field">
414 <div class="field-label">
415 <div class="field-title">Backup filename</div>
416 <div class="field-description">
417 The manager normalizes this into a safe <code>.zip</code> filename. Leave it as-is for the default <code>usr-timestamp.zip</code> format.
418 </div>
419 </div>
420 <div class="field-control">
421 <input
422 type="text"
423 x-model="$store.selfUpdateStore.form.backup_name"
424 :disabled="$store.selfUpdateStore.isBusy"
425 />
426 </div>
427 </div>
428
429 <div class="field">
430 <div class="field-label">
431 <div class="field-title">If the filename already exists</div>
432 <div class="field-description">
433 Choose whether to rename the backup, replace it, or stop the update.
434 </div>
435 </div>
436 <div class="field-control">
437 <select
438 x-model="$store.selfUpdateStore.form.backup_conflict_policy"
439 :disabled="$store.selfUpdateStore.isBusy"
440 >
441 <option value="rename">Rename with suffix</option>
442 <option value="overwrite">Overwrite existing zip</option>
443 <option value="fail">Fail before restart</option>
444 </select>
445 </div>
446 </div>
447 </div>
448
449 <div class="self-update-file-hint">
450 The durable trigger, status, and log files live outside <code>/a0</code>:
451 <code x-text="$store.selfUpdateStore.info?.paths?.update_file || '/exe/a0-self-update.yaml'"></code>,
452 <code x-text="$store.selfUpdateStore.info?.paths?.status_file || '/exe/a0-self-update-status.yaml'"></code>,
453 <code x-text="$store.selfUpdateStore.info?.paths?.log_file || '/exe/a0-self-update.log'"></code>.
454 </div>
455 </div>
456 </template>
457 </div>
458 </template>
459
460 <div class="self-update-error" x-show="$store.selfUpdateStore.error">
461 <span x-text="$store.selfUpdateStore.error"></span>
462 </div>
463
464 <div class="self-update-progress-state" x-show="$store.selfUpdateStore.restarting">
465 <div class="self-update-progress-spinner"></div>
466 <div>
467 <div class="self-update-progress-title" x-text="$store.selfUpdateStore.restartStatusText || 'Update in progress'"></div>
468 <div class="self-update-progress-copy" x-text="$store.selfUpdateStore.restartDetailText || 'Waiting for Agent Zero to restart and become healthy again.'"></div>
469 </div>
470 </div>
471
472 <div class="modal-footer" data-modal-footer>
473 <template x-if="$store.selfUpdateStore.activeTab === 'quick'">
474 <button
475 type="button"
476 class="btn btn-ok"
477 :class="{ 'btn-field': !$store.selfUpdateStore.quickUpdateAvailable }"
478 @click="$store.selfUpdateStore.scheduleQuickUpdate()"
479 :disabled="!$store.selfUpdateStore.quickUpdateAvailable"
480 >
481 Restart and Update
482 </button>
483 </template>
484 <template x-if="$store.selfUpdateStore.activeTab === 'advanced'">
485 <button
486 type="button"
487 class="btn btn-ok"
488 :class="{ 'btn-field': !$store.selfUpdateStore.canScheduleUpdate }"
489 @click="$store.selfUpdateStore.scheduleUpdate()"
490 :disabled="!$store.selfUpdateStore.canScheduleUpdate"
491 >
492 Restart and Update
493 </button>
494 </template>
495 <button
496 type="button"
497 class="btn btn-field"
498 @click="$store.selfUpdateStore.refresh()"
499 :disabled="$store.selfUpdateStore.isBusy"
500 >
501 Refresh
502 </button>
503 <button
504 type="button"
505 class="btn btn-cancel"
506 @click="$store.selfUpdateStore.close()"
507 :disabled="$store.selfUpdateStore.restarting"
508 >
509 Cancel
510 </button>
511 </div>
512 </div>
513 </template>
514 </div>
515
516 <style>
517 .self-update-modal {
518 display: flex;
519 flex-direction: column;
520 gap: 1rem;
521 }
522
523 .self-update-tabs .nav {
524 display: flex;
525 padding-left: 0;
526 margin: 0;
527 list-style: none;
528 border-bottom: 1px solid var(--color-border);
529 gap: 0.25rem;
530 }
531
532 .self-update-major-version-banner {
533 margin-bottom: -0.1rem;
534 }
535
536 .self-update-major-version-banner-copy {
537 display: flex;
538 flex-direction: column;
539 gap: 0.2rem;
540 max-width: 58rem;
541 }
542
543 .self-update-major-version-banner-title {
544 font-weight: 700;
545 }
546
547 .self-update-tabs .nav-link {
548 font-family: "Rubik", Arial, Helvetica, sans-serif;
549 border: 1px solid transparent;
550 border-top-left-radius: 4px;
551 border-top-right-radius: 4px;
552 padding: 0.4rem 0.7rem;
553 background: transparent;
554 color: var(--color-text-secondary);
555 cursor: pointer;
556 display: inline-flex;
557 align-items: center;
558 gap: 0.3rem;
559 }
560
561 .self-update-tabs .nav-link.active {
562 color: var(--color-text-primary);
563 border-color: var(--color-border);
564 border-bottom-color: transparent;
565 background: var(--color-background);
566 }
567
568 .self-update-quick-card {
569 padding: 0.25rem 0 0;
570 }
571
572 .self-update-quick-header {
573 display: flex;
574 align-items: center;
575 justify-content: flex-start;
576 gap: 0.75rem;
577 margin-bottom: 0.65rem;
578 }
579
580 .status-pill.self-update-quick-status {
581 margin-top: 0;
582 padding: 0.34rem 0.82rem;
583 font-size: 0.98rem;
584 font-weight: 700;
585 letter-spacing: 0.05em;
586 }
587
588 .self-update-quick-copy {
589 color: var(--color-text-muted);
590 line-height: 1.55;
591 }
592
593 .self-update-quick-copy-prominent {
594 font-size: 1.18rem;
595 color: var(--color-text);
596 max-width: 58rem;
597 }
598
599 .self-update-quick-grid {
600 display: grid;
601 grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
602 gap: 0.9rem;
603 align-items: stretch;
604 margin-top: 1rem;
605 }
606
607 .self-update-quick-version-card {
608 border: 1px solid var(--color-border);
609 border-radius: 1rem;
610 padding: 1rem;
611 background: var(--color-panel);
612 }
613
614 .self-update-quick-version-card-accent {
615 border-color: color-mix(in srgb, var(--color-border) 85%, transparent);
616 }
617
618 .self-update-quick-arrow {
619 display: flex;
620 align-items: center;
621 justify-content: center;
622 min-width: 3.25rem;
623 }
624
625 .self-update-quick-arrow .material-symbols-outlined {
626 font-size: 2rem;
627 }
628
629 .self-update-quick-icon-success {
630 color: var(--color-success, #16a34a);
631 }
632
633 .self-update-quick-icon-info {
634 color: var(--color-primary, #2563eb);
635 }
636
637 .self-update-quick-icon-warning {
638 color: var(--color-warning, #d97706);
639 }
640
641 .self-update-quick-icon-neutral {
642 color: var(--color-text-muted);
643 }
644
645 .self-update-quick-version-card .summary-value {
646 font-size: clamp(2rem, 3vw, 2.5rem);
647 line-height: 1.05;
648 }
649
650 .self-update-quick-version-card .summary-meta {
651 margin-top: 0.65rem;
652 font-size: 1rem;
653 }
654
655 .self-update-quick-note {
656 margin-top: 1rem;
657 padding-top: 1rem;
658 border-top: 1px solid color-mix(in srgb, var(--color-border) 78%, transparent);
659 color: var(--color-text-muted);
660 line-height: 1.5;
661 }
662
663 .self-update-copy {
664 color: var(--color-text-muted);
665 line-height: 1.5;
666 }
667
668 .self-update-copy p {
669 margin: 0 0 0.75rem;
670 }
671
672 .self-update-advanced-layout {
673 display: flex;
674 flex-direction: column;
675 gap: 1rem;
676 }
677
678 .self-update-panel {
679 border: 1px solid var(--color-border);
680 border-radius: 0.9rem;
681 background: var(--color-panel);
682 }
683
684 .self-update-panel-toggle {
685 width: 100%;
686 display: flex;
687 align-items: center;
688 justify-content: space-between;
689 gap: 0.75rem;
690 padding: 0.9rem 1rem;
691 border: 0;
692 background: transparent;
693 cursor: pointer;
694 font-weight: 700;
695 color: var(--color-text);
696 text-align: left;
697 }
698
699 .self-update-panel-toggle-icon {
700 transition: transform 0.2s ease;
701 }
702
703 .self-update-panel-toggle-trailing {
704 display: inline-flex;
705 align-items: center;
706 gap: 0.75rem;
707 flex: 0 0 auto;
708 }
709
710 .self-update-panel-toggle[aria-expanded="true"] .self-update-panel-toggle-icon {
711 transform: rotate(180deg);
712 }
713
714 .self-update-panel-body {
715 padding: 0 1rem 1rem;
716 }
717
718 .self-update-progress-state {
719 display: flex;
720 align-items: center;
721 gap: 0.9rem;
722 padding: 1rem 1.1rem;
723 border-radius: 0.9rem;
724 background: rgba(37, 99, 235, 0.08);
725 color: var(--color-text);
726 }
727
728 .self-update-progress-spinner {
729 width: 1.5rem;
730 height: 1.5rem;
731 flex: 0 0 auto;
732 border-radius: 999px;
733 border: 3px solid rgba(37, 99, 235, 0.18);
734 border-top-color: var(--color-primary, #2563eb);
735 animation: spin 1s linear infinite;
736 }
737
738 .self-update-progress-title {
739 font-weight: 700;
740 margin-bottom: 0.25rem;
741 }
742
743 .self-update-progress-copy {
744 color: var(--color-text-muted);
745 line-height: 1.45;
746 }
747
748 .self-update-warning-banner {
749 display: flex;
750 flex-wrap: wrap;
751 align-items: flex-start;
752 justify-content: space-between;
753 gap: 0.75rem;
754 padding: 0.85rem 0.95rem;
755 border: 1px solid var(--color-warning-text);
756 border-radius: 0.8rem;
757 background: color-mix(in srgb, var(--color-warning-text) 12%, transparent);
758 color: var(--color-warning-text);
759 line-height: 1.5;
760 }
761
762 .self-update-warning-banner a {
763 color: inherit;
764 font-weight: 700;
765 text-decoration: underline;
766 }
767
768 .self-update-version-grid {
769 display: grid;
770 gap: 0.75rem;
771 grid-template-columns: repeat(2, minmax(0, 1fr));
772 }
773
774 .self-update-summary-grid {
775 display: grid;
776 gap: 0.75rem;
777 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
778 }
779
780 .self-update-summary-card {
781 border: 1px solid var(--color-border);
782 border-radius: 0.9rem;
783 padding: 0.9rem 1rem;
784 background: var(--color-panel);
785 }
786
787 .summary-label {
788 font-size: 0.8rem;
789 text-transform: uppercase;
790 letter-spacing: 0.04em;
791 color: var(--color-text-muted);
792 }
793
794 .summary-value {
795 font-size: 1.1rem;
796 font-weight: 600;
797 margin-top: 0.35rem;
798 }
799
800 .summary-meta,
801 .status-path {
802 margin-top: 0.35rem;
803 color: var(--color-text-muted);
804 font-size: 0.9rem;
805 word-break: break-word;
806 }
807
808 .self-update-warning,
809 .self-update-error {
810 padding: 0.8rem 0.9rem;
811 border-radius: 8px;
812 }
813
814 .self-update-warning {
815 background: var(--color-warning-bg);
816 color: var(--color-warning);
817 }
818
819 .self-update-error {
820 background: var(--color-error-bg);
821 color: var(--color-error);
822 }
823
824 .self-update-file-hint {
825 color: var(--color-text-muted);
826 line-height: 1.5;
827 font-size: 0.95rem;
828 }
829
830 .status-pill {
831 display: inline-flex;
832 align-items: center;
833 border: 1px solid var(--color-border);
834 border-radius: 999px;
835 padding: 0.2rem 0.55rem;
836 margin-top: 0.35rem;
837 font-size: 0.82rem;
838 text-transform: uppercase;
839 letter-spacing: 0.04em;
840 }
841
842 .self-update-header-status {
843 margin-top: 0;
844 }
845
846 .status-pill-success {
847 border-color: color-mix(in srgb, var(--color-success, #16a34a) 55%, var(--color-border));
848 background: color-mix(in srgb, var(--color-success, #16a34a) 18%, transparent);
849 color: var(--color-success, #16a34a);
850 }
851
852 .status-pill-info {
853 border-color: color-mix(in srgb, var(--color-primary, #2563eb) 55%, var(--color-border));
854 background: color-mix(in srgb, var(--color-primary, #2563eb) 16%, transparent);
855 color: var(--color-primary, #2563eb);
856 }
857
858 .status-pill-error {
859 border-color: color-mix(in srgb, var(--color-error, #dc2626) 55%, var(--color-border));
860 background: color-mix(in srgb, var(--color-error, #dc2626) 18%, transparent);
861 color: var(--color-error, #dc2626);
862 }
863
864 .status-pill-warning {
865 border-color: color-mix(in srgb, var(--color-warning, #d97706) 55%, var(--color-border));
866 background: color-mix(in srgb, var(--color-warning, #d97706) 18%, transparent);
867 color: var(--color-warning, #d97706);
868 }
869
870 .status-pill-neutral {
871 border-color: var(--color-border);
872 background: transparent;
873 color: var(--color-text);
874 }
875
876 .status-message {
877 margin-top: 0.7rem;
878 line-height: 1.5;
879 }
880
881 @media (max-width: 720px) {
882 .self-update-quick-grid {
883 grid-template-columns: minmax(0, 1fr);
884 }
885
886 .self-update-quick-arrow {
887 transform: rotate(90deg);
888 }
889
890 .self-update-version-grid {
891 grid-template-columns: minmax(0, 1fr);
892 }
893 }
894 </style>
895 </body>
896 </html>