lanscape 1.3.4__tar.gz → 1.3.5a1__tar.gz

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.

Potentially problematic release.


This version of lanscape might be problematic. Click here for more details.

Files changed (84) hide show
  1. {lanscape-1.3.4/lanscape.egg-info → lanscape-1.3.5a1}/PKG-INFO +1 -1
  2. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/net_tools.py +20 -7
  3. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/runtime_args.py +12 -0
  4. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/scan_config.py +50 -2
  5. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/web_browser.py +62 -11
  6. lanscape-1.3.5a1/lanscape/ui/__init__.py +0 -0
  7. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/app.py +11 -33
  8. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/blueprints/api/scan.py +1 -5
  9. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/blueprints/api/tools.py +16 -1
  10. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/blueprints/web/routes.py +0 -3
  11. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/main.py +16 -6
  12. lanscape-1.3.5a1/lanscape/ui/shutdown_handler.py +53 -0
  13. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/css/style.css +94 -20
  14. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/js/main.js +25 -48
  15. lanscape-1.3.5a1/lanscape/ui/static/js/scan-config.js +107 -0
  16. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/lanscape.webmanifest +4 -3
  17. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/main.html +39 -36
  18. lanscape-1.3.5a1/lanscape/ui/templates/scan/config.html +168 -0
  19. {lanscape-1.3.4 → lanscape-1.3.5a1/lanscape.egg-info}/PKG-INFO +1 -1
  20. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape.egg-info/SOURCES.txt +4 -0
  21. {lanscape-1.3.4 → lanscape-1.3.5a1}/pyproject.toml +1 -1
  22. {lanscape-1.3.4 → lanscape-1.3.5a1}/tests/test_api.py +1 -2
  23. {lanscape-1.3.4 → lanscape-1.3.5a1}/LICENSE +0 -0
  24. {lanscape-1.3.4 → lanscape-1.3.5a1}/MANIFEST.in +0 -0
  25. {lanscape-1.3.4 → lanscape-1.3.5a1}/README.md +0 -0
  26. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/__init__.py +0 -0
  27. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/__main__.py +0 -0
  28. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/__init__.py +0 -0
  29. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/app_scope.py +0 -0
  30. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/decorators.py +0 -0
  31. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/errors.py +0 -0
  32. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/ip_parser.py +0 -0
  33. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/logger.py +0 -0
  34. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/mac_lookup.py +0 -0
  35. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/port_manager.py +0 -0
  36. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/service_scan.py +0 -0
  37. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/subnet_scan.py +0 -0
  38. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/libraries/version_manager.py +0 -0
  39. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/resources/mac_addresses/convert_csv.py +0 -0
  40. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/resources/mac_addresses/mac_db.json +0 -0
  41. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/resources/ports/convert_csv.py +0 -0
  42. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/resources/ports/full.json +0 -0
  43. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/resources/ports/large.json +0 -0
  44. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/resources/ports/medium.json +0 -0
  45. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/resources/ports/small.json +0 -0
  46. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/resources/services/definitions.jsonc +0 -0
  47. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/blueprints/__init__.py +0 -0
  48. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/blueprints/api/__init__.py +0 -0
  49. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/blueprints/api/port.py +0 -0
  50. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/blueprints/web/__init__.py +0 -0
  51. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/img/ico/android-chrome-192x192.png +0 -0
  52. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/img/ico/android-chrome-512x512.png +0 -0
  53. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/img/ico/apple-touch-icon.png +0 -0
  54. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/img/ico/favicon-16x16.png +0 -0
  55. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/img/ico/favicon-32x32.png +0 -0
  56. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/img/ico/favicon.ico +0 -0
  57. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/img/ico/site.webmanifest +0 -0
  58. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/js/core.js +0 -0
  59. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/js/layout-sizing.js +0 -0
  60. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/js/on-tab-close.js +0 -0
  61. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/js/quietReload.js +0 -0
  62. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/js/shutdown-server.js +0 -0
  63. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/js/subnet-info.js +0 -0
  64. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/static/js/subnet-selector.js +0 -0
  65. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/base.html +0 -0
  66. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/core/head.html +0 -0
  67. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/core/scripts.html +0 -0
  68. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/error.html +0 -0
  69. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/info.html +0 -0
  70. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/scan/export.html +0 -0
  71. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/scan/ip-table-row.html +0 -0
  72. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/scan/ip-table.html +0 -0
  73. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/scan/overview.html +0 -0
  74. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/scan/scan-error.html +0 -0
  75. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/scan.html +0 -0
  76. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape/ui/templates/shutdown.html +0 -0
  77. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape.egg-info/dependency_links.txt +0 -0
  78. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape.egg-info/requires.txt +0 -0
  79. {lanscape-1.3.4 → lanscape-1.3.5a1}/lanscape.egg-info/top_level.txt +0 -0
  80. {lanscape-1.3.4 → lanscape-1.3.5a1}/setup.cfg +0 -0
  81. {lanscape-1.3.4 → lanscape-1.3.5a1}/tests/test_env.py +0 -0
  82. {lanscape-1.3.4 → lanscape-1.3.5a1}/tests/test_library.py +0 -0
  83. {lanscape-1.3.4 → lanscape-1.3.5a1}/tests/test_logging.py +0 -0
  84. {lanscape-1.3.4 → lanscape-1.3.5a1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lanscape
3
- Version: 1.3.4
3
+ Version: 1.3.5a1
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  License-Expression: MIT
@@ -1,5 +1,4 @@
1
1
  import logging
2
- import platform
3
2
  import ipaddress
4
3
  import traceback
5
4
  import subprocess
@@ -28,6 +27,9 @@ log = logging.getLogger('NetTools')
28
27
  class IPAlive(JobStatsMixin):
29
28
  """Class to check if a device is alive using ARP and/or ping scans."""
30
29
  caught_errors: List[DeviceError] = []
30
+ _icmp_alive: bool = False
31
+ _arp_alive: bool = False
32
+
31
33
 
32
34
  @job_tracker
33
35
  def is_alive(
@@ -95,11 +97,21 @@ class IPAlive(JobStatsMixin):
95
97
  self.caught_errors.append(DeviceError(e))
96
98
  # Fallback to system ping command
97
99
  try:
98
- cmd = ["ping", "-c", str(cfg.ping_count), "-W", str(cfg.timeout), ip] if not psutil.WINDOWS else \
99
- ["ping", "-n", str(cfg.ping_count), "-w", str(cfg.timeout * 1000), ip]
100
- result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
100
+ if psutil.WINDOWS:
101
+ cmd = [
102
+ "ping", "-n", str(cfg.ping_count),
103
+ "-w", str(int(cfg.timeout * 1000)), ip
104
+ ]
105
+ else:
106
+ cmd = ["ping", "-c", str(cfg.ping_count), "-W", str(cfg.timeout), ip]
107
+
108
+ result = subprocess.run(
109
+ cmd, stdout=subprocess.PIPE,
110
+ stderr=subprocess.PIPE,
111
+ text=True, check=False
112
+ )
101
113
  return result.returncode == 0
102
- except Exception as fallback_error:
114
+ except subprocess.CalledProcessError as fallback_error:
103
115
  self.caught_errors.append(DeviceError(fallback_error))
104
116
  return False
105
117
 
@@ -117,6 +129,7 @@ class IPAlive(JobStatsMixin):
117
129
 
118
130
  class Device(IPAlive):
119
131
  """Represents a network device with metadata and scanning capabilities."""
132
+
120
133
  def __init__(self, ip: str):
121
134
  super().__init__()
122
135
  self.ip: str = ip
@@ -245,7 +258,7 @@ def get_ip_address(interface: str):
245
258
  def unix_like(): # Combined Linux and macOS
246
259
  try:
247
260
  # pylint: disable=import-outside-toplevel, import-error
248
- import fcntl
261
+ import fcntl
249
262
  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
250
263
  ip_address = socket.inet_ntoa(fcntl.ioctl(
251
264
  sock.fileno(),
@@ -280,7 +293,7 @@ def get_netmask(interface: str):
280
293
  def unix_like(): # Combined Linux and macOS
281
294
  try:
282
295
  # pylint: disable=import-outside-toplevel, import-error
283
- import fcntl
296
+ import fcntl
284
297
  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
285
298
  netmask = socket.inet_ntoa(fcntl.ioctl(
286
299
  sock.fileno(),
@@ -1,3 +1,5 @@
1
+ """Runtime argument handler for LANscape as module"""
2
+
1
3
  from dataclasses import dataclass, fields
2
4
  import argparse
3
5
  from typing import Any, Dict, Optional
@@ -5,6 +7,7 @@ from typing import Any, Dict, Optional
5
7
 
6
8
  @dataclass
7
9
  class RuntimeArgs:
10
+ """Class representing runtime arguments for the application."""
8
11
  reloader: bool = False
9
12
  port: int = 5001
10
13
  logfile: Optional[str] = None
@@ -14,6 +17,9 @@ class RuntimeArgs:
14
17
 
15
18
 
16
19
  def parse_args() -> RuntimeArgs:
20
+ """
21
+ Parse command line arguments and return a RuntimeArgs instance.
22
+ """
17
23
  parser = argparse.ArgumentParser(description='LANscape')
18
24
 
19
25
  parser.add_argument('--reloader', action='store_true',
@@ -27,6 +33,8 @@ def parse_args() -> RuntimeArgs:
27
33
  help='Enable flask logging (disables click output)')
28
34
  parser.add_argument('--persistent', action='store_true',
29
35
  help='Don\'t exit after browser is closed')
36
+ parser.add_argument('--debug', action='store_true',
37
+ help='Shorthand debug mode (equivalent to "--loglevel DEBUG --reloader")')
30
38
 
31
39
  # Parse the arguments
32
40
  args = parser.parse_args()
@@ -37,6 +45,10 @@ def parse_args() -> RuntimeArgs:
37
45
  field_names = {field.name for field in fields(
38
46
  RuntimeArgs)} # Get dataclass field names
39
47
 
48
+ if args.debug:
49
+ args_dict['loglevel'] = 'DEBUG'
50
+ args_dict['reloader'] = True
51
+
40
52
  # Only pass arguments that exist in the Args dataclass
41
53
  filtered_args = {name: args_dict[name]
42
54
  for name in field_names if name in args_dict}
@@ -1,8 +1,9 @@
1
- from typing import List
1
+ from typing import List, Dict
2
2
  import ipaddress
3
3
  from pydantic import BaseModel, Field
4
4
  from enum import Enum
5
5
 
6
+
6
7
  from lanscape.libraries.port_manager import PortManager
7
8
  from lanscape.libraries.ip_parser import parse_ip_input
8
9
 
@@ -82,7 +83,9 @@ class ScanConfig(BaseModel):
82
83
  return cls.model_validate(data)
83
84
 
84
85
  def to_dict(self) -> dict:
85
- return self.model_dump()
86
+ dump = self.model_dump()
87
+ dump['lookup_type'] = self.lookup_type.value
88
+ return dump
86
89
 
87
90
  def get_ports(self) -> List[int]:
88
91
  return PortManager().get_port_list(self.port_list).keys()
@@ -95,3 +98,48 @@ class ScanConfig(BaseModel):
95
98
  b = f'ports={self.port_list}'
96
99
  c = f'scan_type={self.lookup_type.value}'
97
100
  return f'ScanConfig({a}, {b}, {c})'
101
+
102
+
103
+ DEFAULT_CONFIGS: Dict[str, ScanConfig] = {
104
+ 'balanced': ScanConfig(subnet='', port_list='medium'),
105
+ 'accurate': ScanConfig(
106
+ subnet='',
107
+ port_list='large',
108
+ t_cnt_port_scan=5,
109
+ t_cnt_port_test=64,
110
+ t_cnt_isalive=64,
111
+ task_scan_ports=True,
112
+ task_scan_port_services=False,
113
+ lookup_type=ScanType.BOTH,
114
+ arp_config=ArpConfig(
115
+ attempts=3,
116
+ timeout=2.5
117
+ ),
118
+ ping_config=PingConfig(
119
+ attempts=3,
120
+ ping_count=2,
121
+ timeout=1.5,
122
+ retry_delay=0.5
123
+ )
124
+ ),
125
+ 'fast': ScanConfig(
126
+ subnet='',
127
+ port_list='small',
128
+ t_cnt_port_scan=20,
129
+ t_cnt_port_test=256,
130
+ t_cnt_isalive=512,
131
+ task_scan_ports=True,
132
+ task_scan_port_services=False,
133
+ lookup_type=ScanType.BOTH,
134
+ arp_config=ArpConfig(
135
+ attempts=1,
136
+ timeout=1.0
137
+ ),
138
+ ping_config=PingConfig(
139
+ attempts=1,
140
+ ping_count=1,
141
+ timeout=0.5,
142
+ retry_delay=0.25
143
+ )
144
+ )
145
+ }
@@ -60,18 +60,8 @@ def open_webapp(url: str) -> bool:
60
60
 
61
61
  def get_default_browser_executable() -> Optional[str]:
62
62
  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
63
+ return windows_get_browser_from_registry()
71
64
 
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
65
 
76
66
  elif sys.platform.startswith("linux"):
77
67
  # First, find the .desktop file name
@@ -140,3 +130,64 @@ def get_default_browser_executable() -> Optional[str]:
140
130
 
141
131
  else:
142
132
  raise NotImplementedError(f"Unsupported platform: {sys.platform!r}")
133
+
134
+
135
+ def windows_get_browser_from_registry() -> Optional[str]:
136
+ """Get the default web browser executable path on Windows."""
137
+
138
+ import winreg # pylint: disable=import-outside-toplevel
139
+
140
+ def get_reg(base, path, key=None):
141
+ """Helper function to read a registry key."""
142
+ try:
143
+ with winreg.OpenKey(base, path) as reg:
144
+ return winreg.QueryValueEx(reg, key)[0]
145
+ except FileNotFoundError:
146
+ return None
147
+
148
+ def extract_executable(cmd: str) -> Optional[str]:
149
+ """Extract the executable path from a command string."""
150
+ match = re.match(r'"?([^"]+)"?', cmd)
151
+ return match.group(1) if match else None
152
+
153
+ def get_user_preferred_browser():
154
+ """Get the user preferred browser from the registry."""
155
+ progid = get_reg(
156
+ winreg.HKEY_CURRENT_USER,
157
+ r'Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice',
158
+ 'ProgId'
159
+ )
160
+ if not progid:
161
+ log.debug('No user preferred browser found in registry')
162
+ return None
163
+
164
+ browser_path = get_reg(
165
+ winreg.HKEY_CLASSES_ROOT,
166
+ f'{progid}\\shell\\open\\command'
167
+ )
168
+
169
+ if not browser_path:
170
+ log.debug(f'progid {progid} does not have a command in registry')
171
+ return None
172
+
173
+ return extract_executable(browser_path)
174
+
175
+ def get_system_default_browser():
176
+ """Get the system default browser from the registry."""
177
+ reg = get_reg(
178
+ winreg.HKEY_CLASSES_ROOT,
179
+ r'http\shell\open\command'
180
+ )
181
+ if not reg:
182
+ log.debug('No system default browser found in registry')
183
+ return None
184
+
185
+ return extract_executable(reg)
186
+
187
+ user_browser = get_user_preferred_browser()
188
+ if user_browser:
189
+ return extract_executable(user_browser)
190
+
191
+ system_browser = get_system_default_browser()
192
+ if system_browser:
193
+ return extract_executable(system_browser)
File without changes
@@ -1,13 +1,15 @@
1
- from flask import Flask, render_template, request
1
+
2
2
  import traceback
3
3
  import threading
4
4
  import logging
5
- import os
6
-
5
+ 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
7
8
  from lanscape.libraries.runtime_args import RuntimeArgs, parse_args
8
9
  from lanscape.libraries.version_manager import is_update_available, get_installed_version, lookup_latest_version
9
10
  from lanscape.libraries.app_scope import is_local_run
10
11
  from lanscape.libraries.net_tools import is_arp_supported
12
+ from lanscape.ui.shutdown_handler import FlaskShutdownHandler
11
13
 
12
14
  app = Flask(
13
15
  __name__
@@ -15,9 +17,7 @@ app = Flask(
15
17
  log = logging.getLogger('flask')
16
18
 
17
19
  # 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
20
+ #################################
21
21
 
22
22
  app.register_blueprint(api_bp)
23
23
  app.register_blueprint(web_bp)
@@ -65,32 +65,8 @@ set_global_safe('is_arp_supported', is_arp_supported)
65
65
  # External hook to kill flask server
66
66
  ################################
67
67
 
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)
68
+ shutdown_handler = FlaskShutdownHandler(app)
69
+ shutdown_handler.register_endpoints()
94
70
 
95
71
  # Generalized error handling
96
72
  ################################
@@ -111,14 +87,16 @@ def internal_error(e):
111
87
 
112
88
 
113
89
  def start_webserver_daemon(args: RuntimeArgs) -> threading.Thread:
90
+ """Start the web server in a daemon thread."""
114
91
  proc = threading.Thread(target=start_webserver, args=(args,))
115
92
  proc.daemon = True # Kill thread when main thread exits
116
93
  proc.start()
117
- log.info('Flask server initializing as dameon')
94
+ log.info('Flask server initializing as daemon')
118
95
  return proc
119
96
 
120
97
 
121
98
  def start_webserver(args: RuntimeArgs) -> int:
99
+ """Start webserver (blocking)"""
122
100
  run_args = {
123
101
  'host': '0.0.0.0',
124
102
  'port': args.port,
@@ -68,8 +68,4 @@ def get_scan_config():
68
68
  pulls config from the request body
69
69
  """
70
70
  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
- )
71
+ return ScanConfig.from_dict(data)
@@ -1,9 +1,14 @@
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')
@@ -34,3 +39,13 @@ def list_subnet():
34
39
  return jsonify(get_all_network_subnets())
35
40
  except BaseException:
36
41
  return jsonify({'error': traceback.format_exc()})
42
+
43
+
44
+ @api_bp.route('/api/tools/config/defaults')
45
+ def get_default_configs():
46
+ """
47
+ Get default scan configurations.
48
+ """
49
+ return jsonify(
50
+ {key: config.to_dict() for key, config in DEFAULT_CONFIGS.items()}
51
+ )
@@ -16,12 +16,10 @@ def index():
16
16
  subnet = smart_select_primary_subnet(subnets)
17
17
 
18
18
  port_list = 'medium'
19
- parallelism = 1
20
19
  if scan_id := request.args.get('scan_id'):
21
20
  if scan := scan_manager.get_scan(scan_id):
22
21
  subnet = scan.cfg.subnet
23
22
  port_list = scan.cfg.port_list
24
- parallelism = scan.cfg.t_multiplier
25
23
 
26
24
  else:
27
25
  log.debug(f'Redirecting, scan {scan_id} doesnt exist in memory')
@@ -30,7 +28,6 @@ def index():
30
28
  'main.html',
31
29
  subnet=subnet,
32
30
  port_list=port_list,
33
- parallelism=parallelism,
34
31
  alternate_subnets=subnets
35
32
  )
36
33
 
@@ -7,8 +7,10 @@ import time
7
7
  import logging
8
8
  import traceback
9
9
  import os
10
+ import requests
11
+
10
12
  from lanscape.libraries.logger import configure_logging
11
- from lanscape.libraries.runtime_args import parse_args, RuntimeArgs
13
+ from lanscape.libraries.runtime_args import parse_args
12
14
  from lanscape.libraries.web_browser import open_webapp
13
15
  from lanscape.libraries.net_tools import is_arp_supported
14
16
  from lanscape.libraries.version_manager import get_installed_version, is_update_available
@@ -25,6 +27,7 @@ IS_FLASK_RELOAD = os.environ.get("WERKZEUG_RUN_MAIN")
25
27
 
26
28
 
27
29
  def main():
30
+ """core entry point for running lanscape as a module."""
28
31
  try:
29
32
  _main()
30
33
  except KeyboardInterrupt:
@@ -47,10 +50,15 @@ def _main():
47
50
  args.port = get_valid_port(args.port)
48
51
 
49
52
  if not is_arp_supported():
50
- log.warning('ARP is not supported, device discovery is degraded. For more information, see the help guide: https://github.com/mdennis281/LANscape/blob/main/support/arp-issues.md')
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))
51
59
 
52
60
  try:
53
- start_webserver_ui(args)
61
+ start_webserver_ui()
54
62
  log.info('Exiting...')
55
63
  except Exception as e:
56
64
  # showing error in debug only because this is handled gracefully
@@ -60,6 +68,7 @@ def _main():
60
68
 
61
69
 
62
70
  def try_check_update():
71
+ """Check for updates and log if available."""
63
72
  try:
64
73
  if is_update_available():
65
74
  log.info('An update is available!')
@@ -86,7 +95,8 @@ def open_browser(url: str, wait=2) -> bool:
86
95
  return False
87
96
 
88
97
 
89
- def start_webserver_ui(args: RuntimeArgs):
98
+ def start_webserver_ui():
99
+ """Start the web server and open the UI in a browser."""
90
100
  uri = f'http://127.0.0.1:{args.port}'
91
101
 
92
102
  # running reloader requires flask to run in main thread
@@ -128,9 +138,9 @@ def get_valid_port(port: int):
128
138
 
129
139
 
130
140
  def terminate():
131
- import requests
141
+ """send a request to the shutdown flask"""
132
142
  log.info('Attempting flask shutdown')
133
- requests.get(f'http://127.0.0.1:{args.port}/shutdown?type=core')
143
+ requests.get(f'http://127.0.0.1:{args.port}/shutdown?type=core', timeout=2)
134
144
 
135
145
 
136
146
  if __name__ == "__main__":
@@ -0,0 +1,53 @@
1
+ import logging
2
+ import os
3
+ from flask import request
4
+
5
+
6
+ from lanscape.libraries.runtime_args import parse_args
7
+
8
+
9
+ log = logging.getLogger('shutdown_handler')
10
+
11
+
12
+ class FlaskShutdownHandler:
13
+ """Handles shutdown requests for the Flask application.
14
+ """
15
+
16
+ def __init__(self, app):
17
+ self.app = app
18
+ self._exiting = False
19
+
20
+ def register_endpoints(self):
21
+ """Register shutdown endpoints to the Flask app."""
22
+
23
+ @self.app.route('/shutdown', methods=['POST', 'GET'])
24
+ def shutdown():
25
+ req_type = request.args.get('type')
26
+ self.shutdown_request(req_type)
27
+ return "Done"
28
+
29
+ @self.app.teardown_request
30
+ def teardown(_):
31
+ self.exit_if_requested()
32
+
33
+ def shutdown_request(self, req_type: str):
34
+ """Handles shutdown requests based on the type of request.
35
+ Args:
36
+ req_type (str): The type of shutdown request.
37
+ """
38
+ if req_type == 'browser-close':
39
+ args = parse_args()
40
+ if args.persistent:
41
+ log.info('Detected browser close, not exiting flask.')
42
+ return "Ignored"
43
+ log.info('Web browser closed, terminating flask. (disable with --persistent)')
44
+ elif req_type == 'core':
45
+ log.info('Core requested exit, terminating flask.')
46
+ else:
47
+ log.info('Received external exit request. Terminating flask.')
48
+ self._exiting = True
49
+
50
+ def exit_if_requested(self):
51
+ """Exits the application if a shutdown request has been made."""
52
+ if self._exiting:
53
+ os._exit(0)