From 2c4d6928330e2690a1125e237e06d1d9d01de978 Mon Sep 17 00:00:00 2001 From: sarismik Date: Tue, 24 Feb 2015 16:13:55 -0500 Subject: [PATCH] Mike Saris UI/UX enhancements --- public/css/style.less | 49 +++++++++++-- routes/index.js | 141 ++++++++++++++++++++++++------------- views/error.html | 12 ++++ views/layout.html | 51 +++++++------- views/status.html | 157 ++++++++++++++++++++++++++---------------- views/tasks.html | 108 +++++++++++++++++++---------- 6 files changed, 341 insertions(+), 177 deletions(-) create mode 100644 views/error.html diff --git a/public/css/style.less b/public/css/style.less index ba620aa..a10eb2b 100644 --- a/public/css/style.less +++ b/public/css/style.less @@ -1,7 +1,44 @@ -body { - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; -} - -a { - color: #00B7FF; +body { + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} + +hr { + display: block; + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-left: auto; + margin-right: auto; + border-style: inset; + border-width: 1px; +} + +pre { + margin:0; + padding:0; + background-color: silver; +} + +h3 { + margin:0; + padding:0; +} + +.inactive { + background-color: #ffdddd; +} +.active { + background-color: #ddffdd; +} +.stable { + color: #009900; +} +.unstable { + color: #CC0000; +} +.semistable { + color: #FFCC00; } \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index c545adc..a17d419 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,51 +1,94 @@ -var moment = require('moment'), - assert = require('assert'), - prettycron = require('prettycron') - - // All of these routes assume (and implicitly require) that req.apiClient has already been set by either userLoginRequired or userLoginOptional -exports.status = function(req, res){ - req.apiClient.get('/status', function(err, req, rez, result) { +var moment = require('moment'), + assert = require('assert'), + prettycron = require('prettycron') + + // All of these routes assume (and implicitly require) that req.apiClient has already been set by either userLoginRequired or userLoginOptional +exports.status = function(req, res){ + req.apiClient.get('/status', function(err, req, rez, result) { res.render('status.html',{ data: result, json: JSON.stringify }); - }); -}; - -exports.tasks = function(req, res){ - req.apiClient.get('/tasks'+(req.params.filter ? '/'+req.params.filter : ''), function(err, req, rez, result) { - assert.ifError(err); - // console.log(rez.body); - res.render('tasks.html',{ data: result, json: JSON.stringify, prettycron: prettycron, moment: moment }); - }); -}; -exports.runs = function(req, res){ - var from = moment().subtract(1, 'day').toISOString(); - - req.apiClient.get('/log?filter='+req.params.filter+'&runsOnly=true&from='+from, function(err, req, rez) { - // rez.body is a newline-separated list of JSON objects, and isn't JSON-parseable directly. Let's parse them ourselves. - var runs = []; - rez.body.split('\n').forEach(function(item, n) { - // console.log("Now doing "+n); - item && runs.push(JSON.parse(item)); - }); - // console.log(runs); - - - res.render('runs.html',{ data: runs, json: JSON.stringify, moment: moment }); - }); -}; -exports.log = function(req, res){ - var from = moment().subtract(1, 'day').toISOString(); - - - req.apiClient.get('/log?filter='+req.params.filter+'&from='+from, function(err, req, rez) { - var log_entries = []; - rez.body.split('\n').forEach(function(item, n) { - item && log_entries.push(JSON.parse(item)); - }); - // console.log(log_entries); - res.render('log.html',{ data: log_entries, json: JSON.stringify, moment: moment }); - }); -}; - -exports.login = function(req, res){ - res.render('login.html') + }); +}; + +exports.tasks = function(req, res){ + var apiCall = '/tasks'; + if (typeof req.params.filter == 'string') { + apiCall += '/'+req.params.filter; + } + req.apiClient.get(apiCall, function(err, req, rez, result) { + if (err) { + res.render('error.html', { message: 'You requested tasks for an invalid collection' }); + return; + } + // console.log(rez.body); + var collectionSuccessRates = new Map(); + var taskSuccessRates = new Map(); + result.collections.forEach(function(collection, n) { + var numTaskRunsInCollection = 0; + var numSuccessfulTaskRunsInCollection = 0; + for(var taskName in collection.tasks) { + if (collection.tasks.hasOwnProperty(taskName)) { + var task = collection.tasks[taskName]; + var numTaskRuns = 0; + var numSuccessfulTaskRuns = 0; + for (i = 0; i < task.recentExitCodes.length; i++) { + numTaskRunsInCollection++; + numTaskRuns++; + if (task.recentExitCodes[i] == 0) { + numSuccessfulTaskRunsInCollection++; + numSuccessfulTaskRuns++; + } + } + if (numTaskRuns != 0) { + taskSuccessRates.set(task.id, Math.round(numSuccessfulTaskRuns / numTaskRuns * 100)); + } + else { + taskSuccessRates.set(task.id, 0); + } + } + } + if (numTaskRunsInCollection != 0) { + collectionSuccessRates.set(collection.collection, + Math.round(numSuccessfulTaskRunsInCollection / numTaskRunsInCollection * 100)); + } + else { + collectionSuccessRates.set(collection.collection, 0); + } + }); + + res.render('tasks.html',{ data: result, json: JSON.stringify, prettycron: prettycron, moment: moment, + taskSuccessRates: taskSuccessRates, collectionSuccessRates: collectionSuccessRates}); + }); +}; +exports.runs = function(req, res){ + var from = moment().subtract(1, 'day').toISOString(); + + req.apiClient.get('/log?filter='+req.params.filter+'&runsOnly=true&from='+from, function(err, req, rez) { + // rez.body is a newline-separated list of JSON objects, and isn't JSON-parseable directly. Let's parse them ourselves. + var runs = []; + rez.body.split('\n').forEach(function(item, n) { + // console.log("Now doing "+n); + item && runs.push(JSON.parse(item)); + }); + // console.log(runs); + + + res.render('runs.html',{ data: runs, json: JSON.stringify, moment: moment }); + }); +}; +exports.log = function(req, res){ + var from = moment().subtract(1, 'day').toISOString(); + + + req.apiClient.get('/log?filter='+req.params.filter+'&from='+from, function(err, req, rez) { + var log_entries = []; + rez.body.split('\n').forEach(function(item, n) { + item && log_entries.push(JSON.parse(item)); + }); + // console.log(log_entries); + res.render('log.html',{ data: log_entries, json: JSON.stringify, moment: moment }); + }); +}; + +exports.login = function(req, res){ + res.render('login.html') } diff --git a/views/error.html b/views/error.html new file mode 100644 index 0000000..aa76ca4 --- /dev/null +++ b/views/error.html @@ -0,0 +1,12 @@ +{% extends 'layout.html' %} + +{% block bodyclass %}home{% endblock %} + + +{% block main %} + +

Sorry!

+ +{{ message }} + +{% endblock %} diff --git a/views/layout.html b/views/layout.html index ff51743..fcaf067 100644 --- a/views/layout.html +++ b/views/layout.html @@ -1,26 +1,27 @@ - - - - - {% block title %} - Ummon Web - {% endblock %} - - {% block css %} - - {% endblock %} - {% block js %} - - - {% endblock %} - - - - - - - {% block main %} - - {% endblock %} - + + + + + {% block title %} + Ummon Web + {% endblock %} + + {% block css %} + + {% endblock %} + {% block js %} + + + {% endblock %} + + + + + + + Home + {% block main %} + + {% endblock %} + \ No newline at end of file diff --git a/views/status.html b/views/status.html index d23624d..c1e874e 100644 --- a/views/status.html +++ b/views/status.html @@ -1,61 +1,100 @@ -{% extends 'layout.html' %} - -{% block bodyclass %}home{% endblock %} - - -{% block main %} - - - -

Status

+{% extends 'layout.html' %} + +{% block bodyclass %}home{% endblock %} + + +{% block main %} + + + +

ummon-web - Home

- -
Workers: {{data.workers.length}} active out of {{data.maxWorkers}} total
- - - -
Collections: (show all) - +
+ +
+ +
Tasks currently running: + +
+ +
+ +
Tasks currently in the queue: + +
+ + + +{% endblock %} + diff --git a/views/tasks.html b/views/tasks.html index 5e77959..953eaba 100644 --- a/views/tasks.html +++ b/views/tasks.html @@ -1,39 +1,71 @@ -{% extends 'layout.html' %} - -{% block bodyclass %}home{% endblock %} - - -{% block main %} - - - -

Tasks

- - -{% for collection in data.collections %} -
-

{{ collection.collection }}

- Tasks: - - {% for foo, task in collection.tasks %} -

{{task.id}}

View runs View logs - Last successfully run: {{moment(task.lastSuccessfulRun).format()}}
- Command:
{{task.command}}

- {% if task.trigger.time %} - Trigger: {{ prettycron.toString(task.trigger.time) }} - - {% elif task.trigger.after %} - Trigger: After completion of {{task.trigger.after}} - {% elif task.trigger.afterFailed %} - Trigger: After failure of {{task.trigger.afterFailed}} - {% endif %} - {% endfor %} - -
-{% endfor %} - +{% extends 'layout.html' %} + +{% block bodyclass %}home{% endblock %} + + +{% block main %} + +

ummon-web - Tasks

+ + +{% for collection in data.collections %} +
+ +

{{ collection.collection }} + {% set collectionSuccessRate = collectionSuccessRates.get(collection.collection) %} + {% if collectionSuccessRate == 100 %} +
+ {% elif collectionSuccessRate > 75 %} +
+ {% else %} +
+ {% endif %} + {{ collectionSuccessRate }}% +
+ {% if collection.config.enabled == false %} + This collection is DISABLED + {% else %} + This collection is enabled + {% endif %} +

+ + Tasks: + + {% for foo, task in collection.tasks %} +
+

{{task.id}} + {% if task.enabled == false %} + DISABLED + {% else %} + Enabled + {% endif %} + {% set taskSuccessRate = taskSuccessRates.get(task.id) %} + {% if taskSuccessRate == 100 %} +
+ {% elif taskSuccessRate > 75 %} +
+ {% else %} +
+ {% endif %} + {{ taskSuccessRate }}% +
+

+ View runs View logs + Last successfully run: {{moment(task.lastSuccessfulRun).format()}}
+
+ Command:
{{task.command}}
+
+ {% if task.trigger.time %} + Trigger: {{ prettycron.toString(task.trigger.time) }} + + {% elif task.trigger.after %} + Trigger: After completion of {{task.trigger.after}} + {% elif task.trigger.afterFailed %} + Trigger: After failure of {{task.trigger.afterFailed}} + {% endif %} +

+ {% endfor %} +
+{% endfor %} + {% endblock %} \ No newline at end of file