Files
server-manager/public/assets/js/server-services.js
Agent 88f3c1f964 refactor: extract inline CSS/JS from views into separate asset files (20+ files)
Phase A — HTML removed from PHP files:
- RateLimitMiddleware.php: heredoc moved to views/errors/429.php
- public/index.php: inline HTML replaced with View::error()
- AuthMiddleware.php: <script> redirect replaced with <meta refresh>

Phase B — Inline CSS/JS extracted from views:
- CSS: landing.css (459 lines), legal.css (shared by terms+privacy)
- JS: 17 new files extracted from ~20 view files
  - main.js (layout sidebar + notifications)
  - dashboard-chart.js, server-show.js, server-services.js
  - nginx-manager.js, database-manager.js
  - terminal.js, team-show.js, team-index.js
  - server-list.js, server-permissions.js (shared by edit+create)
  - server-ssl.js, server-processes.js, system-users.js
  - admin-users.js, audit-log.js, admin-notifications.js, admin-security.js
  - notifications.js

Total: -3264 lines from views, +288 lines in new assets
2026-06-14 08:04:01 -04:00

87 lines
4.8 KiB
JavaScript

function applyFilters() {
const nameQ = document.getElementById('filterName').value.toLowerCase();
const statusQ = document.getElementById('filterStatus').value;
const filtered = allServices.filter(s => {
if (nameQ && !s.name.toLowerCase().includes(nameQ)) return false;
if (statusQ && s.status !== statusQ) return false;
return true;
});
renderPage(filtered, 1);
}
function renderPage(list, page) {
const totalPages = Math.ceil(list.length / perPage) || 1;
const start = (page - 1) * perPage;
const slice = list.slice(start, start + perPage);
const tbody = document.getElementById('servicesBody');
let html = '';
slice.forEach(s => {
const isActive = s.status === 'active';
const isInactive = s.status === 'inactive';
html += '<tr class="service-row">';
html += '<td><code>' + escHtml(s.name) + '</code></td>';
html += '<td><span class="badge ' + (isActive ? 'badge-success' : isInactive ? 'badge-secondary' : 'badge-warning') + '">' + s.status + '</span></td>';
html += '<td class="actions-cell" style="white-space:nowrap">';
if (!isActive) html += '<button class="btn btn-xs btn-success" onclick="serviceAction(\'start\',\'' + escHtml(s.name) + '\')" title="Start"><i class="fas fa-play"></i></button> ';
if (!isInactive) html += '<button class="btn btn-xs btn-danger" onclick="serviceAction(\'stop\',\'' + escHtml(s.name) + '\')" title="Stop"><i class="fas fa-stop"></i></button> ';
html += '<button class="btn btn-xs btn-warning" onclick="serviceAction(\'restart\',\'' + escHtml(s.name) + '\')" title="Restart"><i class="fas fa-redo"></i></button> ';
html += '<button class="btn btn-xs btn-info" onclick="serviceAction(\'reload\',\'' + escHtml(s.name) + '\')" title="Reload"><i class="fas fa-sync-alt"></i></button> ';
if (!s.enabled) html += '<button class="btn btn-xs btn-secondary" onclick="serviceAction(\'enable\',\'' + escHtml(s.name) + '\')" title="Enable"><i class="fas fa-power-off"></i></button> ';
if (s.enabled) html += '<button class="btn btn-xs btn-secondary" onclick="serviceAction(\'disable\',\'' + escHtml(s.name) + '\')" title="Disable"><i class="fas fa-ban"></i></button> ';
html += '</td></tr>';
});
if (!html) html = '<tr><td colspan="3" style="text-align:center;color:var(--text-muted);padding:2rem">No services match the filter.</td></tr>';
tbody.innerHTML = html;
renderPagination(list, page);
}
function renderPagination(list, page) {
const totalPages = Math.ceil(list.length / perPage) || 1;
const container = document.getElementById('servicesPagination');
let html = '';
for (let p = 1; p <= totalPages && p <= 15; p++) {
html += '<button class="btn btn-xs ' + (p === page ? 'btn-primary' : 'btn-secondary') + '" onclick="renderPage(list,' + p + ')" data-list=\'' + JSON.stringify(list).replace(/'/g,"&#39;") + '\'>' + p + '</button>';
}
if (totalPages > 15) html += '<span class="text-muted" style="font-size:0.8rem">...</span>';
container.innerHTML = html + ' <span class="text-muted" style="font-size:0.8rem">' + list.length + ' services</span>';
container.querySelectorAll('button').forEach(btn => {
const p = parseInt(btn.textContent);
if (!isNaN(p)) {
btn.onclick = function() { renderPage(list, p); };
}
});
}
applyFilters();
function getCsrfToken() { return document.querySelector('meta[name="csrf-token"]')?.content || ''; }
function serviceAction(action, service) {
fetch('/servers/' + serverId + '/services', {
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()},
body:'action='+action+'&service='+encodeURIComponent(service)+'&_csrf_token='+encodeURIComponent(getCsrfToken()),
})
.then(r=>r.json()).then(d=>{
showToast(d.success?'success':'error', d.message);
if (d.output) {
document.getElementById('srTitle').textContent = d.success ? 'Success' : 'Error';
document.getElementById('srMessage').textContent = d.message;
document.getElementById('srOutputText').textContent = d.output;
document.getElementById('srOutput').style.display = 'block';
document.getElementById('serviceResultModal').style.display = 'flex';
}
setTimeout(() => location.reload(), 1500);
})
.catch(err => showToast('error', 'Request failed'));
}
function closeSrModal() {
document.getElementById('serviceResultModal').style.display = 'none';
document.getElementById('srOutput').style.display = 'none';
}
function escHtml(s) { const d=document.createElement('div'); d.textContent=s; return d.innerHTML; }
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeSrModal(); });