changeset 74:1d4fa4c32afa

Add template and static resources, hooked up using the new {{{ITemplateProvider}}} extension point in Trac.
author cmlenz
date Tue, 05 Jul 2005 11:12:17 +0000
parents 6d7753ea1798
children 177d1c52ed48
files bitten/model.py bitten/trac_ext/tests/web_ui.py bitten/trac_ext/web_ui.py htdocs/build.css htdocs/build.png templates/build.cs
diffstat 6 files changed, 232 insertions(+), 153 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/model.py
+++ b/bitten/model.py
@@ -158,6 +158,19 @@
 
     exists = property(fget=lambda self: self.id is not None)
 
+    def delete(self, db=None):
+        if not db:
+            db = self.env.get_db_cnx()
+            handle_ta = True
+        else:
+            handle_ta = False
+
+        cursor = db.cursor()
+        cursor.execute("DELETE FROM bitten_rule WHERE id=%s", (self.id,))
+        cursor.execute("DELETE FROM bitten_platform WHERE id=%s", (self.id,))
+        if handle_ta:
+            db.commit()
+
     def insert(self, db=None):
         if not db:
             db = self.env.get_db_cnx()
--- a/bitten/trac_ext/tests/web_ui.py
+++ b/bitten/trac_ext/tests/web_ui.py
@@ -33,6 +33,7 @@
         PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW')
         req = Mock(Request, path_info='/build', args={}, hdf=HDFWrapper(),
                    perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -45,6 +46,7 @@
         PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN')
         req = Mock(Request, path_info='/build', args={}, hdf=HDFWrapper(),
                    perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -60,6 +62,7 @@
         PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW')
         req = Mock(Request, path_info='/build/test', args={}, hdf=HDFWrapper(),
                    perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -76,6 +79,7 @@
         PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN')
         req = Mock(Request, path_info='/build/test', args={}, hdf=HDFWrapper(),
                    perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -87,6 +91,7 @@
         PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN')
         req = Mock(Request, path_info='/build', args={'action': 'new'},
                    hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -106,6 +111,7 @@
                    args={'action': 'new', 'name': 'test', 'active': 'on',
                          'label': 'Test', 'path': 'test/trunk',
                          'description': 'Bla bla'})
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -129,6 +135,7 @@
                    redirect=redirect, hdf=HDFWrapper(),
                    perm=PermissionCache(self.env, 'joe'),
                    args={'action': 'new', 'cancel': '1', 'name': 'test'})
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -145,6 +152,7 @@
         PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN')
         req = Mock(Request, path_info='/build/test', args={'action': 'edit'},
                    hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -168,6 +176,7 @@
                    args={'action': 'edit', 'name': 'foo', 'active': 'on',
                          'label': 'Test', 'path': 'test/trunk',
                          'description': 'Bla bla'})
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -197,6 +206,7 @@
                    redirect=redirect, hdf=HDFWrapper(),
                    perm=PermissionCache(self.env, 'joe'),
                    args={'action': 'edit', 'cancel': '1'})
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -209,8 +219,10 @@
         config.insert()
 
         PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN')
-        req = Mock(Request, path_info='/build/test', args={'action': 'new'},
+        req = Mock(Request, path_info='/build/test',
+                   args={'action': 'edit', 'new': '1'},
                    hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -231,6 +243,7 @@
         req = Mock(Request, method='POST', path_info='/build/test',
                    redirect=redirect, args={'action': 'new', 'name': 'Test'},
                    hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -250,6 +263,7 @@
         req = Mock(Request, method='POST', path_info='/build/test',
                    redirect=redirect, args={'action': 'new', 'cancel': '1'},
                    hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -270,6 +284,7 @@
         req = Mock(Request, path_info='/build/test',
                    args={'action': 'edit', 'platform': platform.id},
                    hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -297,6 +312,7 @@
                          'name': 'Test'},
                    redirect=redirect, hdf=HDFWrapper(),
                    perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
@@ -323,6 +339,7 @@
                          'cancel': '1'},
                    redirect=redirect, hdf=HDFWrapper(),
                    perm=PermissionCache(self.env, 'joe'))
+        req.hdf['htdocs_location'] = '/htdocs'
 
         module = BuildModule(self.env)
         assert module.match_request(req)
--- a/bitten/trac_ext/web_ui.py
+++ b/bitten/trac_ext/web_ui.py
@@ -24,7 +24,8 @@
 from trac.core import *
 from trac.Timeline import ITimelineEventProvider
 from trac.util import escape, pretty_timedelta
-from trac.web.chrome import INavigationContributor, add_link
+from trac.web.chrome import INavigationContributor, ITemplateProvider, \
+                            add_link, add_stylesheet
 from trac.web.main import IRequestHandler
 from trac.wiki import wiki_to_html
 from bitten.model import Build, BuildConfig, TargetPlatform
@@ -32,144 +33,8 @@
 
 class BuildModule(Component):
 
-    implements(INavigationContributor, IRequestHandler, ITimelineEventProvider)
-
-    build_cs = """
-<?cs include:"header.cs" ?>
- <div id="ctxtnav" class="nav"></div>
- <div id="content" class="build">
-  <h1><?cs var:title ?></h1><?cs
-
-  if:build.mode == 'overview' ?><?cs
-   each:config = build.configs ?>
-    <h2><a href="<?cs var:config.href ?>"><?cs var:config.label ?></a></h2><?cs
-    if:config.description ?><div class="description"><?cs
-     var:config.description ?></div><?cs
-    /if ?><?cs
-   /each ?><?cs
-   if:build.can_create ?><div class="buttons">
-    <form method="get" action=""><div>
-     <input type="hidden" name="action" value="new" />
-     <input type="submit" value="Add configuration" />
-    </div></form></div><?cs
-   /if ?></div><?cs
-
-  elif:build.mode == 'edit_config' ?>
-   <form class="config" method="post" action="">
-    <table><tr>
-     <td class="name"><label>Name:<br />
-      <input type="text" name="name" value="<?cs var:build.config.name ?>" />
-     </label></td>
-     <td class="label"><label>Label (for display):<br />
-      <input type="text" name="label" size="32" value="<?cs
-        var:build.config.label ?>" />
-     </label></td>
-    </tr><tr>
-     <td class="active"><label><input type="checkbox" name="active"<?cs
-       if:build.config.active ?> checked="checked" <?cs /if ?>/> Active
-     </label></td>
-     <td class="path"><label>Repository path:<br />
-      <input type="text" name="path" size="48" value="<?cs
-        var:build.config.path ?>" />
-     </label></td>
-    </tr><tr>
-     <td colspan="2"><fieldset class="iefix">
-      <label for="description">Description (you may use <a tabindex="42" href="<?cs
-        var:trac.href.wiki ?>/WikiFormatting">WikiFormatting</a> here):</label>
-      <p><textarea id="description" name="description" class="wikitext" rows="5" cols="78"><?cs
-        var:build.config.description ?></textarea></p>
-      <script type="text/javascript" src="<?cs
-        var:htdocs_location ?>js/wikitoolbar.js"></script>
-     </fieldset></td>
-    </tr></table>
-    <div class="buttons">
-     <input type="hidden" name="action" value="<?cs
-       if:build.config.exists ?>edit<?cs else ?>new<?cs /if ?>" />
-     <input type="submit" name="cancel" value="Cancel" />
-     <input type="submit" value="<?cs
-       if:build.config.exists ?>Save changes<?cs else ?>Create<?cs /if ?>" />
-    </div>
-   </form><?cs
-   if:build.config.exists ?><div class="platforms">
-    <h2>Target Platforms</h2><?cs
-     if:len(build.platforms) ?><ul><?cs
-      each:platform = build.platforms ?><li><a href="<?cs
-       var:platform.href ?>"><?cs var:platform.name ?></a></li><?cs
-      /each ?></ul><?cs
-     /if ?>
-    <div class="buttons">
-     <form method="get" action=""><div>
-      <input type="hidden" name="action" value="new" />
-      <input type="submit" value="Add target platform" />
-     </div></form>
-    </div>
-   </div><?cs
-   /if ?><?cs
-
-  elif:build.mode == 'view_config' ?><ul>
-   <li>Active: <?cs if:build.config.active ?>yes<?cs else ?>no<?cs /if ?></li>
-   <li>Path: <?cs if:build.config.path ?><a href="<?cs
-     var:build.config.browser_href ?>"><?cs
-     var:build.config.path ?></a></li><?cs /if ?></ul><?cs
-   if:build.config.description ?><div class="description"><?cs
-     var:build.config.description ?></div><?cs /if ?><?cs
-   if:build.config.can_modify ?><div class="buttons">
-    <form method="get" action=""><div>
-     <input type="hidden" name="action" value="edit" />
-     <input type="submit" value="Edit configuration" />
-    </div></form><?cs
-   /if ?></div><?cs
-
-  elif:build.mode == 'edit_platform' ?>
-   <form class="platform" method="post" action="">
-    <div class="field"><label>Name:<br />
-     <input type="text" name="name" value="<?cs var:build.platform.name ?>" />
-    </label></div>
-    <h2>Rules</h2>
-    <table><thead><tr>
-     <th>Property name</th><th>Match pattern</th>
-    </tr></thead><tbody><?cs
-     each:rule = build.platform.rules ?><tr>
-      <td><input type="text" name="property_<?cs var:name(rule) ?>" value="<?cs
-       var:rule.property ?>" /></td>
-      <td><input type="text" name="pattern_<?cs var:name(rule) ?>" value="<?cs
-       var:rule.pattern ?>" /></td>
-      <td><input type="submit" name="rm_rule_<?cs
-        var:name(rule) ?>" value="-" /><input type="submit" name="add_rule_<?cs
-        var:name(rule) ?>" value="+" />
-      </td>
-     </tr><?cs /each ?>
-    </tbody></table>
-    <div class="buttons">
-     <form method="get" action=""><div>
-     <input type="hidden" name="action" value="<?cs
-       if:build.platform.exists ?>edit<?cs else ?>new<?cs /if ?>" />
-      <input type="hidden" name="platform" value="<?cs
-       var:build.platform.id ?>" />
-      <input type="submit" name="cancel" value="Cancel" />
-      <input type="submit" value="<?cs
-       if:build.platform.exists ?>Save changes<?cs else ?>Add platform<?cs
-       /if ?>" />
-     </div></form>
-    </div>
-   </form><?cs
-
-  elif:build.mode == 'view_build' ?>
-   <p class="trigger">Triggered by: Changeset <a href="<?cs
-     var:build.chgset_href ?>">[<?cs var:build.rev ?>]</a> of <a href="<?cs
-     var:build.config.href ?>"><?cs var:build.config.name ?></a></p>
-   <p class="slave">Built by: <strong title="<?cs
-     var:build.slave.ip_address ?>"><?cs var:build.slave.name ?></strong> (<?cs
-     var:build.slave.os ?> <?cs var:build.slave.os.version ?><?cs
-     if:build.slave.machien ?> on <?cs var:build.slave.machine ?><?cs
-     /if ?>)</p>
-   <p class="time">Completed: <?cs var:build.started ?> (<?cs
-     var:build.started_delta ?> ago)<br />Took: <?cs var:build.duration ?></p><?cs
-  /if ?>
-
- </div>
-<?cs include:"footer.cs" ?>
-"""
+    implements(INavigationContributor, IRequestHandler, ITimelineEventProvider,
+               ITemplateProvider)
 
     _status_label = {Build.IN_PROGRESS: 'in progress',
                      Build.SUCCESS: 'completed',
@@ -210,10 +75,18 @@
                 if action == 'new':
                     self._do_create_platform(req, config)
                 else:
+                    self.log.debug('Request args: %s', req.args.keys())
                     platform_id = req.args.get('platform')
                     if platform_id:
                         if action == 'edit':
                             self._do_save_platform(req, config, platform_id)
+                    elif 'delete' in req.args.keys():
+                        self._do_delete_platforms(req)
+                        self._render_config_form(req, config)
+                    elif 'new' in req.args.keys():
+                        platform = TargetPlatform(self.env)
+                        platform.config = config
+                        self._render_platform_form(req, platform)
                     else:
                         self._do_save_config(req, config)
             else:
@@ -228,12 +101,11 @@
                     if platform_id:
                         platform = TargetPlatform(self.env, int(platform_id))
                         self._render_platform_form(req, platform)
+                    elif 'new' in req.args.keys():
+                        platform = TargetPlatform(self.env)
+                        self._render_platform_form(req, platform)
                     else:
                         self._render_config_form(req, config)
-                elif action == 'new':
-                    platform = TargetPlatform(self.env)
-                    platform.config = config
-                    self._render_platform_form(req, platform)
                 else:
                     self._render_config(req, config)
             else:
@@ -242,7 +114,16 @@
                 else:
                     self._render_overview(req)
 
-        return req.hdf.parse(self.build_cs), None
+        add_stylesheet(req, 'build.css')
+        return 'build.cs', None
+
+    # ITemplatesProvider methods
+
+    def get_htdocs_dir(self):
+        return self.config.get('bitten', 'htdocs_dir')
+
+    def get_templates_dir(self):
+        return self.config.get('bitten', 'templates_dir')
 
     # ITimelineEventProvider methods
 
@@ -252,6 +133,7 @@
 
     def get_timeline_events(self, req, start, stop, filters):
         if 'build' in filters:
+            add_stylesheet(req, 'build.css')
             db = self.env.get_db_cnx()
             cursor = db.cursor()
             cursor.execute("SELECT id,config,label,rev,slave,stopped,status "
@@ -343,6 +225,19 @@
 
         req.redirect(self.env.href.build(config_name, action='edit'))
 
+    def _do_delete_platforms(self, req):
+        """Delete selected target platforms."""
+        req.perm.assert_permission('BUILD_MODIFY')
+        self.log.debug('_do_delete_platforms')
+
+        db = self.env.get_db_cnx()
+        for platform_id in [int(id) for id in req.args.get('delete_platform')]:
+            platform = TargetPlatform(self.env, platform_id, db=db)
+            self.log.info('Deleting target platform %s of configuration %s',
+                          platform.name, platform.config)
+            platform.delete(db=db)
+        db.commit()
+
     def _do_save_platform(self, req, config_name, platform_id):
         """Save changes to a target platform."""
         req.perm.assert_permission('BUILD_MODIFY')
@@ -411,6 +306,9 @@
         }
         req.hdf['build.mode'] = 'view_config'
 
+        platforms = TargetPlatform.select(self.env, config=config_name)
+        req.hdf['build.platforms'] = [platform.name for platform in platforms]
+
         repos = self.env.get_repository(req.authname)
         root = repos.get_node(config.path)
         num = 0
@@ -431,11 +329,6 @@
                 'exists': config.exists
             }
 
-            if 'new' in req.args.keys() or 'platform' in req.args.keys():
-                self._render_platform_form(req, config_name,
-                                           req.args.get('platform'))
-                return
-
             req.hdf['title'] = 'Edit Build Configuration "%s"' \
                                % escape(config.label or config.name)
             for idx, platform in enumerate(TargetPlatform.select(self.env,
@@ -452,12 +345,15 @@
 
     def _render_platform_form(self, req, platform):
         req.perm.assert_permission('BUILD_MODIFY')
-        req.hdf['title'] = 'Edit Target Platform "%s"' \
-                           % escape(platform.name)
+        if platform.exists:
+            req.hdf['title'] = 'Edit Target Platform "%s"' \
+                               % escape(platform.name)
+        else:
+            req.hdf['title'] = 'Add Target Platform'
         req.hdf['build.platform'] = {
             'name': platform.name, 'id': platform.id, 'exists': platform.exists,
             'rules': [{'property': propname, 'pattern': pattern}
-                      for propname, pattern in platform.rules]
+                      for propname, pattern in platform.rules] + [('', '')]
         }
         req.hdf['build.mode'] = 'edit_platform'
 
new file mode 100644
--- /dev/null
+++ b/htdocs/build.css
@@ -0,0 +1,10 @@
+/* Timeline styles */
+#content.timeline dt.successbuild, #content.timeline dt.successbuild a,
+#content.timeline dt.failedbuild, #content.timeline dt.failedbuild a {
+ background-image: url(build.png) !important
+}
+
+#content.build form.config td.active { vertical-align: bottom; }
+#content.build form.platforms ul { list-style: none; padding-left: 1em; }
+
+#content.build #builds { margin-top: 2em; }
new file mode 100755
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1e2f5669121b121d8119ca1ca05ff66663a41075
GIT binary patch
literal 300
zc%17D@N?(olHy`uVBq!ia0vp^JRr=%3?!FCJ6-`&%*9TgAsieWw;%dH0CH6Vd_r9R
z|NsB&*|Vd^PoG~B(Kl^QOH0e_8mF|hwBjhusHi9_D=SMa-YEH<-$7=T1o;Is{6_%J
zBLQkaan1sd$YKTtZXpn6ymYtj4^U9C#5JNMI6tkVJh3R1!8b9vC_gtfB{NaMEwd=K
zJijQrSiwZk;FX$sDNwN(NU?KKYGO%dex5=|W^O8jfw{hcp}v8s@ER``pb8657srr_
zImrnLAwek&Oo7(}7}?m4P0(;<6-c-A$O#MC$vB}aC1N7aBcp=tk_|6qZ|sy!=t^W}
ZIP#C{{ok5>vOqH!JYD@<);T3K0RZWzW`O_z
new file mode 100644
--- /dev/null
+++ b/templates/build.cs
@@ -0,0 +1,143 @@
+<?cs include:"header.cs" ?>
+ <div id="ctxtnav" class="nav"></div>
+ <div id="content" class="build">
+  <h1><?cs var:title ?></h1><?cs
+
+  if:build.mode == 'overview' ?><?cs
+   each:config = build.configs ?>
+    <h2><a href="<?cs var:config.href ?>"><?cs var:config.label ?></a></h2><?cs
+    if:config.description ?><div class="description"><?cs
+     var:config.description ?></div><?cs
+    /if ?><?cs
+   /each ?><?cs
+   if:build.can_create ?><div class="buttons">
+    <form method="get" action=""><div>
+     <input type="hidden" name="action" value="new" />
+     <input type="submit" value="Add configuration" />
+    </div></form></div><?cs
+   /if ?></div><?cs
+
+  elif:build.mode == 'edit_config' ?>
+   <form class="config" method="post" action="">
+    <table><tr>
+     <td class="name"><label>Name:<br />
+      <input type="text" name="name" value="<?cs var:build.config.name ?>" />
+     </label></td>
+     <td class="label"><label>Label (for display):<br />
+      <input type="text" name="label" size="32" value="<?cs
+        var:build.config.label ?>" />
+     </label></td>
+    </tr><tr>
+     <td class="active"><label><input type="checkbox" name="active"<?cs
+       if:build.config.active ?> checked="checked"<?cs /if ?> /> Active
+     </label></td>
+     <td class="path"><label>Repository path:<br />
+      <input type="text" name="path" size="48" value="<?cs
+        var:build.config.path ?>" />
+     </label></td>
+    </tr><tr>
+     <td colspan="2"><fieldset class="iefix">
+      <label for="description">Description (you may use <a tabindex="42" href="<?cs
+        var:trac.href.wiki ?>/WikiFormatting">WikiFormatting</a> here):</label>
+      <p><textarea id="description" name="description" class="wikitext" rows="5" cols="78"><?cs
+        var:build.config.description ?></textarea></p>
+      <script type="text/javascript" src="<?cs
+        var:htdocs_location ?>js/wikitoolbar.js"></script>
+     </fieldset></td>
+    </tr></table>
+    <div class="buttons">
+     <input type="hidden" name="action" value="<?cs
+       if:build.config.exists ?>edit<?cs else ?>new<?cs /if ?>" />
+     <input type="submit" value="<?cs
+       if:build.config.exists ?>Save changes<?cs else ?>Create<?cs /if ?>" />
+     <input type="submit" name="cancel" value="Cancel" />
+    </div>
+   </form><?cs
+   if:build.config.exists ?><div class="platforms">
+    <form class="platforms" method="post" action="">
+     <h2>Target Platforms</h2><?cs
+      if:len(build.platforms) ?><ul><?cs
+       each:platform = build.platforms ?>
+        <li><input type="checkbox" name="delete_platform" value="<?cs
+         var:platform.id ?>"> <a href="<?cs
+         var:platform.href ?>"><?cs var:platform.name ?></a>
+        </li><?cs
+       /each ?></ul><?cs
+      /if ?>
+     <div class="buttons">
+      <input type="submit" name="new" value="Add target platform" />
+      <input type="submit" name="delete" value="Delete selected platforms" />
+     </div>
+    </form>
+   </div><?cs
+   /if ?><?cs
+
+  elif:build.mode == 'view_config' ?><ul>
+   <li>Active: <?cs if:build.config.active ?>yes<?cs else ?>no<?cs /if ?></li>
+   <li>Path: <?cs if:build.config.path ?><a href="<?cs
+     var:build.config.browser_href ?>"><?cs
+     var:build.config.path ?></a></li><?cs /if ?></ul><?cs
+   if:build.config.description ?><div class="description"><?cs
+     var:build.config.description ?></div><?cs
+   /if ?><?cs
+   if:build.config.can_modify ?><div class="buttons">
+    <form method="get" action=""><div>
+     <input type="hidden" name="action" value="edit" />
+     <input type="submit" value="Edit configuration" />
+    </div></form><?cs
+   /if ?><?cs
+   if:len(build.platforms) ?>
+    <table class="listing" id="builds"><thead><tr><th>Build</th><?cs
+    each:platform = build.platforms ?><th><?cs var:platform ?><?cs /each ?>
+   </tr></thead></table><?cs
+   /if ?></div><?cs
+
+  elif:build.mode == 'edit_platform' ?>
+   <form class="platform" method="post" action="">
+    <div class="field"><label>Name:<br />
+     <input type="text" name="name" value="<?cs var:build.platform.name ?>" />
+    </label></div>
+    <h2>Rules</h2>
+    <table><thead><tr>
+     <th>Property name</th><th>Match pattern</th>
+    </tr></thead><tbody><?cs
+     each:rule = build.platform.rules ?><tr>
+      <td><input type="text" name="property_<?cs var:name(rule) ?>" value="<?cs
+       var:rule.property ?>" /></td>
+      <td><input type="text" name="pattern_<?cs var:name(rule) ?>" value="<?cs
+       var:rule.pattern ?>" /></td>
+      <td><input type="submit" name="rm_rule_<?cs
+        var:name(rule) ?>" value="-" /><input type="submit" name="add_rule_<?cs
+        var:name(rule) ?>" value="+" />
+      </td>
+     </tr><?cs /each ?>
+    </tbody></table>
+    <div class="buttons">
+     <form method="get" action=""><div>
+     <input type="hidden" name="action" value="<?cs
+       if:build.platform.exists ?>edit<?cs else ?>new<?cs /if ?>" />
+      <input type="hidden" name="platform" value="<?cs
+       var:build.platform.id ?>" />
+      <input type="submit" value="<?cs
+       if:build.platform.exists ?>Save changes<?cs else ?>Add platform<?cs
+       /if ?>" />
+      <input type="submit" name="cancel" value="Cancel" />
+     </div></form>
+    </div>
+   </form><?cs
+
+  elif:build.mode == 'view_build' ?>
+   <p class="trigger">Triggered by: Changeset <a href="<?cs
+     var:build.chgset_href ?>">[<?cs var:build.rev ?>]</a> of <a href="<?cs
+     var:build.config.href ?>"><?cs var:build.config.name ?></a></p>
+   <p class="slave">Built by: <strong title="<?cs
+     var:build.slave.ip_address ?>"><?cs var:build.slave.name ?></strong> (<?cs
+     var:build.slave.os ?> <?cs var:build.slave.os.version ?><?cs
+     if:build.slave.machien ?> on <?cs var:build.slave.machine ?><?cs
+     /if ?>)</p>
+   <p class="time">Completed: <?cs var:build.started ?> (<?cs
+     var:build.started_delta ?> ago)<br />Took: <?cs var:build.duration ?></p><?cs
+  /if ?>
+
+ </div>
+<?cs include:"footer.cs" ?>
Copyright (C) 2012-2017 Edgewall Software