def info(context): revisionHash, revisionDate, revisionRelative = getRevisionInfo() print "Sprint tool, revision %s" % link(revisionHash, gitURL % {'hash': revisionHash}) if isDevMode(): print clr("Development mode", 'red') else: print clr("Production mode", 'green') loadTime = getLoadtime() print "Started %s" % clr(loadTime) print "Up for %s" % clr(timesince(loadTime))
def footer(handler): print "</div>" print "<br style=\"clear:both\">" print "</div>" print "</div>" print "</div>" revisionHash, revisionDate, revisionRelative = getRevisionInfo() print "<div class=\"footer_timestamp\">" print "Current system time: %s<br>" % getNow() print "Current revision:", if isDevMode(): print "<span style=\"color: #f00;\">Development build</span><br>" else: print "<a href=\"%s\">%s</a>" % (gitURL % {'hash': revisionHash}, revisionHash), print "(<span title=\"%s\">%s</span>)<br>" % (revisionDate, revisionRelative) print "</div>" print "<script src=\"/static/less.js\" type=\"text/javascript\"></script>" # In case any less <link>s were in the body print "</body>" print "</html>"
def user(handler, username): user = User.load(username = username) if not user: ErrorBox.die('User', "No user named <b>%s</b>" % stripTags(username)) Markdown.head('form#message-form .body pre code') print "<script src=\"/static/jquery.typing-0.2.0.min.js\" type=\"text/javascript\"></script>" print "<script src=\"/static/users.js\" type=\"text/javascript\"></script>" Chart.include() undelay(handler) handler.title(user.safe.username) handler.replace('$bodytitle$', '', 1) print "<img src=\"%s\" class=\"gravatar\">" % user.getAvatar(64) print "<h1>%s</h1>" % user.safe.username if isDevMode(handler): print "<div class=\"debugtext\">User ID: %d</div>" % user.id print "<div class=\"clear\"></div>" if handler.session['user'] and handler.session['user'].hasPrivilege('Admin'): print "<h3>Admin</h3>" print "<form method=\"post\" action=\"/admin/users\">" print "<input type=\"hidden\" name=\"username\" value=\"%s\">" % user.username print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"resetpw\">Reset password</button>" print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"impersonate\">Impersonate</button>" print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"sessions\">Manage sessions</button>" print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"privileges\">Manage privileges</button>" print "</form>" if user == handler.session['user']: print "<h3>Avatar</h3>" if user.hasLocalAvatar(): print "Your avatar is currently <a href=\"/users/%s/avatar/set\">locally hosted</a>" % user.username else: print "Your avatar can be changed at <a href=\"http://gravatar.com/\" target=\"_new\">http://gravatar.com/</a>. It must be associated with the e-mail <b>%s</b>, and be rated PG. You can also host an avatar <a href=\"/users/%s/avatar/set\">locally</a>, if necessary" % (user.getEmail(), user.username) print "<h3>Authentication</h3>" print "Your sprint tool password can be changed <a href=\"/resetpw\">here</a>.", if settings.kerberosRealm: print "You can also use your %s kerberos password to login" % settings.kerberosRealm, print "<br><br>" if user.hotpKey == '': print "You also have the option to use two-factor authentication via <a href=\"http://en.wikipedia.org/wiki/HOTP\">HOTP</a>. You can use <a href=\"http://support.google.com/a/bin/answer.py?hl=en&answer=1037451\">Google Authenticator</a> to generate verification codes<br><br>" print "<form method=\"post\" action=\"/security/two-factor\">" print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"enable\">Enable two-factor authentication</button>" print "</form>" else: print "You are currently using two-factor authentication<br><br>" print "<form method=\"post\" action=\"/security/two-factor\">" print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"enable\">Reset HOTP key</button>" print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"disable\">Disable two-factor authentication</button>" print "</form>" print "<h3>Messages</h3>" print "Your inbox and sent messages can be viewed <a href=\"/messages/inbox\">here</a><br>" print "<h3>Last seen</h3>" if not user.lastseen: print "Never" elif dateToTs(getNow()) - user.lastseen < 60: print "Just now" else: print "%s ago" % timesince(tsToDate(user.lastseen)) if handler.session['user'] and handler.session['user'] != user: print "<h3>Message</h3>" print "<small>(Messages are formatted in <a target=\"_blank\" href=\"/help/markdown\">markdown</a>)</small>" print "<form id=\"message-form\" method=\"post\" action=\"/messages/send\">" print "<input type=\"hidden\" name=\"userid\" value=\"%d\">" % user.id print "<textarea name=\"body\" class=\"large\"></textarea>" print "<div class=\"body markdown\"><div id=\"preview\"></div></div>" print Button('Send').post().positive() print "</form>" print "<h3>Project distribution</h3>" sprints = filter(lambda s: user in s.members, Sprint.loadAllActive()) sprintHours = map(lambda s: (s, Availability(s).getAllForward(getNow(), user)), sprints) projectHours = map(lambda (p, g): (p, sum(hours for sprint, hours in g)), groupby(sprintHours, lambda (s, a): s.project)) # For now at least, don't show projects with no hours projectHours = filter(lambda (p, h): h > 0, projectHours) if len(projectHours) > 0: chart = Chart('chart') chart.title.text = '' chart.tooltip.formatter = "function() {return '<b>' + this.point.name + '</b>: ' + this.point.y + '%';}" chart.plotOptions.pie.allowPointSelect = True chart.plotOptions.pie.cursor = 'pointer' chart.plotOptions.pie.dataLabels.enabled = False chart.plotOptions.pie.showInLegend = True chart.credits.enabled = False chart.series = seriesList = [] series = { 'type': 'pie', 'name': '', 'data': [] } seriesList.append(series) total = sum(hours for project, hours in projectHours) for project, hours in projectHours: series['data'].append([project.name, float("%2.2f" % (100 * hours / total))]) chart.js() chart.placeholder() else: print "Not a member of any active sprints"
def showBacklog(handler, id, search = None, devEdit = False): requirePriv(handler, 'User') id = int(id) sprint = Sprint.load(id) if not sprint or sprint.isHidden(handler.session['user']): ErrorBox.die('Sprints', "No sprint with ID <b>%d</b>" % id) elif not sprint.canView(handler.session['user']): ErrorBox.die('Private', "You must be a sprint member to view this sprint") # Redirect to search help page if searched for empty string if search == '': redirect('/help/search') handler.title(sprint.safe.name) drawNavArrows(sprint, handler.session['user'], '') tasks = sprint.getTasks() editable = sprint.canEdit(handler.session['user']) or (devEdit and isDevMode(handler)) search = Search(sprint, search) print "<script src=\"/settings/sprints.js\" type=\"text/javascript\"></script>" print "<script type=\"text/javascript\">" print "var sprintid = %d;" % id print "var currentUser = %s;" % toJS(handler.session['user'].username if handler.session['user'] else None) print "var totalTasks = %d;" % len(tasks) # True is a placeholder for the dynamic tokens (status, assigned) print "var searchTokens = %s;" % toJS(filter(None, [search.getBaseString() if search.hasBaseString() else None] + [True] + ["%s:%s" % (filt.getKey(), filt.value) for filt in search.getAll() if filt.getKey() not in ('status', 'assigned')])) print "var searchDescriptions = %s;" % toJS(filter(None, ["matching %s" % search.getBaseString() if search.hasBaseString() else None] + [True] + [filt.description() for filt in search.getAll()])) print "TaskTable.init({link_hours_status: %s});" % toJS(not sprint.isPlanning()) print "$('document').ready(function() {" if search.has('assigned'): print " $('%s').addClass('selected');" % ', '.join("#filter-assigned a[assigned=\"%s\"]" % user.username for user in search.get('assigned').users + ([handler.session['user']] if search.get('assigned').currentUser else [])) if search.has('status'): print " $('%s').addClass('selected');" % ', '.join("#filter-status a[status=\"%s\"]" % status.name for status in search.get('status').statuses) print " apply_filters();" print "});" print "</script>" print "<div id=\"selected-task-box\">" print "<span></span>" print Button('history', id = 'selected-history').positive() print Button('highlight', id = 'selected-highlight').positive() print Button('mass edit', id = 'selected-edit').positive() print Button('cancel', id = 'selected-cancel') #.negative() print "</div>" print "<div class=\"backlog-tabs\">" print tabs(sprint, 'backlog') print "<input type=\"text\" id=\"search\" value=\"%s\">" % search.getFullString().replace('"', '"') print "</div>" undelay(handler) print InfoBox('Loading...', id = 'post-status', close = True) avail = Availability(sprint) if sprint.isActive() else None dayStart = Weekday.today().date() print "<div id=\"filter-assigned\">" print "<a class=\"fancy danger\" href=\"#\"><img src=\"/static/images/cross.png\"> None</a>" for member in sorted(sprint.members): cls = ['fancy'] if not sprint.isPlanning() and avail and avail.get(member, dayStart) == 0: cls.append('away') print "<a class=\"%s\" assigned=\"%s\" href=\"/sprints/%d?search=assigned:%s\"><img src=\"%s\"> %s</a>" % (' '.join(cls), member.username, id, member.username, member.getAvatar(16), member.username) print "</div><br>" print "<div id=\"filter-status\">" print "<a class=\"fancy danger\" href=\"#\"><img src=\"/static/images/cross.png\"> None</a>" for status in sorted(statuses.values()): print "<a class=\"fancy\" status=\"%s\" href=\"/sprints/%d?search=status:%s\"><img src=\"%s\">%s</a>" % (status.name, id, status.name.replace(' ', '-'), status.getIcon(), status.text) print "</div><br>" if handler.session['user'].hasPrivilege('Admin') and 'deleted' in sprint.flags: print "<form method=\"post\" action=\"/admin/projects/%d/cancel-deletion/%d\">" % (sprint.project.id, sprint.id) print WarningBox("This sprint is flagged for deletion during nightly cleanup. %s" % Button('Cancel').mini().post()) print "</form>" if sprint.isPlanning(): if sprint.isActive(): print InfoBox("Today is <b>sprint planning</b> — tasks aren't finalized until the end of the day") else: daysTillPlanning = (tsToDate(sprint.start) - getNow()).days + 1 print InfoBox("The sprint has <b>not begun</b> — planning is %s. All changes are considered to have been made midnight of plan day" % ('tomorrow' if daysTillPlanning == 1 else "in %d days" % daysTillPlanning)) elif sprint.isReview(): print InfoBox("Today is <b>sprint review</b> — this is the last day to make changes to the backlog. All open tasks will be deferred at the end of the day") elif not sprint.isOver(): noHours = filter(lambda task: task.stillOpen() and task.hours == 0, tasks) if noHours != []: print WarningBox("There are <a href=\"/sprints/%d?search=status:not-started,in-progress,blocked hours:0\">open tasks with no hour estimate</a>" % sprint.id) tasks = search.filter(tasks) if isDevMode(handler): print Button('#all-tasks borders', "javascript:$('#all-tasks, #all-tasks tr td').css('border', '1px solid #f00').css('border-collapse', 'collapse');").negative() if not editable: print Button('make editable', "/sprints/%d?devEdit" % id).negative() elif devEdit: print Button('make uneditable', "/sprints/%d" % id).negative() print "<div class=\"debugtext\">" print "start: %d (%s)<br>" % (sprint.start, tsToDate(sprint.start)) print "end: %d (%s)<br>" % (sprint.end, tsToDate(sprint.end)) print "</div>" showing = ResponseWriter() print "<span id=\"task-count\"></span>" # save-search href set by update_task_count() print "<a class=\"save-search\"><img src=\"/static/images/save.png\" title=\"Save search\"></a>" print "<a class=\"cancel-search\" href=\"/sprints/%d\"><img src=\"/static/images/cross.png\" title=\"Clear search\"></a>" % id showing = showing.done() print TaskTable(sprint, editable = editable, tasks = tasks, tableID = 'all-tasks', dateline = showing, taskClasses = {task: ['highlight'] for task in (search.get('highlight').tasks if search.has('highlight') else [])}, debug = isDevMode(handler), groupActions = True, taskModActions = True, index = True, goal = True, status = True, name = True, assigned = True, historicalHours = True, hours = True, devEdit = devEdit)
def newTaskImport(handler, group, source=None, assigned=None): # 'assigned' is ignored, it's just in case the user gets here from a filtered backlog handler.title("New Tasks") requirePriv(handler, "User") id = int(group) print tabs.format(id).where("import") group = Group.load(id) if not group or group.sprint.isHidden(handler.session["user"]): ErrorBox.die("Invalid Group", "No group with ID <b>%d</b>" % id) sprint = group.sprint if not (sprint.isActive() or sprint.isPlanning()): ErrorBox.die("Sprint Closed", "Unable to modify inactive sprint") elif not sprint.canEdit(handler.session["user"]): ErrorBox.die("Permission Denied", "You don't have permission to modify this sprint") sprints = sprint.project.getSprints() sprintIdx = sprints.index(sprint) prevSprint = sprints[sprintIdx - 1] if sprintIdx > 0 else None if not source: print "Select a sprint to import from:<br><br>" print '<form method="get" action="/tasks/new/import">' print '<input type="hidden" name="group" value="%d">' % group.id print '<select name="source" id="import-source">' for projectIter in Project.getAllSorted(handler.session["user"], sprint.project): print '<optgroup label="%s">' % projectIter.safe.name for sprintIter in projectIter.getSprints(): print '<option value="%d"%s>%s</option>' % ( sprintIter.id, " selected" if sprintIter == prevSprint else "", sprintIter.safe.name, ) print "</optgroup>" print "</select>" print "<br><br>" print Button("Next").positive().post() print "</form>" else: id = int(source) source = Sprint.load(id) if not source: ErrorBox.die("Invalid Sprint", "No sprint with ID <b>%d</b>" % id) print '<script type="text/javascript">' nextURL = "/sprints/%d" % sprint.id if assigned: nextURL += "?search=assigned:%s" % stripTags(assigned.replace(" ", ",")) print "next_url = %s;" % toJS(nextURL) print 'post_url = "/tasks/new/import?group=%d&source=%d";' % (group.id, source.id) print "scrummaster = %s;" % toJS(sprint.owner.username) print "TaskTable.init();" print "</script>" print '<b>Source sprint</b>: <a href="/sprints/%d">%s</a><br>' % (source.id, source.name) print '<b>Target sprint</b>: <a href="/sprints/%d">%s</a><br><br>' % (sprint.id, sprint.name) print "All incomplete tasks are listed here, with their current values from the source sprint. You can change any of the fields before importing. Only checked tasks will be imported<br><br>" assignedList = [sprint.owner] + list(sprint.members - {sprint.owner}) print TaskTable( source, editable=True, assignedList=assignedList, checkbox=True, status=True, name=True, assigned=True, hours=True, debug=isDevMode(handler), ) print InfoBox("Loading...", id="post-status", close=True) print Button("Import", id="save-button", type="button").positive() print Button("Cancel", id="cancel-button", type="button").negative() print "</form><br><br>"
def dev(handler): if isDevMode(handler): print WarningBox('Under development', close = True) else: print WarningBox('This feature is still under development and is disabled') done()
def header(handler, includes): print "<!DOCTYPE html>" print "<html>" print "<head>" print "<title>$title$</title>" print "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/syntax-highlighting.css\">" print "<link rel=\"shortcut icon\" href=\"/static/images/favicon.ico\">" print "<script src=\"/static/jquery.js\" type=\"text/javascript\"></script>" print "<script src=\"/static/jquery-migrate-1.1.1.min.js\" type=\"text/javascript\"></script>" print "<link rel=\"stylesheet\" href=\"/static/jquery-ui-1.10.1.custom.css\">" print "<script src=\"/static/jquery-ui-1.10.1.custom.min.js\" type=\"text/javascript\"></script>" print "<link href=\"/static/jquery.contextMenu.css\" rel=\"stylesheet\" type=\"text/css\" />" print "<script src=\"/static/jquery.contextMenu.js\"></script>" print "<script src=\"/static/jquery.mousewheel-min.js\"></script>" print "<script src=\"/static/jquery.terminal-0.4.23.js\"></script>" print "<link href=\"/static/jquery.terminal.css\" rel=\"stylesheet\" type=\"text/css\" />" print "<script src=\"/static/jquery.ba-bbq.js\"></script>" print "<script src=\"/static/bootstrap-dropdown.js\" type=\"text/javascript\"></script>" print "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/bootstrap.css\">" print "<link rel=\"stylesheet\" href=\"/static/chosen/chosen.css\" />" print "<script src=\"/static/chosen/chosen.jquery.js\" type=\"text/javascript\"></script>" print "<script src=\"/static/noty/jquery.noty.js\"></script>" print "<script src=\"/static/noty/layouts/bottomCenter.js\"></script>" print "<script src=\"/static/noty/themes/default.js\"></script>" print "<script src=\"/static/noty/themes/sprint.js\"></script>" print "<script src=\"/dyn.js\" type=\"text/javascript\"></script>" print "<script src=\"/static/script.js\" type=\"text/javascript\"></script>" print "<script src=\"/static/shell.js\" type=\"text/javascript\"></script>" print "<style type=\"text/css\">" if handler.session['user']: print ".username[username~=\"%s\"] {" % handler.session['user'].username print " color: #C00;" print " font-weight: bold;" print "}" print "</style>" for filename in includes['less']: print "<link rel=\"stylesheet/less\" type=\"text/css\" href=\"%s\" />" % filename # for filename in includes['css']: # print "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\" />" % filename for filename in includes['js']: print "<script src=\"%s\" type=\"text/javascript\"></script>" % filename print "<link rel=\"stylesheet/less\" type=\"text/css\" href=\"/static/style.less\">" print "<script type=\"text/javascript\">" print "less = {" print " env: '%s'," % ('development' if isDevMode(handler) else 'production') print " async: false," print " dumpLineNumbers: 'comments'" print "};" print "</script>" print "<script src=\"/static/less.js\" type=\"text/javascript\"></script>" changes = list(getChanges(handler, handler.path)) if changes: print "<script src=\"/static/changelog.js\"></script>" print "<script type=\"text/javascript\">" print "$(document).ready(function() {" fmt = "%%(message)s<div style=\"text-align: right; font-size: 6pt\"><a target=\"_blank\" href=\"%s\">%%(hash)s</a></div>" % gitURL for change in changes: print " showChangelog(%s);" % toJS(fmt % {'hash': change.hash, 'message': change.message}) print "});" print "</script>" print "</head>" print "<body>" print "<div id=\"shell\"></div>" print "<div id=\"frame\">" print "<div id=\"main_a\">" if handler.session['user']: print "<div class=\"avatar\">" print "<img class=\"avatar\" src=\"%s\">" % handler.session['user'].getAvatar() print "<div class=\"subavatar\">" if 'impersonator' in handler.session: print "<img class=\"subavatar\" src=\"%s\" onClick=\"unimpersonate();\" title=\"Unimpersonate\">" % handler.session['impersonator'].getAvatar() else: unreadMessages = Message.loadAll(userid = handler.session['user'].id, read = False) if len(unreadMessages) > 0: print "<a class=\"inbox\" href=\"/messages/inbox\">%d</a>" % len(unreadMessages) print "</div>" print "</div>" print "<div class=\"navigation\">" print "<div class=\"ident\">" if handler.session['user']: print "Logged in as %s" % handler.session['user'] else: print "<a href=\"/login\">Not logged in</a>" print "</div>" if (option('dev') or isDevMode(handler)) and handler.session['user'] and handler.session['user'].hasPrivilege('Dev'): if isDevMode(handler): print "<div class=\"devwarning\" onClick=\"buildmode('production')\">" print "Development" print "</div>" else: print "<div class=\"prodwarning\" onClick=\"buildmode('development')\">" print "Production" print "</div>" print "<div class=\"topmenu\">" print menu.render(handler, handler.path) print "</div>" print "</div>" print "</div>" print "<div id=\"main_b\"></div>" print "<div id=\"main_c\">" if settings.systemMessage: print "<div class=\"sysmessage\">%s</div>" % settings.systemMessage print "<div id=\"main_d\">" print "<h1>$bodytitle$</h1>"