mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-04-28 11:40:47 +00:00
- Add _get_remote_branch_names helper to fetch available branches via git ls-remote with caching - Add _get_local_origin_branch_names fallback for offline scenarios - Add get_available_branch_values and get_available_branches to expose filtered branch list - Add _is_excluded_self_update_branch helper to filter out HEAD, PR branches - Add _sort_branch_names to deduplicate and sort branches with main first - Add
593 lines
23 KiB
HTML
593 lines
23 KiB
HTML
<html>
|
|
<head>
|
|
<title>Self Update</title>
|
|
<script type="module">
|
|
import { store } from "/components/settings/external/self-update-store.js";
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
<div x-data>
|
|
<template x-if="$store.selfUpdateStore">
|
|
<div
|
|
x-init="$store.selfUpdateStore.init()"
|
|
x-destroy="$store.selfUpdateStore.cleanup()"
|
|
class="self-update-modal"
|
|
>
|
|
<div class="self-update-version-grid">
|
|
<div class="self-update-summary-card">
|
|
<div class="summary-label">Current version</div>
|
|
<div class="summary-value" x-text="$store.selfUpdateStore.currentVersion"></div>
|
|
<div class="summary-meta">
|
|
Commit
|
|
<code x-text="$store.selfUpdateStore.info?.current?.short_commit || 'unknown'"></code>
|
|
</div>
|
|
<div class="summary-meta" x-text="`Branch ${$store.selfUpdateStore.currentBranch || 'unknown'}`"></div>
|
|
</div>
|
|
|
|
<div class="self-update-summary-card">
|
|
<div class="summary-label">Latest version</div>
|
|
<div
|
|
class="summary-value"
|
|
x-text="$store.selfUpdateStore.info?.current_branch_latest?.display_version || 'Unavailable'"
|
|
></div>
|
|
<template x-if="$store.selfUpdateStore.info?.current_branch_latest?.short_commit && $store.selfUpdateStore.info?.current_branch_latest?.branch !== 'main'">
|
|
<div class="summary-meta">
|
|
Commit
|
|
<code x-text="$store.selfUpdateStore.info?.current_branch_latest?.short_commit"></code>
|
|
</div>
|
|
</template>
|
|
<div
|
|
class="summary-meta"
|
|
x-text="`Branch ${$store.selfUpdateStore.info?.current_branch_latest?.branch || $store.selfUpdateStore.currentBranch || 'unknown'}`"
|
|
></div>
|
|
<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">
|
|
<div
|
|
class="summary-meta"
|
|
x-text="$store.selfUpdateStore.info?.current_branch_latest?.describe"
|
|
></div>
|
|
</template>
|
|
<template x-if="$store.selfUpdateStore.info?.current_branch_latest?.supported === false">
|
|
<div class="summary-meta">
|
|
Latest official version is only tracked for <code>main</code>, <code>testing</code>,
|
|
and <code>development</code>.
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="self-update-summary-grid" x-show="$store.selfUpdateStore.info?.pending">
|
|
<div class="self-update-summary-card">
|
|
<div class="summary-label">Pending request</div>
|
|
<div
|
|
class="summary-value"
|
|
x-text="$store.selfUpdateStore.formatBranchTag($store.selfUpdateStore.info?.pending?.branch, $store.selfUpdateStore.info?.pending?.tag)"
|
|
></div>
|
|
<div
|
|
class="summary-meta"
|
|
x-text="$store.selfUpdateStore.formatTimestamp($store.selfUpdateStore.info?.pending?.requested_at)"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
<template x-if="!$store.selfUpdateStore.isSupported">
|
|
<div class="self-update-warning">
|
|
Self-update is currently available only in dockerized Agent Zero deployments
|
|
that boot through <code>/exe/run_A0.sh</code>.
|
|
</div>
|
|
</template>
|
|
|
|
<div class="self-update-panel">
|
|
<button
|
|
type="button"
|
|
class="self-update-panel-toggle"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#self-update-howto-collapse"
|
|
aria-expanded="false"
|
|
aria-controls="self-update-howto-collapse"
|
|
>
|
|
<span>How it works?</span>
|
|
<span class="material-symbols-outlined self-update-panel-toggle-icon">expand_more</span>
|
|
</button>
|
|
<div class="collapse" id="self-update-howto-collapse">
|
|
<div class="self-update-panel-body self-update-copy">
|
|
<p>
|
|
Agent Zero saves this request into
|
|
<code x-text="$store.selfUpdateStore.info?.paths?.update_file || '/exe/a0-self-update.yaml'"></code>,
|
|
restarts once, applies the requested branch and version target before the UI
|
|
starts again, then reloads this page when <code>/api/health</code> is healthy.
|
|
</p>
|
|
<p>
|
|
If the updated UI does not become healthy within 2 minutes, the bootstrap
|
|
manager in <code>/exe</code> restores the previous checkout and starts that
|
|
version again, so even an older downgraded <code>/a0</code> can be upgraded back
|
|
by creating the YAML file manually.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<template x-if="$store.selfUpdateStore.info?.last_status">
|
|
<div class="self-update-panel">
|
|
<button
|
|
type="button"
|
|
class="self-update-panel-toggle"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#self-update-last-attempt-collapse"
|
|
aria-expanded="false"
|
|
aria-controls="self-update-last-attempt-collapse"
|
|
>
|
|
<span>Last Attempt</span>
|
|
<span class="self-update-panel-toggle-trailing">
|
|
<span
|
|
class="status-pill self-update-header-status"
|
|
:class="$store.selfUpdateStore.getLastStatusBadgeClass($store.selfUpdateStore.info?.last_status?.status)"
|
|
x-text="$store.selfUpdateStore.getLastStatusLabel($store.selfUpdateStore.info?.last_status?.status)"
|
|
></span>
|
|
<span class="material-symbols-outlined self-update-panel-toggle-icon">expand_more</span>
|
|
</span>
|
|
</button>
|
|
<div class="collapse" id="self-update-last-attempt-collapse">
|
|
<div class="self-update-panel-body">
|
|
<div class="status-message" x-text="$store.selfUpdateStore.info?.last_status?.message || ''"></div>
|
|
<div class="summary-meta">
|
|
Trigger:
|
|
<code x-text="$store.selfUpdateStore.info?.paths?.update_file || '/exe/a0-self-update.yaml'"></code>
|
|
</div>
|
|
<div class="summary-meta">
|
|
Log:
|
|
<code x-text="$store.selfUpdateStore.info?.paths?.log_file || '/exe/a0-self-update.log'"></code>
|
|
</div>
|
|
<div
|
|
class="summary-meta"
|
|
x-text="$store.selfUpdateStore.formatTimestamp($store.selfUpdateStore.info?.last_status?.finished_at)"
|
|
></div>
|
|
<template x-if="$store.selfUpdateStore.info?.last_status?.backup_zip_path">
|
|
<div class="status-path">
|
|
Backup:
|
|
<code x-text="$store.selfUpdateStore.info?.last_status?.backup_zip_path"></code>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template x-if="$store.selfUpdateStore.isSupported">
|
|
<div>
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Target branch</div>
|
|
<div class="field-description">
|
|
Choose which official branch context should be used when resolving the requested tag.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<select
|
|
x-model="$store.selfUpdateStore.form.branch"
|
|
x-effect="$nextTick(() => { $el.value = $store.selfUpdateStore.form.branch || 'main'; })"
|
|
@change="$store.selfUpdateStore.onBranchChanged()"
|
|
:disabled="$store.selfUpdateStore.isBusy"
|
|
>
|
|
<template x-for="branch in ($store.selfUpdateStore.info?.branches || [])" :key="branch.value">
|
|
<option :value="branch.value" x-text="branch.label"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Version</div>
|
|
<div class="field-description">
|
|
Choose a preloaded version target from the
|
|
<a href="https://github.com/agent0ai/agent-zero" target="_blank" rel="noreferrer">Agent Zero repository</a>.
|
|
Only versions from the current major release line are listed here. Newer major lines require a Docker image update first.
|
|
</div>
|
|
<div class="field-description">
|
|
<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>.
|
|
</div>
|
|
<template x-if="$store.selfUpdateStore.tagsError">
|
|
<div class="field-description">
|
|
Version lookup failed:
|
|
<span x-text="$store.selfUpdateStore.tagsError"></span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<template x-if="$store.selfUpdateStore.higherMajorVersionMessage">
|
|
<div class="self-update-warning-banner">
|
|
<div x-text="$store.selfUpdateStore.higherMajorVersionMessage"></div>
|
|
<a
|
|
href="https://www.agent-zero.ai/p/docs/get-started/"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
Docker update guide
|
|
</a>
|
|
</div>
|
|
</template>
|
|
<div class="field-control">
|
|
<select
|
|
x-model="$store.selfUpdateStore.form.tag"
|
|
:disabled="$store.selfUpdateStore.isBusy || $store.selfUpdateStore.tagsLoading || !$store.selfUpdateStore.hasAvailableTags"
|
|
>
|
|
<option value="" x-text="$store.selfUpdateStore.versionSelectPlaceholder"></option>
|
|
<template x-for="tagOption in $store.selfUpdateStore.availableTagOptions" :key="tagOption.value">
|
|
<option :value="tagOption.value" x-text="tagOption.label"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Back up <code>/a0/usr</code> first</div>
|
|
<div class="field-description">
|
|
Creates a zip backup before the release files are replaced.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<label class="toggle">
|
|
<input
|
|
type="checkbox"
|
|
x-model="$store.selfUpdateStore.form.backup_usr"
|
|
:disabled="$store.selfUpdateStore.isBusy"
|
|
/>
|
|
<span class="toggler"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div x-show="$store.selfUpdateStore.form.backup_usr">
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Backup directory</div>
|
|
<div class="field-description">
|
|
Absolute or repo-relative path where the <code>usr</code> zip should be written.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input
|
|
type="text"
|
|
x-model="$store.selfUpdateStore.form.backup_path"
|
|
:disabled="$store.selfUpdateStore.isBusy"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Manual backup</div>
|
|
<div class="field-description">
|
|
Open the existing backup and restore modal if you want to create a backup before scheduling the update.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<button
|
|
type="button"
|
|
class="btn btn-field"
|
|
@click="$store.selfUpdateStore.openManualBackupModal()"
|
|
:disabled="$store.selfUpdateStore.isBusy"
|
|
>
|
|
Manual backup
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">Backup filename</div>
|
|
<div class="field-description">
|
|
The manager normalizes this into a safe <code>.zip</code> filename. Leave it as-is for the default <code>usr-timestamp.zip</code> format.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<input
|
|
type="text"
|
|
x-model="$store.selfUpdateStore.form.backup_name"
|
|
:disabled="$store.selfUpdateStore.isBusy"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<div class="field-label">
|
|
<div class="field-title">If the filename already exists</div>
|
|
<div class="field-description">
|
|
Choose whether to rename the backup, replace it, or stop the update.
|
|
</div>
|
|
</div>
|
|
<div class="field-control">
|
|
<select
|
|
x-model="$store.selfUpdateStore.form.backup_conflict_policy"
|
|
:disabled="$store.selfUpdateStore.isBusy"
|
|
>
|
|
<option value="rename">Rename with suffix</option>
|
|
<option value="overwrite">Overwrite existing zip</option>
|
|
<option value="fail">Fail before restart</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="self-update-file-hint">
|
|
The durable trigger, status, and log files live outside <code>/a0</code>:
|
|
<code x-text="$store.selfUpdateStore.info?.paths?.update_file || '/exe/a0-self-update.yaml'"></code>,
|
|
<code x-text="$store.selfUpdateStore.info?.paths?.status_file || '/exe/a0-self-update-status.yaml'"></code>,
|
|
<code x-text="$store.selfUpdateStore.info?.paths?.log_file || '/exe/a0-self-update.log'"></code>.
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="self-update-error" x-show="$store.selfUpdateStore.error">
|
|
<span x-text="$store.selfUpdateStore.error"></span>
|
|
</div>
|
|
|
|
<div class="self-update-loading" x-show="$store.selfUpdateStore.loading">
|
|
Loading update status...
|
|
</div>
|
|
<div class="self-update-progress-state" x-show="$store.selfUpdateStore.restarting">
|
|
<div class="self-update-progress-spinner"></div>
|
|
<div>
|
|
<div class="self-update-progress-title" x-text="$store.selfUpdateStore.restartStatusText || 'Update in progress'"></div>
|
|
<div class="self-update-progress-copy" x-text="$store.selfUpdateStore.restartDetailText || 'Waiting for Agent Zero to restart and become healthy again.'"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer" data-modal-footer>
|
|
<button
|
|
type="button"
|
|
class="btn btn-ok"
|
|
@click="$store.selfUpdateStore.scheduleUpdate()"
|
|
:disabled="!$store.selfUpdateStore.canScheduleUpdate"
|
|
>
|
|
Schedule Update And Restart
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-field"
|
|
@click="$store.selfUpdateStore.refresh()"
|
|
:disabled="$store.selfUpdateStore.isBusy"
|
|
>
|
|
Refresh Status
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-cancel"
|
|
@click="$store.selfUpdateStore.close()"
|
|
:disabled="$store.selfUpdateStore.restarting"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<style>
|
|
.self-update-modal {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.self-update-copy {
|
|
color: var(--color-text-muted);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.self-update-copy p {
|
|
margin: 0 0 0.75rem;
|
|
}
|
|
|
|
.self-update-panel {
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 0.9rem;
|
|
background: var(--color-panel);
|
|
}
|
|
|
|
.self-update-panel-toggle {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
padding: 0.9rem 1rem;
|
|
border: 0;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
font-weight: 700;
|
|
color: var(--color-text);
|
|
text-align: left;
|
|
}
|
|
|
|
.self-update-panel-toggle-icon {
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.self-update-panel-toggle-trailing {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.self-update-panel-toggle[aria-expanded="true"] .self-update-panel-toggle-icon {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.self-update-panel-body {
|
|
padding: 0 1rem 1rem;
|
|
}
|
|
|
|
.self-update-progress-state {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.9rem;
|
|
padding: 1rem 1.1rem;
|
|
border-radius: 0.9rem;
|
|
background: rgba(37, 99, 235, 0.08);
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.self-update-progress-spinner {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
flex: 0 0 auto;
|
|
border-radius: 999px;
|
|
border: 3px solid rgba(37, 99, 235, 0.18);
|
|
border-top-color: var(--color-primary, #2563eb);
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
.self-update-progress-title {
|
|
font-weight: 700;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.self-update-progress-copy {
|
|
color: var(--color-text-muted);
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.self-update-warning-banner {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
padding: 0.85rem 0.95rem;
|
|
border: 1px solid var(--color-warning-text);
|
|
border-radius: 0.8rem;
|
|
background: color-mix(in srgb, var(--color-warning-text) 12%, transparent);
|
|
color: var(--color-warning-text);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.self-update-warning-banner a {
|
|
color: inherit;
|
|
font-weight: 700;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.self-update-version-grid {
|
|
display: grid;
|
|
gap: 0.75rem;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
|
|
.self-update-summary-grid {
|
|
display: grid;
|
|
gap: 0.75rem;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
}
|
|
|
|
.self-update-summary-card {
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 0.9rem;
|
|
padding: 0.9rem 1rem;
|
|
background: var(--color-panel);
|
|
}
|
|
|
|
.summary-label {
|
|
font-size: 0.8rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
.summary-value {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
margin-top: 0.35rem;
|
|
}
|
|
|
|
.summary-meta,
|
|
.status-path {
|
|
margin-top: 0.35rem;
|
|
color: var(--color-text-muted);
|
|
font-size: 0.9rem;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.self-update-warning,
|
|
.self-update-error,
|
|
.self-update-loading {
|
|
padding: 0.8rem 0.9rem;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.self-update-warning {
|
|
background: var(--color-warning-bg);
|
|
color: var(--color-warning);
|
|
}
|
|
|
|
.self-update-error {
|
|
background: var(--color-error-bg);
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.self-update-loading {
|
|
background: var(--color-panel);
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
.self-update-file-hint {
|
|
color: var(--color-text-muted);
|
|
line-height: 1.5;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.status-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 999px;
|
|
padding: 0.2rem 0.55rem;
|
|
margin-top: 0.35rem;
|
|
font-size: 0.82rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
.self-update-header-status {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.status-pill-success {
|
|
border-color: color-mix(in srgb, var(--color-success, #16a34a) 55%, var(--color-border));
|
|
background: color-mix(in srgb, var(--color-success, #16a34a) 18%, transparent);
|
|
color: var(--color-success, #16a34a);
|
|
}
|
|
|
|
.status-pill-error {
|
|
border-color: color-mix(in srgb, var(--color-error, #dc2626) 55%, var(--color-border));
|
|
background: color-mix(in srgb, var(--color-error, #dc2626) 18%, transparent);
|
|
color: var(--color-error, #dc2626);
|
|
}
|
|
|
|
.status-pill-warning {
|
|
border-color: color-mix(in srgb, var(--color-warning, #d97706) 55%, var(--color-border));
|
|
background: color-mix(in srgb, var(--color-warning, #d97706) 18%, transparent);
|
|
color: var(--color-warning, #d97706);
|
|
}
|
|
|
|
.status-pill-neutral {
|
|
border-color: var(--color-border);
|
|
background: transparent;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.status-message {
|
|
margin-top: 0.7rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
@media (max-width: 720px) {
|
|
.self-update-version-grid {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
}
|
|
</style>
|
|
</body>
|
|
</html>
|