ServerManager project files
This commit is contained in:
276
public/assets/js/app.js
Executable file
276
public/assets/js/app.js
Executable file
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* ServerManager - Main Application JavaScript
|
||||
* ES6+ Modules and utilities
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* ==========================================================================
|
||||
Sidebar Toggle
|
||||
========================================================================== */
|
||||
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarToggle = document.getElementById('sidebarToggle');
|
||||
const mobileToggle = document.getElementById('mobileToggle');
|
||||
|
||||
if (sidebarToggle) {
|
||||
sidebarToggle.addEventListener('click', function () {
|
||||
const isCollapsed = sidebar.classList.toggle('collapsed');
|
||||
localStorage.setItem('sidebar_collapsed', isCollapsed ? '1' : '0');
|
||||
});
|
||||
|
||||
if (localStorage.getItem('sidebar_collapsed') === '1') {
|
||||
sidebar.classList.add('collapsed');
|
||||
}
|
||||
}
|
||||
|
||||
if (mobileToggle) {
|
||||
mobileToggle.addEventListener('click', function () {
|
||||
sidebar.classList.toggle('mobile-open');
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (window.innerWidth <= 768 &&
|
||||
sidebar.classList.contains('mobile-open') &&
|
||||
!sidebar.contains(e.target) &&
|
||||
e.target !== mobileToggle) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
}
|
||||
});
|
||||
|
||||
/* ==========================================================================
|
||||
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;');
|
||||
})();
|
||||
Reference in New Issue
Block a user