ptmanager 0.0.1__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.
ptmanager/__init__.py ADDED
File without changes
ptmanager/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
File without changes
@@ -0,0 +1,95 @@
1
+ from ptlibs import ptprinthelper
2
+ import json
3
+ import os
4
+ import shutil
5
+
6
+ class Config:
7
+ NAME = "config.json"
8
+ PROJECTS_KEY = "projects"
9
+ TEMP = "temp"
10
+ SATID_KEY = "satid"
11
+ PID_KEY = "pid"
12
+
13
+ def __init__(self, config_path: str) -> None:
14
+ self._config_path = config_path
15
+ self._config: dict[list] = None
16
+ try:
17
+ self.load()
18
+ except (FileNotFoundError, json.JSONDecodeError):
19
+ ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Config file not found, creating new ..\n", "ERROR"))
20
+ self.make()
21
+
22
+
23
+ def load(self) -> dict[list]:
24
+ with open(self._config_path + self.NAME) as f:
25
+ self._config = json.load(f)
26
+
27
+
28
+ def make(self) -> dict[list]:
29
+ self.assure_config_path()
30
+ with open(self._config_path + self.NAME, "w+") as f:
31
+ data = {self.SATID_KEY: None, self.PROJECTS_KEY: []}
32
+ f.write(json.dumps(data, indent=4, sort_keys=True))
33
+ self._config = json.loads(json.dumps(data))
34
+
35
+
36
+ def assure_config_path(self) -> None:
37
+ os.makedirs(self._config_path, exist_ok=True)
38
+
39
+
40
+ def delete(self) -> None:
41
+ os.remove(self._config_path + self.NAME)
42
+
43
+ def delete_projects(self) -> None:
44
+ shutil.rmtree(os.path.join(self._config_path, self.PROJECTS_KEY))
45
+
46
+
47
+ def save(self) -> None:
48
+ with open(self._config_path + self.NAME, "w") as f:
49
+ json.dump(self._config, f, indent=4)
50
+
51
+
52
+ def get_path(self) -> str:
53
+ return self._config_path
54
+
55
+ def get_temp_path(self) -> str:
56
+ temp_path = self._config_path + self.TEMP + "/"
57
+ os.makedirs(temp_path, exist_ok=True)
58
+ return temp_path
59
+
60
+
61
+ def print(self) -> None:
62
+ print("-"*40, json.dumps(self._config, indent=4), "-"*40, sep="\n")
63
+
64
+
65
+ def get_projects(self) -> list:
66
+ return self._config[self.PROJECTS_KEY]
67
+
68
+
69
+ def get_satid(self) -> str:
70
+ return self._config[self.SATID_KEY]
71
+
72
+
73
+ def set_satid(self, UID) -> None:
74
+ self._config[self.SATID_KEY] = UID
75
+
76
+
77
+ def add_project(self, project: dict[str]) -> None:
78
+ self._config[self.PROJECTS_KEY].append(project)
79
+
80
+
81
+ def get_pid(self, project_id):
82
+ return self._config[self.PROJECTS_KEY][project_id][self.PID_KEY]
83
+
84
+
85
+ def set_project_pid(self, project_id: int, pid: int) -> None:
86
+ self._config[self.PROJECTS_KEY][project_id][self.PID_KEY] = pid
87
+
88
+
89
+ def remove_project(self, project_id: int) -> None:
90
+ shutil.rmtree(os.path.join(self._config_path, self.PROJECTS_KEY, self.get_project(project_id).get("AS-ID")))
91
+ self._config[self.PROJECTS_KEY].pop(project_id)
92
+
93
+
94
+ def get_project(self, project_id: int):
95
+ return self._config[self.PROJECTS_KEY][project_id]
@@ -0,0 +1,25 @@
1
+ import os
2
+ import signal
3
+
4
+
5
+ class Process:
6
+ def __init__(self, PID: int) -> None:
7
+ self.PID = PID
8
+
9
+ def is_running(self) -> bool:
10
+ "Check if process is running"
11
+ if self.PID is None:
12
+ return False
13
+ try:
14
+ os.kill(self.PID, 0) # Does not terminate, just checks if running
15
+ return True
16
+ except ProcessLookupError:
17
+ return False
18
+
19
+ def kill(self) -> bool:
20
+ "Tries to kill process with PID"
21
+ try:
22
+ os.kill(self.PID, signal.SIGTERM) # Tries to kill process
23
+ return True
24
+ except ProcessLookupError:
25
+ return False
@@ -0,0 +1,338 @@
1
+ import argparse
2
+ import json; from json.decoder import JSONDecodeError
3
+ import os
4
+ import subprocess
5
+ import sys; sys.path.extend([__file__.rsplit("/", 1)[0], os.path.join(__file__.rsplit("/", 1)[0], "modules")])
6
+ import threading
7
+ import time
8
+ import pathlib
9
+
10
+ import requests
11
+
12
+ from process import Process
13
+ from config import Config
14
+
15
+ class ProcessManager:
16
+
17
+ def __init__(self, args):
18
+ self.config: Config = Config(f"{str(pathlib.Path.home())}/.ptmanager/")
19
+ self.satid: str = self.config.get_satid()
20
+ self.target: str = args.target
21
+ self.API_PATH: str = "api/v1/sat/"
22
+
23
+ self.AS_ID: str = args.project_id
24
+ self.project_dir: str = os.path.join(self.config.get_path(), "projects", self.AS_ID)
25
+ self.project_tasks_file: str = os.path.join(self.project_dir, "tasks.json")
26
+
27
+ self.no_ssl_verify = args.no_ssl_verify
28
+ self.proxies: dict = {"http": args.proxy, "https": args.proxy}
29
+ self.free_threads = [i for i in range(args.threads)]
30
+ self.threads_list = ["" for _ in range(args.threads)]
31
+ self.lock = threading.Lock()
32
+
33
+ if not args.target or not args.auth or not args.sid:
34
+ self.ptjsonlib.end_error(f"Target, auth and sid are required", self.use_json)
35
+
36
+ if not os.path.isdir(self.project_dir):
37
+ os.makedirs(self.project_dir)
38
+
39
+
40
+ def run(self, args):
41
+ self.process_front(args.target, args.auth)
42
+
43
+
44
+ def process_front(self, target, auth) -> None:
45
+ while True:
46
+ while not self.free_threads:
47
+ time.sleep(8)
48
+
49
+ self.send_results_to_server(target)
50
+
51
+ task = self.get_task_from_as(target, auth)
52
+ if not task:
53
+ time.sleep(10)
54
+ continue
55
+
56
+ elif task["action"] == "new_task":
57
+ thread_no = self.free_threads.pop()
58
+ self.threads_list[thread_no] = threading.Thread(target=self.process_newtask, name=task["guid"], args=(task, thread_no), daemon=False)
59
+ self.threads_list[thread_no].start()
60
+ elif task["action"] == "status":
61
+ self.status_task(task)
62
+ elif task["action"] == "status-all":
63
+ self.status_all_tasks()
64
+ elif task["action"] == "kill-task":
65
+ self.kill_task(task)
66
+ elif task["action"] == "kill-all":
67
+ self.kill_all_tasks()
68
+ elif task["action"] == "null":
69
+ pass
70
+
71
+
72
+ def send_results_to_server(self, target) -> None:
73
+ self.lock.acquire()
74
+ with self.touchopen(self.project_tasks_file, "r+") as tasks_file:
75
+ try:
76
+ tasks_list: list = json.load(tasks_file)
77
+ except JSONDecodeError:
78
+ tasks_list = []
79
+
80
+
81
+ for task_idx, task in enumerate(tasks_list):
82
+ if task["status"] == "running":
83
+ continue
84
+ task_result_file = os.path.join(self.project_dir, task["guid"])
85
+ if os.path.isfile(task_result_file):
86
+ try:
87
+ with open(task_result_file, "r") as file:
88
+ task_result = json.load(file)
89
+ except (JSONDecodeError, Exception) as e:
90
+ #TODO pokud existuje odkaz na task v tasks.json, ale neexistuje soubor GUID s taskem, nebo je tento soubor vadný, pak se tento záznam nikdy neodstraní z tasks.json
91
+ print("Chyba pri nacitani vysledku automatu ze souboru -", e)
92
+ task_result = {}
93
+ self.lock.release()
94
+ return
95
+ else:
96
+ task_result = None
97
+
98
+ if task_result:
99
+ task_result["guid"] = task["guid"]
100
+ task_result["satid"] = self.satid
101
+ task_result["results"] = json.dumps(task_result["results"])
102
+ response = self.send_to_api(end_point="result", data=(task_result))
103
+ if response.status_code == 200:
104
+ # Remove automat result as it's already been posted to AS.
105
+ try:
106
+ os.remove(task_result_file)
107
+ except OSError as e:
108
+ pass
109
+
110
+ # TODO: Popnuto z tasks_listu, ted je potreba upraveny (popnuty) tasks_list zaktualizovat v souboru
111
+ # TODO: Tak, že otevřu znovu <project_tasks_file> a nahradím jeho obsah popnutym tasks_listem.
112
+ tasks_list.pop(task_idx)
113
+
114
+ # Otevře soubor pro zápis - nahradí seznam aktualizovaným tasks_listem.
115
+ with self.touchopen(self.project_tasks_file, "w") as tasks_file:
116
+ json.dumps(tasks_list, indent=4)
117
+
118
+ self.lock.release()
119
+
120
+
121
+ def send_to_api(self, end_point, data) -> requests.Response:
122
+ target = self.target + self.API_PATH + end_point
123
+ response = requests.post(target, data=json.dumps(data), proxies=self.proxies, verify=self.no_ssl_verify, headers={"Content-Type": "application/json"})
124
+ #if response.status_code != 200:
125
+ #print(f"Response status code is not 200, but {response.status_code}")
126
+ return response
127
+
128
+
129
+ def status_task(self, task) -> None:
130
+ """Retrieve status of <task>, repairs tasks.json if task is not running"""
131
+ self.lock.acquire()
132
+ with self.touchopen(self.project_tasks_file, "r+") as tasks_list_file:
133
+ tasks_list = json.loads(tasks_list_file.read())
134
+ for index, dictionary in enumerate(tasks_list):
135
+ if dictionary["guid"] == task["guid"]:
136
+ if not Process(tasks_list[index]["pid"]).is_running():
137
+ tasks_list[index]["status"] = "error"
138
+ tasks_list[index]["pid"] = None
139
+ tasks_list_file.seek(0)
140
+ tasks_list_file.truncate(0)
141
+ tasks_list_file.write(json.dumps(tasks_list, indent=4))
142
+ self.lock.release()
143
+
144
+
145
+ def status_all_tasks(self) -> None:
146
+ """Repairs all tasks."""
147
+ self.lock.acquire()
148
+ with self.touchopen(self.project_tasks_file, "r+") as tasks_list_file:
149
+ try:
150
+ tasks_list = json.loads(tasks_list_file.read())
151
+ for task in tasks_list:
152
+ if not Process(task["pid"]).is_running():
153
+ task["status"] = "error"
154
+ task["pid"] = None
155
+ tasks_list_file.seek(0)
156
+ tasks_list_file.truncate(0)
157
+ tasks_list_file.write(json.dumps(tasks_list, indent=4))
158
+ except JSONDecodeError:
159
+ pass
160
+ finally:
161
+ self.lock.release()
162
+
163
+
164
+ def kill_all_tasks(self) -> None:
165
+ """Kills all tasks."""
166
+
167
+ for t in self.threads_list:
168
+ if isinstance(t, threading.Thread):
169
+ t.join()
170
+
171
+ for file in os.listdir(self.project_dir):
172
+ if file != "tasks.json":
173
+ os.remove(os.path.join(self.project_dir, file))
174
+
175
+ # TODO - KillAllThreads - Pockat nez se thready dokonci, nebo chladnokrevne zavrazdit vsechno co je thread?
176
+ self.lock.acquire()
177
+ with self.touchopen(self.project_tasks_file, "r+") as tasks_list_file:
178
+ try:
179
+ tasks_list = json.loads(tasks_list_file.read())
180
+ for task in tasks_list:
181
+ if task["pid"]:
182
+ Process(task["pid"]).kill()
183
+ task["status"] = "killed"
184
+ task["pid"] = None
185
+ tasks_list_file.seek(0)
186
+ tasks_list_file.truncate(0)
187
+ tasks_list_file.write(json.dumps(tasks_list, indent=4))
188
+ except JSONDecodeError:
189
+ pass
190
+ finally:
191
+ self.lock.release()
192
+
193
+
194
+ def kill_task(self, task) -> None:
195
+ """Kills task with supplied guid."""
196
+ for t in self.threads_list:
197
+ if isinstance(t, threading.Thread) and t.name == task["guid"]:
198
+ t.join()
199
+ try:
200
+ os.remove(os.path.join(self.project_dir, task["guid"]))
201
+ except OSError:
202
+ # File Not Found
203
+ pass
204
+
205
+ self.lock.acquire()
206
+ with self.touchopen(self.project_tasks_file, "r+") as tasks_list_file:
207
+ try:
208
+ tasks_list = json.loads(tasks_list_file.read())
209
+ for task_in_list in tasks_list:
210
+ if task_in_list["guid"] == task["guid"]:
211
+ if task_in_list["pid"]:
212
+ Process(task_in_list["pid"]).kill()
213
+ task_in_list["status"] = "killed"
214
+ task_in_list["pid"] = None
215
+ tasks_list_file.seek(0)
216
+ tasks_list_file.truncate(0)
217
+ tasks_list_file.write(json.dumps(tasks_list, indent=4))
218
+ except JSONDecodeError:
219
+ pass
220
+ finally:
221
+ self.lock.release()
222
+
223
+
224
+ def touchopen(self, filename, mode):
225
+ open(filename, "a").close() # "touch" file
226
+ return open(filename, mode)
227
+
228
+ def process_newtask(self, task, thread_no) -> None:
229
+ """Process newtask with available thread"""
230
+ result_filename = self.config.get_temp_path() + task["guid"]
231
+ with open(result_filename, "w+") as result_file:
232
+ result_file.truncate(0) # Delete file content, if it exist
233
+ tool_subprocess = subprocess.Popen(task["task"].split(), stdout=result_file, text=True)
234
+ thread_variable = {"guid": task["guid"], "pid": tool_subprocess.pid, "timeStamp": time.time(), "status": "running"}
235
+
236
+ self.lock.acquire()
237
+ # Read tasks_list_file
238
+ with self.touchopen(self.project_tasks_file, "r") as tasks_list_file:
239
+ try:
240
+ tasks_list = json.load(tasks_list_file)
241
+ except JSONDecodeError:
242
+ tasks_list = []
243
+
244
+ # Update tasks_list in memory
245
+ tasks_list.append(thread_variable)
246
+
247
+ # Replace content with updated one
248
+ with self.touchopen(self.project_tasks_file, "w") as tasks_list_file:
249
+ tasks_list_file.write(json.dumps(tasks_list, indent=4))
250
+
251
+ self.lock.release()
252
+
253
+ tool_subprocess.wait()
254
+
255
+ thread_variable.update({"status": "finished", "pid": None})
256
+
257
+ with open(result_filename, "r") as result_file:
258
+ tool_result = result_file.read()
259
+
260
+ os.remove(result_filename)
261
+
262
+
263
+
264
+ self.lock.acquire()
265
+ # Read tasks_list_file
266
+ with self.touchopen(self.project_tasks_file, "r") as tasks_list_file:
267
+ tasks_list = json.load(tasks_list_file)
268
+ self.lock.release()
269
+
270
+ for task_idx, task_dict in enumerate(tasks_list):
271
+ if task_dict["guid"] == thread_variable["guid"]:
272
+ tasks_list[task_idx] = thread_variable
273
+
274
+ self.lock.acquire()
275
+ # Replace content with updated one
276
+ with self.touchopen(self.project_tasks_file, "w") as tasks_list_file:
277
+ tasks_list_file.write(json.dumps(tasks_list, indent=4))
278
+ self.lock.release()
279
+
280
+
281
+ self.lock.acquire()
282
+ with open(os.path.join(self.project_dir, task["guid"]), "w") as task_result_file:
283
+ task_result_file.write(tool_result)
284
+
285
+ self.lock.release()
286
+ self.free_threads.append(thread_no)
287
+
288
+
289
+ def get_task_from_as(self, target=None, auth=None) -> dict:
290
+ """Retrieve task from AS"""
291
+ try:
292
+ response = self.send_to_api(end_point="tasks", data={"satid": self.satid}).json()
293
+ except requests.RequestException as e:
294
+ print(f"Chyba pri ziskavani tasku z AS - {e}")
295
+ return
296
+
297
+ try:
298
+ guid = response["data"]["guid"]
299
+ action = response["data"]["action"]
300
+ command = response["data"]["command"]
301
+ return {"guid": guid, "action": action, "task": command}
302
+ except Exception as e:
303
+ return None
304
+
305
+
306
+
307
+ def _delete_task_from_tasks(self, task) -> None:
308
+ with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), self.AS_ID, "tasks.json"), "r+") as f:
309
+ original_json = json.loads(f.read())
310
+ modified_json = [i for i in original_json if i["guid"] != task["guid"]]
311
+ self.write_to_file_from_start(f, str(modified_json))
312
+
313
+
314
+ def write_to_file_from_start(self, open_file, data: any) -> None:
315
+ open_file.seek(0)
316
+ open_file.truncate(0)
317
+ open_file.write(data)
318
+
319
+
320
+ def parse_args():
321
+ parser = argparse.ArgumentParser(add_help=False)
322
+ parser.add_argument("-T", "--target", type=str)
323
+ parser.add_argument("-a", "--auth", type=str)
324
+ parser.add_argument("-S", "--sid", type=str)
325
+ parser.add_argument("-prj", "--project_id", type=str)
326
+ parser.add_argument("-p", "--proxy", type=str)
327
+ parser.add_argument("--no_ssl_verify", action="store_false")
328
+
329
+ parser.add_argument("-t", "--threads", type=int, default=20)
330
+
331
+ args = parser.parse_args()
332
+ return args
333
+
334
+
335
+ if __name__ == "__main__":
336
+ args = parse_args()
337
+ tool_subprocess = ProcessManager(args)
338
+ tool_subprocess.run(args)
@@ -0,0 +1,195 @@
1
+ import os
2
+ import random
3
+ import signal
4
+ import string
5
+ import subprocess
6
+ import sys; sys.path.extend([__file__.rsplit("/", 1)[0], os.path.join(__file__.rsplit("/", 1)[0], "modules")])
7
+ import urllib
8
+ import uuid
9
+ import json
10
+
11
+ import requests
12
+
13
+ from ptlibs import ptjsonlib, ptprinthelper
14
+
15
+ from config import Config
16
+ from process import Process
17
+
18
+
19
+ class ProjectManager:
20
+ def __init__(self, ptjsonlib: ptjsonlib.PtJsonLib, use_json: bool, proxies: dict, no_ssl_verify: bool, config: Config) -> None:
21
+ self.ptjsonlib = ptjsonlib
22
+ self.use_json = use_json
23
+ self.no_ssl_verify = no_ssl_verify
24
+ self.proxies = {"http": proxies, "https": proxies}
25
+ self.config = config
26
+
27
+ def register_project(self, target: str, auth_token: str) -> None:
28
+ """Registers new project"""
29
+ if not target:
30
+ self.ptjsonlib.end_error("Missing --target parameter", self.use_json)
31
+ if not auth_token:
32
+ self.ptjsonlib.end_error("Missing --auth parameter", self.use_json)
33
+
34
+ target = self.is_url(target)
35
+ if not target:
36
+ self.ptjsonlib.end_error("Target is not a valid URL", self.use_json)
37
+
38
+ registration_url = target + "api/v1/sat/register"
39
+ try:
40
+ response = requests.post(registration_url, proxies=self.proxies, verify=self.no_ssl_verify, data={"token": auth_token, "satid": self.config.get_satid()})
41
+ except requests.RequestException as e:
42
+ self.ptjsonlib.end_error("Server is not responding", self.use_json)
43
+
44
+ if response.status_code == 200:
45
+ try:
46
+ response_json = response.json()
47
+ except json.JSONDecodeError:
48
+ self.ptjsonlib.end_error(f"Could not parse target response as json - {response.text}", self.use_json)
49
+
50
+ if response_json.get("success"):
51
+ print(response_json["data"]['name'])
52
+ print(response_json['message'])
53
+ project_name = response_json['data']['name']
54
+ else:
55
+ self.ptjsonlib.end_error(response_json, self.use_json)
56
+ else:
57
+ self.ptjsonlib.end_error(f"[{response.status_code}] {response.text}", self.use_json)
58
+
59
+ AS_ID = ''.join(random.choice(string.ascii_lowercase) for _ in range(8))
60
+ self.config.add_project({"project_name": project_name, "target": target, "auth": auth_token, "pid": None, "AS-ID": AS_ID})
61
+
62
+ def is_url(self, url: int):
63
+ try:
64
+ result = urllib.parse.urlparse(url)
65
+ if result.path:
66
+ while result.path.endswith("/"):
67
+ result = result._replace(path=result.path[:-1])
68
+ result = result._replace(path=result.path + "/")
69
+ return result.geturl()
70
+ except ValueError:
71
+ return False
72
+
73
+ def start_project(self, project_id: int) -> None:
74
+ """Starts specified project."""
75
+ print("Starting ....")
76
+ project = self.config.get_project(project_id)
77
+ if project["pid"]:
78
+ if not Process(project["pid"]).is_running():
79
+ self.config.set_project_pid(project_id, None)
80
+ else:
81
+ self.ptjsonlib.end_error(f"Project is already running with PID {project['pid']}", self.use_json)
82
+
83
+ response = {"sessionid": "sessionid"}
84
+ try:
85
+ subprocess_args = [sys.executable, os.path.realpath(os.path.join(__file__.rsplit("/", 1)[0], "process_manager.py")), "--target", project["target"], "--auth", project["auth"], "--sid", response["sessionid"], "--project_id", project["AS-ID"]]
86
+
87
+ if self.proxies.get("http"):
88
+ subprocess_args += ["--proxy", self.proxies.get("http")]
89
+ if not self.no_ssl_verify:
90
+ subprocess_args += ["--no_ssl_verify"]
91
+
92
+ process = subprocess.Popen(subprocess_args)
93
+
94
+ except Exception as e:
95
+ self.ptjsonlib.end_error(e, self.use_json)
96
+ self.config.set_project_pid(project_id, process.pid)
97
+ ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Started Project {project_id+1} with PID {process.pid}", "INFO"))
98
+
99
+
100
+ def end_project(self, project_id: int) -> None:
101
+ process_pid = self.config.get_pid(project_id)
102
+ if process_pid:
103
+ try:
104
+ os.kill(process_pid, signal.SIGTERM)
105
+ self.config.set_project_pid(project_id, None)
106
+ ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Killed process with PID {process_pid}", "OK"))
107
+ except ProcessLookupError:
108
+ ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Proccess [{process_pid}] is not running ", "ERROR"))
109
+ self.config.set_project_pid(project_id, None)
110
+ except Exception as e:
111
+ self.ptjsonlib.end_error(e, self.use_json)
112
+ else:
113
+ self.ptjsonlib.end_error(f"Project is not running", self.use_json)
114
+
115
+ def reset_project(self, project_id: int) -> None:
116
+ if self.config.get_pid(project_id):
117
+ self.end_project(project_id)
118
+ self.start_project(project_id)
119
+ else:
120
+ self.start_project(project_id)
121
+
122
+ ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Succesfully registered project", "OK"))
123
+
124
+
125
+ def delete_project(self, project_id: int) -> None:
126
+ # TODO: Send request to delete project from AS
127
+ if self.config.get_pid(project_id):
128
+ self.ptjsonlib.end_error(f"Project is running, end project first", self.use_json)
129
+
130
+ project = self.config.get_project(project_id)
131
+ request_path = project["target"] + "api/v1/sat/delete"
132
+
133
+ try:
134
+ response = requests.post(request_path, proxies=self.proxies, verify=self.no_ssl_verify, data={"satid": self.config.get_satid()}) #project["auth"]
135
+ self.config.remove_project(project_id)
136
+ ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Project deleted succesfully", "OK"))
137
+ except requests.RequestException as e:
138
+ ptprinthelper.ptprint(f"Server is not responding", "ERROR")
139
+ if self._yes_no_prompt("Cannot delete project from AS. Delete TS from server manualy.\nProject will be deleted locally only. Are you sure?", bullet_type=None):
140
+ self.config.remove_project(project_id)
141
+ ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Local project deleted succesfully", "OK"))
142
+
143
+
144
+ def list_projects(self) -> None:
145
+ print(f"{ptprinthelper.get_colored_text('ID', 'TITLE')}{' '*4}{ptprinthelper.get_colored_text('Project Name', 'TITLE')}{' '*20}{ptprinthelper.get_colored_text('PID', 'TITLE')}{' '*7}{ptprinthelper.get_colored_text('Status', 'TITLE')}{' '*9}")
146
+ print(f"{'-'*6}{'-'*32}{'-'*10}{'-'*15}")
147
+ if not self.config.get_projects():
148
+ print(" ")
149
+ self.ptjsonlib.end_error("No projects found, register a project first", self.use_json)
150
+
151
+ for index, project in enumerate(self.config.get_projects(), 1):
152
+ if project["pid"]:
153
+ if not Process(project["pid"]).is_running():
154
+ self.config.set_project_pid(index - 1, None)
155
+ project["pid"] = None
156
+ pid = project["pid"]
157
+ if pid:
158
+ status = "running"
159
+ if not pid:
160
+ status = "-"
161
+ pid = "-"
162
+
163
+ print(f"{index}{' '*(6-len(str(index)))}", end="")
164
+ print(f"{project['project_name']}{' '*(32-len(project['project_name']))}", end="")
165
+ print(f"{str(pid)}{' '*(10-len(str(pid)))}", end="")
166
+ print(f"{status}{' '*(15-len(status))}", end="")
167
+ print("")
168
+
169
+ def register_uid(self) -> None:
170
+ UID = str(uuid.uuid1())
171
+ if self.config.get_satid():
172
+ if self._yes_no_prompt("UID already exists, are you sure you want to create new? All your projects will be deleted!"):
173
+ self.config.delete_projects()
174
+ self.config.delete()
175
+ self.config.make()
176
+ self.config.set_satid(UID)
177
+ else:
178
+ exit()
179
+ else:
180
+ self.config.set_satid(UID)
181
+ print("[*] New UID generated")
182
+ self.config.print()
183
+
184
+
185
+ def _yes_no_prompt(self, msg, bullet_type="OK") -> bool:
186
+ ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"{msg}", bullet_type), end=" ")
187
+ reply = input(f"y/N: ").upper().strip()
188
+ if reply == "Y":
189
+ return True
190
+ elif reply == "N" or reply == "":
191
+ return False
192
+ else:
193
+ return self._yes_no_prompt(msg)
194
+
195
+ #TODO: Implementovat přepínač pro insecure SSL a upravit všechny requesty, aby s tímto přepínačem spolupracovaly