webui: automation settings panel, set-location, responsive layout
- Automation tab: settings form (define_settings) beside the editor, stacking above it when narrow; script/settings save split. - Set location modal: current location + reset checkbox. - saveScriptSettings/setSavePath API wiring; plugins panel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5a5b5f4330
commit
9a1d67acd4
11 changed files with 741 additions and 13 deletions
95
public/plugins/health.js
Normal file
95
public/plugins/health.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
window.Naut.registerPlugin({
|
||||
id: 'health',
|
||||
name: 'Health Overview',
|
||||
|
||||
views: [{
|
||||
id: 'health',
|
||||
label: 'Health',
|
||||
render(host, { state, f }) {
|
||||
const torrents = state.snapshot.torrents;
|
||||
const lowAvailability = torrents.filter((t) => t.availability < 1.05);
|
||||
const idleComplete = torrents.filter((t) => t.progress >= 1 && t.upspeed === 0);
|
||||
const slowDownloads = torrents.filter((t) => t.progress < 1 && t.dlspeed < 32 * 1024);
|
||||
const rows = [...lowAvailability, ...slowDownloads]
|
||||
.filter((t, i, arr) => arr.findIndex((x) => x.hash === t.hash) === i)
|
||||
.sort((a, b) => a.availability - b.availability)
|
||||
.slice(0, 18);
|
||||
|
||||
host.innerHTML = `<div class="pane">
|
||||
<div class="settings-grid">
|
||||
${metric('Low availability', lowAvailability.length)}
|
||||
${metric('Idle completed', idleComplete.length)}
|
||||
${metric('Slow downloads', slowDownloads.length)}
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Torrents needing attention</h3>
|
||||
${rows.length ? table(rows, f) : '<div class="empty" style="height:120px">No health issues in the current snapshot</div>'}
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
}],
|
||||
|
||||
sidebarSections: [{
|
||||
id: 'health',
|
||||
title: 'Health',
|
||||
render({ state }) {
|
||||
const torrents = state.snapshot.torrents;
|
||||
const attention = torrents.filter((t) => t.availability < 1.05 || (t.progress < 1 && t.dlspeed < 32 * 1024)).length;
|
||||
return `<div class="side-item" data-health-open>
|
||||
<span class="ic">+</span><span class="lbl">Needs attention</span><span class="cnt">${attention}</span>
|
||||
</div>`;
|
||||
},
|
||||
onMount(root, { setView }) {
|
||||
root.querySelector('[data-health-open]')?.addEventListener('click', () => setView('health'));
|
||||
},
|
||||
}],
|
||||
|
||||
detailTabs: [{
|
||||
id: 'health-detail',
|
||||
label: 'Health',
|
||||
async render(host, { api, hash, f }) {
|
||||
const t = await api.torrent(hash);
|
||||
host.innerHTML = `<div class="props">
|
||||
<div class="section-h">Health</div>
|
||||
${prop('Availability', t.availability.toFixed(3))}
|
||||
${prop('Connected seeds', `${t.seeds} / ${t.seedsTotal}`)}
|
||||
${prop('Connected peers', `${t.peers} / ${t.peersTotal}`)}
|
||||
${prop('Last activity', f.ago(t.lastActivity))}
|
||||
${prop('Transfer state', t.dlspeed || t.upspeed ? 'active' : 'idle')}
|
||||
</div>`;
|
||||
},
|
||||
}],
|
||||
|
||||
detailPanels: [{
|
||||
id: 'health-summary',
|
||||
title: 'Plugin: Health',
|
||||
render(t, { f }) {
|
||||
const flags = [];
|
||||
if (t.availability < 1.05) flags.push('low availability');
|
||||
if (t.progress < 1 && t.dlspeed < 32 * 1024) flags.push('slow download');
|
||||
if (t.progress >= 1 && t.upspeed === 0) flags.push('idle seeding');
|
||||
return `${prop('Attention', flags.length ? flags.join(', ') : 'none')}
|
||||
${prop('Last activity', f.ago(t.lastActivity))}`;
|
||||
},
|
||||
}],
|
||||
});
|
||||
|
||||
function metric(label, value) {
|
||||
return `<div class="card"><h3>${label}</h3><div class="plugin-metric">${value}</div></div>`;
|
||||
}
|
||||
|
||||
function prop(k, v) {
|
||||
return `<div class="prop"><span class="k">${k}</span><span class="v">${v}</span></div>`;
|
||||
}
|
||||
|
||||
function table(rows, f) {
|
||||
return `<table class="dtbl"><thead><tr>
|
||||
<th>Name</th><th class="num">Done</th><th class="num">Availability</th><th class="num">Down</th><th class="num">Up</th>
|
||||
</tr></thead><tbody>${rows.map((t) => `<tr>
|
||||
<td>${f.esc(t.name)}</td>
|
||||
<td class="num">${f.pct(t.progress)}</td>
|
||||
<td class="num">${t.availability.toFixed(2)}</td>
|
||||
<td class="num">${f.rate(t.dlspeed)}</td>
|
||||
<td class="num">${f.rate(t.upspeed)}</td>
|
||||
</tr>`).join('')}</tbody></table>`;
|
||||
}
|
||||
5
public/plugins/plugins.json
Normal file
5
public/plugins/plugins.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"modules": [
|
||||
"/plugins/health.js"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue