1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 from netrender
.utils
import *
22 import netrender
.model
26 # bitwise definition of the different files type
32 src_folder
= os
.path
.split(__file__
)[0]
34 #function to return counter of different type of files
35 # job: the job that contain files
41 for file in job
.files
:
42 if file.filepath
.endswith(".bphys"):
44 elif file.filepath
.endswith((".bobj.gz", ".bvel.gz")):
46 elif not file == job
.files
[0]:
48 return tot_cache
,tot_fluid
,tot_other
;
53 handler
.wfile
.write(bytes(text
, encoding
='utf8'))
55 def head(title
, refresh
= False):
56 output("<html><head>")
58 output("<meta http-equiv='refresh' content=5>")
59 output("<script src='/html/netrender.js' type='text/javascript'></script>")
62 output("</title></head><body>")
63 output("<link rel='stylesheet' href='/html/netrender.css' type='text/css'>")
66 def link(text
, url
, script
=""):
67 return "<a href='%s' %s>%s</a>" % (url
, script
, text
)
69 def tag(name
, text
, attr
=""):
70 return "<%s %s>%s</%s>" % (name
, attr
, text
, name
)
72 def startTable(border
=1, class_style
= None, caption
= None):
73 output("<table border='%i'" % border
)
76 output(" class='%s'" % class_style
)
81 output("<caption>%s</caption>" % caption
)
83 def headerTable(*headers
):
87 output("<td>" + c
+ "</td>")
89 output("</tr></thead>")
91 def rowTable(*data
, id = None, class_style
= None, extra
= None):
95 output(" id='%s'" % id)
98 output(" class='%s'" % class_style
)
101 output(" %s" % extra
)
106 output("<td>" + str(c
) + "</td>")
113 def checkbox(title
, value
, script
=""):
114 return """<input type="checkbox" title="%s" %s %s>""" % (title
, "checked" if value
else "", ("onclick=\"%s\"" % script
) if script
else "")
116 def sendjson(message
):
117 handler
.send_head(content
= "application/json")
118 output(json
.dumps(message
,sort_keys
=False))
120 def sendFile(filename
,content_type
):
121 f
= open(os
.path
.join(src_folder
,filename
), 'rb')
123 handler
.send_head(content
= content_type
)
124 shutil
.copyfileobj(f
, handler
.wfile
)
127 # return serialized version of job for html interface
129 # includeFiles: boolean to indicate if we want file to be serialized too into job
130 # includeFrames; boolean to indicate if we want frame to be serialized too into job
131 def gethtmlJobInfo(job
,includeFiles
=True,includeFrames
=True):
133 results
= job
.framesStatus()
134 serializedJob
= job
.serialize(withFiles
=includeFiles
, withFrames
=includeFrames
)
135 serializedJob
["p_rule"] = handler
.server
.balancer
.applyPriorities(job
)
136 serializedJob
["e_rule"] = handler
.server
.balancer
.applyExceptions(job
)
137 serializedJob
["wait"] = int(time
.time() - job
.last_dispatched
) if job
.status
!= netrender
.model
.JOB_FINISHED
else "N/A"
138 serializedJob
["length"] = len(job
);
139 serializedJob
["done"] = results
[netrender
.model
.FRAME_DONE
]
140 serializedJob
["dispatched"] = results
[netrender
.model
.FRAME_DISPATCHED
]
141 serializedJob
["error"] = results
[netrender
.model
.FRAME_ERROR
]
142 tot_cache
, tot_fluid
, tot_other
= countFiles(job
)
143 serializedJob
["totcache"] = tot_cache
144 serializedJob
["totfluid"] = tot_fluid
145 serializedJob
["totother"] = tot_other
146 serializedJob
["wktime"] = (time
.time()-job
.start_time
) if job
.status
!= netrender
.model
.JOB_FINISHED
else (job
.finish_time
-job
.start_time
)
148 serializedJob
={"name":"invalid job"}
150 return serializedJob
;
152 # return serialized files based on cumulative file_type
153 # job_id: id of the job
154 # message: serialized content
155 # file_type: any combinaison of CACHE_FILE,FLUID_FILES, OTHER_FILES
157 def getFiles(job_id
,message
,file_type
):
159 job
=handler
.server
.getJobID(job_id
)
160 print ("job.files.length="+str(len(job
.files
)))
162 for file in job
.files
:
163 filedata
=file.serialize()
164 filedata
["name"] = os
.path
.split(file.filepath
)[1]
166 if file.filepath
.endswith(".bphys") and (file_type
& CACHE_FILES
):
167 message
.append(filedata
);
169 if file.filepath
.endswith((".bobj.gz", ".bvel.gz")) and (file_type
& FLUID_FILES
):
170 message
.append(filedata
);
172 if (not file == job
.files
[0]) and (file_type
& OTHER_FILES
) and (not file.filepath
.endswith((".bobj.gz", ".bvel.gz"))) and not file.filepath
.endswith(".bphys"):
173 message
.append(filedata
);
178 if handler
.path
== "/html/netrender.js":
179 sendFile("netrender.js","text/javascript")
181 elif handler
.path
== "/html/netrender.css":
182 sendFile("netrender.css","text/css")
184 elif handler
.path
=="/html/newui":
185 sendFile("newui.html","text/html")
187 elif handler
.path
.startswith("/html/js"):
188 path
, filename
= os
.path
.split(handler
.path
)
189 sendFile("js/"+filename
,"text/javascript")
191 elif handler
.path
.startswith("/html/css/images"):
192 path
, filename
= os
.path
.split(handler
.path
)
193 sendFile("css/images/"+filename
,"image/png")
195 elif handler
.path
.startswith("/html/css"):
196 path
, filename
= os
.path
.split(handler
.path
)
197 sendFile("css/"+filename
,"text/css")
198 # return all master rules information
199 elif handler
.path
== "/html/rules":
201 for rule
in handler
.server
.balancer
.rules
:
202 message
.append(rule
.serialize())
203 for rule
in handler
.server
.balancer
.priorities
:
204 message
.append(rule
.serialize())
205 for rule
in handler
.server
.balancer
.exceptions
:
206 message
.append(rule
.serialize())
208 #return all slaves list
209 elif handler
.path
== "/html/slaves":
211 for slave
in handler
.server
.slaves
:
212 serializedSlave
= slave
.serialize()
214 serializedSlave
["job_name"] = slave
.job
.name
215 serializedSlave
["job_id"] = slave
.job
.id
217 serializedSlave
["job_name"] = "None"
218 serializedSlave
["job_id"] = "0"
219 message
.append(serializedSlave
)
221 # return all job list
222 elif handler
.path
== "/html/jobs":
224 for job
in handler
.server
.jobs
:
226 message
.append(gethtmlJobInfo(job
, False, False))
228 #return a job information
229 elif handler
.path
.startswith("/html/job_"):
231 job_id
= handler
.path
[10:]
232 job
= handler
.server
.getJobID(job_id
)
237 message
.append(gethtmlJobInfo(job
, includeFiles
=False))
239 # return all frames for a job
240 elif handler
.path
.startswith("/html/frames_"):
242 job_id
= handler
.path
[13:]
243 job
= handler
.server
.getJobID(job_id
)
248 message
.append(f
.serialize())
251 # return physic cache files
252 elif handler
.path
.startswith("/html/cachefiles_"):
253 job_id
= handler
.path
[17:]
255 getFiles(job_id
, message
, CACHE_FILES
);
257 #return fluid cache files
258 elif handler
.path
.startswith("/html/fluidfiles_"):
259 job_id
= handler
.path
[17:]
262 getFiles(job_id
, message
, FLUID_FILES
);
265 #return list of other files ( images, sequences ...)
266 elif handler
.path
.startswith("/html/otherfiles_"):
267 job_id
= handler
.path
[17:]
270 getFiles(job_id
, message
, OTHER_FILES
);
272 # return blend file info
273 elif handler
.path
.startswith("/html/blendfile_"):
274 job_id
= handler
.path
[16:]
275 job
= handler
.server
.getJobID(job_id
)
279 message
.append(job
.files
[0].serialize())
281 # return black listed slaves for a job
282 elif handler
.path
.startswith("/html/blacklist_"):
284 job_id
= handler
.path
[16:]
285 job
= handler
.server
.getJobID(job_id
)
289 for slave_id
in job
.blacklist
:
290 slave
= handler
.server
.slaves_map
.get(slave_id
, None)
291 message
.append(slave
.serialize())
293 # return all slaves currently assigned to a job
295 elif handler
.path
.startswith("/html/slavesjob_"):
297 job_id
= handler
.path
[16:]
298 job
= handler
.server
.getJobID(job_id
)
301 for slave
in handler
.server
.slaves
:
302 if slave
.job
and slave
.job
== job
:
303 message
.append(slave
.serialize())
305 # here begin code for simple ui
306 elif handler
.path
== "/html" or handler
.path
== "/":
307 handler
.send_head(content
= "text/html")
308 head("NetRender", refresh
= True)
310 output("<h2>Jobs</h2>")
335 handler
.server
.balance()
337 for job
in handler
.server
.jobs
:
338 results
= job
.framesStatus()
340 time_finished
= job
.time_finished
341 time_started
= job
.time_started
344 """<button title="cancel job" onclick="cancel_job('%s');">X</button>""" % job
.id +
345 """<button title="pause job" onclick="request('/pause_%s', null);">P</button>""" % job
.id +
346 """<button title="reset all frames" onclick="request('/resetall_%s_0', null);">R</button>""" % job
.id,
348 link(job
.name
, "/html/job" + job
.id),
349 job
.category
if job
.category
else "<i>None</i>",
350 ";".join(sorted(job
.tags
)) if job
.tags
else "<i>None</i>",
351 "%s [%s]" % (netrender
.model
.JOB_TYPES
[job
.type], netrender
.model
.JOB_SUBTYPES
[job
.subtype
]),
353 """<button title="increase chunks size" onclick="request('/edit_%s', "{'chunks': %i}");">+</button>""" % (job
.id, job
.chunks
+ 1) +
354 """<button title="decrease chunks size" onclick="request('/edit_%s', "{'chunks': %i}");" %s>-</button>""" % (job
.id, job
.chunks
- 1, "disabled=True" if job
.chunks
== 1 else ""),
356 """<button title="increase priority" onclick="request('/edit_%s', "{'priority': %i}");">+</button>""" % (job
.id, job
.priority
+ 1) +
357 """<button title="decrease priority" onclick="request('/edit_%s', "{'priority': %i}");" %s>-</button>""" % (job
.id, job
.priority
- 1, "disabled=True" if job
.priority
== 1 else ""),
358 "%0.1f%%" % (job
.usage
* 100),
359 "%is" % int(time
.time() - job
.last_dispatched
) if job
.status
!= netrender
.model
.JOB_FINISHED
else "N/A",
362 results
[netrender
.model
.FRAME_DONE
],
363 results
[netrender
.model
.FRAME_DISPATCHED
],
364 str(results
[netrender
.model
.FRAME_ERROR
]) +
365 """<button title="reset error frames" onclick="request('/reset_%s_0', null);" %s>R</button>""" % (job
.id, "disabled=True" if not results
[netrender
.model
.FRAME_ERROR
] else ""),
366 "yes" if handler
.server
.balancer
.applyPriorities(job
) else "no",
367 "yes" if handler
.server
.balancer
.applyExceptions(job
) else "no",
368 time
.ctime(time_started
) if time_started
else "Not Started",
369 time
.ctime(time_finished
) if time_finished
else "Not Finished"
374 output("<h2>Slaves</h2>")
377 headerTable("name", "address", "tags", "last seen", "stats", "job")
379 for slave
in handler
.server
.slaves
:
380 rowTable(slave
.name
, slave
.address
[0], ";".join(sorted(slave
.tags
)) if slave
.tags
else "<i>All</i>", time
.ctime(slave
.last_seen
), slave
.stats
, link(slave
.job
.name
, "/html/job" + slave
.job
.id) if slave
.job
else "None")
383 output("<h2>Configuration</h2>")
385 output("""<button title="remove all jobs" onclick="clear_jobs();">CLEAR JOB LIST</button>""")
389 output(link("new interface", "/html/newui"))
391 startTable(caption
= "Rules", class_style
= "rules")
393 headerTable("type", "enabled", "description", "limit")
395 for rule
in handler
.server
.balancer
.rules
:
398 checkbox("", rule
.enabled
, "balance_enable('%s', '%s')" % (rule
.id(), str(not rule
.enabled
).lower())),
401 """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule
.id(), str(rule
.limit
)) if hasattr(rule
, "limit") else " "
404 for rule
in handler
.server
.balancer
.priorities
:
407 checkbox("", rule
.enabled
, "balance_enable('%s', '%s')" % (rule
.id(), str(not rule
.enabled
).lower())),
410 """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule
.id(), str(rule
.limit
)) if hasattr(rule
, "limit") else " "
413 for rule
in handler
.server
.balancer
.exceptions
:
416 checkbox("", rule
.enabled
, "balance_enable('%s', '%s')" % (rule
.id(), str(not rule
.enabled
).lower())),
419 """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule
.id(), str(rule
.limit
)) if hasattr(rule
, "limit") else " "
423 output("</body></html>")
425 elif handler
.path
.startswith("/html/job"):
426 handler
.send_head(content
= "text/html")
427 job_id
= handler
.path
[9:]
431 output(link("Back to Main Page", "/html"))
433 job
= handler
.server
.getJobID(job_id
)
436 output("<h2>Job Information</h2>")
442 rowTable("resolution", "%ix%i at %i%%" % job
.resolution
)
444 rowTable("tags", ";".join(sorted(job
.tags
)) if job
.tags
else "<i>None</i>")
446 rowTable("results", link("download all", resultURL(job_id
)))
451 if job
.type == netrender
.model
.JOB_BLENDER
:
452 output("<h2>Files</h2>")
461 rowTable(job
.files
[0].original_path
)
462 tot_cache
, tot_fluid
, tot_other
= countFiles(job
)
465 rowTable("%i physic cache files" % tot_cache
, class_style
= "toggle", extra
= "onclick='toggleDisplay(".cache", "none", "table-row")'")
466 for file in job
.files
:
467 if file.filepath
.endswith(".bphys"):
468 rowTable(os
.path
.split(file.filepath
)[1], class_style
= "cache")
471 rowTable("%i fluid bake files" % tot_fluid
, class_style
= "toggle", extra
= "onclick='toggleDisplay(".fluid", "none", "table-row")'")
472 for file in job
.files
:
473 if file.filepath
.endswith((".bobj.gz", ".bvel.gz")):
474 rowTable(os
.path
.split(file.filepath
)[1], class_style
= "fluid")
477 rowTable("%i other files" % tot_other
, class_style
= "toggle", extra
= "onclick='toggleDisplay(".other", "none", "table-row")'")
478 for file in job
.files
:
480 not file.filepath
.endswith(".bphys")
481 and not file.filepath
.endswith((".bobj.gz", ".bvel.gz"))
482 and not file == job
.files
[0]
485 rowTable(file.filepath
, class_style
= "other")
488 elif job
.type == netrender
.model
.JOB_VCS
:
489 output("<h2>Versioning</h2>")
493 rowTable("System", job
.version_info
.system
.name
)
494 rowTable("Remote Path", job
.version_info
.rpath
)
495 rowTable("Working Path", job
.version_info
.wpath
)
496 rowTable("Revision", job
.version_info
.revision
)
497 rowTable("Render File", job
.files
[0].filepath
)
502 output("<h2>Blacklist</h2>")
505 headerTable("name", "address")
507 for slave_id
in job
.blacklist
:
508 slave
= handler
.server
.slaves_map
.get(slave_id
, None)
510 rowTable(slave
.name
, slave
.address
[0])
514 output("<h2>Transitions</h2>")
517 headerTable("Event", "Time")
519 for transition
, time_value
in job
.transitions
:
520 rowTable(transition
, time
.ctime(time_value
))
524 output("<h2>Frames</h2>")
528 if job
.hasRenderResult():
529 headerTable("no", "status", "render time", "slave", "log", "result", "")
531 for frame
in job
.frames
:
535 "%.1fs" % frame
.time
,
536 frame
.slave
.name
if frame
.slave
else " ",
537 link("view log", logURL(job_id
, frame
.number
)) if frame
.log_path
else " ",
538 link("view result", renderURL(job_id
, frame
.number
)) + " [" +
539 tag("span", "show", attr
="class='thumb' onclick='showThumb(%s, %i)'" % (job
.id, frame
.number
)) + "]" if frame
.status
== netrender
.model
.FRAME_DONE
else " ",
540 "<img name='thumb%i' title='hide thumbnails' src='' class='thumb' onclick='showThumb(%s, %i)'>" % (frame
.number
, job
.id, frame
.number
)
543 headerTable("no", "status", "process time", "slave", "log")
545 for frame
in job
.frames
:
549 "%.1fs" % frame
.time
,
550 frame
.slave
.name
if frame
.slave
else " ",
551 link("view log", logURL(job_id
, frame
.number
)) if frame
.log_path
else " "
556 output("no such job")
558 output(link("Back to Main Page", "/html"))
560 output("</body></html>")