lanscape 1.3.3__py3-none-any.whl → 1.3.5__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/libraries/app_scope.py +0 -1
- lanscape/libraries/decorators.py +10 -5
- 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 +139 -68
- lanscape/libraries/port_manager.py +83 -0
- lanscape/libraries/runtime_args.py +12 -0
- lanscape/libraries/scan_config.py +148 -5
- lanscape/libraries/service_scan.py +3 -3
- lanscape/libraries/subnet_scan.py +104 -16
- lanscape/libraries/version_manager.py +50 -7
- lanscape/libraries/web_browser.py +136 -68
- lanscape/resources/mac_addresses/convert_csv.py +13 -2
- lanscape/resources/ports/convert_csv.py +13 -3
- lanscape/ui/__init__.py +0 -0
- lanscape/ui/app.py +32 -36
- 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 +58 -10
- lanscape/ui/blueprints/api/tools.py +17 -1
- lanscape/ui/blueprints/web/__init__.py +4 -0
- lanscape/ui/blueprints/web/routes.py +52 -5
- lanscape/ui/main.py +17 -7
- lanscape/ui/shutdown_handler.py +57 -0
- lanscape/ui/static/css/style.css +94 -20
- lanscape/ui/static/js/main.js +25 -48
- lanscape/ui/static/js/scan-config.js +107 -0
- lanscape/ui/static/lanscape.webmanifest +4 -3
- lanscape/ui/templates/main.html +39 -36
- lanscape/ui/templates/scan/config.html +168 -0
- {lanscape-1.3.3.dist-info → lanscape-1.3.5.dist-info}/METADATA +1 -1
- {lanscape-1.3.3.dist-info → lanscape-1.3.5.dist-info}/RECORD +38 -34
- {lanscape-1.3.3.dist-info → lanscape-1.3.5.dist-info}/WHEEL +0 -0
- {lanscape-1.3.3.dist-info → lanscape-1.3.5.dist-info}/licenses/LICENSE +0 -0
- {lanscape-1.3.3.dist-info → lanscape-1.3.5.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,80 +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
|
-
|
|
64
|
-
import winreg
|
|
65
|
-
# On Windows the HKEY_CLASSES_ROOT\http\shell\open\command key
|
|
66
|
-
# holds the command for opening HTTP URLs.
|
|
67
|
-
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r"http\shell\open\command") as key:
|
|
68
|
-
cmd, _ = winreg.QueryValueEx(key, None)
|
|
69
|
-
except Exception:
|
|
70
|
-
return None
|
|
71
|
-
|
|
72
|
-
# cmd usually looks like: '"C:\\Program Files\\Foo\\foo.exe" %1'
|
|
73
|
-
m = re.match(r'\"?(.+?\.exe)\"?', cmd)
|
|
74
|
-
return m.group(1) if m else None
|
|
75
|
-
|
|
76
|
-
elif sys.platform.startswith("linux"):
|
|
77
|
-
# First, find the .desktop file name
|
|
78
|
-
desktop_file = None
|
|
79
|
-
try:
|
|
80
|
-
# Try xdg-mime
|
|
81
|
-
p = subprocess.run(
|
|
82
|
-
["xdg-mime", "query", "default", "x-scheme-handler/http"],
|
|
83
|
-
capture_output=True, text=True,
|
|
84
|
-
check=True
|
|
85
|
-
)
|
|
86
|
-
desktop_file = p.stdout.strip()
|
|
87
|
-
except subprocess.CalledProcessError:
|
|
88
|
-
pass
|
|
66
|
+
return windows_get_browser_from_registry()
|
|
89
67
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try:
|
|
93
|
-
p = subprocess.run(
|
|
94
|
-
["xdg-settings", "get", "default-web-browser"],
|
|
95
|
-
capture_output=True, text=True,
|
|
96
|
-
check=True
|
|
97
|
-
)
|
|
98
|
-
desktop_file = p.stdout.strip()
|
|
99
|
-
except subprocess.CalledProcessError:
|
|
100
|
-
pass
|
|
101
|
-
|
|
102
|
-
# Final fallback: BROWSER environment variable
|
|
103
|
-
if not desktop_file:
|
|
104
|
-
return os.environ.get("BROWSER")
|
|
105
|
-
|
|
106
|
-
# Look for that .desktop file in standard locations
|
|
107
|
-
search_paths = [
|
|
108
|
-
os.path.expanduser("~/.local/share/applications"),
|
|
109
|
-
"/usr/local/share/applications",
|
|
110
|
-
"/usr/share/applications",
|
|
111
|
-
]
|
|
112
|
-
for path in search_paths:
|
|
113
|
-
full_path = os.path.join(path, desktop_file)
|
|
114
|
-
if os.path.isfile(full_path):
|
|
115
|
-
with open(full_path, encoding="utf-8", errors="ignore") as f:
|
|
116
|
-
for line in f:
|
|
117
|
-
if line.startswith("Exec="):
|
|
118
|
-
exec_cmd = line[len("Exec="):].strip()
|
|
119
|
-
# strip arguments like “%u”, “--flag”, etc.
|
|
120
|
-
exec_cmd = exec_cmd.split()[0]
|
|
121
|
-
exec_cmd = exec_cmd.split("%")[0]
|
|
122
|
-
return exec_cmd
|
|
123
|
-
return None
|
|
68
|
+
if sys.platform.startswith("linux"):
|
|
69
|
+
return linux_get_browser_executable()
|
|
124
70
|
|
|
125
|
-
|
|
71
|
+
if sys.platform.startswith("darwin"):
|
|
126
72
|
# macOS: try to find Chrome first for app mode support, fallback to default
|
|
127
73
|
try:
|
|
128
74
|
p = subprocess.run(
|
|
@@ -138,5 +84,127 @@ def get_default_browser_executable() -> Optional[str]:
|
|
|
138
84
|
# Fallback to system default
|
|
139
85
|
return "/usr/bin/open"
|
|
140
86
|
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def windows_get_browser_from_registry() -> Optional[str]:
|
|
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
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
import winreg # pylint: disable=import-outside-toplevel
|
|
153
|
+
except ImportError:
|
|
154
|
+
log.debug("winreg module not available")
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
def get_reg(base, path, key=None):
|
|
158
|
+
"""Helper function to read a registry key."""
|
|
159
|
+
try:
|
|
160
|
+
with winreg.OpenKey(base, path) as reg:
|
|
161
|
+
return winreg.QueryValueEx(reg, key)[0]
|
|
162
|
+
except FileNotFoundError:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
def extract_executable(cmd: str) -> Optional[str]:
|
|
166
|
+
"""Extract the executable path from a command string."""
|
|
167
|
+
match = re.match(r'"?([^"]+)"?', cmd)
|
|
168
|
+
return match.group(1) if match else None
|
|
169
|
+
|
|
170
|
+
def get_user_preferred_browser():
|
|
171
|
+
"""Get the user preferred browser from the registry."""
|
|
172
|
+
progid = get_reg(
|
|
173
|
+
winreg.HKEY_CURRENT_USER,
|
|
174
|
+
r'Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice',
|
|
175
|
+
'ProgId'
|
|
176
|
+
)
|
|
177
|
+
if not progid:
|
|
178
|
+
log.debug('No user preferred browser found in registry')
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
browser_path = get_reg(
|
|
182
|
+
winreg.HKEY_CLASSES_ROOT,
|
|
183
|
+
f'{progid}\\shell\\open\\command'
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if not browser_path:
|
|
187
|
+
log.debug(f'progid {progid} does not have a command in registry')
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
return extract_executable(browser_path)
|
|
191
|
+
|
|
192
|
+
def get_system_default_browser():
|
|
193
|
+
"""Get the system default browser from the registry."""
|
|
194
|
+
reg = get_reg(
|
|
195
|
+
winreg.HKEY_CLASSES_ROOT,
|
|
196
|
+
r'http\shell\open\command'
|
|
197
|
+
)
|
|
198
|
+
if not reg:
|
|
199
|
+
log.debug('No system default browser found in registry')
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
return extract_executable(reg)
|
|
203
|
+
|
|
204
|
+
user_browser = get_user_preferred_browser()
|
|
205
|
+
if user_browser:
|
|
206
|
+
return extract_executable(user_browser)
|
|
207
|
+
|
|
208
|
+
system_browser = get_system_default_browser()
|
|
209
|
+
if system_browser:
|
|
210
|
+
return extract_executable(system_browser)
|
|
@@ -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/__init__.py
ADDED
|
File without changes
|
lanscape/ui/app.py
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
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
|
-
import
|
|
6
|
-
|
|
8
|
+
from flask import Flask, render_template
|
|
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
|
|
7
11
|
from lanscape.libraries.runtime_args import RuntimeArgs, parse_args
|
|
8
|
-
from lanscape.libraries.version_manager import
|
|
12
|
+
from lanscape.libraries.version_manager import (
|
|
13
|
+
is_update_available, get_installed_version, lookup_latest_version
|
|
14
|
+
)
|
|
9
15
|
from lanscape.libraries.app_scope import is_local_run
|
|
10
16
|
from lanscape.libraries.net_tools import is_arp_supported
|
|
17
|
+
from lanscape.ui.shutdown_handler import FlaskShutdownHandler
|
|
11
18
|
|
|
12
19
|
app = Flask(
|
|
13
20
|
__name__
|
|
@@ -15,9 +22,7 @@ app = Flask(
|
|
|
15
22
|
log = logging.getLogger('flask')
|
|
16
23
|
|
|
17
24
|
# Import and register BPs
|
|
18
|
-
|
|
19
|
-
from lanscape.ui.blueprints.api import api_bp, tools, port, scan
|
|
20
|
-
from lanscape.ui.blueprints.web import web_bp, routes
|
|
25
|
+
#################################
|
|
21
26
|
|
|
22
27
|
app.register_blueprint(api_bp)
|
|
23
28
|
app.register_blueprint(web_bp)
|
|
@@ -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
|
|
|
@@ -65,41 +80,20 @@ set_global_safe('is_arp_supported', is_arp_supported)
|
|
|
65
80
|
# External hook to kill flask server
|
|
66
81
|
################################
|
|
67
82
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@app.route("/shutdown", methods=['GET', 'POST'])
|
|
72
|
-
def exit_app():
|
|
73
|
-
|
|
74
|
-
req_type = request.args.get('type')
|
|
75
|
-
if req_type == 'browser-close':
|
|
76
|
-
args = parse_args()
|
|
77
|
-
if args.persistent:
|
|
78
|
-
log.info('Dectected browser close, not exiting flask.')
|
|
79
|
-
return "Ignored"
|
|
80
|
-
log.info('Web browser closed, terminating flask. (disable with --peristent)')
|
|
81
|
-
elif req_type == 'core':
|
|
82
|
-
log.info('Core requested exit, terminating flask.')
|
|
83
|
-
else:
|
|
84
|
-
log.info('Received external exit request. Terminating flask.')
|
|
85
|
-
global exiting
|
|
86
|
-
exiting = True
|
|
87
|
-
return "Done"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@app.teardown_request
|
|
91
|
-
def teardown(exception):
|
|
92
|
-
if exiting:
|
|
93
|
-
os._exit(0)
|
|
83
|
+
shutdown_handler = FlaskShutdownHandler(app)
|
|
84
|
+
shutdown_handler.register_endpoints()
|
|
94
85
|
|
|
95
86
|
# Generalized error handling
|
|
96
87
|
################################
|
|
97
88
|
|
|
98
89
|
|
|
99
90
|
@app.errorhandler(500)
|
|
100
|
-
def internal_error(
|
|
91
|
+
def internal_error(_):
|
|
101
92
|
"""
|
|
102
|
-
|
|
93
|
+
Handle internal errors by showing a formatted error page with traceback.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Rendered error template with traceback information
|
|
103
97
|
"""
|
|
104
98
|
tb = traceback.format_exc()
|
|
105
99
|
return render_template('error.html',
|
|
@@ -111,14 +105,16 @@ def internal_error(e):
|
|
|
111
105
|
|
|
112
106
|
|
|
113
107
|
def start_webserver_daemon(args: RuntimeArgs) -> threading.Thread:
|
|
108
|
+
"""Start the web server in a daemon thread."""
|
|
114
109
|
proc = threading.Thread(target=start_webserver, args=(args,))
|
|
115
110
|
proc.daemon = True # Kill thread when main thread exits
|
|
116
111
|
proc.start()
|
|
117
|
-
log.info('Flask server initializing as
|
|
112
|
+
log.info('Flask server initializing as daemon')
|
|
118
113
|
return proc
|
|
119
114
|
|
|
120
115
|
|
|
121
116
|
def start_webserver(args: RuntimeArgs) -> int:
|
|
117
|
+
"""Start webserver (blocking)"""
|
|
122
118
|
run_args = {
|
|
123
119
|
'host': '0.0.0.0',
|
|
124
120
|
'port': args.port,
|
|
@@ -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,11 +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
|
-
return ScanConfig(
|
|
72
|
-
subnet=data['subnet'],
|
|
73
|
-
port_list=data['port_list'],
|
|
74
|
-
t_multiplier=data.get('parallelism', 1.0)
|
|
75
|
-
)
|
|
123
|
+
return ScanConfig.from_dict(data)
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API endpoints for subnet testing and listing.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import traceback
|
|
1
6
|
from flask import request, jsonify
|
|
2
7
|
from lanscape.ui.blueprints.api import api_bp
|
|
3
8
|
from lanscape.libraries.net_tools import get_all_network_subnets
|
|
4
9
|
from lanscape.libraries.ip_parser import parse_ip_input
|
|
5
10
|
from lanscape.libraries.errors import SubnetTooLargeError
|
|
6
|
-
import
|
|
11
|
+
from lanscape.libraries.scan_config import DEFAULT_CONFIGS
|
|
7
12
|
|
|
8
13
|
|
|
9
14
|
@api_bp.route('/api/tools/subnet/test')
|
|
10
15
|
def test_subnet():
|
|
16
|
+
"""check validity of a subnet"""
|
|
11
17
|
subnet = request.args.get('subnet')
|
|
12
18
|
if not subnet:
|
|
13
19
|
return jsonify({'valid': False, 'msg': 'Subnet cannot be blank', 'count': -1})
|
|
@@ -34,3 +40,13 @@ def list_subnet():
|
|
|
34
40
|
return jsonify(get_all_network_subnets())
|
|
35
41
|
except BaseException:
|
|
36
42
|
return jsonify({'error': traceback.format_exc()})
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@api_bp.route('/api/tools/config/defaults')
|
|
46
|
+
def get_default_configs():
|
|
47
|
+
"""
|
|
48
|
+
Get default scan configurations.
|
|
49
|
+
"""
|
|
50
|
+
return jsonify(
|
|
51
|
+
{key: config.to_dict() for key, config in DEFAULT_CONFIGS.items()}
|
|
52
|
+
)
|