chore: initial import

This commit is contained in:
2025-12-22 21:22:10 +00:00
commit ddb13d0e59
8 changed files with 393 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
async function fetchJSON(url, opts) {
const res = await fetch(url, opts);
return res.json();
}
async function loadStatuses() {
const table = document.getElementById('servicesTable');
table.innerHTML = '<tr><td colspan="4">Loading...</td></tr>';
try {
const data = await fetchJSON('/api/statuses');
table.innerHTML = '';
for (const r of data.results || []) {
const tr = document.createElement('tr');
const activeLine = r.parsed?.active || 'Unknown';
const isRunning = /Active:\s+active/i.test(activeLine) && /\(running\)/i.test(activeLine);
const isFailed = /Active:\s+(failed|inactive)/i.test(activeLine) || /\(dead\)/i.test(activeLine);
const statusClass = isRunning ? 'ok' : (isFailed ? 'bad' : '');
const portsText = Array.isArray(r.ports) && r.ports.length ? r.ports.join(', ') : '';
tr.innerHTML = `
<td>${r.name}</td>
<td><span class=\"status ${statusClass}\">${activeLine}</span></td>
<td>${portsText}</td>
<td>${r.parsed?.pid || ''}</td>
<td class=\"controls\">
<button data-name=\"${r.name}\" class=\"view-logs\">View Logs</button>
<button data-name=\"${r.name}\" class=\"view-status\">Raw Status</button>
</td>
`;
table.appendChild(tr);
}
wireButtons();
} catch (err) {
table.innerHTML = `<tr><td colspan="4">Error: ${err}</td></tr>`;
}
}
function wireButtons() {
document.querySelectorAll('.view-logs').forEach(btn => {
btn.addEventListener('click', async () => {
const name = btn.getAttribute('data-name');
const data = await fetchJSON(`/api/logs?name=${encodeURIComponent(name)}&n=100`);
document.getElementById('logs').value = data.logs || JSON.stringify(data, null, 2);
});
});
document.querySelectorAll('.view-status').forEach(btn => {
btn.addEventListener('click', async () => {
const name = btn.getAttribute('data-name');
const data = await fetchJSON(`/api/status?name=${encodeURIComponent(name)}`);
document.getElementById('logs').value = data.raw || JSON.stringify(data, null, 2);
});
});
}
async function addService() {
const nameInput = document.getElementById('serviceName');
const name = nameInput.value.trim();
if (!name.endsWith('.service')) {
alert('Service name must end with .service');
return;
}
const data = await fetchJSON('/api/services', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
});
nameInput.value = '';
await loadStatuses();
}
let autoRefreshTimer = null;
function updateAutoRefresh() {
const enabled = document.getElementById('autoRefreshToggle').checked;
const intervalSec = Number(document.getElementById('autoRefreshInterval').value || '10');
if (autoRefreshTimer) {
clearInterval(autoRefreshTimer);
autoRefreshTimer = null;
}
if (enabled) {
autoRefreshTimer = setInterval(loadStatuses, intervalSec * 1000);
}
}
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('refresh').addEventListener('click', loadStatuses);
document.getElementById('addService').addEventListener('click', addService);
document.getElementById('autoRefreshToggle').addEventListener('change', updateAutoRefresh);
document.getElementById('autoRefreshInterval').addEventListener('change', updateAutoRefresh);
loadStatuses();
updateAutoRefresh();
});
+64
View File
@@ -0,0 +1,64 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Service Dashboard</title>
<style>
body { font-family: system-ui, sans-serif; margin: 20px; }
header { display: flex; align-items: center; gap: 12px; }
.status { padding: 6px 10px; border-radius: 6px; background: #f5f5f5; }
.ok { color: #0a7b22; }
.bad { color: #b00020; }
table { border-collapse: collapse; width: 100%; margin-top: 16px; }
th, td { border-bottom: 1px solid #ddd; padding: 8px; }
.controls { display: flex; gap: 8px; }
textarea { width: 100%; height: 200px; }
.row { display: flex; gap: 8px; margin-top: 16px; }
input[type=text] { flex: 1; padding: 8px; }
button { padding: 8px 12px; }
</style>
</head>
<body>
<header>
<h1>Service Dashboard</h1>
<button id="refresh">Refresh Status</button>
<label style="margin-left:16px; display:flex; align-items:center; gap:6px;">
<input type="checkbox" id="autoRefreshToggle" /> Auto-refresh
</label>
<select id="autoRefreshInterval" aria-label="Auto-refresh interval" style="margin-left:8px;">
<option value="5">5s</option>
<option value="10" selected>10s</option>
<option value="30">30s</option>
<option value="60">60s</option>
</select>
</header>
<section class="row">
<input id="serviceName" type="text" placeholder="e.g. my-app.service" />
<button id="addService">Add Service</button>
</section>
<section>
<table>
<thead>
<tr>
<th>Service</th>
<th>Status</th>
<th>Ports</th>
<th>Details</th>
<th>Logs</th>
</tr>
</thead>
<tbody id="servicesTable"></tbody>
</table>
</section>
<section>
<h3>Logs</h3>
<textarea id="logs" readonly></textarea>
</section>
<script src="/app.js"></script>
</body>
</html>