diff --git a/src/web/static/images/cancelled.png b/resources/cancelled.png similarity index 100% rename from src/web/static/images/cancelled.png rename to resources/cancelled.png diff --git a/src/web/static/images/error.png b/resources/error.png similarity index 100% rename from src/web/static/images/error.png rename to resources/error.png diff --git a/src/web/static/images/not_started.png b/resources/not_started.png similarity index 100% rename from src/web/static/images/not_started.png rename to resources/not_started.png diff --git a/src/web/static/images/gears.png b/resources/running.png similarity index 100% rename from src/web/static/images/gears.png rename to resources/running.png diff --git a/src/web/static/images/scheduled.png b/resources/scheduled.png similarity index 100% rename from src/web/static/images/scheduled.png rename to resources/scheduled.png diff --git a/src/web/static/images/spinner.gif b/resources/spinner.gif similarity index 100% rename from src/web/static/images/spinner.gif rename to resources/spinner.gif diff --git a/src/api/api_server.py b/src/api/api_server.py index 3aa26ab..934ba9d 100755 --- a/src/api/api_server.py +++ b/src/api/api_server.py @@ -13,10 +13,9 @@ import time from datetime import datetime from zipfile import ZipFile -import json2html import psutil import yaml -from flask import Flask, request, render_template, send_file, after_this_request, Response, redirect, url_for, abort +from flask import Flask, request, send_file, after_this_request, Response, redirect, url_for, abort from src.api.add_job_helpers import handle_uploaded_project_files, process_zipped_project, create_render_jobs from src.api.serverproxy_manager import ServerProxyManager @@ -31,7 +30,7 @@ from src.utilities.server_helper import generate_thumbnail_for_job from src.utilities.zeroconf_server import ZeroconfServer logger = logging.getLogger() -server = Flask(__name__, template_folder='web/templates', static_folder='web/static') +server = Flask(__name__) ssl._create_default_https_context = ssl._create_unverified_context # disable SSL for downloads categories = [RenderStatus.RUNNING, RenderStatus.ERROR, RenderStatus.NOT_STARTED, RenderStatus.SCHEDULED, @@ -52,17 +51,6 @@ def sorted_jobs(all_jobs, sort_by_date=True): return sorted_job_list -@server.route('/') -@server.route('/index') -def index(): - with open(system_safe_path(os.path.join(Config.config_dir(), 'presets.yaml'))) as f: - render_presets = yaml.load(f, Loader=yaml.FullLoader) - - return render_template('index.html', all_jobs=sorted_jobs(RenderQueue.all_jobs()), - hostname=server.config['HOSTNAME'], renderer_info=renderer_info(), - render_clients=[server.config['HOSTNAME']], preset_list=render_presets) - - @server.get('/api/jobs') def jobs_json(): try: @@ -79,20 +67,6 @@ def jobs_json(): return [], 500 -@server.route('/ui/job//full_details') -def job_detail(job_id): - found_job = RenderQueue.job_with_id(job_id) - table_html = json2html.json2html.convert(json=found_job.json(), - table_attributes='class="table is-narrow is-striped is-fullwidth"') - media_url = None - if found_job.file_list() and found_job.status == RenderStatus.COMPLETED: - media_basename = os.path.basename(found_job.file_list()[0]) - media_url = f"/api/job/{job_id}/file/{media_basename}" - return render_template('details.html', detail_table=table_html, media_url=media_url, - hostname=server.config['HOSTNAME'], job_status=found_job.status.value.title(), - job=found_job, renderer_info=renderer_info()) - - @server.route('/api/job//thumbnail') def job_thumbnail(job_id): big_thumb = request.args.get('size', False) == "big" @@ -130,17 +104,8 @@ def job_thumbnail(job_id): elif os.path.exists(thumb_image_path): return send_file(thumb_image_path, mimetype='image/jpeg') - # Misc status icons - if found_job.status == RenderStatus.RUNNING: - return send_file('../web/static/images/gears.png', mimetype="image/png") - elif found_job.status == RenderStatus.CANCELLED: - return send_file('../web/static/images/cancelled.png', mimetype="image/png") - elif found_job.status == RenderStatus.SCHEDULED: - return send_file('../web/static/images/scheduled.png', mimetype="image/png") - elif found_job.status == RenderStatus.NOT_STARTED: - return send_file('../web/static/images/not_started.png', mimetype="image/png") - # errors - return send_file('../web/static/images/error.png', mimetype="image/png") + return found_job.status.value, 200 + return found_job.status.value, 404 # Get job file routing @@ -291,18 +256,7 @@ def add_job_handler(): elif request.form.get('json', None): jobs_list = json.loads(request.form['json']) else: - # Cleanup flat form data into nested structure - form_dict = {k: v for k, v in dict(request.form).items() if v} - args = {} - arg_keys = [k for k in form_dict.keys() if '-arg_' in k] - for server_hostname in arg_keys: - if form_dict['renderer'] in server_hostname or 'AnyRenderer' in server_hostname: - cleaned_key = server_hostname.split('-arg_')[-1] - args[cleaned_key] = form_dict[server_hostname] - form_dict.pop(server_hostname) - args['raw'] = form_dict.get('raw_args', None) - form_dict['args'] = args - jobs_list = [form_dict] + return "Invalid data", 400 except Exception as e: err_msg = f"Error processing job data: {e}" logger.error(err_msg) @@ -318,10 +272,7 @@ def add_job_handler(): for response in results: if response.get('error', None): return results, 400 - if request.args.get('redirect', False): - return redirect(url_for('index')) - else: - return results, 200 + return results, 200 except Exception as e: logger.exception(f"Unknown error adding job: {e}") return 'unknown error', 500 @@ -499,11 +450,6 @@ def get_renderer_help(renderer): return f"Cannot find renderer '{renderer}'", 400 -@server.route('/upload') -def upload_file_page(): - return render_template('upload.html', supported_renderers=EngineManager.supported_engines()) - - def start_server(): def eval_loop(delay_sec=1): while True: diff --git a/src/ui/main_window.py b/src/ui/main_window.py index 3228a3b..a8f4ff6 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -1,13 +1,14 @@ ''' app/ui/main_window.py ''' import datetime +import io import logging import os -import socket import subprocess import sys import threading import time +import PIL from PIL import Image from PyQt6.QtCore import Qt, QByteArray, QBuffer, QIODevice, QThread from PyQt6.QtGui import QPixmap, QImage, QFont, QIcon @@ -15,7 +16,6 @@ from PyQt6.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QListWidget, QTab QTableWidgetItem, QLabel, QVBoxLayout, QHeaderView, QMessageBox, QGroupBox, QPushButton, QListWidgetItem, \ QFileDialog -from src.api.server_proxy import RenderServerProxy from src.render_queue import RenderQueue from src.utilities.misc_helper import get_time_elapsed, resources_dir, is_localhost from src.utilities.status_utils import RenderStatus @@ -278,15 +278,25 @@ class MainWindow(QMainWindow): def fetch_preview(job_id): try: + default_image_path = "error.png" before_fetch_hostname = self.current_server_proxy.hostname + response = self.current_server_proxy.request(f'job/{job_id}/thumbnail?size=big') if response.ok: - import io - image_data = response.content - image = Image.open(io.BytesIO(image_data)) - if self.current_server_proxy.hostname == before_fetch_hostname and job_id == \ - self.selected_job_ids()[0]: - self.load_image_data(image) + try: + with io.BytesIO(response.content) as image_data_stream: + image = Image.open(image_data_stream) + if self.current_server_proxy.hostname == before_fetch_hostname and job_id == \ + self.selected_job_ids()[0]: + self.load_image_data(image) + return + except PIL.UnidentifiedImageError: + default_image_path = response.text + else: + default_image_path = default_image_path or response.text + + self.load_image_path(os.path.join(resources_dir(), default_image_path)) + except ConnectionError as e: logger.error(f"Connection error fetching image: {e}") except Exception as e: diff --git a/src/web/static/images/desktop.png b/src/web/static/images/desktop.png deleted file mode 100644 index b04158c..0000000 Binary files a/src/web/static/images/desktop.png and /dev/null differ diff --git a/src/web/static/images/logo.png b/src/web/static/images/logo.png deleted file mode 100644 index 9ffcebd..0000000 Binary files a/src/web/static/images/logo.png and /dev/null differ diff --git a/src/web/static/js/job_table.js b/src/web/static/js/job_table.js deleted file mode 100644 index 12b3ca4..0000000 --- a/src/web/static/js/job_table.js +++ /dev/null @@ -1,64 +0,0 @@ -const grid = new gridjs.Grid({ -columns: [ - { data: (row) => row.id, - name: 'Thumbnail', - formatter: (cell) => gridjs.html(``), - sort: {enabled: false} - }, - { id: 'name', - name: 'Name', - data: (row) => row.name, - formatter: (name, row) => gridjs.html(`${name}`) - }, - { id: 'renderer', data: (row) => `${row.renderer}-${row.renderer_version}`, name: 'Renderer' }, - { id: 'priority', name: 'Priority' }, - { id: 'status', - name: 'Status', - data: (row) => row, - formatter: (cell, row) => gridjs.html(` - ${cell.status} - ${cell.status} - `)}, - { id: 'time_elapsed', name: 'Time Elapsed' }, - { data: (row) => row.total_frames ?? 'N/A', name: 'Frame Count' }, - { id: 'client', name: 'Client'}, - { data: (row) => row.last_output ?? 'N/A', - name: 'Last Output', - formatter: (output, row) => gridjs.html(`${output}`) - }, - { data: (row) => row, - name: 'Commands', - formatter: (cell, row) => gridjs.html(` -
- - - - - -
- `), - sort: false - }, - { id: 'owner', name: 'Owner' } -], -autoWidth: true, -server: { - url: '/api/jobs', - then: results => results['jobs'], -}, -sort: true, -}).render(document.getElementById('table')); \ No newline at end of file diff --git a/src/web/static/js/modals.js b/src/web/static/js/modals.js deleted file mode 100644 index e314dc7..0000000 --- a/src/web/static/js/modals.js +++ /dev/null @@ -1,44 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - // Functions to open and close a modal - function openModal($el) { - $el.classList.add('is-active'); - } - - function closeModal($el) { - $el.classList.remove('is-active'); - } - - function closeAllModals() { - (document.querySelectorAll('.modal') || []).forEach(($modal) => { - closeModal($modal); - }); - } - - // Add a click event on buttons to open a specific modal - (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => { - const modal = $trigger.dataset.target; - const $target = document.getElementById(modal); - - $trigger.addEventListener('click', () => { - openModal($target); - }); - }); - - // Add a click event on various child elements to close the parent modal - (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => { - const $target = $close.closest('.modal'); - - $close.addEventListener('click', () => { - closeModal($target); - }); - }); - - // Add a keyboard event to close all modals - document.addEventListener('keydown', (event) => { - const e = event || window.event; - - if (e.keyCode === 27) { // Escape key - closeAllModals(); - } - }); -}); \ No newline at end of file diff --git a/src/web/templates/details.html b/src/web/templates/details.html deleted file mode 100644 index 020daa6..0000000 --- a/src/web/templates/details.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends 'layout.html' %} - -{% block body %} -
-
- {% if media_url: %} - - {% elif job_status == 'Running': %} -
- - - Rendering - Rendering in Progress - {{(job.worker_data()['percent_complete'] * 100) | int}}% -
Time Elapsed: {{job.worker_data()['time_elapsed']}} -
- -
- {% else %} -
- - - {{job_status}} - -
- {% endif %} -
- {{detail_table|safe}} -
-{% endblock %} \ No newline at end of file diff --git a/src/web/templates/index.html b/src/web/templates/index.html deleted file mode 100644 index f0de9ec..0000000 --- a/src/web/templates/index.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends 'layout.html' %} - -{% block body %} -
-
-
- -{% endblock %} \ No newline at end of file diff --git a/src/web/templates/layout.html b/src/web/templates/layout.html deleted file mode 100644 index 71ac978..0000000 --- a/src/web/templates/layout.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - Zordon Dashboard - - - - - - - - - - - - -{% block body %} -{% endblock %} - - - - - \ No newline at end of file diff --git a/src/web/templates/upload.html b/src/web/templates/upload.html deleted file mode 100644 index f4522aa..0000000 --- a/src/web/templates/upload.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - -

Upload a file

- -
-
-
-
-
- - - -
- Render Client: - -
-
- Priority: - -
-
- - -
- -
- - -
-
- - \ No newline at end of file