Add docker actions, arr profile enforcer, monitor caching, and web file symlink
Web files now served via symlink to the git repo so git pull changes survive reboots without rebuilding the txz. Also includes: docker pull/rebuild/restart with live log streaming, arr_profile_enforcer for Sonarr/Radarr quality profiles, monitor page cache fix (background writer now in cron), and ARR_KIDS/SONARR/RADARR profile name vars in master.conf.
This commit is contained in:
@@ -545,6 +545,33 @@ function vvIoSum(devices) {
|
||||
devices.forEach(dev => { const io = vvDiskIo[dev]; if (io) { r += io.r ?? 0; w += io.w ?? 0; } });
|
||||
return [r, w];
|
||||
}
|
||||
function vvWdRsyncToggle(el) {
|
||||
const flag = el.dataset.flag;
|
||||
const on = el.dataset.enabled !== '1';
|
||||
el.dataset.enabled = on ? '1' : '0';
|
||||
el.style.color = on ? '#4caf50' : '#333';
|
||||
el.style.background = on ? '#0f1a0f' : '#111';
|
||||
el.style.borderColor = on ? '#1a3a1a' : '#222';
|
||||
const fd = new FormData();
|
||||
fd.append('name', flag);
|
||||
fd.append('enabled', on ? '1' : '0');
|
||||
fetch('/plugins/varaverk/api/flag_toggle.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (!d.ok) {
|
||||
el.dataset.enabled = on ? '0' : '1';
|
||||
el.style.color = on ? '#333' : '#4caf50';
|
||||
el.style.background = on ? '#111' : '#0f1a0f';
|
||||
el.style.borderColor = on ? '#222' : '#1a3a1a';
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
el.dataset.enabled = on ? '0' : '1';
|
||||
el.style.color = on ? '#333' : '#4caf50';
|
||||
el.style.background = on ? '#111' : '#0f1a0f';
|
||||
el.style.borderColor = on ? '#222' : '#1a3a1a';
|
||||
});
|
||||
}
|
||||
function vvIoTotalSum(devices) {
|
||||
let tr = 0, tw = 0;
|
||||
devices.forEach(dev => { const io = vvDiskIo[dev]; if (io) { tr += io.tr ?? 0; tw += io.tw ?? 0; } });
|
||||
@@ -1102,11 +1129,26 @@ function vvPollMonitor() {
|
||||
const ramFree = stab.ram_free_gb ?? 0;
|
||||
const ramColor = ramFree < 6 ? '#f44336' : ramFree < 12 ? '#ff9800' : '#4caf50';
|
||||
const load = stab.load_1min ?? 0;
|
||||
const loadColor = load > 6 ? '#f44336' : load > 3 ? '#ff9800' : '#4caf50';
|
||||
const _wdCores = sys.cpu_cores || 0;
|
||||
const loadColor = _wdCores > 0
|
||||
? (load > _wdCores * 2 ? '#f44336' : load > _wdCores ? '#ff9800' : '#4caf50')
|
||||
: (load > 6 ? '#f44336' : load > 3 ? '#ff9800' : '#4caf50');
|
||||
const nicOk = (stab.nic_state ?? '') === 'up';
|
||||
const sshdOk = stab.sshd_ok ?? true;
|
||||
const zombies = stab.zombies ?? 0;
|
||||
|
||||
const uptimeSec = sys.uptime_sec ?? 0;
|
||||
const uptimeDays = Math.floor(uptimeSec / 86400);
|
||||
const uptimeHrs = Math.floor((uptimeSec % 86400) / 3600);
|
||||
const uptimeStr = uptimeDays > 0 ? `${uptimeDays}d ${uptimeHrs}h` : `${uptimeHrs}h`;
|
||||
const uptimeColor = uptimeDays === 0 ? '#ff9800' : '#4caf50';
|
||||
const stabCount = stabNames.length;
|
||||
|
||||
const fdOpen = stab.fd_open ?? 0;
|
||||
const fdPct = stab.fd_pct ?? 0;
|
||||
const fdStr = fdOpen >= 1e6 ? (fdOpen/1e6).toFixed(1)+'M' : fdOpen >= 1000 ? (fdOpen/1000).toFixed(1)+'k' : String(fdOpen);
|
||||
const fdColor = fdPct >= 50 ? '#f44336' : fdPct >= 20 ? '#ff9800' : '#4caf50';
|
||||
|
||||
const cpuRow = stab.cpu_temp != null
|
||||
? `<span style="color:#444;">CPU</span><span style="color:${wdPct(stab.cpu_temp,75,90)};">${stab.cpu_temp}°C</span>` : '';
|
||||
let statsHtml = `<div style="display:grid;grid-template-columns:auto 1fr auto 1fr;gap:2px 8px;font-size:11px;margin-top:8px;margin-bottom:6px;">
|
||||
@@ -1117,9 +1159,13 @@ function vvPollMonitor() {
|
||||
<span style="color:#444;">Load</span><span style="color:${loadColor};">${load}</span>
|
||||
${cpuRow}
|
||||
<span style="color:#444;">Zombies</span><span style="color:${zombies>0?'#ff9800':'#4caf50'};">${zombies}</span>
|
||||
<span style="color:#444;">FD open</span><span style="color:${fdColor};">${fdStr}</span>
|
||||
<span style="color:#444;">${stab.nic??'nic'}</span><span style="color:${nicOk?'#4caf50':'#f44336'};">● ${stab.nic_state??'?'}</span>
|
||||
<span style="color:#444;">sshd</span><span style="color:${sshdOk?'#4caf50':'#f44336'};">${sshdOk?'● ok':'✗ down'}</span>
|
||||
<span style="color:#444;">NPM</span><span style="color:${npmStrikes>0?'#ff9800':'#4caf50'};">${npmStrikes>0?npmStrikes+'× strikes':'● ok'}</span>
|
||||
<span style="color:#444;">Uptime</span><span style="color:${uptimeColor};">${uptimeStr}</span>
|
||||
<span style="color:#444;">Reboots</span><span style="color:${reboots>0?'#f44336':'#4caf50'};">${reboots}/12h</span>
|
||||
<span style="color:#444;">Strikes</span><span style="color:${stabCount>0?'#ff9800':'#4caf50'};">${stabCount>0?stabCount+' active':'none'}</span>
|
||||
</div>`;
|
||||
html += statsHtml;
|
||||
|
||||
@@ -1317,13 +1363,24 @@ function vvPollMonitor() {
|
||||
let html = `<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
|
||||
<span style="font-size:10px;font-weight:600;color:${gCol};">● ${enabled ? 'ENABLED' : 'DISABLED'}</span>
|
||||
<div style="display:flex;gap:3px;">`;
|
||||
[['C','critical'],['D','daily'],['I','intermediate'],['W','weekly']].forEach(([s,k]) => {
|
||||
const on = windows[k] ?? false;
|
||||
const run = active.some(a => a.profile?.includes(k));
|
||||
const bg = run ? '#1a2a0a' : on ? '#0f1a0f' : '#111';
|
||||
const brd = run ? '#3a6a1a' : on ? '#1a3a1a' : '#222';
|
||||
const col = run ? '#8bc34a' : on ? '#4caf50' : '#333';
|
||||
html += `<span title="${k}" style="font-size:9px;padding:2px 5px;border-radius:2px;
|
||||
const _flagMap = {
|
||||
critical: 'CRITICAL_RSYNC_ENABLED',
|
||||
intermediate: 'INTERMEDIATE_RSYNC_ENABLED',
|
||||
daily: 'DAILY_RSYNC_ENABLED',
|
||||
weekly: 'WEEKLY_RSYNC_ENABLED',
|
||||
monthly: 'MONTHLY_RSYNC_ENABLED',
|
||||
};
|
||||
[['C','critical'],['I','intermediate'],['D','daily'],['W','weekly'],['M','monthly']].forEach(([s,k]) => {
|
||||
const on = windows[k] ?? false;
|
||||
const run = active.some(a => a.profile?.includes(k));
|
||||
const flag = _flagMap[k];
|
||||
const bg = run ? '#1a2a0a' : on ? '#0f1a0f' : '#111';
|
||||
const brd = run ? '#3a6a1a' : on ? '#1a3a1a' : '#222';
|
||||
const col = run ? '#8bc34a' : on ? '#4caf50' : '#333';
|
||||
const tog = flag ? `data-flag="${flag}" data-enabled="${on?'1':'0'}" onclick="vvWdRsyncToggle(this)"` : '';
|
||||
const cur = flag ? 'cursor:pointer;' : '';
|
||||
const tip = `Toggle ${k} rsync`;
|
||||
html += `<span title="${tip}" ${tog} style="${cur}font-size:9px;padding:2px 5px;border-radius:2px;
|
||||
background:${bg};border:1px solid ${brd};color:${col};font-weight:600;">${s}</span>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
@@ -2180,27 +2237,31 @@ function vvRenderDockerFolders(data) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Build folder list — ungrouped gets a 📁 emoji icon
|
||||
const allFolders = (data.folders ?? []).map(f => ({ ...f, isEmoji: false }));
|
||||
const ug = data.ungrouped ?? [];
|
||||
if (ug.length) allFolders.push({ id:'__ungrouped__', name:'Ungrouped', icon:'📁', isEmoji:true, containers:ug });
|
||||
// Build item list — folders first, then ungrouped containers as individual rows
|
||||
const _folders = (data.folders ?? []).map(f => ({ ...f, _type: 'folder', isEmoji: false }));
|
||||
const _solo = (data.ungrouped ?? []).map(c => ({ ...c, _type: 'container' }));
|
||||
const _items = [..._folders, ..._solo];
|
||||
|
||||
if (!allFolders.length) {
|
||||
if (!_items.length) {
|
||||
html += '<div class="vv-df-empty">No containers found</div>';
|
||||
el.innerHTML = html;
|
||||
return;
|
||||
}
|
||||
|
||||
function renderItem(item) {
|
||||
return item._type === 'folder' ? renderFolder(item) : renderContainer(item);
|
||||
}
|
||||
|
||||
// Column count: 3 big / 2 intermediate / 1 small
|
||||
const _w = window.innerWidth;
|
||||
const _cols = _w > 1400 ? 3 : _w > 640 ? 2 : 1;
|
||||
|
||||
if (_cols === 1) {
|
||||
html += `<div class="vv-df-col">${allFolders.map(renderFolder).join('')}</div>`;
|
||||
html += `<div class="vv-df-col">${_items.map(renderItem).join('')}</div>`;
|
||||
} else {
|
||||
const perCol = Math.ceil(allFolders.length / _cols);
|
||||
const perCol = Math.ceil(_items.length / _cols);
|
||||
const colDivs = Array.from({length: _cols}, (_, i) =>
|
||||
`<div class="vv-df-col">${allFolders.slice(i * perCol, (i + 1) * perCol).map(renderFolder).join('')}</div>`
|
||||
`<div class="vv-df-col">${_items.slice(i * perCol, (i + 1) * perCol).map(renderItem).join('')}</div>`
|
||||
).join('');
|
||||
html += `<div class="vv-df-cols">${colDivs}</div>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user