forked from lix-project/hydra
This commit is contained in:
parent
f4a44db664
commit
8f42bf303f
|
@ -29,6 +29,7 @@ sub getBuild {
|
|||
sub index :Path :Args(0) {
|
||||
my ( $self, $c ) = @_;
|
||||
$c->stash->{template} = 'index.tt';
|
||||
$c->stash->{jobs} = [$c->model('DB::Jobs')->all];
|
||||
$c->stash->{projects} = [$c->model('DB::Projects')->all];
|
||||
$c->stash->{allBuilds} = [$c->model('DB::Builds')->search(undef, {order_by => "timestamp DESC"})];
|
||||
# Get the latest build for each unique job.
|
||||
|
|
|
@ -8,8 +8,8 @@ use base 'DBIx::Class::Schema';
|
|||
__PACKAGE__->load_classes;
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:G17vptu+2rEUXbsqVtoXzQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:xP97YDrN7Bm2B/BlbQJ7fQ
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -21,8 +21,8 @@ __PACKAGE__->set_primary_key("build", "logphase");
|
|||
__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" });
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:xvWlrugDQD11vH+7f91K0A
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:pt0CJFX1pP9Z2TjqrTjTkw
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -21,8 +21,8 @@ __PACKAGE__->set_primary_key("build", "path");
|
|||
__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" });
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SMsT6htcybeWNHhv82+ilA
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3NKUaF4u4H6ZmIRCeva8yA
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -38,6 +38,16 @@ __PACKAGE__->add_columns(
|
|||
{ 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(
|
||||
"inputs",
|
||||
"HydraFrontend::Schema::Inputs",
|
||||
|
@ -55,8 +65,8 @@ __PACKAGE__->has_many(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:nfVureYYGM1V/NHroQA5Tw
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:8s5Z03ugocOVb021EwGVag
|
||||
|
||||
__PACKAGE__->has_many(dependents => 'HydraFrontend::Schema::Inputs', 'dependency');
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ __PACKAGE__->set_primary_key("id");
|
|||
__PACKAGE__->belongs_to("build", "HydraFrontend::Schema::Builds", { id => "build" });
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3PAsUD+79bZk4vGeSyyACg
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:AzV6B/6CCrroPlO32n2p3A
|
||||
|
||||
__PACKAGE__->belongs_to("dependency", "HydraFrontend::Schema::Builds", { id => "dependency" });
|
||||
|
||||
|
|
|
@ -14,6 +14,10 @@ __PACKAGE__->add_columns(
|
|||
{ 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 },
|
||||
"project",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
"jobset",
|
||||
|
@ -30,10 +34,20 @@ __PACKAGE__->add_columns(
|
|||
{ 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" },
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:T8O0XTTOZXapWpJbzjKLTw
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:GubRofAmJ/sbJbjyV3aKSQ
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -6,7 +6,7 @@ use warnings;
|
|||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("jobSetInputAlts");
|
||||
__PACKAGE__->table("jobsetInputAlts");
|
||||
__PACKAGE__->add_columns(
|
||||
"project",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
|
@ -33,8 +33,8 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:DzEHCDlnponciGmGASknlg
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZjjWLbAWExxOqsDz41A3KA
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -6,7 +6,7 @@ use warnings;
|
|||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("jobSetInputs");
|
||||
__PACKAGE__->table("jobsetInputs");
|
||||
__PACKAGE__->add_columns(
|
||||
"project",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
|
@ -43,8 +43,8 @@ __PACKAGE__->has_many(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Lm2oIWEUSHFICYMX2qmTfw
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:6hzbFjPWQ872UxFhhpxjFg
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -6,7 +6,7 @@ use warnings;
|
|||
use base 'DBIx::Class';
|
||||
|
||||
__PACKAGE__->load_components("Core");
|
||||
__PACKAGE__->table("jobSets");
|
||||
__PACKAGE__->table("jobsets");
|
||||
__PACKAGE__->add_columns(
|
||||
"name",
|
||||
{ data_type => "text", is_nullable => 0, size => undef },
|
||||
|
@ -20,6 +20,14 @@ __PACKAGE__->add_columns(
|
|||
{ 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",
|
||||
|
@ -38,10 +46,18 @@ __PACKAGE__->has_many(
|
|||
"foreign.project" => "self.project",
|
||||
},
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"jobs",
|
||||
"HydraFrontend::Schema::Jobs",
|
||||
{
|
||||
"foreign.jobset" => "self.name",
|
||||
"foreign.project" => "self.project",
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:EmATMMeNmMd2AI8lVzcLFA
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:oRV4yw0DWG5PI0agcM7QHA
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -12,15 +12,25 @@ __PACKAGE__->add_columns(
|
|||
{ data_type => "text", 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" },
|
||||
);
|
||||
__PACKAGE__->has_many(
|
||||
"jobs",
|
||||
"HydraFrontend::Schema::Jobs",
|
||||
{ "foreign.project" => "self.name" },
|
||||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-09 01:36:21
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZifQocKoHOPRrJQSPggZ+w
|
||||
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2008-11-10 10:30:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9SeEXSEOH1ocrdkoa7fx5Q
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[% WRAPPER layout.tt title="Hydra Overview" %]
|
||||
[% USE date %]
|
||||
|
||||
<h1>Job <tt>[% build.project %]:[% build.attrname %]</tt> build [% id %]</h1>
|
||||
<h1>Job <tt>[% build.project.name %]:[% build.attrname %]</tt> build [% id %]</h1>
|
||||
|
||||
|
||||
<h2>Information</h2>
|
||||
|
@ -13,11 +13,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<th>Project:</th>
|
||||
<td><a href="[% c.uri_for('/project' build.project) %]"><tt>[% build.project %]</tt></a></td>
|
||||
<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 %]</tt></td>
|
||||
<td><tt>[% build.jobset.name %]</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Job name:</th>
|
||||
|
@ -87,7 +87,7 @@
|
|||
<td><tt>[% input.type %]</tt></td>
|
||||
<td>
|
||||
[% IF input.type == "build" %]
|
||||
<a href="[% c.uri_for('/build' input.dependency.id) %]">Job <tt>[% input.dependency.project %]:[% input.dependency.attrname %]</tt> build [% input.dependency.id %]</a>
|
||||
<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 %]
|
||||
|
@ -153,7 +153,7 @@
|
|||
<tbody>
|
||||
[% FOREACH input IN build.dependents -%]
|
||||
<tr>
|
||||
<td><a href="[% c.uri_for('/build' input.build.id) %]">Job <tt>[% input.build.project %]:[% input.build.attrname %]</tt> build [% input.build.id %]</a></td>
|
||||
<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>
|
||||
|
|
|
@ -1,4 +1,25 @@
|
|||
[% WRAPPER layout.tt title="Hydra Overview" %]
|
||||
[% USE date %]
|
||||
|
||||
<h1>Queue</h1>
|
||||
|
||||
<table class="tablesorter">
|
||||
<thead>
|
||||
<tr><th>Priority</th><th>Project</th><th>Job</th><th>System</th><th>Timestamp</th><th>Description</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH job IN jobs -%]
|
||||
<tr>
|
||||
<td>[% job.priority %]</td>
|
||||
<td><tt>[% job.project.name %]</tt></td>
|
||||
<td><tt>[% job.jobset.name %]</tt></td>
|
||||
<td><tt>[% job.system %]</tt></td>
|
||||
<td>[% date.format(job.timestamp, '%Y-%m-%d %H:%M:%S') %]</td>
|
||||
<td>[% job.description %]</td>
|
||||
</tr>
|
||||
[% END -%]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h1>Job status</h1>
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
[% END %]
|
||||
</td>
|
||||
<td><a href="[% c.uri_for('/build' build.id) %]">[% build.id %]</a></td>
|
||||
<td><a href="[% c.uri_for('/project' build.project) %]"><tt>[% build.project %]</tt></a></td>
|
||||
<td><a href="[% c.uri_for('/job' build.project build.attrname) %]"><tt>[% build.attrname %]</tt></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>
|
||||
|
|
|
@ -4,7 +4,7 @@ create table builds (
|
|||
|
||||
-- Info about the inputs.
|
||||
project text not null, -- !!! foreign key
|
||||
jobSet text not null, -- !!! foreign key
|
||||
jobset text not null, -- !!! foreign key
|
||||
attrName text not null,
|
||||
|
||||
-- Info about the build result.
|
||||
|
@ -16,7 +16,10 @@ create table builds (
|
|||
errorMsg text, -- error message in case of a Nix failure
|
||||
startTime integer, -- in Unix time, 0 = used cached build result
|
||||
stopTime integer,
|
||||
system text not null
|
||||
system text not null,
|
||||
|
||||
foreign key (project) references projects(name), -- ignored by sqlite
|
||||
foreign key (project, jobset) references jobsets(project, name) -- ignored by sqlite
|
||||
);
|
||||
|
||||
|
||||
|
@ -28,7 +31,7 @@ create table inputs (
|
|||
build integer,
|
||||
job integer,
|
||||
|
||||
-- Copied from the jobSetInputs from which the build was created.
|
||||
-- Copied from the jobsetinputs from which the build was created.
|
||||
name text not null,
|
||||
type text not null,
|
||||
uri text,
|
||||
|
@ -83,29 +86,29 @@ create table projects (
|
|||
-- A jobset consists of a set of inputs (e.g. SVN repositories), one
|
||||
-- of which contains a Nix expression containing an attribute set
|
||||
-- describing build jobs.
|
||||
create table jobSets (
|
||||
create table jobsets (
|
||||
name text not null,
|
||||
project text not null,
|
||||
description text,
|
||||
nixExprInput text not null, -- name of the jobSetInput containing the Nix expression
|
||||
nixExprInput text not null, -- name of the jobsetInput containing the Nix expression
|
||||
nixExprPath text not null, -- relative path of the Nix expression
|
||||
primary key (project, name),
|
||||
foreign key (project) references projects(name) on delete cascade, -- ignored by sqlite
|
||||
foreign key (project, name, nixExprInput) references jobSetInputs(project, job, name)
|
||||
foreign key (project, name, nixExprInput) references jobsetInputs(project, job, name)
|
||||
);
|
||||
|
||||
|
||||
create table jobSetInputs (
|
||||
create table jobsetInputs (
|
||||
project text not null,
|
||||
jobset text not null,
|
||||
name text not null,
|
||||
type text not null, -- "svn", "cvs", "path", "file", "string"
|
||||
primary key (project, jobset, name),
|
||||
foreign key (project, jobset) references jobSets(project, name) on delete cascade -- ignored by sqlite
|
||||
foreign key (project, jobset) references jobsets(project, name) on delete cascade -- ignored by sqlite
|
||||
);
|
||||
|
||||
|
||||
create table jobSetInputAlts (
|
||||
create table jobsetInputAlts (
|
||||
project text not null,
|
||||
jobset text not null,
|
||||
input text not null,
|
||||
|
@ -118,7 +121,7 @@ create table jobSetInputAlts (
|
|||
value text, -- for type == 'string'
|
||||
|
||||
primary key (project, jobset, input, altnr),
|
||||
foreign key (project, jobset, input) references jobSetInputs(project, jobset, name) on delete cascade -- ignored by sqlite
|
||||
foreign key (project, jobset, input) references jobsetInputs(project, jobset, name) on delete cascade -- ignored by sqlite
|
||||
);
|
||||
|
||||
|
||||
|
@ -127,15 +130,21 @@ create table jobs (
|
|||
timestamp integer not null, -- time this build was added to the db (in Unix time)
|
||||
|
||||
priority integer not null,
|
||||
|
||||
busy integer not null, -- true means someone is building this job now
|
||||
locker text not null, -- !!! hostname/pid of the process building this job?
|
||||
|
||||
-- Info about the inputs.
|
||||
project text not null, -- !!! foreign key
|
||||
jobSet text not null, -- !!! foreign key
|
||||
jobset text not null, -- !!! foreign key
|
||||
attrName text not null,
|
||||
|
||||
-- What this job will build.
|
||||
description text,
|
||||
drvPath text not null,
|
||||
outPath text not null,
|
||||
system text not null
|
||||
system text not null,
|
||||
|
||||
foreign key (project) references projects(name), -- ignored by sqlite
|
||||
foreign key (project, jobset) references jobsets(project, name) -- ignored by sqlite
|
||||
);
|
||||
|
|
|
@ -16,7 +16,7 @@ sub isValidPath {
|
|||
|
||||
|
||||
sub buildJob {
|
||||
my ($project, $jobset, $jobName, $drvPath, $outPath, $usedInputs, $system) = @_;
|
||||
my ($project, $jobset, $jobName, $description, $drvPath, $outPath, $usedInputs, $system) = @_;
|
||||
|
||||
if (scalar($db->resultset('Builds')->search({project => $project->name, jobset => $jobset->name, attrname => $jobName, outPath => $outPath})) > 0) {
|
||||
print " already done\n";
|
||||
|
@ -58,6 +58,7 @@ sub buildJob {
|
|||
, project => $project->name
|
||||
, jobset => $jobset->name
|
||||
, attrname => $jobName
|
||||
, description => $description
|
||||
, drvpath => $drvPath
|
||||
, outpath => $outPath
|
||||
, iscachedbuild => $isCachedBuild
|
||||
|
@ -175,17 +176,17 @@ sub checkJob {
|
|||
my $infoXml = `nix-env -f $nixExprPath --query --available "*" --attr-path --out-path --drv-path --meta --xml --system-filter "*" --attr $jobName $extraArgs`
|
||||
or die "cannot get information about the job: $?";
|
||||
|
||||
my $info = XMLin($infoXml, KeyAttr => ['attrPath', 'name'])
|
||||
my $info = XMLin($infoXml, ForceArray => 1, KeyAttr => ['attrPath', 'name'])
|
||||
or die "cannot parse XML output";
|
||||
|
||||
my $job = $info->{item};
|
||||
die if !defined $job || $job->{attrPath} ne $jobName;
|
||||
my $job = $info->{item}->{$jobName};
|
||||
die if !defined $job;
|
||||
|
||||
my $description = defined $job->{meta}->{description} ? $job->{meta}->{description}->{value} : "";
|
||||
die unless $job->{drvPath} eq $drvPath;
|
||||
my $outPath = $job->{outPath};
|
||||
|
||||
buildJob($project, $jobset, $jobName, $drvPath, $outPath, $inputInfo, $job->{system});
|
||||
buildJob($project, $jobset, $jobName, $description, $drvPath, $outPath, $inputInfo, $job->{system});
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue