lanscape 1.3.0a6__py3-none-any.whl → 1.3.1a1__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.
@@ -38,13 +38,19 @@ class IPAlive:
38
38
  for future in as_completed(futures):
39
39
  try:
40
40
  if future.result():
41
- # one check succeeded — dont block on the other
42
- executor.shutdown(wait=False, cancel_futures=True)
41
+ # one check succeeded — don't block on the other
42
+ # Cancel remaining futures in a version-compatible way
43
+ for f in futures:
44
+ if not f.done():
45
+ f.cancel()
46
+
47
+ executor.shutdown(wait=False) # Python 3.8 compatible
43
48
  return True
44
49
  except Exception as e:
45
50
  # treat any error as a False response
51
+ log.debug(f'Error while checking {ip}: {e}')
46
52
  self.caught_errors.append(DeviceError(e))
47
- pass
53
+
48
54
 
49
55
  # neither check found the host alive
50
56
  executor.shutdown()
@@ -341,17 +347,24 @@ def get_all_network_subnets():
341
347
 
342
348
  return subnets
343
349
 
344
- def smart_select_primary_subnet(subnets: List[dict]=get_all_network_subnets()) -> str:
350
+ def smart_select_primary_subnet(subnets: List[dict] | None = None) -> str:
345
351
  """
346
- Finds the largest subnet within max ip range
347
- not perfect, but works better than subnets[0]
352
+ Finds the largest subnet within max ip range. If no subnets are
353
+ available, returns an empty string instead of raising ``KeyError``.
348
354
  """
355
+ subnets = subnets or get_all_network_subnets()
356
+
357
+ if not subnets:
358
+ return ""
359
+
349
360
  selected = {}
350
361
  for subnet in subnets:
351
- if selected.get('address_cnt',0) < subnet['address_cnt'] < MAX_IPS_ALLOWED:
362
+ if selected.get("address_cnt", 0) < subnet["address_cnt"] < MAX_IPS_ALLOWED:
352
363
  selected = subnet
353
- if not selected and len(subnets):
364
+
365
+ if not selected:
354
366
  selected = subnets[0]
355
- return selected['subnet']
367
+
368
+ return selected.get("subnet", "")
356
369
 
357
370
 
@@ -10,6 +10,7 @@ class RuntimeArgs:
10
10
  logfile: bool = False
11
11
  loglevel: str = 'INFO'
12
12
  flask_logging: bool = False
13
+ persistent: bool = False
13
14
 
14
15
  def parse_args() -> RuntimeArgs:
15
16
  parser = argparse.ArgumentParser(description='LANscape')
@@ -19,6 +20,7 @@ def parse_args() -> RuntimeArgs:
19
20
  parser.add_argument('--logfile', action='store_true', help='Log output to lanscape.log')
20
21
  parser.add_argument('--loglevel', default='INFO', help='Set the log level')
21
22
  parser.add_argument('--flask-logging', action='store_true', help='Enable flask logging (disables click output)')
23
+ parser.add_argument('--persistent', action='store_true', help='Don\'t exit after browser is closed')
22
24
 
23
25
  # Parse the arguments
24
26
  args = parser.parse_args()
@@ -14,7 +14,7 @@ import webbrowser
14
14
  import logging
15
15
  import re
16
16
  import time
17
- import traceback
17
+ from typing import Optional
18
18
  from ..ui.app import app
19
19
 
20
20
  log = logging.getLogger('WebBrowser')
@@ -29,6 +29,13 @@ def open_webapp(url: str) -> bool:
29
29
  """
30
30
  start = time.time()
31
31
  try:
32
+ if sys.platform.startswith("darwin"):
33
+ # macOS does not support chrome-style app mode via the generic
34
+ # method. Fallback to the system "open" command which will use the
35
+ # default browser.
36
+ subprocess.run(["open", url], check=True)
37
+ return True
38
+
32
39
  exe = get_default_browser_executable()
33
40
  if not exe:
34
41
  raise RuntimeError('Unable to find browser binary')
@@ -56,7 +63,7 @@ def open_webapp(url: str) -> bool:
56
63
  return False
57
64
 
58
65
 
59
- def get_default_browser_executable() -> str | None:
66
+ def get_default_browser_executable() -> Optional[str]:
60
67
  if sys.platform.startswith("win"):
61
68
  try:
62
69
  import winreg
@@ -117,8 +124,12 @@ def get_default_browser_executable() -> str | None:
117
124
  # strip arguments like “%u”, “--flag”, etc.
118
125
  exec_cmd = exec_cmd.split()[0]
119
126
  exec_cmd = exec_cmd.split("%")[0]
120
- return exec_cmd
127
+ return exec_cmd
121
128
  return None
122
129
 
130
+ elif sys.platform.startswith("darwin"):
131
+ # macOS will use the system 'open' command to launch the default browser
132
+ return "/usr/bin/open"
133
+
123
134
  else:
124
135
  raise NotImplementedError(f"Unsupported platform: {sys.platform!r}")
@@ -90,7 +90,6 @@ class ApiTestCase(unittest.TestCase):
90
90
  ]
91
91
  for uri in uris:
92
92
  response = self.app.get(uri)
93
- print(uri, response.status_code)
94
93
  self.assertEqual(response.status_code,200)
95
94
 
96
95
 
lanscape/ui/app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, render_template
1
+ from flask import Flask, render_template, request
2
2
  from time import sleep
3
3
  import multiprocessing
4
4
  import traceback
@@ -62,11 +62,22 @@ set_global_safe('is_local',is_local_run)
62
62
  ################################
63
63
 
64
64
  exiting = False
65
- @app.route("/shutdown")
65
+ @app.route("/shutdown", methods=['GET', 'POST'])
66
66
  def exit_app():
67
+
68
+ req_type = request.args.get('type')
69
+ if req_type == 'browser-close':
70
+ args = parse_args()
71
+ if args.persistent:
72
+ log.info('Dectected browser close, not exiting flask.')
73
+ return "Ignored"
74
+ log.info('Web browser closed, terminating flask. (disable with --peristent)')
75
+ elif req_type == 'core':
76
+ log.info('Core requested exit, terminating flask.')
77
+ else:
78
+ log.info('Received external exit request. Terminating flask.')
67
79
  global exiting
68
80
  exiting = True
69
- log.info('Received external exit request. Terminating flask.')
70
81
  return "Done"
71
82
 
72
83
  @app.teardown_request
@@ -89,7 +100,7 @@ def internal_error(e):
89
100
  ## Webserver creation functions
90
101
  ################################
91
102
 
92
- def start_webserver_dameon(args: RuntimeArgs) -> threading.Thread:
103
+ def start_webserver_daemon(args: RuntimeArgs) -> threading.Thread:
93
104
  proc = threading.Thread(target=start_webserver, args=(args,))
94
105
  proc.daemon = True # Kill thread when main thread exits
95
106
  proc.start()
lanscape/ui/main.py CHANGED
@@ -12,7 +12,7 @@ args = parse_args()
12
12
  configure_logging(args.loglevel, args.logfile, args.flask_logging)
13
13
 
14
14
  from ..libraries.version_manager import get_installed_version, is_update_available
15
- from .app import start_webserver_dameon, start_webserver
15
+ from .app import start_webserver_daemon, start_webserver
16
16
  import socket
17
17
 
18
18
 
@@ -100,13 +100,13 @@ def start_webserver_ui(args: RuntimeArgs):
100
100
  ).start()
101
101
  start_webserver(args)
102
102
  else:
103
- flask_thread = start_webserver_dameon(args)
103
+ flask_thread = start_webserver_daemon(args)
104
104
  app_closed = open_browser(uri)
105
105
 
106
106
  # depending on env, open_browser may or
107
107
  # may not be coupled with the closure of UI
108
108
  # (if in browser tab, it's uncoupled)
109
- if not app_closed:
109
+ if not app_closed or args.persistent:
110
110
  # not doing a direct join so i can still
111
111
  # terminate the app with ctrl+c
112
112
  while flask_thread.is_alive():
@@ -126,7 +126,7 @@ def get_valid_port(port: int):
126
126
  def terminate():
127
127
  import requests
128
128
  log.info('Attempting flask shutdown')
129
- requests.get(f'http://127.0.0.1:{args.port}/shutdown')
129
+ requests.get(f'http://127.0.0.1:{args.port}/shutdown?type=core')
130
130
 
131
131
 
132
132
 
@@ -0,0 +1,29 @@
1
+ // This is the payload you want to send on unload.
2
+ // It can be as simple as JSON or just an empty POST.
3
+ function sendOnUnload() {
4
+ const url = '/shutdown?type=browser-close';
5
+ const data = JSON.stringify({ });
6
+ // (1) Using navigator.sendBeacon
7
+ if (navigator.sendBeacon) {
8
+ const blob = new Blob([data], { type: 'application/json' });
9
+ navigator.sendBeacon(url, blob);
10
+ }
11
+ // (2) Or—you can use fetch with keepalive (supported in modern browsers)
12
+ else {
13
+ fetch(url, {
14
+ method: 'POST',
15
+ body: data,
16
+ headers: { 'Content-Type': 'application/json' },
17
+ keepalive: true
18
+ })
19
+ .catch((err) => {
20
+ // If it fails, there’s not much you can do here.
21
+ console.warn('sendOnUnload fetch failed:', err);
22
+ });
23
+ }
24
+ }
25
+
26
+ // Attach to both beforeunload and unload to increase reliability.
27
+ // beforeunload fires slightly earlier, but some browsers block async work there.
28
+ window.addEventListener('beforeunload', sendOnUnload);
29
+ window.addEventListener('unload', sendOnUnload);
@@ -3,3 +3,4 @@
3
3
  <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
4
4
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"></script>
5
5
  <script src="{{ url_for('static', filename='js/core.js') }}"></script>
6
+ <script src="{{ url_for('static', filename='js/on-tab-close.js') }}"></script>
@@ -33,8 +33,8 @@
33
33
  This project has been a learning journey, & I hope it helps you
34
34
  discover more about your network as well. Enjoy!
35
35
  </p>
36
- <a href="https://github.com/mdennis281/" target="_blank"></a>
37
- <button class="btn btn-primary m-2">Github</button>
36
+ <a href="https://github.com/mdennis281/" target="_blank">
37
+ <button class="btn btn-primary m-2">GitHub</button>
38
38
  </a>
39
39
  <a href="https://github.com/mdennis281/LANscape" target="_blank">
40
40
  <button class="btn btn-secondary m-2">Project Repo</button>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lanscape
3
- Version: 1.3.0a6
3
+ Version: 1.3.1a1
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  Project-URL: Homepage, https://github.com/mdennis281/py-lanscape
@@ -14,7 +14,7 @@ License-File: LICENSE
14
14
  Requires-Dist: Flask<5.0,>=3.0
15
15
  Requires-Dist: psutil<7.0,>=6.0
16
16
  Requires-Dist: requests<3.0,>=2.32
17
- Requires-Dist: Setuptools<81
17
+ Requires-Dist: setuptools
18
18
  Requires-Dist: scapy<3.0,>=2.3.2
19
19
  Requires-Dist: tabulate==0.9.0
20
20
  Requires-Dist: pytest
@@ -32,7 +32,8 @@ python -m lanscape
32
32
  ```
33
33
 
34
34
  ## Flags
35
- - `--port <port number>` port of the flask app (default: 5001)
35
+ - `--port <port number>` port of the flask app (default: automagic)
36
+ - `--persistent` dont shutdown server when browser tab is closed (default: false)
36
37
  - `--reloader` essentially flask debug mode- good for local development (default: false)
37
38
  - `--logfile` save log output to lanscape.log
38
39
  - `--loglevel <level>` set the logger's log level (default: INFO)
@@ -55,6 +56,14 @@ can sometimes require admin-level permissions to retrieve accurate results.
55
56
  ### Message "WARNING: No libpcap provider available ! pcap won't be used"
56
57
  This is a missing dependency related to the ARP lookup. This is handled in the code, but you would get marginally faster/better results with this installed: [npcap download](https://npcap.com/#download)
57
58
 
59
+ ### The accuracy of the devices found is low
60
+ I use a combination of ARP and Ping to determine if a device is online. This method drops in stability when used in many threads.
61
+ Recommendations:
62
+
63
+ - Drop parallelism value (advanced dropdown)
64
+ - Use python > 3.10 im noticing threadpool improvements after this version
65
+ - Create a bug - I'm curious
66
+
58
67
 
59
68
  ### Something else
60
69
  Feel free to submit a github issue detailing your experience.
@@ -6,13 +6,13 @@ lanscape/libraries/errors.py,sha256=DaercNEZD_tUuXF7KsNk3SD6AqAwT-S7fvzpEybVn08,
6
6
  lanscape/libraries/ip_parser.py,sha256=ElXz3LU5CUYWqKOHEyrj5Y4Iv6OBtoSlbCcxhCsibfQ,2226
7
7
  lanscape/libraries/logger.py,sha256=doD8KKb4TNWDwVXc1VR7NK4UdharrAoRHl8vZnSAupI,1407
8
8
  lanscape/libraries/mac_lookup.py,sha256=-dRV0ygtjjh3JkgL3GTi_5-w7pcZ1oj4XVH4chjsmRs,2121
9
- lanscape/libraries/net_tools.py,sha256=boiccYtpv3v37l239b9jQvYUBgz_ynm0W149ceVCSBU,11473
9
+ lanscape/libraries/net_tools.py,sha256=JU4mnQptO3-WGGlGR4lCRvOM8qpoCIvw6CuApiTQ-t4,11855
10
10
  lanscape/libraries/port_manager.py,sha256=fNext3FNfGnGYRZK9RhTEwQ2K0e0YmmMlhK4zVAvoCw,1977
11
- lanscape/libraries/runtime_args.py,sha256=zL8QB_Y69OBGjScytuuyHqdy2XimwpypMSPM3_etz7g,1811
11
+ lanscape/libraries/runtime_args.py,sha256=ICX_JkOmqDQdewZNfRxJb9jMggDw1XqF5CxM9zXZE_Q,1947
12
12
  lanscape/libraries/service_scan.py,sha256=jLU84ZoJnqSQbE30Zly2lm2zHrCGutNXjla1sEvp1hE,1949
13
13
  lanscape/libraries/subnet_scan.py,sha256=0LW_xdoL-PRp59rJr6r6pSL3LiXEO_SJnjdrgEF_pO8,13120
14
14
  lanscape/libraries/version_manager.py,sha256=v-IsZ7sYIsNRiraIRckGZthlyL0iTuscR6jF_o9LBK8,1720
15
- lanscape/libraries/web_browser.py,sha256=mMc_48g2_GZ0FdHOUuy63pBgYKIdmEz16WtYd0pzhIM,4324
15
+ lanscape/libraries/web_browser.py,sha256=jtF__C2rTXhlZsp98l2l5Qa-V2nUA8egu-Ldvo5k8lQ,4800
16
16
  lanscape/resources/mac_addresses/convert_csv.py,sha256=w3Heed5z2mHYDEZNBep3_hNg4dbrp_N6J54MGxnrq4s,721
17
17
  lanscape/resources/mac_addresses/mac_db.json,sha256=ygtFSwNwJzDlg6hmAujdgCyzUjxt9Di75J8SO4xYIs8,2187804
18
18
  lanscape/resources/ports/convert_csv.py,sha256=mWe8zucWVfnlNEx_ZzH5Vc3tJJbdi-Ih4nm2yKNrRN0,720
@@ -23,11 +23,11 @@ lanscape/resources/ports/small.json,sha256=Mj3zGVG1F2eqZx2YkrLpTL8STeLcqB8_65IR6
23
23
  lanscape/resources/services/definitions.jsonc,sha256=71w9Q7r4RoBYiIMkzzO2KdEJXaSIchNccYQueqAhD4E,8842
24
24
  lanscape/tests/__init__.py,sha256=xYPeceOF-ppTS0wnq7CkVYQMwupmeSHxbYLbGj_imZ0,113
25
25
  lanscape/tests/_helpers.py,sha256=wXJfUwzL3Fq4XBsC3dValCbXsf0U8FisuM_yo1de4QQ,371
26
- lanscape/tests/test_api.py,sha256=881eHwSHT5GnOB61zbt3BRzCZnSwN2lndwCM5kIHLgo,7359
26
+ lanscape/tests/test_api.py,sha256=sjcjt3b1ZwvQiALOSfXoQxTlN1Z1_TRPC0EEy-bRIgE,7313
27
27
  lanscape/tests/test_env.py,sha256=ivFhCcemJ9vbe0_KtUkbqDY4r9nsDB8rVLUVjV-sNj8,673
28
28
  lanscape/tests/test_library.py,sha256=OPcTsUoR5IureSNDbePxid2BG98mfNNIJmCIY0BVz3w,1553
29
- lanscape/ui/app.py,sha256=Efa9gJnAcA_a3taCne9EHU4YxTCoRDd1M6UGa3rb2Qw,3126
30
- lanscape/ui/main.py,sha256=zDs7epDeXRoxZAHt9aLXB3kypXdZa1pSeqCIitwN7Ig,3998
29
+ lanscape/ui/app.py,sha256=jMr72omFHOJJWxb9vwfpC7Lg-YIpw-4sZFAbWHnx_D4,3594
30
+ lanscape/ui/main.py,sha256=KObgvdOlIhx4VQgcaNLcLrnkFXmOhfVpUyI8tZDQmJo,4027
31
31
  lanscape/ui/blueprints/__init__.py,sha256=agvgPOSVbrxddaw6EY64ZZr1CQi1Qzwcs1t0lZMv5oY,206
32
32
  lanscape/ui/blueprints/api/__init__.py,sha256=t0QOq3vHFWmlZm_3YFPQbQzCn1a_a5cmRchtIxwy4eY,103
33
33
  lanscape/ui/blueprints/api/port.py,sha256=2UA38umzXE8pMitx1E-_wJHyL1dYYbtM6Kg5zVtfj6A,1019
@@ -47,25 +47,26 @@ lanscape/ui/static/img/ico/site.webmanifest,sha256=ep4Hzh9zhmiZF2At3Fp1dQrYQuYF_
47
47
  lanscape/ui/static/js/core.js,sha256=y-f8iQPIetllUY0lSCwnGbPCk5fTJbbU6Pxm3rw1EBU,1111
48
48
  lanscape/ui/static/js/layout-sizing.js,sha256=23UuKdEmRChg6fyqj3DRvcsNfMoa6MRt6dkaT0k7_UY,841
49
49
  lanscape/ui/static/js/main.js,sha256=s0ipGqmuuFHnH9KKoUVaDRJ10_YqYoJ-9r_YnbsH8Mw,7676
50
+ lanscape/ui/static/js/on-tab-close.js,sha256=VkEpnvrK1abY3fE8YnfAOZB4J1XvUZB8qz_m1elD2cQ,1095
50
51
  lanscape/ui/static/js/quietReload.js,sha256=_mHzpUsGL4Lm1hNsr3VYSOGVcgGA2y1-eZHacssTXGs,724
51
52
  lanscape/ui/static/js/shutdown-server.js,sha256=WkO7_SNSHM_6kReUoCoExIdCf7Sl7IPiSiNxpbI-r0s,469
52
53
  lanscape/ui/static/js/subnet-info.js,sha256=aytt0LkBx4FVq36TxiMEw3aM7XQLHg_ng1U2WDwZVF4,577
53
54
  lanscape/ui/static/js/subnet-selector.js,sha256=OG01pDaSOPLq3Ial0aO0CqPcob9tPZA1MZKGmQG0W7Q,366
54
55
  lanscape/ui/templates/base.html,sha256=P5xnMlvDXYkYSXdSZUWaRfhsszNuZPP7A56hemBrAFs,1498
55
56
  lanscape/ui/templates/error.html,sha256=zXFO0zPIfQORWq1ZMiSZ8G7FjfhVVr-aaYC0HeBl4Rs,1068
56
- lanscape/ui/templates/info.html,sha256=oCC59keGEfgUB4WaCozaeZEfNb8Nr7y61DmkRBMqs18,2461
57
+ lanscape/ui/templates/info.html,sha256=K2ckljQujyXBaf0VmKO7IMm2S_wmN0sHD4vWA8Y4gJA,2457
57
58
  lanscape/ui/templates/main.html,sha256=M12xJSN6Ga565vIPhdCiqcr1tYgDrqzuQTeuXtk-8yo,3759
58
59
  lanscape/ui/templates/scan.html,sha256=Fz1Q4CzRq5qpKgszTAQLhaLVV0A6gBraT33mNDmpYRE,390
59
60
  lanscape/ui/templates/shutdown.html,sha256=v0cGT5CJWi-V8b5sUN3l-QIDNUmHTvKGi2gDlhmRlrs,724
60
61
  lanscape/ui/templates/core/head.html,sha256=6XyoDIz-IMPIRG-ti2LFY9FFX39UTIf_K6-6USni_ek,788
61
- lanscape/ui/templates/core/scripts.html,sha256=TRW74VUDasOTFYkaDhKKFnEwHyNx-_rmzSm5nE4n_ys,521
62
+ lanscape/ui/templates/core/scripts.html,sha256=5dg0KsPnBPKXIuCah3CKCJRE7aijBE3dhUXMUzj15-U,601
62
63
  lanscape/ui/templates/scan/export.html,sha256=Qi0m2xJPbC5I2rxzekXjvQ6q9gm2Lr4VJW6riLhIaU0,776
63
64
  lanscape/ui/templates/scan/ip-table-row.html,sha256=ptY24rxJRaA4PEEQRDncaq6Q0ql5RJ87Kn0zKRCzOHw,4842
64
65
  lanscape/ui/templates/scan/ip-table.html,sha256=ds__UP9JiTKf5IxCmTMzw--eN_yg1Pvn3Nj1KvQxeZg,940
65
66
  lanscape/ui/templates/scan/overview.html,sha256=FsX-jSFhGKwCxZGKE8AMKk328UuawN6O9RNTzYvIOts,1205
66
67
  lanscape/ui/templates/scan/scan-error.html,sha256=Q4eZM5ThrxnFaWOSTUpK8hA2ksHwhxOBTaVUCLALhyA,1032
67
- lanscape-1.3.0a6.dist-info/licenses/LICENSE,sha256=cCO-NbS01Ilwc6djHjZ7LIgPFRkRmWdr0fH2ysXKioA,1090
68
- lanscape-1.3.0a6.dist-info/METADATA,sha256=HhwZRp7f-0IwPDHginCcuPUqp5TsdPHJgecFcF4jUN0,2123
69
- lanscape-1.3.0a6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
70
- lanscape-1.3.0a6.dist-info/top_level.txt,sha256=E9D4sjPz_6H7c85Ycy_pOS2xuv1Wm-ilKhxEprln2ps,9
71
- lanscape-1.3.0a6.dist-info/RECORD,,
68
+ lanscape-1.3.1a1.dist-info/licenses/LICENSE,sha256=cCO-NbS01Ilwc6djHjZ7LIgPFRkRmWdr0fH2ysXKioA,1090
69
+ lanscape-1.3.1a1.dist-info/METADATA,sha256=EnT4ZtH3JI88rf2QLLpUs9h8Uc1d-pOK0XB_8vK064g,2567
70
+ lanscape-1.3.1a1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
71
+ lanscape-1.3.1a1.dist-info/top_level.txt,sha256=E9D4sjPz_6H7c85Ycy_pOS2xuv1Wm-ilKhxEprln2ps,9
72
+ lanscape-1.3.1a1.dist-info/RECORD,,