Commit 1b55096a authored by Leo Iannacone's avatar Leo Iannacone

Merge remote-tracking branch 'github/master' into portable

parents 91969ff7 d847759a
# 1.0.0 (2014-07-23)
* Migrate to coffee-script, code refactory
* [new] style - more focus on files content
* [new] removed useless header, save space
* [new] move git repo to debomatic organization in github
* [fix] merge correctly user configuration - closes #1
* [fix] read correctly user configuration with absolute path - closes #2
* [fix] wait for the creation of the json log if does not exists - #closes 3
# 0.6.0 (2014-07-10)
* [new] [module] write a JSON file about package status in its own directory
* [new] update to socket.io 1.x
......
......@@ -24,23 +24,23 @@ Restart debomatic service.
Install **npm** and **nodejs** on your system. On debian like systems type this in a command line:
```
sudo apt-get install npm nodejs
sudo apt-get install npm nodejs nodejs-legacy
```
## Installation
Move to **debomatic-webui/** directory and type:
You have to install node dependencies locally, creates automatically the user configuration file and install **coffee-script** globally. Move to **debomatic-webui/** directory and type:
```
npm install
sudo npm install -g coffee-script
```
That command downloads node dependences locally and creates automatically user configuration file.
## Usage
Take a look at auto-generated **user.config.js**. Edit as you wish and then run service with:
Take a look at auto-generated **user.config.coffee**. Edit as you wish and then run service with:
```
nodejs debomatic-webui -c user.config.js
coffee debomatic-webui -c user.config
```
That's all.
......@@ -116,7 +116,7 @@ class DebomaticModule_JSONLogger:
info[key] = status[key]
with open(package_json, 'w') as infofd:
json = toJSON(info, indent=4)
json = toJSON(info, indent=4, sort_keys=True)
infofd.write(json + '\n')
def pre_chroot(self, args):
......@@ -204,7 +204,14 @@ class LogParser():
def parse_piuparts(self):
with open(self.file, 'r') as fd:
last_line = fd.readlines()[-1]
offs = -1024
while True:
fd.seek(offs, 2)
lines = fd.readlines()
if len(lines) > 1:
last_line = lines[-1]
break
offs *= 2
if last_line.find('ERROR:') >= 0:
return 'E'
return None
......
{
"arrow_spacing": {
"level": "warn"
},
"camel_case_classes": {
"level": "error"
},
"coffeescript_error": {
"level": "error"
},
"colon_assignment_spacing": {
"level": "warn",
"spacing": {
"left": 0,
"right": 1
}
},
"cyclomatic_complexity": {
"value": 10,
"level": "error"
},
"duplicate_key": {
"level": "error"
},
"empty_constructor_needs_parens": {
"level": "ignore"
},
"indentation": {
"value": 4,
"level": "error"
},
"line_endings": {
"level": "ignore",
"value": "unix"
},
"max_line_length": {
"value": 80,
"level": "ignore",
"limitComments": true
},
"missing_fat_arrows": {
"level": "error"
},
"newlines_after_classes": {
"value": 3,
"level": "warn"
},
"no_backticks": {
"level": "error"
},
"no_debugger": {
"level": "warn"
},
"no_empty_functions": {
"level": "ignore"
},
"no_empty_param_list": {
"level": "ignore"
},
"no_implicit_braces": {
"level": "ignore",
"strict": true
},
"no_implicit_parens": {
"strict": true,
"level": "ignore"
},
"no_interpolation_in_single_quotes": {
"level": "warn"
},
"no_plusplus": {
"level": "ignore"
},
"no_stand_alone_at": {
"level": "ignore"
},
"no_tabs": {
"level": "error"
},
"no_throwing_strings": {
"level": "error"
},
"no_trailing_semicolons": {
"level": "error"
},
"no_trailing_whitespace": {
"level": "error",
"allowed_in_comments": false,
"allowed_in_empty_lines": true
},
"no_unnecessary_double_quotes": {
"level": "ignore"
},
"no_unnecessary_fat_arrows": {
"level": "warn"
},
"non_empty_constructor_needs_parens": {
"level": "ignore"
},
"space_operators": {
"level": "ignore"
}
}
#!/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);
}
});
require('./lib/app')
###
Module dependencies.
###
app = module.exports = require("express")()
server = require("http").createServer(app)
io = require("socket.io")(server)
serve_static = require("serve-static")
serve_index = require("serve-index")
errorhandler = require("errorhandler")
routes = require("../routes")
config = require("./config")
utils = require("./utils")
Client = require("./client")
Debomatic = require("./debomatic")
# error handler setup
env = process.env.NODE_ENV or "development"
if env is "development"
app.use(errorhandler({dumpExceptions: true, showStack: true}))
else
app.use(errorhandler())
# the views
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)
# preferences 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 + "*", (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 "/" # 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>
<h2>You cannot see the chroot internals</h2>
"""
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) ->
debomatic = new Debomatic(io.sockets)
debomatic.start()
io.sockets.on "connection", (socket) ->
client = new Client(socket)
client.start()
client.send_status(debomatic.status)
client.send_status_debomatic(debomatic.running)
client.send_distributions(debomatic.distributions)
console.log "Debomatic-webui listening on %s:%d in %s mode",
server.address().address,
server.address().port,
app.settings.env
server.on "error", (e) ->
if e.code is "EADDRINUSE"
console.log "Address in use %s:%d. Exit.", config.host, config.port
process.exit 3
else
console.error e
return
'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;
fs = require("fs")
path = require("path")
config = require("./config")
utils = require("./utils")
e = config.events.client
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)
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 parseerr
utils.errors_handler("Client:" +
"read_package_status:parseerr:",
parseerr)
return
cb content
class Client
constructor: (@socket) ->
send_package_files_list: (data) ->
get_files_list_from_package data, (new_data) =>
@socket.emit e.package_files_list, new_data
send_distribution_packages: (data) ->
distro_path = utils.get_distribution_pool_path(data)
utils.get_files_list distro_path, true, (packages) =>
data.distribution.packages = []
for p in packages
pack = {}
info = p.split("_")
pack.name = info[0]
pack.version = info[1]
pack.orig_name = p
read_package_status {distribution: data.distribution, package: pack}, (content) =>
@socket.emit e.distribution_packages_status, content
data.distribution.packages.push pack
@socket.emit e.distribution_packages, data
send_file: (data) ->
file_path = utils.get_file_path(data)
get_preview = data.file.name in config.web.file.preview and not data.file.force
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 get_preview and config.web.file.num_lines > 0
last_lines = config.web.file.num_lines
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 e.file, data
send_status: (status) ->
data = status
if status instanceof Array == false
data = []
data.push v for k, v of status
@socket.emit e.status, data
send_status_debomatic: (running) ->
@socket.emit config.events.broadcast.status_debomatic, running: running
send_distributions: (distributions) ->
@socket.emit config.events.broadcast.distributions, distributions
start: ->
# init events
@socket.on e.distribution_packages, (data) =>
return unless utils.check_data_distribution(data)
distribution_path = utils.get_distribution_pool_path(data)
@send_distribution_packages(data)
utils.watch_path_onsocket e.distribution_packages, @socket, data, distribution_path, (new_data) =>
@send_distribution_packages(new_data)
@socket.on e.package_files_list, (data) =>
return unless utils.check_data_package(data)
package_path = utils.get_package_path(data)
@send_package_files_list(data)
utils.watch_path_onsocket e.package_files_list, @socket, data, package_path, (new_data) =>
@send_package_files_list(new_data)
@socket.on e.file, (data) =>
return unless utils.check_data_file(data)
file_path = utils.get_file_path(data)
data.file.content = null
@send_file(data)
utils.watch_path_onsocket e.file_newcontent, @socket, data, file_path, (new_data) =>
@socket.emit(e.file_newcontent, new_data)
@socket.on e.package_info, (data) =>
return unless utils.check_data_package(data)
read_package_status data, (content) =>
@socket.emit e.package_info, content
# 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
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);
data = null;
});
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);
data = null;
});
socket.on(_e.file, function (data) {
if (!utils.check_data_file(data))
return;
__handler_get_file(socket, data);
data = null;
});
socket.on(_e.package_info, function (data) {
if (!utils.check_data_package(data))
return;
__send_package_info(socket, data);
data = null;
});
// 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) {
utils.errors_handler('client:disconnect', 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.coffee' file instead.
###
fs = require('fs')
extend = require('extend')
crypto = require('crypto')
cseval = require('coffee-script').eval
Parser = require("./parser")
#start config-auto-export
###
Init some values, do not touch these
###
config = {}
config.debomatic = {}
config.web = {}
config.web.debomatic = {}
config.web.debomatic.admin = {}
config.web.debomatic.dput = {}
config.web.file = {}
config.web.preferences = {}
###
Configure host and port
###
config.host = "localhost"
config.port = 3000
###
Deb-O-Matic settings
###
config.debomatic.path = "/srv/debomatic-amd64"
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
###
Admin email and name to show in the home page.
For the email address please use the SPAMFREE form "you AT host DOT org",
it will be converted client side by javascript
###
config.web.debomatic.admin.email = "you AT debian DOT org"
config.web.debomatic.admin.name = "Your Name"
###
Configuration of dput to show in the home page.
###
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
###
List of files to get a simple preview and number of lines
to show
###
config.web.file.preview = ["buildlog"]
config.web.file.num_lines = 25
###
The default user preferences.
See /preferences page for more info
###
config.web.preferences.autoscroll = 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
###
The version
###
config.version = require("../package").version
###
List of files to not show in webui
###
config.debomatic.excluded_files = [
"datestamp"
"json"
]
###
The routes, that are the pages urls
###
config.routes = {}
config.routes.debomatic = "/debomatic"
config.routes.distribution = "/distribution"
config.routes.preferences = "/preferences"
config.routes.commands = "/commands"
###
The events
###
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"
###
The 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
# Merge the configuration
try
parser = new Parser()
user_config = parser.getUserConfig()
if user_config
console.log "Reading user configutation ..."
data = fs.readFileSync(user_config, 'utf8')
user_config_content = cseval(data + "\nmodule.exports = config")
config = extend(true, config, user_config_content)
else
console.log "No user config specified. Using global settings."
# export some variable to web
config.web.paths = config.routes
config.web.events = config.events
config.web.status = config.status
config.web.host = config.host
# get the debomatic pidfile
config.debomatic.pidfile = "/var/run/debomatic-" +
crypto.createHash("sha256")
.update(config.debomatic.path)
.digest("hex")
module.exports = config
catch err
if err.code is 'ENOENT'
console.error 'File "%s" not found', user_config
else if err.code is 'EISDIR'
console.error 'File "%s" is a directory', user_config
else
console.error "Error reading user configutation:"
console.error err
process.exit 2
'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;
}
fs = require("fs")
config = require("./config")
utils = require("./utils")
Tail = utils.Tail
e = config.events.broadcast
_get_distributions = (callback) ->
utils.get_files_list config.debomatic.path, true, (directories) ->
distributions = []
for dir in directories
data = {}
data.distribution = {}
data.distribution.name = dir
pool_path = utils.get_distribution_pool_path(data)
distributions.push dir if fs.existsSync(pool_path)
callback(distributions)
class Debomatic
constructor: (@sockets) ->
@status = {}
@distributions = []
@running = fs.existsSync (config.debomatic.pidfile)
_get_distributions (distributions) => @distributions = distributions
# watcher on new distributions
watch_distributions: ->
fs.watch config.debomatic.path, (event, fileName) =>
check = =>
_get_distributions (new_distributions) =>
if not utils.arrayEqual(@distributions, new_distributions)
@distributions = new_distributions
@sockets.emit(e.distributions, @distributions)
# wait half a second to get pool subdir created
setTimeout(check, 500)
watch_pidfile: ->
fs.watchFile config.debomatic.pidfile, (curr, prev) =>
# if === 0 means pidfile does not exists
@running = curr.ino isnt 0
@sockets.emit e.status_debomatic, running: @running
# watcher on build_status
watch_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 "Debomatoci:" +
"watch_status:JSON.parse(new_content) - ",
err, @sockets
return
# get a id rapresentation of status
get_key = (status) ->
key = status.distribution
key += "/#{status.package}" if status.package?
key += "/#{status.status}" if status.status?
return key
key = get_key(data)
if data.hasOwnProperty("success") and @status[key]?
delete @status[key]
else
@status[key] = data
@sockets.emit e.status_update, data
watcher.on "error", (msg) =>
@sockets.emit config.events.error, msg
start: ->
# if json file does not still exist wait for its creation
if not fs.existsSync(config.debomatic.jsonfile)
fs.watchFile config.debomatic.jsonfile, (curr, prev) =>
if curr.ino isnt 0
fs.unwatchFile(config.debomatic.jsonfile)
@watch_status()
else
@watch_status()
@watch_pidfile()
@watch_distributions()
module.exports = Debomatic
path = require('path')
class Parser
constructor: ->
@args = process.argv[2..]
@name = process.argv[1].split("/").pop()
if '-h' in @args
@help()
help: ->
console.log """
Usage: %s [-c config]
-h print this help
-c set user configuration file
""", @name
process.exit 1
return
getUserConfig: ->
if '-c' in @args
if @args.length < 2
@help()
user_config = @args[@args.indexOf('-c') + 1]
if user_config[0] isnt '/'
user_config = process.cwd() + "/" + user_config
return path.normalize(user_config)
return null
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';
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").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)
arrayEqual = (a, b) ->
a.length is b.length and a.every (elem, i) -> elem is b[i]
watch_path_onsocket = (event_name, socket, data, watch_path, callback) ->
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"
callback(data))
else if stats.isFile()
watcher = new Tail(watch_path)
watcher.on "line", (new_content, tailInfo) ->
data.file.new_content = new_content + "\n"
callback(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
errors_handler = (from, err, socket) ->
from = "NO SOCKET: " + from unless socket
console.error from, err.message
socket.emit config.events.error, err.message if socket
return
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.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.errors_handler = errors_handler
module.exports.arrayEqual = arrayEqual
module.exports.Tail = Tail
'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;
{
"name": "debomatic-webui",
"version": "0.0.1",
"version": "1.0.0",
"private": true,
"dependencies": {
"express": "4.x",
"serve-index": "*",
"serve-static": "*",
"errorhandler ": "*",
"ejs": ">= 0.0.1",
"ejs": "1.*",
"socket.io": "1.*",
"tail": "*"
"tail": "*",
"extend": "*"
},
"scripts": {
"install": "bash scripts/install.sh"
"install": "bash scripts/install.sh",
"start": "coffee debomatic-webui",
"test": "cd test; mocha -b -R spec --compilers coffee:coffee-script/register --require should tests.coffee"
},
"bin": {
"debomatic-webui": "./debomatic-webui"
}
},
"coffeelintConfig": ".coffeelintrc"
}
......@@ -520,7 +520,7 @@ function Page_Distrubion(socket) {
$(window).scroll(sticky.init);
},
stop: function () {
$(window).off('scroll');
$(window).off('scroll', sticky.init);
},
reset: function () {
sticky.stop();
......@@ -579,12 +579,16 @@ function Page_Distrubion(socket) {
set: function (socket_error) {
if ($('#error').is(':visible'))
return;
$('#error span').html(socket_error);
socket_error = socket_error.replace(/File (.*) deleted(.*)/,
'<b>File removed</b>&nbsp;&nbsp;<em>$1</em>');
socket_error = socket_error.replace(/ENOENT, [a-z]+ '(.*)'/,
'<b>No such file or directory</b>&nbsp;&nbsp;<em>$1</em>');
$('#error .message').html(socket_error);
error.view();
},
clean: function () {
$('#error').hide();
$('#error span').html('');
$('#error .message').html('');
},
view: function () {
$('#error').fadeIn(100);
......
......@@ -134,15 +134,6 @@ function Page_Generic() {
};
this.preferences = function () {
if (config.preferences.header) {
$('#pageheader').show();
$('footer .info').hide();
$('.navbar .home-link').hide();
} else {
$('#pageheader').hide();
$('footer .info').show();
$('.navbar .home-link').show();
}
var bootstrap_theme_css = '/external_libs/bootstrap-3.2.0-dist/css/bootstrap-theme.min.css';
if (config.preferences.glossy_theme) {
if ($('head').find('link[href="' + bootstrap_theme_css + '"]').length === 0)
......@@ -196,4 +187,14 @@ function Page_Generic() {
// update html according with preferences
this.preferences();
// show the smile face
$('#footer .copyright').mouseenter(function () {
$('#smile').animate({
'background-position-y': '-50px'
}, 200);
}).mouseleave(function () {
$('#smile').animate({
'background-position-y': '20px'
}, 150);
});
}
#pageheader {
background: url('../images/debian-logo.png') right no-repeat;
cursor: pointer;
}
#pageheader .lead {
padding-bottom: 0;
body {
background: url("../images/image-background-paper.png") repeat-y center top #f7f7f7
}
.navbar {
margin-top: 20px;
}
.home-link {
cursor: pointer;
text-shadow: 0 1px 1px black;
}
.home-link .navbar-brand {
color: #eee;
}
.navbar .home-link .icon {
background: url('../images/debian-logo-small.png') right no-repeat;
width: 17px;
height: 50px;
margin-left: 10px;
margin-left: 14px;
}
pre {
white-space: pre-wrap;
word-break: break-word;
background: white;
border-color: #d6d6d6;
}
.breadcrumb {
margin-bottom:10px;
margin-bottom: 0px;
background: none;
}
.nav>li>a:hover, .nav>li>a:focus {
background-color: rgba(0,0,0,0.05);
}
#error {
......@@ -110,7 +124,7 @@ footer {
width: 100%;
bottom: 0;
background: #fafafa;
border-top: 1px solid #f0f0f0;
border-top: 1px solid #eaeaea;
}
footer .info {
......@@ -174,11 +188,6 @@ footer .info {
line-height: 35px;
}
#file .content {
white-space: pre-wrap;
word-break: break-word;
}
#file .no-background {
background: none;
border: none;
......@@ -200,9 +209,25 @@ footer .info {
#package_info {
display: inline-block;
background: #f4f4f4;
background: none;
padding: 4px 10px;
margin-bottom: 10px;
border-radius: 4px;
font-size: 75%;
}
#smile {
background: transparent url(../images/smile-code.png) right 20px no-repeat;
width: 125px;
height: 125px;
position: fixed;
right: 5px;
bottom:0;
}
#footer .copyright {
height: 40px;
margin-top: -10px;
padding-top: 10px;
cursor: pointer;
}
"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);
};
......@@ -6,38 +6,32 @@ import os
base_path = os.environ['SCRIPTS_DIR']
global_config_file = os.path.join(base_path, '../lib/config.js')
user_config_file = os.path.join(base_path, '../user.config.js')
global_config_file = os.path.join(base_path, '../lib/config.coffee')
user_config_file = os.path.join(base_path, '../user.config')
if os.path.isfile(user_config_file):
print ("A config user file already exists. Skipping creation.")
exit()
print ("A config user file already exists. Skipping creation.")
exit()
export_header = """
/*
* debomatic-webui user configuration
*/
export_header = """###
debomatic-webui user configuration
###
"""
export_config = []
export_config = [export_header]
with open(global_config_file) as fd:
start = False
for line in fd:
if line.find('#start config-auto-export') >= 0:
start = True
continue
elif line.find('#end config-auto-export') >= 0:
break
if start:
export_config.append(line)
export_config.append('// DO NOT EDIT THIS LINE:\n')
export_config.append('module.exports = config\n')
start = False
for line in fd:
if line.find('#start config-auto-export') >= 0:
start = True
continue
elif line.find('#end config-auto-export') >= 0:
break
if start:
export_config.append(line)
print ("Creating user configuration ...")
with open(user_config_file, 'w') as fd:
fd.write(export_header)
fd.write(''.join(export_config))
fd.write(''.join(export_config))
fs = require('fs')
path = require('path')
spawn = require('child_process').spawn
sh = require('execSync')
config = require('./tests.config')
io = require('socket.io-client')
events = require('../lib/config').events
should = require('should')
server = null
exec = (c) ->
c = c.replace('"', '\"')
sh.run("echo '#{c}' >> commands.log")
sh.run(c)
class Helper
constructor: ->
@base = config.debomatic.path
@json = config.debomatic.jsonfile
make_distribution: (distribution) ->
dpath = path.join(@base, distribution, 'pool')
exec("mkdir -p #{dpath}")
make_package: (distribution, debpack) ->
dpath = path.join(@base, distribution, 'pool', debpack)
exec("mkdir -p #{dpath}")
make_file: (distribution, debpack, extension, data) ->
file_path = path.join(@base, distribution, 'pool', debpack, debpack) + '.' + extension
@make_package(distribution, debpack)
exec("echo #{data} > #{file_path}")
append_file: (distribution, debpack, extension, data) ->
file_path = path.join(@base, distribution, 'pool', debpack, debpack) + '.' + extension
@make_package(distribution, debpack)
exec("echo '#{data}' >> #{file_path}")
append_json: (data) ->
exec("echo '#{data}' >> #{@json}")
clean: (distribution, debpack, file_extension) ->
b_path = @base
b_path = path.join(b_path, distribution, 'pool') if distribution?
b_path = path.join(b_path, debpack) if debpack?
b_path += path.join(b_path, debpack) + '.' + file_extension if file_extension?
exec("rm -r #{b_path}")
clean_all: () ->
exec("rm -r #{@base}/*")
exec("rm -r #{@json}")
get_query: (distribution, debpack, file_extension) ->
result =
distribution:
name: distribution
if debpack?
result.package =
orig_name: debpack
name: debpack.split('_')[0]
version: debpack.split('_')[1]
if file_extension?
result.file =
name: file_extension
return result
helper = new Helper()
client = null
server = null
options =
transports: ['websocket'],
'force new connection': true
describe 'start server', ->
before( (done) =>
helper.append_json('')
helper.make_distribution('trusty')
helper.make_distribution('unstable')
server = spawn('coffee', ['../debomatic-webui', '-c', 'tests.config.coffee'])
server.stdout.on 'data', (data) -> console.log('SERVER OUT: ', data.toString('utf-8'))
server.stderr.on 'data', (data) -> console.error('SERVER ERR', data.toString('utf-8'))
server.on 'exit', (code) ->
console.error('Server exit with code ' + code)
process.exit(code)
this.timeout(7000)
setTimeout(done, 500)
)
it 'server started', (done) ->
server.should.be.ok
done()
describe 'client', ->
before( (done) ->
helper.append_json('{"status": "build", "package": "test_1.2.3", "distribution": "unstable"}')
#launch_server() if process.env.SERVER
done()
)
beforeEach( (done) ->
client = io.connect("http://#{config.host}:#{config.port}", options)
client.once 'connect', ->
done()
)
afterEach( (done) ->
client.disconnect()
done()
)
it 'get distributions', (done) ->
client.once events.broadcast.distributions, (data) ->
data.should.be.eql(['trusty', 'unstable'])
done()
it 'get debomatic status', (done) ->
client.once events.broadcast.status_debomatic, (data) ->
data.running.should.be.false
done()
it 'get package status', (done) ->
client.once events.client.status, (data) ->
data = data[0]
data.status.should.be.eql('build')
data.distribution.should.be.eql('unstable')
data.package.should.be.eql('test_1.2.3')
done()
it 'on getting distribution packages', (done) ->
helper.make_package('unstable', 'test_1.2.3')
helper.make_package('unstable', 'test_1.2.4')
client.emit(events.client.distribution_packages, helper.get_query('unstable'))
client.once events.client.distribution_packages, (data) ->
data.distribution.name.should.be.eql('unstable')
packages_name = []
for p in data.distribution.packages
packages_name.push(p.orig_name)
packages_name.should.be.eql(['test_1.2.3', 'test_1.2.4'])
done()
it 'on getting package list', (done) ->
helper.make_file('unstable', 'test_1.2.3', 'buildlog', 'test')
helper.make_file('unstable', 'test_1.2.3', 'lintian', 'test')
client.emit(events.client.package_files_list, helper.get_query('unstable', 'test_1.2.3'))
client.once events.client.package_files_list, (data) ->
data.distribution.name.should.be.eql('unstable')
data.package.orig_name.should.be.eql('test_1.2.3')
files_name = []
for f in data.package.files
files_name.push(f.name)
files_name.should.be.eql(['buildlog', 'lintian'])
done()
describe 'on getting file', ->
it 'full content', (done) ->
helper.make_file('unstable', 'test_1.2.3', 'buildlog', 'this is a test')
client.emit(events.client.file, helper.get_query('unstable', 'test_1.2.3', 'buildlog'))
client.once events.client.file, (data) ->
data.distribution.name.should.be.eql('unstable')
data.package.orig_name.should.be.eql('test_1.2.3')
data.file.name.should.be.eql('buildlog')
data.file.content.should.be.eql('this is a test\n')
done()
it 'new content', (done) ->
client.once events.client.file_newcontent, (data) ->
data.distribution.name.should.be.eql('unstable')
data.package.orig_name.should.be.eql('test_1.2.3')
data.file.name.should.be.eql('buildlog')
data.file.new_content.should.be.eql('this is an appending test\n')
done()
client.emit(events.client.file, helper.get_query('unstable', 'test_1.2.3', 'buildlog'))
helper.append_file('unstable', 'test_1.2.3', 'buildlog', 'this is an appending test')
describe 'on build package ends', ->
it 'should receive a status update', (done) ->
client.once events.broadcast.status_update, (data) ->
data.success.should.be.ok
done()
str = '{"status": "build", "success": true, "package": "test_1.2.3", "uploader": "", "distribution": "unstable"}'
helper.append_json(str)
it 'should no longer receive status of same package', (done) ->
client1 = io.connect("http://#{config.host}:#{config.port}", options)
client1.once events.client.status, (data) ->
data.length.should.be.eql(1)
data[0].package.should.be.eql('test_1.2.4')
done()
str = '{"status": "build", "package": "test_1.2.4", "distribution": "unstable"}'
helper.append_json(str)
describe 'should get error on removing', ->
it 'package', (done) ->
helper.make_file('unstable', 'test_1.2.5', 'buildlog', 'test on error')
client.emit(events.client.file, helper.get_query('unstable', 'test_1.2.5', 'buildlog'))
client.once events.error, (data) ->
data.indexOf('buildlog').should.be.ok
data.indexOf('test_1.2.5').should.be.ok
data.indexOf('unstable').should.be.ok
data.indexOf('deleted').should.be.ok
done()
helper.clean('unstable', 'test_1.2.5')
process.on 'exit', () ->
client.disconnect()
helper.clean_all()# if process.env.SERVER
server.kill() if server?
###
debomatic-webui user configuration
###
###
Init some values, do not touch these
###
config = {}
config.debomatic = {}
config.web = {}
config.web.debomatic = {}
config.web.debomatic.admin = {}
config.web.debomatic.dput = {}
config.web.file = {}
config.web.preferences = {}
###
Configure host and port
###
config.host = "localhost"
config.port = 3030
###
Deb-O-Matic settings
###
config.debomatic.path = "debomatic/dir"
config.debomatic.jsonfile = "debomatic/json.log"
###
List of files to get a simple preview and number of lines
to show
###
config.web.file.preview = ["buildlog"]
config.web.file.num_lines = 25
# DO NOT EDIT THIS LINE:
module.exports = config
......@@ -66,7 +66,7 @@ cannot be downloaded and processed by Deb-o-Matic.
<h3>Porter uploads</h3>
<p>You could want to prepare a porter upload, a binary-only upload which generates
architecture dependent binaries only. Additional information can be found in
<a class="reference external" href="http://www.debian.org/doc/manuals/developers-reference/pkgs.html#porter-guidelines">Debian Developer’s Reference</a>.</p>
<a class="reference external" href="http://www.debian.org/doc/manuals/developers-reference/pkgs.html#porter-guidelines" target="_blank">Debian Developer’s Reference</a>.</p>
<p>In order to do so, you must use the <code>porter</code> command:</p>
<pre>echo "porter foo_version dist Joe Doe &lt;j.doe@acme.com&gt;" &gt; foo.commands</pre>
<p>where foo is the name of the source package you want to rebuild, version is
......
......@@ -5,10 +5,6 @@
</ol>
</nav>
<div id="error" class="alert alert-danger">
<strong>Oh snap!</strong> <span></span>
</div>
<article class="row">
<aside id="sidebar" class="col-md-3">
<nav id="packages">
......@@ -70,6 +66,12 @@
<header>
<h1 id="title"></h1>
</header>
<div id="error" class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">Oh snap!</h3>
</div>
<div class="panel-body message"></div>
</div>
<div id="welcome"></div>
<div id="file">
<div id="package_info"></div>
......
</div> <!-- #wrapper -->
<footer class="container-fluid">
<small class="copyright pull-right text-muted">
Fork me on <a href="https://github.com/LeoIannacone/debomatic-webui">github</a>
<div id="smile"></div>
<footer id="footer" class="container-fluid">
<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>
</small>
<small class="info pull-right text-muted"><%= web.debomatic.architecture %> (<%= version %>)</small>
<small class="info pull-right text-muted">v<%= version %> &bull;</small>
<div id="status" class="clearfix">
<span class="label label-default">status:</span>
<span class="debomatic idle text-muted">Idle</span>
......
......@@ -11,15 +11,10 @@
<body>
<div id="wrapper" class="container-fluid">
<header id="pageheader" onclick="window.location.href='/'">
<h1><%= web.title %> <small><%= version %></small></h1>
<p class="lead"><%= web.description %></p>
</header>
<nav class="navbar navbar-inverse" role="navigation">
<div class="navbar-header">
<span class="navbar-brand">Distributions</span>
<div onclick="window.location.href='/'" class="navbar-header home-link">
<span class="pull-left icon"></span>
<span class="navbar-brand">Deb-o-Matic <small><%= web.debomatic.architecture %></small></span>
</div>
<div class="collapse navbar-collapse">
<div id="distributions">
......@@ -33,10 +28,6 @@
<% if (web.paths.preferences) { %>
<li><a href="<%= web.paths.preferences %>">Preferences</a></li>
<% } %>
<li class="home-link">
<a class="pull-left" href="/">Home</a>
<span class="pull-right icon"></span>
</li>
</ul>
</div>
</div>
......
......@@ -3,7 +3,7 @@
<article id="home" class="page row">
<header class="col-md-12">
<h1>Welcome!</h1>
<p class="text-muted lead">This is <%= web.debomatic.architecture %> Debian source package build service powered by <a class="alert-link" href="http://launchpad.net/debomatic">Deb-o-Matic</a></p>
<p class="text-muted lead">This is <%= web.debomatic.architecture %> Debian source package build service powered by <a class="alert-link" href="http://launchpad.net/debomatic" target="_blank">Deb-o-Matic</a></p>
</header>
<section>
......@@ -104,7 +104,7 @@ scp_compress = 1
<p>It is also extendable using modules that are loaded and executed
during the build phases.</p>
<p><a class="btn btn-primary" href="http://launchpad.net/debomatic">Get more!</a></p>
<p><a class="btn btn-primary" href="http://launchpad.net/debomatic" target="_blank">Get more!</a></p>
</section>
</article>
......@@ -114,12 +114,12 @@ scp_compress = 1
<p><strong>debomatic-webui</strong> is a web interface for Deb-o-Matic 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.</p>
<p>This interface is built up on <a href="//nodejs.org">node</a> platform
and uses intensely <a href="//socket.io">socket.io</a> and <a href="//jquery.com/">jquery</a> technologies.</p>
<p>This interface is built up on <a href="//nodejs.org" target="_blank">node</a> platform
and uses intensely <a href="//socket.io" target="_blank">socket.io</a> and <a href="//jquery.com/" target="_blank">jquery</a> technologies.</p>
<p>Whenever you want to leave a suggestion or file a bug report, please open a <a href="https://github.com/LeoIannacone/debomatic-webui/issues">new issue</a>.</p>
<p>Whenever you want to leave a suggestion or file a bug report, please open a <a href="https://github.com/debomatic/debomatic-webui/issues" target="_blank">new issue</a>.</p>
<p><a class="btn btn-primary" href="https://github.com/LeoIannacone/debomatic-webui">Get more!</a></p>
<p><a class="btn btn-primary" href="https://github.com/debomatic/debomatic-webui" target="_blank">Get more!</a></p>
</section>
</article>
......
......@@ -16,8 +16,6 @@
</h4>
<dl class="dl-horizontal">
<dt>Show header</dt><dd>Hide header to have a minimized interface <span class="text-muted">as much as possible</span>.</dd>
<dt>Enable sidebar</dt><dd>Enable sidebar in <a href="<%= web.paths.distribution %>">distribution</a> page.</dd>
<dt>Glossy theme</dt><dd>Use a glossy theme for the interface. Not too much changes, just fancy.</dd>
......@@ -28,7 +26,7 @@
<dt>File font-size</dt><dd>Increase the font size for files content.</dd>
<dt class="debug">Debug level</dt><dd class="debug">Set debug level, please take a look at <a href="/javascripts/debug.js">debug.js</a> for more information. Level <code>0</code> means no debug. Please <strong>DO NOT</strong> enable debug if you do not really need it. Debug can affect perfomance.</dd>
<dt class="debug">Debug level</dt><dd class="debug">Set debug level, please take a look at <a href="/javascripts/debug.js" target="_blank">debug.js</a> for more information. Level <code>0</code> means no debug. Please <strong>DO NOT</strong> enable debug if you do not really need it. Debug can affect perfomance.</dd>
</dl>
</section>
......@@ -41,11 +39,6 @@
<h3 class="panel-title">Settings</h3>
</div>
<div class="panel-body">
<div class="checkbox">
<label>
<input id="header" type="checkbox"> Show header
</label>
</div>
<div class="checkbox">
<label>
<input id="sidebar" type="checkbox"> Enable sidebar
......@@ -91,4 +84,4 @@
</div>
</article>
<% include footer.ejs %>
\ No newline at end of file
<% include footer.ejs %>
#!/bin/bash
VERSION="$1"
BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.."
PACKAGE="${BASE_DIR}/debomatic-webui/package.json"
CURRENT="`grep '"version":' ${PACKAGE} | awk -F'"' '{print $4}'`"
if [ $# == 0 ] ; then
echo "Please specify a version number."
echo "Please specify a new version number."
echo "Current verion is ${CURRENT}"
exit
fi
VERSION=$1
BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.."
CONFIG=${BASE_DIR}/debomatic-webui/lib/config.js
CURRENT="`grep "^config.version" ${CONFIG} | awk -F"'" '{print $2}'`"
y='n'
echo -n "Current version is ${CURRENT}. Bump to ${VERSION} ? [y/N] "
read y
if [ "$y" == "y" -o "$y" == "Y" ] ; then
sed -r "s/^config.version = '(.*)'/config.version = '${VERSION}'/" -i $CONFIG || exit 1
sed -r "s/\"version\": \"(.*)\"/\"version\": \"${VERSION}\"/" -i $PACKAGE || exit 1
y='n'
echo -n "Do git-commit? [y/N] "
read y
if [ "$y" == "y" -o "$y" == "Y" ] ; then
git commit -m "Bumped version ${VERSION}" ${CONFIG}
git commit -m "Bumped version ${VERSION}" ${PACKAGE}
y='n'
echo -n "Do git-tag? [y/N] "
read y
......
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