Merge claude/1
This commit is contained in:
commit
f6fecd023a
@ -1322,6 +1322,83 @@
|
|||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
color: rgba(var(--text-rgb), 0.7);
|
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 {
|
.backup-snapshot-overflow {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
|
|||||||
@ -91,6 +91,14 @@ class BackupAppCard {
|
|||||||
|
|
||||||
_renderRow(s, iconUrl) {
|
_renderRow(s, iconUrl) {
|
||||||
const sid = String(s.id || '');
|
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 `
|
return `
|
||||||
<div class="task-item backup-snapshot-item" data-snapshot="${this.escape(sid)}" data-loc="${this.escape(String(s.locIdx))}">
|
<div class="task-item backup-snapshot-item" data-snapshot="${this.escape(sid)}" data-loc="${this.escape(String(s.locIdx))}">
|
||||||
<div class="task-header">
|
<div class="task-header">
|
||||||
@ -113,13 +121,24 @@ class BackupAppCard {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-details">
|
<div class="task-details">
|
||||||
<div class="task-meta">
|
<div class="backup-snapshot-meta">
|
||||||
<div class="meta-item"><strong>Backup ID:</strong> <code>${this.escape(sid)}</code></div>
|
<div class="bsm-grid">
|
||||||
<div class="meta-item"><strong>Location:</strong> ${this.escape(s.locName)}</div>
|
${s.hostname ? field('Host', this.escape(s.hostname)) : ''}
|
||||||
<div class="meta-item"><strong>When:</strong> ${this.escape(this._fmtFull(s.time))}</div>
|
${field('Location', `<span class="backup-snapshot-loc-pill">${this.escape(s.locName)}</span>`)}
|
||||||
${s.hostname ? `<div class="meta-item"><strong>Host:</strong> ${this.escape(s.hostname)}</div>` : ''}
|
${field('Backup ID', `<code>${this.escape(sid)}</code>`)}
|
||||||
${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>` : ''}
|
${field('When', `<span title="${this.escape(this._fmtFull(s.time))}">${this.escape(this._fmtNice(s.time))}</span>`)}
|
||||||
${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>` : ''}
|
${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>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@ -151,6 +170,12 @@ class BackupAppCard {
|
|||||||
if (isNaN(d.getTime())) return iso;
|
if (isNaN(d.getTime())) return iso;
|
||||||
return d.toString();
|
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() {
|
async loadData() {
|
||||||
const ts = Date.now();
|
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>`
|
? `<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>`;
|
: `<span class="backup-snapshot-app-chip">${this.escape(displayName)}</span>`;
|
||||||
const sid = String(r.id);
|
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 `
|
return `
|
||||||
<div class="task-item backup-snapshot-item" data-snapshot="${this.escape(sid)}" data-loc="${this.escape(String(r.locIdx))}">
|
<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">
|
<div class="task-header" data-action="toggle-snapshot-row">
|
||||||
@ -1181,14 +1191,25 @@ class BackupPage {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-details">
|
<div class="task-details">
|
||||||
<div class="task-meta">
|
<div class="backup-snapshot-meta">
|
||||||
<div class="meta-item"><strong>App:</strong> ${this.escape(displayName)}${hasApp ? ` (<code>${this.escape(r.app)}</code>)` : ''}</div>
|
<div class="bsm-grid">
|
||||||
<div class="meta-item"><strong>Backup ID:</strong> <code>${this.escape(sid)}</code></div>
|
${field('App', `${this.escape(displayName)}${hasApp ? ` <code>${this.escape(r.app)}</code>` : ''}`)}
|
||||||
<div class="meta-item"><strong>Location:</strong> ${this.escape(r.locName)}</div>
|
${field('Host', this.escape(r.host))}
|
||||||
<div class="meta-item"><strong>When:</strong> ${this.escape(this._fmtFullTime(r.time))}</div>
|
${field('Location', `<span class="backup-snapshot-loc-pill">${this.escape(r.locName)}</span>`)}
|
||||||
<div class="meta-item"><strong>Host:</strong> ${this.escape(r.host)}</div>
|
${field('Backup ID', `<code>${this.escape(sid)}</code>`)}
|
||||||
${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>` : ''}
|
${field('When', `<span title="${this.escape(this._fmtFullTime(r.time))}">${this.escape(this._fmtNiceTime(r.time))}</span>`)}
|
||||||
${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>` : ''}
|
${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>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@ -1206,6 +1227,12 @@ class BackupPage {
|
|||||||
if (isNaN(d.getTime())) return String(iso);
|
if (isNaN(d.getTime())) return String(iso);
|
||||||
return d.toString();
|
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() {
|
renderConfiguration() {
|
||||||
const body = document.getElementById('backup-configuration-body');
|
const body = document.getElementById('backup-configuration-body');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user