big changes in profiles and users

This commit is contained in:
Mateusz Gruszczyński
2026-05-26 09:00:29 +02:00
parent 629b06a9df
commit 92d870878f
9 changed files with 471 additions and 162 deletions

View File

@@ -1 +1,33 @@
export const rssSource = " async function loadRss(){ const j=await (await fetch('/api/rss')).json(); const feeds=j.feeds||[], rules=j.rules||[], history=j.history||[]; if($('rssManager')) $('rssManager').innerHTML=`<h6>Feeds</h6>${table(['Name','URL','Interval','Last check','Last error','Actions'],feeds.map(f=>[esc(f.name),esc(f.url),esc(f.interval_minutes||30)+' min',humanDateCell(f.last_checked_at),esc(f.last_error||''),`<button class=\"btn btn-xs btn-outline-primary rss-edit-feed\" data-feed='${esc(JSON.stringify(f))}'><i class=\"fa-solid fa-pen-to-square\"></i> Edit</button> <button class=\"btn btn-xs btn-outline-danger rss-delete-feed\" data-id=\"${esc(f.id)}\"><i class=\"fa-solid fa-trash-can\"></i> Delete</button>`]))}<h6 class=\"mt-3\">Rules</h6>${table(['Name','Include','Exclude','Filters','Path','Label','Actions'],rules.map(r=>[esc(r.name),esc(r.pattern),esc(r.exclude_pattern||''),esc([r.min_size_mb?`min ${r.min_size_mb}MB`:'',r.max_size_mb?`max ${r.max_size_mb}MB`:'',r.category,r.quality,r.season?`S${r.season}`:'',r.episode?`E${r.episode}`:''].filter(Boolean).join(', ')),esc(r.save_path),esc(r.label),`<button class=\"btn btn-xs btn-outline-primary rss-edit-rule\" data-rule='${esc(JSON.stringify(r))}'><i class=\"fa-solid fa-pen-to-square\"></i> Edit</button> <button class=\"btn btn-xs btn-outline-danger rss-delete-rule\" data-id=\"${esc(r.id)}\"><i class=\"fa-solid fa-trash-can\"></i> Delete</button>`]))}<h6 class=\"mt-3\">RSS log</h6>${table(['Time','Title','Status','Message'],history.map(h=>[humanDateCell(h.created_at),esc(h.title||h.link||''),esc(h.status),esc(h.message||'')]))}`; }\n \n\n function fillBackupSettings(settings={}){\n if($('backupAutoEnabled')) $('backupAutoEnabled').checked=!!settings.enabled;\n if($('backupAutoInterval')) $('backupAutoInterval').value=settings.interval_hours||24;\n if($('backupRetentionDays')) $('backupRetentionDays').value=settings.retention_days||30;\n }\n function backupPreviewDetails(table={}){\n const sample=table.sample||[];\n if(!sample.length) return '<div class=\"backup-preview-empty\">No saved rows in this table.</div>';\n const keys=[...new Set(sample.flatMap(row=>Object.keys(row||{})))].slice(0,8);\n return responsiveTable(keys.map(esc), sample.map(row=>keys.map(key=>esc(row?.[key] ?? ''))), 'backup-preview-sample-table');\n }\n function backupPreviewTable(preview={}){\n const tables=preview.tables||[];\n const rows=tables.map(t=>`<details class=\"backup-preview-table-details\"><summary><span><b>${esc(t.name)}</b><small>${esc(t.rows)} row(s) \u00b7 ${(t.columns||[]).length} column(s)</small></span></summary>${backupPreviewDetails(t)}</details>`).join('');\n return `<div class=\"surface-section backup-preview-card\"><div class=\"section-title\"><i class=\"fa-solid fa-eye\"></i> Backup preview</div><div class=\"small text-muted mb-2\">Created: ${esc(preview.created_at||'-')} \u00b7 ${preview.automatic?'automatic':'manual'} \u00b7 sensitive values hidden</div>${rows || '<div class=\"empty-mini\">Backup has no previewable settings.</div>'}</div>`;\n }\n async function loadBackup(){\n const j=await (await fetch('/api/backup')).json();\n const rows=j.backups||[];\n fillBackupSettings(j.auto||{});\n if($('backupManager')) $('backupManager').innerHTML=responsiveTable(['Name','Created','Type','Actions'],rows.map(b=>[esc(b.name),humanDateCell(b.created_at),b.automatic?'Auto':'Manual',`<div class=\"table-action-group backup-actions\"><button class=\"btn btn-xs btn-outline-info backup-preview-btn\" data-id=\"${esc(b.id)}\"><i class=\"fa-solid fa-eye\"></i> Preview</button><a class=\"btn btn-xs btn-outline-secondary\" href=\"/api/backup/${esc(b.id)}/download\"><i class=\"fa-solid fa-download\"></i> Download</a><button class=\"btn btn-xs btn-outline-warning backup-restore\" data-id=\"${esc(b.id)}\"><i class=\"fa-solid fa-rotate-left\"></i> Restore</button><button class=\"btn btn-xs btn-outline-danger backup-delete\" data-id=\"${esc(b.id)}\"><i class=\"fa-solid fa-trash-can\"></i> Delete</button></div>`]),'backup-table');\n }\n\n";
export const rssSource = " async function loadRss(){ const j=await (await fetch('/api/rss')).json(); const feeds=j.feeds||[], rules=j.rules||[], history=j.history||[]; if($('rssManager')) $('rssManager').innerHTML=`<h6>Feeds</h6>${table(['Name','URL','Interval','Last check','Last error','Actions'],feeds.map(f=>[esc(f.name),esc(f.url),esc(f.interval_minutes||30)+' min',humanDateCell(f.last_checked_at),esc(f.last_error||''),`<button class=\"btn btn-xs btn-outline-primary rss-edit-feed\" data-feed='${esc(JSON.stringify(f))}'><i class=\"fa-solid fa-pen-to-square\"></i> Edit</button> <button class=\"btn btn-xs btn-outline-danger rss-delete-feed\" data-id=\"${esc(f.id)}\"><i class=\"fa-solid fa-trash-can\"></i> Delete</button>`]))}<h6 class=\"mt-3\">Rules</h6>${table(['Name','Include','Exclude','Filters','Path','Label','Actions'],rules.map(r=>[esc(r.name),esc(r.pattern),esc(r.exclude_pattern||''),esc([r.min_size_mb?`min ${r.min_size_mb}MB`:'',r.max_size_mb?`max ${r.max_size_mb}MB`:'',r.category,r.quality,r.season?`S${r.season}`:'',r.episode?`E${r.episode}`:''].filter(Boolean).join(', ')),esc(r.save_path),esc(r.label),`<button class=\"btn btn-xs btn-outline-primary rss-edit-rule\" data-rule='${esc(JSON.stringify(r))}'><i class=\"fa-solid fa-pen-to-square\"></i> Edit</button> <button class=\"btn btn-xs btn-outline-danger rss-delete-rule\" data-id=\"${esc(r.id)}\"><i class=\"fa-solid fa-trash-can\"></i> Delete</button>`]))}<h6 class=\"mt-3\">RSS log</h6>${table(['Time','Title','Status','Message'],history.map(h=>[humanDateCell(h.created_at),esc(h.title||h.link||''),esc(h.status),esc(h.message||'')]))}`; }\n \n\n function fillBackupSettings(settings={}){
if($('backupAutoEnabled')) $('backupAutoEnabled').checked=!!settings.enabled;
if($('backupAutoInterval')) $('backupAutoInterval').value=settings.interval_hours||24;
if($('backupRetentionDays')) $('backupRetentionDays').value=settings.retention_days||30;
}
function backupPreviewDetails(table={}){
const sample=table.sample||[];
if(!sample.length) return '<div class="backup-preview-empty">No saved rows in this table.</div>';
const keys=[...new Set(sample.flatMap(row=>Object.keys(row||{})))].slice(0,8);
return responsiveTable(keys.map(esc), sample.map(row=>keys.map(key=>esc(row?.[key] ?? ''))), 'backup-preview-sample-table');
}
function backupPreviewTable(preview={}){
const tables=preview.tables||[];
const rows=tables.map(t=>`<details class="backup-preview-table-details"><summary><span><b>${esc(t.name)}</b><small>${esc(t.rows)} row(s) · ${(t.columns||[]).length} column(s)</small></span></summary>${backupPreviewDetails(t)}</details>`).join('');
const type=preview.backup_type==='app'?'application':'profile';
return `<div class="surface-section backup-preview-card"><div class="section-title"><i class="fa-solid fa-eye"></i> Backup preview</div><div class="small text-muted mb-2">${esc(type)} backup · Created: ${esc(preview.created_at||'-')} · ${preview.automatic?'automatic':'manual'} · sensitive values hidden</div>${rows || '<div class="empty-mini">Backup has no previewable settings.</div>'}</div>`;
}
function backupRows(rows=[]){
return responsiveTable(['Name','Created','Type','Actions'],rows.map(b=>[esc(b.name),humanDateCell(b.created_at),b.automatic?'Auto':'Manual',`<div class="table-action-group backup-actions"><button class="btn btn-xs btn-outline-info backup-preview-btn" data-id="${esc(b.id)}"><i class="fa-solid fa-eye"></i> Preview</button><a class="btn btn-xs btn-outline-secondary" href="/api/backup/${esc(b.id)}/download"><i class="fa-solid fa-download"></i> Download</a><button class="btn btn-xs btn-outline-warning backup-restore" data-id="${esc(b.id)}" data-type="${esc(b.backup_type||'profile')}"><i class="fa-solid fa-rotate-left"></i> Restore</button><button class="btn btn-xs btn-outline-danger backup-delete" data-id="${esc(b.id)}"><i class="fa-solid fa-trash-can"></i> Delete</button></div>`]),'backup-table');
}
function switchBackupPane(pane){
document.querySelectorAll('[data-backup-pane]').forEach(x=>x.classList.toggle('active',x.dataset.backupPane===pane));
document.querySelectorAll('[data-backup-panel]').forEach(x=>x.classList.toggle('d-none',x.dataset.backupPanel!==pane));
}
async function loadBackup(){
const j=await (await fetch('/api/backup')).json();
fillBackupSettings(j.auto||{});
if($('profileBackupManager')) $('profileBackupManager').innerHTML=backupRows(j.profile_backups||[]);
if($('appBackupManager')) $('appBackupManager').innerHTML=j.can_app_backup ? backupRows(j.app_backups||[]) : '<div class="empty-mini">Application backups are admin-only.</div>';
if(!j.can_app_backup) document.querySelector('[data-backup-pane="app"]')?.classList.add('disabled');
}
\n\n";