diff --git a/Data/data/webserver/dashboard.html b/Data/data/webserver/dashboard.html
index 54ec3b02..d964ae23 100644
--- a/Data/data/webserver/dashboard.html
+++ b/Data/data/webserver/dashboard.html
@@ -14,9 +14,14 @@
}
.container { max-width: 1000px; margin: 0 auto; padding: 20px; }
header {
- text-align: center;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
padding: 20px 0;
}
+ .header-left {
+ text-align: left;
+ }
header h1 {
font-size: 1.8em;
color: #fff;
@@ -26,6 +31,95 @@
color: #7f8c8d;
font-size: 0.9em;
}
+ .header-controls {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ }
+ .refresh-btn {
+ background: rgba(255, 255, 255, 0.1);
+ border: none;
+ color: #fff;
+ width: 36px;
+ height: 36px;
+ border-radius: 8px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s;
+ font-size: 1.1em;
+ }
+ .refresh-btn:hover {
+ background: rgba(255, 255, 255, 0.15);
+ }
+ .refresh-btn:active {
+ transform: scale(0.95);
+ }
+ .refresh-btn.spinning svg {
+ animation: spin 0.8s linear infinite;
+ }
+ .auto-refresh-toggle {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: rgba(255, 255, 255, 0.05);
+ padding: 6px 12px;
+ border-radius: 8px;
+ font-size: 0.85em;
+ }
+ .auto-refresh-toggle label {
+ color: #95a5a6;
+ cursor: pointer;
+ user-select: none;
+ }
+ .toggle-switch {
+ position: relative;
+ width: 40px;
+ height: 22px;
+ }
+ .toggle-switch input {
+ opacity: 0;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+ z-index: 1;
+ }
+ .toggle-slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 22px;
+ transition: 0.3s;
+ }
+ .toggle-slider:before {
+ position: absolute;
+ content: "";
+ height: 16px;
+ width: 16px;
+ left: 3px;
+ bottom: 3px;
+ background: #fff;
+ border-radius: 50%;
+ transition: 0.3s;
+ }
+ .toggle-switch input:checked + .toggle-slider {
+ background: #3498db;
+ }
+ .toggle-switch input:checked + .toggle-slider:before {
+ transform: translateX(18px);
+ }
+ .countdown {
+ color: #7f8c8d;
+ font-size: 0.8em;
+ min-width: 24px;
+ text-align: center;
+ }
/* Tab Navigation */
.tabs {
@@ -330,7 +424,10 @@
}
@media (max-width: 600px) {
+ header { flex-direction: column; gap: 15px; }
+ .header-left { text-align: center; }
header h1 { font-size: 1.4em; }
+ .header-controls { width: 100%; justify-content: center; }
.grid { grid-template-columns: 1fr; }
.tabs { flex-wrap: wrap; }
.tab { padding: 10px 16px; font-size: 0.85em; }
@@ -344,8 +441,25 @@
- Tactility Dashboard
- Loading...
+
+
@@ -408,7 +522,7 @@
- Tactility WebServer - Auto-refreshes every 30 seconds
+ Tactility WebServer
@@ -420,6 +534,20 @@ function showTab(tabName) {
document.querySelector(`.tab[onclick="showTab('${tabName}')"]`).classList.add('active');
document.getElementById(`${tabName}-tab`).classList.add('active');
+ // Pause/resume auto-refresh based on active tab to avoid misleading countdown
+ if (tabName === 'dashboard') {
+ // Resume auto-refresh if toggle is on
+ if (autoRefreshToggle.checked && !refreshInterval) {
+ startAutoRefresh();
+ }
+ } else {
+ // Stop auto-refresh when leaving dashboard (countdown would be misleading)
+ // Toggle state is preserved, so it will resume when returning to dashboard
+ if (refreshInterval) {
+ stopAutoRefresh();
+ }
+ }
+
if (tabName === 'files' && !filesLoaded) {
refreshFiles();
filesLoaded = true;
@@ -438,7 +566,10 @@ function handleHashChange() {
}
}
window.addEventListener('hashchange', handleHashChange);
-if (window.location.hash) handleHashChange();
+if (window.location.hash) {
+ // Defer until auto-refresh elements are initialized to avoid ReferenceError
+ setTimeout(handleHashChange, 0);
+}
// Utility functions
function formatBytes(bytes) {
@@ -607,7 +738,6 @@ function renderDashboard(data) {
Quick Actions
Sync Assets
- Refresh
${data.features_enabled?.screenshot ? 'Screenshot ' : ''}
Reboot
@@ -733,12 +863,80 @@ async function captureScreenshot(btn) {
btn.textContent = 'Screenshot';
}
let refreshInterval;
+let countdownInterval;
+let countdownValue = 30;
+const REFRESH_INTERVAL_SECONDS = 30;
+
+const autoRefreshToggle = document.getElementById('autoRefreshToggle');
+const countdownEl = document.getElementById('countdown');
+
+function updateCountdown() {
+ countdownValue--;
+ if (countdownValue <= 0) {
+ countdownValue = REFRESH_INTERVAL_SECONDS;
+ }
+ countdownEl.textContent = countdownValue + 's';
+}
+
+function startAutoRefresh() {
+ countdownValue = REFRESH_INTERVAL_SECONDS;
+ countdownEl.textContent = countdownValue + 's';
+ countdownEl.style.display = '';
+
+ countdownInterval = setInterval(updateCountdown, 1000);
+ refreshInterval = setInterval(() => {
+ if (document.getElementById('dashboard-tab').classList.contains('active')) {
+ loadDashboard();
+ countdownValue = REFRESH_INTERVAL_SECONDS;
+ }
+ }, REFRESH_INTERVAL_SECONDS * 1000);
+}
+
+function stopAutoRefresh() {
+ if (refreshInterval) clearInterval(refreshInterval);
+ if (countdownInterval) clearInterval(countdownInterval);
+ refreshInterval = null;
+ countdownInterval = null;
+ countdownEl.style.display = 'none';
+}
+
+autoRefreshToggle.addEventListener('change', () => {
+ if (autoRefreshToggle.checked) {
+ // Only start if on dashboard tab to avoid misleading countdown
+ if (document.getElementById('dashboard-tab').classList.contains('active') && !refreshInterval) {
+ startAutoRefresh();
+ }
+ } else {
+ stopAutoRefresh();
+ }
+});
+
+async function manualRefresh() {
+ const btn = document.querySelector('.refresh-btn');
+ btn.classList.add('spinning');
+
+ const activeTab = document.querySelector('.tab-content.active');
+ if (activeTab.id === 'dashboard-tab') {
+ await loadDashboard();
+ if (autoRefreshToggle.checked) {
+ countdownValue = REFRESH_INTERVAL_SECONDS;
+ }
+ } else if (activeTab.id === 'files-tab') {
+ await refreshFiles();
+ } else if (activeTab.id === 'apps-tab') {
+ await loadApps();
+ }
+
+ btn.classList.remove('spinning');
+}
+
async function rebootDevice(btn) {
if (!confirm('Reboot device now?')) return;
const status = document.getElementById('actionStatus');
btn.disabled = true;
btn.textContent = 'Rebooting...';
- if (refreshInterval) clearInterval(refreshInterval);
+ stopAutoRefresh();
+ autoRefreshToggle.checked = false;
try {
await fetch('/admin/reboot', { method: 'POST' });
} catch (e) { }
@@ -1137,11 +1335,7 @@ async function installAppFile(file) {
// Initial load
loadDashboard();
-refreshInterval = setInterval(() => {
- if (document.getElementById('dashboard-tab').classList.contains('active')) {
- loadDashboard();
- }
-}, 30000);
+startAutoRefresh();