Files

141 lines
5.9 KiB
HTML
Raw Permalink Normal View History

{% extends "base.html" %}
{% block title %}Service Detection{% endblock %}
{% block content %}
<h1>[?] Service Detection</h1>
<div class="toolbar">
<button class="btn" onclick="runScan()">Scan Server</button>
<button class="btn" onclick="browseAll()">Browse All 200+ Services</button>
</div>
<div id="scan-results"></div>
<div class="card" id="browse-panel" style="display:none">
<div class="card-title">Service Database ({{count}} services)</div>
<input type="text" id="svc-search" placeholder="Filter services..." style="width:100%;margin-bottom:10px" oninput="filterServices()">
<div id="all-services" style="max-height:600px;overflow-y:auto"></div>
</div>
<div class="card">
<div class="card-title">Output</div>
<div class="output" id="output"><span class="info">Click "Scan Server" to detect installed services</span></div>
</div>
{% endblock %}
{% block scripts %}
<script>
let allServices = {};
async function runScan() {
document.getElementById('scan-results').innerHTML = '<div class="card"><div class="card-title">Scanning...</div><div class="output"><span class="info">Checking processes, ports, packages, configs, docker, systemd...</span></div></div>';
const res = await apiGet('/api/detect/scan');
if (!res.ok) {
document.getElementById('scan-results').innerHTML = '<div class="card"><div class="output"><span class="err">'+res.error+'</span></div></div>';
return;
}
const services = res.data;
if (!services.length) {
document.getElementById('scan-results').innerHTML = '<div class="card"><div class="output"><span class="info">No services detected (or SSH failed)</span></div></div>';
return;
}
// Group by category
const groups = {};
services.forEach(s => {
const cat = s.category;
if (!groups[cat]) groups[cat] = [];
groups[cat].push(s);
});
let html = '<div class="card"><div class="card-title">Detected Services (' + services.length + ' found)</div></div>';
for (const [cat, svcs] of Object.entries(groups)) {
html += '<div class="card"><div class="card-title">' + escHtml(cat) + ' (' + svcs.length + ')</div>';
html += '<table><thead><tr><th>Service</th><th>Confidence</th><th>Evidence</th><th>Configs</th><th>Actions</th></tr></thead><tbody>';
for (const s of svcs) {
const conf = s.score >= 5 ? 'status-ok' : s.score >= 3 ? 'status-warn' : 'status-err';
const confLabel = s.score >= 5 ? 'HIGH' : s.score >= 3 ? 'MED' : 'LOW';
const configLinks = s.configs.filter(c => c && !c.endsWith('/')).map(c =>
`<a href="#" onclick="editConfig('${c}');return false" style="font-size:11px">${c.split('/').pop()}</a>`
).join(', ');
const configDirs = s.configs.filter(c => c && c.endsWith('/')).map(c =>
`<a href="#" onclick="browseConfig('${c}');return false" style="font-size:11px;color:#888">${c}</a>`
).join(', ');
html += '<tr>';
html += '<td><strong>' + escHtml(s.name) + '</strong></td>';
html += '<td class="' + conf + '">' + confLabel + ' (' + s.score + ')</td>';
html += '<td style="font-size:11px;color:#888">' + s.evidence.map(escHtml).join(', ') + '</td>';
html += '<td>' + configLinks + (configDirs ? '<br>' + configDirs : '') + '</td>';
html += '<td>';
if (s.ports.length) html += '<span style="color:#555">ports: ' + s.ports.join(',') + '</span> ';
html += '</td>';
html += '</tr>';
}
html += '</tbody></table></div>';
}
document.getElementById('scan-results').innerHTML = html;
}
function editConfig(path) {
window.location.href = '/configs?file=' + encodeURIComponent(path);
}
function browseConfig(path) {
window.location.href = '/files?path=' + encodeURIComponent(path);
}
async function browseAll() {
const panel = document.getElementById('browse-panel');
panel.style.display = '';
if (Object.keys(allServices).length) return;
const res = await apiGet('/api/detect/all-services');
if (!res.ok) return;
allServices = res.data;
renderAllServices(allServices);
}
function renderAllServices(data) {
let html = '';
let total = 0;
for (const [cat, svcs] of Object.entries(data)) {
total += svcs.length;
html += '<h2 style="margin-top:10px">' + escHtml(cat) + ' (' + svcs.length + ')</h2>';
html += '<table><thead><tr><th>Service</th><th>Ports</th><th>Packages</th><th>Config Files</th></tr></thead><tbody>';
for (const s of svcs) {
html += '<tr>';
html += '<td>' + escHtml(s.name) + '</td>';
html += '<td style="color:#888">' + (s.ports.length ? s.ports.join(', ') : '-') + '</td>';
html += '<td style="font-size:11px;color:#888">' + (s.packages.length ? s.packages.join(', ') : '-') + '</td>';
html += '<td style="font-size:11px">' + (s.configs.length ? s.configs.map(c =>
'<a href="#" onclick="editConfig(\'' + c + '\');return false">' + c + '</a>'
).join('<br>') : '-') + '</td>';
html += '</tr>';
}
html += '</tbody></table>';
}
document.getElementById('all-services').innerHTML = html;
document.querySelector('#browse-panel .card-title').textContent = 'Service Database (' + total + ' services)';
}
function filterServices() {
const q = document.getElementById('svc-search').value.toLowerCase();
if (!q) { renderAllServices(allServices); return; }
const filtered = {};
for (const [cat, svcs] of Object.entries(allServices)) {
const matches = svcs.filter(s =>
s.name.toLowerCase().includes(q) ||
cat.toLowerCase().includes(q) ||
s.packages.some(p => p.includes(q)) ||
s.configs.some(c => c.includes(q))
);
if (matches.length) filtered[cat] = matches;
}
renderAllServices(filtered);
}
</script>
{% endblock %}