[Durian-svn] [2686] New UI for farm, not integrated yet.
brecht
institute at blender.org
Fri Apr 30 13:44:13 CEST 2010
Revision: 2686
https://blenderinstitute.dyndns.org/durian-svn/?do=log&project=durian&path=/&rev=2686
Author: brecht
Date: 2010-04-30 13:44:13 +0200 (Fri, 30 Apr 2010)
Log Message:
-----------
New UI for farm, not integrated yet.
Added Paths:
-----------
frm/master_ui.css
frm/master_ui.js
frm/master_ui.py
Added: frm/master_ui.css
===================================================================
--- frm/master_ui.css (rev 0)
+++ frm/master_ui.css 2010-04-30 11:44:13 UTC (rev 2686)
@@ -0,0 +1,87 @@
+body {
+ background-color:#eee;
+ font-size:12px;
+ font-family: "Lucida Sans","Lucida Sans Unicode","Lucida Grande",Lucida,sans-serif;
+
+}
+a {
+ /*text-decoration:none;*/
+ color:#666;
+}
+a:hover {
+ color:#000;
+}
+h2 {
+ background-color:#ddd;
+ font-size:120%;
+ padding:5px;
+}
+
+h2 {
+ background-color:#ddd;
+ font-size:110%;
+ padding:5px;
+}
+
+table {
+ text-align:left;
+ border:0;
+ background-color:#ddd;
+ padding: 0px;
+ margin: 0px;
+ width: 1024px;
+}
+
+thead{
+ font-size:90%;
+ color:#555;
+ background-color:#ccc;
+}
+td {
+ border:0;
+ padding:2px;
+ padding-left:10px;
+ padding-right:10px;
+ margin-left:20px;
+ background-color:#ddd;
+}
+td:hover {
+ background-color:#ccc;
+}
+tr {
+ border:0;
+}
+button {
+ color: #111;
+ width: auto;
+ height: auto;
+}
+
+.toggle {
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.cache {
+ display: none;
+}
+
+.fluid {
+ display: none;
+}
+
+.rules {
+ width: 60em;
+ text-align: left;
+}
+
+img.thumb {
+ display: none;
+ cursor: pointer;
+}
+
+span.thumb {
+ text-decoration: underline;
+ cursor: pointer;
+}
+
Added: frm/master_ui.js
===================================================================
--- frm/master_ui.js (rev 0)
+++ frm/master_ui.js 2010-04-30 11:44:13 UTC (rev 2686)
@@ -0,0 +1,9 @@
+
+function request(url, data)
+{
+ xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("POST", url, false);
+ xmlhttp.send(data);
+ window.location.reload()
+}
+
Added: frm/master_ui.py
===================================================================
--- frm/master_ui.py (rev 0)
+++ frm/master_ui.py 2010-04-30 11:44:13 UTC (rev 2686)
@@ -0,0 +1,394 @@
+#!/shared/software/python/bin/python3.1
+
+import cgi
+import http.server
+import os
+import os.path
+import pickle
+import shutil
+import subprocess
+import urllib
+
+from render_dirs import FARM_DIR
+
+TOTAL_PROGRESS = "No statistics yet."
+JOBS = []
+SLAVES = []
+JOBS_FILE = os.path.join(FARM_DIR, "jobs.pkl")
+SLAVES_FILE = os.path.join(FARM_DIR, "slaves.pkl")
+REPO_PATH = "/media/data/svnroot/durian"
+
+def latest_svn_info():
+ command = "svnlook youngest " + REPO_PATH
+ p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
+ rev = p.communicate()[0].strip()
+
+ if len(rev):
+ logs = ""
+ for i in range(max(int(rev)-5, 0), int(rev)+1):
+ command = "svnlook log -r%d %s" % (i, REPO_PATH)
+ p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
+ log = p.communicate()[0].strip()
+
+ logs += log + "<br/>\n"
+
+ return logs, rev
+ else:
+ # in case we fail to read
+ return "", "HEAD"
+
+############################ Jobs ########################
+
+class Job:
+ priority_types = ('Low', 'Medium', 'High', 'Critical')
+ quality_types = ('Final 2K', 'Final 4K')
+ status_types = ('Disabled', 'In Progress', 'Done')
+
+ def __init__(self, id, revision):
+ self.id = id
+ self.status = "Disabled"
+ self.progress = "0% (0/0)"
+ self.priority = "Medium"
+ self.revision = revision
+ self.quality = "Final 2K"
+ self.enabled = False
+
+ @staticmethod
+ def find(id):
+ for job in JOBS:
+ if job.id == id:
+ return job
+
+ @staticmethod
+ def dump():
+ f = open(JOBS_FILE, "wb")
+ pickle.dump(JOBS, f)
+ f.close()
+
+ @staticmethod
+ def load():
+ global JOBS
+ try:
+ f = open(JOBS_FILE, "rb")
+ JOBS = pickle.load(f)
+ f.close()
+ except IOError:
+ print("No jobs file, starting with 0 jobs.")
+ JOBS = []
+
+ def sortkey(self):
+ if job.priority == 'Critical':
+ return 0
+ elif job.priority == 'High':
+ return 1
+ elif job.priority == 'Medium':
+ return 2
+ else:
+ return 3
+
+def job_set_enabled(id, enabled):
+ # slaves executing a job will detect that it is disabled in the
+ # master loop and stop rendering that job
+ job = Job.find(id)
+ job.enabled = enabled
+ if job.enabled: job.status = 'Waiting'
+ else: job.status = 'Disabled'
+ Job.dump()
+
+def job_set_priority(id, priority):
+ # changes in priority are detected in master loop
+ Job.find(id).priority = priority
+ Job.dump()
+
+def job_set_quality(id, quality):
+ # XXX not implemented yet
+ Job.find(id).quality = quality
+ Job.dump()
+
+# XXX not implemented yet
+#def job_clear(params):
+# Job.find(id).progress = "0/0"
+# Job.dump()
+
+def job_add(id, revision):
+ # master loop will find the new job when refreshes it's jobs list in
+ # the outer loop, so actually starting to render may take a while
+ for job in JOBS:
+ if job.id == id:
+ return
+
+ JOBS.append(Job(id, revision))
+ Job.dump()
+
+def job_remove(id):
+ # disabling first ensures that any slaves rendering this stop the job
+ job_set_enabled(id, False)
+ JOBS.remove(Job.find(id))
+ Job.dump()
+
+############################ Slaves ########################
+
+class Slave:
+ def __init__(self, id, ip):
+ self.id = id
+ self.ip = ip
+ self.status = "Disabled"
+ self.enabled = False
+
+ @staticmethod
+ def find(id):
+ for slave in SLAVES:
+ if slave.id == id:
+ return slave
+
+ @staticmethod
+ def dump():
+ f = open(SLAVES_FILE, "wb")
+ pickle.dump(SLAVES, f)
+ f.close()
+
+ @staticmethod
+ def load():
+ global SLAVES
+ try:
+ f = open(SLAVES_FILE, "rb")
+ SLAVES = pickle.load(f)
+ f.close()
+ except IOError:
+ print("No slaves file, starting with 0 slaves.")
+ SLAVES = []
+
+def slave_set_enabled(id, enabled):
+ # master loop will detect slave.enabled and start/stop jobs
+ slave = Slave.find(id)
+ slave.enabled = enabled
+ if slave.enabled: slave.status = 'Waiting'
+ else: slave.status = 'Disabled'
+ Slave.dump()
+
+def slave_add(id, ip):
+ # master loop will detect new added slaves automatically
+ for slave in SLAVES:
+ if slave.ip == ip:
+ return
+
+ SLAVES.append(Slave(id, ip))
+ Slave.dump()
+
+def slave_remove(id):
+ # master loop will keep reference and then detect that it got removed
+ slave_set_enabled(id, False)
+ SLAVES.remove(Slave.find(id))
+ Slave.dump()
+
+######################### Server ###########################
+
+class HHandler(http.server.BaseHTTPRequestHandler):
+ def do_POST(self):
+ # get arguments
+ length = int(self.headers['content-length'])
+ if length == 0:
+ return
+ command = str(self.rfile.read(length), encoding='utf8')
+
+ # execute a statement
+ if self.path == '/exec':
+ print("executing", command)
+ eval(command)
+ # add a slave
+ elif self.path == '/slave_add':
+ d = urllib.parse.parse_qs(command)
+ slave_add(d['id'][0], d['ip'][0])
+ # add a job
+ elif self.path == '/job_add':
+ d = urllib.parse.parse_qs(command)
+ job_add(d['id'][0], d['revision'][0])
+
+ self.send_response(http.client.SEE_OTHER)
+ self.send_header("Location", "/")
+ self.end_headers()
+
+ """
+ self.send_response(http.client.NO_CONTENT)
+ self.send_header("Content-type", "application/octet-stream")
+ self.end_headers()
+ """
+
+ def do_GET(self):
+ def output(text):
+ self.wfile.write(bytes(text, encoding='utf8'))
+
+ def title(text):
+ output("<h1>" + text + "</h1>\n")
+
+ def section(text):
+ output("<h3>" + text + "</h3>\n")
+
+ def table_begin(*columns):
+ output("<table>\n")
+ output("<thead>\n")
+ table_row(*columns)
+ output("</thead>\n")
+
+ def table_row(*columns):
+ output("<tr>")
+ for col in columns:
+ output("<td>" + str(col) + "</td>")
+ output("</tr>")
+
+ def table_end():
+ output("</table>\n")
+
+ def checkbox(function, id, enabled):
+ if enabled: state = "checked"
+ else: state = ""
+
+ output("""<input type='checkbox' %s onchange='request("/exec", "%s(\\"%s\\", " + (this.checked? "True": "False") + ")")'>""" % (state, function, id))
+
+ def dropdown(function, id, value, options):
+ output("""<select onchange='request("/exec", "%s(\\"%s\\", \\"" + this.value + "\\")")'>\n""" % (function, id))
+
+ for option in options:
+ if value == option: state = "selected"
+ else: state = ""
+ output("\t<option %s value='%s'>%s</option>\n" % (state, option, option))
+
+ output("</select>\n")
+
+ def action(function, name, question):
+ if question:
+ output("""<button onclick='if(confirm("%s")) request("/exec", "%s")'>%s</button>""" % (question, function, name))
+ else:
+ output("""<button onclick='request("/exec", "%s")'>%s</button>""" % (function, name))
+
+ def return_file(path):
+ f = open(path, 'rb')
+
+ self.send_response(http.client.OK)
+ self.send_header("Content-type", "text/css")
+ self.end_headers()
+
+ shutil.copyfileobj(f, self.wfile)
+
+ f.close()
+
+ if self.path == '/master_ui.css':
+ return_file('master_ui.css')
+ elif self.path == '/master_ui.js':
+ return_file('master_ui.js')
+ elif self.path == '/preview.png':
+ return_file(os.path.join(FARM_DIR, 'slideshow/preview_small00001.png'))
+ else:
+ # headers
+ self.send_response(http.client.OK)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+
+ # html begin
+ output("<html><head>\n")
+ output("<meta http-equiv='refresh' content=30>\n")
@@ Diff output truncated at 10240 characters. @@
More information about the Durian-svn
mailing list