[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