diff --git a/modules/pihole/assets/pihole.css b/modules/pihole/assets/pihole.css
index 3775e1e..6e00a93 100644
--- a/modules/pihole/assets/pihole.css
+++ b/modules/pihole/assets/pihole.css
@@ -198,6 +198,47 @@
color: var(--muted);
}
+.pihole-console-modal {
+ width: min(720px, 96vw);
+}
+
+.pihole-console-body {
+ margin-top: 16px;
+ display: grid;
+ gap: 10px;
+ max-height: 48vh;
+ overflow: auto;
+ padding: 4px;
+}
+
+.pihole-console-line {
+ display: grid;
+ gap: 4px;
+ padding: 12px 14px;
+ border-radius: 14px;
+ border: 1px solid var(--line);
+ background: rgba(255, 255, 255, 0.6);
+}
+
+.pihole-console-line span {
+ font-size: 0.8rem;
+ color: var(--muted);
+}
+
+.pihole-console-line strong {
+ font-weight: 600;
+}
+
+.pihole-console-line.is-success {
+ border-color: rgba(0, 179, 164, 0.22);
+ background: rgba(0, 179, 164, 0.08);
+}
+
+.pihole-console-line.is-error {
+ border-color: rgba(255, 90, 61, 0.24);
+ background: rgba(255, 90, 61, 0.08);
+}
+
.pihole-instance-card {
padding: 16px;
background: var(--panel-2);
diff --git a/modules/pihole/assets/pihole.js b/modules/pihole/assets/pihole.js
index 0ba9dc7..33551a9 100644
--- a/modules/pihole/assets/pihole.js
+++ b/modules/pihole/assets/pihole.js
@@ -19,6 +19,9 @@
let refreshTimer = null;
let loadInFlight = false;
let actionInFlight = false;
+ let actionConsoleApi = null;
+ let actionConsoleBody = null;
+ let actionConsoleClose = null;
const apiCall = async (action, payload = {}) => {
const res = await fetch(`/module/pihole/api?action=${encodeURIComponent(action)}`,
@@ -47,9 +50,82 @@
el.textContent = value;
};
+ const ensureActionConsole = () => {
+ if (actionConsoleApi && actionConsoleBody && actionConsoleClose) {
+ return;
+ }
+
+ const root = document.createElement('div');
+ root.className = 'modal';
+ root.dataset.piholeConsoleModal = '1';
+ root.setAttribute('aria-hidden', 'true');
+ root.innerHTML = `
+
+ `;
+ document.body.appendChild(root);
+
+ actionConsoleBody = root.querySelector('[data-pihole-console-body]');
+ actionConsoleClose = root.querySelector('[data-pihole-console-close]');
+ const clearBtn = root.querySelector('[data-pihole-console-clear]');
+
+ actionConsoleApi = window.NexusModal && typeof window.NexusModal.create === 'function'
+ ? window.NexusModal.create(root, { initialFocus: '[data-pihole-console-close]' })
+ : {
+ open() {
+ root.classList.add('is-open');
+ root.setAttribute('aria-hidden', 'false');
+ },
+ close() {
+ root.classList.remove('is-open');
+ root.setAttribute('aria-hidden', 'true');
+ },
+ };
+
+ if (actionConsoleClose) {
+ actionConsoleClose.addEventListener('click', () => {
+ if (!actionInFlight) {
+ actionConsoleApi.close();
+ }
+ });
+ }
+
+ if (clearBtn) {
+ clearBtn.addEventListener('click', () => {
+ if (actionConsoleBody) {
+ actionConsoleBody.innerHTML = '';
+ }
+ });
+ }
+ };
+
+ const appendActionLog = (message, tone = 'info') => {
+ ensureActionConsole();
+ if (!actionConsoleBody) {
+ return;
+ }
+ const row = document.createElement('div');
+ row.className = `pihole-console-line is-${tone}`;
+ row.innerHTML = `${new Date().toLocaleTimeString('de-DE')}${message}`;
+ actionConsoleBody.appendChild(row);
+ actionConsoleBody.scrollTop = actionConsoleBody.scrollHeight;
+ };
+
const setActionLock = (locked, message = 'Bitte warten ...') => {
actionInFlight = locked;
page.classList.toggle('is-busy', locked);
+ ensureActionConsole();
let overlay = page.querySelector('[data-pihole-busy-overlay]');
if (!overlay) {
@@ -67,6 +143,9 @@
}
overlay.hidden = !locked;
+ if (actionConsoleClose) {
+ actionConsoleClose.disabled = locked;
+ }
page.querySelectorAll('button, input, select, textarea').forEach((el) => {
const formControl = el;
@@ -229,26 +308,38 @@
: action === 'gravity'
? 'Listen werden aktualisiert ...'
: 'Aktion wird ausgefuehrt ...';
+ ensureActionConsole();
+ if (actionConsoleBody) {
+ actionConsoleBody.innerHTML = '';
+ }
+ actionConsoleApi.open();
+ appendActionLog(actionLabel, 'info');
setActionLock(true, actionLabel);
if (action === 'enable') {
+ appendActionLog(`Aktiviere ${instance === 'all' ? 'alle Instanzen' : `Instanz ${instance}`}.`, 'info');
await apiCall('enable', payload);
} else if (action === 'disable' || action === 'disable-custom') {
if (!payload.minutes || payload.minutes <= 0) {
- alert('Bitte Minuten angeben.');
+ appendActionLog('Fehler: Bitte Minuten angeben.', 'error');
return;
}
+ appendActionLog(`Deaktiviere ${instance === 'all' ? 'alle Instanzen' : `Instanz ${instance}`} fuer ${payload.minutes} Minuten.`, 'info');
await apiCall('disable', payload);
} else if (action === 'gravity') {
+ appendActionLog(`Starte Listen-Update fuer ${instance === 'all' ? 'alle Instanzen' : `Instanz ${instance}`}.`, 'info');
await apiCall('gravity', payload);
const status = document.querySelector('[data-list-update-status]');
if (status) status.textContent = 'Listen-Update gestartet.';
} else if (action === 'update') {
+ appendActionLog(`Starte Pi-hole-Update fuer ${instance === 'all' ? 'alle Instanzen' : `Instanz ${instance}`}.`, 'info');
await apiCall('update', payload);
}
+ appendActionLog('Aktion abgeschlossen. Dashboard wird aktualisiert.', 'success');
await loadDashboard();
+ appendActionLog('Anzeige erfolgreich aktualisiert.', 'success');
} catch (err) {
- alert(`Aktion fehlgeschlagen: ${err.message}`);
+ appendActionLog(`Aktion fehlgeschlagen: ${err.message}`, 'error');
} finally {
setActionLock(false);
}