diff --git a/README.rst b/README.rst index 203dc9f..3c7efeb 100644 --- a/README.rst +++ b/README.rst @@ -126,8 +126,8 @@ you can use the script:: :src: ../WorkflowWebTools/test/config.yml :analyzer: shell-script -Maintaining the Web Tools -------------------------- +Maintaining the Python Backend +------------------------------ For developers wishing to make adjustments to the modules or anyone else who wants to understand some of the backend of the server, @@ -185,6 +185,15 @@ Show Log .. automodule:: WorkflowWebTools.showlog :members: +JavaScript and Mako User Interface +---------------------------------- + +Some documentation of the JavaScript used on the webpages are given below. + +.. autoanysrc:: phony + :src: ../WorkflowWebTools/runserver/static/js/*.js + :analyzer: js + WorkflowWebTools Forks' Build Statuses -------------------------------------- diff --git a/clusterworkflows.py b/clusterworkflows.py index 98d546a..a1511c9 100644 --- a/clusterworkflows.py +++ b/clusterworkflows.py @@ -17,7 +17,7 @@ .. math:: \\mathrm{distance} = \\frac{d}{\\sqrt{2} |\\vec{v}|} + - 2.0 w \\left(\\frac{|\\vec{v}|}{|\\vec{v}| + m} - 0.5\\right) + 2 w \\left(\\frac{|\\vec{v}|}{|\\vec{v}| + m} - 0.5\\right) *d* is the 'distance' parameter, *w* is the 'width' parameter, and *m* is the 'midpoint' parameter set in the ``config.yml``. diff --git a/globalerrors.py b/globalerrors.py index 90df3b9..16834ec 100644 --- a/globalerrors.py +++ b/globalerrors.py @@ -25,8 +25,6 @@ def __init__(self, data_location=''): self.clusters = None self.data_location = data_location - if not self.data_location: - self.data_location = serverconfig.all_errors_path() self.setup() def __del__(self): @@ -38,10 +36,15 @@ def setup(self): self.timestamp = time.time() + if self.data_location: + data_location = self.data_location + else: + data_location = serverconfig.all_errors_path() + # Store everything into an SQL database for fast retrival - if self.data_location.endswith('.db') and os.path.exists(self.data_location): - self.conn = sqlite3.connect(self.data_location, check_same_thread=False) + if data_location.endswith('.db') and os.path.exists(data_location): + self.conn = sqlite3.connect(data_location, check_same_thread=False) curs = self.conn.cursor() else: @@ -49,7 +52,7 @@ def setup(self): curs = self.conn.cursor() errorutils.create_table(curs) - errorutils.add_to_database(curs, self.data_location) + errorutils.add_to_database(curs, data_location) def get_all(column): """Get list of all unique entries in the database diff --git a/manageactions.py b/manageactions.py index 0018d36..b3cad6f 100644 --- a/manageactions.py +++ b/manageactions.py @@ -1,17 +1,21 @@ -""" -Module to manage actions of WorkflowWebTools. +"""Module to manage actions of WorkflowWebTools. :author: Daniel Abercrombie """ import os import json +import glob from datetime import datetime +from datetime import timedelta from . import reasonsmanip +ACTIONS_DIRECTORY = 'actions' +"""The location to store the actions JSON files""" + def extract_reasons_params(**kwargs): """Extracts the reasons and parameters for an action from kwargs @@ -78,8 +82,13 @@ def submitaction(user, workflows, action, **kwargs): reasons, params = extract_reasons_params(**kwargs) - output_file_name = 'actions/{0}_{1}.json'.\ - format(user, datetime.now().strftime('%Y%m%d')) + if not os.path.exists(ACTIONS_DIRECTORY): + os.makedirs(ACTIONS_DIRECTORY) + + output_file_name = os.path.join( + ACTIONS_DIRECTORY, '{0}_{1}.json'.\ + format(user, datetime.now().strftime('%Y%m%d')) + ) add_to_json = {} if os.path.isfile(output_file_name): @@ -100,3 +109,58 @@ def submitaction(user, workflows, action, **kwargs): json.dump(add_to_json, outputfile) return workflows, action, reasons, params + + +def get_prev_actions(num_days): + """Get the keys and values of recent actions + + :param int num_days: is the number of days to check for actions + :rtype: generator + """ + + date = datetime.now() + date_int = int(date.strftime('%Y%m%d')) + prev_int = int((date - timedelta(num_days)).strftime('%Y%m%d')) + + for match in glob.iglob(os.path.join(ACTIONS_DIRECTORY, '*.json')): + check_int = int(match.split('_')[-1].rstrip('.json')) + if prev_int <= check_int <= date_int: + with open(match, 'r') as infile: + output = json.load(infile) + for key, value in output.iteritems(): + value['user'] = match.split('/')[-1].split('_')[0] + yield key, value + + +def get_actions(num_days): + """Get the recent actions to be act on in dictionary form + + :param int num_days: is the number of days to check for actions + :returns: A dictionary of actions, to be rendered as JSON + :rtype: dict + """ + + output = {} + + for key, value in get_prev_actions(num_days): + output[key] = value + + return output + + +def get_acted_workflows(num_days): + """Get all of the workflows that have actions assigned + + :param int num_days: is the number of past days to check for actions. + This speeds up the check while not losing the + ability to look farther back in time. + :returns: a list of workflows acted on + :rtype: list + """ + + workflows = [] + + for key, _ in get_prev_actions(num_days): + workflows.append(key) + + return workflows diff --git a/manageusers.py b/manageusers.py index 4e322fd..ee4f351 100644 --- a/manageusers.py +++ b/manageusers.py @@ -169,8 +169,9 @@ def resetpassword(code, password): def add_user(email, username, password, url): - """Adds the user to the users database and sends a verification email, - if the parameters are valid. + """ + Adds the user to the users database and sends a verification email, + if the parameters are valid. :param str email: The user email to send verification to. Make sure to check for valid domains. diff --git a/runserver/actions/test.json b/runserver/actions/test.json deleted file mode 100644 index 941574f..0000000 --- a/runserver/actions/test.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - - "test" : { - "Actions": "test", - - "Parameters": { - "test": "True", - "what": "test" - }, - - "Reasons": "I needed a test" - } - -} diff --git a/runserver/static/css/rotation.css b/runserver/static/css/rotation.css index 0f6849f..b4bfbd0 100644 --- a/runserver/static/css/rotation.css +++ b/runserver/static/css/rotation.css @@ -22,3 +22,11 @@ th.rotate > div { font-size: 70%; word-wrap: break-word; } + +.done { + background-color:#4fef4f; +} + +.todo { + background-color:#ef4f4f; +} \ No newline at end of file diff --git a/runserver/static/css/style.css b/runserver/static/css/style.css index 3bbf0a6..31b1e0c 100644 --- a/runserver/static/css/style.css +++ b/runserver/static/css/style.css @@ -1,7 +1,7 @@ html { margin:1em auto; max-width:60em; - background-color:#efefef; + background-color:#a0a0a0; } body { @@ -9,11 +9,15 @@ body { padding:2em; } -h1, h2, h3 { +h1, h2 { text-align:center; } +h3 { + background-color:#e6f7ff; +} + footer { padding:2em; background-color:#e6f7ff; -} \ No newline at end of file +} diff --git a/runserver/static/js/addreason.js b/runserver/static/js/addreason.js index 67b5219..c7bfae5 100644 --- a/runserver/static/js/addreason.js +++ b/runserver/static/js/addreason.js @@ -1,3 +1,19 @@ +/*""" +.. describe:: addreason.js + +This file contains the functions used to add reasons to the :ref:`workflow-view-ref` +as well as adding parameters to the page when different actions are selected. +The fields added by this script are handled by the :py:mod:`WorkflowWebTools.reasonsmanip` +and :py:mod:`WorkflowWebTools.manageactions` modules. + +.. todo:: + + This is some of the messiest code in the package. + Need to impliment some JSLint check. + +:author: Daniel Abercrombie +*/ + var count = 0; function getDefaultReason(num) { @@ -88,52 +104,125 @@ function checkReason(num) { } +function makeTable(entries, header) { + + header.unshift('Parameter'); + var paramTable = document.createElement('TABLE'); + var headerRow = paramTable.insertRow(0); + + for (var iData = 0; iData < header.length; iData++) { + var cell = headerRow.insertCell(iData); + cell.innerHTML = '' + header[iData] + ''; + } + + for (var iParam = 0; iParam < entries.length; iParam++) { + var row = paramTable.insertRow(iParam + 1); + var cell = row.insertCell(0); + cell.innerHTML = entries[iParam]; + for (var iOpt = 1; iOpt < header.length; iOpt++) { + cell = row.insertCell(iOpt); + if (header[iOpt] == 'Same' || header[iOpt] == 'False') + cell.innerHTML = ''; + else + cell.innerHTML = ''; + } + } + + return paramTable +} + function makeParamTable(action) { var paramDiv = document.getElementById('actionparams'); paramDiv.innerHTML = ''; paramDiv.style.padding = "10px"; - var paramTable = document.createElement('TABLE'); - var header = ['Parameter', 'Decrease', 'Same', 'Increase']; var params = []; + var bools = []; + var texts = []; + var opts = {}; if ( action.value == 'clone' ) { params = [ 'splitting', 'memory', 'timeout', - ] + ]; + bools = [ + 'invalidate', + ]; + texts = [ + 'group', + 'max_memory', + ]; } else if (action.value == 'recover') { params = [ 'memory', 'timeouts', - ] + ]; + bools = [ + 'replica', + 'trustsite' + ]; + texts = [ + 'LFN', + 'ERA', + 'procstring', + 'procversion', + ]; + opts = { + Activity: [ + 'reprocessing', + 'production', + 'test', + ] + }; + } else if (action.value == 'investigate') { + texts = [ + 'other', + ] } - var headerRow = paramTable.insertRow(0); + if (params.length != 0) { + paramDiv.appendChild(makeTable(params, ['Decrease', 'Same', 'Increase'])); + } - for (var iData = 0; iData < header.length; iData++) { - var cell = headerRow.insertCell(iData); - cell.innerHTML = header[iData]; + if (bools.length != 0) { + paramDiv.appendChild(makeTable(bools, ['True', 'False'])); } - for (var iParam = 0; iParam < params.length; iParam++) { - var row = paramTable.insertRow(iParam + 1); - var cell = row.insertCell(0); - cell.innerHTML = params[iParam]; - for (var iOpt = 1; iOpt < header.length; iOpt++) { - cell = row.insertCell(iOpt); - if (header[iOpt] == 'Same') - cell.innerHTML = ''; - else - cell.innerHTML = ''; + for (key in opts) { + var optionDiv = document.createElement("DIV"); + var keytext = document.createElement("b"); + keytext.innerHTML = key + ':
'; + optionDiv.appendChild(keytext); + for (opt in opts[key]) { + + var opttext = document.createTextNode(' ' + opts[key][opt] + ' '); + optionDiv.appendChild(opttext); + var option = document.createElement("INPUT"); + option.setAttribute("type", "radio"); + option.setAttribute("name", "param_" + key); + option.setAttribute("value", opts[key][opt]); + optionDiv.appendChild(option); + } + paramDiv.appendChild(optionDiv); } - if (params.length != 0) { + for (itext in texts) { + var inpDiv = document.createElement("DIV"); + inpDiv.style.padding = '0.5em'; + var text = document.createTextNode(texts[itext] + ' '); + var inp = document.createElement("INPUT"); + inp.setAttribute("type", "text"); + inp.setAttribute("name", "param_" + texts[itext]); + inpDiv.appendChild(text) + inpDiv.appendChild(inp) + paramDiv.appendChild(inpDiv) + } - paramDiv.appendChild(paramTable); + if (params.length != 0) { var checkDiv = document.createElement('DIV'); checkDiv.id = 'siteslistcheck'; diff --git a/runserver/static/js/checkuser.js b/runserver/static/js/checkuser.js index 7ec96d6..5eedce3 100644 --- a/runserver/static/js/checkuser.js +++ b/runserver/static/js/checkuser.js @@ -1,4 +1,26 @@ +/*""" +.. describe:: checkuser.js + +JavaScript functions that are used when registering +a new user or reseting a password. +The password check is not done by any other part of the system, +but all other checks are performed by the Python backend on +submission for security purposes. + +:author: Daniel Abercrombie +*/ + + function checkPassword () { + /*""" + .. function:: checkPassword() + + Checks input fields with the ids #firstword and #secondword. + If they match, sets the innerHTML of the div #confirmresult + with a green positive message. + Otherwise the div is filled with a red warning message. + */ + var firstpass = document.getElementById('firstword').value; var secondpass = document.getElementById('secondword').value; @@ -15,6 +37,14 @@ function checkPassword () { } function validateForm () { + /*""" + .. function:: validateForm() + + Checks the form for registering a new user. + The requirements for the form values are given + in :ref:`new-user-ref`. + */ + var form = document.forms['infoform']; var errorDir = document.getElementById('error'); errorDir.innerHTML = ''; diff --git a/runserver/static/js/piechart.js b/runserver/static/js/piechart.js index 6dab493..7a372be 100644 --- a/runserver/static/js/piechart.js +++ b/runserver/static/js/piechart.js @@ -1,12 +1,28 @@ -// List the colors for the pie chart. -// Will eventually want to link severity of errors to different colors +/*""" +.. describe:: piechart.js + +Contains the drawPieCharts function for the global errors page. + +:author: Daniel Abercrombie +*/ var colors = ["#ff0000", "#00ff00", "#0000ff", "#00ffff", "#ff00ff", "#ffff00", "#808080", "#808000", "#800080", "#008080", "#000000"]; -// This function finds multiple canvases and draws pie charts onto them +function drawPieCharts() { + /*""" + .. function:: drawPieCharts() + + This function finds multiple canvases and draws pie charts onto them. + The number of errors for each canvas are given as separate arguments. + The piecharts are split with the largest contributer always having the same color. + The radius of the piechart is a fraction of the canvas size, + determined by the following equation: + + .. math:: -function drawpiecharts() { + \frac{r}{r_{canvas}} = \frac{n_{errors}}{n_{errors} + 10} + */ var iChart; var canvases = document.getElementsByClassName("piechart"); var ncanvases = Math.min(canvases.length, arguments.length); diff --git a/runserver/templates/actionsubmitted.html b/runserver/templates/actionsubmitted.html index d8dbeb7..0037443 100644 --- a/runserver/templates/actionsubmitted.html +++ b/runserver/templates/actionsubmitted.html @@ -6,21 +6,21 @@

Submitted action to Unified with the following parameters:

-

User

+

User

${user} -

Workflows

+

Workflows

% for wf in workflows: ${wf}
% endfor -

Action

+

Action

${action} -

Parameters

+

Parameters

${params} -

Reasons

+

Reasons

% for reason in reasons:

${reason['short']}

${reason['long']}

diff --git a/runserver/templates/globalerror.html b/runserver/templates/globalerror.html index 857a749..802ac36 100644 --- a/runserver/templates/globalerror.html +++ b/runserver/templates/globalerror.html @@ -2,6 +2,7 @@ 4D Errors + <%include file="rotation_tables.html"/> - <%include file="rotation_tables.html"/> <% @@ -18,7 +18,7 @@ for info in errordata['pieinfo']: stringpieinfo.append(str(info)) %> - + % else: -
@@ -49,11 +49,15 @@ % if errordata['pievar'] == "stepname": ${row['name']} - <% - workflowstep = '/' + '/'.join(row['name'].split('
')) - workflowpd = workflowstep.split('/')[1] - %> + <% + workflowstep = '/' + '/'.join(row['name'].split('
')) + workflowpd = workflowstep.split('/')[1] + if workflowpd in acted_workflows: + bg_type = 'done' + else: + bg_type = 'todo' + %> +
${row['name']} diff --git a/runserver/templates/newuser.html b/runserver/templates/newuser.html index a3cf177..10f0814 100644 --- a/runserver/templates/newuser.html +++ b/runserver/templates/newuser.html @@ -1,52 +1,47 @@ - - - - - - - - +<%inherit file="simple.html"/> - +<%block name="title">New User Registration -

- On a successful submission, - you will get a confirmation page that an email was sent. -

+<%block name="scripts"> + + + -
- E-mail:
-
- Username:
-
- Password:
-
- Confirm Password:
- -
- -
+

+ On a successful submission, + you will get a confirmation page that an email was sent. +

-

- The username must only contain alphanumeric characters, - or it will not be added to the database. -

- +
+ E-mail:
+
+ Username:
+
+ Password:
+
+ Confirm Password:
+ +
+ +
-

- The email must end with one of the following: -

-
    - % for email in emails: - % if email[0] == '@': -
  • ${email} - % endif - % endfor -
+

+ The username must only contain alphanumeric characters, + or it will not be added to the database. +

+ -
+

+ The email must end with one of the following: +

+
    + % for email in emails: + % if email[0] == '@': +
  • ${email} + % endif + % endfor +
- - +
diff --git a/runserver/templates/simple.html b/runserver/templates/simple.html index f480b52..2c3b4b4 100644 --- a/runserver/templates/simple.html +++ b/runserver/templates/simple.html @@ -5,6 +5,7 @@ <%block name="title"/> + <%block name="scripts"/> @@ -13,6 +14,7 @@