Commit 9420750c authored by Leo Iannacone's avatar Leo Iannacone

Merge branch 'sbuild' into portable

parents b5212a6d 9e3de8c7
debomatic-webui
===============
**debomatic-webui** is a web interface for [Deb-o-Matic](https://launchpad.net/debomatic) aims to give to users a simple way to browse logs and to know what's going on debomatic build service providing a real-time packages status.
**debomatic-webui** is a web interface for [Deb-o-Matic](http://debomatic.github.io) aims to give to users a simple way to browse logs and to know what's going on debomatic build service providing a real-time packages status.
This interface is built up on [node](http://nodejs.org/) platform and uses intensely [socket.io](http://socket.io/) and [jquery](http://jquery.com/) technologies.
Whenever you want to leave a suggestion or file a bug report, please open a [new issue](https://github.com/LeoIannacone/debomatic-webui/issues).
......
......@@ -29,9 +29,11 @@ from time import time
from json import dumps as toJSON
from json import load as fileToJSON
from collections import defaultdict
from hashlib import md5
class DebomaticModule_JSONLoggerStart:
def __init__(self):
self.logger = DebomaticModule_JSONLogger()
self.first = True
......@@ -44,6 +46,7 @@ class DebomaticModule_JSONLoggerStart:
class DebomaticModule_JSONLoggerStop:
def __init__(self):
self.logger = DebomaticModule_JSONLogger()
self.last = True
......@@ -64,30 +67,33 @@ class DebomaticModule_JSONLogger:
def _set_json_logfile_name(self, args):
"""If debomatic config file has section [jsonlogger] try to get
'jsonfile' option and override the default value."""
if 'opts' in args and \
args['opts'].has_section('jsonlogger') and \
args['opts'].has_option('jsonlogger', 'jsonfile'):
self.jsonfile = args['opts'].get('jsonlogger', 'jsonfile').strip()
if (args.opts.has_section('jsonlogger') and
args.opts.has_option('jsonlogger', 'jsonfile')):
self.jsonfile = args.opts.get('jsonlogger', 'jsonfile').strip()
def _get_package_json_filename(self, args):
"""Get the path of package JSON file"""
return '%(directory)s/pool/%(package)s/%(package)s.json' % args
return ('%(directory)s/pool/%(package)s/%(package)s.json' %
{'directory': args.directory, 'package': args.package})
def _get_distribution_status(self, args):
def _get_distribution_status(self, args, with_success=False):
"""From args to distribution status"""
status = {}
status['status'] = args['cmd']
status['distribution'] = args['distribution']
if 'success' in args:
status['success'] = args['success']
status['status'] = args.action
status['distribution'] = args.distribution
if with_success:
status['success'] = args.success
return status
def _get_package_status(self, args):
"""From args to package status"""
status = {}
for k in ['package', 'distribution', 'uploader']:
if k in args:
status[k] = args[k]
status['package'] = args.package
status['distribution'] = args.distribution
status['uploader'] = args.uploader
if args.uploader != "":
email = args.uploader.lower().encode('utf-8')
status['gravatar'] = md5(email).hexdigest()
return status
def _append_json_logfile(self, args, status):
......@@ -126,11 +132,15 @@ class DebomaticModule_JSONLogger:
num /= 1024.0
def pre_chroot(self, args):
if args.action is None:
return
distribution = self._get_distribution_status(args)
self._append_json_logfile(args, distribution)
def post_chroot(self, args):
distribution = self._get_distribution_status(args)
if args.action is None:
return
distribution = self._get_distribution_status(args, with_success=True)
self._append_json_logfile(args, distribution)
def pre_build(self, args):
......@@ -145,18 +155,19 @@ class DebomaticModule_JSONLogger:
def post_build(self, args):
status = self._get_package_status(args)
status['status'] = 'build'
status['success'] = args['success']
status['success'] = args.success
status['files'] = {}
resultdir = os.path.join(args['directory'], 'pool', args['package'])
resultdir = os.path.join(args.directory, 'pool', args.package)
for filename in os.listdir(resultdir):
if filename.endswith('.json'):
continue
full_path = os.path.join(resultdir, filename)
info = {}
info['size'] = self._get_human_size(os.path.getsize(full_path))
tag = LogParser(full_path).parse()
tag, level = LogParser(full_path).parse()
if tag:
info['tags'] = tag
info['level'] = level
status['files'][filename] = info
self._write_package_json(args, status)
status.pop('files', None)
......@@ -165,6 +176,7 @@ class DebomaticModule_JSONLogger:
# Parser for log files
class LogParser():
def __init__(self, file_path):
self.file = file_path
self.basename = os.path.basename(file_path)
......@@ -173,16 +185,18 @@ class LogParser():
def parse(self):
if not os.path.isfile(self.file):
return None
result = None
tag = None
# level can be: info, warning, danger
level = "info" # by default
if self.extension == 'lintian':
result = self.parse_lintian()
tag, level = self.parse_lintian()
elif self.extension == 'autopkgtest':
result = self.parse_autopkgtest()
tag, level = self.parse_autopkgtest()
elif self.extension == 'piuparts':
result = self.parse_piuparts()
tag, level = self.parse_piuparts()
elif self.extension == 'blhc':
result = self.parse_blhc()
return result
tag, level = self.parse_blhc()
return tag, level
def parse_lintian(self):
tags = defaultdict(int)
......@@ -190,7 +204,13 @@ class LogParser():
for line in fd:
if len(line) >= 2 and line[0] != 'N' and line[1] == ':':
tags[line[0]] += 1
return self._from_tags_to_result(tags)
tags = self._from_tags_to_result(tags)
level = "info"
if 'E' in tags:
level = "danger"
elif 'W' in tags:
level = "warning"
return tags, level
def parse_autopkgtest(self):
tags = defaultdict(int)
......@@ -211,14 +231,14 @@ class LogParser():
tags[info[0]] += 1
elif found and line == '\n':
break
return self._from_tags_to_result(tags)
return self._from_tags_to_result(tags), 'danger'
def parse_piuparts(self):
with open(self.file, 'r') as fd:
lines = fd.readlines()
if len(lines) == 0 or lines[-1].find('ERROR:') >= 0:
return 'E'
return None
return 'E', 'danger'
return None, None
def parse_blhc(self):
tags = defaultdict(int)
......@@ -229,7 +249,7 @@ class LogParser():
continue
tag = info[0].replace('FLAGS', '')
tags[tag] += 1
return ' '.join(sorted(list(tags.keys())))
return ' '.join(sorted(list(tags.keys()))), 'warning'
def _from_tags_to_result(self, tags):
keys = sorted(list(tags.keys()))
......
......@@ -7,6 +7,7 @@ io = require("socket.io")(server)
serve_static = require("serve-static")
serve_index = require("serve-index")
compression = require("compression")
errorhandler = require("errorhandler")
routes = require("../routes")
......@@ -24,6 +25,9 @@ if env is "development"
else
app.use(errorhandler())
# use compression by default
app.use(compression())
# the views
app.set("views", __dirname + "/../views")
app.set("view engine", "ejs")
......@@ -66,6 +70,15 @@ if config.routes.debomatic
res.set('Content-Type', 'text/plain')
next()
# always download log files and some source files, like .dsc and .changes ones
app.all config.routes.debomatic + '/:distribution/pool/:package/:file', (req, res, next) ->
type = utils.file_type(req.params.file)
ext = req.params.file.split('.').pop()
if type is "log" or ext in ["changes", "dsc"]
res.set('Content-Type', 'text/plain')
res.set('Content-Disposition', 'attachment; filename=' + req.params.file)
next()
app.use(config.routes.debomatic, serve_static(config.debomatic.path))
app.use(config.routes.debomatic, serve_index(config.debomatic.path,
{view: "details", icons: true}))
......@@ -73,9 +86,6 @@ if config.routes.debomatic
# serve stylesheet-javascript
app.use(serve_static(__dirname + "/../public"))
# serve dsc files as octet-stream
serve_static.mime.define("application/octet-stream": ["dsc"])
# Listening
server.listen config.port, config.host, null, (err) ->
......
......@@ -20,9 +20,10 @@ get_files_list_from_package = (data, callback) ->
config.routes.debomatic)
file.orig_name = f
file.name = f.split("_")[0]
if file.extension in ["deb", "ddeb", "udeb"]
type = utils.file_type(f)
if type is "deb"
data.package.debs.push(file)
else if file.extension in ["changes", "dsc"] or f.indexOf('.tar.') > 0 or f.indexOf('.diff.') > 0
else if type is "source"
file.name = f.replace(data.package.orig_name + ".", "")
if file.extension is "changes"
file.name = f.split('_').pop()
......@@ -73,7 +74,7 @@ class Client
pack.orig_name = p
read_package_status {distribution: data.distribution, package: pack}, (content) =>
for attr of content
if attr not in ['distribution', 'package', 'status', 'success']
if attr not in ['distribution', 'package', 'status', 'success', 'gravatar']
delete content[attr]
@socket.emit e.distribution_packages_status, content
data.distribution.packages.push pack
......
......@@ -37,8 +37,8 @@ config.debomatic.jsonfile = "/var/log/debomatic-json.log"
Web template configuration
Title and description for the header
###
config.web.debomatic.architecture = "amd64"
config.web.title = "Deb-o-Matic " + config.web.debomatic.architecture
config.web.debomatic.architecture = "system" # or amd64, i386, ...
config.web.title = "Deb-o-Matic"
###
Admin email and name to show in the home page.
......@@ -88,6 +88,7 @@ List of files to not show in webui
config.debomatic.excluded_files = [
"datestamp"
"json"
"build"
]
###
......@@ -163,6 +164,12 @@ try
crypto.createHash("sha256")
.update(config.debomatic.path)
.digest("hex")
if config.web.debomatic.architecture == "system"
check = "dpkg-architecture -qDEB_BUILD_ARCH"
require("child_process").exec check, (error, stdout, stderr) ->
config.web.debomatic.architecture = stdout.trim()
module.exports = config
catch err
......
......@@ -5,6 +5,8 @@ glob = require("glob")
Tail = require("tail").Tail
_check_no_backward = (backward_path) ->
if backward_path is undefined
return false
if typeof backward_path is 'string'
return backward_path.indexOf("..") < 0
return true
......@@ -33,7 +35,7 @@ get_distributions = (callback) ->
if err
errors_handler "get_distributions", err
return
distributions = (dir.split(path.sep)[-2...-1] for dir in directories)
distributions = (dir.split(path.sep)[-2...-1].pop() for dir in directories)
callback(distributions)
get_distribution_pool_path = (data) ->
......@@ -117,6 +119,14 @@ errors_handler = (from, err, socket) ->
socket.emit config.events.error, msg if socket
return
file_type = (filename) ->
extension = filename.split(".").pop()
if extension in ["deb", "ddeb", "udeb"]
return "deb"
if extension in ["changes", "dsc"] or filename.indexOf('.tar.') > 0 or filename.indexOf('.diff.') > 0
return "source"
return "log"
Tail::watchEvent = (e) ->
_this = this
if e is "change"
......@@ -152,5 +162,6 @@ module.exports.get_file_path = get_file_path
module.exports.get_files_list = get_files_list
module.exports.watch_path_onsocket = watch_path_onsocket
module.exports.errors_handler = errors_handler
module.exports.file_type = file_type
module.exports.arrayEqual = arrayEqual
module.exports.Tail = Tail
Copyright (c) 2009-2014 Jeremy Ashkenas
Copyright (c) 2009-2015 Jeremy Ashkenas
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
......
{
"name": "coffee-script",
"version": "1.9.1",
"main": [
"lib/coffee-script/coffee-script.js"
],
"description": "Unfancy JavaScript",
"keywords": [
"javascript",
"language",
"coffeescript",
"compiler"
],
"devDependencies": {
"uglify-js": "~2.2",
"jison": ">=0.2.0",
"highlight.js": "~8.0.0",
"underscore": "~1.5.2",
"docco": "~0.6.2"
},
"author": {
"name": "Jeremy Ashkenas"
},
"ignore": [
"test"
]
}
// Generated by CoffeeScript 1.8.0
// Generated by CoffeeScript 1.9.1
(function() {
var CoffeeScript, compile, runScripts,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
CoffeeScript = require('./coffee-script');
......@@ -34,14 +34,14 @@
if ((typeof btoa !== "undefined" && btoa !== null) && (typeof JSON !== "undefined" && JSON !== null) && (typeof unescape !== "undefined" && unescape !== null) && (typeof encodeURIComponent !== "undefined" && encodeURIComponent !== null)) {
compile = function(code, options) {
var js, v3SourceMap, _ref;
var js, ref, v3SourceMap;
if (options == null) {
options = {};
}
options.sourceMap = true;
options.inline = true;
_ref = CoffeeScript.compile(code, options), js = _ref.js, v3SourceMap = _ref.v3SourceMap;
return "" + js + "\n//# sourceMappingURL=data:application/json;base64," + (btoa(unescape(encodeURIComponent(v3SourceMap)))) + "\n//# sourceURL=coffeescript";
ref = CoffeeScript.compile(code, options), js = ref.js, v3SourceMap = ref.v3SourceMap;
return js + "\n//# sourceMappingURL=data:application/json;base64," + (btoa(unescape(encodeURIComponent(v3SourceMap)))) + "\n//# sourceURL=coffeescript";
};
}
......@@ -60,9 +60,9 @@
xhr.overrideMimeType('text/plain');
}
xhr.onreadystatechange = function() {
var param, _ref;
var param, ref;
if (xhr.readyState === 4) {
if ((_ref = xhr.status) === 0 || _ref === 200) {
if ((ref = xhr.status) === 0 || ref === 200) {
param = [xhr.responseText, options];
if (!hold) {
CoffeeScript.run.apply(CoffeeScript, param);
......@@ -79,19 +79,19 @@
};
runScripts = function() {
var coffees, coffeetypes, execute, i, index, s, script, scripts, _fn, _i, _len;
var coffees, coffeetypes, execute, fn, i, index, j, len, s, script, scripts;
scripts = window.document.getElementsByTagName('script');
coffeetypes = ['text/coffeescript', 'text/literate-coffeescript'];
coffees = (function() {
var _i, _len, _ref, _results;
_results = [];
for (_i = 0, _len = scripts.length; _i < _len; _i++) {
s = scripts[_i];
if (_ref = s.type, __indexOf.call(coffeetypes, _ref) >= 0) {
_results.push(s);
var j, len, ref, results;
results = [];
for (j = 0, len = scripts.length; j < len; j++) {
s = scripts[j];
if (ref = s.type, indexOf.call(coffeetypes, ref) >= 0) {
results.push(s);
}
}
return _results;
return results;
})();
index = 0;
execute = function() {
......@@ -103,7 +103,7 @@
return execute();
}
};
_fn = function(script, i) {
fn = function(script, i) {
var options;
options = {
literate: script.type === coffeetypes[1]
......@@ -118,9 +118,9 @@
return coffees[i] = [script.innerHTML, options];
}
};
for (i = _i = 0, _len = coffees.length; _i < _len; i = ++_i) {
for (i = j = 0, len = coffees.length; j < len; i = ++j) {
script = coffees[i];
_fn(script, i);
fn(script, i);
}
return execute();
};
......
// Generated by CoffeeScript 1.8.0
// Generated by CoffeeScript 1.9.1
(function() {
var CoffeeScript, cakefileDirectory, fatalError, fs, helpers, missingTask, oparse, options, optparse, path, printTasks, switches, tasks;
......@@ -24,9 +24,9 @@
helpers.extend(global, {
task: function(name, description, action) {
var _ref;
var ref;