Merge claude/1
This commit is contained in:
commit
6b07d12bdf
@ -794,6 +794,12 @@
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
/* Enable/disable toggle in a location row header — sits between the row
|
||||
info and the expand chevron, controlling the location without expanding it. */
|
||||
.backup-loc-enable-toggle {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* Repo card extras */
|
||||
.backup-repo-stats {
|
||||
display: flex;
|
||||
|
||||
@ -52,14 +52,14 @@ const BACKUP_LOC_FIELD_DEFS = {
|
||||
};
|
||||
|
||||
const BACKUP_LOC_FIELDS_BY_TYPE = {
|
||||
local: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'PATH_MODE', 'PATH', 'APPEND_ONLY'],
|
||||
sftp: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'SSH_USER', 'SSH_HOST', 'SSH_PORT', 'SSH_PATH', 'SSH_AUTH', 'SSH_PASS', 'URI', 'APPEND_ONLY'],
|
||||
rest: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'],
|
||||
s3: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'S3_ACCESS_KEY', 'S3_SECRET_KEY', 'APPEND_ONLY'],
|
||||
b2: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'B2_ACCOUNT_ID', 'B2_ACCOUNT_KEY', 'APPEND_ONLY'],
|
||||
gs: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'],
|
||||
azure: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'],
|
||||
rclone: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY']
|
||||
local: ['NAME', 'ENGINE', 'TYPE', 'PATH_MODE', 'PATH', 'APPEND_ONLY'],
|
||||
sftp: ['NAME', 'ENGINE', 'TYPE', 'SSH_USER', 'SSH_HOST', 'SSH_PORT', 'SSH_PATH', 'SSH_AUTH', 'SSH_PASS', 'URI', 'APPEND_ONLY'],
|
||||
rest: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'],
|
||||
s3: ['NAME', 'ENGINE', 'TYPE', 'URI', 'S3_ACCESS_KEY', 'S3_SECRET_KEY', 'APPEND_ONLY'],
|
||||
b2: ['NAME', 'ENGINE', 'TYPE', 'URI', 'B2_ACCOUNT_ID', 'B2_ACCOUNT_KEY', 'APPEND_ONLY'],
|
||||
gs: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'],
|
||||
azure: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'],
|
||||
rclone: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY']
|
||||
};
|
||||
|
||||
function backupRetentionDetectPreset(values, includeInherit = false) {
|
||||
@ -164,6 +164,13 @@ class BackupPage {
|
||||
return;
|
||||
}
|
||||
|
||||
const locEnable = e.target.closest('[data-action="toggle-location-enabled"]');
|
||||
if (locEnable) {
|
||||
const cb = locEnable.querySelector('input[type="checkbox"]');
|
||||
this.setLocationEnabled(parseInt(locEnable.dataset.loc, 10), cb ? cb.checked : true);
|
||||
return;
|
||||
}
|
||||
|
||||
const locHeader = e.target.closest('[data-action="toggle-location"]');
|
||||
if (locHeader) {
|
||||
this.toggleLocationExpand(parseInt(locHeader.dataset.loc, 10));
|
||||
@ -546,6 +553,10 @@ class BackupPage {
|
||||
<span class="backup-location-row-sep">·</span>
|
||||
<span class="backup-location-row-stat">${size}</span>
|
||||
</div>
|
||||
<span class="backup-toggle backup-loc-enable-toggle" data-action="toggle-location-enabled" data-loc="${l.idx}" title="${l.enabled ? 'Enabled — click to disable this location' : 'Disabled — click to enable this location'}">
|
||||
<input type="checkbox" ${l.enabled ? 'checked' : ''} aria-label="Enable this location">
|
||||
<span class="backup-toggle-slider"></span>
|
||||
</span>
|
||||
<svg class="backup-location-chevron" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
@ -585,16 +596,31 @@ class BackupPage {
|
||||
|
||||
return `
|
||||
<div class="config-category backup-location-config" data-section="location-${idx}">
|
||||
<h3>Connection</h3>
|
||||
<div class="backup-location-connection-fields" id="backup-location-${idx}-connection">
|
||||
${this.renderLocFields(idx, connectionFields, l)}
|
||||
<div class="domains-wrapper">
|
||||
<div class="domains-header">
|
||||
<div>
|
||||
<h3>Connection</h3>
|
||||
<p class="category-description">How LibrePortal connects to this storage location.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="domains-divider"></div>
|
||||
<div class="backup-location-connection-fields" id="backup-location-${idx}-connection">
|
||||
${this.renderLocFields(idx, connectionFields, l)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-category backup-location-config">
|
||||
<h3>Retention</h3>
|
||||
<p class="category-description">When to delete old backups from this location.</p>
|
||||
<div id="backup-location-${idx}-retention">
|
||||
${this.formRetention(`CFG_BACKUP_LOC_${idx}_`, retentionValues, true)}
|
||||
<div class="domains-wrapper">
|
||||
<div class="domains-header">
|
||||
<div>
|
||||
<h3>Retention</h3>
|
||||
<p class="category-description">When to delete old backups from this location.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="domains-divider"></div>
|
||||
<div id="backup-location-${idx}-retention">
|
||||
${this.formRetention(`CFG_BACKUP_LOC_${idx}_`, retentionValues, true)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="backup-location-actions">
|
||||
@ -964,7 +990,6 @@ class BackupPage {
|
||||
"Custom…" is selected. */
|
||||
formRetention(prefix, values, includeInherit = false) {
|
||||
const preset = backupRetentionDetectPreset(values, includeInherit);
|
||||
const meta = BACKUP_RETENTION_PRESET_META[preset];
|
||||
const presetOptions = Object.entries(BACKUP_RETENTION_PRESET_META)
|
||||
.filter(([k]) => k !== 'inherit-global' || includeInherit)
|
||||
.map(([k, v]) => `<option value="${k}" ${k === preset ? 'selected' : ''}>${this.escape(v.label)}</option>`)
|
||||
@ -977,10 +1002,9 @@ class BackupPage {
|
||||
return `
|
||||
<div class="backup-form-grid backup-retention-block" data-retention-prefix="${this.escape(prefix)}" data-retention-allow-inherit="${includeInherit ? '1' : '0'}">
|
||||
<label class="backup-form-row">
|
||||
<span class="backup-form-label">Backup style</span>
|
||||
<span class="backup-form-label">Backup style <span class="tooltip" title="Use whatever the Configuration tab specifies. Pick something else here only when this location needs a different policy.">ℹ️</span></span>
|
||||
<select class="form-control" data-retention-preset>${presetOptions}</select>
|
||||
</label>
|
||||
<div class="backup-retention-hint backup-card-hint" data-retention-hint>${this.escape(meta?.hint || '')}</div>
|
||||
${customRetentionHidden}
|
||||
</div>
|
||||
<div class="backup-retention-advanced" data-retention-advanced ${preset === 'custom' ? '' : 'hidden'}>
|
||||
@ -1002,8 +1026,6 @@ class BackupPage {
|
||||
const prefix = block.dataset.retentionPrefix;
|
||||
const allowInherit = block.dataset.retentionAllowInherit === '1';
|
||||
const preset = selectEl.value;
|
||||
const hintEl = block.querySelector('[data-retention-hint]');
|
||||
if (hintEl) hintEl.textContent = BACKUP_RETENTION_PRESET_META[preset]?.hint || '';
|
||||
|
||||
if (preset === 'custom') {
|
||||
if (advanced) advanced.hidden = false;
|
||||
@ -1437,6 +1459,20 @@ class BackupPage {
|
||||
|
||||
/* ----- Generic save handler ----- */
|
||||
|
||||
async setLocationEnabled(idx, enabled) {
|
||||
const encoded = `CFG_BACKUP_LOC_${idx}_ENABLED=${enabled ? 'true' : 'false'}`;
|
||||
try {
|
||||
if (!window.tasksManager?.router) throw new Error('Task system not available');
|
||||
await window.tasksManager.router.routeAction('config_update', {
|
||||
changes: `'${encoded.replace(/'/g, "'\\''")}'`
|
||||
});
|
||||
this.notify(`${enabled ? 'Enabling' : 'Disabling'} this location…`, 'success');
|
||||
setTimeout(() => this.reloadAfterSave(), 2500);
|
||||
} catch (err) {
|
||||
this.notify(`Save failed: ${err.message || err}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async saveSection(sectionId) {
|
||||
let scope;
|
||||
if (sectionId.startsWith('location-')) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user