Commit 09ee2149 authored by Leo Iannacone's avatar Leo Iannacone

Merge branch 'feature/history' into develop

parents 11007a6c 960fff2d
...@@ -42,6 +42,10 @@ if config.routes.preferences ...@@ -42,6 +42,10 @@ if config.routes.preferences
if config.routes.commands if config.routes.commands
app.get(config.routes.commands, routes.commands) app.get(config.routes.commands, routes.commands)
# history page
if config.routes.history
app.get(config.routes.history, routes.history)
# debomatic static page # debomatic static page
if config.routes.debomatic if config.routes.debomatic
chroot_forbidden = (res) -> chroot_forbidden = (res) ->
......
...@@ -98,6 +98,7 @@ config.routes.debomatic = "/debomatic" ...@@ -98,6 +98,7 @@ config.routes.debomatic = "/debomatic"
config.routes.distribution = "/distribution" config.routes.distribution = "/distribution"
config.routes.preferences = "/preferences" config.routes.preferences = "/preferences"
config.routes.commands = "/commands" config.routes.commands = "/commands"
config.routes.history = "/history"
### ###
The events The events
......
fs = require("fs") fs = require("fs")
glob = require("glob")
config = require("./config") config = require("./config")
utils = require("./utils") utils = require("./utils")
Tail = utils.Tail Tail = utils.Tail
e = config.events.broadcast e = config.events.broadcast
_get_distributions = (callback) -> _get_distributions = (callback) ->
utils.get_files_list config.debomatic.path, true, (directories) -> glob "#{config.debomatic.path}/*/pool", {}, (err, directories) ->
distributions = [] distributions = []
for dir in directories for dir in directories
data = {} name = dir.split('/')[-2..][0]
data.distribution = {} distributions.push name
data.distribution.name = dir
pool_path = utils.get_distribution_pool_path(data)
distributions.push dir if fs.existsSync(pool_path)
callback(distributions) callback(distributions)
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"ejs": "1.*", "ejs": "1.*",
"socket.io": "1.*", "socket.io": "1.*",
"tail": "*", "tail": "*",
"glob": "*",
"extend": "*" "extend": "*"
}, },
"scripts": { "scripts": {
......
...@@ -4,18 +4,16 @@ ...@@ -4,18 +4,16 @@
/* global Preferences: false */ /* global Preferences: false */
/* global Page_Generic: false */ /* global Page_Generic: false */
/* global Page_Distrubion: false */ /* global Page_Distrubion: false */
/* global Page_History: false */
'use strict'; 'use strict';
var preferences = new Preferences(); var preferences = new Preferences(),
page_generic = new Page_Generic();
var page_generic = new Page_Generic();
if (window.location.pathname == config.paths.preferences) { if (window.location.pathname == config.paths.preferences) {
preferences.initPage(); preferences.initPage();
} } else if (window.location.pathname == '/') {
if (window.location.pathname == '/') {
// convert email addresses in the right format // convert email addresses in the right format
var emails = $('.email'); var emails = $('.email');
$.each(emails, function () { $.each(emails, function () {
...@@ -31,11 +29,12 @@ if (window.location.pathname == '/') { ...@@ -31,11 +29,12 @@ if (window.location.pathname == '/') {
$(this).html(real_email); $(this).html(real_email);
}); });
} }
var socket = io.connect('/'); var socket = io.connect('/');
page_generic.start(socket); page_generic.start(socket);
if (window.location.pathname == config.paths.distribution) { if (window.location.pathname == config.paths.distribution) {
new Page_Distrubion(socket).start(); new Page_Distrubion(socket).start();
} else if (window.location.pathname == config.paths.history) {
new Page_History().start(socket);
} }
...@@ -355,24 +355,6 @@ function Page_Distrubion(socket) { ...@@ -355,24 +355,6 @@ function Page_Distrubion(socket) {
return; return;
} }
function _get_two_digits(num) {
return ("0" + num).slice(-2);
}
function _get_time(timestamp) {
var date = new Date(timestamp * 1000);
var locale = navigator.language || 'en-US';
var options = {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
};
var result = date.toLocaleDateString(locale, options);
result += ' <b>' + _get_two_digits(date.getHours()) + ':' + _get_two_digits(date.getMinutes()) + '</b>';
return result;
}
if (socket_data.hasOwnProperty('files')) { if (socket_data.hasOwnProperty('files')) {
var s_files = socket_data.files; var s_files = socket_data.files;
for (var file in s_files) { for (var file in s_files) {
...@@ -390,15 +372,16 @@ function Page_Distrubion(socket) { ...@@ -390,15 +372,16 @@ function Page_Distrubion(socket) {
if (socket_data.uploader) if (socket_data.uploader)
info += "Uploaded by " + socket_data.uploader + ' - '; info += "Uploaded by " + socket_data.uploader + ' - ';
info += "Build started " + _get_time(socket_data.start); info += "Build started " + Utils.format_time(socket_data.start, true);
if (socket_data.end) { if (socket_data.end) {
info += ' - finished ' + _get_time(socket_data.end); info += ' - finished ' + Utils.format_time(socket_data.end, true);
info += ' - elapsed time: <b>'; info += ' - elapsed time: <b>';
var elapsed = new Date((socket_data.end - socket_data.start) * 1000); var elapsed = new Date((socket_data.end - socket_data.start) * 1000);
info += _get_two_digits(elapsed.getUTCHours()) + ':'; var tot_hours = (elapsed.getUTCDate() - 1) * 24 + elapsed.getUTCHours();
info += _get_two_digits(elapsed.getUTCMinutes()) + ':'; info += Utils.num_two_digits(tot_hours) + ':';
info += _get_two_digits(elapsed.getUTCSeconds()); info += Utils.num_two_digits(elapsed.getUTCMinutes()) + ':';
info += Utils.num_two_digits(elapsed.getUTCSeconds());
} }
$("#package_info").html(info); $("#package_info").html(info);
...@@ -805,7 +788,7 @@ function Page_Distrubion(socket) { ...@@ -805,7 +788,7 @@ function Page_Distrubion(socket) {
// Init sticky-package back_on_top on click // Init sticky-package back_on_top on click
$('#sticky-package').on('click', function () { $('#sticky-package').on('click', function () {
back_on_top_pressed = true; back_on_top_pressed = true;
debug(1, 'back on top pressed, disabling autoscroll') debug(1, 'back on top pressed, disabling autoscroll');
page.go.up(100); page.go.up(100);
}); });
......
...@@ -13,13 +13,6 @@ function Page_Generic() { ...@@ -13,13 +13,6 @@ function Page_Generic() {
return result; return result;
} }
function __get_status_html_href(status_data) {
var result = config.paths.distribution + '#' + status_data.distribution;
if (status_data.hasOwnProperty('package'))
result += '/' + status_data.package.replace('_', '/') + '/buildlog';
return result;
}
function __get_status_html_title(status_data) { function __get_status_html_title(status_data) {
var result = status_data.status + ': ' + status_data.distribution; var result = status_data.status + ': ' + status_data.distribution;
if (status_data.hasOwnProperty('package')) if (status_data.hasOwnProperty('package'))
...@@ -43,7 +36,7 @@ function Page_Generic() { ...@@ -43,7 +36,7 @@ function Page_Generic() {
button.addClass('btn btn-xs'); button.addClass('btn btn-xs');
button.addClass(_s.status); button.addClass(_s.status);
button.attr('title', __get_status_html_title(_s)); button.attr('title', __get_status_html_title(_s));
button.attr('href', __get_status_html_href(_s)); button.attr('href', Utils.get_url_to_package(_s));
button.html(__get_status_html_inner(_s)); button.html(__get_status_html_inner(_s));
var info = Utils.get_status_icon_and_class(_s); var info = Utils.get_status_icon_and_class(_s);
button.addClass('btn-' + info.className); button.addClass('btn-' + info.className);
......
'use strict';
/* global Utils: false */
/* global Chartist: false */
/* global debug: false */
/* global debug_socket: false */
/* global dom_history: false */
function Page_History() {
var distributions_counter = {};
var days_counter = {};
var all_distributions = [];
var all_days = [];
function _get_short_day(timestamp) {
var date = new Date(timestamp * 1000);
var locale = navigator.language || 'en-US';
var options = {
month: "numeric",
day: "numeric",
};
return date.toLocaleDateString(locale, options);
}
function _get_id(package_status) {
var p = package_status;
return "package/" + p.distribution + "/" + p.package;
}
function _add_row(package_status) {
var p = package_status;
if (!p.hasOwnProperty('success'))
return;
var info = Utils.get_status_icon_and_class(p);
var label = info.label || 'building';
var distribution_url = Utils.get_url_to_package({
'distribution': p.distribution
});
var row = '<tr id="' + _get_id(package_status) + '">';
var package_url = Utils.get_url_to_package(p);
row += '<td><a href="' + distribution_url + '">' + p.distribution + '</a></td>';
row += '<td><a href="' + package_url + '">' + p.package + '</td>';
row += '<td>' + Utils.format_time(p.start, false, true) + '</td>';
row += '<td>' + Utils.format_time(p.end, false, true) + '</td>';
row += '<td>' + p.uploader + '</td>';
row += '<td class="' + info.className + ' text-' + info.className + '">' + label + '</td>';
row += '</tr>';
$('table tbody').append(row);
}
function _count_distributions(package_status) {
var p = package_status;
if (distributions_counter.hasOwnProperty(p.distribution))
distributions_counter[p.distribution]++;
else distributions_counter[p.distribution] = 1;
if (all_distributions.indexOf(p.distribution) < 0)
all_distributions.push(p.distribution);
}
function _count_days(package_status) {
var p = package_status;
var day = _get_short_day(p.start);
if (days_counter.hasOwnProperty(p.distribution)) {
if (days_counter[p.distribution].hasOwnProperty(day))
days_counter[p.distribution][day]++;
else
days_counter[p.distribution][day] = 1;
} else {
days_counter[p.distribution] = {};
days_counter[p.distribution][day] = 1;
}
if (all_days.indexOf(day) < 0)
all_days.push(day);
}
function _sort_table() {
// create the theme for tablesorter
$.extend($.tablesorter.themes.bootstrap, {
table: 'table table-condensed table-bordered table-striped',
caption: 'caption',
header: 'bootstrap-header',
sortNone: 'bootstrap-icon-unsorted',
sortAsc: 'glyphicon glyphicon-chevron-up',
sortDesc: 'glyphicon glyphicon-chevron-down',
});
// call the tablesorter plugin and apply the uitheme widget
$("table").tablesorter({
theme: "bootstrap",
widthFixed: true,
headerTemplate: '{content} {icon}',
widgets: ["uitheme", "filter"],
sortList: [
[3, 1]
]
});
// add some fancy class to input fields
$("table input").addClass('form-control');
$("table select").addClass('form-control');
}
function _create_graph_distributions() {
// build the distribution Pie graph
var distributions_data = {
series: [],
labels: []
};
for (var distro in distributions_counter) {
if (distributions_counter.hasOwnProperty(distro)) {
distributions_data.series.push(distributions_counter[distro]);
distributions_data.labels.push(distro + " (" + distributions_counter[distro] + ")");
}
}
Chartist.Pie('#distributions-chart', distributions_data, {
donut: true,
donutWidth: 15,
});
}
function _create_graph_days() {
// build the days Line graph
var days_data = {
series: [],
labels: all_days
};
for (var i = 0; i < all_distributions.length; i++) {
var info = [];
var distro = all_distributions[i];
for (var j = 0; j < all_days.length; j++) {
var day = all_days[j];
if (days_counter[distro].hasOwnProperty(day))
info.push(days_counter[distro][day]);
else
info.push(0);
}
days_data.series.push(info);
}
Chartist.Line('#days-chart', days_data);
}
this.start = function (socket) {
socket.on(config.events.broadcast.status_update, function (socket_data) {
// TODO - implements _update_table
});
};
// init table and some objects
for (var i = 0; i < dom_history.length; i++) {
var p = dom_history[i];
_add_row(p);
// count stats
_count_distributions(p);
_count_days(p);
}
_sort_table();
_create_graph_distributions();
_create_graph_days();
}
...@@ -58,13 +58,16 @@ var Utils = { ...@@ -58,13 +58,16 @@ var Utils = {
var _s = status_data; var _s = status_data;
var className = null; var className = null;
var icon = null; var icon = null;
var label = null;
if (_s.hasOwnProperty('success')) { if (_s.hasOwnProperty('success')) {
if (_s.success === true) { if (_s.success === true) {
className = _c.success; className = _c.success;
icon = _i.success; icon = _i.success;
label = 'success';
} else { } else {
className = _c.fail; className = _c.fail;
icon = _i.fail; icon = _i.fail;
label = 'fail';
} }
} else { } else {
className = _c[_s.status]; className = _c[_s.status];
...@@ -79,7 +82,8 @@ var Utils = { ...@@ -79,7 +82,8 @@ var Utils = {
return { return {
className: className, className: className,
icon: icon icon: icon,
label: label
}; };
}, },
...@@ -105,5 +109,45 @@ var Utils = { ...@@ -105,5 +109,45 @@ var Utils = {
"/": "&#x2F;" "/": "&#x2F;"
}[s]; }[s];
}); });
},
// returns a two digits num
num_two_digits: function (num) {
return ("0" + num).slice(-2);
},
// format time from a timestamp
format_time: function (timestamp, time_in_bold, short) {
if (!timestamp)
return '';
var date = new Date(timestamp * 1000);
var locale = navigator.language || 'en-US';
var options = null;
if (short)
options = {
year: "numeric",
month: "numeric",
day: "numeric",
};
else
options = {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
};
var result_date = date.toLocaleDateString(locale, options);
var result_time = Utils.num_two_digits(date.getHours()) + ':' + Utils.num_two_digits(date.getMinutes());
if (time_in_bold) result_time = '<b>' + result_time + '</b>';
return result_date + ' ' + result_time;
},
// get href url from status object
get_url_to_package: function (status_data) {
var result = config.paths.distribution + '#' + status_data.distribution;
if (status_data.hasOwnProperty('package'))
result += '/' + status_data.package.replace('_', '/') + '/buildlog';
return result;
} }
}; };
...@@ -257,3 +257,14 @@ footer .info { ...@@ -257,3 +257,14 @@ footer .info {
padding-top: 10px; padding-top: 10px;
cursor: pointer; cursor: pointer;
} }
.ct-chart {
height: 180px;
width: 50%;
float: left;
margin-bottom: 30px;
}
.ct-chart .ct-label {
font-size: 0.9em;
}
"use strict" glob = require("glob")
config = require("../lib/config") config = require("../lib/config")
fs = require("fs")
exports.index = (req, res) -> exports.index = (req, res) ->
res.render "index", config res.render "index", config
return return
...@@ -15,3 +17,20 @@ exports.preferences = (req, res) -> ...@@ -15,3 +17,20 @@ exports.preferences = (req, res) ->
exports.commands = (req, res) -> exports.commands = (req, res) ->
res.render "commands", config res.render "commands", config
return return
exports.history = (req, res) ->
glob "#{config.debomatic.path}/*/pool/*/*.json", {}, (err, files) ->
get_info = (json_path) ->
json = JSON.parse(fs.readFileSync(json_path, 'utf8'))
delete json.files
return json
compare = (a,b) ->
if a.end < b.end
return -1
if a.end > b.end
return 1
return 0
config.history = (get_info(f) for f in files)
config.history.sort(compare)
res.render "history", config
...@@ -25,6 +25,30 @@ get_jquery() { ...@@ -25,6 +25,30 @@ get_jquery() {
cd .. cd ..
} }
get_tablesorter() {
DIR_TABLESORTER="tablesorter"
if [ -d ${EXT_LIBS_DIR}/${DIR_TABLESORTER} ] ; then return ; fi
mkdir ${DIR_TABLESORTER}
cd ${DIR_TABLESORTER}
echo "Downloading tablesorter ..."
curl -s -O -L "http://mottie.github.io/tablesorter/js/jquery.tablesorter.min.js"
curl -s -O -L "http://mottie.github.io/tablesorter/js/jquery.tablesorter.widgets.min.js"
curl -s -O -L "http://mottie.github.io/tablesorter/css/theme.bootstrap.css"
cd ..
}
get_chartist() {
VERSION="0.1.11"
NAME="chartist-js-${VERSION}"
if [ -d ${EXT_LIBS_DIR}/${NAME} ] ; then return ; fi
ARCHIVE=v${VERSION}.zip
URL="https://github.com/gionkunz/chartist-js/archive/${ARCHIVE}"
echo "Downloading chartist-js ${VERSION} ..."
curl -s -O -L ${URL} && \
unzip -q ${ARCHIVE} && rm ${ARCHIVE}
}
if [ ! -d ${EXT_LIBS_DIR} ] ; then mkdir -p ${EXT_LIBS_DIR} ; fi if [ ! -d ${EXT_LIBS_DIR} ] ; then mkdir -p ${EXT_LIBS_DIR} ; fi
TMP_DIR="`mktemp -d`" TMP_DIR="`mktemp -d`"
...@@ -32,6 +56,8 @@ cd ${TMP_DIR} ...@@ -32,6 +56,8 @@ cd ${TMP_DIR}
get_jquery get_jquery
get_bootstrap get_bootstrap
get_tablesorter
get_chartist
if [ "`ls -1`" != "" ] ; then mv * ${EXT_LIBS_DIR} ; fi if [ "`ls -1`" != "" ] ; then mv * ${EXT_LIBS_DIR} ; fi
cd && rm -r ${TMP_DIR} cd && rm -r ${TMP_DIR}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div id="smile"></div> <div id="smile"></div>
<footer id="footer" class="container-fluid"> <footer id="footer" class="container-fluid">
<small onclick="window.open('https://github.com/debomatic/debomatic-webui','_blank');" class="copyright pull-right text-muted"> <small onclick="window.open('https://github.com/debomatic/debomatic-webui','_blank');" class="copyright pull-right text-muted">
Fork me on <a href="https://github.com/debomatic/debomatic-webui">github</a> Fork me on <a>github</a>
</small> </small>
<small class="info pull-right text-muted">v<%= version %> &bull;</small> <small class="info pull-right text-muted">v<%= version %> &bull;</small>
<div id="status" class="clearfix"> <div id="status" class="clearfix">
...@@ -14,9 +14,12 @@ ...@@ -14,9 +14,12 @@
</div> </div>
</footer> </footer>
<script src='/socket.io/socket.io.js?<%= version %>'></script> <script src="/socket.io/socket.io.js?<%= version %>"></script>
<script src='/external_libs/jquery/jquery-1.11.0.min.js'></script> <script src="/external_libs/jquery/jquery-1.11.0.min.js"></script>
<script src="/external_libs/tablesorter/jquery.tablesorter.min.js"></script>
<script src="/external_libs/tablesorter/jquery.tablesorter.widgets.min.js"></script>
<script src="/external_libs/bootstrap-3.2.0-dist/js/bootstrap.min.js"></script> <script src="/external_libs/bootstrap-3.2.0-dist/js/bootstrap.min.js"></script>
<script src="/external_libs/chartist-js-0.1.11/libdist/chartist.min.js"></script>
<script> <script>
var config = <%- JSON.stringify(web) %> var config = <%- JSON.stringify(web) %>
...@@ -46,6 +49,7 @@ ...@@ -46,6 +49,7 @@
<script src='/javascripts/preferences.js?<%= version %>'></script> <script src='/javascripts/preferences.js?<%= version %>'></script>
<script src='/javascripts/page_generic.js?<%= version %>'></script> <script src='/javascripts/page_generic.js?<%= version %>'></script>
<script src='/javascripts/page_distribution.js?<%= version %>'></script> <script src='/javascripts/page_distribution.js?<%= version %>'></script>
<script src='/javascripts/page_history.js?<%= version %>'></script>
<script src='/javascripts/main.js?<%= version %>'></script> <script src='/javascripts/main.js?<%= version %>'></script>
</body> </body>
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/external_libs/bootstrap-3.2.0-dist/css/bootstrap.min.css"> <link rel="stylesheet" href="/external_libs/bootstrap-3.2.0-dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/external_libs/bootstrap-3.2.0-dist/css/bootstrap-theme.min.css"> <link rel="stylesheet" href="/external_libs/bootstrap-3.2.0-dist/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/external_libs/chartist-js-0.1.11/libdist/chartist.min.css">
<link rel="stylesheet" href="/external_libs/tablesorter/theme.bootstrap.css">
<link rel="stylesheet" href="/stylesheets/style.css?<%= version %>"> <link rel="stylesheet" href="/stylesheets/style.css?<%= version %>">
</head> </head>
...@@ -22,6 +24,9 @@ ...@@ -22,6 +24,9 @@
</div> </div>
<div id="pages"> <div id="pages">
<ul class="nav navbar-nav pull-right"> <ul class="nav navbar-nav pull-right">
<% if (web.paths.history) { %>
<li><a href="<%= web.paths.history %>">History</a></li>
<% } %>
<% if (web.paths.commands) { %> <% if (web.paths.commands) { %>
<li><a href="<%= web.paths.commands %>">Commands</a></li> <li><a href="<%= web.paths.commands %>">Commands</a></li>
<% } %> <% } %>
......
<% include header.ejs %>
<article class="page">
<header><h2>History</h2></header>
<p class="lead text-muted">
The log of packages
</p>
<script> var dom_history = <%- JSON.stringify(history) %> </script>
<div id="charts">
<div id="days-chart" class="ct-chart"></div>
<div id="distributions-chart" class="ct-chart"></div>
</div>
<table id="history" class="tablesorter">
<thead>
<tr>
<th class="filter-select filter-exact" data-placeholder="All">Distribution</th>
<th>Package</th>
<th data-empty="top" data-placeholder=">= 01/01/1990">Start</th>
<th data-empty="top" data-placeholder=">= 01/01/1990">End</th>
<th>Uploader</th>
<th class="filter-select filter-exact" data-placeholder="All">Status</th>
</tr>
</thead>
<tbody></tbody>
</table>
</article>
<% include footer.ejs %>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment