263 lines
9.7 KiB
JavaScript
263 lines
9.7 KiB
JavaScript
/**
|
|
* ServerManager - Main Application JavaScript
|
|
* ES6+ Modules and utilities
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
/* ==========================================================================
|
|
Sidebar Toggle
|
|
========================================================================== */
|
|
|
|
const sidebar = document.getElementById('sidebar');
|
|
const sidebarToggle = document.getElementById('sidebarToggle');
|
|
|
|
if (sidebarToggle && sidebar) {
|
|
sidebarToggle.addEventListener('click', function () {
|
|
const isCollapsed = sidebar.classList.toggle('collapsed');
|
|
try { localStorage.setItem('sidebar_collapsed', isCollapsed ? '1' : '0'); } catch (e) {}
|
|
});
|
|
|
|
try {
|
|
if (window.innerWidth > 768 && localStorage.getItem('sidebar_collapsed') === '1') {
|
|
sidebar.classList.add('collapsed');
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Theme Toggle
|
|
========================================================================== */
|
|
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
|
|
if (themeToggle) {
|
|
const savedTheme = localStorage.getItem('theme') || 'dark';
|
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
updateThemeIcon(savedTheme);
|
|
|
|
themeToggle.addEventListener('click', function () {
|
|
const current = document.documentElement.getAttribute('data-theme');
|
|
const next = current === 'dark' ? 'light' : 'dark';
|
|
document.documentElement.setAttribute('data-theme', next);
|
|
localStorage.setItem('theme', next);
|
|
updateThemeIcon(next);
|
|
});
|
|
}
|
|
|
|
function updateThemeIcon(theme) {
|
|
if (!themeToggle) return;
|
|
const icon = themeToggle.querySelector('i');
|
|
if (icon) {
|
|
icon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
|
|
}
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Toast Notifications
|
|
========================================================================== */
|
|
|
|
window.showToast = function (type, message) {
|
|
const container = document.getElementById('toastContainer');
|
|
if (!container) return;
|
|
|
|
const toast = document.createElement('div');
|
|
toast.className = 'toast toast-' + type;
|
|
toast.innerHTML = `
|
|
<div class="toast-body">
|
|
<i class="fas fa-${type === 'success' ? 'check-circle' :
|
|
type === 'error' ? 'times-circle' :
|
|
type === 'warning' ? 'exclamation-triangle' : 'info-circle'}"></i>
|
|
${escapeHtml(message)}
|
|
</div>
|
|
<button class="toast-close">×</button>
|
|
`;
|
|
|
|
container.appendChild(toast);
|
|
|
|
const closeBtn = toast.querySelector('.toast-close');
|
|
closeBtn.addEventListener('click', function () {
|
|
toast.remove();
|
|
});
|
|
|
|
setTimeout(() => {
|
|
if (toast.parentNode) {
|
|
toast.style.opacity = '0';
|
|
toast.style.transition = 'opacity 0.3s ease';
|
|
setTimeout(() => toast.remove(), 300);
|
|
}
|
|
}, 5000);
|
|
};
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Auto-dismiss Toasts
|
|
========================================================================== */
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
document.querySelectorAll('.toast[data-autohide="true"]').forEach(function (toast) {
|
|
setTimeout(() => {
|
|
toast.style.opacity = '0';
|
|
toast.style.transition = 'opacity 0.3s ease';
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, 5000);
|
|
});
|
|
});
|
|
|
|
/* ==========================================================================
|
|
Password Toggle
|
|
========================================================================== */
|
|
|
|
document.addEventListener('click', function (e) {
|
|
const toggle = e.target.closest('.input-toggle-password');
|
|
if (!toggle) return;
|
|
|
|
const input = toggle.parentElement.querySelector('input');
|
|
const icon = toggle.querySelector('i');
|
|
|
|
if (input) {
|
|
const isPassword = input.type === 'password';
|
|
input.type = isPassword ? 'text' : 'password';
|
|
if (icon) {
|
|
icon.className = isPassword ? 'fas fa-eye-slash' : 'fas fa-eye';
|
|
}
|
|
}
|
|
});
|
|
|
|
/* ==========================================================================
|
|
Dropdowns
|
|
========================================================================== */
|
|
|
|
document.addEventListener('click', function (e) {
|
|
const toggle = e.target.closest('.dropdown-toggle');
|
|
if (toggle) {
|
|
const dropdown = toggle.closest('.dropdown');
|
|
const wasActive = dropdown.classList.contains('active');
|
|
|
|
document.querySelectorAll('.dropdown.active').forEach(d => {
|
|
d.classList.remove('active');
|
|
});
|
|
|
|
if (!wasActive) {
|
|
dropdown.classList.add('active');
|
|
}
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
|
|
if (!e.target.closest('.dropdown')) {
|
|
document.querySelectorAll('.dropdown.active').forEach(d => {
|
|
d.classList.remove('active');
|
|
});
|
|
}
|
|
});
|
|
|
|
/* ==========================================================================
|
|
Dashboard Auto-Refresh
|
|
========================================================================== */
|
|
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
let autoRefreshInterval = null;
|
|
const AUTO_REFRESH_MS = 30000;
|
|
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', function () {
|
|
if (autoRefreshInterval) {
|
|
clearInterval(autoRefreshInterval);
|
|
autoRefreshInterval = null;
|
|
refreshBtn.classList.remove('spinning');
|
|
showToast('info', 'Auto-refresh disabled');
|
|
} else {
|
|
autoRefreshInterval = setInterval(refreshDashboardStats, AUTO_REFRESH_MS);
|
|
refreshBtn.classList.add('spinning');
|
|
showToast('info', 'Auto-refresh enabled (30s)');
|
|
refreshDashboardStats();
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshDashboardStats() {
|
|
fetch('/dashboard/stats')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success && data.data) {
|
|
const stats = data.data;
|
|
const el = (id) => document.getElementById(id);
|
|
if (el('statTotalServers')) el('statTotalServers').textContent = stats.total_servers;
|
|
if (el('statOnlineServers')) el('statOnlineServers').textContent = stats.online_servers;
|
|
if (el('statOfflineServers')) el('statOfflineServers').textContent = stats.offline_servers;
|
|
if (el('statAvgCpu')) el('statAvgCpu').textContent = stats.avg_cpu + '%';
|
|
if (el('statAvgRam')) el('statAvgRam').textContent = stats.avg_ram + '%';
|
|
if (el('statAvgDisk')) el('statAvgDisk').textContent = stats.avg_disk + '%';
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
/* ==========================================================================
|
|
API Request Helper
|
|
========================================================================== */
|
|
|
|
window.api = {
|
|
get: function (url) {
|
|
return fetch(url, {
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': 'Bearer ' + (localStorage.getItem('api_token') || ''),
|
|
},
|
|
}).then(r => r.json());
|
|
},
|
|
|
|
post: function (url, data) {
|
|
return fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Authorization': 'Bearer ' + (localStorage.getItem('api_token') || ''),
|
|
},
|
|
body: JSON.stringify(data),
|
|
}).then(r => r.json());
|
|
},
|
|
|
|
put: function (url, data) {
|
|
return fetch(url, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Authorization': 'Bearer ' + (localStorage.getItem('api_token') || ''),
|
|
},
|
|
body: JSON.stringify(data),
|
|
}).then(r => r.json());
|
|
},
|
|
|
|
delete: function (url) {
|
|
return fetch(url, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Authorization': 'Bearer ' + (localStorage.getItem('api_token') || ''),
|
|
},
|
|
}).then(r => r.json());
|
|
},
|
|
};
|
|
|
|
/* ==========================================================================
|
|
Console Info
|
|
========================================================================== */
|
|
|
|
console.log(
|
|
'%c ServerManager %c v1.0.0 ',
|
|
'background:#6366f1;color:white;padding:4px 8px;border-radius:4px 0 0 4px;font-weight:bold;',
|
|
'background:#1a1d2b;color:#9ca3af;padding:4px 8px;border-radius:0 4px 4px 0;'
|
|
);
|
|
console.log('%cLinux Server Management Platform', 'color:#6b7280;font-style:italic;');
|
|
})();
|