* HydraFrontend -> Hydra.
19
src/Hydra/Makefile.PL
Normal file
@ -0,0 +1,19 @@
|
||||
# IMPORTANT: if you delete this file your app will not work as
|
||||
# expected. you have been warned
|
||||
use inc::Module::Install;
|
||||
|
||||
name 'HydraFrontend';
|
||||
all_from 'lib/HydraFrontend.pm';
|
||||
|
||||
requires 'Catalyst::Runtime' => '5.7015';
|
||||
requires 'Catalyst::Plugin::ConfigLoader';
|
||||
requires 'Catalyst::Plugin::Static::Simple';
|
||||
requires 'Catalyst::Action::RenderView';
|
||||
requires 'parent';
|
||||
requires 'Config::General'; # This should reflect the config file format you've chosen
|
||||
# See Catalyst::Plugin::ConfigLoader for supported formats
|
||||
catalyst;
|
||||
|
||||
install_script glob('script/*.pl');
|
||||
auto_install;
|
||||
WriteAll;
|
1
src/Hydra/hydrafrontend.conf
Normal file
@ -0,0 +1 @@
|
||||
name HydraFrontend
|
23
src/Hydra/lib/HydraFrontend.pm
Normal file
@ -0,0 +1,23 @@
|
||||
package HydraFrontend;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Catalyst::Runtime '5.70';
|
||||
|
||||
use parent qw/Catalyst/;
|
||||
use Catalyst qw/-Debug
|
||||
ConfigLoader
|
||||
Static::Simple
|
||||
StackTrace
|
||||
/;
|
||||
our $VERSION = '0.01';
|
||||
|
||||
__PACKAGE__->config(
|
||||
name => 'HydraFrontend',
|
||||
default_view => "TT"
|
||||
);
|
||||
|
||||
__PACKAGE__->setup();
|
||||
|
||||
1;
|
408
src/Hydra/lib/HydraFrontend/Controller/Root.pm
Normal file
@ -0,0 +1,408 @@
|
||||
package HydraFrontend::Controller::Root;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use parent 'Catalyst::Controller';
|
||||
use HydraFrontend::Helper::Nix;
|
||||
|
||||
#
|
||||
# Sets the actions in this controller to be registered with no prefix
|
||||
# so they function identically to actions created in MyApp.pm
|
||||
#
|
||||
__PACKAGE__->config->{namespace} = '';
|
||||
|
||||
|
||||
# Security checking of filenames.
|
||||
my $pathCompRE = "(?:[A-Za-z0-9-\+][A-Za-z0-9-\+\._]*)";
|
||||
my $relPathRE = "(?:$pathCompRE(?:\/$pathCompRE)*)";
|
||||
|
||||
|
||||
sub begin :Private {
|
||||
my ( $self, $c ) = @_;
|
||||
$c->stash->{projects} = [$c->model('DB::Projects')->search({}, {order_by => 'displayname'})];
|
||||
$c->stash->{curUri} = $c->request->uri;
|
||||
}
|
||||
|
||||
|
||||
sub error {
|
||||
my ($c, $msg) = @_;
|
||||
$c->stash->{template} = 'error.tt';
|
||||
$c->stash->{error} = $msg;
|
||||
$c->response->status(404);
|
||||
}
|
||||
|
||||
|
||||
sub trim {
|
||||
my $s = shift;
|
||||
$s =~ s/^\s+|\s+$//g;
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
sub getBuild {
|
||||
my ($c, $id) = @_;
|
||||
(my $build) = $c->model('DB::Builds')->search({ id => $id });
|
||||
return $build;
|
||||
}
|
||||
|
||||
|
||||
sub index :Path :Args(0) {
|
||||
my ( $self, $c ) = @_;
|
||||
$c->stash->{template} = 'index.tt';
|
||||
$c->stash->{scheduled} = [$c->model('DB::Builds')->search(
|
||||
{finished => 0}, {join => 'schedulingInfo'})]; # !!!
|
||||
$c->stash->{allBuilds} = [$c->model('DB::Builds')->search(
|
||||
{finished => 1}, {order_by => "timestamp DESC"})];
|
||||
# Get the latest finished build for each unique job.
|
||||
$c->stash->{latestBuilds} = [$c->model('DB::Builds')->search(undef,
|
||||
{ join => 'resultInfo'
|
||||
, where => "finished != 0 and timestamp = (select max(timestamp) from Builds where project == me.project and attrName == me.attrName and finished != 0)"
|
||||
, order_by => "project, attrname"
|
||||
})];
|
||||
}
|
||||
|
||||
|
||||
sub updateProject {
|
||||
my ($c, $project) = @_;
|
||||
|
||||
my $projectName = trim $c->request->params->{name};
|
||||
die "Invalid project name: $projectName" unless $projectName =~ /^[[:alpha:]]\w*$/;
|
||||
|
||||
my $displayName = trim $c->request->params->{displayname};
|
||||
die "Invalid display name: $displayName" if $displayName eq "";
|
||||
|
||||
$project->name($projectName);
|
||||
$project->displayname($displayName);
|
||||
$project->description(trim $c->request->params->{description});
|
||||
$project->enabled(trim($c->request->params->{enabled}) eq "1" ? 1 : 0);
|
||||
|
||||
$project->update;
|
||||
|
||||
my %jobsetNames;
|
||||
|
||||
foreach my $param (keys %{$c->request->params}) {
|
||||
next unless $param =~ /^jobset-(\w+)-name$/;
|
||||
my $baseName = $1;
|
||||
next if $baseName eq "template";
|
||||
|
||||
my $jobsetName = trim $c->request->params->{"jobset-$baseName-name"};
|
||||
die "Invalid jobset name: $jobsetName" unless $jobsetName =~ /^[[:alpha:]]\w*$/;
|
||||
|
||||
# The Nix expression path must be relative and can't contain ".." elements.
|
||||
my $nixExprPath = trim $c->request->params->{"jobset-$baseName-nixexprpath"};
|
||||
die "Invalid Nix expression path: $nixExprPath" if $nixExprPath !~ /^$relPathRE$/;
|
||||
|
||||
my $nixExprInput = trim $c->request->params->{"jobset-$baseName-nixexprinput"};
|
||||
die "Invalid Nix expression input name: $nixExprInput" unless $nixExprInput =~ /^\w+$/;
|
||||
|
||||
$jobsetNames{$jobsetName} = 1;
|
||||
|
||||
my $jobset;
|
||||
|
||||
if ($baseName =~ /^\d+$/) { # numeric base name is auto-generated, i.e. a new entry
|
||||
$jobset = $project->jobsets->create(
|
||||
{ name => $jobsetName
|
||||
, description => trim $c->request->params->{"jobset-$baseName-description"}
|
||||
, nixexprpath => $nixExprPath
|
||||
, nixexprinput => $nixExprInput
|
||||
});
|
||||
} else { # it's an existing jobset
|
||||
$jobset = ($project->jobsets->search({name => $baseName}))[0];
|
||||
die unless defined $jobset;
|
||||
$jobset->name($jobsetName);
|
||||
$jobset->description(trim $c->request->params->{"jobset-$baseName-description"});
|
||||
$jobset->nixexprpath($nixExprPath);
|
||||
$jobset->nixexprinput($nixExprInput);
|
||||
$jobset->update;
|
||||
}
|
||||
|
||||
my %inputNames;
|
||||
|
||||
# Process the inputs of this jobset.
|
||||
foreach my $param (keys %{$c->request->params}) {
|
||||
next unless $param =~ /^jobset-$baseName-input-(\w+)-name$/;
|
||||
my $baseName2 = $1;
|
||||
next if $baseName2 eq "template";
|
||||
print STDERR "GOT INPUT: $baseName2\n";
|
||||
|
||||
my $inputName = trim $c->request->params->{"jobset-$baseName-input-$baseName2-name"};
|
||||
die "Invalid input name: $inputName" unless $inputName =~ /^[[:alpha:]]\w*$/;
|
||||
|
||||
my $inputType = trim $c->request->params->{"jobset-$baseName-input-$baseName2-type"};
|
||||
die "Invalid input type: $inputType" unless
|
||||
$inputType eq "svn" || $inputType eq "cvs" || $inputType eq "tarball" ||
|
||||
$inputType eq "string" || $inputType eq "path";
|
||||
|
||||
$inputNames{$inputName} = 1;
|
||||
|
||||
my $input;
|
||||
if ($baseName2 =~ /^\d+$/) { # numeric base name is auto-generated, i.e. a new entry
|
||||
$input = $jobset->jobsetinputs->create(
|
||||
{ name => $inputName
|
||||
, type => $inputType
|
||||
});
|
||||
} else { # it's an existing jobset
|
||||
$input = ($jobset->jobsetinputs->search({name => $baseName2}))[0];
|
||||
die unless defined $input;
|
||||
$input->name($inputName);
|
||||
$input->type($inputType);
|
||||
$input->update;
|
||||
}
|
||||
|
||||
# Update the values for this input. Just delete all the
|
||||
# current ones, then create the new values.
|
||||
$input->jobsetinputalts->delete_all;
|
||||
my $values = $c->request->params->{"jobset-$baseName-input-$baseName2-values"};
|
||||
$values = [] unless defined $values;
|
||||
$values = [$values] unless ref($values) eq 'ARRAY';
|
||||
my $altnr = 0;
|
||||
foreach my $value (@{$values}) {
|
||||
print STDERR "VALUE: $value\n";
|
||||
$input->jobsetinputalts->create({altnr => $altnr++, value => trim $value});
|
||||
}
|
||||
}
|
||||
|
||||
# Get rid of deleted inputs.
|
||||
my @inputs = $jobset->jobsetinputs->all;
|
||||
foreach my $input (@inputs) {
|
||||
$input->delete unless defined $inputNames{$input->name};
|
||||
}
|
||||
}
|
||||
|
||||
# Get rid of deleted jobsets, i.e., ones that are no longer submitted in the parameters.
|
||||
my @jobsets = $project->jobsets->all;
|
||||
foreach my $jobset (@jobsets) {
|
||||
$jobset->delete unless defined $jobsetNames{$jobset->name};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub project :Local {
|
||||
my ( $self, $c, $projectName, $subcommand ) = @_;
|
||||
$c->stash->{template} = 'project.tt';
|
||||
|
||||
(my $project) = $c->model('DB::Projects')->search({ name => $projectName });
|
||||
return error($c, "Project $projectName doesn't exist.") if !defined $project;
|
||||
|
||||
my $isPosted = $c->request->method eq "POST";
|
||||
|
||||
$subcommand = "" unless defined $subcommand;
|
||||
|
||||
if ($subcommand eq "edit") {
|
||||
$c->stash->{edit} = 1;
|
||||
} elsif ($subcommand eq "submit" && $isPosted) {
|
||||
$c->model('DB')->schema->txn_do(sub {
|
||||
updateProject($c, $project);
|
||||
});
|
||||
return $c->res->redirect($c->uri_for("/project", trim $c->request->params->{name}));
|
||||
} elsif ($subcommand eq "delete" && $isPosted) {
|
||||
$c->model('DB')->schema->txn_do(sub {
|
||||
$project->delete;
|
||||
});
|
||||
return $c->res->redirect($c->uri_for("/"));
|
||||
} elsif ($subcommand eq "") {
|
||||
} else {
|
||||
return error($c, "Unknown subcommand $subcommand.");
|
||||
}
|
||||
|
||||
$c->stash->{curProject} = $project;
|
||||
|
||||
$c->stash->{finishedBuilds} = $c->model('DB::Builds')->search(
|
||||
{project => $projectName, finished => 1});
|
||||
|
||||
$c->stash->{succeededBuilds} = $c->model('DB::Builds')->search(
|
||||
{project => $projectName, finished => 1, buildStatus => 0},
|
||||
{join => 'resultInfo'});
|
||||
|
||||
$c->stash->{scheduledBuilds} = $c->model('DB::Builds')->search(
|
||||
{project => $projectName, finished => 0});
|
||||
|
||||
$c->stash->{busyBuilds} = $c->model('DB::Builds')->search(
|
||||
{project => $projectName, finished => 0, busy => 1},
|
||||
{join => 'schedulingInfo'});
|
||||
|
||||
$c->stash->{totalBuildTime} = $c->model('DB::Builds')->search(
|
||||
{project => $projectName},
|
||||
{join => 'resultInfo', select => {sum => 'stoptime - starttime'}, as => ['sum']})
|
||||
->first->get_column('sum');
|
||||
$c->stash->{totalBuildTime} = 0 unless defined $c->stash->{totalBuildTime};
|
||||
|
||||
$c->stash->{jobNames} =
|
||||
[$c->model('DB::Builds')->search({project => $projectName}, {select => [{distinct => 'attrname'}], as => ['attrname']})];
|
||||
}
|
||||
|
||||
|
||||
sub createproject :Local {
|
||||
my ( $self, $c, $subcommand ) = @_;
|
||||
|
||||
if (defined $subcommand && $subcommand eq "submit") {
|
||||
eval {
|
||||
my $projectName = $c->request->params->{name};
|
||||
$c->model('DB')->schema->txn_do(sub {
|
||||
# Note: $projectName is validated in updateProject,
|
||||
# which will abort the transaction if the name isn't
|
||||
# valid.
|
||||
my $project = $c->model('DB::Projects')->create({name => $projectName, displayname => ""});
|
||||
updateProject($c, $project);
|
||||
});
|
||||
return $c->res->redirect($c->uri_for("/project", $projectName));
|
||||
};
|
||||
if ($@) {
|
||||
return error($c, $@);
|
||||
}
|
||||
}
|
||||
|
||||
$c->stash->{template} = 'project.tt';
|
||||
$c->stash->{create} = 1;
|
||||
$c->stash->{edit} = 1;
|
||||
}
|
||||
|
||||
|
||||
sub job :Local {
|
||||
my ( $self, $c, $projectName, $jobName ) = @_;
|
||||
$c->stash->{template} = 'job.tt';
|
||||
|
||||
(my $project) = $c->model('DB::Projects')->search({ name => $projectName });
|
||||
return error($c, "Project $projectName doesn't exist.") if !defined $project;
|
||||
$c->stash->{curProject} = $project;
|
||||
|
||||
$c->stash->{jobName} = $jobName;
|
||||
$c->stash->{builds} = [$c->model('DB::Builds')->search(
|
||||
{finished => 1, project => $projectName, attrName => $jobName},
|
||||
{order_by => "timestamp DESC"})];
|
||||
}
|
||||
|
||||
|
||||
sub default :Path {
|
||||
my ( $self, $c ) = @_;
|
||||
error($c, "Page not found.");
|
||||
}
|
||||
|
||||
|
||||
sub build :Local {
|
||||
my ( $self, $c, $id ) = @_;
|
||||
|
||||
my $build = getBuild($c, $id);
|
||||
return error($c, "Build with ID $id doesn't exist.") if !defined $build;
|
||||
|
||||
$c->stash->{curProject} = $build->project;
|
||||
|
||||
$c->stash->{template} = 'build.tt';
|
||||
$c->stash->{build} = $build;
|
||||
$c->stash->{id} = $id;
|
||||
|
||||
$c->stash->{curTime} = time;
|
||||
|
||||
if (!$build->finished && $build->schedulingInfo->busy) {
|
||||
my $logfile = $build->schedulingInfo->logfile;
|
||||
$c->stash->{logtext} = `cat $logfile`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub log :Local {
|
||||
my ( $self, $c, $id ) = @_;
|
||||
|
||||
my $build = getBuild($c, $id);
|
||||
return error($c, "Build $id doesn't exist.") if !defined $build;
|
||||
|
||||
return error($c, "Build $id didn't produce a log.") if !defined $build->resultInfo->logfile;
|
||||
|
||||
$c->stash->{template} = 'log.tt';
|
||||
$c->stash->{build} = $build;
|
||||
|
||||
# !!! should be done in the view (as a TT plugin).
|
||||
$c->stash->{logtext} = loadLog($build->resultInfo->logfile);
|
||||
}
|
||||
|
||||
|
||||
sub nixlog :Local {
|
||||
my ( $self, $c, $id, $stepnr ) = @_;
|
||||
|
||||
my $build = getBuild($c, $id);
|
||||
return error($c, "Build with ID $id doesn't exist.") if !defined $build;
|
||||
|
||||
my $step = $build->buildsteps->find({stepnr => $stepnr});
|
||||
return error($c, "Build $id doesn't have a build step $stepnr.") if !defined $step;
|
||||
|
||||
return error($c, "Build step $stepnr of build $id does not have a log file.") if $step->logfile eq "";
|
||||
|
||||
$c->stash->{template} = 'log.tt';
|
||||
$c->stash->{build} = $build;
|
||||
$c->stash->{step} = $step;
|
||||
|
||||
# !!! should be done in the view (as a TT plugin).
|
||||
$c->stash->{logtext} = loadLog($step->logfile);
|
||||
}
|
||||
|
||||
|
||||
sub loadLog {
|
||||
my ($path) = @_;
|
||||
|
||||
die unless defined $path;
|
||||
|
||||
# !!! quick hack
|
||||
my $pipeline = ($path =~ /.bz2$/ ? "cat $path | bzip2 -d" : "cat $path")
|
||||
. " | nix-log2xml | xsltproc xsl/mark-errors.xsl - | xsltproc xsl/log2html.xsl - | tail -n +2";
|
||||
|
||||
return `$pipeline`;
|
||||
}
|
||||
|
||||
|
||||
sub download :Local {
|
||||
my ( $self, $c, $id, $productnr, $filename, @path ) = @_;
|
||||
|
||||
my $build = getBuild($c, $id);
|
||||
return error($c, "Build with ID $id doesn't exist.") if !defined $build;
|
||||
|
||||
my $product = $build->buildproducts->find({productnr => $productnr});
|
||||
return error($c, "Build $id doesn't have a product $productnr.") if !defined $product;
|
||||
|
||||
return error($c, "Product " . $product->path . " has disappeared.") unless -e $product->path;
|
||||
|
||||
# Security paranoia.
|
||||
foreach my $elem (@path) {
|
||||
return error($c, "Invalid filename $elem.") if $elem !~ /^$pathCompRE$/;
|
||||
}
|
||||
|
||||
my $path = $product->path;
|
||||
$path .= "/" . join("/", @path) if scalar @path > 0;
|
||||
|
||||
# If this is a directory but no "/" is attached, then redirect.
|
||||
if (-d $path && substr($c->request->uri, -1) ne "/") {
|
||||
return $c->res->redirect($c->request->uri . "/");
|
||||
}
|
||||
|
||||
$path = "$path/index.html" if -d $path && -e "$path/index.html";
|
||||
|
||||
if (!-e $path) {
|
||||
return error($c, "File $path does not exist.");
|
||||
}
|
||||
|
||||
$c->serve_static_file($path);
|
||||
}
|
||||
|
||||
|
||||
sub closure :Local {
|
||||
my ( $self, $c, $buildId, $productnr ) = @_;
|
||||
|
||||
my $build = getBuild($c, $buildId);
|
||||
return error($c, "Build with ID $buildId doesn't exist.") if !defined $build;
|
||||
|
||||
my $product = $build->buildproducts->find({productnr => $productnr});
|
||||
return error($c, "Build $buildId doesn't have a product $productnr.") if !defined $product;
|
||||
|
||||
return error($c, "Product is not a Nix build.") if $product->type ne "nix-build";
|
||||
|
||||
return error($c, "Path " . $product->path . " is no longer available.") unless HydraFrontend::Helper::Nix::isValidPath($product->path);
|
||||
|
||||
$c->stash->{current_view} = 'HydraFrontend::View::NixClosure';
|
||||
$c->stash->{storePath} = $product->path;
|
||||
$c->stash->{name} = $build->nixname;
|
||||
}
|
||||
|
||||
|
||||
sub end : ActionClass('RenderView') {}
|
||||
|
||||
|
||||
1;
|
14
src/Hydra/lib/HydraFrontend/Helper/Nix.pm
Normal file
@ -0,0 +1,14 @@
|
||||
package HydraFrontend::Helper::Nix;
|
||||
|
||||
use strict;
|
||||
|
||||
|
||||
sub isValidPath {
|
||||
my $path = shift;
|
||||
$SIG{CHLD} = 'DEFAULT'; # !!! work around system() failing if SIGCHLD is ignored
|
||||
return system("nix-store --check-validity $path") == 0;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
36
src/Hydra/lib/HydraFrontend/Model/DB.pm
Normal file
@ -0,0 +1,36 @@
|
||||
package HydraFrontend::Model::DB;
|
||||
|
||||
use strict;
|
||||
use base 'Catalyst::Model::DBIC::Schema';
|
||||
|
||||
__PACKAGE__->config(
|
||||
schema_class => 'HydraFrontend::Schema',
|
||||
connect_info => [
|
||||
'dbi:SQLite:../hydra.sqlite',
|
||||
|
||||
],
|
||||
);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
HydraFrontend::Model::DB - Catalyst DBIC Schema Model
|
||||
=head1 SYNOPSIS
|
||||
|
||||
See L<HydraFrontend>
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
L<Catalyst::Model::DBIC::Schema> Model using schema L<HydraFrontend::Schema>
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Eelco Dolstra
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software, you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
16
src/Hydra/lib/HydraFrontend/Schema.pm
Normal file
@ -0,0 +1,16 @@
|
||||
package HydraFrontend::Schema;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Schema';
|
||||
|
||||
__PACKAGE__->load_classes;
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:rS2THZrlrDHnIAWmvduE1g
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
48
src/Hydra/lib/HydraFrontend/Schema/Buildinputs.pm
Normal file
@ -0,0 +1,48 @@
|
||||
package HydraFrontend::Schema::Buildinputs;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("BuildInputs");
|
||||
__PACKAGE__->add_columns(
|
||||
"id",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"build",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"type",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"uri",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"revision",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"tag",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"value",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"dependency",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"path",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"sha256hash",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" });
|
||||
__PACKAGE__->belongs_to(
|
||||
"dependency",
|
||||
"HydraFrontend::Schema::Builds",
|
||||
{ id => "dependency" },
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9u9ep3Cq/SginPyhrzXlTA
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
41
src/Hydra/lib/HydraFrontend/Schema/Buildproducts.pm
Normal file
@ -0,0 +1,41 @@
|
||||
package HydraFrontend::Schema::Buildproducts;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("BuildProducts");
|
||||
__PACKAGE__->add_columns(
|
||||
"build",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"productnr",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"type",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"subtype",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"filesize",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"sha1hash",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"sha256hash",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"path",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"description",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("build", "productnr");
|
||||
__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" });
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:d85fCxlq/WDfQa20zXYuzw
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
35
src/Hydra/lib/HydraFrontend/Schema/Buildresultinfo.pm
Normal file
@ -0,0 +1,35 @@
|
||||
package HydraFrontend::Schema::Buildresultinfo;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("BuildResultInfo");
|
||||
__PACKAGE__->add_columns(
|
||||
"id",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"iscachedbuild",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"buildstatus",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"errormsg",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"starttime",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"stoptime",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"logfile",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
__PACKAGE__->belongs_to("id", "HydraFrontend::Schema::Builds", { id => "id" });
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:c2KXbqA8Xan4Lgf7AlK2EA
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
96
src/Hydra/lib/HydraFrontend/Schema/Builds.pm
Normal file
@ -0,0 +1,96 @@
|
||||
package HydraFrontend::Schema::Builds;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("Builds");
|
||||
__PACKAGE__->add_columns(
|
||||
"id",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"finished",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"timestamp",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"project",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"jobset",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"attrname",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"nixname",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"description",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"drvpath",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"outpath",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"system",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
__PACKAGE__->belongs_to(
|
||||
"project",
|
||||
"HydraFrontend::Schema::Projects",
|
||||
{ name => "project" },
|
||||
);
|
||||
__PACKAGE__->belongs_to(
|
||||
"jobset",
|
||||
"HydraFrontend::Schema::Jobsets",
|
||||
{ name => "jobset", project => "project" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"buildschedulinginfoes",
|
||||
"HydraFrontend::Schema::Buildschedulinginfo",
|
||||
{ "foreign.id" => "self.id" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"buildresultinfoes",
|
||||
"HydraFrontend::Schema::Buildresultinfo",
|
||||
{ "foreign.id" => "self.id" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"buildsteps",
|
||||
"HydraFrontend::Schema::Buildsteps",
|
||||
{ "foreign.id" => "self.id" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"buildinputs_builds",
|
||||
"HydraFrontend::Schema::Buildinputs",
|
||||
{ "foreign.build" => "self.id" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"buildinputs_dependencies",
|
||||
"HydraFrontend::Schema::Buildinputs",
|
||||
{ "foreign.dependency" => "self.id" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"buildproducts",
|
||||
"HydraFrontend::Schema::Buildproducts",
|
||||
{ "foreign.build" => "self.id" },
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/Iabv2HeyAsubLe+yPc/6Q
|
||||
|
||||
__PACKAGE__->has_many(dependents => 'HydraFrontend::Schema::Buildinputs', 'dependency');
|
||||
|
||||
__PACKAGE__->has_many(inputs => 'HydraFrontend::Schema::Buildinputs', 'build');
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"schedulingInfo",
|
||||
"HydraFrontend::Schema::Buildschedulinginfo",
|
||||
{ id => "id" },
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"resultInfo",
|
||||
"HydraFrontend::Schema::Buildresultinfo",
|
||||
{ id => "id" },
|
||||
);
|
||||
|
||||
1;
|
31
src/Hydra/lib/HydraFrontend/Schema/Buildschedulinginfo.pm
Normal file
@ -0,0 +1,31 @@
|
||||
package HydraFrontend::Schema::Buildschedulinginfo;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("BuildSchedulingInfo");
|
||||
__PACKAGE__->add_columns(
|
||||
"id",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"priority",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"busy",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"locker",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"logfile",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
__PACKAGE__->belongs_to("id", "HydraFrontend::Schema::Builds", { id => "id" });
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:vqJ7HEML5YNn5VIXEhZbnw
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
43
src/Hydra/lib/HydraFrontend/Schema/Buildsteps.pm
Normal file
@ -0,0 +1,43 @@
|
||||
package HydraFrontend::Schema::Buildsteps;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("BuildSteps");
|
||||
__PACKAGE__->add_columns(
|
||||
"id",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"stepnr",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"type",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"drvpath",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"outpath",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"logfile",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"busy",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"status",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"errormsg",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"starttime",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"stoptime",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("id", "stepnr");
|
||||
__PACKAGE__->belongs_to("id", "HydraFrontend::Schema::Builds", { id => "id" });
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BuZp6PHq9l/9xyA/x7TOVQ
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
39
src/Hydra/lib/HydraFrontend/Schema/Jobsetinputalts.pm
Normal file
@ -0,0 +1,39 @@
|
||||
package HydraFrontend::Schema::Jobsetinputalts;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("JobsetInputAlts");
|
||||
__PACKAGE__->add_columns(
|
||||
"project",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"jobset",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"input",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"altnr",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"value",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"revision",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
"tag",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("project", "jobset", "input", "altnr");
|
||||
__PACKAGE__->belongs_to(
|
||||
"jobsetinput",
|
||||
"HydraFrontend::Schema::Jobsetinputs",
|
||||
{ jobset => "jobset", name => "input", project => "project" },
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:x7OCv8YzB2L4H+RxEfwjbg
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
51
src/Hydra/lib/HydraFrontend/Schema/Jobsetinputs.pm
Normal file
@ -0,0 +1,51 @@
|
||||
package HydraFrontend::Schema::Jobsetinputs;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("JobsetInputs");
|
||||
__PACKAGE__->add_columns(
|
||||
"project",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"jobset",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"type",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("project", "jobset", "name");
|
||||
__PACKAGE__->has_many(
|
||||
"jobsets",
|
||||
"HydraFrontend::Schema::Jobsets",
|
||||
{
|
||||
"foreign.name" => "self.job",
|
||||
"foreign.nixexprinput" => "self.name",
|
||||
"foreign.project" => "self.project",
|
||||
},
|
||||
);
|
||||
__PACKAGE__->belongs_to(
|
||||
"jobset",
|
||||
"HydraFrontend::Schema::Jobsets",
|
||||
{ name => "jobset", project => "project" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"jobsetinputalts",
|
||||
"HydraFrontend::Schema::Jobsetinputalts",
|
||||
{
|
||||
"foreign.input" => "self.name",
|
||||
"foreign.jobset" => "self.jobset",
|
||||
"foreign.project" => "self.project",
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SKU48+1LqxIcuVY5gaDHCg
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
56
src/Hydra/lib/HydraFrontend/Schema/Jobsets.pm
Normal file
@ -0,0 +1,56 @@
|
||||
package HydraFrontend::Schema::Jobsets;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("Jobsets");
|
||||
__PACKAGE__->add_columns(
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"project",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"description",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"nixexprinput",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"nixexprpath",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("project", "name");
|
||||
__PACKAGE__->has_many(
|
||||
"builds",
|
||||
"HydraFrontend::Schema::Builds",
|
||||
{
|
||||
"foreign.jobset" => "self.name",
|
||||
"foreign.project" => "self.project",
|
||||
},
|
||||
);
|
||||
__PACKAGE__->belongs_to(
|
||||
"project",
|
||||
"HydraFrontend::Schema::Projects",
|
||||
{ name => "project" },
|
||||
);
|
||||
__PACKAGE__->belongs_to(
|
||||
"jobsetinput",
|
||||
"HydraFrontend::Schema::Jobsetinputs",
|
||||
{ job => "name", name => "nixexprinput", project => "project" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"jobsetinputs",
|
||||
"HydraFrontend::Schema::Jobsetinputs",
|
||||
{
|
||||
"foreign.jobset" => "self.name",
|
||||
"foreign.project" => "self.project",
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:F3WF5YS/Yas12dK2Gyekpg
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
38
src/Hydra/lib/HydraFrontend/Schema/Projects.pm
Normal file
@ -0,0 +1,38 @@
|
||||
package HydraFrontend::Schema::Projects;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("Projects");
|
||||
__PACKAGE__->add_columns(
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"displayname",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"description",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"enabled",
|
||||
{ data_type => "integer", is_nullable => 0, size => undef },
|
||||
);
|
||||
__PACKAGE__->set_primary_key("name");
|
||||
__PACKAGE__->has_many(
|
||||
"builds",
|
||||
"HydraFrontend::Schema::Builds",
|
||||
{ "foreign.project" => "self.name" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"jobsets",
|
||||
"HydraFrontend::Schema::Jobsets",
|
||||
{ "foreign.project" => "self.name" },
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-24 17:46:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:M+HA5YEL1oKKTQlLvhb6dw
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
26
src/Hydra/lib/HydraFrontend/View/NixClosure.pm
Normal file
@ -0,0 +1,26 @@
|
||||
package HydraFrontend::View::NixClosure;
|
||||
|
||||
use strict;
|
||||
use base qw/Catalyst::View/;
|
||||
use IO::Pipe;
|
||||
use POSIX qw(dup2);
|
||||
|
||||
sub process {
|
||||
my ( $self, $c ) = @_;
|
||||
|
||||
$c->response->content_type('application/x-nix-export');
|
||||
$c->response->header('Content-Disposition' => 'attachment; filename=' . $c->stash->{name} . '.closure.gz');
|
||||
|
||||
my $storePath = $c->stash->{storePath};
|
||||
|
||||
open(OUTPUT, "nix-store --export `nix-store -qR $storePath` | gzip |");
|
||||
|
||||
my $fh = new IO::Handle;
|
||||
$fh->fdopen(fileno(OUTPUT), "r") or die;
|
||||
|
||||
$c->response->body($fh);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
8
src/Hydra/lib/HydraFrontend/View/TT.pm
Normal file
@ -0,0 +1,8 @@
|
||||
package HydraFrontend::View::TT;
|
||||
|
||||
use strict;
|
||||
use base 'Catalyst::View::TT';
|
||||
|
||||
__PACKAGE__->config(TEMPLATE_EXTENSION => '.tt');
|
||||
|
||||
1;
|
374
src/Hydra/root/build.tt
Normal file
@ -0,0 +1,374 @@
|
||||
[% WRAPPER layout.tt title="Hydra Overview" %]
|
||||
[% PROCESS common.tt %]
|
||||
[% USE HTML %]
|
||||
[% USE date %]
|
||||
[% USE mibs=format("%.2f") %]
|
||||
|
||||
<h1>
|
||||
Job <tt>[% build.project.name %]:[% build.attrname %]</tt> build [% id %]
|
||||
[% IF !build.finished %]
|
||||
[% IF build.schedulingInfo.busy %]
|
||||
(currently building)
|
||||
[% ELSE %]
|
||||
(scheduled)
|
||||
[% END %]
|
||||
[% END %]
|
||||
</h1>
|
||||
|
||||
|
||||
<h2>Information</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Build ID:</th>
|
||||
<td>[% build.id %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Time added:</th>
|
||||
<td>[% date.format(build.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status:</th>
|
||||
<td>
|
||||
[% IF build.finished %]
|
||||
[% IF build.resultInfo.buildstatus == 0 %]
|
||||
<img src="/static/images/success.gif" />
|
||||
<strong>Success</strong>
|
||||
[% ELSIF build.resultInfo.buildstatus == 1 %]
|
||||
<img src="/static/images/failure.gif" />
|
||||
<span class="error">Build returned a non-zero exit code</span>
|
||||
[% ELSIF build.resultInfo.buildstatus == 2 %]
|
||||
<img src="/static/images/failure.gif" />
|
||||
<span class="error">A dependency of the build failed</span>
|
||||
[% ELSE %]
|
||||
<img src="/static/images/failure.gif" />
|
||||
<span class="error">Build failed</span>
|
||||
(see <a href="#nix-error">below</a>)
|
||||
[% END %]
|
||||
[% ELSIF build.schedulingInfo.busy %]
|
||||
<strong>Build in progress</strong>
|
||||
[% ELSE %]
|
||||
<strong>Scheduled to be built</strong>
|
||||
[% END %]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Project:</th>
|
||||
<td><a href="[% c.uri_for('/project' build.project.name) %]"><tt>[% build.project.name %]</tt></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Jobset:</th>
|
||||
<td><tt>[% build.jobset.name %]</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Job name:</th>
|
||||
<td><a href="[% c.uri_for('/job' build.project.name build.attrname) %]"><tt>[% build.attrname %]</tt></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nix name:</th>
|
||||
<td><tt>[% build.nixname %]</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Description:</th>
|
||||
<td>[% build.description %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>System:</th>
|
||||
<td><tt>[% build.system %]</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Derivation store path:</th>
|
||||
<td><tt>[% build.drvpath %]</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Output store path:</th>
|
||||
<td><tt>[% build.outpath %]</tt></td>
|
||||
</tr>
|
||||
[% IF build.finished %]
|
||||
<tr>
|
||||
<th>Build started:</th>
|
||||
<td>[% IF build.resultInfo.starttime %][% date.format(build.resultInfo.starttime, '%Y-%m-%d %H:%M:%S') %][% ELSE %]<em>(cached build)</em>[% END %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Build finished:</th>
|
||||
<td>[% IF build.resultInfo.stoptime %][% date.format(build.resultInfo.stoptime, '%Y-%m-%d %H:%M:%S') %][% ELSE %]<em>(cached build)</em>[% END %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Duration (seconds):</th>
|
||||
<td>
|
||||
[% IF build.resultInfo.iscachedbuild %]
|
||||
<em>(cached build)</em>
|
||||
[% ELSE %]
|
||||
[% build.resultInfo.stoptime - build.resultInfo.starttime %]
|
||||
[% END %]
|
||||
</td>
|
||||
</tr>
|
||||
[% IF build.resultInfo.logfile %]
|
||||
<tr>
|
||||
<th>Logfile:</th>
|
||||
<td>
|
||||
<a href="[% c.uri_for('/log' build.id) %]"><strong>Available</strong></a>
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
[% ELSE %]
|
||||
<tr>
|
||||
<th>Priority:</th>
|
||||
<td>[% build.schedulingInfo.priority %]</td>
|
||||
</tr>
|
||||
[% IF build.schedulingInfo.busy %]
|
||||
<tr>
|
||||
<th>Logfile:</th>
|
||||
<td><tt>[% build.schedulingInfo.logfile %]</tt></td>
|
||||
</tr>
|
||||
[% END %]
|
||||
[% END %]
|
||||
</table>
|
||||
|
||||
|
||||
<h2>Build inputs</h2>
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th>Name</th><th>Type</th><th>What</th><th>Store path</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH input IN build.inputs -%]
|
||||
<tr>
|
||||
<td><tt>[% input.name %]</tt></td>
|
||||
<td><tt>[% type = input.type; inputTypes.$type %]</tt></td>
|
||||
<td>
|
||||
[% IF input.type == "build" %]
|
||||
<a href="[% c.uri_for('/build' input.dependency.id) %]">Job <tt>[% input.dependency.project.name %]:[% input.dependency.attrname %]</tt> build [% input.dependency.id %]</a>
|
||||
[% ELSIF input.type == "string" %]
|
||||
<tt>"[% input.value %]"</tt>
|
||||
[% ELSE %]
|
||||
<tt>[% input.uri %]</tt>
|
||||
[% END %]
|
||||
</td>
|
||||
<td><tt>[% input.path %]</tt></td>
|
||||
</tr>
|
||||
[% END -%]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
[% IF build.buildsteps %]
|
||||
|
||||
<h2 id="buildsteps">Build steps</h2>
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th>Nr</th><th>What</th><th>Duration</th><th>Status</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH step IN build.buildsteps -%]
|
||||
<tr>
|
||||
<td>[% step.stepnr %]</td>
|
||||
<td>
|
||||
[% IF step.type == 0 %]
|
||||
Build of <tt>[% step.outpath %]</tt>
|
||||
[% ELSE %]
|
||||
Substitution of <tt>[% step.outpath %]</tt>
|
||||
[% END %]
|
||||
</td>
|
||||
<td>
|
||||
[% IF step.busy == 0 %]
|
||||
[% step.stoptime - step.starttime %]s
|
||||
[% ELSE %]
|
||||
[% IF build.finished %]
|
||||
[% build.resultInfo.stoptime - step.starttime %]s
|
||||
[% ELSE %]
|
||||
[% curTime - step.starttime %]s
|
||||
[% END %]
|
||||
[% END %]
|
||||
</td>
|
||||
<td>
|
||||
[% IF step.busy == 1 %]
|
||||
[% IF build.finished %]
|
||||
<span class="error">Aborted</span>
|
||||
[% ELSE %]
|
||||
<strong>Building</strong>
|
||||
[% END %]
|
||||
[% ELSIF step.status == 0 %]
|
||||
Succeeded
|
||||
[% ELSE %]
|
||||
<span class="error">Failed: [% HTML.escape(step.errormsg) %]</span>
|
||||
[% END %]
|
||||
[% IF step.logfile %]
|
||||
(<a href="[% c.uri_for('/nixlog' build.id step.stepnr) %]">log</a>)
|
||||
[% END %]
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% IF build.finished %]
|
||||
|
||||
|
||||
[% IF build.resultInfo.errormsg %]
|
||||
|
||||
<h2 id="nix-error">Nix error output</h2>
|
||||
|
||||
<pre class="buildlog">
|
||||
[% HTML.escape(build.resultInfo.errormsg) -%]
|
||||
</pre>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% IF build.buildproducts %]
|
||||
|
||||
|
||||
<h2>Build products</h2>
|
||||
|
||||
<ul class="productList">
|
||||
|
||||
[% FOREACH product IN build.buildproducts -%]
|
||||
<li class="product">
|
||||
[% SWITCH product.type %]
|
||||
|
||||
[% CASE "nix-build" %]
|
||||
<a href="[% c.uri_for('/closure' build.id product.productnr) %]">
|
||||
<img src="/static/images/nix-build.png" alt="Source" />
|
||||
Nix build of path <tt>[% product.path %]</tt>
|
||||
</a>
|
||||
[<a class="productDetailsToggle" href="javascript:">help</a>]
|
||||
<div class="help productDetails">
|
||||
<p>If you have Nix installed on your machine, this build and all
|
||||
its dependencies can be unpacked into your local Nix store by
|
||||
doing:</p>
|
||||
|
||||
<pre>$ gunzip < [% HTML.escape(build.nixname) %].closure.gz | nix-store --import</pre>
|
||||
|
||||
or to download and unpack in one command:
|
||||
|
||||
<pre>$ curl [% c.uri_for('/closure' build.id product.productnr) %] | gunzip | nix-store --import</pre>
|
||||
|
||||
<p>The package can then be found in the path <tt>[%
|
||||
product.path %]</tt>. If you get the error message “imported
|
||||
archive lacks a signature”, you should make sure that you have
|
||||
sufficient access rights to the Nix store, e.g., run the
|
||||
command as <tt>root</tt>.</p>
|
||||
</div>
|
||||
|
||||
[% CASE "file" %]
|
||||
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
|
||||
[% SWITCH product.subtype %]
|
||||
[% CASE "source-dist" %]
|
||||
<img src="/static/images/source-dist.png" alt="Source" /> Source distribution <tt>[% product.name %]</tt>
|
||||
[% CASE "rpm" %]
|
||||
<img src="/static/images/rpm.png" alt="RPM" /> RPM package <tt>[% product.name %]</tt>
|
||||
[% CASE "deb" %]
|
||||
<img src="/static/images/debian.png" alt="RPM" /> Debian package <tt>[% product.name %]</tt>
|
||||
[% CASE DEFAULT %]
|
||||
File <tt>[% product.name %]</tt> of type <tt>[% product.subtype %]</tt>
|
||||
[% END %]
|
||||
</a>
|
||||
[<a class="productDetailsToggle" href="javascript:">details</a>]
|
||||
<div class="productDetails">
|
||||
<table>
|
||||
<tr>
|
||||
<th>URL:</th>
|
||||
<td>
|
||||
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
|
||||
<tt>[% c.uri_for('/download' build.id product.productnr product.name) %]</tt>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><th>File size:</th><td>[% product.filesize %] bytes ([% mibs(product.filesize / (1024 * 1024)) %] MiB)</td></tr>
|
||||
<tr><th>SHA-1 hash:</th><td>[% product.sha1hash %]</td></tr>
|
||||
<tr><th>SHA-256 hash:</th><td>[% product.sha256hash %]</td></tr>
|
||||
<tr><th>Full path:</th><td><tt>[% product.path %]</tt></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
[% CASE "report" %]
|
||||
|
||||
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
|
||||
<img src="/static/images/report.png" alt="Report" />
|
||||
[% SWITCH product.subtype %]
|
||||
[% CASE "coverage" %]
|
||||
Code coverage analysis report
|
||||
[% CASE DEFAULT %]
|
||||
Report of type <tt>[% product.subtype %]</tt>
|
||||
[% END %]
|
||||
</a>
|
||||
|
||||
[% CASE "doc" %]
|
||||
|
||||
<a href="[% c.uri_for('/download' build.id product.productnr product.name) %]">
|
||||
<img src="/static/images/document.png" alt="Document" />
|
||||
[% SWITCH product.subtype %]
|
||||
[% CASE "readme" %]
|
||||
“README” file
|
||||
[% CASE DEFAULT %]
|
||||
Documentation of type <tt>[% product.subtype %]</tt>
|
||||
[% END %]
|
||||
</a>
|
||||
|
||||
[% CASE DEFAULT %]
|
||||
Something of type <tt>[% product.type %]</tt>
|
||||
|
||||
[% END %]
|
||||
</li>
|
||||
[% END -%]
|
||||
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.productDetailsToggle').toggle(
|
||||
function () { $(".productDetails", $(this).parents(".product")).fadeIn(); },
|
||||
function () { $(".productDetails", $(this).parents(".product")).hide(); }
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% IF build.dependents %]
|
||||
|
||||
<h2>Used by</h2>
|
||||
|
||||
<p>The following builds have used this build as an input:</p>
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th>Build</th><th>Input name</th><th>System</th><th>Timestamp</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH input IN build.dependents -%]
|
||||
<tr>
|
||||
<td><a href="[% c.uri_for('/build' input.build.id) %]">Job <tt>[% input.build.project.name %]:[% input.build.attrname %]</tt> build [% input.build.id %]</a></td>
|
||||
<td><tt>[% input.name %]</tt></td>
|
||||
<td><tt>[% input.build.system %]</tt></td>
|
||||
<td>[% date.format(input.build.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
|
||||
</tr>
|
||||
[% END -%]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% ELSIF build.schedulingInfo.busy %]
|
||||
|
||||
|
||||
<h2>Log</h2>
|
||||
|
||||
<pre class="buildlog">
|
||||
[% HTML.escape(logtext) -%]
|
||||
</pre>
|
||||
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% END %]
|
||||
|
9
src/Hydra/root/common.tt
Normal file
@ -0,0 +1,9 @@
|
||||
[% inputTypes =
|
||||
{ "svn" = "Subversion checkout"
|
||||
, "cvs" = "CVS checkout"
|
||||
, "tarball" = "Download of a tarball"
|
||||
, "string" = "String value"
|
||||
, "path" = "Local path"
|
||||
, "build" = "Build output"
|
||||
}
|
||||
%]
|
8
src/Hydra/root/error.tt
Normal file
@ -0,0 +1,8 @@
|
||||
[% WRAPPER layout.tt title="Hydra Overview" %]
|
||||
[% USE HTML %]
|
||||
|
||||
<h1>Error</h1>
|
||||
|
||||
<p>I'm very sorry, but an error occurred: <span class="error-msg">[% HTML.escape(error) %]</span></p>
|
||||
|
||||
[% END %]
|
79
src/Hydra/root/index.tt
Normal file
@ -0,0 +1,79 @@
|
||||
[% WRAPPER layout.tt title="Hydra Overview" %]
|
||||
[% USE date %]
|
||||
|
||||
|
||||
<h1>Hydra Overview</h1>
|
||||
|
||||
|
||||
<h2>Queue</h2>
|
||||
|
||||
[% IF scheduled.size == 0 %]
|
||||
|
||||
<p>The queue is empty.</p>
|
||||
|
||||
[% ELSE %]
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th>#</th><th>Priority</th><th>Project</th><th>Job</th><th>System</th><th>Timestamp</th><th>Description</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH build IN scheduled -%]
|
||||
<tr [% IF build.schedulingInfo.busy %]class="runningJob"[% END %] >
|
||||
<td><a href="[% c.uri_for('/build' build.id) %]">[% build.id %]</a></td>
|
||||
<td>[% build.schedulingInfo.priority %]</td>
|
||||
<td><tt>[% build.project.name %]</tt></td>
|
||||
<td><tt>[% build.attrname %]</tt></td>
|
||||
<td><tt>[% build.system %]</tt></td>
|
||||
<td>[% date.format(build.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
|
||||
<td>[% build.description %]</td>
|
||||
</tr>
|
||||
[% END -%]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
<h2>Job status</h2>
|
||||
|
||||
<p>Below are the latest builds for each job.</p>
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th></th><th>#</th><th>Project</th><th>Job</th><th>System</th><th>Timestamp</th><th>Description</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH build IN latestBuilds -%]
|
||||
[% INCLUDE "short-build-info.tt" %]
|
||||
[% END -%]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<h2>All builds</h2>
|
||||
|
||||
<p>Number of builds: [% allBuilds.size %]</p>
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th></th><th>#</th><th>Project</th><th>Job</th><th>System</th><th>Timestamp</th><th>Description</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH build IN allBuilds -%]
|
||||
[% INCLUDE "short-build-info.tt" %]
|
||||
[% END -%]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<h2>Projects</h2>
|
||||
|
||||
<ul>
|
||||
[% FOREACH project IN projects -%]
|
||||
<li><a href="[% c.uri_for('/project' project.name) %]"><tt>[% project.name %]</tt></a></li>
|
||||
[% END -%]
|
||||
</ul>
|
||||
|
||||
|
||||
[% END %]
|
16
src/Hydra/root/job.tt
Normal file
@ -0,0 +1,16 @@
|
||||
[% WRAPPER layout.tt title="Hydra Overview" %]
|
||||
|
||||
<h1>All builds for job <tt>[% curProject.name %]:[% jobName %]</tt></h1>
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th></th><th>Id</th><th>Project</th><th>Job</th><th>System</th><th>Timestamp</th><th>Description</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH build IN builds -%]
|
||||
[% INCLUDE "short-build-info.tt" %]
|
||||
[% END -%]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[% END %]
|
122
src/Hydra/root/layout.tt
Normal file
@ -0,0 +1,122 @@
|
||||
[% USE date -%]
|
||||
[% USE HTML -%]
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
[% BLOCK makeLink %]
|
||||
<li [% IF curUri == uri %]class="active"[% END %]>
|
||||
<div class="title"><a href="[% uri %]">[% title %]</a></div>
|
||||
</li>
|
||||
[% END %]
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
|
||||
<head>
|
||||
<title>[% title %]</title>
|
||||
<link rel="stylesheet" href="/static/css/hydra.css" type="text/css" />
|
||||
<link rel="stylesheet" href="/static/css/logfile.css" type="text/css" />
|
||||
<script type="text/javascript" src="/static/js/jquery-pack.js"></script>
|
||||
<script type="text/javascript" src="/static/js/tablesorter/jquery.tablesorter.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("table.tablesorter").tablesorter();
|
||||
|
||||
/* Set the appearance of the toggle depending on whether the
|
||||
corresponding subtree is initially shown or hidden. */
|
||||
$(".logTreeToggle").map(function() {
|
||||
if ($(this).siblings("ul:hidden").length == 0) {
|
||||
$(this).text("-");
|
||||
} else {
|
||||
$(this).text("+");
|
||||
}
|
||||
});
|
||||
|
||||
/* When a toggle is clicked, show or hide the subtree. */
|
||||
$(".logTreeToggle").click(function() {
|
||||
if ($(this).siblings("ul:hidden").length != 0) {
|
||||
$(this).siblings("ul").show();
|
||||
$(this).text("-");
|
||||
} else {
|
||||
$(this).siblings("ul").hide();
|
||||
$(this).text("+");
|
||||
}
|
||||
});
|
||||
|
||||
/* Implementation of the expand all link. */
|
||||
$(".logTreeExpandAll").click(function() {
|
||||
$(".logTreeToggle", $(this).siblings(".toplevel")).map(function() {
|
||||
$(this).siblings("ul").show();
|
||||
$(this).text("-");
|
||||
});
|
||||
});
|
||||
|
||||
/* Implementation of the collapse all link. */
|
||||
$(".logTreeCollapseAll").click(function() {
|
||||
$(".logTreeToggle", $(this).siblings(".toplevel")).map(function() {
|
||||
$(this).siblings("ul").hide();
|
||||
$(this).text("+");
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
|
||||
<div id="leftnavbar">
|
||||
<ul class="menu">
|
||||
<li>
|
||||
<div class="title">Hydra</div>
|
||||
<ul class="submenu">
|
||||
[% INCLUDE makeLink uri = c.uri_for('/') title = "Overview" %]
|
||||
[% INCLUDE makeLink uri = c.uri_for('/queue') title = "Queue" %]
|
||||
[% INCLUDE makeLink uri = c.uri_for('/latest') title = "Latest builds" %]
|
||||
[% INCLUDE makeLink uri = c.uri_for('/all') title = "All builds" %]
|
||||
[% INCLUDE makeLink uri = c.uri_for('/search') title = "Search builds" %]
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div class="title">Projects</div>
|
||||
<ul class="submenu">
|
||||
[% FOREACH project IN projects %]
|
||||
<li [% IF curUri == c.uri_for('/project' project.name) %]class="active"[% END %]>
|
||||
<div class="title"><a href="[% c.uri_for('/project' project.name) %]">[% HTML.escape(project.displayname) %]</a></div>
|
||||
[% IF curProject.name == project.name %]
|
||||
<ul class="subsubmenu">
|
||||
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'edit') title = "Edit" %]
|
||||
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'status') title = "Status" %]
|
||||
[% INCLUDE makeLink uri = c.uri_for('/project' project.name 'all') title = "All builds" %]
|
||||
</ul>
|
||||
[% END %]
|
||||
</li>
|
||||
[% END %]
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div class="title">Admin</div>
|
||||
<ul class="submenu">
|
||||
[% INCLUDE makeLink uri = c.uri_for('/users') title = "Users" %]
|
||||
[% INCLUDE makeLink uri = c.uri_for('/createproject') title = "Create a project" %]
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
[% content %]
|
||||
<div id="footer">
|
||||
<hr />
|
||||
Generated at [% date.format %].
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
9
src/Hydra/root/log.tt
Normal file
@ -0,0 +1,9 @@
|
||||
[% WRAPPER layout.tt title="Hydra Overview" %]
|
||||
|
||||
<h1>Logfile for <tt>[% build.project.name %]:[% build.attrname %]</tt> build [% build.id %][%IF step %], step [% step.stepnr %] (<tt>[% step.outpath %]</tt>)[% END %]</h1>
|
||||
|
||||
<div class="buildlog">
|
||||
[% logtext -%]
|
||||
</div>
|
||||
|
||||
[% END %]
|
296
src/Hydra/root/project.tt
Normal file
@ -0,0 +1,296 @@
|
||||
[% WRAPPER layout.tt title="Hydra Overview" %]
|
||||
[% PROCESS common.tt %]
|
||||
[% USE HTML %]
|
||||
|
||||
|
||||
[% BLOCK renderSelection %]
|
||||
[% IF edit %]
|
||||
<select [% HTML.attributes(id => param, name => param) %]>
|
||||
[% FOREACH name IN options.keys.sort %]
|
||||
<option [% HTML.attributes(value => name) %] [% IF name == curValue; "selected='selected'"; END %]>[% options.$name %]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
[% ELSE %]
|
||||
[% options.$curValue %]
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
||||
|
||||
[% BLOCK maybeEditString %]
|
||||
[% IF edit %]
|
||||
<input type="text" class="string [% extraClass %]" [% HTML.attributes(id => param, name => param, value => value) %] />
|
||||
[% ELSE %]
|
||||
[% HTML.escape(value) %]
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
||||
|
||||
[% BLOCK renderInputAlt %]
|
||||
[% IF edit %]<button type="button" onclick='$(this).parents(".inputalt").remove()'><img src="/static/images/failure.gif" alt="Delete value" /></button>[% END -%]
|
||||
[% INCLUDE maybeEditString param=param value=alt.value %]
|
||||
[% IF edit %]<br />[% END %]
|
||||
[% END %]
|
||||
|
||||
|
||||
[% BLOCK renderInput %]
|
||||
|
||||
<tr class="input [% extraClass %]" id="[% id %]">
|
||||
<td>
|
||||
[% IF edit %]<button type="button" onclick='$(this).parents(".input").remove()'><img src="/static/images/failure.gif" alt="Delete input" /></button>[% END -%]
|
||||
<tt>[% INCLUDE maybeEditString param="$baseName-name" value=input.name extraClass="shortString" %]</tt>
|
||||
</td>
|
||||
<td>
|
||||
[% INCLUDE renderSelection curValue=input.type param="$baseName-type" options=inputTypes %]
|
||||
</td>
|
||||
<td class="inputalts" id="[% baseName %]">
|
||||
[% FOREACH alt IN input.jobsetinputalts -%]
|
||||
<tt class="inputalt">
|
||||
[% IF input.type == "string" && !edit %]
|
||||
"[% HTML.escape(alt.value) %]"
|
||||
[% ELSE %]
|
||||
[% INCLUDE renderInputAlt alt=alt param="$baseName-values" %]
|
||||
[% END %]
|
||||
</tt>
|
||||
[% END %]
|
||||
[% IF edit %]<button type="button" onclick='return false' class="add-inputalt">+</button>[% END %]
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% BLOCK renderJobset %]
|
||||
|
||||
<div class="jobset[% IF edit %] jobset-edit[% END %]" id="[% "jobset-$baseName" %]">
|
||||
|
||||
<h3>
|
||||
[% IF edit %]<button type="button" onclick='$(this).parents(".jobset").remove()'><img src="/static/images/failure.gif" alt="Delete value" /></button>[% END %]
|
||||
[% IF jobset %]Jobset <tt>[% jobset.name %]</tt>[% ELSE %]New jobset[% END %]
|
||||
</h3>
|
||||
|
||||
<h4>Information</h4>
|
||||
|
||||
<table>
|
||||
[% IF edit %]
|
||||
<tr>
|
||||
<th>Identifier:</th>
|
||||
<td>[% INCLUDE maybeEditString param="jobset-$baseName-name" value=jobset.name %]</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
<tr>
|
||||
<th>Description:</th>
|
||||
<td>[% INCLUDE maybeEditString param="jobset-$baseName-description" value=jobset.description %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nix expression:</th>
|
||||
<td>
|
||||
<tt>[% INCLUDE maybeEditString param="jobset-$baseName-nixexprpath" value=jobset.nixexprpath extraClass="shortString" %]</tt> in input
|
||||
<tt>[% INCLUDE maybeEditString param="jobset-$baseName-nixexprinput" value=jobset.nixexprinput extraClass="shortString" %]</tt>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>Inputs</h4>
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th>Input name</th><th>Type</th><th>Values</th></tr>
|
||||
</thead>
|
||||
<tbody class="inputs">
|
||||
[% FOREACH input IN jobset.jobsetinputs -%]
|
||||
[% INCLUDE renderInput input=input baseName="jobset-$baseName-input-$input.name" %]
|
||||
[% END %]
|
||||
[% IF edit %]
|
||||
<tr>
|
||||
<td colspan="3"><button type="button" class="add-input">Add a new input</button></td>
|
||||
</tr>
|
||||
[% END %]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% IF edit %]
|
||||
<form action="[% IF create %][% c.uri_for('/createproject/submit') %][% ELSE %][% c.uri_for('/project' curProject.name 'submit') %][% END %]" method="post">
|
||||
[% END %]
|
||||
|
||||
|
||||
[% IF create %]
|
||||
<h1>New Project</h1>
|
||||
[% ELSE %]
|
||||
<h1>Project <tt>[% curProject.name %]</tt></h1>
|
||||
[% END %]
|
||||
|
||||
|
||||
<h2>General information</h2>
|
||||
|
||||
<table>
|
||||
[% IF edit %]
|
||||
<tr>
|
||||
<th>Identifier:</th>
|
||||
<td><tt>[% INCLUDE maybeEditString param="name" value=curProject.name %]</tt></td>
|
||||
</tr>
|
||||
[% END %]
|
||||
<tr>
|
||||
<th>Display name:</th>
|
||||
<td>[% INCLUDE maybeEditString param="displayname" value=curProject.displayname %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Description:</th>
|
||||
<td>[% INCLUDE maybeEditString param="description" value=curProject.description %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Enabled:</th>
|
||||
<td>
|
||||
[% INCLUDE renderSelection param="enabled" curValue=curProject.enabled options={"1" = "Yes", "0" = "No"} %]
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<h2>Jobsets</h2>
|
||||
|
||||
[% IF curProject.jobsets && curProject.jobsets.size > 0 || edit %]
|
||||
|
||||
[% IF edit %]
|
||||
<p><button type="button" id="add-jobset">Add a new jobset</button></p>
|
||||
|
||||
<div id="jobset-template" class="template">
|
||||
[% INCLUDE renderJobset jobset="" baseName="template" %]
|
||||
</div>
|
||||
|
||||
<table class="template"> <!-- dummy wrapper needed because “hidden” trs are visible anyway -->
|
||||
[% INCLUDE renderInput input="" extraClass="template" id="input-template" baseName="input-template" %]
|
||||
</table>
|
||||
|
||||
<tt class="inputalt template" id="inputalt-template">
|
||||
[% INCLUDE renderInputAlt alt=alt %]
|
||||
</tt>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var id = 0;
|
||||
|
||||
$("#add-jobset").click(function() {
|
||||
var newid = "jobset-" + id++;
|
||||
var x = $("#jobset-template").clone(true).attr("id", newid).insertAfter($("#jobset-template")).slideDown("fast");
|
||||
$("#jobset-template", x).attr("id", newid);
|
||||
$("#jobset-template-name", x).attr("name", newid + "-name");
|
||||
$("#jobset-template-description", x).attr("name", newid + "-description");
|
||||
$("#jobset-template-nixexprpath", x).attr("name", newid + "-nixexprpath");
|
||||
$("#jobset-template-nixexprinput", x).attr("name", newid + "-nixexprinput");
|
||||
return false;
|
||||
});
|
||||
|
||||
$(".add-input").click(function() {
|
||||
var jobset = $(this).parents(".jobset");
|
||||
var inputid = jobset.attr("id");
|
||||
var newid = inputid + "-input-" + id++;
|
||||
var x = $("#input-template").clone(true).attr("id", "").insertBefore($(this).parents("tr")).show();
|
||||
$("#input-template-name", x).attr("name", newid + "-name");
|
||||
$("#input-template-type", x).attr("name", newid + "-type");
|
||||
$("#input-template", x).attr("id", newid);
|
||||
return false;
|
||||
});
|
||||
|
||||
$(".add-inputalt").click(function() {
|
||||
var x = $("#inputalt-template").clone(true).insertBefore($(this)).attr("id", "").show();
|
||||
$("input", x).attr("name", x.parents(".inputalts").attr("id") + "-values");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
[% END %]
|
||||
|
||||
[% FOREACH jobset IN curProject.jobsets -%]
|
||||
[% INCLUDE renderJobset jobset=jobset baseName=jobset.name %]
|
||||
[% END -%]
|
||||
|
||||
[% ELSE %]
|
||||
|
||||
<p>No jobsets have been defined yet.</p>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% IF !edit %]
|
||||
|
||||
|
||||
<h2>Jobs</h2>
|
||||
|
||||
[% IF jobName && jobNames.size > 0 %]
|
||||
|
||||
<ul>
|
||||
[% FOREACH jobName IN jobNames -%]
|
||||
<li><a href="[% c.uri_for('/job' curProject.name jobName.attrname) %]"><tt>[% jobName.attrname %]</tt></a></li>
|
||||
[% END %]
|
||||
</ul>
|
||||
|
||||
[% ELSE %]
|
||||
|
||||
<p>No builds have been performed or scheduled.</p>
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
<h2>Statistics</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Finished builds:</th>
|
||||
<td>[% finishedBuilds %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><img src="/static/images/success.gif" /> Succeeded builds:</th>
|
||||
<td>[% succeededBuilds %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><img src="/static/images/failure.gif" /> Failed builds:</th>
|
||||
<td>[% finishedBuilds - succeededBuilds %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total build time:</th>
|
||||
<td>[% totalBuildTime %]s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Scheduled builds:</th>
|
||||
<td>[% scheduledBuilds %]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Currently executing builds:</th>
|
||||
<td>[% busyBuilds %]</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% IF edit %]
|
||||
|
||||
<hr />
|
||||
|
||||
<p><button type="submit"><img src="/static/images/success.gif" />[%IF create %]Create[% ELSE %]Apply changes[% END %]</button></p>
|
||||
|
||||
</form>
|
||||
|
||||
[% IF !create %]
|
||||
|
||||
<form action="[% c.uri_for('/project' curProject.name 'delete') %]" method="post">
|
||||
<p><button id="delete-project" type="submit"><img src="/static/images/failure.gif" />Delete this project</button></p>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$("#delete-project").click(function() {
|
||||
return confirm("Are you sure you want to delete this project?");
|
||||
});
|
||||
</script>
|
||||
|
||||
[% END %]
|
||||
|
||||
[% END %]
|
||||
|
||||
|
||||
[% END %]
|
17
src/Hydra/root/short-build-info.tt
Normal file
@ -0,0 +1,17 @@
|
||||
[% USE date %]
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
[% IF build.resultInfo.buildstatus == 0 %]
|
||||
<img src="/static/images/success.gif" />
|
||||
[% ELSE %]
|
||||
<img src="/static/images/failure.gif" />
|
||||
[% END %]
|
||||
</td>
|
||||
<td><a href="[% c.uri_for('/build' build.id) %]">[% build.id %]</a></td>
|
||||
<td><a href="[% c.uri_for('/project' build.project.name) %]"><tt>[% build.project.name %]</tt></a></td>
|
||||
<td><a href="[% c.uri_for('/job' build.project.name build.attrname) %]"><tt>[% build.attrname %]</tt></a></td>
|
||||
<td><tt>[% build.system %]</tt></td>
|
||||
<td>[% date.format(build.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
|
||||
<td>[% build.description %]</td>
|
||||
</tr>
|
397
src/Hydra/root/static/css/hydra.css
Normal file
@ -0,0 +1,397 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: white;
|
||||
margin: 2em 1em 2em 1em;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-weight: bold;
|
||||
color: #005aa0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 220%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 130%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
empty-cells: show;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: 2px 5px;
|
||||
border: solid black 1px;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #ffffc0;
|
||||
}
|
||||
|
||||
td.pkgname {
|
||||
font-size: 140%;
|
||||
font-weight: bold;
|
||||
color: #005aa0;
|
||||
background: #ffffe0;
|
||||
}
|
||||
|
||||
td.pkgname table {
|
||||
border: none;
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
td.pkgname table td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
td.pkgname td.pkgname {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td.reltype {
|
||||
font-weight: bold;
|
||||
color: #400000;
|
||||
}
|
||||
|
||||
td.date, span.date, span.svnrev {
|
||||
color: #400000;
|
||||
}
|
||||
|
||||
a:link { color: #0048b3; }
|
||||
a:visited { color: #002a6a; }
|
||||
a:hover { background: #ffffcd; }
|
||||
|
||||
span.relname {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
span.filename, span.command {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
span.md5 {
|
||||
font-family: monospace;
|
||||
color: #400000;
|
||||
}
|
||||
|
||||
.failurewarning {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
p.failurewarning {
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
span.system {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table.derivationList {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
table.derivationList, table.derivationList td, table.derivationList th {
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
table.derivationList tr.odd {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
table.derivationList td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.derivationList td.system {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover, a:visited:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
table.buildfarmResults td, table.buildfarmResults th {
|
||||
border: none;
|
||||
}
|
||||
|
||||
td.buildfarmMainColumn {
|
||||
background-color: #E0E0E0;
|
||||
border: solid;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
color: red;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
pre.buildlog {
|
||||
border: 1px solid black;
|
||||
padding: 0.3em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
div.buildlog {
|
||||
border: 1px solid black;
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
ul.productList {
|
||||
list-style: none;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
ul.productList li {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
tr.runningJob {
|
||||
background-color: #ff3030;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.productDetails {
|
||||
display: none;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
.template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.jobset {
|
||||
border: solid black 1px;
|
||||
-moz-border-radius: 1em;
|
||||
border-radius: 1em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
div.jobset-edit {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
div.jobset-edit h3, div.jobset h3 {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
div.help {
|
||||
border: solid black 1px;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
div.help pre {
|
||||
padding-left: 1.5em;
|
||||
color: #400000;
|
||||
}
|
||||
|
||||
|
||||
/* Sortable tables */
|
||||
|
||||
table.tablesorter {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.tablesorter thead tr .header {
|
||||
background-image: url(/static/js/tablesorter/themes/blue/bg.gif);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table.tablesorter thead tr .headerSortUp {
|
||||
background-image: url(/static/js/tablesorter/themes/blue/asc.gif);
|
||||
}
|
||||
|
||||
table.tablesorter thead tr .headerSortDown {
|
||||
background-image: url(/static/js/tablesorter/themes/blue/desc.gif);
|
||||
}
|
||||
|
||||
table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp {
|
||||
background-color: #ffe000;
|
||||
}
|
||||
|
||||
table.tablesorter thead tr th {
|
||||
padding-right: 1.5em;
|
||||
}
|
||||
|
||||
|
||||
/* Navbar */
|
||||
|
||||
#leftnavbar {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
width: 13em;
|
||||
border-right: 1px solid gray;
|
||||
}
|
||||
|
||||
#content {
|
||||
position: absolute;
|
||||
left: 13em;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0em;
|
||||
}
|
||||
|
||||
#leftnavbar ul.menu {
|
||||
display: block;
|
||||
padding: 0 0 0 0;
|
||||
margin: 0 0 0 0;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#leftnavbar ul.menu a {
|
||||
color: #005aa0;
|
||||
}
|
||||
|
||||
#leftnavbar ul.menu > li {
|
||||
list-style: none;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
#leftnavbar ul.menu > li > div.title {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border-width: 0px;
|
||||
border-bottom-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #c0c0c0;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#leftnavbar ul.submenu {
|
||||
padding: 0 0 0 0;
|
||||
margin-left: 0em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
|
||||
#leftnavbar ul.submenu > li {
|
||||
font-size: 90%;
|
||||
list-style: none;
|
||||
border-width: 0px;
|
||||
border-bottom-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #c0c0c0;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
#leftnavbar ul.submenu > li > div.title {
|
||||
padding-left: 1.3em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
background-image: url(/static/images/arrow-right.gif);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0.3em center;
|
||||
}
|
||||
|
||||
#leftnavbar ul.submenu > li.active > div.title {
|
||||
background-image: url(/static/images/arrow-right-active.gif);
|
||||
background-color: #eef2ff;
|
||||
font-weight: bold;
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
#leftnavbar ul.subsubmenu {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#leftnavbar ul.subsubmenu > li {
|
||||
font-size: 90%;
|
||||
list-style: none;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
border-width: 0px;
|
||||
border-top-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #c0c0c0;
|
||||
}
|
||||
|
||||
#leftnavbar ul.subsubmenu > li > div.title {
|
||||
padding-left: 3.3em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
background-image: url(/static/images/arrow-right.gif);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2.3em center;
|
||||
}
|
||||
|
||||
#leftnavbar ul.subsubmenu > li.active > div.title {
|
||||
background-image: url(/static/images/arrow-right-active.gif);
|
||||
background-color: #eef2ff;
|
||||
font-weight: bold;
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
#footer {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
||||
/* Editing */
|
||||
|
||||
input.string {
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
background-color: #fffff0;
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
input.shortString {
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: #fffff0;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #f0f0e0;
|
||||
}
|
89
src/Hydra/root/static/css/logfile.css
Normal file
@ -0,0 +1,89 @@
|
||||
ul.nesting, ul.toplevel {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul.toplevel {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.line, .head {
|
||||
padding-top: 0em;
|
||||
}
|
||||
|
||||
ul.nesting li.line, ul.nesting li.lastline {
|
||||
position: relative;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.nesting li.line {
|
||||
padding-left: 2.0em;
|
||||
}
|
||||
|
||||
ul.nesting li.lastline {
|
||||
padding-left: 2.1em; // for the 0.1em border-left in .lastline > .lineconn
|
||||
}
|
||||
|
||||
li.line {
|
||||
border-left: 0.1em solid #6185a0;
|
||||
}
|
||||
|
||||
li.line > span.lineconn, li.lastline > span.lineconn {
|
||||
position: absolute;
|
||||
height: 0.65em;
|
||||
left: 0em;
|
||||
width: 1.5em;
|
||||
border-bottom: 0.1em solid #6185a0;
|
||||
}
|
||||
|
||||
li.lastline > span.lineconn {
|
||||
border-left: 0.1em solid #6185a0;
|
||||
}
|
||||
|
||||
|
||||
em.storeref {
|
||||
color: #500000;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
em.storeref:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
*.popup {
|
||||
display: none;
|
||||
/* background: url('http://losser.st-lab.cs.uu.nl/~mbravenb/menuback.png') repeat; */
|
||||
background: #ffffcd;
|
||||
border: solid #555555 1px;
|
||||
position: absolute;
|
||||
top: 0em;
|
||||
left: 0em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
em.storeref:hover span.popup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
|
||||
.logTreeToggle {
|
||||
text-decoration: none;
|
||||
font-family: monospace;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.errorLine {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.prio3 {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
}
|
BIN
src/Hydra/root/static/images/arrow-right-active.gif
Normal file
After Width: | Height: | Size: 103 B |
BIN
src/Hydra/root/static/images/arrow-right.gif
Normal file
After Width: | Height: | Size: 100 B |
BIN
src/Hydra/root/static/images/debian.png
Normal file
After Width: | Height: | Size: 422 B |
BIN
src/Hydra/root/static/images/document.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src/Hydra/root/static/images/failure.gif
Normal file
After Width: | Height: | Size: 233 B |
BIN
src/Hydra/root/static/images/nix-build.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
src/Hydra/root/static/images/report.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/Hydra/root/static/images/rpm-fedora.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/Hydra/root/static/images/rpm.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/Hydra/root/static/images/source-dist.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/Hydra/root/static/images/success.gif
Normal file
After Width: | Height: | Size: 164 B |
37
src/Hydra/script/hydrafrontend_cgi.pl
Executable file
@ -0,0 +1,37 @@
|
||||
#!/var/run/current-system/sw/bin/perl -w
|
||||
|
||||
BEGIN { $ENV{CATALYST_ENGINE} ||= 'CGI' }
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use HydraFrontend;
|
||||
|
||||
HydraFrontend->run;
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
hydrafrontend_cgi.pl - Catalyst CGI
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
See L<Catalyst::Manual>
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Run a Catalyst application as a cgi script.
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Catalyst Contributors, see Catalyst.pm
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
|
||||
This library is free software, you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
74
src/Hydra/script/hydrafrontend_create.pl
Executable file
@ -0,0 +1,74 @@
|
||||
#!/var/run/current-system/sw/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
use Pod::Usage;
|
||||
use Catalyst::Helper;
|
||||
|
||||
my $force = 0;
|
||||
my $mech = 0;
|
||||
my $help = 0;
|
||||
|
||||
GetOptions(
|
||||
'nonew|force' => \$force,
|
||||
'mech|mechanize' => \$mech,
|
||||
'help|?' => \$help
|
||||
);
|
||||
|
||||
pod2usage(1) if ( $help || !$ARGV[0] );
|
||||
|
||||
my $helper = Catalyst::Helper->new( { '.newfiles' => !$force, mech => $mech } );
|
||||
|
||||
pod2usage(1) unless $helper->mk_component( 'HydraFrontend', @ARGV );
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
hydrafrontend_create.pl - Create a new Catalyst Component
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
hydrafrontend_create.pl [options] model|view|controller name [helper] [options]
|
||||
|
||||
Options:
|
||||
-force don't create a .new file where a file to be created exists
|
||||
-mechanize use Test::WWW::Mechanize::Catalyst for tests if available
|
||||
-help display this help and exits
|
||||
|
||||
Examples:
|
||||
hydrafrontend_create.pl controller My::Controller
|
||||
hydrafrontend_create.pl controller My::Controller BindLex
|
||||
hydrafrontend_create.pl -mechanize controller My::Controller
|
||||
hydrafrontend_create.pl view My::View
|
||||
hydrafrontend_create.pl view MyView TT
|
||||
hydrafrontend_create.pl view TT TT
|
||||
hydrafrontend_create.pl model My::Model
|
||||
hydrafrontend_create.pl model SomeDB DBIC::Schema MyApp::Schema create=dynamic\
|
||||
dbi:SQLite:/tmp/my.db
|
||||
hydrafrontend_create.pl model AnotherDB DBIC::Schema MyApp::Schema create=static\
|
||||
dbi:Pg:dbname=foo root 4321
|
||||
|
||||
See also:
|
||||
perldoc Catalyst::Manual
|
||||
perldoc Catalyst::Manual::Intro
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Create a new Catalyst Component.
|
||||
|
||||
Existing component files are not overwritten. If any of the component files
|
||||
to be created already exist the file will be written with a '.new' suffix.
|
||||
This behavior can be suppressed with the C<-force> option.
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Catalyst Contributors, see Catalyst.pm
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
This library is free software, you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
79
src/Hydra/script/hydrafrontend_fastcgi.pl
Executable file
@ -0,0 +1,79 @@
|
||||
#!/var/run/current-system/sw/bin/perl -w
|
||||
|
||||
BEGIN { $ENV{CATALYST_ENGINE} ||= 'FastCGI' }
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
use Pod::Usage;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use HydraFrontend;
|
||||
|
||||
my $help = 0;
|
||||
my ( $listen, $nproc, $pidfile, $manager, $detach, $keep_stderr );
|
||||
|
||||
GetOptions(
|
||||
'help|?' => \$help,
|
||||
'listen|l=s' => \$listen,
|
||||
'nproc|n=i' => \$nproc,
|
||||
'pidfile|p=s' => \$pidfile,
|
||||
'manager|M=s' => \$manager,
|
||||
'daemon|d' => \$detach,
|
||||
'keeperr|e' => \$keep_stderr,
|
||||
);
|
||||
|
||||
pod2usage(1) if $help;
|
||||
|
||||
HydraFrontend->run(
|
||||
$listen,
|
||||
{ nproc => $nproc,
|
||||
pidfile => $pidfile,
|
||||
manager => $manager,
|
||||
detach => $detach,
|
||||
keep_stderr => $keep_stderr,
|
||||
}
|
||||
);
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
hydrafrontend_fastcgi.pl - Catalyst FastCGI
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
hydrafrontend_fastcgi.pl [options]
|
||||
|
||||
Options:
|
||||
-? -help display this help and exits
|
||||
-l -listen Socket path to listen on
|
||||
(defaults to standard input)
|
||||
can be HOST:PORT, :PORT or a
|
||||
filesystem path
|
||||
-n -nproc specify number of processes to keep
|
||||
to serve requests (defaults to 1,
|
||||
requires -listen)
|
||||
-p -pidfile specify filename for pid file
|
||||
(requires -listen)
|
||||
-d -daemon daemonize (requires -listen)
|
||||
-M -manager specify alternate process manager
|
||||
(FCGI::ProcManager sub-class)
|
||||
or empty string to disable
|
||||
-e -keeperr send error messages to STDOUT, not
|
||||
to the webserver
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Run a Catalyst application as fastcgi.
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Catalyst Contributors, see Catalyst.pm
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
This library is free software, you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
114
src/Hydra/script/hydrafrontend_server.pl
Executable file
@ -0,0 +1,114 @@
|
||||
#!/var/run/current-system/sw/bin/perl -w
|
||||
|
||||
BEGIN {
|
||||
$ENV{CATALYST_ENGINE} ||= 'HTTP';
|
||||
$ENV{CATALYST_SCRIPT_GEN} = 31;
|
||||
require Catalyst::Engine::HTTP;
|
||||
}
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
use Pod::Usage;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
|
||||
my $debug = 0;
|
||||
my $fork = 0;
|
||||
my $help = 0;
|
||||
my $host = undef;
|
||||
my $port = $ENV{HYDRAFRONTEND_PORT} || $ENV{CATALYST_PORT} || 3000;
|
||||
my $keepalive = 0;
|
||||
my $restart = $ENV{HYDRAFRONTEND_RELOAD} || $ENV{CATALYST_RELOAD} || 0;
|
||||
my $restart_delay = 1;
|
||||
my $restart_regex = '(?:/|^)(?!\.#).+(?:\.yml$|\.yaml$|\.conf|\.pm)$';
|
||||
my $restart_directory = undef;
|
||||
my $follow_symlinks = 0;
|
||||
|
||||
my @argv = @ARGV;
|
||||
|
||||
GetOptions(
|
||||
'debug|d' => \$debug,
|
||||
'fork' => \$fork,
|
||||
'help|?' => \$help,
|
||||
'host=s' => \$host,
|
||||
'port=s' => \$port,
|
||||
'keepalive|k' => \$keepalive,
|
||||
'restart|r' => \$restart,
|
||||
'restartdelay|rd=s' => \$restart_delay,
|
||||
'restartregex|rr=s' => \$restart_regex,
|
||||
'restartdirectory=s@' => \$restart_directory,
|
||||
'followsymlinks' => \$follow_symlinks,
|
||||
);
|
||||
|
||||
pod2usage(1) if $help;
|
||||
|
||||
if ( $restart && $ENV{CATALYST_ENGINE} eq 'HTTP' ) {
|
||||
$ENV{CATALYST_ENGINE} = 'HTTP::Restarter';
|
||||
}
|
||||
if ( $debug ) {
|
||||
$ENV{CATALYST_DEBUG} = 1;
|
||||
}
|
||||
|
||||
# This is require instead of use so that the above environment
|
||||
# variables can be set at runtime.
|
||||
require HydraFrontend;
|
||||
|
||||
HydraFrontend->run( $port, $host, {
|
||||
argv => \@argv,
|
||||
'fork' => $fork,
|
||||
keepalive => $keepalive,
|
||||
restart => $restart,
|
||||
restart_delay => $restart_delay,
|
||||
restart_regex => qr/$restart_regex/,
|
||||
restart_directory => $restart_directory,
|
||||
follow_symlinks => $follow_symlinks,
|
||||
} );
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
hydrafrontend_server.pl - Catalyst Testserver
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
hydrafrontend_server.pl [options]
|
||||
|
||||
Options:
|
||||
-d -debug force debug mode
|
||||
-f -fork handle each request in a new process
|
||||
(defaults to false)
|
||||
-? -help display this help and exits
|
||||
-host host (defaults to all)
|
||||
-p -port port (defaults to 3000)
|
||||
-k -keepalive enable keep-alive connections
|
||||
-r -restart restart when files get modified
|
||||
(defaults to false)
|
||||
-rd -restartdelay delay between file checks
|
||||
-rr -restartregex regex match files that trigger
|
||||
a restart when modified
|
||||
(defaults to '\.yml$|\.yaml$|\.conf|\.pm$')
|
||||
-restartdirectory the directory to search for
|
||||
modified files, can be set mulitple times
|
||||
(defaults to '[SCRIPT_DIR]/..')
|
||||
-follow_symlinks follow symlinks in search directories
|
||||
(defaults to false. this is a no-op on Win32)
|
||||
See also:
|
||||
perldoc Catalyst::Manual
|
||||
perldoc Catalyst::Manual::Intro
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Run a Catalyst Testserver for this application.
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Catalyst Contributors, see Catalyst.pm
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
This library is free software, you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
53
src/Hydra/script/hydrafrontend_test.pl
Executable file
@ -0,0 +1,53 @@
|
||||
#!/var/run/current-system/sw/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
use Pod::Usage;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use Catalyst::Test 'HydraFrontend';
|
||||
|
||||
my $help = 0;
|
||||
|
||||
GetOptions( 'help|?' => \$help );
|
||||
|
||||
pod2usage(1) if ( $help || !$ARGV[0] );
|
||||
|
||||
print request($ARGV[0])->content . "\n";
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
hydrafrontend_test.pl - Catalyst Test
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
hydrafrontend_test.pl [options] uri
|
||||
|
||||
Options:
|
||||
-help display this help and exits
|
||||
|
||||
Examples:
|
||||
hydrafrontend_test.pl http://localhost/some_action
|
||||
hydrafrontend_test.pl /some_action
|
||||
|
||||
See also:
|
||||
perldoc Catalyst::Manual
|
||||
perldoc Catalyst::Manual::Intro
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Run a Catalyst action from the command line.
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Catalyst Contributors, see Catalyst.pm
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
This library is free software, you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
83
src/Hydra/xsl/log2html.xsl
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
||||
<xsl:output method='html' encoding="UTF-8"
|
||||
doctype-public="-//W3C//DTD HTML 4.01//EN"
|
||||
doctype-system="http://www.w3.org/TR/html4/strict.dtd" />
|
||||
|
||||
<xsl:template match="logfile">
|
||||
[<a href="javascript:" class="logTreeExpandAll">Expand all</a>]
|
||||
[<a href="javascript:" class="logTreeCollapseAll">Collapse all</a>]
|
||||
<ul class='toplevel'>
|
||||
<xsl:for-each select='line|nest'>
|
||||
<li>
|
||||
<xsl:apply-templates select='.'/>
|
||||
</li>
|
||||
</xsl:for-each>
|
||||
</ul>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="nest">
|
||||
|
||||
<!-- The tree should be collapsed by default if all children are
|
||||
unimportant or if the header is unimportant. -->
|
||||
<!-- <xsl:variable name="collapsed"
|
||||
select="count(.//line[not(@priority = 3)]) = 0 or ./head[@priority = 3]" /> -->
|
||||
<xsl:variable name="collapsed" select="count(.//*[@error]) = 0"/>
|
||||
|
||||
<xsl:variable name="style"><xsl:if test="$collapsed">display: none;</xsl:if></xsl:variable>
|
||||
<xsl:variable name="arg"><xsl:choose><xsl:when test="$collapsed">true</xsl:when><xsl:otherwise>false</xsl:otherwise></xsl:choose></xsl:variable>
|
||||
|
||||
<xsl:if test="line|nest">
|
||||
<a href="javascript:" class="logTreeToggle"></a>
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:if>
|
||||
|
||||
<xsl:apply-templates select='head'/>
|
||||
|
||||
<!-- Be careful to only generate <ul>s if there are <li>s, otherwise it’s malformed. -->
|
||||
<xsl:if test="line|nest">
|
||||
|
||||
<ul class='nesting' style="{$style}">
|
||||
<xsl:for-each select='line|nest'>
|
||||
|
||||
<!-- Is this the last line? If so, mark it as such so that it
|
||||
can be rendered differently. -->
|
||||
<xsl:variable name="class"><xsl:choose><xsl:when test="position() != last()">line</xsl:when><xsl:otherwise>lastline</xsl:otherwise></xsl:choose></xsl:variable>
|
||||
|
||||
<li class='{$class}'>
|
||||
<span class='lineconn' />
|
||||
<span class='linebody'>
|
||||
<xsl:apply-templates select='.'/>
|
||||
</span>
|
||||
</li>
|
||||
</xsl:for-each>
|
||||
</ul>
|
||||
</xsl:if>
|
||||
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="head|line">
|
||||
<code>
|
||||
<xsl:if test="@error">
|
||||
<xsl:attribute name="class">errorLine</xsl:attribute>
|
||||
</xsl:if>
|
||||
<xsl:if test="@priority = 3">
|
||||
<xsl:attribute name="class">prio3</xsl:attribute>
|
||||
</xsl:if>
|
||||
<xsl:apply-templates/>
|
||||
</code>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="storeref">
|
||||
<em class='storeref'>
|
||||
<span class='popup'><xsl:apply-templates/></span>
|
||||
<span class='elided'>/...</span><xsl:apply-templates select='name'/><xsl:apply-templates select='path'/>
|
||||
</em>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
24
src/Hydra/xsl/mark-errors.xsl
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()"/>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="line">
|
||||
<line>
|
||||
<xsl:if test="contains(text(), ' *** ') or
|
||||
contains(text(), 'LaTeX Error') or
|
||||
contains(text(), 'FAIL:') or
|
||||
contains(text(), ' error: ') or
|
||||
true">
|
||||
<xsl:attribute name="error"></xsl:attribute>
|
||||
</xsl:if>
|
||||
<xsl:apply-templates select="@*|node()"/>
|
||||
</line>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|