refactor(ui)!: verwijderen pods/systemd tab en samenvoegen functionaliteit in containers
This commit is contained in:
+34
-252
@@ -49,9 +49,6 @@
|
|||||||
<div class="tab" id="tab-containers" onclick="setTab('containers')" title="Containers">
|
<div class="tab" id="tab-containers" onclick="setTab('containers')" title="Containers">
|
||||||
<span class="navIcon">📦</span><span class="navLabel">Containers</span>
|
<span class="navIcon">📦</span><span class="navLabel">Containers</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab" id="tab-systemd" onclick="setTab('systemd')" title="Systemd">
|
|
||||||
<span class="navIcon">⚙️</span><span class="navLabel">Systemd</span>
|
|
||||||
</div>
|
|
||||||
<div class="tab" id="tab-files" onclick="setTab('files')" title="Files">
|
<div class="tab" id="tab-files" onclick="setTab('files')" title="Files">
|
||||||
<span class="navIcon">📁</span><span class="navLabel">Files</span>
|
<span class="navIcon">📁</span><span class="navLabel">Files</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +71,6 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<span class="pill"><span class="b" id="countPods">-</span> pods</span>
|
<span class="pill"><span class="b" id="countPods">-</span> pods</span>
|
||||||
<span class="pill"><span class="b" id="countContainers">-</span> containers</span>
|
<span class="pill"><span class="b" id="countContainers">-</span> containers</span>
|
||||||
<span class="pill"><span class="b" id="countSystemd">-</span> units (UI)</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
Deze UI gebruikt jouw API endpoints onder <span class="mono">/api</span> (same origin).
|
Deze UI gebruikt jouw API endpoints onder <span class="mono">/api</span> (same origin).
|
||||||
@@ -82,20 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card half">
|
|
||||||
<div class="cardHeader">
|
|
||||||
<div class="cardTitle">Systemd units (uit UI lijst)</div>
|
|
||||||
<div class="flex">
|
|
||||||
<button class="btn" onclick="systemdRefresh()">Ververs status</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="cardBody">
|
|
||||||
<div id="systemdMini" class="muted">Nog geen data.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="view-containers" class="grid" style="display:none">
|
<div id="view-containers" class="grid" style="display:none">
|
||||||
<div class="card" style="grid-column: 1 / -1;">
|
<div class="card" style="grid-column: 1 / -1;">
|
||||||
<div class="cardHeader">
|
<div class="cardHeader">
|
||||||
@@ -125,64 +108,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="view-systemd" class="grid" style="display:none">
|
|
||||||
<div class="card" style="grid-column: 1 / -1;">
|
|
||||||
<div class="cardHeader">
|
|
||||||
<div class="cardTitle">Systemd (allowlist via UI)</div>
|
|
||||||
<div class="flex">
|
|
||||||
<button class="btn ok" onclick="daemonReload()">daemon-reload</button>
|
|
||||||
<button class="btn" onclick="systemdRefresh()">Ververs status</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="cardBody">
|
|
||||||
<div class="split">
|
|
||||||
<div>
|
|
||||||
<div class="muted" style="margin-bottom:8px">
|
|
||||||
Units (één per regel). Deze lijst wordt opgeslagen in je browser (localStorage).
|
|
||||||
</div>
|
|
||||||
<textarea id="systemdUnits" class="textarea" spellcheck="false"></textarea>
|
|
||||||
<div class="flex" style="margin-top:10px">
|
|
||||||
<button class="btn" onclick="saveSystemdUnits()">Opslaan</button>
|
|
||||||
<button class="btn ghost" onclick="loadDefaultUnits()">Standaard</button>
|
|
||||||
<span class="pill">Gebruik allowlist op server om te beperken.</span>
|
|
||||||
</div>
|
|
||||||
<div class="hint">
|
|
||||||
De server enforce’t jouw allowlist. Als je hier een unit invult die niet toegestaan is, krijg je 403.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="muted" style="margin-bottom:8px">
|
|
||||||
Snelle actie op één unit:
|
|
||||||
</div>
|
|
||||||
<input id="systemdOne" class="input mono" placeholder="bijv. sonarr.service" />
|
|
||||||
<div class="flex" style="margin-top:10px">
|
|
||||||
<button class="btn" onclick="systemdActionSingle('status')">Status</button>
|
|
||||||
<button class="btn ok" onclick="systemdActionSingle('start')">Start</button>
|
|
||||||
<button class="btn warn" onclick="systemdActionSingle('restart')">Restart</button>
|
|
||||||
<button class="btn bad" onclick="systemdActionSingle('stop')">Stop</button>
|
|
||||||
</div>
|
|
||||||
<div class="hint">
|
|
||||||
Tip: gebruik <span class="mono">demo1.service</span>, <span class="mono">demo2.service</span>, <span class="mono">sonarr.service</span> om te testen.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top:16px">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Unit</th>
|
|
||||||
<th>Laatste status (API output)</th>
|
|
||||||
<th>Acties</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="systemdTbody"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="view-files" class="grid" style="display:none">
|
<div id="view-files" class="grid" style="display:none">
|
||||||
<div class="card" style="grid-column: 1 / -1;">
|
<div class="card" style="grid-column: 1 / -1;">
|
||||||
<div class="cardHeader">
|
<div class="cardHeader">
|
||||||
@@ -214,7 +139,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<textarea id="filesEditor" class="textarea mono" spellcheck="false" placeholder="Selecteer links een bestand..."></textarea>
|
<textarea id="filesEditor" class="textarea mono" spellcheck="false" placeholder="Selecteer links een bestand..."></textarea>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
Na wijzigen van <span class="mono">*.container</span> moet je meestal <span class="mono">daemon-reload</span> doen (kan via Systemd-tab of dashboard-knop).
|
Na wijzigen van <span class="mono">*.container</span> moet je meestal <span class="mono">daemon-reload</span> doen (via de dashboard-knop).
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -354,7 +279,7 @@
|
|||||||
async function pingApi() {
|
async function pingApi() {
|
||||||
try {
|
try {
|
||||||
// simpele ping: pods ophalen
|
// simpele ping: pods ophalen
|
||||||
await api('/pods', 'GET');
|
await api('/pods-dashboard', 'GET');
|
||||||
setApiState(true, 'API: OK');
|
setApiState(true, 'API: OK');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setApiState(false, 'API: fout (' + e.message + ')');
|
setApiState(false, 'API: fout (' + e.message + ')');
|
||||||
@@ -372,22 +297,16 @@
|
|||||||
async function refreshActive() {
|
async function refreshActive() {
|
||||||
try {
|
try {
|
||||||
if (currentTab === 'containers') await fetchContainers();
|
if (currentTab === 'containers') await fetchContainers();
|
||||||
else if (currentTab === 'systemd') await systemdRefresh();
|
|
||||||
else {
|
else {
|
||||||
// dashboard: haal in achtergrond counts + mini systemd
|
|
||||||
const [pods, containers] = await Promise.all([
|
const [pods, containers] = await Promise.all([
|
||||||
api('/pods-dashboard','GET'),
|
api('/pods-dashboard','GET'),
|
||||||
api('/containers','GET')
|
api('/containers-dashboard','GET')
|
||||||
]);
|
]);
|
||||||
document.getElementById('countPods').textContent = (pods || []).length;
|
document.getElementById('countPods').textContent = (pods || []).length;
|
||||||
// containers list kan array of object zijn; jij gebruikt array
|
|
||||||
const cCount = Array.isArray(containers) ? containers.length : (containers?.length || 0);
|
const list = Array.isArray(containers) ? containers : (containers?.containers || []);
|
||||||
|
const cCount = list.length;
|
||||||
document.getElementById('countContainers').textContent = cCount;
|
document.getElementById('countContainers').textContent = cCount;
|
||||||
|
|
||||||
const units = await getSystemdUnitsFromServer();
|
|
||||||
document.getElementById('countSystemd').textContent = units.length;
|
|
||||||
|
|
||||||
await systemdMiniRefresh();
|
|
||||||
}
|
}
|
||||||
setApiState(true, 'API: OK');
|
setApiState(true, 'API: OK');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -396,49 +315,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Pods ----
|
// ---- Pods ----
|
||||||
async function fetchPods() {
|
|
||||||
const pods = await api('/pods-dashboard','GET');
|
|
||||||
document.getElementById('countPods').textContent = (pods || []).length;
|
|
||||||
const tbody = document.getElementById('podsTbody');
|
|
||||||
if (!tbody) return; // Pods-tab verwijderd: alleen countPods updaten, geen rendering
|
|
||||||
tbody.innerHTML = (pods || []).map(p => {
|
|
||||||
const name = p.Name || p.name || '';
|
|
||||||
const status = p.Status || p.status || '';
|
|
||||||
const containers = (Array.isArray(p.Containers) ? p.Containers : [])
|
|
||||||
.map(c => {
|
|
||||||
if (typeof c === 'string') return c; // jouw nieuwe API: list of strings
|
|
||||||
const n = c?.Names;
|
|
||||||
if (Array.isArray(n)) return n[0] || '';
|
|
||||||
if (typeof n === 'string') return n;
|
|
||||||
return c?.Name || c?.name || '';
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(', ');
|
|
||||||
return `
|
|
||||||
<tr>
|
|
||||||
<td><strong>${esc(name)}</strong></td>
|
|
||||||
<td>${badgeFromStatus(status)}</td>
|
|
||||||
<td class="muted">${esc(containers || '')}</td>
|
|
||||||
<td>
|
|
||||||
<div class="flex">
|
|
||||||
<button class="btn small ok" onclick="podAction('start','${esc(name)}')">Start</button>
|
|
||||||
<button class="btn small warn" onclick="podAction('restart','${esc(name)}')">Restart</button>
|
|
||||||
<button class="btn small bad" onclick="podAction('stop','${esc(name)}')">Stop</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function podAction(action, name) {
|
async function podAction(action, name) {
|
||||||
try {
|
try {
|
||||||
const res = await api(`/pods/actions/${encodeURIComponent(action)}/${encodeURIComponent(name)}`, 'POST');
|
const res = await api(`/pods/actions/${encodeURIComponent(action)}/${encodeURIComponent(name)}`, 'POST');
|
||||||
showModal(`Pod ${action}: ${name}`, JSON.stringify(res, null, 2));
|
showModal(`Pod ${action}: ${name}`, JSON.stringify(res, null, 2));
|
||||||
await fetchPods();
|
|
||||||
if (currentTab === 'containers') {
|
await fetchContainers(); // refresh pods + containers view in één keer
|
||||||
await fetchContainers();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showModal(`Pod ${action} fout`, e.stack || e.message);
|
showModal(`Pod ${action} fout`, e.stack || e.message);
|
||||||
}
|
}
|
||||||
@@ -511,15 +395,15 @@
|
|||||||
<td>${ports || '-'}</td>
|
<td>${ports || '-'}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<button class="btn icon" title="Inspect" onclick="containerInspect('${esc(name)}')">🔍</button>
|
<button class="btn icon" title="Inspect" onclick="containerInspect(decodeURIComponent('${encodeURIComponent(name)}'))">🔍</button>
|
||||||
<button class="btn icon" title="Logs" onclick="containerLogs('${esc(name)}')">📄</button>
|
<button class="btn icon" title="Logs" onclick="containerLogs(decodeURIComponent('${encodeURIComponent(name)}'))">📄</button>
|
||||||
${renderActionsDropdown(menuId, 'containerAction', esc(name))}
|
${renderActionsDropdown(menuId, 'containerAction', esc(name))}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
let containersC2P = new Map();
|
||||||
function renderContainersGrouped(list, tbody, podStatus) {
|
function renderContainersGrouped(list, tbody, podStatus) {
|
||||||
const groups = new Map();
|
const groups = new Map();
|
||||||
for (const c of (list || [])) {
|
for (const c of (list || [])) {
|
||||||
@@ -538,8 +422,6 @@
|
|||||||
return _cmpStr(a, b);
|
return _cmpStr(a, b);
|
||||||
});
|
});
|
||||||
|
|
||||||
const table = tbody.closest('table');
|
|
||||||
const colCount = table ? table.querySelectorAll('thead th').length : 9;
|
|
||||||
containersC2P = new Map();
|
containersC2P = new Map();
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
@@ -589,7 +471,7 @@
|
|||||||
html += `
|
html += `
|
||||||
<tr class="pod-group-row">
|
<tr class="pod-group-row">
|
||||||
<td>
|
<td>
|
||||||
<span class="pod-toggle" data-pod="${pod}" style="cursor:pointer; user-select:none;">
|
<span class="pod-toggle" data-pod="${encodeURIComponent(pod)}" style="cursor:pointer; user-select:none;">
|
||||||
${collapsed ? '▶' : '▼'} <b>${esc(pod)}</b>
|
${collapsed ? '▶' : '▼'} <b>${esc(pod)}</b>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -626,7 +508,7 @@
|
|||||||
if (cname) containersC2P.set(cname, pod);
|
if (cname) containersC2P.set(cname, pod);
|
||||||
const row = renderContainerRow(c).replace(
|
const row = renderContainerRow(c).replace(
|
||||||
'<tr',
|
'<tr',
|
||||||
`<tr class="pod-item-row" data-pod="${pod}"${collapsed ? ' style="display:none;"' : ''}`
|
`<tr class="pod-item-row" data-pod="${encodeURIComponent(pod)}"${collapsed ? ' style="display:none;"' : ''}`
|
||||||
);
|
);
|
||||||
html += row;
|
html += row;
|
||||||
}
|
}
|
||||||
@@ -638,17 +520,18 @@
|
|||||||
const t = ev.target.closest('.pod-toggle');
|
const t = ev.target.closest('.pod-toggle');
|
||||||
if (!t) return;
|
if (!t) return;
|
||||||
|
|
||||||
const pod = t.getAttribute('data-pod');
|
const podEnc = t.getAttribute('data-pod') || '';
|
||||||
|
const pod = decodeURIComponent(podEnc);
|
||||||
const isNowCollapsed = !_isCollapsed(pod);
|
const isNowCollapsed = !_isCollapsed(pod);
|
||||||
_setCollapsed(pod, isNowCollapsed);
|
_setCollapsed(pod, isNowCollapsed);
|
||||||
|
|
||||||
tbody.querySelectorAll(`.pod-item-row[data-pod="${CSS.escape(pod)}"]`).forEach(r => {
|
tbody.querySelectorAll(`.pod-item-row[data-pod="${CSS.escape(podEnc)}"]`).forEach(r => {
|
||||||
r.style.display = isNowCollapsed ? 'none' : '';
|
r.style.display = isNowCollapsed ? 'none' : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
t.innerHTML =
|
t.innerHTML =
|
||||||
`${isNowCollapsed ? '▶' : '▼'} <b>${pod}</b> ` +
|
`${isNowCollapsed ? '▶' : '▼'} <b>${pod}</b> ` +
|
||||||
`<span style="opacity:.7;">(${tbody.querySelectorAll(`.pod-item-row[data-pod="${CSS.escape(pod)}"]`).length})</span>`;
|
`<span style="opacity:.7;">(${tbody.querySelectorAll(`.pod-item-row[data-pod="${CSS.escape(podEnc)}"]`).length})</span>`;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,11 +553,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tbody = document.getElementById('containersTbody');
|
const tbody = document.getElementById('containersTbody');
|
||||||
|
if (!tbody) return;
|
||||||
renderContainersGrouped(list, tbody, podStatus);
|
renderContainersGrouped(list, tbody, podStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
let containersStatsES = null;
|
let containersStatsES = null;
|
||||||
let containersC2P = new Map();
|
|
||||||
|
|
||||||
function startContainersStatsStream() {
|
function startContainersStatsStream() {
|
||||||
if (containersStatsES) return;
|
if (containersStatsES) return;
|
||||||
@@ -682,7 +566,13 @@
|
|||||||
containersStatsES = new EventSource("/api/containers/stats/stream?interval=1");
|
containersStatsES = new EventSource("/api/containers/stats/stream?interval=1");
|
||||||
|
|
||||||
containersStatsES.addEventListener("stats", (ev) => {
|
containersStatsES.addEventListener("stats", (ev) => {
|
||||||
const payload = JSON.parse(ev.data);
|
let payload;
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(ev.data);
|
||||||
|
} catch (e) {
|
||||||
|
// Slechte SSE chunk -> negeren zodat de stream niet "stilvalt"
|
||||||
|
return;
|
||||||
|
}
|
||||||
const statsList = payload?.data?.Stats || [];
|
const statsList = payload?.data?.Stats || [];
|
||||||
// totals per pod voor deze SSE tick
|
// totals per pod voor deze SSE tick
|
||||||
const podCpu = new Map(); // podName -> cpuPct sum
|
const podCpu = new Map(); // podName -> cpuPct sum
|
||||||
@@ -802,49 +692,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Systemd UI storage ----
|
|
||||||
const LS_KEY = 'mvp_systemd_units_v1';
|
|
||||||
function loadDefaultUnits() {
|
|
||||||
const defaults = ["demo1.service","demo2.service","sonarr.service"];
|
|
||||||
document.getElementById('systemdUnits').value = defaults.join("\n");
|
|
||||||
saveSystemdUnits();
|
|
||||||
}
|
|
||||||
function saveSystemdUnits() {
|
|
||||||
const raw = document.getElementById('systemdUnits').value || '';
|
|
||||||
const units = raw.split('\n').map(x => x.trim()).filter(Boolean);
|
|
||||||
localStorage.setItem(LS_KEY, JSON.stringify(units));
|
|
||||||
systemdRenderRows(units);
|
|
||||||
refreshActive();
|
|
||||||
}
|
|
||||||
async function getSystemdUnitsFromServer() {
|
|
||||||
const data = await api('/systemd/allowlist', 'GET');
|
|
||||||
const units = Array.isArray(data.units) ? data.units : [];
|
|
||||||
// vul textarea ook
|
|
||||||
const ta = document.getElementById('systemdUnits');
|
|
||||||
if (ta) ta.value = units.join("\n");
|
|
||||||
return units;
|
|
||||||
}
|
|
||||||
function systemdRenderRows(units) {
|
|
||||||
const tbody = document.getElementById('systemdTbody');
|
|
||||||
tbody.innerHTML = units.map(u => `
|
|
||||||
<tr>
|
|
||||||
<td><strong class="mono">${esc(u)}</strong></td>
|
|
||||||
<td class="muted mono" id="sys-out-${cssSafeId(u)}">-</td>
|
|
||||||
<td>
|
|
||||||
<div class="flex">
|
|
||||||
<button class="btn small" onclick="systemdAction('status','${esc(u)}')">Status</button>
|
|
||||||
<button class="btn small ok" onclick="systemdAction('start','${esc(u)}')">Start</button>
|
|
||||||
<button class="btn small warn" onclick="systemdAction('restart','${esc(u)}')">Restart</button>
|
|
||||||
<button class="btn small bad" onclick="systemdAction('stop','${esc(u)}')">Stop</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function cssSafeId(s){
|
function cssSafeId(s){
|
||||||
// simpele safe id: base64-ish
|
const bytes = new TextEncoder().encode(String(s));
|
||||||
return btoa(unescape(encodeURIComponent(s))).replaceAll('=','').replaceAll('+','-').replaceAll('/','_');
|
let bin = '';
|
||||||
|
bytes.forEach(b => bin += String.fromCharCode(b));
|
||||||
|
return btoa(bin).replaceAll('=','').replaceAll('+','-').replaceAll('/','_');
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeContainerName(n) {
|
function normalizeContainerName(n) {
|
||||||
@@ -861,11 +713,6 @@
|
|||||||
return n + "B";
|
return n + "B";
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeUnit(unit) {
|
|
||||||
// encodeURIComponent is genoeg voor @ en .
|
|
||||||
return encodeURIComponent(unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function daemonReload() {
|
async function daemonReload() {
|
||||||
try {
|
try {
|
||||||
const res = await api('/daemon-reload','POST');
|
const res = await api('/daemon-reload','POST');
|
||||||
@@ -875,67 +722,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function systemdAction(action, unit) {
|
|
||||||
try {
|
|
||||||
const res = await api(`/${encodeURIComponent(action)}/${encodeUnit(unit)}`, 'POST');
|
|
||||||
// res.output kan lang zijn
|
|
||||||
showModal(`systemctl ${action} ${unit}`, (res.output ?? JSON.stringify(res, null, 2)));
|
|
||||||
// update inline status cell
|
|
||||||
const cell = document.getElementById('sys-out-' + cssSafeId(unit));
|
|
||||||
if (cell) {
|
|
||||||
const summary = (res.output || '').split('\n').slice(0,3).join(' / ') || '(geen output)';
|
|
||||||
cell.textContent = summary;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showModal(`systemctl ${action} fout`, e.stack || e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function systemdActionSingle(action) {
|
|
||||||
const unit = (document.getElementById('systemdOne').value || '').trim();
|
|
||||||
if (!unit) return showModal('Systemd', 'Vul eerst een unit in (bijv. sonarr.service).');
|
|
||||||
await systemdAction(action, unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function systemdRefresh() {
|
|
||||||
const units = await getSystemdUnitsFromServer();
|
|
||||||
systemdRenderRows(units);
|
|
||||||
document.getElementById('countSystemd').textContent = units.length;
|
|
||||||
|
|
||||||
for (const u of units) {
|
|
||||||
try {
|
|
||||||
const res = await api(`/status/${encodeUnit(u)}`, 'POST');
|
|
||||||
const cell = document.getElementById('sys-out-' + cssSafeId(u));
|
|
||||||
if (cell) {
|
|
||||||
const first = (res.output || '').split('\n')[0] || '';
|
|
||||||
const activeLine = (res.output || '').split('\n').find(x => x.trim().startsWith('Active:')) || '';
|
|
||||||
cell.textContent = (first + ' | ' + activeLine).trim();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const cell = document.getElementById('sys-out-' + cssSafeId(u));
|
|
||||||
if (cell) cell.textContent = 'ERROR: ' + e.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function systemdMiniRefresh() {
|
|
||||||
const units = await getSystemdUnitsFromServer();
|
|
||||||
const mini = document.getElementById('systemdMini');
|
|
||||||
if (!mini) return;
|
|
||||||
|
|
||||||
const lines = [];
|
|
||||||
for (const u of units.slice(0, 6)) {
|
|
||||||
try {
|
|
||||||
const res = await api(`/status/${encodeUnit(u)}`, 'POST');
|
|
||||||
const activeLine = (res.output || '').split('\n').find(x => x.trim().startsWith('Active:')) || '';
|
|
||||||
lines.push(`${u}: ${activeLine.replace('Active:','').trim() || 'unknown'}`);
|
|
||||||
} catch (e) {
|
|
||||||
lines.push(`${u}: ERROR (${e.message})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mini.innerHTML = `<pre>${esc(lines.join('\n'))}</pre>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// Files tab (systemd subtree)
|
// Files tab (systemd subtree)
|
||||||
// =========================
|
// =========================
|
||||||
@@ -1002,8 +788,8 @@
|
|||||||
<span>📂 ${esc(folderLabel)}</span>
|
<span>📂 ${esc(folderLabel)}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex" onclick="event.stopPropagation();">
|
<span class="flex" onclick="event.stopPropagation();">
|
||||||
<button class="btn small ok" title="Nieuw bestand in ${esc(folderLabel)}" onclick="filesNewFileInFolder('${esc(uiFolderPath)}')">+</button>
|
<button class="btn small ok" title="Nieuw bestand in ${esc(folderLabel)}" onclick="filesNewFileInFolder(decodeURIComponent('${encodeURIComponent(uiFolderPath)}'))">+</button>
|
||||||
<button class="btn small bad" title="Verwijder map (alleen als leeg)" onclick="filesDeleteFolder('${esc(uiFolderPath)}')">🗑️</button>
|
<button class="btn small bad" title="Verwijder map (alleen als leeg)" onclick="filesDeleteFolder(decodeURIComponent('${encodeURIComponent(uiFolderPath)}'))">🗑️</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
@@ -1020,7 +806,7 @@
|
|||||||
const fullUi = uiFolderPath ? `${uiFolderPath}/${f}` : f;
|
const fullUi = uiFolderPath ? `${uiFolderPath}/${f}` : f;
|
||||||
parts.push(`
|
parts.push(`
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 0;border-bottom:1px dashed rgba(36,52,95,.35)">
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 0;border-bottom:1px dashed rgba(36,52,95,.35)">
|
||||||
<span class="mono" style="cursor:pointer" onclick="filesOpen('${esc(fullUi)}')">📄 ${esc(f)}</span>
|
<span class="mono" style="cursor:pointer" onclick="filesOpen(decodeURIComponent('${encodeURIComponent(fullUi)}'))">📄 ${esc(f)}</span>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
@@ -1164,10 +950,6 @@
|
|||||||
applySidebarState();
|
applySidebarState();
|
||||||
const t = document.getElementById('sidebarToggle');
|
const t = document.getElementById('sidebarToggle');
|
||||||
if (t) t.onclick = toggleSidebar;
|
if (t) t.onclick = toggleSidebar;
|
||||||
// preload systemd units UI
|
|
||||||
const units = await getSystemdUnitsFromServer();
|
|
||||||
systemdRenderRows(units);
|
|
||||||
document.getElementById('countSystemd').textContent = units.length;
|
|
||||||
|
|
||||||
// Files editor: CodeMirror init (alleen als textarea bestaat)
|
// Files editor: CodeMirror init (alleen als textarea bestaat)
|
||||||
const taFiles = document.getElementById('filesEditor');
|
const taFiles = document.getElementById('filesEditor');
|
||||||
|
|||||||
Reference in New Issue
Block a user