Merge claude/1
This commit is contained in:
commit
f6fecd023a
@ -1322,6 +1322,83 @@
|
||||
font-size: 0.72rem;
|
||||
color: rgba(var(--text-rgb), 0.7);
|
||||
}
|
||||
/* Snapshot detail panel. The shared .task-meta/.meta-item layout forces
|
||||
one nowrap line per item and clips long values (the full date, repo
|
||||
paths) mid-string, so the backup row gets its own label-over-value grid
|
||||
plus full-width blocks for tags and paths that wrap cleanly. */
|
||||
.backup-snapshot-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
.backup-snapshot-meta .bsm-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 14px 18px;
|
||||
}
|
||||
.backup-snapshot-meta .bsm-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
.backup-snapshot-meta .bsm-label {
|
||||
font-size: 0.68rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
color: rgba(var(--text-rgb), 0.45);
|
||||
}
|
||||
.backup-snapshot-meta .bsm-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.backup-snapshot-meta .bsm-value code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.78rem;
|
||||
background: rgba(var(--text-rgb), 0.06);
|
||||
color: rgba(var(--text-rgb), 0.82);
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.backup-snapshot-meta .bsm-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding-top: 13px;
|
||||
border-top: 1px solid rgba(var(--text-rgb), 0.08);
|
||||
}
|
||||
.backup-snapshot-meta .bsm-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
.backup-snapshot-meta .bsm-paths {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.backup-snapshot-meta .bsm-paths code {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.78rem;
|
||||
background: rgba(var(--text-rgb), 0.06);
|
||||
color: rgba(var(--text-rgb), 0.82);
|
||||
padding: 4px 9px;
|
||||
border-radius: 5px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.backup-snapshot-overflow {
|
||||
margin-top: 10px;
|
||||
font-size: 0.78rem;
|
||||
|
||||
@ -91,6 +91,14 @@ class BackupAppCard {
|
||||
|
||||
_renderRow(s, iconUrl) {
|
||||
const sid = String(s.id || '');
|
||||
|
||||
const tagMap = {};
|
||||
(s.tags || []).forEach(t => { const i = t.indexOf('='); if (i > 0) tagMap[t.slice(0, i)] = t.slice(i + 1); });
|
||||
const engineName = tagMap.engine ? tagMap.engine.charAt(0).toUpperCase() + tagMap.engine.slice(1) : null;
|
||||
const otherTags = (s.tags || []).filter(t => !/^(app|host|engine|paths?)=/.test(t));
|
||||
const field = (label, valueHtml) =>
|
||||
`<div class="bsm-field"><span class="bsm-label">${label}</span><span class="bsm-value">${valueHtml}</span></div>`;
|
||||
|
||||
return `
|
||||
<div class="task-item backup-snapshot-item" data-snapshot="${this.escape(sid)}" data-loc="${this.escape(String(s.locIdx))}">
|
||||
<div class="task-header">
|
||||
@ -113,13 +121,24 @@ class BackupAppCard {
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-details">
|
||||
<div class="task-meta">
|
||||
<div class="meta-item"><strong>Backup ID:</strong> <code>${this.escape(sid)}</code></div>
|
||||
<div class="meta-item"><strong>Location:</strong> ${this.escape(s.locName)}</div>
|
||||
<div class="meta-item"><strong>When:</strong> ${this.escape(this._fmtFull(s.time))}</div>
|
||||
${s.hostname ? `<div class="meta-item"><strong>Host:</strong> ${this.escape(s.hostname)}</div>` : ''}
|
||||
${s.tags && s.tags.length ? `<div class="meta-item"><strong>Tags:</strong> ${s.tags.map(t => `<span class="backup-snapshot-tag">${this.escape(t)}</span>`).join(' ')}</div>` : ''}
|
||||
${s.paths && s.paths.length ? `<div class="meta-item"><strong>Paths:</strong><br><code>${s.paths.map(p => this.escape(p)).join('<br>')}</code></div>` : ''}
|
||||
<div class="backup-snapshot-meta">
|
||||
<div class="bsm-grid">
|
||||
${s.hostname ? field('Host', this.escape(s.hostname)) : ''}
|
||||
${field('Location', `<span class="backup-snapshot-loc-pill">${this.escape(s.locName)}</span>`)}
|
||||
${field('Backup ID', `<code>${this.escape(sid)}</code>`)}
|
||||
${field('When', `<span title="${this.escape(this._fmtFull(s.time))}">${this.escape(this._fmtNice(s.time))}</span>`)}
|
||||
${engineName ? field('Engine', this.escape(engineName)) : ''}
|
||||
</div>
|
||||
${otherTags.length ? `
|
||||
<div class="bsm-block">
|
||||
<span class="bsm-label">Tags</span>
|
||||
<div class="bsm-tags">${otherTags.map(t => `<span class="backup-snapshot-tag">${this.escape(t)}</span>`).join('')}</div>
|
||||
</div>` : ''}
|
||||
${s.paths && s.paths.length ? `
|
||||
<div class="bsm-block">
|
||||
<span class="bsm-label">Paths</span>
|
||||
<ul class="bsm-paths">${s.paths.map(p => `<li><code>${this.escape(p)}</code></li>`).join('')}</ul>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@ -151,6 +170,12 @@ class BackupAppCard {
|
||||
if (isNaN(d.getTime())) return iso;
|
||||
return d.toString();
|
||||
}
|
||||
_fmtNice(iso) {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return iso;
|
||||
return d.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
const ts = Date.now();
|
||||
|
||||
@ -1154,6 +1154,16 @@ class BackupPage {
|
||||
? `<a class="backup-snapshot-link backup-snapshot-app-chip" href="${this.escape(deepLink)}" data-deep-link="${this.escape(deepLink)}" title="Open ${this.escape(displayName)} backups">${this.escape(displayName)}</a>`
|
||||
: `<span class="backup-snapshot-app-chip">${this.escape(displayName)}</span>`;
|
||||
const sid = String(r.id);
|
||||
|
||||
// Restic stamps app=/host=/engine= into the snapshot tags; surface
|
||||
// those as their own fields and keep any remaining tags as chips.
|
||||
const tagMap = {};
|
||||
(r.tags || []).forEach(t => { const i = t.indexOf('='); if (i > 0) tagMap[t.slice(0, i)] = t.slice(i + 1); });
|
||||
const engineName = tagMap.engine ? this.engineDisplayName(tagMap.engine) : null;
|
||||
const otherTags = (r.tags || []).filter(t => !/^(app|host|engine|paths?)=/.test(t));
|
||||
const field = (label, valueHtml) =>
|
||||
`<div class="bsm-field"><span class="bsm-label">${label}</span><span class="bsm-value">${valueHtml}</span></div>`;
|
||||
|
||||
return `
|
||||
<div class="task-item backup-snapshot-item" data-snapshot="${this.escape(sid)}" data-loc="${this.escape(String(r.locIdx))}">
|
||||
<div class="task-header" data-action="toggle-snapshot-row">
|
||||
@ -1181,14 +1191,25 @@ class BackupPage {
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-details">
|
||||
<div class="task-meta">
|
||||
<div class="meta-item"><strong>App:</strong> ${this.escape(displayName)}${hasApp ? ` (<code>${this.escape(r.app)}</code>)` : ''}</div>
|
||||
<div class="meta-item"><strong>Backup ID:</strong> <code>${this.escape(sid)}</code></div>
|
||||
<div class="meta-item"><strong>Location:</strong> ${this.escape(r.locName)}</div>
|
||||
<div class="meta-item"><strong>When:</strong> ${this.escape(this._fmtFullTime(r.time))}</div>
|
||||
<div class="meta-item"><strong>Host:</strong> ${this.escape(r.host)}</div>
|
||||
${r.tags && r.tags.length ? `<div class="meta-item"><strong>Tags:</strong> ${r.tags.map(t => `<span class="backup-snapshot-tag">${this.escape(t)}</span>`).join(' ')}</div>` : ''}
|
||||
${r.paths && r.paths.length ? `<div class="meta-item"><strong>Paths:</strong><br><code>${r.paths.map(p => this.escape(p)).join('<br>')}</code></div>` : ''}
|
||||
<div class="backup-snapshot-meta">
|
||||
<div class="bsm-grid">
|
||||
${field('App', `${this.escape(displayName)}${hasApp ? ` <code>${this.escape(r.app)}</code>` : ''}`)}
|
||||
${field('Host', this.escape(r.host))}
|
||||
${field('Location', `<span class="backup-snapshot-loc-pill">${this.escape(r.locName)}</span>`)}
|
||||
${field('Backup ID', `<code>${this.escape(sid)}</code>`)}
|
||||
${field('When', `<span title="${this.escape(this._fmtFullTime(r.time))}">${this.escape(this._fmtNiceTime(r.time))}</span>`)}
|
||||
${engineName ? field('Engine', this.escape(engineName)) : ''}
|
||||
</div>
|
||||
${otherTags.length ? `
|
||||
<div class="bsm-block">
|
||||
<span class="bsm-label">Tags</span>
|
||||
<div class="bsm-tags">${otherTags.map(t => `<span class="backup-snapshot-tag">${this.escape(t)}</span>`).join('')}</div>
|
||||
</div>` : ''}
|
||||
${r.paths && r.paths.length ? `
|
||||
<div class="bsm-block">
|
||||
<span class="bsm-label">Paths</span>
|
||||
<ul class="bsm-paths">${r.paths.map(p => `<li><code>${this.escape(p)}</code></li>`).join('')}</ul>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@ -1206,6 +1227,12 @@ class BackupPage {
|
||||
if (isNaN(d.getTime())) return String(iso);
|
||||
return d.toString();
|
||||
}
|
||||
_fmtNiceTime(iso) {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return String(iso);
|
||||
return d.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });
|
||||
}
|
||||
|
||||
renderConfiguration() {
|
||||
const body = document.getElementById('backup-configuration-body');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user