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.
Files changed (36) hide show
  1. lanscape/__init__.py +9 -1
  2. lanscape/libraries/app_scope.py +0 -1
  3. lanscape/libraries/decorators.py +26 -9
  4. lanscape/libraries/device_alive.py +229 -0
  5. lanscape/libraries/errors.py +10 -0
  6. lanscape/libraries/ip_parser.py +73 -1
  7. lanscape/libraries/logger.py +29 -1
  8. lanscape/libraries/mac_lookup.py +5 -0
  9. lanscape/libraries/net_tools.py +156 -188
  10. lanscape/libraries/port_manager.py +83 -0
  11. lanscape/libraries/scan_config.py +173 -19
  12. lanscape/libraries/service_scan.py +3 -3
  13. lanscape/libraries/subnet_scan.py +111 -26
  14. lanscape/libraries/version_manager.py +50 -7
  15. lanscape/libraries/web_browser.py +75 -58
  16. lanscape/resources/mac_addresses/convert_csv.py +13 -2
  17. lanscape/resources/ports/convert_csv.py +13 -3
  18. lanscape/ui/app.py +24 -6
  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 +57 -5
  23. lanscape/ui/blueprints/api/tools.py +1 -0
  24. lanscape/ui/blueprints/web/__init__.py +4 -0
  25. lanscape/ui/blueprints/web/routes.py +52 -2
  26. lanscape/ui/main.py +1 -10
  27. lanscape/ui/shutdown_handler.py +5 -1
  28. lanscape/ui/static/css/style.css +35 -24
  29. lanscape/ui/static/js/scan-config.js +76 -2
  30. lanscape/ui/templates/main.html +0 -7
  31. lanscape/ui/templates/scan/config.html +71 -10
  32. {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/METADATA +27 -10
  33. {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/RECORD +36 -35
  34. {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/WHEEL +0 -0
  35. {lanscape-1.3.5a1.dist-info → lanscape-1.3.6.dist-info}/licenses/LICENSE +0 -0
  36. {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
- 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,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
- 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
66
  return windows_get_browser_from_registry()
64
67
 
68
+ if sys.platform.startswith("linux"):
69
+ return linux_get_browser_executable()
65
70
 
66
- elif sys.platform.startswith("linux"):
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
- else:
132
- 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
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
- import winreg # pylint: disable=import-outside-toplevel
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
- # 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
 
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 # pylint: ignore=unused-import
7
- from lanscape.ui.blueprints.api import api_bp, tools, port, scan # pylint: ignore=unused-import
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 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
+ )
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(e):
91
+ def internal_error(_):
77
92
  """
78
- 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
79
97
  """
80
98
  tb = traceback.format_exc()
81
99
  return render_template('error.html',
@@ -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,7 +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
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
+ Blueprint for web-related routes and views.
3
+ """
4
+
1
5
  from flask import Blueprint
2
6
 
3
7
  web_bp = Blueprint('web', __name__)
@@ -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
- filter = request.args.get('filter')
41
- return render_template('scan.html', data=data, section=section, filter=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...')
@@ -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('Web browser closed, terminating flask. (disable with --persistent)')
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."""