Faithful prototype-augment split of backup-page.js (2353->753 line base) into fetch-client, dashboard, snapshots, locations, location-fields, ssh-key, retention-presets, configuration, engine-details, location-modal, snapshot-actions, migrate (+ the earlier cron-schedule). Methods relocated verbatim (mechanical sed/awk extraction, no logic change); all augment BackupPage.prototype and load after the base via the ordered kernel loader. Verified: all 99 original methods present exactly once across base+clusters, no duplicates, all 14 files node --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
163 lines
8.0 KiB
JavaScript
163 lines
8.0 KiB
JavaScript
// Auto-extracted from backup-page.js (verbatim) — augments BackupPage.prototype. Loaded after the base.
|
||
Object.assign(BackupPage.prototype, {
|
||
renderLocFields(idx, suffixes, loc) {
|
||
if (typeof ConfigShared === 'undefined' || !ConfigShared.generateField) {
|
||
return `<div class="backup-empty-state">Configuration system not loaded.</div>`;
|
||
}
|
||
const locValueLookup = {
|
||
NAME: loc.name, ENABLED: loc.enabled ? 'true' : 'false', TYPE: loc.type,
|
||
ENGINE: loc.engine || 'restic',
|
||
PATH_MODE: loc.path_mode || 'custom',
|
||
PATH: loc.path, URI: loc.uri, SSH_USER: loc.ssh_user, SSH_HOST: loc.ssh_host,
|
||
SSH_PORT: loc.ssh_port, SSH_PATH: loc.ssh_path,
|
||
SSH_AUTH: loc.ssh_auth || 'key', SSH_PASS: '',
|
||
S3_ACCESS_KEY: '', S3_SECRET_KEY: '',
|
||
B2_ACCOUNT_ID: '', B2_ACCOUNT_KEY: '',
|
||
APPEND_ONLY: loc.append_only ? 'true' : 'false',
|
||
CUSTOM_RETENTION: loc.custom_retention ? 'true' : 'false',
|
||
KEEP_LAST: loc.keep_last, KEEP_DAILY: loc.keep_daily,
|
||
KEEP_WEEKLY: loc.keep_weekly, KEEP_MONTHLY: loc.keep_monthly,
|
||
KEEP_YEARLY: loc.keep_yearly
|
||
};
|
||
|
||
// Field metadata comes from configs.json (window.configData) via
|
||
// locFieldMeta; the basic/advanced split is decided by the caller, which
|
||
// renders each group into its own tab (Connection vs Advanced).
|
||
let html = '<div class="config-fields">';
|
||
for (const suffix of suffixes) {
|
||
const m = this.locFieldMeta(idx, suffix);
|
||
if (!m.exists) continue;
|
||
const value = (locValueLookup[suffix] ?? '').toString();
|
||
html += ConfigShared.generateField(`config-${m.key}`, m.key, value, m.title, m.description, {}, {});
|
||
}
|
||
html += '</div>';
|
||
return html;
|
||
},
|
||
locFieldMeta(idx, suffix) {
|
||
const key = `CFG_BACKUP_LOC_${idx}_${suffix}`;
|
||
const cfg = window.configData?.config?.[key] || {};
|
||
const def = BACKUP_LOC_FIELD_DEFS[suffix] || {};
|
||
return {
|
||
key,
|
||
exists: !!(cfg.title || cfg.description || BACKUP_LOC_FIELD_DEFS[suffix]),
|
||
title: cfg.title || def.title || suffix,
|
||
description: cfg.description ?? def.description ?? '',
|
||
advanced: LOC_ADVANCED_SUFFIXES.has(suffix) || cfg.advanced === true
|
||
};
|
||
},
|
||
locFieldsForType(type) {
|
||
return this.locSchema?.types?.[type]
|
||
|| BACKUP_LOC_FIELDS_BY_TYPE[type]
|
||
|| BACKUP_LOC_FIELDS_BY_TYPE.local;
|
||
},
|
||
locFieldGroups(idx, type) {
|
||
const suffixes = this.locFieldsForType(type);
|
||
const connection = [];
|
||
const advanced = [];
|
||
for (const suffix of suffixes) {
|
||
const m = this.locFieldMeta(idx, suffix);
|
||
if (!m.exists) continue;
|
||
(m.advanced ? advanced : connection).push(suffix);
|
||
}
|
||
return { connection, advanced };
|
||
},
|
||
renderConnectionInner(idx, type, loc, connectionSuffixes) {
|
||
let html = this.renderLocFields(idx, connectionSuffixes, loc);
|
||
if (type === 'sftp') html += this.renderBackupSshKeyCard(loc);
|
||
return html;
|
||
},
|
||
formRetention(prefix, values, includeInherit = false) {
|
||
const preset = backupRetentionDetectPreset(values, includeInherit);
|
||
const meta = BACKUP_RETENTION_PRESET_META[preset];
|
||
const presetOptions = this.retentionPresetOptions(preset, includeInherit);
|
||
|
||
const customRetentionHidden = includeInherit
|
||
? `<input type="hidden" name="${prefix}CUSTOM_RETENTION" value="${preset === 'inherit-global' ? 'false' : 'true'}" data-backup-field>`
|
||
: '';
|
||
|
||
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 class="tooltip" data-retention-tooltip title="${this.escape(meta?.hint || '')}">ℹ️</span></span>
|
||
<select class="form-control" data-retention-preset>${presetOptions}</select>
|
||
</label>
|
||
${customRetentionHidden}
|
||
</div>
|
||
<div class="backup-retention-advanced" data-retention-advanced ${preset === 'custom' ? '' : 'hidden'}>
|
||
<div class="backup-form-grid">
|
||
${this.formInput(`${prefix}KEEP_LAST`, 'Keep last', values.last, 'number', '', 'backups')}
|
||
${this.formInput(`${prefix}KEEP_DAILY`, 'Keep daily', values.daily, 'number', '', 'days')}
|
||
${this.formInput(`${prefix}KEEP_WEEKLY`, 'Keep weekly', values.weekly, 'number', '', 'weeks')}
|
||
${this.formInput(`${prefix}KEEP_MONTHLY`, 'Keep monthly', values.monthly, 'number', '', 'months')}
|
||
${this.formInput(`${prefix}KEEP_YEARLY`, 'Keep yearly', values.yearly, 'number', '', 'years')}
|
||
</div>
|
||
</div>
|
||
`;
|
||
},
|
||
formInput(name, label, value, type = 'text', placeholder = '', unit = '') {
|
||
const escVal = this.escape(value ?? '');
|
||
const escPh = this.escape(placeholder);
|
||
const escLabel = this.escape(label);
|
||
const inputHTML = `<input type="${type}" name="${name}" value="${escVal}" placeholder="${escPh}" class="form-control" data-backup-field>`;
|
||
const wrapped = unit ? `<div class="input-group">${inputHTML}<span class="input-group-text">${this.escape(unit)}</span></div>` : inputHTML;
|
||
return `
|
||
<label class="backup-form-row">
|
||
<span class="backup-form-label">${escLabel}</span>
|
||
${wrapped}
|
||
</label>
|
||
`;
|
||
},
|
||
formSelect(name, label, value, options) {
|
||
const escLabel = this.escape(label);
|
||
const opts = options.map(([v, lbl]) => `<option value="${this.escape(v)}" ${v === value ? 'selected' : ''}>${this.escape(lbl)}</option>`).join('');
|
||
return `
|
||
<label class="backup-form-row">
|
||
<span class="backup-form-label">${escLabel}</span>
|
||
<select name="${name}" class="form-control" data-backup-field>${opts}</select>
|
||
</label>
|
||
`;
|
||
},
|
||
formToggle(name, label, checked) {
|
||
const escLabel = this.escape(label);
|
||
return `
|
||
<label class="backup-form-row backup-form-row-toggle">
|
||
<span class="backup-form-label">${escLabel}</span>
|
||
<span class="backup-toggle">
|
||
<input type="checkbox" name="${name}" ${checked ? 'checked' : ''} data-backup-field data-backup-bool>
|
||
<span class="backup-toggle-slider"></span>
|
||
</span>
|
||
</label>
|
||
`;
|
||
},
|
||
formCrontab(name, label, value) {
|
||
if (typeof ConfigShared === 'undefined' || !ConfigShared.createCrontabField) {
|
||
return this.formInput(name, label, value, 'text', 'minute hour day month weekday');
|
||
}
|
||
const fieldId = `config-${name}`;
|
||
let cronHtml = ConfigShared.createCrontabField(fieldId, name, value, label, '');
|
||
cronHtml = cronHtml.replace(`name="${name}"`, `name="${name}" data-backup-field`);
|
||
return `
|
||
<label class="backup-form-row">
|
||
<span class="backup-form-label">${this.escape(label)}</span>
|
||
${cronHtml}
|
||
</label>
|
||
`;
|
||
},
|
||
formReadOnly(label, value) {
|
||
return `
|
||
<div class="backup-form-row">
|
||
<span class="backup-form-label">${this.escape(label)}</span>
|
||
<span class="backup-form-readonly">${this.escape(value)}</span>
|
||
</div>
|
||
`;
|
||
},
|
||
tagFieldsForSave(container) {
|
||
container.querySelectorAll('input[name], select[name], textarea[name]').forEach(el => {
|
||
if (!el.hasAttribute('data-backup-field')) {
|
||
el.setAttribute('data-backup-field', '');
|
||
if (el.type === 'checkbox') el.setAttribute('data-backup-bool', '');
|
||
}
|
||
});
|
||
},
|
||
});
|