lanscape 1.3.5a1__py3-none-any.whl → 1.3.6__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 -1
- lanscape/libraries/app_scope.py +0 -1
- lanscape/libraries/decorators.py +26 -9
- lanscape/libraries/device_alive.py +229 -0
- lanscape/libraries/errors.py +10 -0
- lanscape/libraries/ip_parser.py +73 -1
- lanscape/libraries/logger.py +29 -1
- lanscape/libraries/mac_lookup.py +5 -0
- lanscape/libraries/net_tools.py +156 -188
- lanscape/libraries/port_manager.py +83 -0
- lanscape/libraries/scan_config.py +173 -19
- lanscape/libraries/service_scan.py +3 -3
- lanscape/libraries/subnet_scan.py +111 -26
- lanscape/libraries/version_manager.py +50 -7
- lanscape/libraries/web_browser.py +75 -58
- lanscape/resources/mac_addresses/convert_csv.py +13 -2
- lanscape/resources/ports/convert_csv.py +13 -3
- lanscape/ui/app.py +24 -6
- lanscape/ui/blueprints/__init__.py +4 -1
- lanscape/ui/blueprints/api/__init__.py +2 -0
- lanscape/ui/blueprints/api/port.py +46 -0
- lanscape/ui/blueprints/api/scan.py +57 -5
- lanscape/ui/blueprints/api/tools.py +1 -0
- lanscape/ui/blueprints/web/__init__.py +4 -0
- lanscape/ui/blueprints/web/routes.py +52 -2
- lanscape/ui/main.py +1 -10
- lanscape/ui/shutdown_handler.py +5 -1
- lanscape/ui/static/css/style.css +35 -24
- lanscape/ui/static/js/scan-config.js +76 -2
- lanscape/ui/templates/main.html +0 -7
- lanscape/ui/templates/scan/config.html +71 -10
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/METADATA +27 -10
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/RECORD +36 -35
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/WHEEL +0 -0
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/licenses/LICENSE +0 -0
- {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/top_level.txt +0 -0
|
@@ -37,7 +37,7 @@ def open_webapp(url: str) -> bool:
|
|
|
37
37
|
|
|
38
38
|
if time.time() - start < 2:
|
|
39
39
|
log.debug(
|
|
40
|
-
|
|
40
|
+
'Unable to hook into closure of UI, listening for flask shutdown')
|
|
41
41
|
return False
|
|
42
42
|
return True
|
|
43
43
|
|
|
@@ -49,70 +49,26 @@ def open_webapp(url: str) -> bool:
|
|
|
49
49
|
success = webbrowser.open(url)
|
|
50
50
|
log.debug(f'Opened {url} in browser tab: {success}')
|
|
51
51
|
if not success:
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
# pylint: disable=raise-missing-from
|
|
53
|
+
raise RuntimeError(
|
|
54
|
+
'Unknown error while opening browser tab') from e
|
|
55
|
+
except Exception as e2:
|
|
54
56
|
log.warning(
|
|
55
|
-
|
|
56
|
-
log.debug(f'As tab error: {
|
|
57
|
+
'Exhausted all options to open browser, you need to open manually')
|
|
58
|
+
log.debug(f'As tab error: {e2}')
|
|
57
59
|
log.info(f'LANScape UI is running on {url}')
|
|
58
60
|
return False
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
def get_default_browser_executable() -> Optional[str]:
|
|
64
|
+
"""Platform-agnostic method to get the default browser executable path."""
|
|
62
65
|
if sys.platform.startswith("win"):
|
|
63
66
|
return windows_get_browser_from_registry()
|
|
64
67
|
|
|
68
|
+
if sys.platform.startswith("linux"):
|
|
69
|
+
return linux_get_browser_executable()
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
# First, find the .desktop file name
|
|
68
|
-
desktop_file = None
|
|
69
|
-
try:
|
|
70
|
-
# Try xdg-mime
|
|
71
|
-
p = subprocess.run(
|
|
72
|
-
["xdg-mime", "query", "default", "x-scheme-handler/http"],
|
|
73
|
-
capture_output=True, text=True,
|
|
74
|
-
check=True
|
|
75
|
-
)
|
|
76
|
-
desktop_file = p.stdout.strip()
|
|
77
|
-
except subprocess.CalledProcessError:
|
|
78
|
-
pass
|
|
79
|
-
|
|
80
|
-
if not desktop_file:
|
|
81
|
-
# Fallback to xdg-settings
|
|
82
|
-
try:
|
|
83
|
-
p = subprocess.run(
|
|
84
|
-
["xdg-settings", "get", "default-web-browser"],
|
|
85
|
-
capture_output=True, text=True,
|
|
86
|
-
check=True
|
|
87
|
-
)
|
|
88
|
-
desktop_file = p.stdout.strip()
|
|
89
|
-
except subprocess.CalledProcessError:
|
|
90
|
-
pass
|
|
91
|
-
|
|
92
|
-
# Final fallback: BROWSER environment variable
|
|
93
|
-
if not desktop_file:
|
|
94
|
-
return os.environ.get("BROWSER")
|
|
95
|
-
|
|
96
|
-
# Look for that .desktop file in standard locations
|
|
97
|
-
search_paths = [
|
|
98
|
-
os.path.expanduser("~/.local/share/applications"),
|
|
99
|
-
"/usr/local/share/applications",
|
|
100
|
-
"/usr/share/applications",
|
|
101
|
-
]
|
|
102
|
-
for path in search_paths:
|
|
103
|
-
full_path = os.path.join(path, desktop_file)
|
|
104
|
-
if os.path.isfile(full_path):
|
|
105
|
-
with open(full_path, encoding="utf-8", errors="ignore") as f:
|
|
106
|
-
for line in f:
|
|
107
|
-
if line.startswith("Exec="):
|
|
108
|
-
exec_cmd = line[len("Exec="):].strip()
|
|
109
|
-
# strip arguments like “%u”, “--flag”, etc.
|
|
110
|
-
exec_cmd = exec_cmd.split()[0]
|
|
111
|
-
exec_cmd = exec_cmd.split("%")[0]
|
|
112
|
-
return exec_cmd
|
|
113
|
-
return None
|
|
114
|
-
|
|
115
|
-
elif sys.platform.startswith("darwin"):
|
|
71
|
+
if sys.platform.startswith("darwin"):
|
|
116
72
|
# macOS: try to find Chrome first for app mode support, fallback to default
|
|
117
73
|
try:
|
|
118
74
|
p = subprocess.run(
|
|
@@ -128,14 +84,75 @@ def get_default_browser_executable() -> Optional[str]:
|
|
|
128
84
|
# Fallback to system default
|
|
129
85
|
return "/usr/bin/open"
|
|
130
86
|
|
|
131
|
-
|
|
132
|
-
|
|
87
|
+
# Unsupported platform
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def linux_get_browser_executable() -> Optional[str]:
|
|
92
|
+
"""Get the default web browser executable path on Linux."""
|
|
93
|
+
# First, find the .desktop file name
|
|
94
|
+
desktop_file = None
|
|
95
|
+
try:
|
|
96
|
+
# Try xdg-mime
|
|
97
|
+
p = subprocess.run(
|
|
98
|
+
["xdg-mime", "query", "default", "x-scheme-handler/http"],
|
|
99
|
+
capture_output=True, text=True,
|
|
100
|
+
check=True
|
|
101
|
+
)
|
|
102
|
+
desktop_file = p.stdout.strip()
|
|
103
|
+
except subprocess.CalledProcessError:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
if not desktop_file:
|
|
107
|
+
# Fallback to xdg-settings
|
|
108
|
+
try:
|
|
109
|
+
p = subprocess.run(
|
|
110
|
+
["xdg-settings", "get", "default-web-browser"],
|
|
111
|
+
capture_output=True, text=True,
|
|
112
|
+
check=True
|
|
113
|
+
)
|
|
114
|
+
desktop_file = p.stdout.strip()
|
|
115
|
+
except subprocess.CalledProcessError:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
# Final fallback: BROWSER environment variable
|
|
119
|
+
if not desktop_file:
|
|
120
|
+
return os.environ.get("BROWSER")
|
|
121
|
+
|
|
122
|
+
# Look for that .desktop file in standard locations
|
|
123
|
+
search_paths = [
|
|
124
|
+
os.path.expanduser("~/.local/share/applications"),
|
|
125
|
+
"/usr/local/share/applications",
|
|
126
|
+
"/usr/share/applications",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
exec_cmd = None
|
|
130
|
+
for path in search_paths:
|
|
131
|
+
full_path = os.path.join(path, desktop_file)
|
|
132
|
+
if os.path.isfile(full_path):
|
|
133
|
+
with open(full_path, encoding="utf-8", errors="ignore") as f:
|
|
134
|
+
for line in f:
|
|
135
|
+
if line.startswith("Exec="):
|
|
136
|
+
exec_cmd = line[len("Exec="):].strip()
|
|
137
|
+
# strip arguments like "%u", "--flag", etc.
|
|
138
|
+
exec_cmd = exec_cmd.split()[0]
|
|
139
|
+
exec_cmd = exec_cmd.split("%")[0]
|
|
140
|
+
return exec_cmd
|
|
141
|
+
|
|
142
|
+
return exec_cmd
|
|
133
143
|
|
|
134
144
|
|
|
135
145
|
def windows_get_browser_from_registry() -> Optional[str]:
|
|
136
146
|
"""Get the default web browser executable path on Windows."""
|
|
147
|
+
# Import winreg only on Windows platforms
|
|
148
|
+
if not sys.platform.startswith("win"):
|
|
149
|
+
return None
|
|
137
150
|
|
|
138
|
-
|
|
151
|
+
try:
|
|
152
|
+
import winreg # pylint: disable=import-outside-toplevel
|
|
153
|
+
except ImportError:
|
|
154
|
+
log.debug("winreg module not available")
|
|
155
|
+
return None
|
|
139
156
|
|
|
140
157
|
def get_reg(base, path, key=None):
|
|
141
158
|
"""Helper function to read a registry key."""
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
CSV to JSON converter for MAC address vendor mappings.
|
|
3
|
+
Processes vendor MAC address prefix data from CSV format to a simplified JSON lookup table
|
|
4
|
+
for use in the LANscape application. Only used during development, not at runtime.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
import csv
|
|
4
8
|
import json
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
def main():
|
|
12
|
+
"""
|
|
13
|
+
Main function to convert CSV MAC vendor data to a JSON mapping.
|
|
14
|
+
|
|
15
|
+
Reads MAC vendor information from a CSV file, processes it to extract
|
|
16
|
+
MAC address prefixes and vendor names, and writes the resulting mapping
|
|
17
|
+
to a JSON file for efficient lookup.
|
|
18
|
+
"""
|
|
8
19
|
ans = {}
|
|
9
20
|
with open('mac-vendors-export.csv', 'r', encoding='utf-8') as f:
|
|
10
21
|
data = csv.reader(f)
|
|
@@ -15,7 +26,7 @@ def main():
|
|
|
15
26
|
ans[service['Mac Prefix']] = service['Vendor Name']
|
|
16
27
|
except BaseException:
|
|
17
28
|
pass
|
|
18
|
-
with open('mac_db.json', 'w') as f:
|
|
29
|
+
with open('mac_db.json', 'w', encoding='utf-8') as f:
|
|
19
30
|
json.dump(ans, f, indent=2)
|
|
20
31
|
|
|
21
32
|
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
CSV to JSON converter for port service mappings.
|
|
3
|
+
Converts IANA service-names-port-numbers CSV file to a simplified JSON format
|
|
4
|
+
for use in the LANscape application. Only used during development, not at runtime.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
import csv
|
|
4
8
|
import json
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
def main():
|
|
12
|
+
"""
|
|
13
|
+
Main function to convert CSV port data to a JSON mapping.
|
|
14
|
+
|
|
15
|
+
Reads port information from a CSV file, processes it to extract port numbers
|
|
16
|
+
and service names, and writes the resulting mapping to a JSON file.
|
|
17
|
+
"""
|
|
8
18
|
ans = {}
|
|
9
|
-
with open('service-names-port-numbers.csv', 'r') as f:
|
|
19
|
+
with open('service-names-port-numbers.csv', 'r', encoding='utf-8') as f:
|
|
10
20
|
data = csv.reader(f)
|
|
11
21
|
services = csv_to_dict(data)
|
|
12
22
|
for service in services:
|
|
@@ -15,7 +25,7 @@ def main():
|
|
|
15
25
|
ans[service['Port Number']] = service['Service Name']
|
|
16
26
|
except BaseException:
|
|
17
27
|
pass
|
|
18
|
-
with open('valid_ports.json', 'w') as f:
|
|
28
|
+
with open('valid_ports.json', 'w', encoding='utf-8') as f:
|
|
19
29
|
json.dump(ans, f, indent=2)
|
|
20
30
|
|
|
21
31
|
|
lanscape/ui/app.py
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
Flask application for LANscape web UI that provides device discovery and network monitoring.
|
|
3
|
+
Handles initialization, routing, error handling, and web server management.
|
|
4
|
+
"""
|
|
2
5
|
import traceback
|
|
3
6
|
import threading
|
|
4
7
|
import logging
|
|
5
8
|
from flask import Flask, render_template
|
|
6
|
-
from lanscape.ui.blueprints.web import web_bp, routes
|
|
7
|
-
from lanscape.ui.blueprints.api import api_bp, tools, port, scan
|
|
9
|
+
from lanscape.ui.blueprints.web import web_bp, routes # pylint: disable=unused-import
|
|
10
|
+
from lanscape.ui.blueprints.api import api_bp, tools, port, scan # pylint: disable=unused-import
|
|
8
11
|
from lanscape.libraries.runtime_args import RuntimeArgs, parse_args
|
|
9
|
-
from lanscape.libraries.version_manager import
|
|
12
|
+
from lanscape.libraries.version_manager import (
|
|
13
|
+
is_update_available, get_installed_version, lookup_latest_version
|
|
14
|
+
)
|
|
10
15
|
from lanscape.libraries.app_scope import is_local_run
|
|
11
16
|
from lanscape.libraries.net_tools import is_arp_supported
|
|
12
17
|
from lanscape.ui.shutdown_handler import FlaskShutdownHandler
|
|
@@ -27,6 +32,16 @@ app.register_blueprint(web_bp)
|
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
def is_substring_in_values(results: dict, substring: str) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Check if a substring exists in any value of a dictionary.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
results: Dictionary to search through values
|
|
40
|
+
substring: String to search for
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Boolean indicating if substring was found in any value
|
|
44
|
+
"""
|
|
30
45
|
return any(substring.lower() in str(v).lower() for v in results.values()) if substring else True
|
|
31
46
|
|
|
32
47
|
|
|
@@ -73,9 +88,12 @@ shutdown_handler.register_endpoints()
|
|
|
73
88
|
|
|
74
89
|
|
|
75
90
|
@app.errorhandler(500)
|
|
76
|
-
def internal_error(
|
|
91
|
+
def internal_error(_):
|
|
77
92
|
"""
|
|
78
|
-
|
|
93
|
+
Handle internal errors by showing a formatted error page with traceback.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Rendered error template with traceback information
|
|
79
97
|
"""
|
|
80
98
|
tb = traceback.format_exc()
|
|
81
99
|
return render_template('error.html',
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
"""Source for all things blueprint related in LANscape UI"""
|
|
2
2
|
import logging
|
|
3
|
+
|
|
4
|
+
from lanscape.libraries.subnet_scan import ScanManager
|
|
5
|
+
|
|
3
6
|
# defining here so blueprints can access the same
|
|
4
7
|
# manager instance
|
|
5
8
|
scan_manager = ScanManager()
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API endpoints for port list management in the LANscape application.
|
|
3
|
+
Provides CRUD operations for managing port lists used in network scans.
|
|
4
|
+
"""
|
|
1
5
|
from flask import request, jsonify
|
|
2
6
|
from lanscape.ui.blueprints.api import api_bp
|
|
3
7
|
from lanscape.libraries.port_manager import PortManager
|
|
@@ -8,26 +12,68 @@ from lanscape.libraries.port_manager import PortManager
|
|
|
8
12
|
|
|
9
13
|
@api_bp.route('/api/port/list', methods=['GET'])
|
|
10
14
|
def get_port_lists():
|
|
15
|
+
"""
|
|
16
|
+
Get all available port lists.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
JSON array of port list names
|
|
20
|
+
"""
|
|
11
21
|
return jsonify(PortManager().get_port_lists())
|
|
12
22
|
|
|
13
23
|
|
|
14
24
|
@api_bp.route('/api/port/list/<port_list>', methods=['GET'])
|
|
15
25
|
def get_port_list(port_list):
|
|
26
|
+
"""
|
|
27
|
+
Get a specific port list by name.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
port_list: Name of the port list to retrieve
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
JSON object mapping port numbers to service names
|
|
34
|
+
"""
|
|
16
35
|
return jsonify(PortManager().get_port_list(port_list))
|
|
17
36
|
|
|
18
37
|
|
|
19
38
|
@api_bp.route('/api/port/list/<port_list>', methods=['POST'])
|
|
20
39
|
def create_port_list(port_list):
|
|
40
|
+
"""
|
|
41
|
+
Create a new port list.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
port_list: Name for the new port list
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
JSON response indicating success or failure
|
|
48
|
+
"""
|
|
21
49
|
data = request.get_json()
|
|
22
50
|
return jsonify(PortManager().create_port_list(port_list, data))
|
|
23
51
|
|
|
24
52
|
|
|
25
53
|
@api_bp.route('/api/port/list/<port_list>', methods=['PUT'])
|
|
26
54
|
def update_port_list(port_list):
|
|
55
|
+
"""
|
|
56
|
+
Update an existing port list.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
port_list: Name of the port list to update
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
JSON response indicating success or failure
|
|
63
|
+
"""
|
|
27
64
|
data = request.get_json()
|
|
28
65
|
return jsonify(PortManager().update_port_list(port_list, data))
|
|
29
66
|
|
|
30
67
|
|
|
31
68
|
@api_bp.route('/api/port/list/<port_list>', methods=['DELETE'])
|
|
32
69
|
def delete_port_list(port_list):
|
|
70
|
+
"""
|
|
71
|
+
Delete a port list.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
port_list: Name of the port list to delete
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
JSON response indicating success or failure
|
|
78
|
+
"""
|
|
33
79
|
return jsonify(PortManager().delete_port_list(port_list))
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""
|
|
2
|
+
API endpoints for network scanning functionality in the LANscape application.
|
|
3
|
+
Provides routes for initiating, monitoring, and retrieving network scan results.
|
|
4
|
+
"""
|
|
4
5
|
|
|
5
|
-
from flask import request, jsonify
|
|
6
6
|
import json
|
|
7
7
|
import traceback
|
|
8
8
|
|
|
9
|
+
from flask import request, jsonify
|
|
10
|
+
|
|
11
|
+
from lanscape.ui.blueprints.api import api_bp
|
|
12
|
+
from lanscape.libraries.subnet_scan import ScanConfig
|
|
13
|
+
from lanscape.ui.blueprints import scan_manager
|
|
14
|
+
|
|
9
15
|
# Subnet Scanner API
|
|
10
16
|
############################################
|
|
11
17
|
|
|
@@ -13,6 +19,14 @@ import traceback
|
|
|
13
19
|
@api_bp.route('/api/scan', methods=['POST'])
|
|
14
20
|
@api_bp.route('/api/scan/threaded', methods=['POST'])
|
|
15
21
|
def scan_subnet_threaded():
|
|
22
|
+
"""
|
|
23
|
+
Start a new network scan in a separate thread.
|
|
24
|
+
|
|
25
|
+
Accepts scan configuration as JSON in the request body.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
JSON response with scan status and ID
|
|
29
|
+
"""
|
|
16
30
|
try:
|
|
17
31
|
config = get_scan_config()
|
|
18
32
|
scan = scan_manager.new_scan(config)
|
|
@@ -24,6 +38,14 @@ def scan_subnet_threaded():
|
|
|
24
38
|
|
|
25
39
|
@api_bp.route('/api/scan/async', methods=['POST'])
|
|
26
40
|
def scan_subnet_async():
|
|
41
|
+
"""
|
|
42
|
+
Start a scan and wait for it to complete before returning.
|
|
43
|
+
|
|
44
|
+
Accepts scan configuration as JSON in the request body.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
JSON response with scan status and ID after completion
|
|
48
|
+
"""
|
|
27
49
|
config = get_scan_config()
|
|
28
50
|
scan = scan_manager.new_scan(config)
|
|
29
51
|
scan_manager.wait_until_complete(scan.uid)
|
|
@@ -33,6 +55,15 @@ def scan_subnet_async():
|
|
|
33
55
|
|
|
34
56
|
@api_bp.route('/api/scan/<scan_id>', methods=['GET'])
|
|
35
57
|
def get_scan(scan_id):
|
|
58
|
+
"""
|
|
59
|
+
Retrieve the full results of a completed scan.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
scan_id: Unique identifier for the scan
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
JSON representation of scan results
|
|
66
|
+
"""
|
|
36
67
|
scan = scan_manager.get_scan(scan_id)
|
|
37
68
|
# cast to str and back to handle custom JSON serialization
|
|
38
69
|
return jsonify(json.loads(scan.results.export(str)))
|
|
@@ -40,6 +71,15 @@ def get_scan(scan_id):
|
|
|
40
71
|
|
|
41
72
|
@api_bp.route('/api/scan/<scan_id>/summary', methods=['GET'])
|
|
42
73
|
def get_scan_summary(scan_id):
|
|
74
|
+
"""
|
|
75
|
+
Retrieve a summary of the scan results.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
scan_id: Unique identifier for the scan
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
JSON representation of scan summary
|
|
82
|
+
"""
|
|
43
83
|
scan = scan_manager.get_scan(scan_id)
|
|
44
84
|
if not scan:
|
|
45
85
|
return jsonify({'error': 'scan not found'}), 404
|
|
@@ -58,6 +98,15 @@ def get_scan_summary(scan_id):
|
|
|
58
98
|
|
|
59
99
|
@api_bp.route('/api/scan/<scan_id>/terminate', methods=['GET'])
|
|
60
100
|
def terminate_scan(scan_id):
|
|
101
|
+
"""Terminate a running scan.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
scan_id (str): Unique identifier for the scan
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
JSON response indicating success or failure
|
|
108
|
+
"""
|
|
109
|
+
|
|
61
110
|
scan = scan_manager.get_scan(scan_id)
|
|
62
111
|
scan.terminate()
|
|
63
112
|
return jsonify({'success': True})
|
|
@@ -65,7 +114,10 @@ def terminate_scan(scan_id):
|
|
|
65
114
|
|
|
66
115
|
def get_scan_config():
|
|
67
116
|
"""
|
|
68
|
-
|
|
117
|
+
Extract and parse scan configuration from the request body.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
ScanConfig object constructed from the request JSON data
|
|
69
121
|
"""
|
|
70
122
|
data = request.get_json()
|
|
71
123
|
return ScanConfig.from_dict(data)
|
|
@@ -13,6 +13,7 @@ from lanscape.libraries.scan_config import DEFAULT_CONFIGS
|
|
|
13
13
|
|
|
14
14
|
@api_bp.route('/api/tools/subnet/test')
|
|
15
15
|
def test_subnet():
|
|
16
|
+
"""check validity of a subnet"""
|
|
16
17
|
subnet = request.args.get('subnet')
|
|
17
18
|
if not subnet:
|
|
18
19
|
return jsonify({'valid': False, 'msg': 'Subnet cannot be blank', 'count': -1})
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Web blueprint routes for the LANscape application.
|
|
3
|
+
Handles UI views including the main dashboard, scan results, error display, and exports.
|
|
4
|
+
"""
|
|
1
5
|
from flask import render_template, request, redirect
|
|
2
6
|
from lanscape.ui.blueprints.web import web_bp
|
|
3
7
|
from lanscape.libraries.net_tools import (
|
|
@@ -12,6 +16,12 @@ from lanscape.ui.blueprints import scan_manager, log
|
|
|
12
16
|
|
|
13
17
|
@web_bp.route('/', methods=['GET'])
|
|
14
18
|
def index():
|
|
19
|
+
"""
|
|
20
|
+
Render the main application interface.
|
|
21
|
+
|
|
22
|
+
Displays the primary network subnet selection interface and existing scan results.
|
|
23
|
+
If a scan_id is provided, it loads the configuration from that scan.
|
|
24
|
+
"""
|
|
15
25
|
subnets = get_all_network_subnets()
|
|
16
26
|
subnet = smart_select_primary_subnet(subnets)
|
|
17
27
|
|
|
@@ -35,16 +45,35 @@ def index():
|
|
|
35
45
|
@web_bp.route('/scan/<scan_id>', methods=['GET'])
|
|
36
46
|
@web_bp.route('/scan/<scan_id>/<section>', methods=['GET'])
|
|
37
47
|
def render_scan(scan_id, section='all'):
|
|
48
|
+
"""
|
|
49
|
+
Render a specific scan result.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
scan_id: Unique identifier for the scan
|
|
53
|
+
section: Section of the scan results to display (default: 'all')
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Rendered scan template or redirect to home if scan not found
|
|
57
|
+
"""
|
|
38
58
|
if scanner := scan_manager.get_scan(scan_id):
|
|
39
59
|
data = scanner.results.export()
|
|
40
|
-
|
|
41
|
-
return render_template('scan.html', data=data, section=section, filter=
|
|
60
|
+
filter_text = request.args.get('filter')
|
|
61
|
+
return render_template('scan.html', data=data, section=section, filter=filter_text)
|
|
42
62
|
log.debug(f'Redirecting, scan {scan_id} doesnt exist in memory')
|
|
43
63
|
return redirect('/')
|
|
44
64
|
|
|
45
65
|
|
|
46
66
|
@web_bp.route('/errors/<scan_id>')
|
|
47
67
|
def view_errors(scan_id):
|
|
68
|
+
"""
|
|
69
|
+
Display errors that occurred during a scan.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
scan_id: Unique identifier for the scan
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Rendered error template or redirect to home if scan not found
|
|
76
|
+
"""
|
|
48
77
|
if scanner := scan_manager.get_scan(scan_id):
|
|
49
78
|
data = scanner.results.export()
|
|
50
79
|
return render_template('scan/scan-error.html', data=data)
|
|
@@ -54,6 +83,15 @@ def view_errors(scan_id):
|
|
|
54
83
|
|
|
55
84
|
@web_bp.route('/export/<scan_id>')
|
|
56
85
|
def export_scan(scan_id):
|
|
86
|
+
"""
|
|
87
|
+
Provide an exportable view of scan results.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
scan_id: Unique identifier for the scan
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Rendered export template or redirect to home if scan not found
|
|
94
|
+
"""
|
|
57
95
|
if scanner := scan_manager.get_scan(scan_id):
|
|
58
96
|
export_json = scanner.results.export(str)
|
|
59
97
|
return render_template(
|
|
@@ -67,9 +105,21 @@ def export_scan(scan_id):
|
|
|
67
105
|
|
|
68
106
|
@web_bp.route('/shutdown-ui')
|
|
69
107
|
def shutdown_ui():
|
|
108
|
+
"""
|
|
109
|
+
Display the shutdown confirmation page.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Rendered shutdown template
|
|
113
|
+
"""
|
|
70
114
|
return render_template('shutdown.html')
|
|
71
115
|
|
|
72
116
|
|
|
73
117
|
@web_bp.route('/info')
|
|
74
118
|
def app_info():
|
|
119
|
+
"""
|
|
120
|
+
Display application information and version details.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Rendered info template
|
|
124
|
+
"""
|
|
75
125
|
return render_template('info.html')
|
lanscape/ui/main.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"""Main entry point for the LANscape application when running as a module."""
|
|
2
2
|
import socket
|
|
3
3
|
|
|
4
4
|
|
|
@@ -12,7 +12,6 @@ import requests
|
|
|
12
12
|
from lanscape.libraries.logger import configure_logging
|
|
13
13
|
from lanscape.libraries.runtime_args import parse_args
|
|
14
14
|
from lanscape.libraries.web_browser import open_webapp
|
|
15
|
-
from lanscape.libraries.net_tools import is_arp_supported
|
|
16
15
|
from lanscape.libraries.version_manager import get_installed_version, is_update_available
|
|
17
16
|
from lanscape.ui.app import start_webserver_daemon, start_webserver
|
|
18
17
|
# do this so any logs generated on import are displayed
|
|
@@ -49,14 +48,6 @@ def _main():
|
|
|
49
48
|
|
|
50
49
|
args.port = get_valid_port(args.port)
|
|
51
50
|
|
|
52
|
-
if not is_arp_supported():
|
|
53
|
-
warn = (
|
|
54
|
-
'ARP is not supported, device discovery is degraded. ',
|
|
55
|
-
'For more information, see the help guide: ',
|
|
56
|
-
'https://github.com/mdennis281/LANscape/blob/main/support/arp-issues.md'
|
|
57
|
-
)
|
|
58
|
-
log.warning(''.join(warn))
|
|
59
|
-
|
|
60
51
|
try:
|
|
61
52
|
start_webserver_ui()
|
|
62
53
|
log.info('Exiting...')
|
lanscape/ui/shutdown_handler.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Logic for handling shutdown requests in a Flask application."""
|
|
2
|
+
|
|
1
3
|
import logging
|
|
2
4
|
import os
|
|
3
5
|
from flask import request
|
|
@@ -40,12 +42,14 @@ class FlaskShutdownHandler:
|
|
|
40
42
|
if args.persistent:
|
|
41
43
|
log.info('Detected browser close, not exiting flask.')
|
|
42
44
|
return "Ignored"
|
|
43
|
-
log.info(
|
|
45
|
+
log.info(
|
|
46
|
+
'Web browser closed, terminating flask. (disable with --persistent)')
|
|
44
47
|
elif req_type == 'core':
|
|
45
48
|
log.info('Core requested exit, terminating flask.')
|
|
46
49
|
else:
|
|
47
50
|
log.info('Received external exit request. Terminating flask.')
|
|
48
51
|
self._exiting = True
|
|
52
|
+
return "Done"
|
|
49
53
|
|
|
50
54
|
def exit_if_requested(self):
|
|
51
55
|
"""Exits the application if a shutdown request has been made."""
|