]> git.giorgioravera.it Git - network-manager.git/commitdiff
Log & Settings Review
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Fri, 24 Jul 2026 09:21:55 +0000 (11:21 +0200)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Fri, 24 Jul 2026 09:21:55 +0000 (11:21 +0200)
frontend/aliases.html
frontend/devices.html
frontend/hosts.html
frontend/js/logs.js
frontend/js/settings.js
frontend/leases.html
frontend/logs.html
frontend/modals.html
frontend/settings.html

index a4e11aac725bb8a6f72e4323615345db3f10c38b..0c87f1f8505906bbd3735beb0b1900194e079d53 100644 (file)
@@ -52,6 +52,7 @@
                         <a href="/aliases"  class="btn btn-primary active">Alias</a>
                         <a href="/leases"   class="btn btn-primary">       DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary">       Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary">       Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                 </div>
@@ -76,6 +77,7 @@
                         <a href="/aliases"  class="btn btn-primary active">Alias</a>
                         <a href="/leases"   class="btn btn-primary">       DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary">       Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary">       Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                     </div>
index 247d97ea582cf108420e502eec8686c1b1b453f5..8c686588869a421ac6e1f08df09db5c71a70bd0a 100644 (file)
@@ -52,6 +52,7 @@
                         <a href="/aliases"  class="btn btn-primary">       Alias</a>
                         <a href="/leases"   class="btn btn-primary">       DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary active">Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary">       Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                 </div>
@@ -76,6 +77,7 @@
                         <a href="/aliases"  class="btn btn-primary">       Alias</a>
                         <a href="/leases"   class="btn btn-primary">       DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary active">Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary">       Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                     </div>
index cbd2103508e60bb08b8d1eca366c4c5024a5036b..ddfd8944988cae2c74a2028f9bf26370232f18a6 100644 (file)
@@ -52,6 +52,7 @@
                         <a href="/aliases"  class="btn btn-primary">       Alias</a>
                         <a href="/leases"   class="btn btn-primary">       DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary">       Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary">       Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                 </div>
@@ -76,6 +77,7 @@
                         <a href="/aliases"  class="btn btn-primary">       Alias</a>
                         <a href="/leases"   class="btn btn-primary">       DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary">       Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary">       Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                     </div>
index 7f85a7c4d6b88e1a97949104a551c507487ed588..5067eefc947a2e1dcf2ec78c660e5bf3be2e7847 100644 (file)
@@ -10,10 +10,106 @@ const logViewer = document.getElementById("logViewer");
 const loader = document.getElementById("loader");
 let selectedLogType = "app";
 let live = true;
+// concurrent requests
 let requestId = 0;
 let loading = false;
+// selection
 let selectedRows = new Set();
 let lastSelectedRowId = null;
+// search
+let searchText = "";
+let levelFilters = new Set();
+// scroll
+let savedScrollPosition = null;
+
+// -----------------------------
+// manage the live stream
+// -----------------------------
+function handleLiveToggle() {
+    const btn = document.querySelector('[data-action="liveToggle"]');
+    const icon = btn.querySelector('i');
+
+    live = !live;
+
+    btn.classList.toggle('btn-success', live);
+    btn.classList.toggle('btn-outline-secondary', !live);
+
+    icon.className = live
+        ? 'bi bi-broadcast'
+        : 'bi bi-pause-circle';
+}
+
+// -----------------------------
+// apply filter
+// -----------------------------
+function applyLogFilters() {
+
+    const rows = document.querySelectorAll(".log-row");
+
+    rows.forEach(row => {
+
+        const text = row.textContent.toLowerCase();
+
+        const level = row.dataset.level;
+
+        const matchesText =
+            !searchText ||
+            text.includes(searchText);
+
+        const matchesLevel =
+            levelFilters.size === 0 ||
+            levelFilters.has(level);
+
+        row.style.display =
+            matchesText && matchesLevel
+                ? ""
+                : "none";
+    });
+}
+
+// -----------------------------
+// update filter button
+// -----------------------------
+function updateFilterButton() {
+    const btn = document.getElementById("filterBtn");
+    if (!btn) return;
+
+    const icon = btn.querySelector("i");
+
+    const active =
+        searchText.length > 0 ||
+        levelFilters.size > 0;
+
+    icon.className = active
+        ? "bi bi-funnel-fill"
+        : "bi bi-sliders";
+}
+
+// -----------------------------
+// clear filter
+// -----------------------------
+function clearFilters() {
+    searchText = "";
+    levelFilters.clear();
+
+    const searchInput =
+        document.getElementById("logSearch");
+
+    if (searchInput) {
+        searchInput.value = "";
+    }
+
+    document
+        .querySelectorAll(".level-filter")
+        .forEach(cb => cb.checked = false);
+
+    applyLogFilters();
+    updateFilterButton();
+
+    requestAnimationFrame(() => {
+        logViewer.scrollTop = savedScrollPosition;
+    });
+}
 
 // -----------------------------
 // load logs from backend
@@ -54,7 +150,6 @@ async function loadLogs() {
 // shows logs in the table
 // -----------------------------
 function renderLogs(text, isNearBottom, previousScroll, previousHeight) {
-
     const lines = text.split("\n");
     const fragment = document.createDocumentFragment();
 
@@ -77,8 +172,11 @@ function renderLogs(text, isNearBottom, previousScroll, previousHeight) {
             // define rowID as combination of the log
             const rowId = `${date}|${level}|${source}|${msg}`;
 
+            // add field for selection and search
             div.dataset.index = index;
             div.dataset.rowId = rowId;
+            div.dataset.level = level;
+            div.dataset.source = source;
 
             div.classList.add("log-row");
 
@@ -90,17 +188,18 @@ function renderLogs(text, isNearBottom, previousScroll, previousHeight) {
 
                     const rows = Array.from(
                         document.querySelectorAll(".log-row")
+                    ).filter(
+                        row => row.style.display !== "none"
                     );
 
-                    const currentIndex = Number(div.dataset.index);
-
                     const lastRow = rows.find(
                         r => r.dataset.rowId === lastSelectedRowId
                     );
 
                     if (lastRow) {
 
-                        const lastIndex = Number(lastRow.dataset.index);
+                        const currentIndex = rows.indexOf(div);
+                        const lastIndex    = rows.indexOf(lastRow);
 
                         const start = Math.min(lastIndex, currentIndex);
                         const end   = Math.max(lastIndex, currentIndex);
@@ -185,6 +284,8 @@ function renderLogs(text, isNearBottom, previousScroll, previousHeight) {
     logViewer.innerHTML = "";
     logViewer.appendChild(fragment);
 
+    // apply current filter
+    applyLogFilters();
 
     if (isNearBottom) {
         // "tail -f"
@@ -194,7 +295,7 @@ function renderLogs(text, isNearBottom, previousScroll, previousHeight) {
         const newHeight = logViewer.scrollHeight;
         if (newHeight < previousHeight) {
             // log rotation o reset
-            logViewer.scrollTop = 0;
+            logViewer.scrollTop = Math.min(previousScroll, newHeight);
         } else {
             const diff = newHeight - previousHeight;
             logViewer.scrollTop = previousScroll + diff;
@@ -328,6 +429,10 @@ async function handleRestartApp(button) {
 // Action Handlers
 // -----------------------------
 const actionHandlers = {
+    // Live Stream
+    liveToggle: (e, el) => {
+        handleLiveToggle();
+    },
     // Refresh Logs
     refreshLogs: (e, el) => {
         loadLogs();
@@ -338,6 +443,7 @@ const actionHandlers = {
     //},
     // Reload DNS
     reloadDns: async (e, el) => {
+        showToast("DNS is reloading...", true);
         await handleReload(
             el,
             serviceReloadDNS,
@@ -348,6 +454,7 @@ const actionHandlers = {
     },
     // Reload DHCP
     reloadDhcp: async (e, el) => {
+        showToast("DHCP is reloading...", true);
         await handleReload(
             el,
             serviceReloadDHCP,
@@ -356,7 +463,7 @@ const actionHandlers = {
             "Reloading DHCP..."
         );
     },
-    // Reload DHCP
+    // Reload App
     restartApp: async (e, el) => {
         await handleRestartApp(el)
     },
@@ -385,6 +492,7 @@ async function initApp() {
 
     initEvents();
     initDropdown();
+    initFilters();
 
     // auto refresh (tipo tail -f)
     setInterval(() => {
@@ -396,15 +504,15 @@ async function initApp() {
 // GLOBAL EVENTS INIT
 // -----------------------------
 function initEvents() {
-
+    // Click
     document.addEventListener('click', handleActionClick);
 
-    const liveToggle = document.getElementById("liveToggle");
-    if (liveToggle) {
-        liveToggle.addEventListener("change", (e) => {
-            live = e.target.checked;
-        });
-    }
+    // Esc button
+    document.addEventListener('keydown', e => {
+        if (e.key === 'Escape') {
+            clearFilters();
+        }
+    });
 }
 
 // -----------------------------
@@ -459,3 +567,41 @@ function initDropdown() {
 
         });
 }
+
+// -----------------------------
+// Init filters
+// -----------------------------
+function initFilters() {
+
+    const searchInput =
+        document.getElementById("logSearch");
+
+    if (searchInput) {
+        searchInput.addEventListener("input", e => {
+            if (!searchText) {
+                savedScrollPosition = logViewer.scrollTop;
+            }
+            searchText = e.target.value.trim().toLowerCase();
+            applyLogFilters();
+            updateFilterButton();
+        });
+    }
+
+    document
+        .querySelectorAll(".level-filter")
+        .forEach(cb => {
+            cb.addEventListener("change", () => {
+                if (levelFilters.size === 0 && searchText.length === 0 ) {
+                    savedScrollPosition = logViewer.scrollTop;
+                }
+                levelFilters.clear();
+                document
+                    .querySelectorAll(".level-filter:checked")
+                    .forEach(x => levelFilters.add(x.value));
+                applyLogFilters();
+                updateFilterButton();
+            });
+        });
+
+    updateFilterButton();
+}
\ No newline at end of file
index 498c3bd790a4c7c7b2f5157fd7d7ccdd36db4d8e..3f4a17de8149694d4d3780b1c1aea1a1755cbdfc 100644 (file)
@@ -1,7 +1,7 @@
 // Import common js
 import { loadModals, showToast, handleSearch, clearSearch, showConfirmModal, handleReload } from './common.js';
 // Import services
-import { serviceGetConfigs, serviceGetConfig, serviceUpdateConfig, serviceResetConfig, serviceRestartApp, serviceIsAlive } from './services.js';
+import { serviceGetConfigs, serviceGetConfig, serviceUpdateConfig, serviceResetConfig, serviceReloadDNS, serviceReloadDHCP, serviceRestartApp, serviceIsAlive } from './services.js';
 
 // -----------------------------
 // State variables
@@ -693,11 +693,32 @@ const actionHandlers = {
     edit: () => {
         // handled by bootstrap modal show event
     },
+    reloadDns: async (e, el) => {
+        showToast("DNS is reloading...", true);
+        await handleReload(
+            el,
+            serviceReloadDNS,
+            "DNS reload successfully",
+            "Error reloading DNS",
+            "Reloading DNS..."
+        );
+    },
+    // Reload DHCP
+    reloadDhcp: async (e, el) => {
+        showToast("DHCP is reloading...", true);
+        await handleReload(
+            el,
+            serviceReloadDHCP,
+            "DHCP reload successfully",
+            "Error reloading DHCP",
+            "Reloading DHCP..."
+        );
+    },
     // Reload App
     restartApp: async (e, el) => {
         await handleRestartApp(el)
     },
-}
+};
 
 // -----------------------------
 // DOMContentLoaded: bootstrap app
index 85fa018d066b65762c6d112c5c4960c78c6bc055..065cd3f01bcd5ee2d09aea5833664de892f9fc39 100644 (file)
@@ -52,6 +52,7 @@
                         <a href="/aliases"  class="btn btn-primary">       Alias</a>
                         <a href="/leases"   class="btn btn-primary active">DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary">       Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary">       Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                 </div>
@@ -76,6 +77,7 @@
                         <a href="/aliases"  class="btn btn-primary">       Alias</a>
                         <a href="/leases"   class="btn btn-primary active">DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary">       Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary">       Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                     </div>
index 7d2b4aa67d945ef57688ac828449304aec26dc8d..7f0bd3eee11cde95eb6d77b13175a2a70c748bcf 100644 (file)
                 <!-- Bottoni -->
                 <div class="col-auto col-md-auto d-flex align-items-center gap-2 flex-nowrap">
 
-                    <!-- Live Flag -->
-                    <div class="gap-2">
-                        <input type="checkbox" id="liveToggle" checked> Live
+                    <!-- View Panel -->
+                    <div id="viewDropdown" class="dropdown">
+                        <button
+                            id="viewBtn"
+                            class="btn btn-primary dropdown-toggle d-flex align-items-center gap-2"
+                            data-bs-toggle="dropdown">
+                            <i class="bi bi-eye"></i>
+                            <span class="label d-none d-md-inline">View</span>
+                        </button>
+
+                        <ul class="dropdown-menu dropdown-menu-end shadow">
+
+                            <!-- Live Flag -->
+                            <li class="px-2 py-1">
+                                <button
+                                    class="btn btn-success w-100 d-flex align-items-center gap-2"
+                                    title="Live Stream On"
+                                    aria-label="Live Stream On"
+                                    data-action="liveToggle">
+                                    <i class="bi bi-broadcast"></i>
+                                    <span class="label d-md-inline">Live</span>
+                                </button>
+                            </li>
+
+                            <!-- Refresh -->
+                            <li class="px-2 py-1">
+                                <button
+                                    class="btn btn-primary w-100 d-flex align-items-center gap-2"
+                                    title="Refresh"
+                                    aria-label="Refresh"
+                                    data-action="refreshLogs">
+                                    <i class="bi bi-arrow-repeat"></i>
+                                    <span class="label d-md-inline">Refresh</span>
+                                </button>
+                            </li>
+                        </ul>
                     </div>
 
-                    <!-- LOG TYPE -->
+                    <!-- Log Type -->
                     <div id="logTypeDropdown" class="dropdown">
                         <button
                             id="logTypeBtn"
                             <i class="bi bi-file-text"></i>
                             <span class="label d-none d-md-inline">App</span>
                         </button>
-                        <ul class="dropdown-menu">
+                        <ul class="dropdown-menu dropdown-menu-end shadow">
                             <li><a class="dropdown-item active" data-value="app" href="#">App</a></li>
                             <li><a class="dropdown-item" data-value="access" href="#">Access</a></li>
                             <li><a class="dropdown-item" data-value="dhcp" href="#">DHCP</a></li>
                         </ul>
                     </div>
 
-                    <!-- Refresh -->
-                    <button
-                        class="btn btn-primary d-none d-md-flex align-items-center gap-2 px-3"
-                        title="Refresh"
-                        aria-label="Refresh"
-                        data-action="refreshLogs">
-                        <i class="bi bi-arrow-repeat"></i>
-                        <span class="label">Refresh</span>
-                    </button>
-
-                    <!-- Separator -->
-                    <div class="vr mx-2 d-none d-md-block"></div>
-
-                    <!-- Reload DNS Desktop -->
-                    <button
-                        class="btn btn-primary d-none d-md-flex align-items-center gap-2 px-3"
-                        title="Reload DNS (BIND)"
-                        aria-label="Reload DNS"
-                        data-action="reloadDns">
-                        <i class="bi bi-arrow-repeat"></i>
-                        <span class="label">Reload DNS</span>
-                    </button>
+                    <!-- Filter -->
+                    <div class="dropdown">
+                        <button
+                            id="filterBtn"
+                            class="btn btn-primary dropdown-toggle d-flex align-items-center gap-2 px-3"
+                            data-bs-toggle="dropdown">
+                            <i class="bi bi-sliders"></i>
+                            <span class="label d-none d-md-inline">Filter</span>
+                        </button>
 
-                    <!-- Reload DHCP Desktop -->
-                    <button
-                        class="btn btn-primary d-none d-md-flex align-items-center gap-2 px-3"
-                        title="Reload DHCP (Kea)"
-                        aria-label="Reload DHCP"
-                        data-action="reloadDhcp">
-                        <i class="bi bi-arrow-repeat"></i>
-                        <span class="label">Reload DHCP</span>
-                    </button>
+                        <div class="dropdown-menu dropdown-menu-end shadow p-3" id="filterDropdown">
+                            <!-- Search Bar -->
+                            <div class="mb-2">
+                                <label class="form-label small">Search</label>
+                                <input
+                                    type="text"
+                                    id="logSearch"
+                                    class="form-control mb-2"
+                                    placeholder="Search logs">
+                            </div>
+
+                            <!-- Separator -->
+                            <hr class="dropdown-divider">
+
+                            <div class="mb-2">
+                                <!-- Log Level -->
+                                <div class="form-check">
+                                    <input class="form-check-input level-filter"
+                                        type="checkbox"
+                                        value="INFO"
+                                        id="filterInfo">
+                                    <label class="form-check-label" for="filterInfo">INFO</label>
+                                </div>
+
+                                <div class="form-check">
+                                    <input class="form-check-input level-filter"
+                                        type="checkbox"
+                                        value="WARN"
+                                        id="filterWarn">
+                                    <label class="form-check-label" for="filterWarn">WARN</label>
+                                </div>
+
+                                <div class="form-check">
+                                    <input class="form-check-input level-filter"
+                                        type="checkbox"
+                                        value="ERROR"
+                                        id="filterError">
+                                    <label class="form-check-label" for="filterError">ERROR</label>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
 
                     <!-- Separator -->
                     <div class="vr mx-2 d-none d-md-block"></div>
 
-                    <!-- Restart Desktop -->
-                    <button
-                        class="btn btn-danger d-none d-md-flex align-items-center gap-2 px-3"
-                        title="Restart system"
-                        aria-label="Restart system"
-                        data-action="restartApp">
-                        <i class="bi bi-arrow-repeat"></i>
-                        <span class="label">Restart</span>
-                    </button>
-
-                    <!-- Mobile Dropdown -->
-                    <div class="dropdown d-md-none">
+                    <!-- Admin Panel -->
+                    <div id="adminDropdown" class="dropdown">
                         <button
-                            class="btn btn-primary d-flex align-items-center px-3"
-                            type="button"
-                            data-bs-toggle="dropdown"
-                            aria-expanded="false">
-                            <i class="bi bi-three-dots-vertical"></i>
+                            id="adminBtn"
+                            class="btn btn-primary dropdown-toggle d-flex align-items-center gap-2 px-3"
+                            data-bs-toggle="dropdown">
+                            <i class="bi bi-gear"></i>
+                            <span class="label d-none d-md-inline">Admin</span>
                         </button>
 
                         <ul class="dropdown-menu dropdown-menu-end shadow">
 
-                            <!-- Reload DNS Mobile -->
+                            <!-- Reload DNS -->
                             <li class="px-2 py-1">
                                 <button
-                                    class="btn btn-primary w-100 d-flex align-items-center gap-2"
+                                    class="btn btn-primary w-100 d-flex align-items-center gap-2 text-nowrap"
                                     title="Reload DNS (BIND)"
                                     aria-label="Reload DNS"
                                     data-action="reloadDns">
-                                    <i class="bi bi-arrow-repeat"></i>
-                                    <span>Reload DNS</span>
+                                    <i class="bi bi-arrow-clockwise"></i>
+                                    <span class="label d-md-inline">Reload DNS</span>
                                 </button>
                             </li>
 
-                            <!-- Reload DHCP Mobile -->
+                            <!-- Reload DHCP -->
                             <li class="px-2 py-1">
                                 <button
-                                    class="btn btn-primary w-100 d-flex align-items-center gap-2"
+                                    class="btn btn-primary w-100 d-flex align-items-center gap-2 text-nowrap"
                                     title="Reload DHCP (Kea)"
                                     aria-label="Reload DHCP"
                                     data-action="reloadDhcp">
-                                    <i class="bi bi-arrow-repeat"></i>
-                                    <span>Reload DHCP</span>
+                                    <i class="bi bi-arrow-clockwise"></i>
+                                    <span class="label d-md-inline">Reload DHCP</span>
                                 </button>
                             </li>
 
-                            <!-- Restart Mobile -->
+                            <!-- Separator -->
+                            <li><hr class="dropdown-divider"></li>
+
+                            <!-- Restart -->
                             <li class="px-2 py-1">
                                 <button
-                                    class="btn btn-danger w-100 d-flex align-items-center gap-2"
+                                    class="btn btn-danger w-100 d-flex align-items-center gap-2 text-nowrap"
                                     title="Restart system"
                                     aria-label="Restart system"
                                     data-action="restartApp">
-                                    <i class="bi bi-arrow-repeat"></i>
-                                    <span class="label">Restart</span>
+                                    <i class="bi bi-power"></i>
+                                    <span class="label d-md-inline">Restart</span>
                                 </button>
                             </li>
                         </ul>
index ab9a66854c4b67866b1f3fec4825bbc494e6b677..b468c619adf18bc69e7af3c5f9724533fb48647d 100644 (file)
                             <th style="width: 40px;"></th>
                             <th>Name</th>
                             <th>Date</th>
-                            <th class="text-end">Size</th>
-                            <th style="width: 120px;" class="text-end">Actions</th>
+                            <th>Size</th>
+                            <th style="width: 130px;" class="text-center">Actions</th>
                         </tr>
                     </thead>
                     <tbody id="backupList">
index 8e03d90aadcdd9c8f0e58d26f82f8effa66d662e..66cc3b3106ff833eb281ac9652107f9cf449b857 100644 (file)
@@ -52,6 +52,7 @@
                         <a href="/aliases"  class="btn btn-primary">       Alias</a>
                         <a href="/leases"   class="btn btn-primary">       DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary">       Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary active">Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                 </div>
@@ -76,6 +77,7 @@
                         <a href="/aliases"  class="btn btn-primary">       Alias</a>
                         <a href="/leases"   class="btn btn-primary">       DHCP Leases</a>
                         <a href="/devices"  class="btn btn-primary">       Devices</a>
+                        <a href="/logs"     class="btn btn-primary">       Logs</a>
                         <a href="/settings" class="btn btn-primary active">Settings</a>
                         <button class="btn btn-primary btn-logout">Logout</button>
                     </div>
                     <!-- Separator -->
                     <div class="vr mx-2 d-none d-md-block"></div>
 
-                    <!-- Restart Desktop -->
-                    <button
-                        class="btn btn-danger d-none d-md-flex align-items-center gap-2 px-3"
-                        title="Restart system"
-                        aria-label="Restart system"
-                        data-action="restartApp">
-                        <i class="bi bi-arrow-repeat"></i>
-                        <span class="label">Restart</span>
-                    </button>
+                    <!-- Admin Panel -->
+                    <div id="adminDropdown" class="dropdown">
+                        <button
+                            id="adminBtn"
+                            class="btn btn-primary dropdown-toggle d-none d-md-flex align-items-center gap-2 px-3"
+                            data-bs-toggle="dropdown">
+                            <i class="bi bi-gear"></i>
+                            <span class="label d-none d-md-inline">Admin</span>
+                        </button>
+
+                        <ul class="dropdown-menu dropdown-menu-end shadow">
+
+                            <!-- Reload DNS -->
+                            <li class="px-2 py-1">
+                                <button
+                                    class="btn btn-primary w-100 d-flex align-items-center gap-2 text-nowrap"
+                                    title="Reload DNS (BIND)"
+                                    aria-label="Reload DNS"
+                                    data-action="reloadDns">
+                                    <i class="bi bi-arrow-clockwise"></i>
+                                    <span class="label d-md-inline">Reload DNS</span>
+                                </button>
+                            </li>
+
+                            <!-- Reload DHCP -->
+                            <li class="px-2 py-1">
+                                <button
+                                    class="btn btn-primary w-100 d-flex align-items-center gap-2 text-nowrap"
+                                    title="Reload DHCP (Kea)"
+                                    aria-label="Reload DHCP"
+                                    data-action="reloadDhcp">
+                                    <i class="bi bi-arrow-clockwise"></i>
+                                    <span class="label d-md-inline">Reload DHCP</span>
+                                </button>
+                            </li>
+
+                            <!-- Separator -->
+                            <li><hr class="dropdown-divider"></li>
+
+                            <!-- Restart -->
+                            <li class="px-2 py-1">
+                                <button
+                                    class="btn btn-danger w-100 d-flex align-items-center gap-2 text-nowrap"
+                                    title="Restart system"
+                                    aria-label="Restart system"
+                                    data-action="restartApp">
+                                    <i class="bi bi-power"></i>
+                                    <span class="label d-md-inline">Restart</span>
+                                </button>
+                            </li>
+                        </ul>
+                    </div>
 
                     <!-- Mobile Dropdown -->
                     <div class="dropdown d-md-none">
                                 </button>
                             </li>
 
+                            <!-- Separator -->
+                            <li><hr class="dropdown-divider"></li>
+
+                            <!-- Reload DNS Mobile -->
+                            <li class="px-2 py-1">
+                                <button
+                                    class="btn btn-primary w-100 d-flex align-items-center gap-2"
+                                    title="Reload DNS (BIND)"
+                                    aria-label="Reload DNS"
+                                    data-action="reloadDns">
+                                    <i class="bi bi-arrow-clockwise"></i>
+                                    <span class="label d-md-inline">Reload DNS</span>
+                                </button>
+                            </li>
+
+                            <!-- Reload DHCP Mobile -->
+                            <li class="px-2 py-1">
+                                <button
+                                    class="btn btn-primary w-100 d-flex align-items-center gap-2"
+                                    title="Reload DHCP (Kea)"
+                                    aria-label="Reload DHCP"
+                                    data-action="reloadDhcp">
+                                    <i class="bi bi-arrow-clockwise"></i>
+                                    <span class="label d-md-inline">Reload DHCP</span>
+                                </button>
+                            </li>
+
+                            <!-- Separator -->
+                            <li><hr class="dropdown-divider"></li>
+
                             <!-- Restart Mobile -->
                             <li class="px-2 py-1">
                                 <button