Commit 63211b43 authored by Leo Iannacone's avatar Leo Iannacone

Merge branch 'feature/coffeescript' into develop

parents 1745a989 18109727
......@@ -35,7 +35,7 @@ from collections import defaultdict
# as first and as last one for pre_* and post_* hooks
class DebomaticModule_00_JSONLogger:
def __init__(self):
self.logger = JSONLogger.Instance()
self.logger = DebomaticModule_JSONLogger()
def pre_chroot(self, args):
self.logger.pre_chroot(args)
......@@ -46,7 +46,7 @@ class DebomaticModule_00_JSONLogger:
class DebomaticModule_ZZ_JSONLogger:
def __init__(self):
self.logger = JSONLogger.Instance()
self.logger = DebomaticModule_JSONLogger()
def post_chroot(self, args):
self.logger.post_chroot(args)
......@@ -55,25 +55,8 @@ class DebomaticModule_ZZ_JSONLogger:
self.logger.post_build(args)
# Singleton decorator
class Singleton:
def __init__(self, decorated):
self._decorated = decorated
def Instance(self):
try:
return self._instance
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `Instance()`.')
# The real JSONLogger Class
@Singleton
class JSONLogger:
class DebomaticModule_JSONLogger:
def __init__(self):
self.jsonfile = '/var/log/debomatic-json.log'
......@@ -81,10 +64,33 @@ class 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'):
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()
def _write_json_logfile(self, args, status):
def _get_package_json_filename(self, args):
"""Get the path of package JSON file"""
return '%(directory)s/pool/%(package)s/%(package)s.json' % args
def _get_distribution_status(self, args):
"""From args to distribution status"""
status = {}
status['status'] = args['cmd']
status['distribution'] = args['distribution']
if 'success' in args:
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]
return status
def _append_json_logfile(self, args, status):
"""Write status to jsonfile in JSON format."""
self._set_json_logfile_name(args)
status['time'] = int(time())
......@@ -92,13 +98,9 @@ class JSONLogger:
json = toJSON(status)
logfd.write(json + '\n')
def _get_package_json(self, args):
"""Get the path of package JSON file"""
return '%(directory)s/pool/%(package)s/%(package)s.json' % args
def _write_package_json(self, args, status):
"""Write package status to a JSON file."""
package_json = self._get_package_json(args)
package_json = self._get_package_json_filename(args)
if os.path.isfile(package_json):
with open(package_json, 'r') as infofd:
try:
......@@ -117,40 +119,22 @@ class JSONLogger:
json = toJSON(info, indent=4)
infofd.write(json + '\n')
def _get_distribution_status(self, args):
"""From args to distribution status"""
status = {}
status['status'] = args['cmd']
status['distribution'] = args['distribution']
if 'success' in args:
status['success'] = args['success']
return status
def _get_package_status(self, args):
"""From args to package status"""
keys = ['package', 'distribution', 'uploader']
status = {}
for k in keys:
if k in args:
status[k] = args[k]
return status
def pre_chroot(self, args):
distribution = self._get_distribution_status(args)
self._write_json_logfile(args, distribution)
self._append_json_logfile(args, distribution)
def post_chroot(self, args):
distribution = self._get_distribution_status(args)
self._write_json_logfile(args, distribution)
self._append_json_logfile(args, distribution)
def pre_build(self, args):
package = self._get_package_status(args)
package['status'] = 'build'
package_json = self._get_package_json(args)
package_json = self._get_package_json_filename(args)
if os.path.isfile(package_json):
os.remove(package_json)
self._write_package_json(args, package)
self._write_json_logfile(args, package)
self._append_json_logfile(args, package)
def post_build(self, args):
status = self._get_package_status(args)
......@@ -167,7 +151,7 @@ class JSONLogger:
if tag:
status['tags'][filename] = tag
self._write_package_json(args, status)
self._write_json_logfile(args, status)
self._append_json_logfile(args, status)
# Parser for log files
......@@ -227,9 +211,5 @@ class LogParser():
def _from_tags_to_result(self, tags):
keys = sorted(list(tags.keys()))
result = []
for k in keys:
result.append("%s%s" % (k, tags[k]))
if len(result) > 0:
return ' '.join(result)
return None
result = ["%s%s" % (k, tags[k]) for k in keys]
return ' '.join(result) if result else None
#!/usr/bin/env nodejs
'use strict';
/**
* Module dependencies.
*/
var http = require('http'),
express = require('express'),
serve_static = require('serve-static'),
serve_index = require('serve-index'),
errorhandler = require('errorhandler'),
routes = require('./routes'),
config = require('./lib/config.js'),
utils = require('./lib/utils.js'),
Client = require('./lib/client.js'),
Broadcaster = require('./lib/broadcaster.js');
var app = module.exports = express(),
server = http.createServer(app),
io = require('socket.io')(server),
env = process.env.NODE_ENV || 'development';
if ('development' == env) {
app.use(errorhandler({
dumpExceptions: true,
showStack: true
}));
} else if ('production' == env) {
app.use(errorhandler());
}
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// index page
app.get('/', routes.index);
// distibution page
app.get(config.routes.distribution, routes.distribution);
// parefernces page
if (config.routes.preferences)
app.get(config.routes.preferences, routes.preferences);
// commands page
if (config.routes.commands)
app.get(config.routes.commands, routes.commands);
// debomatic static page
if (config.routes.debomatic) {
app.all(config.routes.debomatic + '*', function (req, res, next) {
// send 403 status when users want to browse the chroots:
// - unstable/unstable
// - unstable/build/*
// this prevents system crashes
var base = config.routes.debomatic;
base += base[base.length - 1] != '/' ? '/' : ''; // append /
var match = req.url.replace(base, '').split('/');
if (match[match.length - 1] === '') match.pop();
if (match.length >= 2 && (
(match[0] == match[1]) || /* case unstable/unstable */
(match[1] == 'build' && match.length > 2) /* case unstable/build/* */
)) {
res.status(403).send('<h1>403 Forbidden</h1>');
} else
next(); // call next() here to move on to next middleware/router
});
app.use(config.routes.debomatic, serve_static(config.debomatic.path));
app.use(config.routes.debomatic, serve_index(config.debomatic.path, {
'view': 'details',
'icons': true
}));
}
// 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, function (err) {
// Checking nodejs with sudo:
// Find out which user used sudo through the environment variable
// and set his user id
var uid = parseInt(process.env.SUDO_UID);
if (uid) {
console.log('Please do not run nodejs with sudo. Changing user to %d', uid);
process.setgid(uid);
process.setuid(uid);
}
// statuses
var status = [];
var broadcast = new Broadcaster(io.sockets, status);
io.sockets.on('connection', function (socket) {
var client = new Client(socket);
client.start();
if (status.length > 0)
client.send_status(status);
client.send_status_debomatic();
});
console.log('Debomatic-webui listening on %s:%d in %s mode', server.address().address, server.address().port, app.settings.env);
});
server.on('error', function (e) {
if (e.code == 'EADDRINUSE') {
console.log('Address in use %s:%d. Exit.', config.host, config.port);
process.exit(1);
} else {
console.error(e);
}
});
"use strict"
###
Module dependencies.
###
http = require("http")
express = require("express")
serve_static = require("serve-static")
serve_index = require("serve-index")
errorhandler = require("errorhandler")
routes = require("./routes")
config = require("./lib/config")
utils = require("./lib/utils")
Client = require("./lib/client")
Broadcaster = require("./lib/broadcaster")
app = module.exports = express()
server = http.createServer(app)
io = require("socket.io")(server)
env = process.env.NODE_ENV or "development"
if "development" is env
app.use errorhandler(
dumpExceptions: true
showStack: true
)
else app.use errorhandler() if "production" is env
app.set "views", __dirname + "/views"
app.set "view engine", "ejs"
# index page
app.get "/", routes.index
# distibution page
app.get config.routes.distribution, routes.distribution
# parefernces page
if config.routes.preferences
app.get config.routes.preferences, routes.preferences
# commands page
app.get config.routes.commands, routes.commands if config.routes.commands
# debomatic static page
if config.routes.debomatic
app.all config.routes.debomatic + "*", (req, res, next) ->
# send 403 status when users want to browse the chroots:
# - unstable/unstable
# - unstable/build/*
# this prevents system crashes
base = config.routes.debomatic
base += (if base[base.length - 1] isnt "/" then "/" else "") # append /
match = req.url.replace(base, "").split("/")
match.pop() if match[match.length - 1] is ""
if match.length >= 2 and
((match[0] is match[1]) or # case unstable/unstable
(match[1] is "build" and match.length > 2)) # case unstable/build/*
res.status(403).send "<h1>403 Forbidden</h1>"
else # call next() here to move on to next middleware/router
next()
return
app.use config.routes.debomatic, serve_static(config.debomatic.path)
app.use(config.routes.debomatic, serve_index(config.debomatic.path,
{view: "details", icons: true}))
# 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) ->
# Checking nodejs with sudo:
# Find out which user used sudo through the environment variable
# and set his user id
uid = parseInt(process.env.SUDO_UID)
if uid
console.log "Please do not run nodejs with sudo. " +
"Changing user to %d", uid
process.setgid uid
process.setuid uid
# statuses
status = []
broadcast = new Broadcaster(io.sockets, status)
io.sockets.on "connection", (socket) ->
client = new Client(socket)
client.start()
client.send_status status if status.length > 0
client.send_status_debomatic()
return
console.log "Debomatic-webui listening on %s:%d in %s mode",
server.address().address,
server.address().port,
app.settings.env
return
server.on "error", (e) ->
if e.code is "EADDRINUSE"
console.log "Address in use %s:%d. Exit.", config.host, config.port
process.exit 1
else
console.error e
return
__watch_status_check_same_obj = (obj1, obj2) ->
if obj1.status is obj2.status
if obj1.distribution is obj2.distribution
if obj1.hasOwnProperty("package") and obj2.hasOwnProperty("package")
return true if obj1.package is obj2.package
return false
return true
false
# watcher on build_status
__watch_status = (socket, status) ->
watcher = new Tail(config.debomatic.jsonfile)
watcher.on "line", (new_content) ->
data = null
try
data = JSON.parse(new_content)
catch err
utils.errors_handler "Broadcaster:" +
"__watch_status:JSON.parse(new_content) - ",
err, socket
return
# looking for same status already in statuses lists
if data.hasOwnProperty("success")
i = 0
while i < status.length
if __watch_status_check_same_obj(data, status[i])
status.splice i, 1
break
else
continue
i++
else
status.push data
socket.emit config.events.broadcast.status_update, data
return
watcher.on "error", (msg) ->
socket.emit config.events.error, msg
return
return
# watcher on new distributions
__watch_distributions = (socket) ->
fs.watch config.debomatic.path,
persistent: true
, (event, fileName) ->
# wait half a second to get pool subdir created
setTimeout (->
utils.send_distributions socket
return
), 500
return
return
__watch_pidfile = (socket) ->
fs.watchFile config.debomatic.pidfile, {
persistent: false
interval: 1007
}
, (curr, prev) ->
# if === 0 means pidfile does not exists
status_debomatic = running: curr.ino isnt 0
try
socket.emit socket.emit(
config.events.broadcast.status_debomatic,
status_debomatic)
return
return
Broadcaster = (sockets, status) ->
if not fs.existsSync(config.debomatic.jsonfile)
# watch until json log file is created
fs.watchFile config.debomatic.jsonfile, (curr, prev) ->
if curr.ino isnt 0
fs.unwatchFile(config.debomatic.jsonfile)
__watch_status(sockets, status)
else
__watch_status(sockets, status)
__watch_distributions(sockets)
__watch_pidfile(sockets)
"use strict"
config = require("./config")
fs = require("fs")
utils = require("./utils")
Tail = require("./tail")
module.exports = Broadcaster
'use strict';
var config = require('./config.js'),
fs = require('fs'),
utils = require('./utils.js'),
Tail = require('./tail.js');
function __watch_status_check_same_obj(obj1, obj2) {
if (obj1.status == obj2.status) {
if (obj1.distribution == obj2.distribution) {
if (obj1.hasOwnProperty('package') &&
obj2.hasOwnProperty('package')) {
if (obj1.package == obj2.package)
return true;
return false;
}
return true;
}
}
return false;
}
// watcher on build_status
function __watch_status(socket, status) {
var watcher = new Tail(config.debomatic.jsonfile);
watcher.on('line', function (new_content) {
var data = null;
try {
data = JSON.parse(new_content);
} catch (err) {
utils.errors_handler('Broadcaster:__watch_status:JSON.parse(new_content) - ', err, socket);
return;
}
// looking for same status already in statuses lists
if (data.hasOwnProperty('success')) {
for (var i = 0; i < status.length; i++) {
if (__watch_status_check_same_obj(data, status[i])) {
status.splice(i, 1);
break;
} else
continue;
}
} else {
status.push(data);
}
socket.emit(config.events.broadcast.status_update, data);
});
watcher.on('error', function (msg) {
socket.emit(config.events.error, msg);
});
}
// watcher on new distributions
function __watch_distributions(socket) {
fs.watch(config.debomatic.path, {
persistent: true
}, function (event, fileName) {
// wait half a second to get pool subdir created
setTimeout(function () {
utils.send_distributions(socket);
}, 500);
});
}
function __watch_pidfile(socket) {
fs.watchFile(config.debomatic.pidfile, {
persistent: false,
interval: 1007
},
function (curr, prev) {
var status_debomatic = {
"running": curr.ino !== 0 // if === 0 means pidfile does not exists
};
try {
socket.emit(socket.emit(config.events.broadcast.status_debomatic, status_debomatic));
} catch (err) {}
});
}
function Broadcaster(sockets, status) {
__watch_status(sockets, status);
__watch_distributions(sockets);
__watch_pidfile(sockets);
return {
};
}
module.exports = Broadcaster;
__get_files_list_from_package = (data, callback) ->
package_path = utils.get_package_path(data)
utils.get_files_list package_path, false, (files) ->
data.package.files = []
data.package.debs = []
data.package.sources = []
for f in files
file = {}
file.extension = f.split(".").pop()
continue if file.extension in config.debomatic.excluded_files
file.path = path.join(package_path, f)
.replace(config.debomatic.path,
config.routes.debomatic)
file.orig_name = f
file.name = f.split("_")[0]
if file.extension in ["deb", "ddeb", "udeb"]
data.package.debs.push(file)
else if file.extension in ["changes", "dsc"] or f.indexOf('.tar') > 0
file.name = f.replace(data.package.orig_name + ".", "")
if file.extension is "changes"
file.name = file.extension
else if f.indexOf('.orig.tar') > 0
file.name = "orig." + f.split(".orig.").pop()
data.package.sources.push(file)
else
file.name = file.extension
data.package.files.push(file)
callback(data)
__send_package_files_list = (event_name, socket, data) ->
__get_files_list_from_package data, (new_data) ->
socket.emit event_name, new_data
return
return
__read_package_status = (data, cb) ->
package_path = utils.get_package_path(data)
package_json = path.join(package_path, data.package.orig_name + ".json")
fs.readFile package_json, {encoding: "utf8"}, (err, content) ->
if err
utils.errors_handler "Client:__read_package_status:", err
return
try
content = JSON.parse(content)
catch parse_err
utils.errors_handler("Client:" +
"__read_package_status:parse_err:",
parse_err)
return
cb content
return
return
__send_package_info = (socket, data) ->
__read_package_status data, (content) ->
socket.emit _e.package_info, content
return
return
__send_package_status = (socket, data) ->
__read_package_status data, (content) ->
socket.emit _e.distribution_packages_status, content
return
return
__send_distribution_packages = (event_name, socket, data) ->
distro_path = utils.get_distribution_pool_path(data)
utils.get_files_list distro_path, true, (packages) ->
data.distribution.packages = []
packages.forEach (p) ->
pack = {}
info = p.split("_")
pack.name = info[0]
pack.version = info[1]
pack.orig_name = p
__send_package_status socket,
distribution: data.distribution
package: pack
data.distribution.packages.push pack
return
socket.emit event_name, data
return
return
__send_file = (event_name, socket, data, last_lines) ->
file_path = utils.get_file_path(data)
fs.readFile file_path, "utf8", (err, content) ->
if err
utils.errors_handler "client:__send_file", err, socket
return
data.file.orig_name = file_path.split("/").pop()
if last_lines > 0
data.file.content = content.split("\n")[-last_lines..].join("\n")
else
data.file.content = content
data.file.path = file_path.replace(config.debomatic.path,
config.routes.debomatic)
socket.emit event_name, data
return
return
__handler_get_file = (socket, data) ->
file_path = utils.get_file_path(data)
send = (event_name, socket, data) ->
data.file.content = null
socket.emit event_name, data
utils.watch_path_onsocket(_e.file_newcontent, socket, data, file_path, send)
if data.file.name in config.web.file.preview and not data.file.force
__send_file(_e.file, socket, data, config.web.file.num_lines)
else
__send_file(_e.file, socket, data)
return
Client = (socket) ->
@start = ->
# init send distributions
utils.send_distributions socket
# init events
socket.on _e.distribution_packages, (data) ->
return unless utils.check_data_distribution(data)
distribution_path = utils.get_distribution_pool_path(data)
utils.generic_handler_watcher(_e.distribution_packages,
socket,
data,
distribution_path,
__send_distribution_packages)
data = null
return
socket.on _e.package_files_list, (data) ->
return unless utils.check_data_package(data)
package_path = utils.get_package_path(data)
utils.generic_handler_watcher(_e.package_files_list,
socket,
data,
package_path,
__send_package_files_list)
data = null
return
socket.on _e.file, (data) ->
return unless utils.check_data_file(data)
__handler_get_file socket, data
data = null
return
socket.on _e.package_info, (data) ->
return unless utils.check_data_package(data)
__send_package_info socket, data
data = null
return
# on client disconnection close all watchers
socket.on "disconnect", ->
socket_watchers = socket.watchers
return unless socket_watchers
for key of socket_watchers
try
socket_watchers[key].close()
catch error_watch
utils.errors_handler "client:disconnect", error_watch
return
return
@send_status = (status) ->
socket.emit _e.status, status
return
@send_status_debomatic = ->
fs.exists config.debomatic.pidfile, (exists) ->
socket.emit config.events.broadcast.status_debomatic,
running: exists
return
return
return
"use strict"
fs = require("fs")
path = require("path")
config = require("./config")
utils = require("./utils")
_e = config.events.client
module.exports = Client
'use strict';
var fs = require('fs'),
path = require('path'),
config = require('./config.js'),
utils = require('./utils.js');
var _e = config.events.client;
function __get_files_list_from_package(data, callback) {
var package_path = utils.get_package_path(data);
utils.get_files_list(package_path, false, function (files) {
data.package.files = [];
data.package.debs = [];
data.package.sources = [];
files.forEach(function (f) {
var file = {};
file.path = path.join(package_path, f).replace(config.debomatic.path, config.routes.debomatic);
file.orig_name = f;
file.name = f.split('_')[0];
file.extension = f.split('.').pop();
if (config.debomatic.excluded_files.indexOf(file.extension) >= 0)
return;
if (file.extension == 'deb' || file.extension == 'ddeb' || file.extension == 'udeb') {
data.package.debs.push(file);
} else if (f.indexOf('.tar') >= 0 || file.extension == 'changes' || file.extension == 'dsc') {
file.name = f.replace(data.package.name + '_' + data.package.version + '.', '');
if (file.extension == 'changes')
file.name = file.extension;
else if (f.indexOf('.tar') >= 0 && f.indexOf('.orig.') > 0)
file.name = 'orig.' + f.split('.orig.').pop();
data.package.sources.push(file);
} else {
file.name = file.extension;
data.package.files.push(file);
}
});
callback(data);
});
}
function __send_package_files_list(event_name, socket, data) {
__get_files_list_from_package(data, function (new_data) {
socket.emit(event_name, new_data);
});
}
function __read_package_status(data, cb) {
var package_path = utils.get_package_path(data);
var package_json = path.join(package_path, data.package.orig_name + '.json');
fs.readFile(package_json, {
encoding: 'utf8'
}, function (err, content) {
if (err) {
utils.errors_handler('Client:__read_package_status:', err);
return;
}
try {
content = JSON.parse(content);
} catch (parse_err) {
utils.errors_handler('Client:__read_package_status:parse_err:', parse_err);
return;
}
cb(content);
});
}
function __send_package_info(socket, data) {
__read_package_status(data, function (content) {
socket.emit(_e.package_info, content);
});
}
function __send_package_status(socket, data) {
__read_package_status(data, function (content) {
socket.emit(_e.distribution_packages_status, content);
});
}
function __send_distribution_packages(event_name, socket, data) {
var distro_path = utils.get_distribution_pool_path(data);
utils.get_files_list(distro_path, true, function (packages) {
data.distribution.packages = [];
packages.forEach(function (p) {
var pack = {};
var info = p.split('_');
pack.name = info[0];
pack.version = info[1];
pack.orig_name = p;
__send_package_status(socket, {
distribution: data.distribution,
package: pack
});
data.distribution.packages.push(pack);
});
socket.emit(event_name, data);
});
}
function __send_file(event_name, socket, data, last_lines) {
var file_path = utils.get_file_path(data);
fs.readFile(file_path, 'utf8', function (err, content) {
if (err) {
utils.errors_handler('client:__send_file', err, socket);
return;
}
data.file.orig_name = file_path.split('/').pop();
if (last_lines > 0)
data.file.content = content.split('\n').slice(-last_lines).join('\n');
else
data.file.content = content;
data.file.path = file_path.replace(config.debomatic.path, config.routes.debomatic);
socket.emit(event_name, data);
});
}
function __handler_get_file(socket, data) {
var file_path = utils.get_file_path(data);
utils.watch_path_onsocket(_e.file_newcontent, socket, data, file_path, function (event_name, socket, data) {
data.file.content = null;
socket.emit(event_name, data);
});
if (config.web.file.preview.indexOf(data.file.name) >= 0 && !data.file.force)
__send_file(_e.file, socket, data, config.web.file.num_lines);
else
__send_file(_e.file, socket, data);
}
function Client(socket) {
this.start = function () {
// init send distributions
utils.send_distributions(socket);
// init events
socket.on(_e.distribution_packages, function (data) {
if (!utils.check_data_distribution(data))
return;
var distribution_path = path.join(config.debomatic.path, data.distribution.name, 'pool');
utils.generic_handler_watcher(_e.distribution_packages, socket, data, distribution_path, __send_distribution_packages);
});
socket.on(_e.package_files_list, function (data) {
if (!utils.check_data_package(data))
return;
var package_path = utils.get_package_path(data);
utils.generic_handler_watcher(_e.package_files_list, socket, data, package_path, __send_package_files_list);
});
socket.on(_e.file, function (data) {
if (!utils.check_data_file(data))
return;
__handler_get_file(socket, data);
});
socket.on(_e.package_info, function (data) {
if (!utils.check_data_package(data))
return;
__send_package_info(socket, data);
});
// on client disconnection close all watchers
socket.on('disconnect', function () {
var socket_watchers = socket.watchers;
if (!socket_watchers)
return;
for (var key in socket_watchers) {
try {
socket_watchers[key].close();
} catch (error_watch) {}
}
});
};
this.send_status = function (status) {
socket.emit(_e.status, status);
};
this.send_status_debomatic = function () {
fs.exists(config.debomatic.pidfile, function (exists) {
socket.emit(config.events.broadcast.status_debomatic, {
'running': exists
});
});
};
}
module.exports = Client;
#
# * Please DO NOT edit this file.
# *
# * Edit auto-generated 'user.config.js' file instead.
# *
#
# #start config-auto-export
config = {}
#
# * Configure host and port.
# * Please for ports < 1000 use authbind. DO NOT RUN nodejs as root.
# * $ authbind nodejs index.js
#
config.host = "localhost"
config.port = 3000
config.debomatic = {}
config.debomatic.path = "/srv/debomatic-amd64"
config.debomatic.jsonfile = "/var/log/debomatic-json.log"
config.routes = {}
config.routes.debomatic = "/debomatic"
config.routes.distribution = "/distribution"
config.routes.preferences = "/preferences"
config.routes.commands = "/commands"
# web configuration
config.web = {}
config.web.debomatic = {}
config.web.debomatic.admin = {}
config.web.debomatic.admin.name = "Your Name"
# please use this SPAMFREE form - it will be converted client side by javascript
config.web.debomatic.admin.email = "you AT debian DOT org"
config.web.debomatic.architecture = "amd64"
config.web.debomatic.dput = {}
config.web.debomatic.dput.incoming = config.debomatic.path
config.web.debomatic.dput.host = config.host
config.web.debomatic.dput.login = "debomatic"
config.web.debomatic.dput.method = "scp"
config.web.debomatic.dput.unsigned_uploads = false
# header title and description
config.web.title = "Deb-o-Matic web.ui"
config.web.description = "This is a web interface for debomatic over " +
config.web.debomatic.architecture
# list of files get preview
config.web.file = {}
config.web.file.preview = ["buildlog"]
config.web.file.num_lines = 25
# default ui settings
config.web.preferences = {}
config.web.preferences.autoscroll = true
config.web.preferences.header = true
config.web.preferences.sidebar = true
config.web.preferences.glossy_theme = true
config.web.preferences.file_background = true
# valid values are [13..16]
config.web.preferences.file_fontsize = 13
# debug level - 0 means disabled
config.web.preferences.debug = 0
# #end config-auto-export
# DO NOT TOUCH these ones
config.version = "0.6.0"
config.debomatic.excluded_files = [
"datestamp"
"json"
]
config.events = {}
config.events.error = "server-error"
config.events.broadcast = {}
config.events.broadcast.distributions = "b.distributions"
config.events.broadcast.status_update = "b.status_update"
config.events.broadcast.status_debomatic = "b.status_debomatic"
config.events.client = {}
config.events.client.distribution_packages = "c.distribution_packages"
config.events.client.distribution_packages_status = "c.distribution_packages_status"
config.events.client.package_files_list = "c.package_files_list"
config.events.client.package_info = "c.package_info"
config.events.client.file = "c.file"
config.events.client.file_newcontent = "c.file_newcontent"
config.events.client.status = "c.status"
# debomatic status according with JSONLogger.py module
config.status = {}
config.status.build = "build"
config.status.create = "create"
config.status.update = "update"
config.status.success = true
config.status.fail = false
try
Parser = require("./parser")
parser = new Parser()
user_config = parser.getUserConfig()
if user_config
console.log "Reading user configutation ..."
# * update object1 with object2 values
_merge = (object1, object2) ->
result = {}
for p of object1
if object2.hasOwnProperty(p)
if typeof object1[p] is "object" and typeof object2[p] is "object"
result[p] = _merge(object1[p], object2[p])
else
result[p] = object2[p]
else
result[p] = object1[p]
return result
config = _merge(config, require(user_config))
else
console.log "No user config specified. Using global settings."
catch err
if err.code is "MODULE_NOT_FOUND"
console.log "File %s not found.", user_config
process.exit 1
else
console.error "Error reading user configutation", err
process.exit 1
finally
# export some variable
config.web.paths = config.routes
config.web.events = config.events
config.web.status = config.status
config.web.host = config.host
# calculate pidfile
config.debomatic.pidfile = "/var/run/debomatic-" +
require("crypto")
.createHash("sha256")
.update(config.debomatic.path)
.digest("hex")
module.exports = config
'use strict';
/*
* Please DO NOT edit this file.
*
* Edit auto-generated 'user.config.js' file instead.
*
*/
// #start config-auto-export
var config = {};
/*
* Configure host and port.
* Please for ports < 1000 use authbind. DO NOT RUN nodejs as root.
* $ authbind nodejs index.js
*/
config.host = 'localhost';
config.port = 3000;
config.debomatic = {};
config.debomatic.path = '/srv/debomatic-amd64';
config.debomatic.jsonfile = '/var/log/debomatic-json.log';
config.routes = {};
config.routes.debomatic = '/debomatic';
config.routes.distribution = '/distribution';
config.routes.preferences = '/preferences';
config.routes.commands = '/commands';
// web configuration
config.web = {};
// debomatic configuration exportable for web
config.web.debomatic = {};
config.web.debomatic.admin = {};
config.web.debomatic.admin.name = 'Your Name';
config.web.debomatic.admin.email = 'you AT debian DOT org'; // please use this SPAMFREE form - it will be converted client side by javascript
config.web.debomatic.architecture = 'amd64';
config.web.debomatic.dput = {};
config.web.debomatic.dput.incoming = config.debomatic.path;
config.web.debomatic.dput.host = config.host;
config.web.debomatic.dput.login = 'debomatic';
config.web.debomatic.dput.method = 'scp';
config.web.debomatic.dput.unsigned_uploads = false;
// header title and description
config.web.title = 'Deb-o-Matic web.ui';
config.web.description = 'This is a web interface for debomatic over ' + config.web.debomatic.architecture;
// list of files get preview
config.web.file = {};
config.web.file.preview = ['buildlog'];
config.web.file.num_lines = 25;
// default ui settings
config.web.preferences = {};
config.web.preferences.autoscroll = true;
config.web.preferences.header = true;
config.web.preferences.sidebar = true;
config.web.preferences.glossy_theme = true;
config.web.preferences.file_background = true;
config.web.preferences.file_fontsize = 13; // valid values are [13..16]
config.web.preferences.debug = 0; // debug level - 0 means disabled
// #end config-auto-export
// DO NOT TOUCH these ones
config.version = '0.6.0';
config.debomatic.excluded_files = ['datestamp', 'json'];
config.events = {};
config.events.error = 'server-error';
config.events.broadcast = {};
config.events.broadcast.distributions = 'b.distributions';
config.events.broadcast.status_update = 'b.status_update';
config.events.broadcast.status_debomatic = 'b.status_debomatic';
config.events.client = {};
config.events.client.distribution_packages = 'c.distribution_packages';
config.events.client.distribution_packages_status = 'c.distribution_packages_status';
config.events.client.package_files_list = 'c.package_files_list';
config.events.client.package_info = 'c.package_info';
config.events.client.file = 'c.file';
config.events.client.file_newcontent = 'c.file_newcontent';
config.events.client.status = 'c.status';
// debomatic status according with JSONLogger.py module
config.status = {};
config.status.build = 'build';
config.status.create = 'create';
config.status.update = 'update';
config.status.success = true;
config.status.fail = false;
// read user configuration and merge it
/*
* update object1 with object2 values
*/
function _merge(object1, object2) {
var result = {};
for (var p in object1) {
if (object2.hasOwnProperty(p)) {
if (typeof object1[p] === 'object' && typeof object2[p] === 'object') {
result[p] = _merge(object1[p], object2[p]);
} else {
result[p] = object2[p];
}
} else {
result[p] = object1[p];
}
}
return result;
}
try {
var Parser = require('./parser.js');
var parser = new Parser();
var user_config = parser.getUserConfig();
if (user_config) {
console.log('Reading user configutation ...');
config = _merge(config, require(user_config));
} else {
console.log('No user config specified. Using global settings.');
}
} catch (err) {
if (err.code == 'MODULE_NOT_FOUND') {
console.log('File %s not found.', user_config);
process.exit(1);
} else {
console.error('Error reading user configutation', err);
process.exit(1);
}
} finally {
// export some variable
config.web.paths = config.routes;
config.web.events = config.events;
config.web.status = config.status;
config.web.host = config.host;
// calculate pidfile
config.debomatic.pidfile = "/var/run/debomatic-" +
require('crypto')
.createHash('sha256')
.update(config.debomatic.path)
.digest('hex');
module.exports = config;
}
#jshint multistr: true
Parser = ->
args = process.argv.slice(2)
help = ->
console.log "Usage: %s [-c config]\n -h print this help \n -c set user configuration file", process.argv[1].split("/").pop()
process.exit 0
return
@getUserConfig = ->
configFile = null
args.forEach (val, index) ->
if val is "-c"
configFile = args[index + 1]
return
if configFile
process.cwd() + "/" + configFile
else
null
args.forEach (val, index) ->
help() if val is "-h"
return
return
"use strict"
module.exports = Parser
/*jshint multistr: true */
'use strict';
function Parser() {
var args = process.argv.slice(2);
var help = function () {
console.log('\
Usage: %s [-c config]\n\
-h print this help \n\
-c set user configuration file',
process.argv[1].split('/').pop());
process.exit(0);
};
this.getUserConfig = function () {
var configFile = null;
args.forEach(function (val, index) {
if (val == '-c') {
configFile = args[index + 1];
return;
}
});
if (configFile)
return process.cwd() + '/' + configFile;
else
return null;
};
args.forEach(function (val, index) {
if (val == '-h') {
help();
}
});
}
module.exports = Parser;
"use strict"
fs = require("fs")
Tail = require("tail").Tail
Tail::watchEvent = (e) ->
_this = this
if e is "change"
fs.stat @filename, (err, stats) ->
if err
_this.emit "error", err
return
_this.pos = stats.size if stats.size < _this.pos
if stats.size > _this.pos
_this.queue.push
start: _this.pos
end: stats.size
_this.pos = stats.size
_this.internalDispatcher.emit "next" if _this.queue.length is 1
else if e is "rename"
@unwatch()
_this.emit "error", "File " + @filename + " deleted."
return
Tail::close = ->
@unwatch()
return
module.exports = Tail
'use strict';
var fs = require('fs'),
Tail = require('tail').Tail;
Tail.prototype.watchEvent = function (e) {
var _this = this;
if (e === 'change') {
return fs.stat(this.filename, function (err, stats) {
if (err) {
_this.emit('error', err);
return;
}
if (stats.size < _this.pos) {
_this.pos = stats.size;
}
if (stats.size > _this.pos) {
_this.queue.push({
start: _this.pos,
end: stats.size
});
_this.pos = stats.size;
if (_this.queue.length === 1) {
return _this.internalDispatcher.emit('next');
}
}
});
} else if (e === 'rename') {
this.unwatch();
_this.emit('error', 'File ' + this.filename + ' deleted.');
}
};
Tail.prototype.close = function () {
this.unwatch();
};
module.exports = Tail;
path = require("path")
fs = require("fs")
config = require("./config")
Tail = require("./tail")
_check_no_backward = (backward_path) ->
if typeof backward_path is 'string'
return backward_path.indexOf("..") < 0
return true
check_data_distribution = (data) ->
_check_no_backward(data) and
_check_no_backward(data.distribution) and
_check_no_backward(data.distribution.name)
check_data_package = (data) ->
check_data_distribution(data) and
_check_no_backward(data.package) and
_check_no_backward(data.package.name) and
_check_no_backward(data.package.version)
check_data_file = (data) ->
check_data_package(data) and
_check_no_backward(data.file) and
_check_no_backward(data.file.name)
get_distribution_pool_path = (data) ->
path.join(config.debomatic.path, data.distribution.name, "pool")
get_package_path = (data) ->
path.join(get_distribution_pool_path(data), data.package.orig_name)
get_file_path = (data) ->
path.join(get_package_path(data),
data.package.orig_name + "." + data.file.name)
get_files_list = (dir, onlyDirectories, callback) ->
fs.readdir dir, (err, files) ->
if err
errors_handler "get_files_list", err
return
result = []
for f in files
try
complete_path = path.join(dir, f)
stat = fs.statSync(complete_path)
if onlyDirectories
result.push(f) if stat.isDirectory()
else
result.push(f) if stat.isFile()
catch fs_error
errors_handler("get_files_list:forEach", fs_error)
continue
callback(result)
watch_path_onsocket = (event_name, socket, data, watch_path, updater) ->
socket_watchers = socket.watchers or {}
try
watcher = socket_watchers[event_name]
watcher.close() if watcher
fs.stat watch_path, (err, stats) ->
if err
errors_handler("watch_path_onsocket:fs.stat",
err, socket)
return
if stats.isDirectory()
watcher = fs.watch(watch_path,
persistent: true,
(event, fileName) ->
if event is "rename"
updater(event_name, socket, data))
else if stats.isFile()
watcher = new Tail(watch_path)
watcher.on("line", (new_content, tailInfo) ->
data.file.new_content = new_content + "\n"
updater event_name, socket, data)
watcher.on "error", (msg) ->
socket.emit config.events.error, msg
socket_watchers[event_name] = watcher
socket.watchers = socket_watchers
catch err
errors_handler("watch_path_onsocket <- " +
arguments_.callee.caller.name,
err, socket)
return
generic_handler_watcher = (event_name, socket, data, watch_path, callback) ->
callback event_name, socket, data
watch_path_onsocket event_name, socket, data, watch_path, callback
send_distributions = (socket) ->
get_files_list config.debomatic.path, true, (directories) ->
distributions = []
for dir in directories
data = {}
data.distribution = {}
data.distribution.name = dir
pool_path = get_distribution_pool_path(data)
distributions.push dir if fs.existsSync(pool_path)
socket.emit config.events.broadcast.distributions, distributions
errors_handler = (from, error, socket) ->
from = "NO SOCKET: " + from unless socket
console.error from, err.message
socket.emit config.events.error, err.message if socket
return
module.exports.check_data_distribution = check_data_distribution
module.exports.check_data_package = check_data_package
module.exports.check_data_file = check_data_file
module.exports.get_distribution_pool_path = get_distribution_pool_path
module.exports.get_package_path = get_package_path
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.generic_handler_watcher = generic_handler_watcher
module.exports.send_distributions = send_distributions
module.exports.errors_handler = errors_handler
'use strict';
var path = require('path'),
fs = require('fs'),
config = require('./config.js'),
Tail = require('./tail.js');
function __errors_handler(from, err, socket) {
if (!socket)
from = 'NO SOCKET: ' + from;
console.error(from, err.message);
if (socket)
socket.emit(config.events.error, err.message);
}
function __check_no_backward(backward_path) {
try {
return backward_path.indexOf('..') < 0;
} catch (err) {
return true;
}
}
function __check_data_distribution(data) {
return __check_no_backward(data) && __check_no_backward(data.distribution) && __check_no_backward(data.distribution.name);
}
function __check_data_package(data) {
return __check_data_distribution(data) && __check_no_backward(data.package) && __check_no_backward(data.package.name) && __check_no_backward(data.package.version);
}
function __check_data_file(data) {
return __check_data_package(data) && __check_no_backward(data.file) && __check_no_backward(data.file.name);
}
function __get_distribution_pool_path(data) {
return path.join(config.debomatic.path, data.distribution.name, 'pool');
}
function __get_package_path(data) {
return path.join(__get_distribution_pool_path(data), data.package.name + '_' + data.package.version);
}
function __get_file_path(data) {
return path.join(__get_package_path(data), data.package.name + '_' + data.package.version + '.' + data.file.name);
}
function __get_files_list(dir, onlyDirectories, callback) {
fs.readdir(dir, function (err, files) {
var result = [];
if (err) {
__errors_handler('__get_files_list', err);
return;
}
files.forEach(function (f) {
try {
var complete_path = path.join(dir, f);
var stat = fs.statSync(complete_path);
if (onlyDirectories) {
if (stat.isDirectory()) {
result.push(f);
}
} else {
if (stat.isFile()) {
result.push(f);
}
}
} catch (fs_error) {
__errors_handler('__get_files_list:forEach', fs_error);
return;
}
});
callback(result);
});
}
function __watch_path_onsocket(event_name, socket, data, watch_path, updater) {
var socket_watchers = socket.watchers || {};
try {
var watcher = socket_watchers[event_name];
if (watcher)
watcher.close();
fs.stat(watch_path, function (err, stats) {
if (err) {
__errors_handler('__watch_path_onsocket:fs.stat', err, socket);
return;
}
if (stats.isDirectory()) {
watcher = fs.watch(watch_path, {
persistent: true
}, function (event, fileName) {
if (event == 'rename')
updater(event_name, socket, data);
});
} else if (stats.isFile()) {
watcher = new Tail(watch_path);
watcher.on('line', function (new_content, tailInfo) {
data.file.new_content = new_content + '\n';
updater(event_name, socket, data);
});
watcher.on('error', function (msg) {
socket.emit(config.events.error, msg);
});
}
socket_watchers[event_name] = watcher;
socket.watchers = socket_watchers;
});
} catch (err) {
__errors_handler('__watch_path_onsocket <- ' + arguments.callee.caller.name, err, socket);
return;
}
}
function __generic_handler_watcher(event_name, socket, data, watch_path, callback) {
callback(event_name, socket, data);
__watch_path_onsocket(event_name, socket, data, watch_path, callback);
}
function __send_distributions(socket) {
__get_files_list(config.debomatic.path, true, function (directories) {
var distributions = [];
directories.forEach(function (dir) {
var data = {};
data.distribution = {};
data.distribution.name = dir;
var pool_path = __get_distribution_pool_path(data);
if (fs.existsSync(pool_path)) {
distributions.push(dir);
}
});
socket.emit(config.events.broadcast.distributions, distributions);
});
}
var utils = {
check_data_distribution: function (data) {
return __check_data_distribution(data);
},
check_data_package: function (data) {
return __check_data_package(data);
},
check_data_file: function (data) {
return __check_data_file(data);
},
get_distribution_pool_path: function (data) {
return __get_distribution_pool_path(data);
},
get_package_path: function (data) {
return __get_package_path(data);
},
get_file_path: function (data) {
return __get_file_path(data);
},
get_files_list: function (dir, onlyDirectories, callback) {
return __get_files_list(dir, onlyDirectories, callback);
},
watch_path_onsocket: function (event_name, socket, data, watch_path, updater) {
return __watch_path_onsocket(event_name, socket, data, watch_path, updater);
},
generic_handler_watcher: function (event_name, socket, data, watch_path, callback) {
return __generic_handler_watcher(event_name, socket, data, watch_path, callback);
},
send_distributions: function (socket) {
return __send_distributions(socket);
},
errors_handler: function (from, error, socket) {
return __errors_handler(from, error, socket);
}
};
module.exports = utils;
......@@ -12,7 +12,8 @@
"tail": "*"
},
"scripts": {
"install": "bash scripts/install.sh"
"install": "bash scripts/install.sh",
"start": "coffee debomatic-webui"
},
"bin": {
"debomatic-webui": "./debomatic-webui"
......
......@@ -725,12 +725,14 @@ function Page_Distrubion(socket) {
socket.on(_e.distribution_packages, function (socket_data) {
debug_socket('received', _e.distribution_packages, socket_data);
packages.set(socket_data);
socket_data = null;
});
socket.on(_e.distribution_packages_status, function (socket_data) {
debug_socket('received', _e.distribution_packages_status, socket_data);
packages.set_status(socket_data);
sticky.set_status(socket_data);
socket_data = null;
});
socket.on(config.events.broadcast.status_update, function (socket_data) {
......@@ -739,26 +741,31 @@ function Page_Distrubion(socket) {
if (socket_data.distribution == view.distribution.name && socket_data.package == view.package.orig_name) {
package_info.get();
}
socket_data = null;
});
socket.on(_e.package_files_list, function (socket_data) {
debug_socket('received', _e.package_files_list, socket_data);
files.set(socket_data);
socket_data = null;
});
socket.on(_e.file, function (socket_data) {
debug_socket('received', _e.file, socket_data);
file.set(socket_data);
socket_data = null;
});
socket.on(_e.file_newcontent, function (socket_data) {
debug_socket('received', _e.file_newcontent, socket_data);
new_lines.push(socket_data.file.new_content);
socket_data = null;
});
socket.on(_e.package_info, function (socket_data) {
debug_socket('received', _e.package_info, socket_data);
package_info.set(socket_data);
socket_data = null;
});
$(window).on('hashchange', function () {
......@@ -775,6 +782,8 @@ function Page_Distrubion(socket) {
update.page(old_view);
page.go.up();
debug(1, 'changing view', 'old:', old_view, 'new:', view);
old_view = null;
new_view = null;
});
......
"use strict"
config = require("../lib/config")
exports.index = (req, res) ->
res.render "index", config
return
exports.distribution = (req, res) ->
res.render "distribution", config
return
exports.preferences = (req, res) ->
res.render "preferences", config
return
exports.commands = (req, res) ->
res.render "commands", config
return
'use strict';
var config = require('../lib/config.js');
exports.index = function (req, res) {
res.render('index', config);
};
exports.distribution = function (req, res) {
res.render('distribution', config);
};
exports.preferences = function (req, res) {
res.render('preferences', config);
};
exports.commands = function (req, res) {
res.render('commands', config);
};
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