diff --git a/dashboard.py b/dashboard.py index 46b1a87..ccd3b7b 100755 --- a/dashboard.py +++ b/dashboard.py @@ -1,28 +1,24 @@ #!/usr/bin/env python import datetime -import json import os.path import socket -import time import threading +import time import traceback -import psutil import requests -import click from rich import box from rich.console import Console +from rich.layout import Layout from rich.live import Live +from rich.panel import Panel from rich.table import Column from rich.table import Table from rich.text import Text -from rich.layout import Layout -from rich.panel import Panel from rich.tree import Tree import zordon_server -from zordon_server import RenderStatus -from zordon_server import string_to_status +from zordon_server import RenderStatus, string_to_status """ The RenderDashboard is designed to be run on a remote machine or on the local server @@ -36,6 +32,8 @@ status_colors = {RenderStatus.ERROR: "red", RenderStatus.CANCELLED: 'orange1', R categories = [RenderStatus.RUNNING, RenderStatus.ERROR, RenderStatus.NOT_STARTED, RenderStatus.SCHEDULED, RenderStatus.COMPLETED, RenderStatus.CANCELLED] +renderer_colors = {'ffmpeg': '[magenta]', 'Blender': '[orange1]', 'aerender':'[purple'} + local_hostname = socket.gethostname() @@ -46,15 +44,19 @@ def status_string_to_color(status_string): def sorted_jobs(all_jobs): - # sorted_jobs = [] - # if all_jobs: - # for status_category in categories: - # found_jobs = [x for x in all_jobs if x['status'] == status_category.value] - # if found_jobs: - # sorted_found_jobs = sorted(found_jobs, key=lambda d: datetime.datetime.fromisoformat(d['date_created']), reverse=True) - # sorted_jobs.extend(sorted_found_jobs) - sorted_jobs = sorted(all_jobs, key=lambda d: datetime.datetime.fromisoformat(d['date_created']), reverse=True) - return sorted_jobs + + sort_by_date = True + if not sort_by_date: + sorted_job_list = [] + if all_jobs: + for status_category in categories: + found_jobs = [x for x in all_jobs if x['status'] == status_category.value] + if found_jobs: + sorted_found_jobs = sorted(found_jobs, key=lambda d: datetime.datetime.fromisoformat(d['date_created']), reverse=True) + sorted_job_list.extend(sorted_found_jobs) + else: + sorted_job_list = sorted(all_jobs, key=lambda d: datetime.datetime.fromisoformat(d['date_created']), reverse=True) + return sorted_job_list def create_node_tree(all_server_data) -> Tree: @@ -69,29 +71,30 @@ def create_node_tree(all_server_data) -> Tree: node_tree = Tree(node_tree_text) - stats_text = f"CPU: {server_data['status']['cpu_percent']}% | RAM: {server_data['status']['memory_percent']}% | " \ - f"Cores: {server_data['status']['cpu_count']} | {server_data['status']['platform'].split('-')[0]}" + stats_text = f"CPU: [yellow]{server_data['status']['cpu_percent']}% [default]| RAM: " \ + f"[yellow]{server_data['status']['memory_percent']}% [default]| Cores: " \ + f"[yellow]{server_data['status']['cpu_count']} [default]| " \ + f"{server_data['status']['platform'].split('-')[0]}" node_tree.add(Tree(stats_text)) running_jobs = [job for job in server_data['jobs'] if job['status'] == 'running'] not_started = [job for job in server_data['jobs'] if job['status'] == 'not_started'] scheduled = [job for job in server_data['jobs'] if job['status'] == 'scheduled'] + jobs_to_display = running_jobs + not_started + scheduled jobs_tree = Tree(f"Running: [green]{len(running_jobs)} [default]| Queued: [cyan]{len(not_started)}" f"[default] | Scheduled: [cyan]{len(scheduled)}") - if running_jobs or not_started or scheduled: - for job in running_jobs: - filename = os.path.basename(job['render']['input']).split('.')[0] - jobs_tree.add(f"[bold]{filename} ({job['id']}) - {status_string_to_color(job['status'])}{(float(job['render']['percent_complete']) * 100):.1f}%") - for job in not_started: - filename = os.path.basename(job['render']['input']).split('.')[0] + for job in jobs_to_display: + renderer = f"{renderer_colors[job['renderer']]}{job['renderer']}[default]" + filename = os.path.basename(job['render']['input']).split('.')[0] + if job['status'] == 'running': + jobs_tree.add(f"[bold]{renderer} {filename} ({job['id']}) - {status_string_to_color(job['status'])}{(float(job['render']['percent_complete']) * 100):.1f}%") + else: jobs_tree.add(f"{filename} ({job['id']}) - {status_string_to_color(job['status'])}{job['status'].title()}") - for job in scheduled: - filename = os.path.basename(job['render']['input']).split('.')[0] - jobs_tree.add(f"{filename} ({job['id']}) - {status_string_to_color(job['status'])}{job['status'].title()}") - else: + + if not jobs_to_display: jobs_tree.add("[italic]No running jobs") node_tree.add(jobs_tree) @@ -102,13 +105,13 @@ def create_node_tree(all_server_data) -> Tree: def create_jobs_table(all_server_data) -> Table: table = Table("ID", "Project", "Output", "Renderer", Column(header="Priority", justify="center"), Column(header="Status", justify="center"), Column(header="Time Elapsed", justify="right"), - Column(header="# Frames", justify="right"), "Node", show_lines=True, + Column(header="# Frames", justify="right"), "Client", show_lines=True, box=box.HEAVY_HEAD) all_jobs = [] for server_name, server_data in all_server_data['servers'].items(): for job in server_data['jobs']: - job['node'] = server_name + #todo: clean this up all_jobs.append(job) all_jobs = sorted_jobs(all_jobs) @@ -117,7 +120,10 @@ def create_jobs_table(all_server_data) -> Table: job_status = string_to_status(job['status']) job_color = '[{}]'.format(status_colors[job_status]) - job_text = f"{job_color}" + job_status.value + job_text = f"{job_color}" + job_status.value.title() + + if job_status == RenderStatus.ERROR and job['render']['errors']: + job_text = job_text + "\n" + "\n".join(job['render']['errors']) elapsed_time = job['render'].get('time_elapsed', 'unknown') @@ -136,8 +142,9 @@ def create_jobs_table(all_server_data) -> Table: # Priority priority_color = ["red", "yellow", "cyan"][(job['priority'] - 1)] - node_title = ("[yellow]" if job['node'] == local_hostname else "[magenta]") + job['node'] - renderer_colors = {'ffmpeg': '[magenta]', 'Blender': '[orange1]'} + client_name = job['client'] or 'unknown' + client_colors = {'unknown': '[red]', local_hostname: '[yellow]'} + client_title = client_colors.get(client_name, '[magenta]') + client_name table.add_row( job['id'], @@ -148,7 +155,7 @@ def create_jobs_table(all_server_data) -> Table: job_text, elapsed_time, str(max(int(job['render']['total_frames']), 1)), - node_title + client_title ) return table @@ -179,7 +186,6 @@ class RenderDashboard: return req.json() except Exception as e: pass - # print(f"Exception fetching server data: {e}") return None def get_jobs(self): @@ -216,20 +222,20 @@ def my_callback(inp): if __name__ == '__main__': - server_ip = input("Enter server IP or None for local: ") or local_hostname + get_server_ip = input("Enter server IP or None for local: ") or local_hostname - client = RenderDashboard(server_ip, "8080") + client = RenderDashboard(get_server_ip, "8080") if not client.connect(): - if server_ip == local_hostname: + if client.server_ip == local_hostname: start_server = input("Local server not running. Start server? (y/n) ") if start_server and start_server[0].lower() == "y": # Startup the local server - zordon_server.start_server(background_thread=True) + zordon_server.RenderServer.start(background_thread=True) test = client.connect() print(f"connected? {test}") else: - print(f"\nUnable to connect to server: {server_ip}") + print(f"\nUnable to connect to server: {client.server_ip}") print("\nVerify IP address is correct and server is running") exit(1) @@ -256,8 +262,8 @@ if __name__ == '__main__': # Server connection header header_text = Text(f"Connected to server: ") - header_text.append(f"{server_ip} ", style="green") - if server_ip == local_hostname: + header_text.append(f"{client.server_ip} ", style="green") + if client.server_ip == local_hostname: header_text.append("(This Computer)", style="yellow") else: header_text.append("(Remote)", style="magenta")