WIP, to be cleaned and merged
This commit is contained in:
		
							parent
							
								
									314adbeb1d
								
							
						
					
					
						commit
						d1e3152a83
					
				
					 30 changed files with 1498 additions and 248 deletions
				
			
		
							
								
								
									
										39
									
								
								benchmark.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								benchmark.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| from datetime import datetime | ||||
| import time | ||||
| import json | ||||
| import statistics as stats | ||||
| from pprint import pprint | ||||
| import os | ||||
| import _ed_lrr | ||||
| 
 | ||||
| NUM_LOOPS=5 | ||||
| 
 | ||||
| results={} | ||||
| 
 | ||||
| def time_run(w,s,file="benchmark.json", loops=None): | ||||
|     global results,NUM_LOOPS | ||||
|     if loops is None: | ||||
|         loops=NUM_LOOPS | ||||
|     for _ in range(loops): | ||||
|         t_start = time.time() | ||||
|         ret = _ed_lrr.route(s,48,None,'bfs',True,False,False,False,0.0,None,r"D:\devel\rust\ED_LRR\stars.csv",w,lambda *args,**kwargs: None) | ||||
|         t_end = time.time() | ||||
|         results.setdefault(w,[]).append({'ret':len(ret),'time':t_end - t_start}) | ||||
|     with open(file, "w") as of: | ||||
|         json.dump(results, of,indent=2) | ||||
| 
 | ||||
| t_start = datetime.today() | ||||
| 
 | ||||
| for w in [1,2,4,7,8,0]: | ||||
|     time_run(w,['Ix','72']) | ||||
| 
 | ||||
| print("Benchmark took:", datetime.today() - t_start) | ||||
| 
 | ||||
| 
 | ||||
| for workers,results in results.items(): | ||||
|     t_total=sum([res['time'] for res in results])/len(results) | ||||
|     avg_len=sum([res['ret'] for res in results])/len(results) | ||||
|     times.append([int(workers),timedelta(seconds=t_total),avg_len]) | ||||
| 
 | ||||
| for k,v,l in sorted(times,key=lambda rec:rec[1]): | ||||
|     print(k,v,l) | ||||
							
								
								
									
										17
									
								
								celery_rabbitmq_setup.ps1
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								celery_rabbitmq_setup.ps1
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| #RABBITMQ | ||||
| rabbitmqctl stop_app | ||||
| rabbitmqctl reset | ||||
| rabbitmqctl start_app | ||||
| rabbitmqctl add_user ed_lrr ed_lrr  | ||||
| rabbitmqctl add_vhost ed_lrr | ||||
| rabbitmqctl set_user_tags ed_lrr ed_lrr | ||||
| rabbitmqctl set_permissions -p ed_lrr ed_lrr ".*" ".*" ".*" | ||||
| rabbitmqctl set_permissions guest ".*" ".*" ".*" | ||||
| rabbitmqctl set_permissions -p ed_lrr guest ".*" ".*" ".*" | ||||
| Write-Host RabbitMQ setup done | ||||
| #Celery | ||||
| $env:FORKED_BY_MULTIPROCESSING=1 | ||||
| Write-Host starting Celery | ||||
| celery -A celery_test worker -E -l info | ||||
| 
 | ||||
| #celery -A celery_test flower --presistent --broker=pyamqp://ed_lrr:ed_lrr@localhost/ed_lrr --broker_api=http://ed_lrr:ed_lrr@localhost:15672/api/ | ||||
							
								
								
									
										9
									
								
								celery_test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								celery_test.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| from celery import Celery | ||||
| import _ed_lrr | ||||
| app = Celery('ed_lrr',backend = 'db+sqlite:///ed_lrr_results.sqlite', broker='pyamqp://ed_lrr:ed_lrr@localhost/ed_lrr') | ||||
| 
 | ||||
| @app.task(bind=True) | ||||
| def route(self,hops,jmp_range): | ||||
|     def callback(state): | ||||
|         self.update_state(state="PROGRESS", meta=state) | ||||
|     return _ed_lrr.route(hops,jmp_range,None,'bfs',True,False,False,False,0.0,None,r"C:\Users\Earthnuker\AppData\Local\ED_LRR\data\stars.csv",0,callback) | ||||
							
								
								
									
										18
									
								
								celery_worker.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								celery_worker.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| import time | ||||
| from celery_test import route | ||||
| 
 | ||||
| jobs = [route.delay(["Ix", "Colonia"], 48), route.delay(["Colonia", "Sol"], 48)] | ||||
| while True: | ||||
|     for job in jobs: | ||||
|         if job.ready(): | ||||
|             print([job, job.state, len(job.info)]) | ||||
|         else: | ||||
|             print([job, job.state, job.info]) | ||||
|     print("="*10) | ||||
|     time.sleep(1) | ||||
| 
 | ||||
| # 02c77491-9abd-4a88-ab2c-acdf2981086b | ||||
| # d56b0ca8-067d-45a6-be9b-bb9e74f196cd | ||||
| 
 | ||||
| # celery -A celery_test flower --presistent --broker=pyamqp://ed_lrr:ed_lrr@localhost/ed_lrr --broker_api=http://ed_lrr:ed_lrr@localhost:15672/api/ | ||||
| 
 | ||||
|  | @ -39,7 +39,7 @@ for file in cfg["history.bodies_path"][::-1]: | |||
| @click.pass_context | ||||
| @click.version_option() | ||||
| def main(ctx): | ||||
|     "Elite: Dangerous long range router, command line interface" | ||||
|     "Elite: Dangerous long range router, command line interface." | ||||
|     MP.freeze_support() | ||||
|     if ctx.invoked_subcommand != "config": | ||||
|         os.makedirs(cfg["folders.data_dir"], exist_ok=True) | ||||
|  | @ -49,12 +49,31 @@ def main(ctx): | |||
|     return | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option("--port", "-p", help="Port to bind to", type=int, default=3777) | ||||
| @click.option("--host", "-h", help="Address to bind to", type=str, default="0.0.0.0") | ||||
| @click.option("--debug", "-d", is_flag=True, help="Run using debug server") | ||||
| def web(host, port, debug): | ||||
|     "Run web interface." | ||||
|     from gevent.pywsgi import WSGIServer | ||||
|     from ed_lrr_gui.web import app | ||||
| 
 | ||||
|     with app.test_client() as c: | ||||
|         c.get("/")  # Force before_first_request hook to run | ||||
|     if debug: | ||||
|         app.debug=True | ||||
|         app.run(host=host, port=port, debug=True) | ||||
|         return | ||||
|     print("Listening on {}:{}".format(host, port)) | ||||
|     server = WSGIServer((host, port), app) | ||||
|     server.serve_forever() | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.argument("option", default=None, required=False) | ||||
| @click.argument("value", default=None, required=False) | ||||
| def config(option, value): | ||||
|     """Change configuration | ||||
|      | ||||
|     """Change configuration. | ||||
| 
 | ||||
|     If "key" and "value" are both omitted the current configuration is printed | ||||
|     """ | ||||
|  | @ -98,14 +117,14 @@ def config(option, value): | |||
| 
 | ||||
| @main.command() | ||||
| def explore(): | ||||
|     "Open file manager in data folder" | ||||
|     "Open file manager in data folder." | ||||
|     click.launch(cfg["folders.data_dir"], locate=True) | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option("--debug", help="Debug print", is_flag=True) | ||||
| @click.option("--debug", help="Enable debug output", is_flag=True) | ||||
| def gui(debug): | ||||
|     "Run the ED LRR GUI (default)" | ||||
|     "Run the ED LRR GUI (default)." | ||||
|     import ed_lrr_gui.gui as ED_LRR_GUI | ||||
| 
 | ||||
|     if (not debug) and os.name == "nt": | ||||
|  | @ -133,7 +152,7 @@ def gui(debug): | |||
|     show_default=True, | ||||
| ) | ||||
| def download(url, folder): | ||||
|     "Download EDSM dumps" | ||||
|     "Download EDSM dumps." | ||||
|     os.makedirs(folder, exist_ok=True) | ||||
|     for file_name in ["systemsWithCoordinates.json", "bodies.json"]: | ||||
|         download_url = urljoin(url, file_name) | ||||
|  | @ -156,6 +175,7 @@ def download(url, folder): | |||
|             unit_divisor=1024, | ||||
|             unit_scale=True, | ||||
|             ascii=True, | ||||
|             smoothing=0 | ||||
|         ) as pbar: | ||||
|             with open(download_path, "wb") as of: | ||||
|                 resp = RQ.get( | ||||
|  | @ -173,7 +193,7 @@ def download(url, folder): | |||
|     "-s", | ||||
|     default=systems_path, | ||||
|     metavar="<path>", | ||||
|     help="Path to stars.csv", | ||||
|     help="Path to systemsWithCoordinates.json", | ||||
|     type=click.Path(exists=True, dir_okay=False), | ||||
|     show_default=True, | ||||
| ) | ||||
|  | @ -196,7 +216,7 @@ def download(url, folder): | |||
|     show_default=True, | ||||
| ) | ||||
| def preprocess(systems, bodies, output): | ||||
|     "Preprocess EDSM dumps" | ||||
|     "Preprocess EDSM dumps." | ||||
|     with click.progressbar( | ||||
|         length=100, label="", show_percent=True, item_show_func=lambda v: v, width=50 | ||||
|     ) as pbar: | ||||
|  | @ -289,12 +309,20 @@ def preprocess(systems, bodies, output): | |||
|     "-m", | ||||
|     default=cfg["route.mode"], | ||||
|     help="Search mode", | ||||
|     type=click.Choice(["bfs", "a-star", "greedy"]), | ||||
|     type=click.Choice(["bfs","bfs_old", "a-star", "greedy"]), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--workers", | ||||
|     "-w", | ||||
|     metavar="<int>", | ||||
|     default=1, | ||||
|     help="Number of worker threads (more is not always better)", | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.argument("systems", nargs=-1) | ||||
| def route(**kwargs): | ||||
|     "Compute a route" | ||||
|     "Compute a route." | ||||
|     if len(kwargs["systems"]) < 2: | ||||
|         exit("Need at least two systems to plot a route") | ||||
|     if kwargs["prune"] == (0, 0): | ||||
|  | @ -302,7 +330,7 @@ def route(**kwargs): | |||
| 
 | ||||
|     def to_string(state): | ||||
|         if state: | ||||
|             return "{prc_done:.2f}% [N:{depth} Q:{queue_size} D:{d_rem:.2f} Ly] {system}".format( | ||||
|             return "{prc_done:.2f}% [N:{depth} | Q:{queue_size} | D:{d_rem:.2f} Ly | S: {n_seen} ({prc_seen:.2f}%)] {system}".format( | ||||
|                 **state | ||||
|             ) | ||||
| 
 | ||||
|  | @ -330,6 +358,7 @@ def route(**kwargs): | |||
|         kwargs["factor"], | ||||
|         None, | ||||
|         kwargs["path"], | ||||
|         kwargs["workers"] | ||||
|     ] | ||||
|     with click.progressbar( | ||||
|         length=100, | ||||
|  | @ -359,7 +388,7 @@ def route(**kwargs): | |||
|         pbar.update(0) | ||||
|     for n, jump in enumerate(state.get("return", []), 1): | ||||
|         jump["n"] = n | ||||
|         if jump["body"].index(jump["system"]) == -1: | ||||
|         if jump["body"].find(jump["system"]) == -1: | ||||
|             jump["where"] = "[{body}] in [{system}]".format(**jump) | ||||
|         else: | ||||
|             jump["where"] = "[{body}]".format(**jump) | ||||
|  | @ -397,6 +426,7 @@ def route(**kwargs): | |||
| def precompute(*args, **kwargs): | ||||
|     "Precompute routing graph" | ||||
|     print("PreComp:", args, kwargs) | ||||
|     raise NotImplementedError | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|  |  | |||
|  | @ -55,5 +55,8 @@ cfg.init("folders.data_dir", os.path.join(config_dir, "data"), comment="Data dir | |||
| 
 | ||||
| cfg.init("GUI.theme", "dark", comment="GUI theme to use") | ||||
| 
 | ||||
| cfg.init("web.port",3777,comment="Port to bind to") | ||||
| cfg.init("web.host","0.0.0.0",comment="Address to bind to") | ||||
| cfg.init("web.debug",False,comment="Run using debug server") | ||||
| 
 | ||||
| cfg.sync() | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| # Form implementation generated from reading ui file 'D:\devel\rust\ed_lrr_gui\ed_lrr_gui\gui\ed_lrr.ui' | ||||
| # | ||||
| # Created by: PyQt5 UI code generator 5.13.1 | ||||
| # Created by: PyQt5 UI code generator 5.14.1 | ||||
| # | ||||
| # WARNING! All changes made in this file will be lost! | ||||
| 
 | ||||
|  | @ -15,9 +15,7 @@ class Ui_ED_LRR(object): | |||
|         ED_LRR.setObjectName("ED_LRR") | ||||
|         ED_LRR.setEnabled(True) | ||||
|         ED_LRR.resize(577, 500) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth(ED_LRR.sizePolicy().hasHeightForWidth()) | ||||
|  | @ -28,14 +26,10 @@ class Ui_ED_LRR(object): | |||
|         ED_LRR.setDocumentMode(False) | ||||
|         ED_LRR.setTabShape(QtWidgets.QTabWidget.Rounded) | ||||
|         self.centralwidget = QtWidgets.QWidget(ED_LRR) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.centralwidget.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) | ||||
|         self.centralwidget.setSizePolicy(sizePolicy) | ||||
|         self.centralwidget.setObjectName("centralwidget") | ||||
|         self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) | ||||
|  | @ -55,39 +49,27 @@ class Ui_ED_LRR(object): | |||
|         self.formLayout.setObjectName("formLayout") | ||||
|         self.lbl_bodies_dl = QtWidgets.QLabel(self.tab_download) | ||||
|         self.lbl_bodies_dl.setObjectName("lbl_bodies_dl") | ||||
|         self.formLayout.setWidget( | ||||
|             1, QtWidgets.QFormLayout.LabelRole, self.lbl_bodies_dl | ||||
|         ) | ||||
|         self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_bodies_dl) | ||||
|         self.lbl_systems_dl = QtWidgets.QLabel(self.tab_download) | ||||
|         self.lbl_systems_dl.setObjectName("lbl_systems_dl") | ||||
|         self.formLayout.setWidget( | ||||
|             3, QtWidgets.QFormLayout.LabelRole, self.lbl_systems_dl | ||||
|         ) | ||||
|         self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_systems_dl) | ||||
|         self.inp_bodies_dl = QtWidgets.QComboBox(self.tab_download) | ||||
|         self.inp_bodies_dl.setEditable(True) | ||||
|         self.inp_bodies_dl.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) | ||||
|         self.inp_bodies_dl.setObjectName("inp_bodies_dl") | ||||
|         self.formLayout.setWidget( | ||||
|             1, QtWidgets.QFormLayout.FieldRole, self.inp_bodies_dl | ||||
|         ) | ||||
|         self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.inp_bodies_dl) | ||||
|         self.inp_systems_dl = QtWidgets.QComboBox(self.tab_download) | ||||
|         self.inp_systems_dl.setEditable(True) | ||||
|         self.inp_systems_dl.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) | ||||
|         self.inp_systems_dl.setObjectName("inp_systems_dl") | ||||
|         self.formLayout.setWidget( | ||||
|             3, QtWidgets.QFormLayout.FieldRole, self.inp_systems_dl | ||||
|         ) | ||||
|         self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.inp_systems_dl) | ||||
|         self.gridLayout = QtWidgets.QGridLayout() | ||||
|         self.gridLayout.setObjectName("gridLayout") | ||||
|         self.inp_bodies_dest_dl = QtWidgets.QComboBox(self.tab_download) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.inp_bodies_dest_dl.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         sizePolicy.setHeightForWidth(self.inp_bodies_dest_dl.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_bodies_dest_dl.setSizePolicy(sizePolicy) | ||||
|         self.inp_bodies_dest_dl.setEditable(False) | ||||
|         self.inp_bodies_dest_dl.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) | ||||
|  | @ -103,14 +85,10 @@ class Ui_ED_LRR(object): | |||
|         self.btn_systems_dest_browse_dl.setObjectName("btn_systems_dest_browse_dl") | ||||
|         self.gridLayout_2.addWidget(self.btn_systems_dest_browse_dl, 0, 1, 1, 1) | ||||
|         self.inp_systems_dest_dl = QtWidgets.QComboBox(self.tab_download) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.inp_systems_dest_dl.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         sizePolicy.setHeightForWidth(self.inp_systems_dest_dl.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_systems_dest_dl.setSizePolicy(sizePolicy) | ||||
|         self.inp_systems_dest_dl.setEditable(False) | ||||
|         self.inp_systems_dest_dl.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) | ||||
|  | @ -133,79 +111,57 @@ class Ui_ED_LRR(object): | |||
|         self.formLayout_3.setObjectName("formLayout_3") | ||||
|         self.lbl_bodies_pp = QtWidgets.QLabel(self.tab_preprocess) | ||||
|         self.lbl_bodies_pp.setObjectName("lbl_bodies_pp") | ||||
|         self.formLayout_3.setWidget( | ||||
|             0, QtWidgets.QFormLayout.LabelRole, self.lbl_bodies_pp | ||||
|         ) | ||||
|         self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_bodies_pp) | ||||
|         self.gr_bodies_pp = QtWidgets.QGridLayout() | ||||
|         self.gr_bodies_pp.setObjectName("gr_bodies_pp") | ||||
|         self.btn_bodies_browse_pp = QtWidgets.QPushButton(self.tab_preprocess) | ||||
|         self.btn_bodies_browse_pp.setObjectName("btn_bodies_browse_pp") | ||||
|         self.gr_bodies_pp.addWidget(self.btn_bodies_browse_pp, 0, 1, 1, 1) | ||||
|         self.inp_bodies_pp = QtWidgets.QComboBox(self.tab_preprocess) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.inp_bodies_pp.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         sizePolicy.setHeightForWidth(self.inp_bodies_pp.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_bodies_pp.setSizePolicy(sizePolicy) | ||||
|         self.inp_bodies_pp.setEditable(False) | ||||
|         self.inp_bodies_pp.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) | ||||
|         self.inp_bodies_pp.setObjectName("inp_bodies_pp") | ||||
|         self.gr_bodies_pp.addWidget(self.inp_bodies_pp, 0, 0, 1, 1) | ||||
|         self.formLayout_3.setLayout( | ||||
|             0, QtWidgets.QFormLayout.FieldRole, self.gr_bodies_pp | ||||
|         ) | ||||
|         self.formLayout_3.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.gr_bodies_pp) | ||||
|         self.lbl_systems_pp = QtWidgets.QLabel(self.tab_preprocess) | ||||
|         self.lbl_systems_pp.setObjectName("lbl_systems_pp") | ||||
|         self.formLayout_3.setWidget( | ||||
|             1, QtWidgets.QFormLayout.LabelRole, self.lbl_systems_pp | ||||
|         ) | ||||
|         self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lbl_systems_pp) | ||||
|         self.gr_systems_pp = QtWidgets.QGridLayout() | ||||
|         self.gr_systems_pp.setObjectName("gr_systems_pp") | ||||
|         self.btn_systems_browse_pp = QtWidgets.QPushButton(self.tab_preprocess) | ||||
|         self.btn_systems_browse_pp.setObjectName("btn_systems_browse_pp") | ||||
|         self.gr_systems_pp.addWidget(self.btn_systems_browse_pp, 0, 1, 1, 1) | ||||
|         self.inp_systems_pp = QtWidgets.QComboBox(self.tab_preprocess) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.inp_systems_pp.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         sizePolicy.setHeightForWidth(self.inp_systems_pp.sizePolicy().hasHeightForWidth()) | ||||
|         self.inp_systems_pp.setSizePolicy(sizePolicy) | ||||
|         self.inp_systems_pp.setEditable(False) | ||||
|         self.inp_systems_pp.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) | ||||
|         self.inp_systems_pp.setObjectName("inp_systems_pp") | ||||
|         self.gr_systems_pp.addWidget(self.inp_systems_pp, 0, 0, 1, 1) | ||||
|         self.formLayout_3.setLayout( | ||||
|             1, QtWidgets.QFormLayout.FieldRole, self.gr_systems_pp | ||||
|         ) | ||||
|         self.formLayout_3.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.gr_systems_pp) | ||||
|         self.lbl_out_pp = QtWidgets.QLabel(self.tab_preprocess) | ||||
|         self.lbl_out_pp.setObjectName("lbl_out_pp") | ||||
|         self.formLayout_3.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_out_pp) | ||||
|         self.gr_out_grid_pp = QtWidgets.QGridLayout() | ||||
|         self.gr_out_grid_pp.setObjectName("gr_out_grid_pp") | ||||
|         self.btn_out_browse_pp = QtWidgets.QPushButton(self.tab_preprocess) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.btn_out_browse_pp.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         sizePolicy.setHeightForWidth(self.btn_out_browse_pp.sizePolicy().hasHeightForWidth()) | ||||
|         self.btn_out_browse_pp.setSizePolicy(sizePolicy) | ||||
|         self.btn_out_browse_pp.setObjectName("btn_out_browse_pp") | ||||
|         self.gr_out_grid_pp.addWidget(self.btn_out_browse_pp, 0, 1, 1, 1) | ||||
|         self.inp_out_pp = QtWidgets.QComboBox(self.tab_preprocess) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth(self.inp_out_pp.sizePolicy().hasHeightForWidth()) | ||||
|  | @ -214,14 +170,10 @@ class Ui_ED_LRR(object): | |||
|         self.inp_out_pp.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop) | ||||
|         self.inp_out_pp.setObjectName("inp_out_pp") | ||||
|         self.gr_out_grid_pp.addWidget(self.inp_out_pp, 0, 0, 1, 1) | ||||
|         self.formLayout_3.setLayout( | ||||
|             2, QtWidgets.QFormLayout.FieldRole, self.gr_out_grid_pp | ||||
|         ) | ||||
|         self.formLayout_3.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.gr_out_grid_pp) | ||||
|         self.btn_preprocess = QtWidgets.QPushButton(self.tab_preprocess) | ||||
|         self.btn_preprocess.setObjectName("btn_preprocess") | ||||
|         self.formLayout_3.setWidget( | ||||
|             3, QtWidgets.QFormLayout.LabelRole, self.btn_preprocess | ||||
|         ) | ||||
|         self.formLayout_3.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.btn_preprocess) | ||||
|         self.tabs.addTab(self.tab_preprocess, "") | ||||
|         self.tab_route = QtWidgets.QWidget() | ||||
|         self.tab_route.setObjectName("tab_route") | ||||
|  | @ -229,18 +181,14 @@ class Ui_ED_LRR(object): | |||
|         self.formLayout_2.setObjectName("formLayout_2") | ||||
|         self.lbl_sys_lst = QtWidgets.QLabel(self.tab_route) | ||||
|         self.lbl_sys_lst.setObjectName("lbl_sys_lst") | ||||
|         self.formLayout_2.setWidget( | ||||
|             0, QtWidgets.QFormLayout.LabelRole, self.lbl_sys_lst | ||||
|         ) | ||||
|         self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lbl_sys_lst) | ||||
|         self.gr_sys = QtWidgets.QGridLayout() | ||||
|         self.gr_sys.setObjectName("gr_sys") | ||||
|         self.btn_sys_lst_browse = QtWidgets.QPushButton(self.tab_route) | ||||
|         self.btn_sys_lst_browse.setObjectName("btn_sys_lst_browse") | ||||
|         self.gr_sys.addWidget(self.btn_sys_lst_browse, 0, 1, 1, 1) | ||||
|         self.inp_sys_lst = QtWidgets.QComboBox(self.tab_route) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth(self.inp_sys_lst.sizePolicy().hasHeightForWidth()) | ||||
|  | @ -273,44 +221,32 @@ class Ui_ED_LRR(object): | |||
|         self.formLayout_2.setLayout(3, QtWidgets.QFormLayout.FieldRole, self.gr_mode) | ||||
|         self.chk_permute = QtWidgets.QCheckBox(self.tab_route) | ||||
|         self.chk_permute.setObjectName("chk_permute") | ||||
|         self.formLayout_2.setWidget( | ||||
|             4, QtWidgets.QFormLayout.LabelRole, self.chk_permute | ||||
|         ) | ||||
|         self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.chk_permute) | ||||
|         self.gridLayout_4 = QtWidgets.QGridLayout() | ||||
|         self.gridLayout_4.setObjectName("gridLayout_4") | ||||
|         self.chk_permute_keep_last = QtWidgets.QCheckBox(self.tab_route) | ||||
|         self.chk_permute_keep_last.setObjectName("chk_permute_keep_last") | ||||
|         self.gridLayout_4.addWidget(self.chk_permute_keep_last, 0, 3, 1, 1) | ||||
|         self.chk_permute_keep_first = QtWidgets.QCheckBox(self.tab_route) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth( | ||||
|             self.chk_permute_keep_first.sizePolicy().hasHeightForWidth() | ||||
|         ) | ||||
|         sizePolicy.setHeightForWidth(self.chk_permute_keep_first.sizePolicy().hasHeightForWidth()) | ||||
|         self.chk_permute_keep_first.setSizePolicy(sizePolicy) | ||||
|         self.chk_permute_keep_first.setTristate(False) | ||||
|         self.chk_permute_keep_first.setObjectName("chk_permute_keep_first") | ||||
|         self.gridLayout_4.addWidget(self.chk_permute_keep_first, 0, 2, 1, 1) | ||||
|         self.lbl_keep = QtWidgets.QLabel(self.tab_route) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth(self.lbl_keep.sizePolicy().hasHeightForWidth()) | ||||
|         self.lbl_keep.setSizePolicy(sizePolicy) | ||||
|         self.lbl_keep.setObjectName("lbl_keep") | ||||
|         self.gridLayout_4.addWidget(self.lbl_keep, 0, 1, 1, 1) | ||||
|         self.formLayout_2.setLayout( | ||||
|             4, QtWidgets.QFormLayout.FieldRole, self.gridLayout_4 | ||||
|         ) | ||||
|         self.formLayout_2.setLayout(4, QtWidgets.QFormLayout.FieldRole, self.gridLayout_4) | ||||
|         self.lst_sys = QtWidgets.QTreeWidget(self.tab_route) | ||||
|         sizePolicy = QtWidgets.QSizePolicy( | ||||
|             QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding | ||||
|         ) | ||||
|         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) | ||||
|         sizePolicy.setHorizontalStretch(0) | ||||
|         sizePolicy.setVerticalStretch(0) | ||||
|         sizePolicy.setHeightForWidth(self.lst_sys.sizePolicy().hasHeightForWidth()) | ||||
|  | @ -417,27 +353,17 @@ class Ui_ED_LRR(object): | |||
| 
 | ||||
|     def retranslateUi(self, ED_LRR): | ||||
|         _translate = QtCore.QCoreApplication.translate | ||||
|         ED_LRR.setWindowTitle( | ||||
|             _translate("ED_LRR", "Elite: Dangerous Long Range Route Plotter") | ||||
|         ) | ||||
|         ED_LRR.setWindowTitle(_translate("ED_LRR", "Elite: Dangerous Long Range Route Plotter")) | ||||
|         self.lbl_bodies_dl.setText(_translate("ED_LRR", "bodies.json")) | ||||
|         self.lbl_systems_dl.setText(_translate("ED_LRR", "systemsWithCoordinates.json")) | ||||
|         self.inp_bodies_dl.setCurrentText( | ||||
|             _translate("ED_LRR", "https://www.edsm.net/dump/bodies.json") | ||||
|         ) | ||||
|         self.inp_systems_dl.setCurrentText( | ||||
|             _translate( | ||||
|                 "ED_LRR", "https://www.edsm.net/dump/systemsWithCoordinates.json" | ||||
|             ) | ||||
|         ) | ||||
|         self.inp_bodies_dl.setCurrentText(_translate("ED_LRR", "https://www.edsm.net/dump/bodies.json")) | ||||
|         self.inp_systems_dl.setCurrentText(_translate("ED_LRR", "https://www.edsm.net/dump/systemsWithCoordinates.json")) | ||||
|         self.btn_bodies_dest_browse_dl.setText(_translate("ED_LRR", "...")) | ||||
|         self.btn_systems_dest_browse_dl.setText(_translate("ED_LRR", "...")) | ||||
|         self.btn_download.setText(_translate("ED_LRR", "Download")) | ||||
|         self.label.setText(_translate("ED_LRR", "Download path")) | ||||
|         self.label_2.setText(_translate("ED_LRR", "Download path")) | ||||
|         self.tabs.setTabText( | ||||
|             self.tabs.indexOf(self.tab_download), _translate("ED_LRR", "Download") | ||||
|         ) | ||||
|         self.tabs.setTabText(self.tabs.indexOf(self.tab_download), _translate("ED_LRR", "Download")) | ||||
|         self.lbl_bodies_pp.setText(_translate("ED_LRR", "bodies.json")) | ||||
|         self.btn_bodies_browse_pp.setText(_translate("ED_LRR", "...")) | ||||
|         self.lbl_systems_pp.setText(_translate("ED_LRR", "systemsWithCoordinates.json")) | ||||
|  | @ -445,9 +371,7 @@ class Ui_ED_LRR(object): | |||
|         self.lbl_out_pp.setText(_translate("ED_LRR", "Output")) | ||||
|         self.btn_out_browse_pp.setText(_translate("ED_LRR", "...")) | ||||
|         self.btn_preprocess.setText(_translate("ED_LRR", "Preprocess")) | ||||
|         self.tabs.setTabText( | ||||
|             self.tabs.indexOf(self.tab_preprocess), _translate("ED_LRR", "Preprocess") | ||||
|         ) | ||||
|         self.tabs.setTabText(self.tabs.indexOf(self.tab_preprocess), _translate("ED_LRR", "Preprocess")) | ||||
|         self.lbl_sys_lst.setText(_translate("ED_LRR", "System List")) | ||||
|         self.btn_sys_lst_browse.setText(_translate("ED_LRR", "...")) | ||||
|         self.btn_add.setText(_translate("ED_LRR", "Add")) | ||||
|  | @ -469,12 +393,8 @@ class Ui_ED_LRR(object): | |||
|         self.chk_primary.setText(_translate("ED_LRR", "Primary Stars Only")) | ||||
|         self.lbl_mode.setText(_translate("ED_LRR", "Mode")) | ||||
|         self.btn_go.setText(_translate("ED_LRR", "GO!")) | ||||
|         self.tabs.setTabText( | ||||
|             self.tabs.indexOf(self.tab_route), _translate("ED_LRR", "Route") | ||||
|         ) | ||||
|         self.tabs.setTabText( | ||||
|             self.tabs.indexOf(self.tab_log), _translate("ED_LRR", "Log") | ||||
|         ) | ||||
|         self.tabs.setTabText(self.tabs.indexOf(self.tab_route), _translate("ED_LRR", "Route")) | ||||
|         self.tabs.setTabText(self.tabs.indexOf(self.tab_log), _translate("ED_LRR", "Log")) | ||||
|         self.menu_file.setTitle(_translate("ED_LRR", "File")) | ||||
|         self.menuWindow.setTitle(_translate("ED_LRR", "Window")) | ||||
|         self.menuStyle.setTitle(_translate("ED_LRR", "Style")) | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ import sys | |||
| from sys import exit | ||||
| from datetime import datetime, timedelta | ||||
| from urllib.request import Request, urlopen | ||||
| 
 | ||||
| import _ed_lrr | ||||
| import ed_lrr_gui | ||||
| import requests as RQ | ||||
|  | @ -436,7 +435,7 @@ class ED_LRR(Ui_ED_LRR): | |||
|                 return | ||||
|         self.bar_status.clearMessage() | ||||
|         print(self.systems) | ||||
|         systems = [str(s["id"]) for s in self.systems] | ||||
|         systems = [s["id"] for s in self.systems] | ||||
|         jump_range = self.sb_range.value() | ||||
|         if jump_range == 0: | ||||
|             self.error( | ||||
|  | @ -466,13 +465,16 @@ class ED_LRR(Ui_ED_LRR): | |||
|         print( | ||||
|             systems, | ||||
|             jump_range, | ||||
|             None, | ||||
|             mode, | ||||
|             primary, | ||||
|             permute, | ||||
|             (keep_first, keep_last), | ||||
|             keep_first, | ||||
|             keep_last, | ||||
|             greedyness, | ||||
|             path, | ||||
|             precomp, | ||||
|             path, | ||||
|             os.cpu_count()-1 | ||||
|         ) | ||||
|         if not self.current_job: | ||||
|             self.bar_status.showMessage("Computing Route...") | ||||
|  | @ -490,6 +492,7 @@ class ED_LRR(Ui_ED_LRR): | |||
|                 greedyness, | ||||
|                 precomp, | ||||
|                 path, | ||||
|                 os.cpu_count()-1 | ||||
|             ) | ||||
|         else: | ||||
|             self.error("there is already a job running!") | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
| # Form implementation generated from reading ui file 'D:\devel\rust\ed_lrr_gui\ed_lrr_gui\gui\widget_route.ui' | ||||
| # | ||||
| # Created by: PyQt5 UI code generator 5.13.1 | ||||
| # Created by: PyQt5 UI code generator 5.14.1 | ||||
| # | ||||
| # WARNING! All changes made in this file will be lost! | ||||
| 
 | ||||
|  | @ -25,9 +25,7 @@ class Ui_diag_route(object): | |||
|         self.lst_route.setAlternatingRowColors(True) | ||||
|         self.lst_route.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) | ||||
|         self.lst_route.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerItem) | ||||
|         self.lst_route.setHorizontalScrollMode( | ||||
|             QtWidgets.QAbstractItemView.ScrollPerPixel | ||||
|         ) | ||||
|         self.lst_route.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) | ||||
|         self.lst_route.setItemsExpandable(False) | ||||
|         self.lst_route.setAllColumnsShowFocus(False) | ||||
|         self.lst_route.setObjectName("lst_route") | ||||
|  | @ -55,11 +53,7 @@ class Ui_diag_route(object): | |||
|         self.lst_route.headerItem().setText(0, _translate("diag_route", "Num")) | ||||
|         self.lst_route.headerItem().setText(1, _translate("diag_route", "System")) | ||||
|         self.lst_route.headerItem().setText(2, _translate("diag_route", "Body")) | ||||
|         self.lst_route.headerItem().setText( | ||||
|             3, _translate("diag_route", "Distance (Ls)") | ||||
|         ) | ||||
|         self.chk_copy.setText( | ||||
|             _translate("diag_route", "Auto-copy next hop to clipboard") | ||||
|         ) | ||||
|         self.lst_route.headerItem().setText(3, _translate("diag_route", "Distance (Ls)")) | ||||
|         self.chk_copy.setText(_translate("diag_route", "Auto-copy next hop to clipboard")) | ||||
|         self.btn_close.setText(_translate("diag_route", "Close")) | ||||
|         self.btn_export.setText(_translate("diag_route", "Export")) | ||||
|  |  | |||
|  | @ -10,7 +10,6 @@ def dist(p1, p2): | |||
|         s += (c1 - c2) ** 2 | ||||
|     return s ** 0.5 | ||||
| 
 | ||||
| 
 | ||||
| colors = { | ||||
|     "O": "#0000FF", | ||||
|     "B": "#140AF0", | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ class Router(Process): | |||
|         self.queue.put({"status": state}) | ||||
| 
 | ||||
|     def run(self): | ||||
|         print("Route(): ",self.args,self.kwargs) | ||||
|         route = _ed_lrr.route(*self.args, **self.kwargs) | ||||
|         self.queue.put({"return": route}) | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| from .app import app, templates, db | ||||
|  | @ -1,100 +0,0 @@ | |||
| from flask import Flask, jsonify | ||||
| import uuid | ||||
| import json | ||||
| from webargs import fields, validate | ||||
| from webargs.flaskparser import use_args | ||||
| from flask_sqlalchemy import SQLAlchemy | ||||
| from sqlalchemy_utils import Timestamp, generic_repr | ||||
| from sqlalchemy.ext.declarative import declarative_base | ||||
| from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref | ||||
| from sqlalchemy.types import Float, String, Boolean | ||||
| 
 | ||||
| app = Flask(__name__) | ||||
| app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///jobs.db" | ||||
| app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False | ||||
| 
 | ||||
| db = SQLAlchemy(app) | ||||
| 
 | ||||
| 
 | ||||
| @generic_repr | ||||
| class Job(db.Model, Timestamp): | ||||
|     id = db.Column(db.String, default=lambda: str(uuid.uuid4()), primary_key=True) | ||||
|     jump_range = db.Column(db.Float, nullable=False) | ||||
|     mode = db.Column(db.String, default="bfs") | ||||
|     systems = db.Column(db.String) | ||||
|     permute = db.Column(db.String, default=None, nullable=True) | ||||
|     primary = db.Column(db.Boolean, default=False) | ||||
|     factor = db.Column(db.Float, default=0.5) | ||||
|     done = db.Column(db.DateTime, nullable=True, default=None) | ||||
|     started = db.Column(db.DateTime, nullable=True, default=None) | ||||
|     progress = db.Column(db.Float, default=0.0) | ||||
| 
 | ||||
|     # ============================================================ | ||||
| 
 | ||||
|     @classmethod | ||||
|     def new(cls, **kwargs): | ||||
|         obj = cls(**kwargs) | ||||
|         db.session.add(obj) | ||||
|         db.session.commit() | ||||
|         print(obj) | ||||
|         return obj | ||||
| 
 | ||||
|     @property | ||||
|     def dict(self): | ||||
|         ret = {} | ||||
|         for col in self.__table__.columns: | ||||
|             ret[col.name] = getattr(self, col.name) | ||||
|         ret["systems"] = json.loads(ret["systems"]) | ||||
|         return ret | ||||
| 
 | ||||
|     @dict.setter | ||||
|     def set_dict(self, *args, **kwargs): | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
| 
 | ||||
| db.create_all() | ||||
| db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| @app.errorhandler(422) | ||||
| @app.errorhandler(400) | ||||
| def handle_error(err): | ||||
|     headers = err.data.get("headers", None) | ||||
|     messages = err.data.get("messages", ["Invalid request."]) | ||||
|     if headers: | ||||
|         return jsonify({"errors": messages}), err.code, headers | ||||
|     else: | ||||
|         return jsonify({"errors": messages}), err.code | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/route", methods=["GET", "POST"]) | ||||
| @use_args( | ||||
|     { | ||||
|         "jump_range": fields.Float(required=True), | ||||
|         "mode": fields.String( | ||||
|             missing="bfs", validate=validate.OneOf(["bfs", "greedy", "a-star"]) | ||||
|         ), | ||||
|         "systems": fields.DelimitedList(fields.String, required=True), | ||||
|         "permute": fields.String( | ||||
|             missing=None, | ||||
|             validate=validate.OneOf(["all", "keep_first", "keep_last", "keep_both"]), | ||||
|         ), | ||||
|         "primary": fields.Boolean(missing=False), | ||||
|         "factor": fields.Float(missing=0.5), | ||||
|     } | ||||
| ) | ||||
| def route(args): | ||||
|     args["systems"] = json.dumps(args["systems"]) | ||||
|     for k, v in args.items(): | ||||
|         print(k, v) | ||||
|     return jsonify({"id": Job.new(**args).id}) | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/status/<uuid:job_id>") | ||||
| def status(job_id): | ||||
|     job = db.session.query(Job).get_or_404(str(job_id)) | ||||
|     return jsonify(job.dict) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     app.run(host="0.0.0.0", port=3777, debug=True) | ||||
							
								
								
									
										665
									
								
								ed_lrr_gui/web/app.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										665
									
								
								ed_lrr_gui/web/app.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,665 @@ | |||
| from flask import ( | ||||
|     Flask, | ||||
|     jsonify, | ||||
|     session, | ||||
|     render_template, | ||||
|     redirect, | ||||
|     url_for, | ||||
|     send_from_directory, | ||||
|     request, | ||||
|     flash, | ||||
|     current_app | ||||
| ) | ||||
| from flask.json.tag import JSONTag | ||||
| import uuid | ||||
| import pickle | ||||
| import os | ||||
| import time | ||||
| import random | ||||
| import base64 | ||||
| import gevent | ||||
| from functools import wraps | ||||
| from concurrent.futures.process import BrokenProcessPool | ||||
| from datetime import datetime, timedelta | ||||
| from decimal import Decimal | ||||
| from multiprocessing import Queue | ||||
| from webargs import fields, validate | ||||
| from webargs.flaskparser import use_kwargs | ||||
| 
 | ||||
| from flask_executor import Executor | ||||
| from flask_sqlalchemy import SQLAlchemy | ||||
| from flask_bootstrap import Bootstrap | ||||
| 
 | ||||
| from flask_nav import Nav, register_renderer | ||||
| from flask_nav.elements import Navbar, View | ||||
| 
 | ||||
| from flask_admin import Admin | ||||
| from flask_admin.contrib.sqla import ModelView | ||||
| 
 | ||||
| from flask_wtf.csrf import CSRFProtect | ||||
| 
 | ||||
| from flask_login import ( | ||||
|     LoginManager, | ||||
|     current_user, | ||||
|     logout_user, | ||||
|     UserMixin, | ||||
|     AnonymousUserMixin, | ||||
|     login_user, | ||||
|     login_required, | ||||
| ) | ||||
| 
 | ||||
| from flask_debugtoolbar import DebugToolbarExtension | ||||
| 
 | ||||
| from werkzeug.http import HTTP_STATUS_CODES | ||||
| from sqlalchemy_utils import generic_repr, JSONType, PasswordType, UUIDType | ||||
| from sqlalchemy.ext.declarative import declarative_base | ||||
| from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref | ||||
| from sqlalchemy.types import Float, String, DateTime | ||||
| from jinja2.exceptions import TemplateNotFound | ||||
| from .forms import RouteForm, LoginForm, RegisterForm, ChangePasswordForm | ||||
| from .utils import prepare_route, BootsrapRenderer, is_safe_url | ||||
| 
 | ||||
| import _ed_lrr as ed_lrr | ||||
| 
 | ||||
| templates = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates") | ||||
| app = Flask(__name__, template_folder=templates) | ||||
| app.config.from_pyfile("config.py") | ||||
| 
 | ||||
| executor = Executor(app) | ||||
| db = SQLAlchemy(app) | ||||
| bootstrap = Bootstrap(app) | ||||
| csrf = CSRFProtect(app) | ||||
| nav = Nav(app) | ||||
| login_manager = LoginManager(app) | ||||
| login_manager.login_view = "login" | ||||
| login_manager.session_protection = "strong" | ||||
| admin = Admin(app, name="ED_LRR", template_mode="bootstrap3") | ||||
| app.debug=True | ||||
| toolbar = DebugToolbarExtension(app) | ||||
| 
 | ||||
| 
 | ||||
| def wants_json_response(): | ||||
|     return request.accept_mimetypes['application/json'] >= \ | ||||
|         request.accept_mimetypes['text/html'] | ||||
| 
 | ||||
| 
 | ||||
| @app.errorhandler(422) | ||||
| @app.errorhandler(400) | ||||
| @app.errorhandler(500) | ||||
| @app.errorhandler(404) | ||||
| def handle_error(err): | ||||
|     if wants_json_response(): | ||||
|         return jsonify(error=str(err),code=err.code), err.code | ||||
|     templates=["error/{}.html".format(err.code),"error/default.html"] | ||||
|     try: | ||||
|         print(dir(err)) | ||||
|         return render_template(templates,error=err),err.code | ||||
|     except TemplateNotFound: | ||||
|         return err.get_response() | ||||
| 
 | ||||
| def role_required(*roles): | ||||
|     def wrapper(fn): | ||||
|         @wraps(fn) | ||||
|         def decorated_view(*args, **kwargs): | ||||
|             if not current_user.is_authenticated(): | ||||
|                return current_app.login_manager.unauthorized() | ||||
|             has_role=False | ||||
|             user=current_app.login_manager.reload_user() | ||||
|             for role in roles: | ||||
|                 has_role|=user.has_role(role) | ||||
|             if not has_role: | ||||
|                 return current_app.login_manager.unauthorized() | ||||
|             return fn(*args, **kwargs) | ||||
|         return decorated_view | ||||
|     return wrapper | ||||
| 
 | ||||
| @login_manager.user_loader | ||||
| def load_user(user_name): | ||||
|     return User.query.get(user_name) | ||||
| 
 | ||||
| 
 | ||||
| @login_manager.request_loader | ||||
| def load_user_from_header(header_val): | ||||
|     for api_key in [request.args.get('api_key'),request.headers.get('X-API-Key')]: | ||||
|         if api_key: | ||||
|             user = User.query.filter_by(api_key=api_key).one_or_none() | ||||
|             if user: | ||||
|                 return user | ||||
|             return None | ||||
|     return None | ||||
| 
 | ||||
| 
 | ||||
| def left_nav(): | ||||
|     links=[View("Home", "index"),View("Route", "route"),View("Jobs", "status",job_id=None)] | ||||
|     if current_user.has_role('admin') or current_user.has_role('worker_host'): | ||||
|         links.insert(2,View("Workers","worker")) | ||||
|     return Navbar( | ||||
|         "E:D LRR", | ||||
|         *links | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def right_nav(): | ||||
|     links = [View("Login", "login"), View("Register", "register")] | ||||
|     if current_user.is_authenticated: | ||||
|         links = [View("Change Password", "change_password"), View("Logout", "logout")] | ||||
|     if current_user.has_role('admin'): | ||||
|         links = [View("Admin", "admin.index")] + links | ||||
|     return Navbar("", *links) | ||||
| 
 | ||||
| 
 | ||||
| register_renderer(app, "bootstrap4", BootsrapRenderer) | ||||
| nav.register_element("left_nav", left_nav) | ||||
| nav.register_element("right_nav", right_nav) | ||||
| 
 | ||||
| 
 | ||||
| def compute_route(args, kwargs): | ||||
|     return ed_lrr.route(*args, **kwargs) | ||||
| 
 | ||||
| 
 | ||||
| class AnonymousUser(AnonymousUserMixin): | ||||
|      | ||||
|     def has_role(self,role): | ||||
|         return False | ||||
| 
 | ||||
|     @property | ||||
|     def roles(self): | ||||
|         return [] | ||||
|      | ||||
|     @roles.setter | ||||
|     def __set_roles(self,value): | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
| 
 | ||||
| login_manager.anonymous_user = AnonymousUser | ||||
| 
 | ||||
| 
 | ||||
| @generic_repr | ||||
| class Worker(db.Model): | ||||
|     id = db.Column( | ||||
|         UUIDType(binary=False, native=False), primary_key=True, default=uuid.uuid4 | ||||
|     ) | ||||
|     name = db.Column(db.String, unique=True) | ||||
|     current_job=db.Column(UUIDType(binary=False, native=False),db.ForeignKey("job.id"), nullable=True,default=None) | ||||
|     job = relationship('Job',backref="workers") | ||||
|     last_active = db.Column(DateTime, nullable=True,default=None) | ||||
|     owner_name = db.Column( | ||||
|         db.String, db.ForeignKey("user.name"), nullable=True,index=True | ||||
|     ) | ||||
|     owner = relationship("User",backref="workers") | ||||
| 
 | ||||
| user_roles = db.Table('user_roles', | ||||
|     db.Column('user_name', db.String, db.ForeignKey('user.name'),primary_key=True), | ||||
|     db.Column('role_name', db.String, db.ForeignKey('role.name'),primary_key=True) | ||||
| ) | ||||
| 
 | ||||
| class Role(db.Model): | ||||
|     name = db.Column(db.String, unique=True,index=True,primary_key=True) | ||||
| 
 | ||||
|     def __init__(self,name): | ||||
|         self.name=name | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return self.name | ||||
| 
 | ||||
| class User(db.Model, UserMixin): | ||||
|     name = db.Column(db.String, unique=True,index=True,primary_key=True) | ||||
|     is_active = db.Column(db.Boolean, default=False) | ||||
|     api_key = db.Column( | ||||
|         UUIDType(binary=False, native=False), nullable=True, default=uuid.uuid4,index=True | ||||
|     ) | ||||
|     password = db.Column(PasswordType(schemes=["pbkdf2_sha512"], max_length=256)) | ||||
|     created = db.Column(DateTime, default=datetime.today) | ||||
| 
 | ||||
|     roles = db.relationship("Role",secondary="user_roles") | ||||
| 
 | ||||
|     def add_roles(self,roles): | ||||
|         for role_name in roles: | ||||
|             role=Role.query.filter_by(name=role_name).one() | ||||
|             if not role in self.roles: | ||||
|                 self.roles.append(role) | ||||
|         db.session.commit() | ||||
| 
 | ||||
|     def has_role(self,role_name): | ||||
|         return Role.query.join(User.roles).filter(User.name==self.name,Role.name==role_name).count()>0 | ||||
|         return ret | ||||
|          | ||||
|     def reset_api_key(self): | ||||
|         self.api_key=uuid,uuid4() | ||||
|         db.session.add(self) | ||||
|         db.session.comiit() | ||||
| 
 | ||||
|     def get_id(self): | ||||
|         return self.name | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return self.name | ||||
| 
 | ||||
| 
 | ||||
| class Job(db.Model): | ||||
|     id = db.Column( | ||||
|         UUIDType(binary=False, native=False), primary_key=True, default=uuid.uuid4 | ||||
|     ) | ||||
|     user_name = db.Column( | ||||
|         db.String, db.ForeignKey("user.name"), nullable=True,index=True | ||||
|     ) | ||||
|     func = db.Column(db.String) | ||||
|     args = db.Column(JSONType) | ||||
|     kwargs = db.Column(JSONType) | ||||
|     state = db.Column(JSONType, default={}) | ||||
|     priority = db.Column(db.Integer, default=0,nullable=True) | ||||
|     created = db.Column(DateTime, default=datetime.today) | ||||
|     finished = db.Column(DateTime, nullable=True, default=None) | ||||
|     started = db.Column(DateTime, nullable=True, default=None) | ||||
|     last_update = db.Column(DateTime, nullable=True, default=None) | ||||
|     user = relationship("User",backref="jobs") | ||||
|     # ============================================================ | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return str(self.id) | ||||
| 
 | ||||
|      | ||||
|     @property | ||||
|     def future(self): | ||||
|         fut = executor.futures._futures.get(self.id) | ||||
|         return fut | ||||
| 
 | ||||
|     @property | ||||
|     def sort_key(self): | ||||
|         state_priorities={"Queued":0,"Starting":1,"Error":1,"Stalled":1,"Running":1} | ||||
|         status_key=state_priorities.get(self.status[1],-1)+1 | ||||
|         user=1-int(self.user is not None) | ||||
|         return (user,-status_key,self.priority,self.created) | ||||
| 
 | ||||
|     @property | ||||
|     def age(self): | ||||
|         dt=datetime.today()-self.created | ||||
|         return dt - dt % timedelta(seconds=1) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def next(cls): | ||||
|         for job in sorted(cls.query.all(),key=lambda v:v.sort_key): | ||||
|             if job.status[1] in ['Done']: | ||||
|                 continue | ||||
|             return job | ||||
|         return None | ||||
|         # return cls.query. | ||||
| 
 | ||||
|     @property | ||||
|     def status(self): | ||||
|         states=[ | ||||
|             ("primary", "Done"), | ||||
|             ("danger", "Error"), | ||||
|             ("info", "Stalled"), | ||||
|             ("success", "Running"), | ||||
|             ("secondary", "Starting"), | ||||
|             ("warning", "Queued") | ||||
|         ] | ||||
|         #return states[self.id.int%len(states)] | ||||
|         if self.state.get("result"): | ||||
|             return ("primary", "Done") | ||||
|         if self.state.get("error"): | ||||
|             return ("danger", "Error") | ||||
|         if self.state.get("progress"): | ||||
|             if (datetime.today() - self.last_update).total_seconds() > (60 * 10): | ||||
|                 return ("info", "Stalled") | ||||
|             return ("success", "Running") | ||||
|         if self.started is not None: | ||||
|             return ("secondary", "Starting") | ||||
|         return ("warning", "Queued") | ||||
| 
 | ||||
|     @status.setter | ||||
|     def __set_status(self): | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     @property | ||||
|     def dict(self): | ||||
|         return { | ||||
|             "id": self.id, | ||||
|             "args": self.args, | ||||
|             "kwargs": self.kwargs, | ||||
|             "state": self.state, | ||||
|             "finished": self.finished, | ||||
|             "created": self.created, | ||||
|             "started": self.started, | ||||
|         } | ||||
| 
 | ||||
|     @dict.setter | ||||
|     def __set_dict(self, value): | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     @property | ||||
|     def route(self): | ||||
|         try: | ||||
|             return prepare_route(self.state["result"]) | ||||
|         except KeyError: | ||||
|             return None | ||||
| 
 | ||||
|     @property | ||||
|     def t_rem(self): | ||||
|         if self.started is None: | ||||
|             return None | ||||
|         runtime = datetime.today() - self.started | ||||
|         try: | ||||
|             prc_done = self.state["progress"]["prc_done"] | ||||
|             if prc_done != 0: | ||||
|                 t_rem = (runtime / prc_done) * (100 - prc_done) | ||||
|                 return timedelta(seconds=round(t_rem.total_seconds(), 0)) | ||||
|             return None | ||||
|         except KeyError: | ||||
|             return None | ||||
| 
 | ||||
|     @t_rem.setter | ||||
|     def __set_t_rem(self, value): | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
|     @classmethod | ||||
|     def new(cls, func, args=None, kwargs=None): | ||||
|         args = args or () | ||||
|         kwargs = kwargs or {} | ||||
|         job = cls(args=args, kwargs=kwargs, func=func.__qualname__) | ||||
|         job.__last_upd = 0.0 | ||||
|         if current_user.is_authenticated: | ||||
|             job.user = current_user | ||||
|         db.session.add(job) | ||||
|         db.session.commit() | ||||
|         return job | ||||
| 
 | ||||
|     def start(self): | ||||
|         global executor | ||||
|         self.state = {} | ||||
|         self.started = None | ||||
|         db.session.add(self) | ||||
|         db.session.commit() | ||||
|         args = self.args + [self.callback] | ||||
|         try: | ||||
|             future = executor.submit_stored(self.id, compute_route, args, self.kwargs) | ||||
|         except (BrokenProcessPool, RuntimeError) as e: | ||||
|             print("Error:", e) | ||||
|             print("Restarting Executor!") | ||||
|             executor = Executor(app) | ||||
|             future = executor.submit_stored(self.id, compute_route, args, self.kwargs) | ||||
|         future.add_done_callback(self.done) | ||||
| 
 | ||||
|     def callback(self, cb_state): | ||||
|         try: | ||||
|             if self.started is None: | ||||
|                 self.started = datetime.today() | ||||
|             if self.last_update is not None: | ||||
|                 time_since_last_upd = ( | ||||
|                     datetime.today() - self.last_update | ||||
|                 ).total_seconds() | ||||
|                 if time_since_last_upd < 5.0: | ||||
|                     return | ||||
|             state = dict() | ||||
|             state.update(self.state) | ||||
|             state.update({"progress": cb_state}) | ||||
|             self.state = state | ||||
|             self.last_update = datetime.today() | ||||
|             db.session.add(self) | ||||
|             db.session.commit() | ||||
|         except Exception as e: | ||||
|             print(e) | ||||
| 
 | ||||
|     def done(self, future): | ||||
|         print(self.id, "DONE") | ||||
|         state = dict() | ||||
|         state.update(self.state) | ||||
|         executor.futures.pop(self.id) | ||||
|         exc = future.exception() | ||||
|         if exc: | ||||
|             state.update( | ||||
|                 {"error": {"type": type(exc).__name__, "args": list(exc.args)}} | ||||
|             ) | ||||
|         else: | ||||
|             state.update({"result": future.result()}) | ||||
|         self.state = state | ||||
|         self.finished = datetime.now() | ||||
|         db.session.add(self) | ||||
|         db.session.commit() | ||||
| 
 | ||||
| 
 | ||||
| db.create_all() | ||||
| for role in ['admin','user','worker_host']: | ||||
|     if Role.query.filter_by(name=role).one_or_none() is None: | ||||
|         db.session.add(Role(role)) | ||||
| 
 | ||||
| def create_user(name,password,roles,active=False): | ||||
|     user=User.query.filter_by(name=name).one_or_none() | ||||
|     if user: | ||||
|         db.session.delete(user) | ||||
|     user=User(name=name,password=password,is_active=active) | ||||
|     user.add_roles(roles) | ||||
|     db.session.add(user) | ||||
|     db.session.commit() | ||||
|     return user | ||||
| 
 | ||||
| create_user('admin','admin',['admin','user'],True) | ||||
| create_user('user','user',['user'],True) | ||||
| create_user('host','host',['user','worker_host'],True) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class SQLAView(ModelView): | ||||
|     column_exclude_list = ["password"] | ||||
|     column_editable_list = [] | ||||
|     create_modal = True | ||||
|     edit_modal = True | ||||
|     can_view_details = True | ||||
|     column_display_pk = True | ||||
| 
 | ||||
|     def is_accessible(self): | ||||
|         return current_user.is_authenticated and current_user.has_role('admin') | ||||
| 
 | ||||
|     def inaccessible_callback(self, name, **kwargs): | ||||
|         return redirect(url_for("login")) | ||||
| 
 | ||||
| 
 | ||||
| class UserView(SQLAView): | ||||
|     from wtforms import PasswordField | ||||
| 
 | ||||
|     column_list = ("name", "active", "password", "api_key","roles") | ||||
|     column_formatters = { | ||||
|         "password": lambda view, context, model, name: "", | ||||
|         "api_key": lambda view, context, model, name: model.api_key or "", | ||||
|     } | ||||
|     form_extra_fields = {"password": PasswordField("Password")} | ||||
| 
 | ||||
| 
 | ||||
| class JobView(SQLAView): | ||||
|     # Job.id,Job.user,Job.func,Job.args,Job.kwargs,Job.state,Job.created,Job.finished,Job.started,Job.last_update | ||||
|     column_list = ("id", "status", "user", "created", "started", "finished") | ||||
|     column_formatters = { | ||||
|         "status": lambda view, context, model, name: model.status[1], | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| class WorkerView(SQLAView): | ||||
|     pass | ||||
|     # # Job.id,Job.user,Job.func,Job.args,Job.kwargs,Job.state,Job.created,Job.finished,Job.started,Job.last_update | ||||
|     # column_list = ("id", "status", "user", "created", "started", "finished") | ||||
|     # column_formatters = { | ||||
|     #     "user": lambda view, context, model, name: model.user.name | ||||
|     #     if model.user | ||||
|     #     else "", | ||||
|     #     "status": lambda view, context, model, name: model.status[1], | ||||
|     # } | ||||
| 
 | ||||
| admin.add_view(JobView(Job, db.session)) | ||||
| admin.add_view(UserView(User, db.session)) | ||||
| admin.add_view(SQLAView(Worker, db.session)) | ||||
| admin.add_view(SQLAView(Role, db.session)) | ||||
| 
 | ||||
| 
 | ||||
| def submit_job(func, *args, **kwargs): | ||||
|     job = Job.new(func, args, kwargs) | ||||
|     job.start() | ||||
|     return job.id | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/api/route", methods=["GET", "POST"]) | ||||
| @use_kwargs( | ||||
|     { | ||||
|         "jump_range": fields.Float(required=True), | ||||
|         "mode": fields.String( | ||||
|             missing="bfs", validate=validate.OneOf(["bfs", "greedy", "a-star"]) | ||||
|         ), | ||||
|         "systems": fields.DelimitedList(fields.String, required=True), | ||||
|         "permute": fields.String( | ||||
|             missing=None, | ||||
|             validate=validate.OneOf( | ||||
|                 ["off", "all", "keep_first", "keep_last", "keep_both"] | ||||
|             ), | ||||
|         ), | ||||
|         "primary": fields.Boolean(missing=False), | ||||
|         "factor": fields.Float(missing=0.5), | ||||
|     } | ||||
| ) | ||||
| def api_route(_=None, **args): | ||||
|     if args["permute"] == "off": | ||||
|         args["permute"] = None | ||||
|     args["systems"] = [s.strip() for s in args["systems"]] | ||||
|     args = ( | ||||
|         args["systems"], | ||||
|         args["jump_range"], | ||||
|         None, | ||||
|         args["mode"], | ||||
|         args["primary"], | ||||
|         args["permute"] is not None, | ||||
|         args["permute"] in ["keep_first", "keep_both"], | ||||
|         args["permute"] in ["keep_last", "keep_both"], | ||||
|         args["factor"], | ||||
|         None, | ||||
|         r"D:\devel\rust\ED_LRR\stars.csv", | ||||
|         app.config['ROUTE_WORKERS'] | ||||
|     ) | ||||
|     return jsonify({"id": submit_job(ed_lrr.route, *args)}) | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/api/status") | ||||
| def api_status(): | ||||
|     info = {"queued_jobs": len(executor.futures._futures)} | ||||
|     return jsonify(info) | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/api/whoami") | ||||
| def api_whoami(): | ||||
|     return jsonify({'name':current_user.name}) | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/api/status/<uuid:job_id>") | ||||
| def api_job_status(job_id): | ||||
|     job = Job.query.get_or_404(str(job_id)) | ||||
|     return jsonify(job.dict) | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/static/<path:path>") | ||||
| def send_static(path): | ||||
|     return send_from_directory("static", path) | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/route", methods=["GET", "POST"]) | ||||
| @login_required | ||||
| def route(): | ||||
|     form = RouteForm() | ||||
|     if form.validate_on_submit(): | ||||
|         data = dict(form.data) | ||||
|         if data["permute"] == "off": | ||||
|             data["permute"] = None | ||||
|         del data["csrf_token"] | ||||
|         del data["submit"] | ||||
|         job = api_route(data) | ||||
|         return redirect(url_for("status", job_id=job.json["id"])) | ||||
|     return render_template("form.html", form=form, title="Plot Route") | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/status/",defaults={'job_id':None}) | ||||
| @app.route("/status/<uuid:job_id>") | ||||
| @login_required | ||||
| def status(job_id=None): | ||||
|     if job_id is not None: | ||||
|         job=Job.query.get_or_404(str(job_id)) | ||||
|         return render_template("job.html", job=job) | ||||
|     return render_template( | ||||
|         "status.html", Job=Job, state=request.args.get("state") | ||||
|     ) | ||||
| 
 | ||||
| @app.route("/") | ||||
| def index(): | ||||
|     return render_template("index.html") | ||||
| 
 | ||||
| @app.route("/login", methods=["GET", "POST"]) | ||||
| def login(): | ||||
|     if current_user.is_authenticated: | ||||
|         return redirect(url_for("index")) | ||||
|     form = LoginForm() | ||||
|     if form.validate_on_submit(): | ||||
|         user = User.query.filter_by(name=form.data["username"]).one_or_none() | ||||
|         if (user is None) or (user.password != form.data["password"]): | ||||
|             flash("Invalid credentials!", "danger") | ||||
|             return redirect(url_for("login")) | ||||
|         if not user.is_active: | ||||
|             flash("Account is deactivated!", "warning") | ||||
|             return redirect(url_for("login")) | ||||
|         login_user(user, remember=form.data["remember"]) | ||||
|         next = request.args.get('next') | ||||
|         if not is_safe_url(next): | ||||
|             next=None | ||||
|         return redirect(next or url_for("status")) | ||||
|     return render_template("form.html", form=form, title="Login") | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/register", methods=["GET", "POST"]) | ||||
| def register(): | ||||
|     form = RegisterForm() | ||||
|     if form.validate_on_submit(): | ||||
|         if User.query.filter_by(name=form.data["username"]).one_or_none() is not None: | ||||
|             flash('Username already exists','danger') | ||||
|             return render_template("form.html", form=form, title="Register") | ||||
|         user = User() | ||||
|         user.name = form.data["username"] | ||||
|         user.password = form.data["password"] | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|         login_user(user) | ||||
|         return redirect(url_for("status")) | ||||
|     return render_template("form.html", form=form, title="Register") | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/change_password", methods=["GET", "POST"]) | ||||
| def change_password(): | ||||
|     if current_user.is_anonymous: | ||||
|         return redirect(url_for("index")) | ||||
|     form = ChangePasswordForm() | ||||
|     if form.validate_on_submit(): | ||||
|         if form.data["old_password"] == current_user.password: | ||||
|             current_user.password = form.data["password"] | ||||
|             flash("Password changed!", "success") | ||||
|         else: | ||||
|             flash("Wrong password!", "danger") | ||||
|             return render_template("form.html", form=form, title="Register") | ||||
|         return redirect(url_for("status")) | ||||
|     return render_template("form.html", form=form, title="Register") | ||||
| 
 | ||||
| @app.route("/workers/") | ||||
| @login_required | ||||
| def worker(): | ||||
|     return render_template("workers.html") | ||||
| 
 | ||||
| @app.route("/logout") | ||||
| def logout(): | ||||
|     logout_user() | ||||
|     return redirect(url_for("login")) | ||||
| 
 | ||||
| 
 | ||||
| @app.before_first_request | ||||
| def resume_jobs(): | ||||
|     print(Job.next()) | ||||
|     with app.test_request_context(): | ||||
|         for job in Job.query.all(): | ||||
|             if job.status[1] != "Done": | ||||
|                 print("Restarting {} with state {}".format(job.id, job.status[1])) | ||||
|                 job.start() | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     app.run(host="127.0.0.1", port=3777, debug=True) | ||||
							
								
								
									
										18
									
								
								ed_lrr_gui/web/config.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ed_lrr_gui/web/config.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| import os | ||||
| 
 | ||||
| SECRET_KEY = "ED_LRR_WEBAPP" | ||||
| 
 | ||||
| SQLALCHEMY_DATABASE_URI = "sqlite:///ed_lrr_web_ui.db" | ||||
| SQLALCHEMY_TRACK_MODIFICATIONS = False | ||||
| 
 | ||||
| ROUTE_WORKERS = 0 | ||||
| 
 | ||||
| EXECUTOR_TYPE = "process" | ||||
| EXECUTOR_MAX_WORKERS = os.cpu_count()-1 | ||||
| EXECUTOR_FUTURES_MAX_LENGTH = 500 | ||||
| 
 | ||||
| FLASK_ADMIN_SWATCH = "Darkly" | ||||
| 
 | ||||
| DEBUG_TB_TEMPLATE_EDITOR_ENABLED = True | ||||
| 
 | ||||
| MAIL_DEFAULT_SENDER = '"ED_LRR Admin" <ed_lrr@gmail.com>' | ||||
							
								
								
									
										104
									
								
								ed_lrr_gui/web/forms.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								ed_lrr_gui/web/forms.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | |||
| from flask_wtf import FlaskForm | ||||
| from wtforms import ( | ||||
|     StringField, | ||||
|     PasswordField, | ||||
|     FieldList, | ||||
|     FloatField, | ||||
|     BooleanField, | ||||
|     SelectField, | ||||
|     SubmitField, | ||||
|     validators, | ||||
|     Field, | ||||
| ) | ||||
| from wtforms.widgets.html5 import NumberInput | ||||
| from wtforms.widgets import TextInput | ||||
| from wtforms.validators import ValidationError | ||||
| 
 | ||||
| class StringListField(Field): | ||||
|     widget = TextInput() | ||||
| 
 | ||||
|     def _value(self): | ||||
|         if self.data: | ||||
|             return u",".join(self.data) | ||||
|         else: | ||||
|             return u"" | ||||
| 
 | ||||
|     def process_formdata(self, valuelist): | ||||
|         if valuelist: | ||||
|             self.data = [x.strip() for x in valuelist[0].split(",")] | ||||
|         else: | ||||
|             self.data = [] | ||||
| 
 | ||||
| 
 | ||||
| class RouteForm(FlaskForm): | ||||
|     systems = StringListField("Systems", [validators.DataRequired()]) | ||||
|     jump_range = FloatField( | ||||
|         "Jump Range (Ly)", | ||||
|         [validators.DataRequired(), validators.NumberRange(0, None)], | ||||
|         widget=NumberInput(min=0, step=0.1), | ||||
|     ) | ||||
|     mode = SelectField( | ||||
|         "Routing Mode", | ||||
|         choices=[ | ||||
|             ("bfs", "Breadth-First Search"), | ||||
|             ("greedy", "Greedy Search"), | ||||
|             ("a-star", "A*-Search"), | ||||
|         ], | ||||
|     ) | ||||
|     permute = SelectField( | ||||
|         "Permutation Mode", | ||||
|         choices=[ | ||||
|             ("off", "Off"), | ||||
|             ("keep_first", "Keep starting system"), | ||||
|             ("keep_last", "Keep destination system"), | ||||
|             ("keep_both", "Keep both endpoints"), | ||||
|         ], | ||||
|     ) | ||||
|     primary = BooleanField("Only route through primary stars") | ||||
|     factor = FloatField( | ||||
|         "Greedyness for A*-Search (%)", | ||||
|         [validators.NumberRange(0, 100)], | ||||
|         default=50, | ||||
|         widget=NumberInput(min=0, max=100, step=1), | ||||
|     ) | ||||
|      | ||||
|     priority = FloatField( | ||||
|         "Priority (0=max, 100=min)", | ||||
|         [validators.NumberRange(0, 100)], | ||||
|         default=0, | ||||
|         widget=NumberInput(min=0, max=100, step=1), | ||||
|     ) | ||||
|     submit = SubmitField("GO!") | ||||
| 
 | ||||
| 
 | ||||
| class LoginForm(FlaskForm): | ||||
|     username = StringField("Username", [validators.Required()]) | ||||
|     password = PasswordField("Password", [validators.Required()]) | ||||
|     remember = BooleanField("Remember me") | ||||
|     submit = SubmitField("Login") | ||||
| 
 | ||||
| 
 | ||||
| class RegisterForm(FlaskForm): | ||||
|     username = StringField("Username", [validators.Required()]) | ||||
|     password = PasswordField( | ||||
|         "Password", | ||||
|         [ | ||||
|             validators.Required(), | ||||
|             validators.EqualTo("confirm", message="Passwords must match"), | ||||
|         ], | ||||
|     ) | ||||
|     confirm = PasswordField("Verify password", [validators.Required()]) | ||||
|     submit = SubmitField("Login") | ||||
| 
 | ||||
| 
 | ||||
| class ChangePasswordForm(FlaskForm): | ||||
|     old_password = PasswordField("Current Password", [validators.Required()]) | ||||
|     password = PasswordField( | ||||
|         "Password", | ||||
|         [ | ||||
|             validators.Required(), | ||||
|             validators.EqualTo("confirm", message="Passwords must match"), | ||||
|         ], | ||||
|     ) | ||||
|     confirm = PasswordField("Verify password", [validators.Required()]) | ||||
|     submit = SubmitField("Change") | ||||
							
								
								
									
										23
									
								
								ed_lrr_gui/web/static/theme.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ed_lrr_gui/web/static/theme.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| body,input,select,pre { | ||||
|     background-color: #222 !important; | ||||
|     color: #eee; | ||||
| } | ||||
| table { | ||||
|     line-height: 1; | ||||
| } | ||||
| .progress { | ||||
|     background-color: #444; | ||||
| } | ||||
| .progress-bar { | ||||
|     background-color: #f70; | ||||
| } | ||||
| 
 | ||||
| .form-control { | ||||
|     color: #eee !important; | ||||
| } | ||||
| 
 | ||||
| #graph { | ||||
|     border: 1px solid #eee; | ||||
|     width: 512px; | ||||
|     height: 512px; | ||||
| } | ||||
							
								
								
									
										5
									
								
								ed_lrr_gui/web/templates/admin/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								ed_lrr_gui/web/templates/admin/index.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| {% extends 'admin/master.html' %} | ||||
| 
 | ||||
| {% block body %} | ||||
|   <p>Hello world</p> | ||||
| {% endblock %} | ||||
							
								
								
									
										48
									
								
								ed_lrr_gui/web/templates/base.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								ed_lrr_gui/web/templates/base.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| {% extends "bootstrap/base.html" %} | ||||
| {% import "bootstrap/utils.html" as utils %} | ||||
| {% block title %}Elite: Dangerous Long Range Router{% endblock %} | ||||
| 
 | ||||
| {% block scrips %} | ||||
| 
 | ||||
| {% endblock %} | ||||
| 
 | ||||
| {% block styles %} | ||||
| {{super()}} | ||||
|     <link rel="stylesheet" href="{{url_for('static', filename='theme.css')}}"> | ||||
| {% endblock %} | ||||
| 
 | ||||
| {% block navbar %} | ||||
| <nav class="navbar navbar-expand-lg navbar-dark" style="background-color: #222;"> | ||||
|     <a class="navbar-brand" href="/">E:D LRR</a> | ||||
|     <ul class="navbar-nav mr-auto"> | ||||
|         {{nav.left_nav.render(renderer='bootstrap4')}} | ||||
|     </ul> | ||||
|      | ||||
|     <ul class="navbar-nav ml-auto"> | ||||
|         {% if current_user.is_authenticated %} | ||||
|         <li class="nav-item"> | ||||
|             <a class="nav-link"> | ||||
|                 Logged in as {{current_user.name}} | ||||
|             </a> | ||||
|         </li> | ||||
|         {% endif %} | ||||
|         {{nav.right_nav.render(renderer='bootstrap4')}} | ||||
|     </ul> | ||||
| </nav> | ||||
| 
 | ||||
| {% endblock %} | ||||
| 
 | ||||
| {% block content %} | ||||
|     <div class="container"> | ||||
|         {% with messages = get_flashed_messages(with_categories=true) %} | ||||
|             {% if messages %} | ||||
|                 {% for category,message in messages %} | ||||
|                 <div class="alert alert-{{category}}" role="{{category}}">{{ message }}</div> | ||||
|                 {% endfor %} | ||||
|             {% endif %} | ||||
|         {% endwith %} | ||||
| 
 | ||||
|         {# application content needs to be provided in the app_content block #} | ||||
|         {% block app_content %}{% endblock %} | ||||
|     </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										6
									
								
								ed_lrr_gui/web/templates/error/404.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ed_lrr_gui/web/templates/error/404.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% block app_content %} | ||||
|     <h1>404 Not Found</h1> | ||||
|     <p><a href="{{ url_for('index') }}"><button type="button" class="btn btn-secondary">Back</button></a></p> | ||||
| {% endblock %} | ||||
							
								
								
									
										16
									
								
								ed_lrr_gui/web/templates/form.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								ed_lrr_gui/web/templates/form.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| {% extends "base.html" %} | ||||
| {% import "bootstrap/wtf.html" as wtf %} | ||||
| 
 | ||||
| {% block app_content %} | ||||
|     <h1>{{title}}</h1> | ||||
|     {% for field in form %} | ||||
|         {% for error in field.errors %} | ||||
|             <div class="alert alert-danger" role="danger">{{error}}</div> | ||||
|         {% endfor %} | ||||
|     {% endfor %} | ||||
|     <div class="row"> | ||||
|         <div class="col-md-4"> | ||||
|             {{ wtf.quick_form(form) }} | ||||
|         </div> | ||||
|     </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										10
									
								
								ed_lrr_gui/web/templates/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								ed_lrr_gui/web/templates/index.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% block app_content %} | ||||
|     <h1>E:D LRR</h1> | ||||
|     <div class="row"> | ||||
|         <div class="col-md-4"> | ||||
|             Number of Jobs: {{current_user.jobs|count}} | ||||
|         </div> | ||||
|     </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										131
									
								
								ed_lrr_gui/web/templates/job.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								ed_lrr_gui/web/templates/job.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,131 @@ | |||
| {% extends "base.html" %} | ||||
| {% block app_content %} | ||||
| <h1>Job Status <span class="badge badge-{{job.status[0]}}">{{ job.status[1] }}</span></h1> | ||||
| <div class="row"> | ||||
|     <div class="col-lg-0"> | ||||
|         {% if job.state.error %} | ||||
|         <ul> | ||||
|             {% for err in job.state.error.args %} | ||||
|             <li>{{err}}</li> | ||||
|             {% endfor %} | ||||
|         </ul> | ||||
|         {% endif %} | ||||
| 
 | ||||
|         {% if job.state.progress %} | ||||
|         <p class="lead">Routing from <b>{{ job.state.progress.from }}</b> to <b>{{ job.state.progress.to }}</b> using | ||||
|             {{ job.state.progress.mode }}</p> | ||||
|         <p>Current system: <b>{{ job.state.progress.system }}</b></p> | ||||
|         <p>Search queue size: <b>{{"{:,}".format(job.state.progress.queue_size) }}</b></p> | ||||
|         <p>Number of systems checked: <b>{{"{:,}".format(job.state.progress.n_seen) }} | ||||
|                 ({{job.state.progress.prc_seen|round(2)}} %)</b></p> | ||||
|         <p>Estimated time remaining: <b>{{job.t_rem}}</b></p> | ||||
|         <p>Search Depth: <b>{{job.state.progress.depth}}</b></p> | ||||
|         <div class="progress" style="width: 100%;"> | ||||
|             <div class="progress-bar progress-bar-striped progress-bar-animated" | ||||
|                 style="width: {{job.state.progress.prc_done}}%;" role="progressbar" | ||||
|                 aria-valuenow="{{job.state.progress.prc_done|round(2)}}" aria-valuemin="0" aria-valuemax="100"> | ||||
|                 {{job.state.progress.prc_done|round(2)}} % | ||||
|             </div> | ||||
|         </div> | ||||
|         {% endif %} | ||||
| 
 | ||||
|         {% if job.state.result %} | ||||
|         <h2>Result</h2> | ||||
| 
 | ||||
|         <h3>Map</h3> | ||||
|         <div id="graph"> | ||||
|         </div> | ||||
|         <script src="https://d3js.org/d3.v5.min.js"></script> | ||||
|         <script type="text/javascript"> | ||||
|             function dist(a, b) { | ||||
|                 var sum = 0; | ||||
|                 for (var i = 0; i < a.length; ++i) { | ||||
|                     sum += Math.pow(a[i] - b[i], 2) | ||||
|                 } | ||||
|                 return Math.pow(sum, 0.5); | ||||
|             } | ||||
|             var width = 512; | ||||
|             var height = 512; | ||||
|             var route = {{job.route | tojson}}; | ||||
|             var vis = d3.select("#graph") | ||||
|                 .append("svg").attr("viewBox", [0, 0, width, height]); | ||||
| 
 | ||||
|             vis.attr("width", width) | ||||
|                 .attr("height", height); | ||||
|             var g = vis.append("g"); | ||||
| 
 | ||||
|             vis.call(d3.zoom() | ||||
|                 .extent([ | ||||
|                     [0, 0], | ||||
|                     [width, height] | ||||
|                 ]) | ||||
|                 .on("zoom", () => { | ||||
|                     g.attr("transform", d3.event.transform); | ||||
|                 })); | ||||
| 
 | ||||
|             var lines = []; | ||||
|             for (var i = 0; i < route.length - 1; ++i) { | ||||
|                 lines.push({ | ||||
|                     x1: route[i].pos[1], | ||||
|                     x2: route[i + 1].pos[1], | ||||
|                     y1: -route[i].pos[2], | ||||
|                     y2: -route[i + 1].pos[2], | ||||
|                     dist: dist(route[i].pos, route[i + 1].pos), | ||||
|                     color: route[i].color || '#eee' | ||||
|                 }) | ||||
|             } | ||||
| 
 | ||||
|             g.selectAll(".line") | ||||
|                 .data(lines) | ||||
|                 .enter() | ||||
|                 .append("line") | ||||
|                 .attr("x1", (l) => l.x1) | ||||
|                 .attr("y1", (l) => l.y1) | ||||
|                 .attr("x2", (l) => l.x2) | ||||
|                 .attr("y2", (l) => l.y2) | ||||
|                 .style("stroke", (l) => l.color) | ||||
|                 .style("stroke-width", 5) | ||||
|                 .append("title") | ||||
|                 .text((l) => Math.round(l.dist * 100) / 100 + " Ly"); | ||||
| 
 | ||||
|             g.selectAll("circle .nodes") | ||||
|                 .data(route) | ||||
|                 .enter() | ||||
|                 .append("svg:circle") | ||||
|                 .attr("class", "nodes") | ||||
|                 .attr("cx", (d) => d.pos[1]) | ||||
|                 .attr("cy", (d) => -d.pos[2]) | ||||
|                 .attr("r", 10) | ||||
|                 .attr("fill", (d) => d.color) | ||||
|                 .append("title") | ||||
|                 .text((d) => d.body + " (" + d.star_type + ")") | ||||
|         </script> | ||||
|         <h3>Jumps</h3> | ||||
|         <table class="table table-striped table-bordered"> | ||||
|             <thead> | ||||
|                 <tr> | ||||
|                     <th scope="col">Num</th> | ||||
|                     <th scope="col">Body</th> | ||||
|                     <th scope="col">Type</th> | ||||
|                     <th scope="col">Distance from arrival</th> | ||||
|                     <th scope="col">Jump distance</th> | ||||
|                 </tr> | ||||
|             </thead> | ||||
|             {% for sys in job.route %} | ||||
|             <tr> | ||||
|                 <th scope="row">{{sys.num}}</td> | ||||
|                 <td>{{sys.body}}</td> | ||||
|                 <td style="color: {{sys.color}}">{{sys.star_type}}</td> | ||||
|                 <td>{{"{:,}".format(sys.distance)}} Ls</td> | ||||
|                 <td>{{"{:,}".format(sys.jump_dist|round(2))}} Ly</td> | ||||
|             </tr> | ||||
|             {% endfor %} | ||||
|             <tr> | ||||
|                 <th scope="row" colspan=4>Total Distance</th> | ||||
|                 <td>{{"{:,}".format(job.route|sum(attribute='jump_dist')|round(2))}} Ly</td> | ||||
|             </tr> | ||||
|         </table> | ||||
|         {% endif %} | ||||
|     </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										87
									
								
								ed_lrr_gui/web/templates/status.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								ed_lrr_gui/web/templates/status.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% block app_content %} | ||||
|     {% if current_user.has_role('admin') %} | ||||
|     {% set jobs = Job.query.all() %} | ||||
|     {% else %} | ||||
|     {% set jobs = current_user.jobs %} | ||||
|     {% endif %} | ||||
|     <h1>System Status</h1> | ||||
|     <div class="row"> | ||||
|         <h2>Overview</h2> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|         <table class="table table-striped table-bordered" style="width: 1px;"> | ||||
|             <tr> | ||||
|                 <th>Status</th> | ||||
|                 <th>Count</th> | ||||
|             </tr> | ||||
|             {% for group in (jobs|groupby('status')) %} | ||||
|                 <tr> | ||||
|                     <td> | ||||
|                         <a href="?state={{group.grouper[1]}}"> | ||||
|                             <span class="badge badge-{{ group.grouper[0] }}">{{ group.grouper[1] }}</span> | ||||
|                         </a> | ||||
|                     </td> | ||||
|                     <td> | ||||
|                         {{group.list|count}} | ||||
|                     </td> | ||||
|                 <tr> | ||||
|             {% endfor %} | ||||
|             <tr> | ||||
|                 <td> | ||||
|                     <a href="?"> | ||||
|                         Total | ||||
|                     </a> | ||||
|                 </td> | ||||
|                 <td> | ||||
|                     {{jobs|count}} | ||||
|                 </td> | ||||
|             </tr> | ||||
|         </table> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|         <h2>Jobs</h2> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|         <!-- Next: {{Job.next().id}} --> | ||||
|         <table class="table table-striped table-bordered" style="width: 100%;"> | ||||
|             <thead> | ||||
|             <tr> | ||||
|                 <th scope="col">ID</th> | ||||
|                 <th scope="col">Systems</th> | ||||
|                 <th scope="col">Status</th> | ||||
|                 <th scope="col">User</th> | ||||
|                 <th scope="col">Priority</th> | ||||
|                 <th scope="col">Progess</th> | ||||
|                 <th scope="col">ETC</th> | ||||
|                 <th scope="col">Created</th> | ||||
|             </tr> | ||||
|             </thead> | ||||
|             {% for job in (jobs|sort(attribute='sort_key')) %} | ||||
|             {% if (state==None) or job.status[1]==state %} | ||||
|             <tr> | ||||
|                 <td style="width: 1px; white-space: nowrap;"><a href="{{url_for('status',job_id=job.id)}}">{{job.id}}</a></td> | ||||
|                 <td style="width: 1px; white-space: nowrap;">{{job.args[0]|join(', ')}}</td> | ||||
|                 <td style="width: 1px; white-space: nowrap;"><span class="badge badge-{{job.status[0]}}">{{ job.status[1] }}</span></td> | ||||
|                 <td style="width: 1px; white-space: nowrap;">{{job.user.name}}</td> | ||||
|                 <td style="width: 1px; white-space: nowrap;">{{job.priority}}</td> | ||||
|                 {% if job.state.progress %} | ||||
|                 <td> | ||||
|                     <div class="progress" style="width: 100%;"> | ||||
|                         <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: {{job.state.progress.prc_done}}%;" role="progressbar" aria-valuenow="{{job.state.progress.prc_done|round(2)}}" aria-valuemin="0" aria-valuemax="100"> | ||||
|                             {{job.state.progress.prc_done|round(2)}} % | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </td> | ||||
|                 {% else %} | ||||
|                 <td>Unknown</td> | ||||
|                 {% endif %} | ||||
|                 <td style="width: 1px; white-space: nowrap;">{{job.t_rem}}</td> | ||||
|                 <td style="width: 1px; white-space: nowrap;">{{job.age}} ago</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% endfor %} | ||||
|         </table> | ||||
|     </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										14
									
								
								ed_lrr_gui/web/templates/workers.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								ed_lrr_gui/web/templates/workers.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| {% extends "base.html" %} | ||||
| 
 | ||||
| {% block app_content %} | ||||
|     <h1>Workers</h1> | ||||
|     <div class="row"> | ||||
|         <div class="col-md-4"> | ||||
|             {% if current_user.is_authenticated %} | ||||
|                 Hello {{current_user.name}}! | ||||
|             {% else %} | ||||
|                 Nothing to see here! | ||||
|             {% endif %} | ||||
|         </div> | ||||
|     </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										73
									
								
								ed_lrr_gui/web/utils.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								ed_lrr_gui/web/utils.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | |||
| from flask_nav.renderers import Renderer | ||||
| from dominate import tags | ||||
| from urllib.parse import urlparse,urljoin | ||||
| from flask import request | ||||
| 
 | ||||
| def is_safe_url(target): | ||||
|     ref_url = urlparse(request.host_url) | ||||
|     test_url = urlparse(urljoin(request.host_url, target)) | ||||
|     return test_url.scheme in ('http', 'https') and \ | ||||
|            ref_url.netloc == test_url.netloc | ||||
| 
 | ||||
| def dist(p1, p2): | ||||
|     s = 0 | ||||
|     for c1, c2 in zip(p1, p2): | ||||
|         s += (c1 - c2) ** 2 | ||||
|     return s ** 0.5 | ||||
| 
 | ||||
| 
 | ||||
| class BootsrapRenderer(Renderer): | ||||
|     def visit_Navbar(self, node): | ||||
|         sub = [] | ||||
|         for item in node.items: | ||||
|             sub.append(self.visit(item)) | ||||
|         return "".join([v.render() for v in sub]) | ||||
| 
 | ||||
|     def visit_View(self, node): | ||||
|         classes = ["nav-link"] | ||||
|         if node.active: | ||||
|             classes.append("active") | ||||
|         return tags.li( | ||||
|             tags.a(node.text, href=node.get_url(), cls=" ".join(classes)), | ||||
|             cls="nav-item", | ||||
|         ) | ||||
| 
 | ||||
|     def visit_Subgroup(self, node): | ||||
|         # almost the same as visit_Navbar, but written a bit more concise | ||||
|         return tags.div(node.title, *[self.visit(item) for item in node.items]) | ||||
| 
 | ||||
| 
 | ||||
| colors = { | ||||
|     "O": "#0000FF", | ||||
|     "B": "#140AF0", | ||||
|     "A": "#3C1EDC", | ||||
|     "F": "#EEEEEE", | ||||
|     "G": "#969646", | ||||
|     "K": "#B43C1E", | ||||
|     "M": "#FF280A", | ||||
|     "L": "#FF1E00", | ||||
|     "T": "#800000", | ||||
|     "Y": "#800000", | ||||
|     "White Dwarf": "#5D67EF", | ||||
|     "Neutron": "#99A0FF", | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def prepare_route(route): | ||||
|     entries = [] | ||||
|     prev = route[0] | ||||
|     num = 1 | ||||
|     for hop in route[1:]: | ||||
|         prev["jump_dist"] = dist(hop["pos"], prev["pos"]) | ||||
|         prev["num"] = num | ||||
|         prev["color"] = colors.get(prev["star_type"].split()[0], "#eee") | ||||
|         prev["distance"] = prev["distance"] | ||||
|         entries.append(prev) | ||||
|         prev = hop | ||||
|         num += 1 | ||||
|     prev["jump_dist"] = 0 | ||||
|     prev["distance"] = prev["distance"] | ||||
|     prev["num"] = num | ||||
|     prev["color"] = colors.get(prev["star_type"].split()[0], "#eee") | ||||
|     entries.append(prev) | ||||
|     return entries | ||||
							
								
								
									
										6
									
								
								ed_lrr_gui/web/worker.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ed_lrr_gui/web/worker.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| import requests as RQ | ||||
| import _ed_lrr as ed_lrr | ||||
| 
 | ||||
| funcs = { | ||||
|     func: getattr(ed_lrr, func) for func in dir(ed_lrr) if not func.startswith("_") | ||||
| } | ||||
							
								
								
									
										68
									
								
								rust/src/galaxy.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								rust/src/galaxy.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| use serde::Deserialize; | ||||
| use serde_json::Result; | ||||
| use serde_json; | ||||
| use std::fs::File; | ||||
| use std::io::Seek; | ||||
| use std::io::{BufRead, BufReader, BufWriter, SeekFrom}; | ||||
| use std::path::PathBuf; | ||||
| use std::str; | ||||
| use std::time::Instant; | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct Coords { | ||||
|     x: f32, | ||||
|     y: f32, | ||||
|     z: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct Body { | ||||
|     name: String, | ||||
|     #[serde(rename = "type")] | ||||
|     body_type: String, | ||||
|     subType: String, | ||||
|     #[serde(rename = "distanceToArrival")] | ||||
|     distance: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct System { | ||||
|     coords: Coords, | ||||
|     name: String, | ||||
|     bodies: Vec<Body>, | ||||
| } | ||||
| 
 | ||||
| fn main() -> std::io::Result<()> { | ||||
|     better_panic::install(); | ||||
|     let mut buffer = String::new(); | ||||
|     let mut bz2_reader = std::process::Command::new("bzip2").args( | ||||
|         &["-d","-c",r#"E:\EDSM\galaxy.json.bz2"#] | ||||
|     ).stdout(std::process::Stdio::piped()) | ||||
|     .spawn() | ||||
|     .expect("Failed to spawn execute bzip2!"); | ||||
|     let mut reader = BufReader::new(bz2_reader.stdout.as_mut().expect("Failed to open stdout of child process")); | ||||
|     let mut count = 0; | ||||
|     while let Ok(n) = reader.read_line(&mut buffer) { | ||||
|         if n==0 { | ||||
|             break; | ||||
|         } | ||||
|         buffer = buffer | ||||
|             .trim() | ||||
|             .trim_end_matches(|c| c == ',') | ||||
|             .trim() | ||||
|             .to_string(); | ||||
|         if let Ok(sys) = serde_json::from_str::<System>(&buffer) { | ||||
|             for b in &sys.bodies { | ||||
|                 if b.body_type == "Star" { | ||||
|                     count += 1; | ||||
|                     if (count % 100_000) == 0 { | ||||
|                         println!("{}: {:?}", count, b); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         buffer.clear(); | ||||
|     } | ||||
|     println!("Total: {}", count); | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										24
									
								
								tests/test_ed_lrr.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								tests/test_ed_lrr.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| import pytest | ||||
| 
 | ||||
| stars_csv = "D:\\devel\\rust\\ED_LRR\\stars.csv" | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.dependency() | ||||
| def test_import(): | ||||
|     import _ed_lrr | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.dependency(depends=["test_import"]) | ||||
| def test_search_works(): | ||||
|     import _ed_lrr | ||||
| 
 | ||||
|     system_names = ["Ix", "Sol", "Colonia", "Sagittarius A*"] | ||||
|     systems = _ed_lrr.find_sys(system_names, stars_csv) | ||||
|     print(systems) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.dependency(depends=["test_import"]) | ||||
| def test_zero_range_fails(): | ||||
|     import _ed_lrr | ||||
| 
 | ||||
|     # _ed_lrr.route() | ||||
							
								
								
									
										18
									
								
								tests/test_gui.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								tests/test_gui.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| import pytest | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.dependency() | ||||
| def test_import(): | ||||
|     import ed_lrr_gui | ||||
|     from ed_lrr_gui.main import main | ||||
|     import ed_lrr_gui.gui as ED_LRR_GUI | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.dependency(depends=["test_import"]) | ||||
| def test_search_works(): | ||||
|     import ed_lrr_gui | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.dependency(depends=["test_import"]) | ||||
| def test_zero_range_fails(): | ||||
|     import ed_lrr_gui | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue