Stabilize discovery workflow

This commit is contained in:
2026-05-29 17:16:46 +08:00
parent 15772bc17e
commit 3981ffdf5e
40 changed files with 2039 additions and 211 deletions
+15
View File
@@ -20,3 +20,18 @@ body {
.navbar-brand {
font-weight: bold;
}
#sidebar-wrapper .sidebar-heading,
#page-content-wrapper .navbar {
min-height: 56px;
}
#sidebar-wrapper .sidebar-heading {
display: flex;
align-items: center;
}
.ports-cell {
max-width: 360px;
white-space: normal;
}
+81
View File
@@ -0,0 +1,81 @@
(function () {
function request(method, url, body, target, swap) {
fetch(url, {
method: method,
body: body,
headers: body ? {'X-Requested-With': 'fetch'} : {'X-Requested-With': 'fetch'}
})
.then(function (response) { return response.text(); })
.then(function (html) {
if (!target) {
return;
}
if (swap === 'outerHTML') {
target.outerHTML = html;
} else {
target.innerHTML = html;
}
install(target);
});
}
function targetFor(element) {
var selector = element.getAttribute('hx-target');
return selector ? document.querySelector(selector) : element;
}
function install(root) {
root.querySelectorAll('[hx-get]').forEach(function (element) {
if (element.dataset.domovoyGetInstalled === '1') {
return;
}
element.dataset.domovoyGetInstalled = '1';
var url = element.getAttribute('hx-get');
var trigger = element.getAttribute('hx-trigger') || '';
var swap = element.getAttribute('hx-swap') || 'innerHTML';
if (element.tagName === 'FORM' && trigger.includes('change')) {
element.addEventListener('change', function () {
var params = new URLSearchParams(new FormData(element));
var nextUrl = url + '?' + params.toString();
var target = targetFor(element);
if (target) {
target.setAttribute('hx-get', nextUrl);
}
request('GET', nextUrl, null, target, swap);
});
}
if (trigger.includes('load')) {
request('GET', url, null, targetFor(element), swap);
}
var every = trigger.match(/every\s+(\d+)s/);
if (every) {
window.setInterval(function () {
request('GET', url, null, targetFor(element), swap);
}, parseInt(every[1], 10) * 1000);
}
});
root.querySelectorAll('[hx-post]').forEach(function (element) {
if (element.dataset.domovoyPostInstalled === '1') {
return;
}
element.dataset.domovoyPostInstalled = '1';
element.addEventListener('click', function () {
request(
'POST',
element.getAttribute('hx-post'),
new FormData(),
targetFor(element),
element.getAttribute('hx-swap') || 'innerHTML'
);
});
});
}
document.addEventListener('DOMContentLoaded', function () {
install(document);
});
})();
+23 -2
View File
@@ -100,7 +100,8 @@ $containerBuilder->addDefinitions([
\Domovoy\Services\Jobs\ScanJobRunner::class => function ($c) {
return new \Domovoy\Services\Jobs\ScanJobRunner(
$c->get(\Domovoy\Repositories\ScanJobRepository::class),
$c->get(\Domovoy\Services\Discovery\NetworkScanner::class)
$c->get(\Domovoy\Services\Discovery\NetworkScanner::class),
$c->get(\Domovoy\Repositories\NetworkRangeRepository::class)
);
},
// Auth
@@ -137,7 +138,10 @@ $containerBuilder->addDefinitions([
return new \Domovoy\Repositories\DeviceRepository($c->get(PDO::class));
},
\Domovoy\Services\Inventory\DeviceService::class => function ($c) {
return new \Domovoy\Services\Inventory\DeviceService($c->get(\Domovoy\Repositories\DeviceRepository::class));
return new \Domovoy\Services\Inventory\DeviceService(
$c->get(\Domovoy\Repositories\DeviceRepository::class),
$c->get(\Domovoy\Repositories\DiscoveredHostRepository::class)
);
},
\Domovoy\Services\Inventory\MergeSuggestionService::class => function ($c) {
return new \Domovoy\Services\Inventory\MergeSuggestionService($c->get(\Domovoy\Repositories\DeviceRepository::class));
@@ -148,6 +152,13 @@ $containerBuilder->addDefinitions([
$c->get(\Domovoy\Repositories\DiscoveredHostRepository::class)
);
},
\Domovoy\Controllers\DiscoveriesController::class => function ($c) {
return new \Domovoy\Controllers\DiscoveriesController(
$c->get(\Domovoy\Repositories\DiscoveredHostRepository::class),
$c->get(\Domovoy\Repositories\NetworkRangeRepository::class),
$c->get(\Domovoy\Services\Inventory\DeviceService::class)
);
},
]);
$container = $containerBuilder->build();
@@ -175,6 +186,16 @@ $app->group('', function (\Slim\Routing\RouteCollectorProxy $group) {
$group->get('/discovery', [\Domovoy\Controllers\DiscoveryController::class, 'index'])->setName('discovery');
$group->post('/discovery/scan', [\Domovoy\Controllers\DiscoveryController::class, 'startScan'])->setName('discovery.scan');
$group->post('/discovery/hosts/ignore', [\Domovoy\Controllers\DiscoveryController::class, 'ignoreHost'])->setName('discovery.hosts.ignore');
$group->get('/discovery/jobs/active', [\Domovoy\Controllers\DiscoveryController::class, 'activeJobs'])->setName('discovery.jobs.active');
$group->get('/discovery/jobs/history', [\Domovoy\Controllers\DiscoveryController::class, 'scanHistory'])->setName('discovery.jobs.history');
$group->post('/discovery/jobs/{id}/pause', [\Domovoy\Controllers\DiscoveryController::class, 'pauseJob'])->setName('discovery.jobs.pause');
$group->post('/discovery/jobs/{id}/resume', [\Domovoy\Controllers\DiscoveryController::class, 'resumeJob'])->setName('discovery.jobs.resume');
$group->post('/discovery/jobs/{id}/cancel', [\Domovoy\Controllers\DiscoveryController::class, 'cancelJob'])->setName('discovery.jobs.cancel');
// Discoveries
$group->get('/discoveries', [\Domovoy\Controllers\DiscoveriesController::class, 'index'])->setName('discoveries');
$group->get('/discoveries/table', [\Domovoy\Controllers\DiscoveriesController::class, 'table'])->setName('discoveries.table');
$group->post('/discoveries/merge', [\Domovoy\Controllers\DiscoveriesController::class, 'merge'])->setName('discoveries.merge');
// Network ranges CRUD
$group->post('/discovery/ranges/create', [\Domovoy\Controllers\NetworkRangeController::class, 'create'])->setName('discovery.ranges.create');