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.
Files changed (38) hide show
  1. lanscape/libraries/app_scope.py +0 -1
  2. lanscape/libraries/decorators.py +10 -5
  3. lanscape/libraries/errors.py +10 -0
  4. lanscape/libraries/ip_parser.py +73 -1
  5. lanscape/libraries/logger.py +29 -1
  6. lanscape/libraries/mac_lookup.py +5 -0
  7. lanscape/libraries/net_tools.py +139 -68
  8. lanscape/libraries/port_manager.py +83 -0
  9. lanscape/libraries/runtime_args.py +12 -0
  10. lanscape/libraries/scan_config.py +148 -5
  11. lanscape/libraries/service_scan.py +3 -3
  12. lanscape/libraries/subnet_scan.py +104 -16
  13. lanscape/libraries/version_manager.py +50 -7
  14. lanscape/libraries/web_browser.py +136 -68
  15. lanscape/resources/mac_addresses/convert_csv.py +13 -2
  16. lanscape/resources/ports/convert_csv.py +13 -3
  17. lanscape/ui/__init__.py +0 -0
  18. lanscape/ui/app.py +32 -36
  19. lanscape/ui/blueprints/__init__.py +4 -1
  20. lanscape/ui/blueprints/api/__init__.py +2 -0
  21. lanscape/ui/blueprints/api/port.py +46 -0
  22. lanscape/ui/blueprints/api/scan.py +58 -10
  23. lanscape/ui/blueprints/api/tools.py +17 -1
  24. lanscape/ui/blueprints/web/__init__.py +4 -0
  25. lanscape/ui/blueprints/web/routes.py +52 -5
  26. lanscape/ui/main.py +17 -7
  27. lanscape/ui/shutdown_handler.py +57 -0
  28. lanscape/ui/static/css/style.css +94 -20
  29. lanscape/ui/static/js/main.js +25 -48
  30. lanscape/ui/static/js/scan-config.js +107 -0
  31. lanscape/ui/static/lanscape.webmanifest +4 -3
  32. lanscape/ui/templates/main.html +39 -36
  33. lanscape/ui/templates/scan/config.html +168 -0
  34. {lanscape-1.3.3.dist-info → lanscape-1.3.5.dist-info}/METADATA +1 -1
  35. {lanscape-1.3.3.dist-info → lanscape-1.3.5.dist-info}/RECORD +38 -34
  36. {lanscape-1.3.3.dist-info → lanscape-1.3.5.dist-info}/WHEEL +0 -0
  37. {lanscape-1.3.3.dist-info → lanscape-1.3.5.dist-info}/licenses/LICENSE +0 -0
  38. {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
- f'Unable to hook into closure of UI, listening for flask shutdown')
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
- raise RuntimeError('Unknown error while opening browser tab')
53
- except Exception as e:
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
- f'Exhausted all options to open browser, you need to open manually')
56
- log.debug(f'As tab error: {e}')
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
- try:
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
- if not desktop_file:
91
- # Fallback to xdg-settings
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
- elif sys.platform.startswith("darwin"):
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
- else:
142
- raise NotImplementedError(f"Unsupported platform: {sys.platform!r}")
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
- # Only used to import csv data - not during runtime
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
- # Only used to import csv data - not during runtime
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
 
File without changes
lanscape/ui/app.py CHANGED
@@ -1,13 +1,20 @@
1
- from flask import Flask, render_template, request
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 os
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 is_update_available, get_installed_version, lookup_latest_version
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
- exiting = False
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(e):
91
+ def internal_error(_):
101
92
  """
102
- handle internal errors nicely
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 dameon')
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
- from lanscape.libraries.subnet_scan import ScanManager
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,5 @@
1
+ """API blueprint def for lanscape"""
2
+
1
3
  from flask import Blueprint
2
4
 
3
5
  api_bp = Blueprint('api', __name__)
@@ -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
- from lanscape.ui.blueprints.api import api_bp
2
- from lanscape.libraries.subnet_scan import ScanConfig
3
- from lanscape.ui.blueprints import scan_manager
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
- pulls config from the request body
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 traceback
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
+ )
@@ -1,3 +1,7 @@
1
+ """
2
+ Blueprint for web-related routes and views.
3
+ """
4
+
1
5
  from flask import Blueprint
2
6
 
3
7
  web_bp = Blueprint('web', __name__)