lanscape 1.4.4__py3-none-any.whl → 2.0.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lanscape/__init__.py +9 -4
- lanscape/__main__.py +1 -0
- lanscape/{libraries → core}/app_scope.py +22 -3
- lanscape/{libraries → core}/decorators.py +88 -52
- lanscape/{libraries → core}/device_alive.py +4 -3
- lanscape/{libraries → core}/errors.py +1 -0
- lanscape/{libraries → core}/ip_parser.py +2 -1
- lanscape/{libraries → core}/logger.py +1 -0
- lanscape/{libraries → core}/mac_lookup.py +1 -0
- lanscape/{libraries → core}/net_tools.py +140 -46
- lanscape/{libraries → core}/port_manager.py +1 -0
- lanscape/{libraries → core}/runtime_args.py +1 -0
- lanscape/{libraries → core}/scan_config.py +104 -5
- lanscape/core/service_scan.py +205 -0
- lanscape/{libraries → core}/subnet_scan.py +19 -11
- lanscape/{libraries → core}/version_manager.py +3 -2
- lanscape/{libraries → core}/web_browser.py +1 -0
- lanscape/resources/mac_addresses/convert_csv.py +1 -0
- lanscape/resources/ports/convert_csv.py +1 -0
- lanscape/resources/services/definitions.jsonc +576 -400
- lanscape/ui/app.py +5 -4
- lanscape/ui/blueprints/__init__.py +2 -1
- lanscape/ui/blueprints/api/__init__.py +1 -0
- lanscape/ui/blueprints/api/port.py +2 -1
- lanscape/ui/blueprints/api/scan.py +2 -1
- lanscape/ui/blueprints/api/tools.py +5 -4
- lanscape/ui/blueprints/web/__init__.py +1 -0
- lanscape/ui/blueprints/web/routes.py +30 -2
- lanscape/ui/main.py +5 -4
- lanscape/ui/shutdown_handler.py +2 -1
- lanscape/ui/static/css/style.css +145 -2
- lanscape/ui/static/js/main.js +30 -2
- lanscape/ui/static/js/scan-config.js +39 -0
- lanscape/ui/templates/scan/config.html +43 -0
- lanscape/ui/templates/scan/device-detail.html +111 -0
- lanscape/ui/templates/scan/ip-table-row.html +12 -78
- lanscape/ui/templates/scan/ip-table.html +1 -1
- {lanscape-1.4.4.dist-info → lanscape-2.0.0a1.dist-info}/METADATA +7 -2
- lanscape-2.0.0a1.dist-info/RECORD +76 -0
- lanscape-2.0.0a1.dist-info/entry_points.txt +2 -0
- lanscape/libraries/service_scan.py +0 -50
- lanscape-1.4.4.dist-info/RECORD +0 -74
- /lanscape/{libraries → core}/__init__.py +0 -0
- {lanscape-1.4.4.dist-info → lanscape-2.0.0a1.dist-info}/WHEEL +0 -0
- {lanscape-1.4.4.dist-info → lanscape-2.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {lanscape-1.4.4.dist-info → lanscape-2.0.0a1.dist-info}/top_level.txt +0 -0
lanscape/ui/app.py
CHANGED
|
@@ -8,12 +8,12 @@ import logging
|
|
|
8
8
|
from flask import Flask, render_template
|
|
9
9
|
from lanscape.ui.blueprints.web import web_bp, routes # pylint: disable=unused-import
|
|
10
10
|
from lanscape.ui.blueprints.api import api_bp, tools, port, scan # pylint: disable=unused-import
|
|
11
|
-
from lanscape.
|
|
12
|
-
from lanscape.
|
|
11
|
+
from lanscape.core.runtime_args import RuntimeArgs, parse_args
|
|
12
|
+
from lanscape.core.version_manager import (
|
|
13
13
|
is_update_available, get_installed_version, lookup_latest_version
|
|
14
14
|
)
|
|
15
|
-
from lanscape.
|
|
16
|
-
from lanscape.
|
|
15
|
+
from lanscape.core.app_scope import is_local_run
|
|
16
|
+
from lanscape.core.net_tools import is_arp_supported
|
|
17
17
|
from lanscape.ui.shutdown_handler import FlaskShutdownHandler
|
|
18
18
|
|
|
19
19
|
app = Flask(
|
|
@@ -122,3 +122,4 @@ def start_webserver(args: RuntimeArgs) -> int:
|
|
|
122
122
|
'use_reloader': args.reloader
|
|
123
123
|
}
|
|
124
124
|
app.run(**run_args)
|
|
125
|
+
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Source for all things blueprint related in LANscape UI"""
|
|
2
2
|
import logging
|
|
3
3
|
|
|
4
|
-
from lanscape.
|
|
4
|
+
from lanscape.core.subnet_scan import ScanManager
|
|
5
5
|
|
|
6
6
|
# defining here so blueprints can access the same
|
|
7
7
|
# manager instance
|
|
8
8
|
scan_manager = ScanManager()
|
|
9
9
|
|
|
10
10
|
log = logging.getLogger('Blueprints')
|
|
11
|
+
|
|
@@ -4,7 +4,7 @@ Provides CRUD operations for managing port lists used in network scans.
|
|
|
4
4
|
"""
|
|
5
5
|
from flask import request, jsonify
|
|
6
6
|
from lanscape.ui.blueprints.api import api_bp
|
|
7
|
-
from lanscape.
|
|
7
|
+
from lanscape.core.port_manager import PortManager
|
|
8
8
|
|
|
9
9
|
# Port Manager API
|
|
10
10
|
############################################
|
|
@@ -77,3 +77,4 @@ def delete_port_list(port_list):
|
|
|
77
77
|
JSON response indicating success or failure
|
|
78
78
|
"""
|
|
79
79
|
return jsonify(PortManager().delete_port_list(port_list))
|
|
80
|
+
|
|
@@ -9,7 +9,7 @@ import traceback
|
|
|
9
9
|
from flask import request, jsonify
|
|
10
10
|
|
|
11
11
|
from lanscape.ui.blueprints.api import api_bp
|
|
12
|
-
from lanscape.
|
|
12
|
+
from lanscape.core.subnet_scan import ScanConfig
|
|
13
13
|
from lanscape.ui.blueprints import scan_manager
|
|
14
14
|
|
|
15
15
|
# Subnet Scanner API
|
|
@@ -121,3 +121,4 @@ def get_scan_config():
|
|
|
121
121
|
"""
|
|
122
122
|
data = request.get_json()
|
|
123
123
|
return ScanConfig.from_dict(data)
|
|
124
|
+
|
|
@@ -5,10 +5,10 @@ API endpoints for subnet testing and listing.
|
|
|
5
5
|
import traceback
|
|
6
6
|
from flask import request, jsonify
|
|
7
7
|
from lanscape.ui.blueprints.api import api_bp
|
|
8
|
-
from lanscape.
|
|
9
|
-
from lanscape.
|
|
10
|
-
from lanscape.
|
|
11
|
-
from lanscape.
|
|
8
|
+
from lanscape.core.net_tools import get_all_network_subnets, is_arp_supported
|
|
9
|
+
from lanscape.core.ip_parser import parse_ip_input
|
|
10
|
+
from lanscape.core.errors import SubnetTooLargeError
|
|
11
|
+
from lanscape.core.scan_config import DEFAULT_CONFIGS
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@api_bp.route('/api/tools/subnet/test')
|
|
@@ -69,3 +69,4 @@ def get_default_configs():
|
|
|
69
69
|
configs[key] = config_dict
|
|
70
70
|
|
|
71
71
|
return jsonify(configs)
|
|
72
|
+
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Web blueprint routes for the LANscape application.
|
|
3
3
|
Handles UI views including the main dashboard, scan results, error display, and exports.
|
|
4
4
|
"""
|
|
5
|
-
from flask import render_template, request, redirect
|
|
5
|
+
from flask import render_template, request, redirect, url_for
|
|
6
6
|
from lanscape.ui.blueprints.web import web_bp
|
|
7
|
-
from lanscape.
|
|
7
|
+
from lanscape.core.net_tools import (
|
|
8
8
|
get_all_network_subnets,
|
|
9
9
|
smart_select_primary_subnet
|
|
10
10
|
)
|
|
@@ -81,6 +81,33 @@ def view_errors(scan_id):
|
|
|
81
81
|
return redirect('/')
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
@web_bp.route('/device/<scan_id>/<device_ip>')
|
|
85
|
+
def view_device(scan_id, device_ip):
|
|
86
|
+
"""
|
|
87
|
+
Display detailed information about a specific device from a scan.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
scan_id: Unique identifier for the scan
|
|
91
|
+
device_ip: IP address of the device to view
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Rendered device detail template or redirect to home if scan not found
|
|
95
|
+
"""
|
|
96
|
+
if scanner := scan_manager.get_scan(scan_id):
|
|
97
|
+
devices = scanner.results.devices
|
|
98
|
+
device_info = next(
|
|
99
|
+
(device for device in devices if getattr(
|
|
100
|
+
device, 'ip', None) == device_ip), None)
|
|
101
|
+
|
|
102
|
+
if device_info:
|
|
103
|
+
return render_template('scan/device-detail.html', device=device_info, scan_id=scan_id)
|
|
104
|
+
|
|
105
|
+
log.debug(f'Device {device_ip} not found in scan {scan_id}')
|
|
106
|
+
return redirect(url_for('render_scan', scan_id=scan_id))
|
|
107
|
+
log.debug(f'Redirecting, scan {scan_id} doesnt exist in memory')
|
|
108
|
+
return redirect('/')
|
|
109
|
+
|
|
110
|
+
|
|
84
111
|
@web_bp.route('/export/<scan_id>')
|
|
85
112
|
def export_scan(scan_id):
|
|
86
113
|
"""
|
|
@@ -123,3 +150,4 @@ def app_info():
|
|
|
123
150
|
Rendered info template
|
|
124
151
|
"""
|
|
125
152
|
return render_template('info.html')
|
|
153
|
+
|
lanscape/ui/main.py
CHANGED
|
@@ -9,10 +9,10 @@ import traceback
|
|
|
9
9
|
import os
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
|
-
from lanscape.
|
|
13
|
-
from lanscape.
|
|
14
|
-
from lanscape.
|
|
15
|
-
from lanscape.
|
|
12
|
+
from lanscape.core.logger import configure_logging
|
|
13
|
+
from lanscape.core.runtime_args import parse_args
|
|
14
|
+
from lanscape.core.web_browser import open_webapp
|
|
15
|
+
from lanscape.core.version_manager import get_installed_version, is_update_available
|
|
16
16
|
from lanscape.ui.app import start_webserver_daemon, start_webserver
|
|
17
17
|
# do this so any logs generated on import are displayed
|
|
18
18
|
args = parse_args()
|
|
@@ -136,3 +136,4 @@ def terminate():
|
|
|
136
136
|
|
|
137
137
|
if __name__ == "__main__":
|
|
138
138
|
main()
|
|
139
|
+
|
lanscape/ui/shutdown_handler.py
CHANGED
|
@@ -5,7 +5,7 @@ import os
|
|
|
5
5
|
from flask import request
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
from lanscape.
|
|
8
|
+
from lanscape.core.runtime_args import parse_args
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
log = logging.getLogger('shutdown_handler')
|
|
@@ -55,3 +55,4 @@ class FlaskShutdownHandler:
|
|
|
55
55
|
"""Exits the application if a shutdown request has been made."""
|
|
56
56
|
if self._exiting:
|
|
57
57
|
os._exit(0)
|
|
58
|
+
|
lanscape/ui/static/css/style.css
CHANGED
|
@@ -555,6 +555,41 @@ input[type="range"] {
|
|
|
555
555
|
color: var(--text-color);
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
+
/* Service Strategy Select Styles */
|
|
559
|
+
.service-strategy-wrapper {
|
|
560
|
+
position: relative;
|
|
561
|
+
display: inline-block;
|
|
562
|
+
width: 60%; /* Narrower than port list */
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.service-strategy {
|
|
566
|
+
position: relative;
|
|
567
|
+
background-color: var(--secondary-bg);
|
|
568
|
+
border: 1px solid var(--border-color);
|
|
569
|
+
color: var(--text-color);
|
|
570
|
+
padding: 10px;
|
|
571
|
+
cursor: pointer;
|
|
572
|
+
width: 100%;
|
|
573
|
+
height: 42px;
|
|
574
|
+
user-select: none;
|
|
575
|
+
appearance: none; /* Hide default arrow */
|
|
576
|
+
transition: all .2s ease-in-out;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.service-strategy:focus {
|
|
580
|
+
border-color: var(--primary-accent-hover);
|
|
581
|
+
outline: none;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.service-strategy-wrapper::after {
|
|
585
|
+
content: '▼';
|
|
586
|
+
position: absolute;
|
|
587
|
+
top: 10px;
|
|
588
|
+
right: 10px;
|
|
589
|
+
pointer-events: none;
|
|
590
|
+
color: var(--text-color);
|
|
591
|
+
}
|
|
592
|
+
|
|
558
593
|
|
|
559
594
|
|
|
560
595
|
.text-color {
|
|
@@ -612,6 +647,35 @@ input[type="range"] {
|
|
|
612
647
|
background-color: var(--primary-bg-accent);
|
|
613
648
|
}
|
|
614
649
|
|
|
650
|
+
.table tbody tr td:has(.info-icon-container),
|
|
651
|
+
.table thead tr th.detail-col
|
|
652
|
+
{
|
|
653
|
+
width: 30px;
|
|
654
|
+
/*
|
|
655
|
+
background-color: var(--body-bg);
|
|
656
|
+
border: 1px solid var(--text-almost-hidden);
|
|
657
|
+
*/
|
|
658
|
+
}
|
|
659
|
+
.table td:has(.info-icon-container) {
|
|
660
|
+
width: 30px;
|
|
661
|
+
text-align: center; /* horizontal center */
|
|
662
|
+
vertical-align: middle; /* vertical center inside the cell */
|
|
663
|
+
padding: 0; /* optional: remove extra padding */
|
|
664
|
+
}
|
|
665
|
+
.table td .info-icon-container {
|
|
666
|
+
display: flex;
|
|
667
|
+
justify-content: center; /* horizontal center */
|
|
668
|
+
align-items: center; /* vertical center */
|
|
669
|
+
height: 100%; /* ensure it takes full cell height */
|
|
670
|
+
}
|
|
671
|
+
.table td .info-icon-container .info-icon {
|
|
672
|
+
font-size: 1.2em;
|
|
673
|
+
color: var(--text-placeholder);
|
|
674
|
+
cursor: pointer;
|
|
675
|
+
}
|
|
676
|
+
.table td .info-icon:hover {
|
|
677
|
+
color: var(--text-color);
|
|
678
|
+
}
|
|
615
679
|
|
|
616
680
|
/* Badge Styles */
|
|
617
681
|
.badge-warning {
|
|
@@ -627,7 +691,7 @@ input[type="range"] {
|
|
|
627
691
|
}
|
|
628
692
|
|
|
629
693
|
.badge-info {
|
|
630
|
-
background-color: var(--
|
|
694
|
+
background-color: var(--primary-accent);
|
|
631
695
|
}
|
|
632
696
|
|
|
633
697
|
.badge-secondary {
|
|
@@ -835,4 +899,83 @@ html {
|
|
|
835
899
|
|
|
836
900
|
}
|
|
837
901
|
|
|
838
|
-
/* END overview container */
|
|
902
|
+
/* END overview container */
|
|
903
|
+
|
|
904
|
+
/* Device Modal Styles */
|
|
905
|
+
#device-modal {
|
|
906
|
+
--bs-modal-width: 750px;
|
|
907
|
+
}
|
|
908
|
+
#device-modal .modal-content {
|
|
909
|
+
background-color: var(--primary-bg);
|
|
910
|
+
}
|
|
911
|
+
#device-modal h6 {
|
|
912
|
+
color: var(--primary-accent);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/* Key/Value grid for overview */
|
|
916
|
+
#device-modal .kv-grid {
|
|
917
|
+
display: grid;
|
|
918
|
+
grid-template-columns: 180px 1fr;
|
|
919
|
+
column-gap: 12px;
|
|
920
|
+
row-gap: 8px;
|
|
921
|
+
align-items: center;
|
|
922
|
+
}
|
|
923
|
+
#device-modal .kv-label {
|
|
924
|
+
color: var(--text-placeholder);
|
|
925
|
+
text-align: right;
|
|
926
|
+
}
|
|
927
|
+
#device-modal .kv-value {
|
|
928
|
+
color: var(--text-color);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/* Port/service chips */
|
|
932
|
+
#device-modal .chip-group { margin-top: 4px; }
|
|
933
|
+
#device-modal .chip {
|
|
934
|
+
display: inline-flex;
|
|
935
|
+
align-items: center;
|
|
936
|
+
gap: 4px;
|
|
937
|
+
padding: 2px 8px;
|
|
938
|
+
margin: 2px;
|
|
939
|
+
font-size: .85rem;
|
|
940
|
+
background-color: var(--primary-bg-accent);
|
|
941
|
+
border: 1px solid var(--border-color);
|
|
942
|
+
border-radius: 999px;
|
|
943
|
+
color: var(--text-color);
|
|
944
|
+
}
|
|
945
|
+
#device-modal .chip .material-symbols-outlined {
|
|
946
|
+
font-size: 16px;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/* Services layout */
|
|
950
|
+
#device-modal .service-list { width: 100%; }
|
|
951
|
+
#device-modal .service-row {
|
|
952
|
+
display: flex;
|
|
953
|
+
align-items: flex-start;
|
|
954
|
+
gap: 8px;
|
|
955
|
+
padding: 6px 0;
|
|
956
|
+
border-bottom: 1px dashed var(--border-color);
|
|
957
|
+
}
|
|
958
|
+
#device-modal .service-row:last-child {
|
|
959
|
+
border-bottom: none;
|
|
960
|
+
}
|
|
961
|
+
#device-modal .service-name {
|
|
962
|
+
min-width: 140px;
|
|
963
|
+
color: var(--text-accent-color);
|
|
964
|
+
font-weight: 600;
|
|
965
|
+
}
|
|
966
|
+
#device-modal .service-ports { flex: 1; }
|
|
967
|
+
|
|
968
|
+
/* Errors */
|
|
969
|
+
#device-modal .error-list li {
|
|
970
|
+
margin-bottom: 4px;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/* Responsive tweaks */
|
|
974
|
+
@media screen and (max-width: 576px) {
|
|
975
|
+
#device-modal .kv-grid {
|
|
976
|
+
grid-template-columns: 130px 1fr;
|
|
977
|
+
}
|
|
978
|
+
#device-modal .service-name {
|
|
979
|
+
min-width: 110px;
|
|
980
|
+
}
|
|
981
|
+
}
|
lanscape/ui/static/js/main.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
$(document).ready(function() {
|
|
4
2
|
// Load port lists into the dropdown
|
|
5
3
|
const scanId = getActiveScanId();
|
|
@@ -217,6 +215,36 @@ $(window).on('resize', function() {
|
|
|
217
215
|
resizeIframe($('#ip-table-frame')[0]);
|
|
218
216
|
});
|
|
219
217
|
|
|
218
|
+
function openDeviceDetail(deviceIp) {
|
|
219
|
+
try {
|
|
220
|
+
const scanId = getActiveScanId();
|
|
221
|
+
if (!scanId || !deviceIp) return;
|
|
222
|
+
|
|
223
|
+
const safeIp = encodeURIComponent(deviceIp.trim());
|
|
224
|
+
|
|
225
|
+
// Remove any existing modal instance to avoid duplicates
|
|
226
|
+
$('#device-modal').remove();
|
|
227
|
+
|
|
228
|
+
$.get(`/device/${scanId}/${safeIp}`, function(html) {
|
|
229
|
+
// Append modal HTML to the document
|
|
230
|
+
$('body').append(html);
|
|
231
|
+
|
|
232
|
+
// Show the modal
|
|
233
|
+
const $modal = $('#device-modal');
|
|
234
|
+
$modal.modal('show');
|
|
235
|
+
|
|
236
|
+
// Clean up after closing
|
|
237
|
+
$modal.on('hidden.bs.modal', function() {
|
|
238
|
+
$(this).remove();
|
|
239
|
+
});
|
|
240
|
+
}).fail(function() {
|
|
241
|
+
console.error('Failed to load device details');
|
|
242
|
+
});
|
|
243
|
+
} catch (e) {
|
|
244
|
+
console.error('Error opening device detail modal:', e);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
220
248
|
|
|
221
249
|
|
|
222
250
|
|
|
@@ -13,6 +13,7 @@ $(document).ready(function() {
|
|
|
13
13
|
|
|
14
14
|
$('#t_cnt_port_scan, #t_cnt_port_test').on('input', updatePortTotals);
|
|
15
15
|
$('#ping_attempts, #ping_ping_count').on('input', updatePingTotals);
|
|
16
|
+
$('#task_scan_port_services').on('change', updateVisibility);
|
|
16
17
|
|
|
17
18
|
// Lookup type toggles
|
|
18
19
|
$('.lookup-type-input').on('change', onLookupTypeChanged);
|
|
@@ -43,6 +44,30 @@ function setScanConfig(configName) {
|
|
|
43
44
|
$('#task_scan_ports').prop('checked', config.task_scan_ports);
|
|
44
45
|
$('#task_scan_port_services').prop('checked', config.task_scan_port_services);
|
|
45
46
|
|
|
47
|
+
// port scan config
|
|
48
|
+
if (config.port_scan_config) {
|
|
49
|
+
$('#port_scan_timeout').val(config.port_scan_config.timeout);
|
|
50
|
+
$('#port_scan_retries').val(config.port_scan_config.retries);
|
|
51
|
+
$('#port_scan_retry_delay').val(config.port_scan_config.retry_delay);
|
|
52
|
+
} else {
|
|
53
|
+
// defaults if missing
|
|
54
|
+
$('#port_scan_timeout').val(1.0);
|
|
55
|
+
$('#port_scan_retries').val(0);
|
|
56
|
+
$('#port_scan_retry_delay').val(0.1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// service config
|
|
60
|
+
if (config.service_scan_config) {
|
|
61
|
+
$('#service_lookup_type').val(config.service_scan_config.lookup_type || 'BASIC');
|
|
62
|
+
$('#service_timeout').val(config.service_scan_config.timeout);
|
|
63
|
+
$('#service_max_concurrent_probes').val(config.service_scan_config.max_concurrent_probes);
|
|
64
|
+
} else {
|
|
65
|
+
// defaults if missing
|
|
66
|
+
$('#service_lookup_type').val('BASIC');
|
|
67
|
+
$('#service_timeout').val(5.0);
|
|
68
|
+
$('#service_max_concurrent_probes').val(10);
|
|
69
|
+
}
|
|
70
|
+
|
|
46
71
|
// lookup type (array of enum values as strings)
|
|
47
72
|
setLookupTypeUI(config.lookup_type || []);
|
|
48
73
|
|
|
@@ -99,6 +124,16 @@ function getScanConfig() {
|
|
|
99
124
|
poke_config: {
|
|
100
125
|
attempts: parseInt($('#poke_attempts').val()),
|
|
101
126
|
timeout: parseFloat($('#poke_timeout').val())
|
|
127
|
+
},
|
|
128
|
+
port_scan_config: {
|
|
129
|
+
timeout: parseFloat($('#port_scan_timeout').val()),
|
|
130
|
+
retries: parseInt($('#port_scan_retries').val()),
|
|
131
|
+
retry_delay: parseFloat($('#port_scan_retry_delay').val())
|
|
132
|
+
},
|
|
133
|
+
service_scan_config: {
|
|
134
|
+
timeout: parseFloat($('#service_timeout').val()),
|
|
135
|
+
lookup_type: $('#service_lookup_type').val(),
|
|
136
|
+
max_concurrent_probes: parseInt($('#service_max_concurrent_probes').val())
|
|
102
137
|
}
|
|
103
138
|
};
|
|
104
139
|
}
|
|
@@ -168,6 +203,10 @@ function updateVisibility() {
|
|
|
168
203
|
// Poke section only when POKE_THEN_ARP is selected
|
|
169
204
|
const showPoke = types.has('POKE_THEN_ARP');
|
|
170
205
|
toggleSection('#section-poke', showPoke);
|
|
206
|
+
|
|
207
|
+
// Service scan section visible only if stage enabled
|
|
208
|
+
const showService = $('#task_scan_port_services').is(':checked');
|
|
209
|
+
toggleSection('#section-service-scan', showService);
|
|
171
210
|
}
|
|
172
211
|
|
|
173
212
|
function toggleSection(selector, show) {
|
|
@@ -181,6 +181,49 @@
|
|
|
181
181
|
</div>
|
|
182
182
|
</div>
|
|
183
183
|
</div>
|
|
184
|
+
<div class="row mt-2">
|
|
185
|
+
<div class="col">
|
|
186
|
+
<label for="port_scan_timeout" class="form-label">Port Timeout (sec)</label>
|
|
187
|
+
<input type="number" step="0.1" id="port_scan_timeout" class="form-control">
|
|
188
|
+
</div>
|
|
189
|
+
<div class="col">
|
|
190
|
+
<label for="port_scan_retries" class="form-label">Retries</label>
|
|
191
|
+
<input type="number" id="port_scan_retries" class="form-control">
|
|
192
|
+
</div>
|
|
193
|
+
<div class="col">
|
|
194
|
+
<label for="port_scan_retry_delay" class="form-label">Retry Delay (sec)</label>
|
|
195
|
+
<input type="number" step="0.1" id="port_scan_retry_delay" class="form-control">
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div id="section-service-scan" class="form-group mt-2 config-section div-hide">
|
|
201
|
+
<h6>Service Scanning</h6>
|
|
202
|
+
<div class="row">
|
|
203
|
+
<div class="col">
|
|
204
|
+
<label for="service_lookup_type" class="form-label">Strategy</label>
|
|
205
|
+
<div class="service-strategy-wrapper">
|
|
206
|
+
<select id="service_lookup_type" class="service-strategy">
|
|
207
|
+
<option value="LAZY">Lazy (few probes)</option>
|
|
208
|
+
<option value="BASIC">Basic (common probes)</option>
|
|
209
|
+
<option value="AGGRESSIVE">Aggressive (all probes)</option>
|
|
210
|
+
</select>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="col">
|
|
214
|
+
<label for="service_timeout" class="form-label">Timeout (sec)</label>
|
|
215
|
+
<input type="number" step="0.1" id="service_timeout" class="form-control">
|
|
216
|
+
</div>
|
|
217
|
+
<div class="col">
|
|
218
|
+
<label for="service_max_concurrent_probes" class="form-label">Max Concurrent Probes</label>
|
|
219
|
+
<input type="number" id="service_max_concurrent_probes" class="form-control">
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="row mt-1">
|
|
223
|
+
<div class="col">
|
|
224
|
+
<small class="text-secondary">Attempts service identification on open ports using selected strategy.</small>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
184
227
|
</div>
|
|
185
228
|
|
|
186
229
|
<div class="form-group mt-2 config-section">
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<div class="modal fade" id="device-modal" tabindex="-1" aria-hidden="true">
|
|
2
|
+
<div class="modal-dialog">
|
|
3
|
+
<div class="modal-content">
|
|
4
|
+
<div class="modal-header border-secondary">
|
|
5
|
+
{% set ip = device.ip|default('Unknown IP') %}
|
|
6
|
+
{% set hostname = device.hostname|default('') %}
|
|
7
|
+
{% set manufacturer = device.manufacturer|default('') %}
|
|
8
|
+
{% set mac = device.mac_addr|default('') %}
|
|
9
|
+
{% set stage = device.stage|default('unknown') %}
|
|
10
|
+
{% set ports = device.ports|default([]) %}
|
|
11
|
+
{% set services = device.services|default({}) %}
|
|
12
|
+
{% set errors = device.caught_errors|default([]) %}
|
|
13
|
+
{% set stage_lower = (stage|string|lower) %}
|
|
14
|
+
{% set stage_badge_class = 'badge-success' if stage_lower == 'complete' else 'badge-warning' if stage_lower in ['running', 'in_progress'] else 'badge-secondary' %}
|
|
15
|
+
<h5 class="modal-title" id="device-modalLabel">
|
|
16
|
+
Device Details
|
|
17
|
+
<span class="text-secondary small ms-2">IP: {{ ip }}</span>
|
|
18
|
+
</h5>
|
|
19
|
+
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="modal-body">
|
|
23
|
+
<div class="config-sections">
|
|
24
|
+
<div class="form-group mt-2 config-section">
|
|
25
|
+
<h6>Overview</h6>
|
|
26
|
+
<div class="kv-grid mt-1">
|
|
27
|
+
<div class="kv-label">IP Address</div>
|
|
28
|
+
<div class="kv-value">{{ ip }}</div>
|
|
29
|
+
|
|
30
|
+
<div class="kv-label">Hostname</div>
|
|
31
|
+
<div class="kv-value">{{ hostname|trim if hostname|default('')|trim else 'Unknown Hostname' }}</div>
|
|
32
|
+
|
|
33
|
+
<div class="kv-label">MAC Address</div>
|
|
34
|
+
<div class="kv-value">{{ mac|trim if mac|default('')|trim else 'Unknown MAC' }}</div>
|
|
35
|
+
|
|
36
|
+
<div class="kv-label">Manufacturer</div>
|
|
37
|
+
<div class="kv-value">{{ manufacturer|trim if manufacturer|default('')|trim else 'Unknown Manufacturer' }}</div>
|
|
38
|
+
|
|
39
|
+
<div class="kv-label">Stage</div>
|
|
40
|
+
<div class="kv-value">
|
|
41
|
+
<span class="badge {{ stage_badge_class }}">{{ stage|default('unknown')|capitalize }}</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="form-group mt-2 config-section">
|
|
47
|
+
<h6>Open Ports</h6>
|
|
48
|
+
{% if ports and ports|length > 0 %}
|
|
49
|
+
<div class="chip-group mt-1">
|
|
50
|
+
{% for p in ports %}
|
|
51
|
+
<span class="chip" title="Port {{ p }}">
|
|
52
|
+
<span class="material-symbols-outlined">lan</span>{{ p }}
|
|
53
|
+
</span>
|
|
54
|
+
{% endfor %}
|
|
55
|
+
</div>
|
|
56
|
+
{% else %}
|
|
57
|
+
<div class="text-secondary">No open ports detected.</div>
|
|
58
|
+
{% endif %}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="form-group mt-2 config-section">
|
|
62
|
+
<h6>Services</h6>
|
|
63
|
+
{% if services and services|length > 0 %}
|
|
64
|
+
<div class="service-list mt-1">
|
|
65
|
+
{% for svc, svc_ports in services|dictsort %}
|
|
66
|
+
<div class="service-row">
|
|
67
|
+
<div class="service-name">
|
|
68
|
+
<span class="material-symbols-outlined align-middle me-1">dns</span>{{ svc|default('Unknown') }}
|
|
69
|
+
</div>
|
|
70
|
+
<div class="service-ports">
|
|
71
|
+
{% set s_ports = svc_ports|default([]) %}
|
|
72
|
+
{% if s_ports and s_ports|length > 0 %}
|
|
73
|
+
{% for sp in s_ports %}
|
|
74
|
+
<span class="chip" title="{{ svc }} on {{ ip }}:{{ sp }}">{{ sp }}</span>
|
|
75
|
+
{% endfor %}
|
|
76
|
+
{% else %}
|
|
77
|
+
<span class="text-secondary">No ports</span>
|
|
78
|
+
{% endif %}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
{% endfor %}
|
|
82
|
+
</div>
|
|
83
|
+
{% else %}
|
|
84
|
+
<div class="text-secondary">No service information available.</div>
|
|
85
|
+
{% endif %}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="form-group mt-2 config-section">
|
|
89
|
+
<h6>Errors</h6>
|
|
90
|
+
{% if errors and errors|length > 0 %}
|
|
91
|
+
<ul class="list-unstyled error-list mt-1">
|
|
92
|
+
{% for err in errors %}
|
|
93
|
+
<li class="text-danger">
|
|
94
|
+
<span class="material-symbols-outlined align-middle me-1">error</span>
|
|
95
|
+
{{ err }}
|
|
96
|
+
</li>
|
|
97
|
+
{% endfor %}
|
|
98
|
+
</ul>
|
|
99
|
+
{% else %}
|
|
100
|
+
<div class="text-secondary">No errors captured.</div>
|
|
101
|
+
{% endif %}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="modal-footer border-secondary">
|
|
107
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|