Remove web components (#70)
* Remove old web code * Add back missing gears file * Client fetches thumbnails instead of being sent by server
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 995 B After Width: | Height: | Size: 995 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
@@ -13,10 +13,9 @@ import time
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
import json2html
|
|
||||||
import psutil
|
import psutil
|
||||||
import yaml
|
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.add_job_helpers import handle_uploaded_project_files, process_zipped_project, create_render_jobs
|
||||||
from src.api.serverproxy_manager import ServerProxyManager
|
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
|
from src.utilities.zeroconf_server import ZeroconfServer
|
||||||
|
|
||||||
logger = logging.getLogger()
|
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
|
ssl._create_default_https_context = ssl._create_unverified_context # disable SSL for downloads
|
||||||
|
|
||||||
categories = [RenderStatus.RUNNING, RenderStatus.ERROR, RenderStatus.NOT_STARTED, RenderStatus.SCHEDULED,
|
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
|
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')
|
@server.get('/api/jobs')
|
||||||
def jobs_json():
|
def jobs_json():
|
||||||
try:
|
try:
|
||||||
@@ -79,20 +67,6 @@ def jobs_json():
|
|||||||
return [], 500
|
return [], 500
|
||||||
|
|
||||||
|
|
||||||
@server.route('/ui/job/<job_id>/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/<job_id>/thumbnail')
|
@server.route('/api/job/<job_id>/thumbnail')
|
||||||
def job_thumbnail(job_id):
|
def job_thumbnail(job_id):
|
||||||
big_thumb = request.args.get('size', False) == "big"
|
big_thumb = request.args.get('size', False) == "big"
|
||||||
@@ -130,17 +104,8 @@ def job_thumbnail(job_id):
|
|||||||
elif os.path.exists(thumb_image_path):
|
elif os.path.exists(thumb_image_path):
|
||||||
return send_file(thumb_image_path, mimetype='image/jpeg')
|
return send_file(thumb_image_path, mimetype='image/jpeg')
|
||||||
|
|
||||||
# Misc status icons
|
return found_job.status.value, 200
|
||||||
if found_job.status == RenderStatus.RUNNING:
|
return found_job.status.value, 404
|
||||||
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")
|
|
||||||
|
|
||||||
|
|
||||||
# Get job file routing
|
# Get job file routing
|
||||||
@@ -291,18 +256,7 @@ def add_job_handler():
|
|||||||
elif request.form.get('json', None):
|
elif request.form.get('json', None):
|
||||||
jobs_list = json.loads(request.form['json'])
|
jobs_list = json.loads(request.form['json'])
|
||||||
else:
|
else:
|
||||||
# Cleanup flat form data into nested structure
|
return "Invalid data", 400
|
||||||
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]
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_msg = f"Error processing job data: {e}"
|
err_msg = f"Error processing job data: {e}"
|
||||||
logger.error(err_msg)
|
logger.error(err_msg)
|
||||||
@@ -318,9 +272,6 @@ def add_job_handler():
|
|||||||
for response in results:
|
for response in results:
|
||||||
if response.get('error', None):
|
if response.get('error', None):
|
||||||
return results, 400
|
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:
|
except Exception as e:
|
||||||
logger.exception(f"Unknown error adding job: {e}")
|
logger.exception(f"Unknown error adding job: {e}")
|
||||||
@@ -499,11 +450,6 @@ def get_renderer_help(renderer):
|
|||||||
return f"Cannot find renderer '{renderer}'", 400
|
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 start_server():
|
||||||
def eval_loop(delay_sec=1):
|
def eval_loop(delay_sec=1):
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
''' app/ui/main_window.py '''
|
''' app/ui/main_window.py '''
|
||||||
import datetime
|
import datetime
|
||||||
|
import io
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import PIL
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PyQt6.QtCore import Qt, QByteArray, QBuffer, QIODevice, QThread
|
from PyQt6.QtCore import Qt, QByteArray, QBuffer, QIODevice, QThread
|
||||||
from PyQt6.QtGui import QPixmap, QImage, QFont, QIcon
|
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, \
|
QTableWidgetItem, QLabel, QVBoxLayout, QHeaderView, QMessageBox, QGroupBox, QPushButton, QListWidgetItem, \
|
||||||
QFileDialog
|
QFileDialog
|
||||||
|
|
||||||
from src.api.server_proxy import RenderServerProxy
|
|
||||||
from src.render_queue import RenderQueue
|
from src.render_queue import RenderQueue
|
||||||
from src.utilities.misc_helper import get_time_elapsed, resources_dir, is_localhost
|
from src.utilities.misc_helper import get_time_elapsed, resources_dir, is_localhost
|
||||||
from src.utilities.status_utils import RenderStatus
|
from src.utilities.status_utils import RenderStatus
|
||||||
@@ -278,15 +278,25 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def fetch_preview(job_id):
|
def fetch_preview(job_id):
|
||||||
try:
|
try:
|
||||||
|
default_image_path = "error.png"
|
||||||
before_fetch_hostname = self.current_server_proxy.hostname
|
before_fetch_hostname = self.current_server_proxy.hostname
|
||||||
|
|
||||||
response = self.current_server_proxy.request(f'job/{job_id}/thumbnail?size=big')
|
response = self.current_server_proxy.request(f'job/{job_id}/thumbnail?size=big')
|
||||||
if response.ok:
|
if response.ok:
|
||||||
import io
|
try:
|
||||||
image_data = response.content
|
with io.BytesIO(response.content) as image_data_stream:
|
||||||
image = Image.open(io.BytesIO(image_data))
|
image = Image.open(image_data_stream)
|
||||||
if self.current_server_proxy.hostname == before_fetch_hostname and job_id == \
|
if self.current_server_proxy.hostname == before_fetch_hostname and job_id == \
|
||||||
self.selected_job_ids()[0]:
|
self.selected_job_ids()[0]:
|
||||||
self.load_image_data(image)
|
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:
|
except ConnectionError as e:
|
||||||
logger.error(f"Connection error fetching image: {e}")
|
logger.error(f"Connection error fetching image: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,64 +0,0 @@
|
|||||||
const grid = new gridjs.Grid({
|
|
||||||
columns: [
|
|
||||||
{ data: (row) => row.id,
|
|
||||||
name: 'Thumbnail',
|
|
||||||
formatter: (cell) => gridjs.html(`<img src="/api/job/${cell}/thumbnail?video_ok" style='width: 200px; min-width: 120px;'>`),
|
|
||||||
sort: {enabled: false}
|
|
||||||
},
|
|
||||||
{ id: 'name',
|
|
||||||
name: 'Name',
|
|
||||||
data: (row) => row.name,
|
|
||||||
formatter: (name, row) => gridjs.html(`<a href="/ui/job/${row.cells[0].data}/full_details">${name}</a>`)
|
|
||||||
},
|
|
||||||
{ 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(`
|
|
||||||
<span class="tag ${(cell.status == 'running') ? 'is-hidden' : ''} ${(cell.status == 'cancelled') ?
|
|
||||||
'is-warning' : (cell.status == 'error') ? 'is-danger' : (cell.status == 'not_started') ?
|
|
||||||
'is-light' : 'is-primary'}">${cell.status}</span>
|
|
||||||
<progress class="progress is-primary ${(cell.status != 'running') ? 'is-hidden': ''}"
|
|
||||||
value="${(parseFloat(cell.percent_complete) * 100.0)}" max="100">${cell.status}</progress>
|
|
||||||
`)},
|
|
||||||
{ 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(`<a href="/api/job/${row.cells[0].data}/logs">${output}</a>`)
|
|
||||||
},
|
|
||||||
{ data: (row) => row,
|
|
||||||
name: 'Commands',
|
|
||||||
formatter: (cell, row) => gridjs.html(`
|
|
||||||
<div class="field has-addons" style='white-space: nowrap; display: inline-block;'>
|
|
||||||
<button class="button is-info" onclick="window.location.href='/ui/job/${row.cells[0].data}/full_details';">
|
|
||||||
<span class="icon"><i class="fa-solid fa-info"></i></span>
|
|
||||||
</button>
|
|
||||||
<button class="button is-link" onclick="window.location.href='/api/job/${row.cells[0].data}/logs';">
|
|
||||||
<span class="icon"><i class="fa-regular fa-file-lines"></i></span>
|
|
||||||
</button>
|
|
||||||
<button class="button is-warning is-active ${(cell.status != 'running') ? 'is-hidden': ''}" onclick="window.location.href='/api/job/${row.cells[0].data}/cancel?confirm=True&redirect=True';">
|
|
||||||
<span class="icon"><i class="fa-solid fa-x"></i></span>
|
|
||||||
</button>
|
|
||||||
<button class="button is-success ${(cell.status != 'completed') ? 'is-hidden': ''}" onclick="window.location.href='/api/job/${row.cells[0].data}/download_all';">
|
|
||||||
<span class="icon"><i class="fa-solid fa-download"></i></span>
|
|
||||||
<span>${cell.file_count}</span>
|
|
||||||
</button>
|
|
||||||
<button class="button is-danger" onclick="window.location.href='/api/job/${row.cells[0].data}/delete?confirm=True&redirect=True'">
|
|
||||||
<span class="icon"><i class="fa-regular fa-trash-can"></i></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`),
|
|
||||||
sort: false
|
|
||||||
},
|
|
||||||
{ id: 'owner', name: 'Owner' }
|
|
||||||
],
|
|
||||||
autoWidth: true,
|
|
||||||
server: {
|
|
||||||
url: '/api/jobs',
|
|
||||||
then: results => results['jobs'],
|
|
||||||
},
|
|
||||||
sort: true,
|
|
||||||
}).render(document.getElementById('table'));
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{% extends 'layout.html' %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class="container" style="text-align:center; width: 100%">
|
|
||||||
<br>
|
|
||||||
{% if media_url: %}
|
|
||||||
<video width="1280" height="720" controls>
|
|
||||||
<source src="{{media_url}}" type="video/mp4">
|
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
{% elif job_status == 'Running': %}
|
|
||||||
<div style="width: 100%; height: 720px; position: relative; background: black; text-align: center; color: white;">
|
|
||||||
<img src="/static/images/gears.png" style="vertical-align: middle; width: auto; height: auto; position:absolute; margin: auto; top: 0; bottom: 0; left: 0; right: 0;">
|
|
||||||
<span style="height: auto; position:absolute; margin: auto; top: 58%; left: 0; right: 0; color: white; width: 60%">
|
|
||||||
<progress class="progress is-primary" value="{{job.worker_data()['percent_complete'] * 100}}" max="100" style="margin-top: 6px;" id="progress-bar">Rendering</progress>
|
|
||||||
Rendering in Progress - <span id="percent-complete">{{(job.worker_data()['percent_complete'] * 100) | int}}%</span>
|
|
||||||
<br>Time Elapsed: <span id="time-elapsed">{{job.worker_data()['time_elapsed']}}</span>
|
|
||||||
</span>
|
|
||||||
<script>
|
|
||||||
var startingStatus = '{{job.status.value}}';
|
|
||||||
function update_job() {
|
|
||||||
$.getJSON('/api/job/{{job.id}}', function(data) {
|
|
||||||
document.getElementById('progress-bar').value = (data.percent_complete * 100);
|
|
||||||
document.getElementById('percent-complete').innerHTML = (data.percent_complete * 100).toFixed(0) + '%';
|
|
||||||
document.getElementById('time-elapsed').innerHTML = data.time_elapsed;
|
|
||||||
if (data.status != startingStatus){
|
|
||||||
clearInterval(renderingTimer);
|
|
||||||
window.location.reload(true);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (startingStatus == 'running'){
|
|
||||||
var renderingTimer = setInterval(update_job, 1000);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div style="width: 100%; height: 720px; position: relative; background: black;">
|
|
||||||
<img src="/static/images/{{job_status}}.png" style="vertical-align: middle; width: auto; height: auto; position:absolute; margin: auto; top: 0; bottom: 0; left: 0; right: 0;">
|
|
||||||
<span style="height: auto; position:absolute; margin: auto; top: 58%; left: 0; right: 0; color: white;">
|
|
||||||
{{job_status}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{{detail_table|safe}}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{% extends 'layout.html' %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class="container is-fluid" style="padding-top: 20px;">
|
|
||||||
<div id="table" class="table"></div>
|
|
||||||
</div>
|
|
||||||
<script src="/static/js/job_table.js"></script>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Zordon Dashboard</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/gridjs/dist/gridjs.umd.js"></script>
|
|
||||||
<link href="https://unpkg.com/gridjs/dist/theme/mermaid.min.css" rel="stylesheet" />
|
|
||||||
<script src="https://kit.fontawesome.com/698705d14d.js" crossorigin="anonymous"></script>
|
|
||||||
<script type="text/javascript" src="/static/js/modals.js"></script>
|
|
||||||
</head>
|
|
||||||
<body onload="rendererChanged(document.getElementById('renderer'))">
|
|
||||||
|
|
||||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
<a class="navbar-item" href="/">
|
|
||||||
<img src="/static/images/logo.png">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="navbarBasicExample" class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item" href="/">
|
|
||||||
Home
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="navbar-end">
|
|
||||||
<div class="navbar-item">
|
|
||||||
<button class="button is-primary js-modal-trigger" data-target="add-job-modal">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa-solid fa-upload"></i>
|
|
||||||
</span>
|
|
||||||
<span>Submit Job</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<div id="add-job-modal" class="modal">
|
|
||||||
<!-- Start Add Form -->
|
|
||||||
<form id="submit_job" action="/api/add_job?redirect=True" method="POST" enctype="multipart/form-data">
|
|
||||||
<div class="modal-background"></div>
|
|
||||||
<div class="modal-card">
|
|
||||||
<header class="modal-card-head">
|
|
||||||
<p class="modal-card-title">Submit New Job</p>
|
|
||||||
<button class="delete" aria-label="close" type="button"></button>
|
|
||||||
</header>
|
|
||||||
<section class="modal-card-body">
|
|
||||||
<!-- File Uploader -->
|
|
||||||
|
|
||||||
<label class="label">Upload File</label>
|
|
||||||
<div id="file-uploader" class="file has-name is-fullwidth">
|
|
||||||
<label class="file-label">
|
|
||||||
<input class="file-input is-small" type="file" name="file">
|
|
||||||
<span class="file-cta">
|
|
||||||
<span class="file-icon">
|
|
||||||
<i class="fas fa-upload"></i>
|
|
||||||
</span>
|
|
||||||
<span class="file-label">
|
|
||||||
Choose a file…
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="file-name">
|
|
||||||
No File Uploaded
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<script>
|
|
||||||
const fileInput = document.querySelector('#file-uploader input[type=file]');
|
|
||||||
fileInput.onchange = () => {
|
|
||||||
if (fileInput.files.length > 0) {
|
|
||||||
const fileName = document.querySelector('#file-uploader .file-name');
|
|
||||||
fileName.textContent = fileInput.files[0].name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const presets = {
|
|
||||||
{% for preset in preset_list: %}
|
|
||||||
{{preset}}: {
|
|
||||||
name: '{{preset_list[preset]['name']}}',
|
|
||||||
renderer: '{{preset_list[preset]['renderer']}}',
|
|
||||||
args: '{{preset_list[preset]['args']}}',
|
|
||||||
},
|
|
||||||
{% endfor %}
|
|
||||||
};
|
|
||||||
|
|
||||||
function rendererChanged(ddl1) {
|
|
||||||
|
|
||||||
var renderers = {
|
|
||||||
{% for renderer in renderer_info: %}
|
|
||||||
{% if renderer_info[renderer]['supported_export_formats']: %}
|
|
||||||
{{renderer}}: [
|
|
||||||
{% for format in renderer_info[renderer]['supported_export_formats']: %}
|
|
||||||
'{{format}}',
|
|
||||||
{% endfor %}
|
|
||||||
],
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
};
|
|
||||||
|
|
||||||
var selectedRenderer = ddl1.value;
|
|
||||||
|
|
||||||
var ddl3 = document.getElementById('preset_list');
|
|
||||||
ddl3.options.length = 0;
|
|
||||||
createOption(ddl3, '-Presets-', '');
|
|
||||||
for (var preset_name in presets) {
|
|
||||||
if (presets[preset_name]['renderer'] == selectedRenderer) {
|
|
||||||
createOption(ddl3, presets[preset_name]['name'], preset_name);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
document.getElementById('raw_args').value = "";
|
|
||||||
|
|
||||||
var ddl2 = document.getElementById('export_format');
|
|
||||||
ddl2.options.length = 0;
|
|
||||||
var options = renderers[selectedRenderer];
|
|
||||||
for (i = 0; i < options.length; i++) {
|
|
||||||
createOption(ddl2, options[i], options[i]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createOption(ddl, text, value) {
|
|
||||||
var opt = document.createElement('option');
|
|
||||||
opt.value = value;
|
|
||||||
opt.text = text;
|
|
||||||
ddl.options.add(opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPresetTextToInput(presetfield, textfield) {
|
|
||||||
var p = presets[presetfield.value];
|
|
||||||
textfield.value = p['args'];
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Renderer & Priority -->
|
|
||||||
<div class="field is-grouped">
|
|
||||||
<p class="control">
|
|
||||||
<label class="label">Renderer</label>
|
|
||||||
<span class="select">
|
|
||||||
<select id="renderer" name="renderer" onchange="rendererChanged(this)">
|
|
||||||
{% for renderer in renderer_info: %}
|
|
||||||
<option name="renderer" value="{{renderer}}">{{renderer}}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<label class="label">Client</label>
|
|
||||||
<span class="select">
|
|
||||||
<select name="client">
|
|
||||||
<option name="client" value="">First Available</option>
|
|
||||||
{% for client in render_clients: %}
|
|
||||||
<option name="client" value="{{client}}">{{client}}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<label class="label">Priority</label>
|
|
||||||
<span class="select">
|
|
||||||
<select name="priority">
|
|
||||||
<option name="priority" value="1">1</option>
|
|
||||||
<option name="priority" value="2" selected="selected">2</option>
|
|
||||||
<option name="priority" value="3">3</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Output Path -->
|
|
||||||
<label class="label">Output</label>
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control is-expanded">
|
|
||||||
<input class="input is-small" type="text" placeholder="Output Name" name="output_path" value="output.mp4">
|
|
||||||
</div>
|
|
||||||
<p class="control">
|
|
||||||
<span class="select is-small">
|
|
||||||
<select id="export_format" name="export_format">
|
|
||||||
<option value="ar">option</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Resolution -->
|
|
||||||
<!-- <label class="label">Resolution</label>-->
|
|
||||||
<!-- <div class="field is-grouped">-->
|
|
||||||
<!-- <p class="control">-->
|
|
||||||
<!-- <input class="input" type="text" placeholder="auto" maxlength="5" size="8" name="AnyRenderer-arg_x_resolution">-->
|
|
||||||
<!-- </p>-->
|
|
||||||
<!-- <label class="label"> x </label>-->
|
|
||||||
<!-- <p class="control">-->
|
|
||||||
<!-- <input class="input" type="text" placeholder="auto" maxlength="5" size="8" name="AnyRenderer-arg_y_resolution">-->
|
|
||||||
<!-- </p>-->
|
|
||||||
<!-- <label class="label"> @ </label>-->
|
|
||||||
<!-- <p class="control">-->
|
|
||||||
<!-- <input class="input" type="text" placeholder="auto" maxlength="3" size="5" name="AnyRenderer-arg_frame_rate">-->
|
|
||||||
<!-- </p>-->
|
|
||||||
<!-- <label class="label"> fps </label>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
|
|
||||||
<label class="label">Command Line Arguments</label>
|
|
||||||
<div class="field has-addons">
|
|
||||||
<p class="control">
|
|
||||||
<span class="select is-small">
|
|
||||||
<select id="preset_list" onchange="addPresetTextToInput(this, document.getElementById('raw_args'))">
|
|
||||||
<option value="preset-placeholder">presets</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p class="control is-expanded">
|
|
||||||
<input class="input is-small" type="text" placeholder="Args" id="raw_args" name="raw_args">
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- End Add Form -->
|
|
||||||
</section>
|
|
||||||
<footer class="modal-card-foot">
|
|
||||||
<input class="button is-link" type="submit"/>
|
|
||||||
<button class="button" type="button">Cancel</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<html>
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
|
|
||||||
<script>
|
|
||||||
$(function() {
|
|
||||||
$('#renderer').change(function() {
|
|
||||||
$('.render_settings').hide();
|
|
||||||
$('#' + $(this).val()).show();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<body>
|
|
||||||
<h3>Upload a file</h3>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<form action="/add_job" method="POST"
|
|
||||||
enctype="multipart/form-data">
|
|
||||||
<div>
|
|
||||||
<input type="file" name="file"/><br>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="hidden" id="origin" name="origin" value="html">
|
|
||||||
|
|
||||||
<div id="client">
|
|
||||||
Render Client:
|
|
||||||
<select name="client">
|
|
||||||
{% for client in render_clients %}
|
|
||||||
<option value="{{client}}">{{client}}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div id="priority">
|
|
||||||
Priority:
|
|
||||||
<select name="priority">
|
|
||||||
<option value="1">1</option>
|
|
||||||
<option value="2" selected>2</option>
|
|
||||||
<option value="3">3</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="renderer">Renderer:</label>
|
|
||||||
<select id="renderer" name="renderer">
|
|
||||||
{% for renderer in supported_renderers %}
|
|
||||||
<option value="{{renderer}}">{{renderer}}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div id="blender" class="render_settings" style="display:none">
|
|
||||||
Engine:
|
|
||||||
<select name="blender+engine">
|
|
||||||
<option value="CYCLES">Cycles</option>
|
|
||||||
<option value="BLENDER_EEVEE">Eevee</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<input type="submit"/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||