From f6f5309a02d8e472ee27098f4f97156c68f3b1dc Mon Sep 17 00:00:00 2001
From: Eelco Dolstra <e.dolstra@tudelft.nl>
Date: Thu, 13 Nov 2008 14:54:50 +0000
Subject: [PATCH]

---
 .../lib/HydraFrontend/Controller/Root.pm      | 78 ++++++++++++++-
 src/HydraFrontend/root/error.tt               |  3 +-
 src/HydraFrontend/root/hydra.css              | 15 +++
 src/HydraFrontend/root/job.tt                 |  2 +-
 src/HydraFrontend/root/layout.tt              |  3 +-
 src/HydraFrontend/root/project.tt             | 99 +++++++++++++++++--
 src/hydra.sql                                 |  2 +-
 src/test.sql                                  |  2 +-
 8 files changed, 185 insertions(+), 19 deletions(-)

diff --git a/src/HydraFrontend/lib/HydraFrontend/Controller/Root.pm b/src/HydraFrontend/lib/HydraFrontend/Controller/Root.pm
index 925637f5..14356877 100644
--- a/src/HydraFrontend/lib/HydraFrontend/Controller/Root.pm
+++ b/src/HydraFrontend/lib/HydraFrontend/Controller/Root.pm
@@ -49,13 +49,51 @@ sub index :Path :Args(0) {
 }
 
 
+sub updateProject {
+    my ($c, $project) = @_;
+
+    my $projectName = $c->request->params->{name};
+    die "Invalid project name: $projectName" unless $projectName =~ /^[[:alpha:]]\w*$/;
+    
+    my $displayName = $c->request->params->{displayname};
+    die "Invalid display name: $displayName" unless $displayName =~ /^\w.*\w$/;
+    
+    $project->name($projectName);
+    $project->displayname($displayName);
+    $project->description($c->request->params->{description});
+    
+    $project->update;
+}
+
+
 sub project :Local {
-    my ( $self, $c, $projectName ) = @_;
+    my ( $self, $c, $projectName, $subcommand ) = @_;
     $c->stash->{template} = 'project.tt';
     
     (my $project) = $c->model('DB::Projects')->search({ name => $projectName });
     return error($c, "Project <tt>$projectName</tt> 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", $projectName));
+    } 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(
@@ -83,13 +121,43 @@ sub project :Local {
 }
 
 
+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, $project, $jobName ) = @_;
+    my ( $self, $c, $projectName, $jobName ) = @_;
     $c->stash->{template} = 'job.tt';
-    $c->stash->{projectName} = $project;
+
+    (my $project) = $c->model('DB::Projects')->search({ name => $projectName });
+    return error($c, "Project <tt>$projectName</tt> 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 => $project, attrName => $jobName},
+        {finished => 1, project => $projectName, attrName => $jobName},
         {order_by => "timestamp DESC"})];
 }
 
diff --git a/src/HydraFrontend/root/error.tt b/src/HydraFrontend/root/error.tt
index 2d8e61ab..01f7ac95 100644
--- a/src/HydraFrontend/root/error.tt
+++ b/src/HydraFrontend/root/error.tt
@@ -1,7 +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">[% error %]</span></p>
+<p>I'm very sorry, but an error occurred: <span class="error-msg">[% HTML.escape(error) %]</span></p>
 
 [% END %]
diff --git a/src/HydraFrontend/root/hydra.css b/src/HydraFrontend/root/hydra.css
index 5059109b..2f3f5d89 100644
--- a/src/HydraFrontend/root/hydra.css
+++ b/src/HydraFrontend/root/hydra.css
@@ -38,6 +38,10 @@ td, th {
     border: solid black 1px;
 }
 
+td {
+    vertical-align: top;
+}
+
 th {
     background: #ffffc0;
 }
@@ -145,6 +149,7 @@ td.buildfarmMainColumn {
 
 .error-msg {
     color: red;
+    white-space: pre;
 }
 
 pre.buildlog {
@@ -321,3 +326,13 @@ h1 {
 #footer {
     font-size: 80%;
 }
+
+
+/* Editing */
+
+input.string {
+    font-family: sans-serif;
+    font-size: 100%;
+    background-color: #fffff0;
+    width: 30em;
+}
diff --git a/src/HydraFrontend/root/job.tt b/src/HydraFrontend/root/job.tt
index 62d6aa8b..293b5442 100644
--- a/src/HydraFrontend/root/job.tt
+++ b/src/HydraFrontend/root/job.tt
@@ -1,6 +1,6 @@
 [% WRAPPER layout.tt title="Hydra Overview" %]
 
-<h1>All builds for job <tt>[% projectName %]:[% jobName %]</tt></h1>
+<h1>All builds for job <tt>[% curProject.name %]:[% jobName %]</tt></h1>
 
 <table class="tablesorter">
   <thead>
diff --git a/src/HydraFrontend/root/layout.tt b/src/HydraFrontend/root/layout.tt
index 064551fa..ace946b0 100644
--- a/src/HydraFrontend/root/layout.tt
+++ b/src/HydraFrontend/root/layout.tt
@@ -50,6 +50,7 @@
                   <div class="title"><a href="[% c.uri_for('/project' project.name) %]">[% 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>
@@ -63,7 +64,7 @@
             <div class="title">Admin</div>
             <ul class="submenu">
               [% INCLUDE makeLink uri = c.uri_for('/users') title = "Users" %]
-              [% INCLUDE makeLink uri = c.uri_for('/create-project') title = "Create a project" %]
+              [% INCLUDE makeLink uri = c.uri_for('/createproject') title = "Create a project" %]
             </ul>
           </li>
         </ul>
diff --git a/src/HydraFrontend/root/project.tt b/src/HydraFrontend/root/project.tt
index 227c28ad..38b4b3c5 100644
--- a/src/HydraFrontend/root/project.tt
+++ b/src/HydraFrontend/root/project.tt
@@ -1,9 +1,46 @@
 [% WRAPPER layout.tt title="Hydra Overview" %]
-
-<h1>Project <tt>[% curProject.name %]</tt></h1>
+[% USE HTML %]
 
 
-<p><strong>Description:</strong> [% curProject.description %]</p>
+[% BLOCK maybeEditString %]
+  [% IF edit %]
+    <input type='text' class='string' [% HTML.attributes(name => param, value => value) %] />
+  [% ELSE %]
+    [% HTML.escape(value) %]
+  [% END %]
+[% 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>
+</table>
 
 
 <h2>Definition</h2>
@@ -17,13 +54,19 @@
   <h4>Information</h4>
 
   <table>
+    [% IF edit %]
+    <tr>
+      <th>Identifier:</th>
+      <td><tt>[% INCLUDE maybeEditString value=jobset.name %]</tt></td>
+    </tr>
+    [% END %]
     <tr>
       <th>Description:</th>
-      <td>[% jobset.description %]</td>
+      <td>[% INCLUDE maybeEditString value=jobset.description %]</td>
     </tr>
     <tr>
       <th>Nix expression:</th>
-      <td><tt>[% jobset.nixexprpath %]</tt> in input <tt>[% jobset.nixexprinput %]</tt></td>
+      <td><tt>[% INCLUDE maybeEditString value=jobset.nixexprpath %]</tt> in input <tt>[% INCLUDE maybeEditString value=jobset.nixexprinput %]</tt></td>
     </tr>
   </table>
 
@@ -36,14 +79,33 @@
     <tbody>
       [% FOREACH input IN jobset.jobsetinputs -%]
         <tr>
-          <td><tt>[% input.name %]</tt></td>
-          <td><tt>[% input.type %]</tt></td>
+          <td><tt>[% INCLUDE maybeEditString value=input.name %]</tt></td>
+          <td><tt>
+            [% IF edit %]
+              <select>
+                <option>svn</option>
+                <option>cvs</option>
+                <option>uri</option>
+                <option>string</option>
+                <option>path</option>
+              </select>
+            [% ELSE %]
+              [% input.type %]
+            [% END %]
+          </tt></td>
           <td>
             [% FOREACH alt IN input.jobsetinputalts -%]
               [% IF input.type == "string" %]
-                <tt>"[% alt.value %]"</tt>
+                <tt>
+                  [% IF edit %]
+                    <input type='text' class='string' value='[% alt.value %]' />
+                    <br />
+                  [% ELSE %]
+                    "[% alt.value %]"
+                  [% END %]
+                </tt>
               [% ELSE %]
-                <tt>[% alt.uri %]</tt>
+                <tt>[% INCLUDE maybeEditString value=alt.uri %]</tt>
               [% END %]
             [% END %]
           </td>
@@ -61,6 +123,9 @@
 [% END %]
 
 
+[% IF !edit %]
+
+
 <h2>Jobs</h2>
 
 [% IF jobNames.size > 0 %]
@@ -108,4 +173,20 @@
 </table>
 
 
+[% END %]
+
+
+[% IF edit %]
+
+  <p><input type="submit" value="Apply" /></p>
+
+  </form>
+
+  <form action="[% c.uri_for('/project' curProject.name 'delete') %]" method="post">
+    <p><input type="submit" value="Delete this project" /></p>
+  </form>
+      
+[% END %]
+
+
 [% END %]
diff --git a/src/hydra.sql b/src/hydra.sql
index 8ddd8725..ff0f14a7 100644
--- a/src/hydra.sql
+++ b/src/hydra.sql
@@ -174,7 +174,7 @@ create table JobsetInputs (
     project       text not null,
     jobset        text not null,
     name          text not null,
-    type          text not null, -- "svn", "cvs", "path", "file", "string"
+    type          text not null, -- "svn", "cvs", "path", "uri", "string"
     primary key   (project, jobset, name),
     foreign key   (project, jobset) references Jobsets(project, name) on delete cascade -- ignored by sqlite
 );
diff --git a/src/test.sql b/src/test.sql
index ebf4f31f..8c7ad093 100644
--- a/src/test.sql
+++ b/src/test.sql
@@ -1,5 +1,5 @@
 insert into projects(name, displayName, description) values('patchelf', 'PatchELF', 'A tool for modifying ELF binaries');
-insert into jobSets(project, name, description, nixExprInput, nixExprPath) values('patchelf', 'trunk', 'PatchELF', 'patchelfSrc', 'release.nix');
+insert into jobSets(project, name, description, nixExprInput, nixExprPath) values('patchelf', 'trunk', 'PatchELF trunk', 'patchelfSrc', 'release.nix');
 insert into jobSetInputs(project, jobset, name, type) values('patchelf', 'trunk', 'patchelfSrc', 'path');
 insert into jobSetInputAlts(project, jobset, input, altnr, uri) values('patchelf', 'trunk', 'patchelfSrc', 0, '/home/eelco/Dev/patchelf-wc');
 insert into jobSetInputs(project, jobset, name, type) values('patchelf', 'trunk', 'nixpkgs', 'path');