From 7a3fed19608c3d3c8477cb4e3780ab02e6849dfb Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Wed, 7 Dec 2022 12:30:44 -0800 Subject: [PATCH 01/15] Add /api/ to all rest call paths --- dashboard.py | 2 +- lib/job_server.py | 32 ++++++++++++++++---------------- lib/render_queue.py | 4 ++-- scheduler_gui.py | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/dashboard.py b/dashboard.py index 311230d..0b05320 100755 --- a/dashboard.py +++ b/dashboard.py @@ -202,7 +202,7 @@ class RenderServerProxy: def request_data(self, payload, timeout=5): try: - req = requests.get(f'http://{self.hostname}:{self.port}/{payload}', timeout=timeout) + req = requests.get(f'http://{self.hostname}:{self.port}/api/{payload}', timeout=timeout) if req.ok: return req.json() except Exception as e: diff --git a/lib/job_server.py b/lib/job_server.py index 3bf7f25..ed42cdf 100755 --- a/lib/job_server.py +++ b/lib/job_server.py @@ -20,12 +20,12 @@ logger = logging.getLogger() server = Flask(__name__) -@server.get('/jobs') +@server.get('/api/jobs') def jobs_json(): return [x.json_safe_copy() for x in RenderQueue.job_queue] -@server.get('/jobs/') +@server.get('/api/jobs/') def filtered_jobs_json(status_val): state = string_to_status(status_val) jobs = [x.json_safe_copy() for x in RenderQueue.jobs_with_status(state)] @@ -35,7 +35,7 @@ def filtered_jobs_json(status_val): return f'Cannot find jobs with status {status_val}', 400 -@server.get('/job_status/') +@server.get('/api/job_status/') def get_job_status(job_id): found_job = RenderQueue.job_with_id(job_id) if found_job: @@ -44,7 +44,7 @@ def get_job_status(job_id): return f'Cannot find job with ID {job_id}', 400 -@server.get('/file_list/') +@server.get('/api/file_list/') def get_file_list(job_id): found_job = RenderQueue.job_with_id(job_id) if found_job: @@ -54,7 +54,7 @@ def get_file_list(job_id): return f'Cannot find job with ID {job_id}', 400 -@server.route('/download_all/') +@server.route('/api/download_all/') def download_all(job_id): zip_filename = None @@ -81,26 +81,26 @@ def download_all(job_id): return f'Cannot find job with ID {job_id}', 400 -@server.post('/register_client') +@server.post('/api/register_client') def register_client(): client_hostname = request.values['hostname'] x = RenderQueue.register_client(client_hostname) return "Success" if x else "Fail" -@server.post('/unregister_client') +@server.post('/api/unregister_client') def unregister_client(): client_hostname = request.values['hostname'] x = RenderQueue.unregister_client(client_hostname) return "Success" if x else "Fail" -@server.get('/clients') +@server.get('/api/clients') def render_clients(): return RenderQueue.render_clients -@server.get('/full_status') +@server.get('/api/full_status') def full_status(): full_results = {'timestamp': datetime.now().isoformat(), 'servers': {}} @@ -127,7 +127,7 @@ def full_status(): return full_results -@server.get('/snapshot') +@server.get('/api/snapshot') def snapshot(): server_status = RenderQueue.status() server_jobs = [x.json_safe_copy() for x in RenderQueue.job_queue] @@ -135,7 +135,7 @@ def snapshot(): return server_data -@server.post('/add_job') +@server.post('/api/add_job') def add_job_handler(): try: @@ -259,7 +259,7 @@ def add_job(job_params, remove_job_dir_on_failure=False): return {'error': err_msg, 'code': 400} -@server.get('/cancel_job') +@server.get('/api/cancel_job') def cancel_job(): job_id = request.args.get('id', None) confirm = request.args.get('confirm', False) @@ -277,18 +277,18 @@ def cancel_job(): return 'job not found', 400 -@server.get('/clear_history') +@server.get('/api/clear_history') def clear_history(): RenderQueue.clear_history() return 'success' -@server.route('/status') +@server.route('/api/status') def status(): return RenderQueue.status() -@server.get('/renderer_info') +@server.get('/api/renderer_info') def renderer_info(): renderer_data = {} for r in RenderWorkerFactory.supported_renderers(): @@ -317,5 +317,5 @@ def post_job_to_server(input_path, job_list, client, server_port=8080): job_files = {'file': (os.path.basename(input_path), open(input_path, 'rb'), 'application/octet-stream'), 'json': (None, json.dumps(job_list), 'application/json')} - req = requests.post(f'http://{client}:{server_port}/add_job', files=job_files) + req = requests.post(f'http://{client}:{server_port}/api/add_job', files=job_files) return req diff --git a/lib/render_queue.py b/lib/render_queue.py index 8362adb..75c7abd 100755 --- a/lib/render_queue.py +++ b/lib/render_queue.py @@ -213,7 +213,7 @@ class RenderQueue: err_msg = f"Client '{hostname}' already registered" else: try: - response = requests.get(f"http://{hostname}:8080/status", timeout=3) + response = requests.get(f"http://{hostname}:8080/api/status", timeout=3) if response.ok: cls.render_clients.append(hostname) logger.info(f"Client '{hostname}' successfully registered") @@ -241,7 +241,7 @@ class RenderQueue: @staticmethod def is_client_available(client_hostname, timeout=3): try: - response = requests.get(f"http://{client_hostname}:8080/status", timeout=timeout) + response = requests.get(f"http://{client_hostname}:8080/api/status", timeout=timeout) if response.ok: return True except requests.ConnectionError as e: diff --git a/scheduler_gui.py b/scheduler_gui.py index b6f00e9..f5c8e99 100755 --- a/scheduler_gui.py +++ b/scheduler_gui.py @@ -24,7 +24,7 @@ server_setup_timeout = 5 def request_data(server_ip, payload, server_port=8080, timeout=2): try: - req = requests.get(f'http://{server_ip}:{server_port}/{payload}', timeout=timeout) + req = requests.get(f'http://{server_ip}:{server_port}/api/{payload}', timeout=timeout) if req.ok: return req.json() except Exception as e: From 89b7cb38f71a23dbdedd39a38cfcf3a99ff76343 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Wed, 7 Dec 2022 13:45:15 -0800 Subject: [PATCH 02/15] Fix some issues around adding jobs through local rest call --- lib/job_server.py | 19 +++++++++---------- lib/render_job.py | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/job_server.py b/lib/job_server.py index ed42cdf..ed61ab4 100755 --- a/lib/job_server.py +++ b/lib/job_server.py @@ -140,33 +140,32 @@ def add_job_handler(): try: """Create new job and add to server render queue""" - json_string = request.form.get('json', None) - uploaded_file = request.files.get('file', None) - if not json_string: + if not request.form.get('json', None) and not request.is_json: return 'missing json data', 400 # handle uploaded files + uploaded_file = request.files.get('file', None) uploaded_file_local_path = None job_dir = None if uploaded_file and uploaded_file.filename: logger.info(f"Receiving uploaded file {uploaded_file.filename}") - new_id = RenderJob.generate_id() - job_dir = os.path.join(server.config['UPLOAD_FOLDER'], new_id + "-" + uploaded_file.filename) - if not os.path.exists(job_dir): - os.makedirs(job_dir) + job_dir = os.path.join(server.config['UPLOAD_FOLDER'], (uploaded_file.filename + "_" + + datetime.now().strftime("%Y.%m.%d_%H.%M.%S"))) + os.makedirs(job_dir, exist_ok=True) uploaded_file_local_path = os.path.join(job_dir, secure_filename(uploaded_file.filename)) uploaded_file.save(uploaded_file_local_path) # convert job input paths for uploaded files and add jobs - jobs_list = json.loads(json_string) + json_string = request.form.get('json', None) + jobs_list = json.loads(json_string) if json_string else [request.json] results = [] for job in jobs_list: if uploaded_file_local_path: job['input_path'] = uploaded_file_local_path - output_dir = os.path.join(job_dir, job.get('name', 'output')) + output_dir = os.path.join(job_dir, job.get('name', None) or 'output') os.makedirs(output_dir, exist_ok=True) job['output_path'] = os.path.join(output_dir, os.path.basename(job['output_path'])) - remove_job_dir = len(jobs_list) == 1 # remove failed job dir for single file uploads only + remove_job_dir = len(jobs_list) == 1 and uploaded_file_local_path # remove failed job dir for single file uploads only add_result = add_job(job, remove_job_dir_on_failure=remove_job_dir) results.append(add_result) diff --git a/lib/render_job.py b/lib/render_job.py index 640af8e..6a366d7 100644 --- a/lib/render_job.py +++ b/lib/render_job.py @@ -22,7 +22,7 @@ class RenderJob: self.date_created = datetime.now() self.scheduled_start = None self.renderer = renderer - self.name = name or os.path.basename(input_path) + '_' + self.date_created.isoformat() + self.name = name or os.path.basename(input_path) + '_' + self.date_created.strftime("%Y.%m.%d_%H.%M.%S") self.worker = RenderWorkerFactory.create_worker(renderer, input_path, output_path, args) self.worker.log_path = os.path.join(os.path.dirname(input_path), self.name + '.log') @@ -45,7 +45,7 @@ class RenderJob: try: job_dict = self.__dict__.copy() job_dict['status'] = self.render_status().value - job_dict['file_hash'] = self.file_hash if isinstance(self.file_hash, str) else self.file_hash() + job_dict['file_hash'] = self.file_hash if self.file_hash and isinstance(self.file_hash, str) else self.file_hash() job_dict['worker'] = self.worker.__dict__.copy() job_dict['worker']['status'] = job_dict['status'] From cc394932a1fac7ec7c0918bd960e3f434dbc7812 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Wed, 7 Dec 2022 14:39:50 -0800 Subject: [PATCH 03/15] Add rest call to get job logs --- lib/job_server.py | 16 +++++++++++++++- lib/render_job.py | 16 ++++++++++++++++ scheduler_gui.py | 11 ++++++----- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/lib/job_server.py b/lib/job_server.py index ed61ab4..a59cb6b 100755 --- a/lib/job_server.py +++ b/lib/job_server.py @@ -9,7 +9,7 @@ from datetime import datetime from zipfile import ZipFile import requests -from flask import Flask, request, render_template, send_file, after_this_request +from flask import Flask, request, render_template, send_file, after_this_request, Response from werkzeug.utils import secure_filename from lib.render_job import RenderJob @@ -44,6 +44,20 @@ def get_job_status(job_id): return f'Cannot find job with ID {job_id}', 400 +@server.get('/api/job//logs') +def get_job_logs(job_id): + found_job = RenderQueue.job_with_id(job_id) + if found_job: + log_path = found_job.worker.log_path + log_data = None + if log_path and os.path.exists(log_path): + with open(log_path) as file: + log_data = file.read() + return Response(log_data, mimetype='text/plain') + else: + return f'Cannot find job with ID {job_id}', 400 + + @server.get('/api/file_list/') def get_file_list(job_id): found_job = RenderQueue.job_with_id(job_id) diff --git a/lib/render_job.py b/lib/render_job.py index 6a366d7..0270b2e 100644 --- a/lib/render_job.py +++ b/lib/render_job.py @@ -81,6 +81,22 @@ class RenderJob: def stop(self): self.worker.stop() + def time_elapsed(self): + # calculate elapsed time + elapsed_time = 'Unknown' + start_time = self.worker.start_time + end_time = self.worker.end_time + + if start_time: + if end_time: + elapsed_time = str(end_time - start_time) + elif self.render_status() == RenderStatus.RUNNING: + elapsed_time = str(datetime.now() - start_time) + return elapsed_time + + def frame_count(self): + return self.worker.total_frames + @classmethod def generate_id(cls): return str(uuid.uuid4()).split('-')[0] \ No newline at end of file diff --git a/scheduler_gui.py b/scheduler_gui.py index f5c8e99..080f302 100755 --- a/scheduler_gui.py +++ b/scheduler_gui.py @@ -334,11 +334,12 @@ class ScheduleJob(Frame): # multiple camera rendering selected_cameras = self.blender_cameras_list.getCheckedItems() if self.blender_cameras_list else None - for cam in selected_cameras: - job_copy = copy.deepcopy(job_json) - job_copy['args']['camera'] = cam.split('-')[0].strip() - job_copy['name'] = pathlib.Path(input_path).stem.replace(' ', '_') + "-" + cam.replace(' ', '') - job_list.append(job_copy) + if selected_cameras: + for cam in selected_cameras: + job_copy = copy.deepcopy(job_json) + job_copy['args']['camera'] = cam.split('-')[0].strip() + job_copy['name'] = pathlib.Path(input_path).stem.replace(' ', '_') + "-" + cam.replace(' ', '') + job_list.append(job_copy) # Submit to server job_list = job_list or [job_json] From fbaf2e3661077420fe14b153baf445e5a72dec40 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Wed, 7 Dec 2022 18:24:02 -0800 Subject: [PATCH 04/15] Initial commit of index html and add ability to delete jobs --- lib/job_server.py | 103 ++++++++++++++++++++++----------- lib/render_job.py | 10 +++- lib/render_queue.py | 13 ++++- requirements.txt | 3 +- start_server.py | 2 +- templates/details.html | 32 +++++++++++ templates/index.html | 127 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 252 insertions(+), 38 deletions(-) create mode 100644 templates/details.html create mode 100644 templates/index.html diff --git a/lib/job_server.py b/lib/job_server.py index a59cb6b..c6a6d4e 100755 --- a/lib/job_server.py +++ b/lib/job_server.py @@ -5,11 +5,12 @@ import logging import os import pathlib import shutil +import json2html from datetime import datetime from zipfile import ZipFile import requests -from flask import Flask, request, render_template, send_file, after_this_request, Response +from flask import Flask, request, render_template, send_file, after_this_request, Response, redirect, url_for from werkzeug.utils import secure_filename from lib.render_job import RenderJob @@ -17,18 +18,33 @@ from lib.render_queue import RenderQueue from utilities.render_worker import RenderWorkerFactory, string_to_status logger = logging.getLogger() -server = Flask(__name__) +server = Flask(__name__, template_folder='../templates') + + +@server.route('/') +@server.route('/index') +def index(): + return render_template('index.html', all_jobs=RenderQueue.job_queue, hostname=RenderQueue.host_name) + + +@server.route('/ui/job//full_details') +def job_detail(job_id): + found_job = RenderQueue.job_with_id(job_id) + if found_job: + table_html = json2html.json2html.convert(json=found_job.json()) + return render_template('details.html', detail_table=table_html) + return f'Cannot find job with ID {job_id}', 400 @server.get('/api/jobs') def jobs_json(): - return [x.json_safe_copy() for x in RenderQueue.job_queue] + return [x.json() for x in RenderQueue.job_queue] @server.get('/api/jobs/') def filtered_jobs_json(status_val): state = string_to_status(status_val) - jobs = [x.json_safe_copy() for x in RenderQueue.jobs_with_status(state)] + jobs = [x.json() for x in RenderQueue.jobs_with_status(state)] if jobs: return jobs else: @@ -39,7 +55,7 @@ def filtered_jobs_json(status_val): def get_job_status(job_id): found_job = RenderQueue.job_with_id(job_id) if found_job: - return found_job.json_safe_copy() + return found_job.json() else: return f'Cannot find job with ID {job_id}', 400 @@ -58,17 +74,16 @@ def get_job_logs(job_id): return f'Cannot find job with ID {job_id}', 400 -@server.get('/api/file_list/') +@server.get('/api/job//file_list') def get_file_list(job_id): found_job = RenderQueue.job_with_id(job_id) if found_job: - job_dir = os.path.dirname(found_job.worker.output_path) - return os.listdir(job_dir) + return '\n'.join(found_job.file_list()) else: return f'Cannot find job with ID {job_id}', 400 -@server.route('/api/download_all/') +@server.route('/api/job//download_all') def download_all(job_id): zip_filename = None @@ -83,7 +98,7 @@ def download_all(job_id): if found_job: output_dir = os.path.dirname(found_job.worker.output_path) if os.path.exists(output_dir): - zip_filename = pathlib.Path(found_job.worker.input_path).stem + '.zip' + zip_filename = os.path.join('/tmp', pathlib.Path(found_job.worker.input_path).stem + '.zip') with ZipFile(zip_filename, 'w') as zipObj: for f in os.listdir(output_dir): zipObj.write(filename=os.path.join(output_dir, f), @@ -144,7 +159,7 @@ def full_status(): @server.get('/api/snapshot') def snapshot(): server_status = RenderQueue.status() - server_jobs = [x.json_safe_copy() for x in RenderQueue.job_queue] + server_jobs = [x.json() for x in RenderQueue.job_queue] server_data = {'status': server_status, 'jobs': server_jobs, 'timestamp': datetime.now().isoformat()} return server_data @@ -229,7 +244,7 @@ def add_job(job_params, remove_job_dir_on_failure=False): render_job = RenderJob(renderer, input_path, output_path, args, priority, job_owner, client, notify=False, custom_id=custom_id, name=name) RenderQueue.add_to_render_queue(render_job, force_start=force_start) - return render_job.json_safe_copy() + return render_job.json() except Exception as e: err_msg = f"Error creating job: {str(e)}" logger.exception(err_msg) @@ -272,22 +287,51 @@ def add_job(job_params, remove_job_dir_on_failure=False): return {'error': err_msg, 'code': 400} -@server.get('/api/cancel_job') -def cancel_job(): - job_id = request.args.get('id', None) - confirm = request.args.get('confirm', False) - if not job_id: - return 'job id not found', 400 - elif not confirm: - return 'confirmation required', 400 +@server.get('/api/job//cancel') +def cancel_job(job_id): + found_job = RenderQueue.job_with_id(job_id) + if not found_job: + return f'Cannot find job with ID {job_id}', 400 + elif not request.args.get('confirm', False): + return 'Confirmation required to cancel job', 400 + + if RenderQueue.cancel_job(found_job): + return redirect(url_for('index')) else: - found = [x for x in RenderQueue.job_queue if x.id == job_id] - if len(found) > 1: - return f'multiple jobs found for ID {job_id}', 400 - elif found: - success = RenderQueue.cancel_job(found[0]) - return success - return 'job not found', 400 + return "Unknown error", 500 + + +@server.route('/api/job//delete', methods=['POST', 'GET']) +def delete_job(job_id): + try: + found_job = RenderQueue.job_with_id(job_id) + if not found_job: + return f'Cannot find job with ID {job_id}', 400 + elif not request.args.get('confirm', False): + return 'Confirmation required to delete job', 400 + + # First, remove all render files and logs + files_to_delete = found_job.file_list() + files_to_delete.append(found_job.log_path()) + for d in files_to_delete: + if os.path.exists(d): + os.remove(d) + + # Check if we can remove the 'output' directory + output_dir = os.path.dirname(files_to_delete[0]) + if server.config['UPLOAD_FOLDER'] in output_dir and os.path.exists(output_dir): + shutil.rmtree(output_dir) + + # See if we own the input file (i.e. was it uploaded) + input_dir = os.path.dirname(found_job.worker.input_path) + if server.config['UPLOAD_FOLDER'] in input_dir and os.path.exists(input_dir): + shutil.rmtree(input_dir) + + RenderQueue.delete_job(found_job) + return redirect(url_for('index')) + + except Exception as e: + return "Unknown error", 500 @server.get('/api/clear_history') @@ -313,11 +357,6 @@ def renderer_info(): return renderer_data -@server.route('/') -def default(): - return "Server running" - - @server.route('/upload') def upload_file_page(): return render_template('upload.html', render_clients=RenderQueue.render_clients, diff --git a/lib/render_job.py b/lib/render_job.py index 0270b2e..e037bc1 100644 --- a/lib/render_job.py +++ b/lib/render_job.py @@ -1,3 +1,4 @@ +import glob import hashlib import json import logging @@ -38,7 +39,7 @@ class RenderJob: return hashlib.md5(open(self.worker.input_path, 'rb').read()).hexdigest() return None - def json_safe_copy(self): + def json(self): """Converts RenderJob into JSON-friendly dict""" import numbers job_dict = None @@ -97,6 +98,13 @@ class RenderJob: def frame_count(self): return self.worker.total_frames + def file_list(self): + job_dir = os.path.dirname(self.worker.output_path) + return glob.glob(os.path.join(job_dir, '*')) + + def log_path(self): + return self.worker.log_path + @classmethod def generate_id(cls): return str(uuid.uuid4()).split('-')[0] \ No newline at end of file diff --git a/lib/render_queue.py b/lib/render_queue.py index 75c7abd..d5cf502 100755 --- a/lib/render_queue.py +++ b/lib/render_queue.py @@ -126,7 +126,7 @@ class RenderQueue: try: logger.debug("Saving Render History") output = {'timestamp': datetime.now().isoformat(), - 'jobs': [j.json_safe_copy() for j in cls.job_queue], + 'jobs': [j.json() for j in cls.job_queue], 'clients': cls.render_clients} output_path = json_path or JSON_FILE with open(output_path, 'w') as f: @@ -167,9 +167,16 @@ class RenderQueue: @classmethod def cancel_job(cls, job): - logger.info('Cancelling job ID: {}'.format(job.id)) + logger.info(f'Cancelling job ID: {job.id}') job.stop() - return job.render_status == RenderStatus.CANCELLED + return job.render_status() == RenderStatus.CANCELLED + + @classmethod + def delete_job(cls, job): + logger.info(f"Deleting job ID: {job.id}") + job.stop() + cls.job_queue.remove(job) + return True @classmethod def renderer_instances(cls): diff --git a/requirements.txt b/requirements.txt index 5b370eb..6862f32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ rich==12.6.0 ffmpeg-python Werkzeug~=2.2.2 tkinterdnd2~=0.3.0 -future~=0.18.2 \ No newline at end of file +future~=0.18.2 +json2html~=1.3.0 \ No newline at end of file diff --git a/start_server.py b/start_server.py index 9dadec6..40cfd05 100755 --- a/start_server.py +++ b/start_server.py @@ -49,7 +49,7 @@ def start_server(background_thread=False): server_thread.join() else: server.run(host='0.0.0.0', port=RenderQueue.port, debug=config.get('flask_debug_enable', False), - use_reloader=False) + use_reloader=True, threaded=True) if __name__ == '__main__': diff --git a/templates/details.html b/templates/details.html new file mode 100644 index 0000000..a1c8bce --- /dev/null +++ b/templates/details.html @@ -0,0 +1,32 @@ + + + + + + Zordon Dashboard + + + + + + + +
+{{detail_table|safe}} +
+ + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..823c9f7 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,127 @@ + + + + + + Zordon Dashboard + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + {% for job in all_jobs %} + + + + + + + + + + + + + + {% endfor %} +
PreviewNameRendererPriorityStatusTime ElapsedFrame CountClientCommands
Image Here{{job.name}}{{job.renderer}}-{{job.worker.renderer_version}}{{job.priority}}{{job.render_status().value}}{{job.time_elapsed()}}{{job.frame_count()}}{{job.client}} +
+ + + {% if job.render_status().value in ['running', 'scheduled', 'not_started']: %} + + {% elif job.render_status().value == 'completed': %} + + {% endif %} + +
+
+
+ + + \ No newline at end of file From d0b14fc00186eb875aad4b0aa55085236debe4db Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Thu, 8 Dec 2022 00:08:09 -0800 Subject: [PATCH 05/15] Added new commands to index.html --- lib/render_job.py | 10 +++------- templates/index.html | 33 +++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/render_job.py b/lib/render_job.py index e037bc1..fc6bf70 100644 --- a/lib/render_job.py +++ b/lib/render_job.py @@ -58,13 +58,6 @@ class RenderJob: for key in keys_to_remove: job_dict['worker'].pop(key, None) - # jobs from current_session generate percent completed - # jobs after loading server pull in a saved value. Have to check if callable object or not - - percent_complete = self.worker.percent_complete if isinstance(self.worker.percent_complete, numbers.Number) \ - else self.worker.percent_complete() - job_dict['worker']['percent_complete'] = percent_complete - # convert to json and back to auto-convert dates to iso format def date_serializer(o): if isinstance(o, datetime): @@ -105,6 +98,9 @@ class RenderJob: def log_path(self): return self.worker.log_path + def percent_complete(self): + return self.worker.percent_complete() + @classmethod def generate_id(cls): return str(uuid.uuid4()).split('-')[0] \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 823c9f7..c0052af 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,14 +6,16 @@ Zordon Dashboard + + -