lanscape 1.3.1a8__py3-none-any.whl → 1.3.2a6__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.
Potentially problematic release.
This version of lanscape might be problematic. Click here for more details.
- lanscape/__init__.py +4 -1
- lanscape/__main__.py +4 -1
- lanscape/ui/static/css/style.css +1 -1
- lanscape/ui/templates/core/scripts.html +1 -1
- lanscape/ui/templates/info.html +1 -1
- lanscape/ui/templates/main.html +6 -4
- {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/METADATA +4 -3
- lanscape-1.3.2a6.dist-info/RECORD +43 -0
- lanscape/libraries/app_scope.py +0 -70
- lanscape/libraries/decorators.py +0 -75
- lanscape/libraries/errors.py +0 -29
- lanscape/libraries/ip_parser.py +0 -65
- lanscape/libraries/logger.py +0 -42
- lanscape/libraries/mac_lookup.py +0 -69
- lanscape/libraries/net_tools.py +0 -480
- lanscape/libraries/port_manager.py +0 -59
- lanscape/libraries/runtime_args.py +0 -44
- lanscape/libraries/service_scan.py +0 -51
- lanscape/libraries/subnet_scan.py +0 -373
- lanscape/libraries/version_manager.py +0 -54
- lanscape/libraries/web_browser.py +0 -141
- lanscape/resources/mac_addresses/convert_csv.py +0 -27
- lanscape/resources/ports/convert_csv.py +0 -27
- lanscape/tests/__init__.py +0 -3
- lanscape/tests/_helpers.py +0 -15
- lanscape/tests/test_api.py +0 -194
- lanscape/tests/test_env.py +0 -30
- lanscape/tests/test_library.py +0 -53
- lanscape/ui/app.py +0 -122
- lanscape/ui/blueprints/__init__.py +0 -7
- lanscape/ui/blueprints/api/__init__.py +0 -5
- lanscape/ui/blueprints/api/port.py +0 -27
- lanscape/ui/blueprints/api/scan.py +0 -69
- lanscape/ui/blueprints/api/tools.py +0 -30
- lanscape/ui/blueprints/web/__init__.py +0 -5
- lanscape/ui/blueprints/web/routes.py +0 -74
- lanscape/ui/main.py +0 -138
- lanscape-1.3.1a8.dist-info/RECORD +0 -72
- {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/WHEEL +0 -0
- {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/licenses/LICENSE +0 -0
- {lanscape-1.3.1a8.dist-info → lanscape-1.3.2a6.dist-info}/top_level.txt +0 -0
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import json
|
|
3
|
-
import uuid
|
|
4
|
-
import logging
|
|
5
|
-
import ipaddress
|
|
6
|
-
import traceback
|
|
7
|
-
import threading
|
|
8
|
-
from time import time
|
|
9
|
-
from time import sleep
|
|
10
|
-
from typing import List, Union
|
|
11
|
-
from tabulate import tabulate
|
|
12
|
-
from dataclasses import dataclass
|
|
13
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
14
|
-
|
|
15
|
-
from .net_tools import Device, is_arp_supported
|
|
16
|
-
from .ip_parser import parse_ip_input
|
|
17
|
-
from .port_manager import PortManager
|
|
18
|
-
from.errors import SubnetScanTerminationFailure
|
|
19
|
-
from .decorators import job_tracker, JobStats, terminator
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
TCNT_PORT_SCANS = 10
|
|
23
|
-
TCNT_PORT_TEST = 128
|
|
24
|
-
TCNT_DEVICE_ISALIVE = 256
|
|
25
|
-
|
|
26
|
-
@dataclass
|
|
27
|
-
class ScanConfig:
|
|
28
|
-
subnet: str
|
|
29
|
-
port_list: str
|
|
30
|
-
t_multiplier: float = 1.0
|
|
31
|
-
t_cnt_port_scan: int = 10
|
|
32
|
-
t_cnt_port_test: int = 128
|
|
33
|
-
t_cnt_isalive: int = 256
|
|
34
|
-
|
|
35
|
-
task_scan_ports: bool = True
|
|
36
|
-
# below wont run if above false
|
|
37
|
-
task_scan_port_services: bool = False # disabling until more stable
|
|
38
|
-
|
|
39
|
-
def t_cnt(self, id: str) -> int:
|
|
40
|
-
return int(int(getattr(self, f't_cnt_{id}')) * float(self.t_multiplier))
|
|
41
|
-
|
|
42
|
-
@staticmethod
|
|
43
|
-
def from_dict(data: dict) -> 'ScanConfig':
|
|
44
|
-
return ScanConfig(
|
|
45
|
-
subnet = data['subnet'],
|
|
46
|
-
port_list = data['port_list'],
|
|
47
|
-
t_multiplier = data.get('parallelism',1.0),
|
|
48
|
-
t_cnt_port_scan = data.get('t_cnt_port_scan',10),
|
|
49
|
-
t_cnt_port_test = data.get('t_cnt_port_test',128),
|
|
50
|
-
t_cnt_isalive = data.get('t_cnt_isalive',256),
|
|
51
|
-
task_scan_ports = data.get('task_scan_ports',True),
|
|
52
|
-
task_scan_port_services = data.get('task_scan_port_services',True)
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
def get_ports(self) -> List[int]:
|
|
56
|
-
return PortManager().get_port_list(self.port_list).keys()
|
|
57
|
-
|
|
58
|
-
def parse_subnet(self) -> List[ipaddress.IPv4Network]:
|
|
59
|
-
return parse_ip_input(self.subnet)
|
|
60
|
-
|
|
61
|
-
def __str__(self):
|
|
62
|
-
return f'ScanCfg(subnet={self.subnet}, ports={self.port_list}, multiplier={self.t_multiplier})'
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class SubnetScanner:
|
|
68
|
-
def __init__(
|
|
69
|
-
self,
|
|
70
|
-
config: ScanConfig
|
|
71
|
-
):
|
|
72
|
-
self.cfg = config
|
|
73
|
-
self.subnet = config.parse_subnet()
|
|
74
|
-
self.ports: List[int] = config.get_ports()
|
|
75
|
-
self.running = False
|
|
76
|
-
self.subnet_str = config.subnet
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
self.job_stats = JobStats()
|
|
80
|
-
self.uid = str(uuid.uuid4())
|
|
81
|
-
self.results = ScannerResults(self)
|
|
82
|
-
self.log: logging.Logger = logging.getLogger('SubnetScanner')
|
|
83
|
-
if not is_arp_supported():
|
|
84
|
-
self.log.warning('ARP is not supported with the active runtime context. Device discovery will be limited to ping responses.')
|
|
85
|
-
self.log.debug(f'Instantiated with uid: {self.uid}')
|
|
86
|
-
self.log.debug(f'Port Count: {len(self.ports)} | Device Count: {len(self.subnet)}')
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def start(self):
|
|
92
|
-
"""
|
|
93
|
-
Scan the subnet for devices and open ports.
|
|
94
|
-
"""
|
|
95
|
-
self._set_stage('scanning devices')
|
|
96
|
-
self.running = True
|
|
97
|
-
with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('isalive')) as executor:
|
|
98
|
-
futures = {executor.submit(self._get_host_details, str(ip)): str(ip) for ip in self.subnet}
|
|
99
|
-
for future in as_completed(futures):
|
|
100
|
-
ip = futures[future]
|
|
101
|
-
try:
|
|
102
|
-
future.result()
|
|
103
|
-
except Exception as e:
|
|
104
|
-
self.log.error(f'[{ip}] scan failed. details below:\n{traceback.format_exc()}')
|
|
105
|
-
self.results.errors.append({
|
|
106
|
-
'basic': f"Error scanning IP {ip}: {e}",
|
|
107
|
-
'traceback': traceback.format_exc(),
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
self._set_stage('testing ports')
|
|
112
|
-
if self.cfg.task_scan_ports:
|
|
113
|
-
self._scan_network_ports()
|
|
114
|
-
self.running = False
|
|
115
|
-
self._set_stage('complete')
|
|
116
|
-
|
|
117
|
-
return self.results
|
|
118
|
-
|
|
119
|
-
def terminate(self):
|
|
120
|
-
self.running = False
|
|
121
|
-
self._set_stage('terminating')
|
|
122
|
-
for i in range(20):
|
|
123
|
-
if not len(self.job_stats.running.keys()):
|
|
124
|
-
self._set_stage('terminated')
|
|
125
|
-
return True
|
|
126
|
-
sleep(.5)
|
|
127
|
-
raise SubnetScanTerminationFailure(self.job_stats.running)
|
|
128
|
-
|
|
129
|
-
def calc_percent_complete(self) -> int: # 0 - 100
|
|
130
|
-
if not self.running: return 100
|
|
131
|
-
|
|
132
|
-
# --- Host discovery (isalive) calculations ---
|
|
133
|
-
avg_host_detail_sec = self.job_stats.timing.get('_get_host_details', 4.5)
|
|
134
|
-
# assume 10% alive percentage if the scan just started
|
|
135
|
-
if len(self.results.devices) and (self.results.devices_scanned):
|
|
136
|
-
est_subnet_alive_percent = (len(self.results.devices)) / (self.results.devices_scanned) # avoid div 0
|
|
137
|
-
else:
|
|
138
|
-
est_subnet_alive_percent = .1
|
|
139
|
-
est_subnet_devices = est_subnet_alive_percent * self.results.devices_total
|
|
140
|
-
|
|
141
|
-
remaining_isalive_sec = (self.results.devices_total - self.results.devices_scanned) * avg_host_detail_sec
|
|
142
|
-
total_isalive_sec = self.results.devices_total * avg_host_detail_sec
|
|
143
|
-
|
|
144
|
-
isalive_multiplier = self.cfg.t_cnt('isalive')
|
|
145
|
-
|
|
146
|
-
# --- Port scanning calculations ---
|
|
147
|
-
device_ports_scanned = self.job_stats.finished.get('_test_port', 0)
|
|
148
|
-
# remediate initial inaccurate results because open ports reurn quickly
|
|
149
|
-
avg_port_test_sec = self.job_stats.timing.get('_test_port', 1) if device_ports_scanned > 20 else 1
|
|
150
|
-
|
|
151
|
-
device_ports_unscanned = max(0, (est_subnet_devices*len(self.ports)) - device_ports_scanned)
|
|
152
|
-
|
|
153
|
-
remaining_port_test_sec = device_ports_unscanned * avg_port_test_sec
|
|
154
|
-
total_port_test_sec = est_subnet_devices * len(self.ports) * avg_port_test_sec
|
|
155
|
-
|
|
156
|
-
port_test_multiplier = self.cfg.t_cnt('port_scan') * self.cfg.t_cnt('port_test')
|
|
157
|
-
|
|
158
|
-
# --- Overall progress ---
|
|
159
|
-
est_total_time = (total_isalive_sec / isalive_multiplier) + (total_port_test_sec / port_test_multiplier)
|
|
160
|
-
est_remaining_time = (remaining_isalive_sec / isalive_multiplier) + (remaining_port_test_sec / port_test_multiplier)
|
|
161
|
-
|
|
162
|
-
return int(abs((1 - (est_remaining_time / est_total_time)) * 100))
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def debug_active_scan(self,sleep_sec=1):
|
|
167
|
-
"""
|
|
168
|
-
Run this after running scan_subnet_threaded
|
|
169
|
-
to see the progress of the scan
|
|
170
|
-
"""
|
|
171
|
-
while self.running:
|
|
172
|
-
percent = self.calc_percent_complete()
|
|
173
|
-
t_elapsed = time() - self.results.start_time
|
|
174
|
-
t_remain = int((100-percent) * (t_elapsed / percent)) if percent else '∞'
|
|
175
|
-
buffer = f'{self.uid} - {self.subnet_str}\n'
|
|
176
|
-
buffer += f'Elapsed: {int(t_elapsed)} sec - Remain: {t_remain} sec\n'
|
|
177
|
-
buffer += f'Scanned: {self.results.devices_scanned}/{self.results.devices_total}'
|
|
178
|
-
buffer += f' - {percent}%\n'
|
|
179
|
-
buffer += str(self.job_stats)
|
|
180
|
-
os.system('cls' if os.name == 'nt' else 'clear')
|
|
181
|
-
print(buffer)
|
|
182
|
-
sleep(sleep_sec)
|
|
183
|
-
|
|
184
|
-
@terminator
|
|
185
|
-
@job_tracker
|
|
186
|
-
def _get_host_details(self, host: str):
|
|
187
|
-
"""
|
|
188
|
-
Get the MAC address and open ports of the given host.
|
|
189
|
-
"""
|
|
190
|
-
device = Device(host)
|
|
191
|
-
device.alive = self._ping(device)
|
|
192
|
-
self.results.scanned()
|
|
193
|
-
if not device.alive:
|
|
194
|
-
return None
|
|
195
|
-
self.log.debug(f'[{host}] is alive, getting metadata')
|
|
196
|
-
device.get_metadata()
|
|
197
|
-
self.results.devices.append(device)
|
|
198
|
-
return True
|
|
199
|
-
|
|
200
|
-
@terminator
|
|
201
|
-
def _scan_network_ports(self):
|
|
202
|
-
with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('port_scan')) as executor:
|
|
203
|
-
futures = {executor.submit(self._scan_ports, device): device for device in self.results.devices}
|
|
204
|
-
for future in futures:
|
|
205
|
-
future.result()
|
|
206
|
-
|
|
207
|
-
@terminator
|
|
208
|
-
@job_tracker
|
|
209
|
-
def _scan_ports(self, device: Device):
|
|
210
|
-
self.log.debug(f'[{device.ip}] Initiating port scan')
|
|
211
|
-
device.stage = 'scanning'
|
|
212
|
-
with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('port_test')) as executor:
|
|
213
|
-
futures = {executor.submit(self._test_port, device, int(port)): port for port in self.ports}
|
|
214
|
-
for future in futures:
|
|
215
|
-
future.result()
|
|
216
|
-
self.log.debug(f'[{device.ip}] Completed port scan')
|
|
217
|
-
device.stage = 'complete'
|
|
218
|
-
|
|
219
|
-
@terminator
|
|
220
|
-
@job_tracker
|
|
221
|
-
def _test_port(self,host: Device, port: int):
|
|
222
|
-
"""
|
|
223
|
-
Test if a port is open on a given host.
|
|
224
|
-
If port open, determine service.
|
|
225
|
-
Device class handles tracking open ports.
|
|
226
|
-
"""
|
|
227
|
-
is_alive = host.test_port(port)
|
|
228
|
-
if is_alive and self.cfg.task_scan_port_services:
|
|
229
|
-
host.scan_service(port)
|
|
230
|
-
return is_alive
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
@terminator
|
|
234
|
-
@job_tracker
|
|
235
|
-
def _ping(self, host: Device):
|
|
236
|
-
"""
|
|
237
|
-
Ping the given host and return True if it's reachable, False otherwise.
|
|
238
|
-
"""
|
|
239
|
-
return host.is_alive(host.ip)
|
|
240
|
-
|
|
241
|
-
def _set_stage(self,stage):
|
|
242
|
-
self.log.debug(f'[{self.uid}] Moving to Stage: {stage}')
|
|
243
|
-
self.results.stage = stage
|
|
244
|
-
if not self.running:
|
|
245
|
-
self.results.end_time = time()
|
|
246
|
-
|
|
247
|
-
class ScannerResults:
|
|
248
|
-
def __init__(self,scan: SubnetScanner):
|
|
249
|
-
self.scan = scan
|
|
250
|
-
self.port_list: str = scan.cfg.port_list
|
|
251
|
-
self.subnet: str = scan.subnet_str
|
|
252
|
-
self.uid = scan.uid
|
|
253
|
-
|
|
254
|
-
self.devices_total: int = len(list(scan.subnet))
|
|
255
|
-
self.devices_scanned: int = 0
|
|
256
|
-
self.devices: List[Device] = []
|
|
257
|
-
|
|
258
|
-
self.errors: List[str] = []
|
|
259
|
-
self.running: bool = False
|
|
260
|
-
self.start_time: float = time()
|
|
261
|
-
self.end_time: int = None
|
|
262
|
-
self.stage = 'instantiated'
|
|
263
|
-
|
|
264
|
-
self.log = logging.getLogger('ScannerResults')
|
|
265
|
-
self.log.debug(f'Instantiated Logger For Scan: {self.scan.uid}')
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def scanned(self):
|
|
269
|
-
self.devices_scanned += 1
|
|
270
|
-
|
|
271
|
-
def get_runtime(self):
|
|
272
|
-
if self.scan.running:
|
|
273
|
-
return int(time()-self.start_time)
|
|
274
|
-
return int(self.end_time-self.start_time)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def export(self,out_type=dict) -> Union[str, dict]:
|
|
279
|
-
"""
|
|
280
|
-
Returns json representation of the scan
|
|
281
|
-
"""
|
|
282
|
-
|
|
283
|
-
self.running = self.scan.running
|
|
284
|
-
self.run_time = int(round(time() - self.start_time,0))
|
|
285
|
-
self.devices_alive = len(self.devices)
|
|
286
|
-
|
|
287
|
-
out = vars(self).copy()
|
|
288
|
-
out.pop('scan')
|
|
289
|
-
out.pop('log')
|
|
290
|
-
out['cfg'] = vars(self.scan.cfg)
|
|
291
|
-
|
|
292
|
-
devices: List[Device] = out.pop('devices')
|
|
293
|
-
sortedDevices = sorted(devices, key=lambda obj: ipaddress.IPv4Address(obj.ip))
|
|
294
|
-
out['devices'] = [device.dict() for device in sortedDevices]
|
|
295
|
-
|
|
296
|
-
if out_type == str:
|
|
297
|
-
return json.dumps(out,default=str, indent=2)
|
|
298
|
-
# otherwise return dict
|
|
299
|
-
return out
|
|
300
|
-
|
|
301
|
-
def __str__(self):
|
|
302
|
-
# Prepare data for tabulate
|
|
303
|
-
data = [
|
|
304
|
-
[device.ip, device.hostname, device.get_mac(), ", ".join(map(str, device.ports))]
|
|
305
|
-
for device in self.devices
|
|
306
|
-
]
|
|
307
|
-
|
|
308
|
-
# Create headers for the table
|
|
309
|
-
headers = ["IP", "Host", "MAC", "Ports"]
|
|
310
|
-
|
|
311
|
-
# Generate the table using tabulate
|
|
312
|
-
table = tabulate(data, headers=headers, tablefmt="grid")
|
|
313
|
-
|
|
314
|
-
# Format and return the complete buffer with table output
|
|
315
|
-
buffer = f"Scan Results - {self.scan.subnet_str} - {self.uid}\n"
|
|
316
|
-
buffer += "---------------------------------------------\n\n"
|
|
317
|
-
buffer += table
|
|
318
|
-
return buffer
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
class ScanManager:
|
|
322
|
-
"""
|
|
323
|
-
Maintain active and completed scans in memory for
|
|
324
|
-
future reference. Singleton implementation.
|
|
325
|
-
"""
|
|
326
|
-
_instance = None
|
|
327
|
-
|
|
328
|
-
def __new__(cls, *args, **kwargs):
|
|
329
|
-
if not cls._instance:
|
|
330
|
-
cls._instance = super(ScanManager, cls).__new__(cls, *args, **kwargs)
|
|
331
|
-
return cls._instance
|
|
332
|
-
|
|
333
|
-
def __init__(self):
|
|
334
|
-
if not hasattr(self, 'scans'): # Prevent reinitialization
|
|
335
|
-
self.scans: List[SubnetScanner] = []
|
|
336
|
-
self.log = logging.getLogger('ScanManager')
|
|
337
|
-
|
|
338
|
-
def new_scan(self, config: ScanConfig) -> SubnetScanner:
|
|
339
|
-
scan = SubnetScanner(config)
|
|
340
|
-
self._start(scan)
|
|
341
|
-
self.log.info(f'Scan started - {config}')
|
|
342
|
-
self.scans.append(scan)
|
|
343
|
-
return scan
|
|
344
|
-
|
|
345
|
-
def get_scan(self, scan_id: str) -> SubnetScanner:
|
|
346
|
-
"""
|
|
347
|
-
Get scan by scan.uid
|
|
348
|
-
"""
|
|
349
|
-
for scan in self.scans:
|
|
350
|
-
if scan.uid == scan_id:
|
|
351
|
-
return scan
|
|
352
|
-
|
|
353
|
-
def terminate_scans(self):
|
|
354
|
-
"""
|
|
355
|
-
Terminate all active scans
|
|
356
|
-
"""
|
|
357
|
-
for scan in self.scans:
|
|
358
|
-
if scan.running:
|
|
359
|
-
scan.terminate()
|
|
360
|
-
|
|
361
|
-
def wait_until_complete(self, scan_id: str) -> SubnetScanner:
|
|
362
|
-
scan = self.get_scan(scan_id)
|
|
363
|
-
while scan.running:
|
|
364
|
-
sleep(.5)
|
|
365
|
-
return scan
|
|
366
|
-
|
|
367
|
-
def _start(self, scan: SubnetScanner):
|
|
368
|
-
t = threading.Thread(target=scan.start)
|
|
369
|
-
t.start()
|
|
370
|
-
return t
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import requests
|
|
3
|
-
import traceback
|
|
4
|
-
from importlib.metadata import version, PackageNotFoundError
|
|
5
|
-
from random import randint
|
|
6
|
-
|
|
7
|
-
from .app_scope import is_local_run
|
|
8
|
-
|
|
9
|
-
log = logging.getLogger('VersionManager')
|
|
10
|
-
|
|
11
|
-
PACKAGE='lanscape'
|
|
12
|
-
LOCAL_VERSION = '0.0.0'
|
|
13
|
-
|
|
14
|
-
latest = None # used to 'remember' pypi version each runtime
|
|
15
|
-
|
|
16
|
-
def is_update_available(package=PACKAGE) -> bool:
|
|
17
|
-
installed = get_installed_version(package)
|
|
18
|
-
available = lookup_latest_version(package)
|
|
19
|
-
|
|
20
|
-
is_update_exempt = (
|
|
21
|
-
'a' in installed, 'b' in installed, # pre-release
|
|
22
|
-
installed == LOCAL_VERSION
|
|
23
|
-
)
|
|
24
|
-
if any(is_update_exempt): return False
|
|
25
|
-
|
|
26
|
-
log.debug(f'Installed: {installed} | Available: {available}')
|
|
27
|
-
return installed != available
|
|
28
|
-
|
|
29
|
-
def lookup_latest_version(package=PACKAGE):
|
|
30
|
-
# Fetch the latest version from PyPI
|
|
31
|
-
global latest
|
|
32
|
-
if not latest:
|
|
33
|
-
no_cache = f'?cachebust={randint(0,6969)}'
|
|
34
|
-
url = f"https://pypi.org/pypi/{package}/json{no_cache}"
|
|
35
|
-
try:
|
|
36
|
-
response = requests.get(url,timeout=5)
|
|
37
|
-
response.raise_for_status() # Raise an exception for HTTP errors
|
|
38
|
-
latest = response.json()['info']['version']
|
|
39
|
-
log.debug(f'Latest pypi version: {latest}')
|
|
40
|
-
except:
|
|
41
|
-
log.debug(traceback.format_exc())
|
|
42
|
-
log.warning('Unable to fetch package version from PyPi')
|
|
43
|
-
return latest
|
|
44
|
-
|
|
45
|
-
def get_installed_version(package=PACKAGE):
|
|
46
|
-
if not is_local_run():
|
|
47
|
-
try:
|
|
48
|
-
return version(package)
|
|
49
|
-
except PackageNotFoundError:
|
|
50
|
-
log.debug(traceback.format_exc())
|
|
51
|
-
log.warning(f'Cannot find {package} installation')
|
|
52
|
-
return LOCAL_VERSION
|
|
53
|
-
|
|
54
|
-
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Get the executable path of the system’s default web browser.
|
|
4
|
-
|
|
5
|
-
Supports:
|
|
6
|
-
- Windows (reads from the registry)
|
|
7
|
-
- Linux (uses xdg-mime / xdg-settings + .desktop file parsing)
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import sys
|
|
11
|
-
import os
|
|
12
|
-
import subprocess
|
|
13
|
-
import webbrowser
|
|
14
|
-
import logging
|
|
15
|
-
import re
|
|
16
|
-
import time
|
|
17
|
-
from typing import Optional
|
|
18
|
-
from ..ui.app import app
|
|
19
|
-
|
|
20
|
-
log = logging.getLogger('WebBrowser')
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def open_webapp(url: str) -> bool:
|
|
24
|
-
"""
|
|
25
|
-
will try to open the web page as an app
|
|
26
|
-
on failure, will open as a tab in default browser
|
|
27
|
-
|
|
28
|
-
returns:
|
|
29
|
-
"""
|
|
30
|
-
start = time.time()
|
|
31
|
-
try:
|
|
32
|
-
exe = get_default_browser_executable()
|
|
33
|
-
if not exe:
|
|
34
|
-
raise RuntimeError('Unable to find browser binary')
|
|
35
|
-
log.debug(f'Opening {url} with {exe}')
|
|
36
|
-
|
|
37
|
-
cmd = f'"{exe}" --app="{url}"'
|
|
38
|
-
subprocess.run(cmd, check=True, shell=True)
|
|
39
|
-
|
|
40
|
-
if time.time() - start < 2:
|
|
41
|
-
log.debug(f'Unable to hook into closure of UI, listening for flask shutdown')
|
|
42
|
-
return False
|
|
43
|
-
return True
|
|
44
|
-
|
|
45
|
-
except Exception as e:
|
|
46
|
-
log.warning('Failed to open webpage as app, falling back to browser tab')
|
|
47
|
-
log.debug(f'As app error: {e}')
|
|
48
|
-
try:
|
|
49
|
-
success = webbrowser.open(url)
|
|
50
|
-
log.debug(f'Opened {url} in browser tab: {success}')
|
|
51
|
-
if not success:
|
|
52
|
-
raise RuntimeError('Unknown error while opening browser tab')
|
|
53
|
-
except Exception as e:
|
|
54
|
-
log.warning(f'Exhausted all options to open browser, you need to open manually')
|
|
55
|
-
log.debug(f'As tab error: {e}')
|
|
56
|
-
log.info(f'LANScape UI is running on {url}')
|
|
57
|
-
return False
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def get_default_browser_executable() -> Optional[str]:
|
|
61
|
-
if sys.platform.startswith("win"):
|
|
62
|
-
try:
|
|
63
|
-
import winreg
|
|
64
|
-
# On Windows the HKEY_CLASSES_ROOT\http\shell\open\command key
|
|
65
|
-
# holds the command for opening HTTP URLs.
|
|
66
|
-
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r"http\shell\open\command") as key:
|
|
67
|
-
cmd, _ = winreg.QueryValueEx(key, None)
|
|
68
|
-
except Exception:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
# cmd usually looks like: '"C:\\Program Files\\Foo\\foo.exe" %1'
|
|
72
|
-
m = re.match(r'\"?(.+?\.exe)\"?', cmd)
|
|
73
|
-
return m.group(1) if m else None
|
|
74
|
-
|
|
75
|
-
elif sys.platform.startswith("linux"):
|
|
76
|
-
# First, find the .desktop file name
|
|
77
|
-
desktop_file = None
|
|
78
|
-
try:
|
|
79
|
-
# Try xdg-mime
|
|
80
|
-
p = subprocess.run(
|
|
81
|
-
["xdg-mime", "query", "default", "x-scheme-handler/http"],
|
|
82
|
-
capture_output=True, text=True,
|
|
83
|
-
check=True
|
|
84
|
-
)
|
|
85
|
-
desktop_file = p.stdout.strip()
|
|
86
|
-
except subprocess.CalledProcessError:
|
|
87
|
-
pass
|
|
88
|
-
|
|
89
|
-
if not desktop_file:
|
|
90
|
-
# Fallback to xdg-settings
|
|
91
|
-
try:
|
|
92
|
-
p = subprocess.run(
|
|
93
|
-
["xdg-settings", "get", "default-web-browser"],
|
|
94
|
-
capture_output=True, text=True,
|
|
95
|
-
check=True
|
|
96
|
-
)
|
|
97
|
-
desktop_file = p.stdout.strip()
|
|
98
|
-
except subprocess.CalledProcessError:
|
|
99
|
-
pass
|
|
100
|
-
|
|
101
|
-
# Final fallback: BROWSER environment variable
|
|
102
|
-
if not desktop_file:
|
|
103
|
-
return os.environ.get("BROWSER")
|
|
104
|
-
|
|
105
|
-
# Look for that .desktop file in standard locations
|
|
106
|
-
search_paths = [
|
|
107
|
-
os.path.expanduser("~/.local/share/applications"),
|
|
108
|
-
"/usr/local/share/applications",
|
|
109
|
-
"/usr/share/applications",
|
|
110
|
-
]
|
|
111
|
-
for path in search_paths:
|
|
112
|
-
full_path = os.path.join(path, desktop_file)
|
|
113
|
-
if os.path.isfile(full_path):
|
|
114
|
-
with open(full_path, encoding="utf-8", errors="ignore") as f:
|
|
115
|
-
for line in f:
|
|
116
|
-
if line.startswith("Exec="):
|
|
117
|
-
exec_cmd = line[len("Exec="):].strip()
|
|
118
|
-
# strip arguments like “%u”, “--flag”, etc.
|
|
119
|
-
exec_cmd = exec_cmd.split()[0]
|
|
120
|
-
exec_cmd = exec_cmd.split("%")[0]
|
|
121
|
-
return exec_cmd
|
|
122
|
-
return None
|
|
123
|
-
|
|
124
|
-
elif sys.platform.startswith("darwin"):
|
|
125
|
-
# macOS: try to find Chrome first for app mode support, fallback to default
|
|
126
|
-
try:
|
|
127
|
-
p = subprocess.run(
|
|
128
|
-
["mdfind", "kMDItemCFBundleIdentifier == 'com.google.Chrome'"],
|
|
129
|
-
capture_output=True, text=True, check=True
|
|
130
|
-
)
|
|
131
|
-
chrome_paths = p.stdout.strip().split('\n')
|
|
132
|
-
if chrome_paths and chrome_paths[0]:
|
|
133
|
-
return f"{chrome_paths[0]}/Contents/MacOS/Google Chrome"
|
|
134
|
-
except subprocess.CalledProcessError:
|
|
135
|
-
pass
|
|
136
|
-
|
|
137
|
-
# Fallback to system default
|
|
138
|
-
return "/usr/bin/open"
|
|
139
|
-
|
|
140
|
-
else:
|
|
141
|
-
raise NotImplementedError(f"Unsupported platform: {sys.platform!r}")
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Only used to import csv data - not during runtime
|
|
2
|
-
|
|
3
|
-
import csv, json
|
|
4
|
-
|
|
5
|
-
def main():
|
|
6
|
-
ans = {}
|
|
7
|
-
with open('mac-vendors-export.csv', 'r', encoding='utf-8') as f:
|
|
8
|
-
data = csv.reader(f)
|
|
9
|
-
services = csv_to_dict(data)
|
|
10
|
-
for service in services:
|
|
11
|
-
if service['Vendor Name'] and service['Mac Prefix']:
|
|
12
|
-
try:
|
|
13
|
-
ans[service['Mac Prefix']] = service['Vendor Name']
|
|
14
|
-
except:
|
|
15
|
-
pass
|
|
16
|
-
with open('mac_db.json', 'w') as f:
|
|
17
|
-
json.dump(ans, f, indent=2)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def csv_to_dict(data):
|
|
21
|
-
"""
|
|
22
|
-
Convert a CSV file to a dictionary.
|
|
23
|
-
"""
|
|
24
|
-
header = next(data)
|
|
25
|
-
return [dict(zip(header, row)) for row in data]
|
|
26
|
-
|
|
27
|
-
main()
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Only used to import csv data - not during runtime
|
|
2
|
-
|
|
3
|
-
import csv, json
|
|
4
|
-
|
|
5
|
-
def main():
|
|
6
|
-
ans = {}
|
|
7
|
-
with open('service-names-port-numbers.csv', 'r') as f:
|
|
8
|
-
data = csv.reader(f)
|
|
9
|
-
services = csv_to_dict(data)
|
|
10
|
-
for service in services:
|
|
11
|
-
if service['Service Name'] and service['Port Number']:
|
|
12
|
-
try:
|
|
13
|
-
ans[service['Port Number']] = service['Service Name']
|
|
14
|
-
except:
|
|
15
|
-
pass
|
|
16
|
-
with open('valid_ports.json', 'w') as f:
|
|
17
|
-
json.dump(ans, f, indent=2)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def csv_to_dict(data):
|
|
21
|
-
"""
|
|
22
|
-
Convert a CSV file to a dictionary.
|
|
23
|
-
"""
|
|
24
|
-
header = next(data)
|
|
25
|
-
return [dict(zip(header, row)) for row in data]
|
|
26
|
-
|
|
27
|
-
main()
|
lanscape/tests/__init__.py
DELETED
lanscape/tests/_helpers.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from ..libraries.ip_parser import get_address_count
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def right_size_subnet(subnet: str):
|
|
6
|
-
"""
|
|
7
|
-
Used to improve speed of test time
|
|
8
|
-
"""
|
|
9
|
-
if get_address_count(subnet) > 500:
|
|
10
|
-
parts = subnet.split('/')
|
|
11
|
-
ip = parts[0]
|
|
12
|
-
mask = int(parts[1])
|
|
13
|
-
mask += 1
|
|
14
|
-
return right_size_subnet(f"{ip}/{mask}")
|
|
15
|
-
return subnet
|