Modify markup of Tahoe web pages to be more amenable to styling; some minor changes...
authorKevin Reid <kpreid@mac.com>
Tue, 26 May 2009 23:25:45 +0000 (16:25 -0700)
committerKevin Reid <kpreid@mac.com>
Tue, 26 May 2009 23:25:45 +0000 (16:25 -0700)
16 files changed:
src/allmydata/test/test_checker.py
src/allmydata/test/test_system.py
src/allmydata/test/test_web.py
src/allmydata/web/check_results.py
src/allmydata/web/deep-check-and-repair-results.xhtml
src/allmydata/web/deep-check-results.xhtml
src/allmydata/web/directory.py
src/allmydata/web/directory.xhtml
src/allmydata/web/info.xhtml
src/allmydata/web/introducer.xhtml
src/allmydata/web/introweb.py
src/allmydata/web/manifest.xhtml
src/allmydata/web/reliability.xhtml
src/allmydata/web/root.py
src/allmydata/web/status.xhtml
src/allmydata/web/welcome.xhtml

index 8dc24489e4eb730666d3ebab484d7b936386e763..a63db06bacdd44077739f967abc82b5c03d4a00e 100644 (file)
@@ -103,7 +103,7 @@ class WebResultsRendering(unittest.TestCase, WebRenderingMixin):
         s = self.remove_tags(html)
         self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
         self.failUnlessIn("Not Recoverable! : rather dead", s)
-        self.failUnlessIn("Corrupt shares: sh#2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (peer-0)", s)
+        self.failUnlessIn("Corrupt shares: Share ID Nickname Node ID sh#2 peer-0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", s)
 
         html = self.render2(w)
         s = self.remove_tags(html)
index c1c9b08a24c58495ed436fc962303b1e438db734..964af2e3c8e29de90ce58562c54c37dfedd51032 100644 (file)
@@ -1101,12 +1101,13 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
         public = "uri/" + self._root_directory_uri
         d = getPage(base)
         def _got_welcome(page):
-            expected = "Connected Storage Servers: <span>%d</span>" % (self.numclients)
+            # XXX This test is oversensitive to formatting
+            expected = "Connected to <span>%d</span>\n     of <span>%d</span> known storage servers:" % (self.numclients, self.numclients)
             self.failUnless(expected in page,
                             "I didn't see the right 'connected storage servers'"
                             " message in: %s" % page
                             )
-            expected = "My nodeid: <span>%s</span>" % (b32encode(self.clients[0].nodeid).lower(),)
+            expected = "<th>My nodeid:</th> <td class=\"nodeid mine data-chars\">%s</td>" % (b32encode(self.clients[0].nodeid).lower(),)
             self.failUnless(expected in page,
                             "I didn't see the right 'My nodeid' message "
                             "in: %s" % page)
index b99e26abc84657a8a1bc9e2d0669d49126acc24b..78c3e62dedc7be841690e0e23cdc4629a65e95dc 100644 (file)
@@ -879,10 +879,10 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
             # the FILE reference points to a URI, but it should end in bar.txt
             bar_url = ("%s/file/%s/@@named=/bar.txt" %
                        (ROOT, urllib.quote(self._bar_txt_uri)))
-            get_bar = "".join([r'<td>',
+            get_bar = "".join([r'<td>FILE</td>',
+                               r'\s+<td>',
                                r'<a href="%s">bar.txt</a>' % bar_url,
                                r'</td>',
-                               r'\s+<td>FILE</td>',
                                r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
                                ])
             self.failUnless(re.search(get_bar, res), res)
@@ -904,8 +904,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
 
             # the DIR reference just points to a URI
             sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
-            get_sub = ((r'<td><a href="%s">sub</a></td>' % sub_url)
-                       + r'\s+<td>DIR</td>')
+            get_sub = ((r'<td>DIR</td>')
+                       +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
             self.failUnless(re.search(get_sub, res), res)
         d.addCallback(_check)
 
@@ -921,8 +921,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
         d.addCallback(lambda res:
                       self.GET(self.public_url, followRedirect=True))
         def _check3(res):
-            self.failUnless(re.search(r'<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
-                                      '</td>\s+<td>DIR-RO</td>', res))
+            self.failUnless(re.search('<td>DIR-RO</td>'
+                                      r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
         d.addCallback(_check3)
 
         # and an empty directory
@@ -1895,7 +1895,7 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
         # Fetch the welcome page.
         d = self.GET("/")
         def _after_get_welcome_page(res):
-            MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="create directory" />', re.I)
+            MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*?<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create a directory" />', re.I)
             mo = MKDIR_BUTTON_RE.search(res)
             formaction = mo.group(1)
             formt = mo.group(2)
index 6eaf4be4e3c66f71298152fbc2beafbedd9c0f83..03c689c226098d653084c8d7e076faac3ca2b20e 100644 (file)
@@ -97,10 +97,13 @@ class ResultsBase:
             for (serverid, si, shnum) in data["list-corrupt-shares"]:
                 nickname = c.get_nickname_for_peerid(serverid)
                 badsharemap.append(T.tr[T.td["sh#%d" % shnum],
-                                        T.td[T.tt[base32.b2a(serverid)],
-                                             " (", nickname, ")"],
+                                        T.td[T.div(class_="nickname")[nickname],
+                                              T.div(class_="nodeid")[T.tt[base32.b2a(serverid)]]],
                                         ])
-            add("Corrupt shares", T.table(border="1")[badsharemap])
+            add("Corrupt shares", T.table()[
+                T.tr[T.th["Share ID"],
+                     T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]]],
+                badsharemap])
         else:
             add("Corrupt shares", "none")
 
@@ -122,11 +125,12 @@ class ResultsBase:
                     shareid_s = shareid
                 nickname = c.get_nickname_for_peerid(serverid)
                 sharemap.append(T.tr[T.td[shareid_s],
-                                     T.td[T.tt[base32.b2a(serverid)],
-                                          " (", nickname, ")"],
+                                     T.td[T.div(class_="nickname")[nickname],
+                                          T.div(class_="nodeid")[T.tt[base32.b2a(serverid)]]]
                                      ])
         add("Good Shares (sorted in share order)",
-            T.table(border="1")[sharemap])
+            T.table()[T.tr[T.th["Share ID"], T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]]],
+                      sharemap])
 
 
         add("Recoverable Versions", data["count-recoverable-versions"])
@@ -145,14 +149,16 @@ class ResultsBase:
             shareids = servers.get(serverid, [])
             shareids.reverse()
             shareids_s = [ T.tt[shareid, " "] for shareid in sorted(shareids) ]
-            servermap.append(T.tr[T.td[T.tt[base32.b2a(serverid)],
-                                       " (", nickname, ")"],
-                                  T.td[shareids_s] ])
+            servermap.append(T.tr[T.td[T.div(class_="nickname")[nickname],
+                                       T.div(class_="nodeid")[T.tt[base32.b2a(serverid)]]],
+                                  T.td[shareids_s],
+                                  ])
             num_shares_left -= len(shareids)
             if not num_shares_left:
                 break
         add("Share Balancing (servers in permuted order)",
-            T.table(border="1")[servermap])
+            T.table()[T.tr[T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]], T.th["Share IDs"]],
+                      servermap])
 
         return T.ul[r]
 
index 3724d169c57a086b890a336748ab1626603cc55e..b440de7ef5f8b4db26d724f986c02469b9fb692f 100644 (file)
@@ -69,7 +69,7 @@
 <div n:render="return" />
 
 <div>
-<table n:render="sequence" n:data="all_objects" border="1">
+<table n:render="sequence" n:data="all_objects">
   <tr n:pattern="header">
     <td>Relative Path</td>
     <td>Healthy Pre-Repair</td>
index 8c4fb9311fc13131e0cae2adb896ce29ce10b0d8..daf1943b19b32d8ec5feaf8561f217fb811398a1 100644 (file)
@@ -43,7 +43,7 @@
 <div n:render="corrupt_shares_p">
 <h2>Corrupt Shares</h2>
 <p>If repair fails, these shares need to be manually inspected and removed.</p>
-<table n:render="sequence" n:data="corrupt_shares" border="1">
+<table n:render="sequence" n:data="corrupt_shares">
   <tr n:pattern="header">
     <td>Server</td>
     <td>Server Nickname</td>
@@ -63,7 +63,7 @@
 
 <div>
 <h2>All Results</h2>
-<table n:render="sequence" n:data="all_objects" border="1">
+<table n:render="sequence" n:data="all_objects">
   <tr n:pattern="header">
     <td>Relative Path</td>
     <td>Healthy</td>
index ac0b09e8dc414f6418473c949699d3b6f6b654cc..5c83b51b9f337f1457d32dbda6dcbc589fc316fb 100644 (file)
@@ -533,7 +533,7 @@ class DirectoryAsHTML(rend.Page):
 
     def render_header(self, ctx, data):
         si_s = abbreviated_dirnode(self.node)
-        header = ["Directory SI=%s" % si_s]
+        header = ["Directory SI=", T.span(class_="data-chars")[si_s]]
         if self.node.is_readonly():
             header.append(" (read-only)")
         return ctx.tag[header]
index e167aebabfc71e95b39d1c6e18e40da065d24fd2..6dd6a57ba14210756b8b9a1dd7a399603896583d 100644 (file)
@@ -5,44 +5,46 @@
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 </head><body class="tahoe-directory-page">
 
-<h2 n:render="header"></h2>
+<h1 n:render="header"></h1>
 
-<div><a href=".">Refresh this view</a></div>
-<div n:render="welcome" />
+<div class="toolbar">
+  <div class="toolbar-item" n:render="welcome" />
+  <div class="toolbar-item"><a href=".">Refresh</a></div>
 
-<div><a href="?t=info">More info on this directory</a></div>
-<div n:render="show_readonly" />
+  <div class="toolbar-item"><a href="?t=info">More info on this directory</a></div>
+  <div class="toolbar-item" n:render="show_readonly" />
+</div>
 
 <div n:render="try_children">
-<table n:render="sequence" n:data="children" border="1">
-  <tr n:pattern="header">
-    <td>Filename</td>
-    <td>Type</td>
-    <td>Size</td>
-    <td>Times</td>
-    <td></td>
-    <td></td>
-    <td></td>
-  </tr>
-  <tr n:pattern="item" n:render="row">
-    <td><n:slot name="filename"/></td>
-    <td><n:slot name="type"/></td>
-    <td><n:slot name="size"/></td>
-    <td><n:slot name="times"/></td>
-    <td><n:slot name="delete"/></td>
-    <td><n:slot name="rename"/></td>
-    <td><n:slot name="info"/></td>
-  </tr>
-
-  <tr n:pattern="empty"><td>directory is empty</td></tr>
-
-</table>
+  <table class="tahoe-directory" n:render="sequence" n:data="children">
+    <tr n:pattern="header">
+      <th>Type</th>
+      <th>Filename</th>
+      <th>Size</th>
+      <th>Times</th>
+      <th></th>
+      <th></th>
+      <th></th>
+    </tr>
+    <tr n:pattern="item" n:render="row">
+      <td><n:slot name="type"/></td>
+      <td><n:slot name="filename"/></td>
+      <td><n:slot name="size"/></td>
+      <td><n:slot name="times"/></td>
+      <td><n:slot name="delete"/></td>
+      <td><n:slot name="rename"/></td>
+      <td><n:slot name="info"/></td>
+    </tr>
+
+    <tr n:pattern="empty"><td colspan="9" class="empty-marker">This directory is empty.</td></tr>
+
+  </table>
 </div>
 
+<div class="tahoe-directory-footer">
+  <div n:render="forms"/>
 
-<div n:render="forms"/>
-
-<div class="results" n:render="results"/>
+  <div class="results" n:render="results"/>
+</div>
 
-  </body>
-</html>
+</body></html>
index a209887964270829c582bd328768a15dde67f5a3..5e0a09c1600493eba112aec0dad31fb8bea8f6d9 100644 (file)
@@ -6,40 +6,42 @@
   <link href="/tahoe_css" rel="stylesheet" type="text/css"/>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 
-<h2 n:render="header"></h2>
+</head><body>
+
+<h1 n:render="header"></h1>
 
 <ul>
   <li>Object Type: <span n:render="type" /></li>
   <li>Storage Index: <tt n:render="si" /></li>
   <li>Object Size: <span n:render="size" /></li>
-  <li>Access Caps (URIs):
-  <table border="1">
+  <li>Access Caps (Tahoe-URIs):
+  <table class="table-headings-left">
     <span n:render="is_directory">
     <tr>
-      <td>Directory writecap</td>
-      <td><tt n:render="directory_writecap" /></td>
+      <th>Directory writecap</th>
+      <td><tt class="data-chars" n:render="directory_writecap" /></td>
     </tr>
     <tr>
-      <td>Directory readcap</td>
-      <td><tt n:render="directory_readcap" /></td>
+      <th>Directory readcap</th>
+      <td><tt class="data-chars" n:render="directory_readcap" /></td>
     </tr>
     <tr>
-      <td>Directory verifycap</td>
-      <td><tt n:render="directory_verifycap" /></td>
+      <th>Directory verifycap</th>
+      <td><tt class="data-chars" n:render="directory_verifycap" /></td>
     </tr>
     </span>
 
     <tr>
-      <td>File writecap</td>
-      <td><tt n:render="file_writecap" /></td>
+      <th>File writecap</th>
+      <td><tt class="data-chars" n:render="file_writecap" /></td>
     </tr>
     <tr>
-      <td>File readcap</td>
-      <td><tt n:render="file_readcap" /></td>
+      <th>File readcap</th>
+      <td><tt class="data-chars" n:render="file_readcap" /></td>
     </tr>
     <tr>
-      <td>File verifycap</td>
-      <td><tt n:render="file_verifycap" /></td>
+      <th>File verifycap</th>
+      <td><tt class="data-chars" n:render="file_verifycap" /></td>
     </tr>
   </table></li>
   <li><a href="?t=json">JSON</a></li>
@@ -64,6 +66,4 @@
   <div n:render="manifest_form" />
 </div>
 
-
-  </body>
-</html>
+</body></html>
index dea37a6f3a7d765ad6eb74772c4e5b31197baab3..ac5cf7b51dd6732241ee177922e2ef4e3a60244b 100644 (file)
@@ -21,7 +21,7 @@
 <h2>Service Announcements</h2>
 
 <div>
-<table n:render="sequence" n:data="services" border="1">
+<table n:render="sequence" n:data="services">
   <tr n:pattern="header">
     <td>PeerID / Nickname</td>
     <td>Advertised IPs</td>
@@ -43,7 +43,7 @@
 <h2>Subscribed Clients</h2>
 
 <div>
-<table n:render="sequence" n:data="subscribers" border="1">
+<table n:render="sequence" n:data="subscribers">
   <tr n:pattern="header">
     <td>PeerID / Nickname</td>
     <td>Advertised IPs</td>
index 27edbcbb4fb0f526c71bc3d75e5968b11fe45f2f..c405cfd04ad93d64641157807dfab43374499cde 100644 (file)
@@ -68,7 +68,7 @@ class IntroducerRoot(rend.Page):
     def data_version(self, ctx, data):
         return get_package_versions_string()
     def data_import_path(self, ctx, data):
-        return str(allmydata)
+        return str(allmydata).replace("/", "/ ") # XXX kludge for wrapping
     def data_my_nodeid(self, ctx, data):
         return idlib.nodeid_b2a(self.introducer_node.nodeid)
 
index ffc86b9ae399eecf9d9c3e7053af0f62220e5c60..6645f3354d0fb7624298eb168e35a8cd805b10a8 100644 (file)
@@ -12,7 +12,7 @@
 
 <h2 n:render="reload" />
 
-<table n:render="sequence" n:data="items" border="1">
+<table n:render="sequence" n:data="items">
   <tr n:pattern="header">
     <td>Path</td>
     <td>cap</td>
index ad3b091a5e10fe5cecfe431dcb7a5e0a5a478c65..910f23af6679df50cd88d3ae65717a1c26e4730a 100644 (file)
@@ -42,7 +42,7 @@ repair bandwidth to configure on a Tahoe grid.</p>
 </ul>
 
 <div>
-<table n:render="sequence" n:data="simulation_table" border="1">
+<table n:render="sequence" n:data="simulation_table">
   <tr n:pattern="header">
     <td>t</td>
     <td>P_repair</td>
index b84f76f61e97416c992461763ed3d61366e0cec2..910847ca2d5e67d6cae605fcab465a3930a58c76 100644 (file)
@@ -276,6 +276,7 @@ class Root(rend.Page):
 
         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
         ctx.fillSlots("connected", connected)
+        ctx.fillSlots("connected-bool", not not rsc.rref)
         ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
         ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
                                                  time.localtime(rsc.announcement_time)))
@@ -290,10 +291,10 @@ class Root(rend.Page):
                       enctype="multipart/form-data")[
             T.fieldset[
             T.legend(class_="freeform-form-label")["Download a file"],
-            "URI to download: ",
-            T.input(type="text", name="uri"), " ",
-            "Filename to download as: ",
-            T.input(type="text", name="filename"), " ",
+            T.div["Tahoe-URI to download: ",
+                  T.input(type="text", name="uri")],
+            T.div["Filename to download as: ",
+                  T.input(type="text", name="filename")],
             T.input(type="submit", value="Download!"),
             ]]
         return T.div[form]
@@ -305,7 +306,7 @@ class Root(rend.Page):
                       enctype="multipart/form-data")[
             T.fieldset[
             T.legend(class_="freeform-form-label")["View a file or directory"],
-            "URI to view: ",
+            "Tahoe-URI to view: ",
             T.input(type="text", name="uri"), " ",
             T.input(type="submit", value="View!"),
             ]]
@@ -317,11 +318,11 @@ class Root(rend.Page):
                       enctype="multipart/form-data")[
             T.fieldset[
             T.legend(class_="freeform-form-label")["Upload a file"],
-            "Choose a file: ",
-            T.input(type="file", name="file", class_="freeform-input-file"),
+            T.div["Choose a file: ",
+                  T.input(type="file", name="file", class_="freeform-input-file")],
             T.input(type="hidden", name="t", value="upload"),
-            " Mutable?:", T.input(type="checkbox", name="mutable"),
-            T.input(type="submit", value="Upload!"),
+            T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
+                  " ", T.input(type="submit", value="Upload!")],
             ]]
         return T.div[form]
 
@@ -330,10 +331,10 @@ class Root(rend.Page):
         form = T.form(action="uri", method="post",
                       enctype="multipart/form-data")[
             T.fieldset[
-            T.legend(class_="freeform-form-label")["Create a directory."],
+            T.legend(class_="freeform-form-label")["Create a directory"],
             T.input(type="hidden", name="t", value="mkdir"),
             T.input(type="hidden", name="redirect_to_result", value="true"),
-            T.input(type="submit", value="create directory"),
+            T.input(type="submit", value="Create a directory"),
             ]]
         return T.div[form]
 
index 94e5bed54432aeebad4c79f3aeb120a6c2fa1c5c..53fc2b41a96f5bcd25e85218164c7b6349f022b6 100644 (file)
 
 
 <h2>Active Operations:</h2>
-<table n:render="sequence" n:data="active_operations" border="1">
+<table class="table-headings-top" n:render="sequence" n:data="active_operations">
   <tr n:pattern="header">
-    <td>Type</td>
-    <td>Storage Index</td>
-    <td>Helper?</td>
-    <td>Total Size</td>
-    <td>Progress</td>
-    <td>Status</td>
+    <th>Type</th>
+    <th>Storage Index</th>
+    <th>Helper?</th>
+    <th>Total Size</th>
+    <th>Progress</th>
+    <th>Status</th>
   </tr>
   <tr n:pattern="item" n:render="row">
     <td><n:slot name="type"/></td>
 
 
 <h2>Recent Operations:</h2>
-<table n:render="sequence" n:data="recent_operations" border="1">
+<table class="table-headings-top" n:render="sequence" n:data="recent_operations">
   <tr n:pattern="header">
-    <td>Started</td>
-    <td>Type</td>
-    <td>Storage Index</td>
-    <td>Helper?</td>
-    <td>Total Size</td>
-    <td>Progress</td>
-    <td>Status</td>
+    <th>Started</th>
+    <th>Type</th>
+    <th>Storage Index</th>
+    <th>Helper?</th>
+    <th>Total Size</th>
+    <th>Progress</th>
+    <th>Status</th>
   </tr>
   <tr n:pattern="item" n:render="row">
     <td><n:slot name="started"/></td>
index 69da82e25b21537db0f782218c4dcc0caf83ed7f..b578de661438e8e44c799ae1954dbef1b1f5f378 100644 (file)
@@ -7,75 +7,94 @@
 
 <h1>Welcome To AllMyData "Tahoe"!</h1>
 
-<h2>Status of this Client</h2>
+<div class="section" id="this-client">
+  <h2>This Client</h2>
+
+  <p>
+    <a href="status/">Recent Uploads and Downloads</a>,
+    <a href="statistics">Operational Statistics</a>
+  </p>
+
+  <table class="node-info table-headings-left">
+    <tr><th>My nickname:</th> <td class="nickname mine" n:render="string" n:data="my_nickname" /></tr>
+    <tr><th>My nodeid:</th> <td class="nodeid mine data-chars" n:render="string" n:data="my_nodeid" /></tr>
+    <tr><th>My versions:</th> <td n:render="string" n:data="version" /></tr>
+    <tr><th>Tahoe code imported from:</th> <td n:render="string" n:data="import_path" /></tr>
+    <tr><th>Services running:</th> <td n:render="services" /></tr>
+  </table>
+  
 
-<div>My nodeid: <span n:render="string" n:data="my_nodeid" /></div>
-<div>My nickname: <span n:render="string" n:data="my_nickname" /></div>
-<div>My versions: <span n:render="string" n:data="version" /></div>
-<div>Tahoe code imported from: <span n:render="string" n:data="import_path" /></div>
-<div n:render="services">Services Running:</div>
+</div>
+
+<div class="section" id="controls">
+<h2>Controls</h2>
 
-<div>
-  <a href="status/">Recent Uploads and Downloads</a>,
-  <a href="statistics">Operational Statistics</a>
+  <p>There are also controls for each directory on that directory's page.</p>
+
+  <div n:render="mkdir_form" />
+  <div n:render="view_form" />
+  <div n:render="upload_form" />
+  <div n:render="download_form" />
 </div>
 
-<h2>The Controls</h2>
-
-<div>There are also controls for each directory on that directory's page.</div>
-
-<div n:render="mkdir_form" />
-<div n:render="view_form" />
-<div n:render="upload_form" />
-<div n:render="download_form" />
-
-
-<h2>Status of the Storage Grid</h2>
-
-<div>Introducer: <span n:render="string" n:data="introducer_furl" /></div>
-<div>Connected to introducer?: <span n:render="string" n:data="connected_to_introducer" /></div>
-
-<div>Helper: <span n:render="string" n:data="helper_furl" /></div>
-<div>Connected to helper?: <span n:render="string" n:data="connected_to_helper" /></div>
-
-<br />
-
-<div>Known Storage Servers: <span n:render="string" n:data="known_storage_servers" /></div>
-<div>Connected Storage Servers: <span n:render="string" n:data="connected_storage_servers" /></div>
-
-<div>
-<table n:render="sequence" n:data="services" border="1">
-  <tr n:pattern="header">
-    <td>PeerID</td>
-    <td>Nickname</td>
-    <td>Connected?</td>
-    <td>Since</td>
-    <td>Announced</td>
-    <td>Version</td>
-    <td>Service Name</td>
-  </tr>
-  <tr n:pattern="item" n:render="service_row">
-    <td><tt><n:slot name="peerid"/></tt></td>
-    <td><b><n:slot name="nickname"/></b></td>
-    <td><tt><n:slot name="connected"/></tt></td>
-    <td><tt><n:slot name="since"/></tt></td>
-    <td><tt><n:slot name="announced"/></tt></td>
-    <td><tt><n:slot name="version"/></tt></td>
-    <td><tt><n:slot name="service_name"/></tt></td>
-  </tr>
-  <tr n:pattern="empty"><td>no peers!</td></tr>
-</table>
+<div class="section" id="grid">
+  <h2>Status of the Storage Grid</h2>
+
+  <div>
+    <n:attr name="class">connected-<n:invisible n:render="string" n:data="connected_to_introducer" /></n:attr>
+    <div>Introducer: <span class="data-chars" n:render="string" n:data="introducer_furl" /></div>
+    <div>Connected to introducer?: <span n:render="string" n:data="connected_to_introducer" /></div>
+  </div>
+
+  <div>
+    <n:attr name="class">connected-<n:invisible n:render="string" n:data="connected_to_helper" /></n:attr>
+    <div>Helper: <span n:render="string" n:data="helper_furl" /></div>
+    <div>Connected to helper?: <span n:render="string" n:data="connected_to_helper" /></div>
+  </div>
+
+  <p>Connected to <span n:render="string" n:data="connected_storage_servers" />
+     of <span n:render="string" n:data="known_storage_servers" /> known storage servers:</p>
+
+  <div>
+    <table class="services table-headings-top" n:render="sequence" n:data="services">
+      <tr n:pattern="header">
+        <th>Service Name</th>
+        <th class="nickname-and-peerid">
+            <div class="service-nickname">Nickname</div>
+            <div class="nodeid data-chars">PeerID</div></th>
+        <th>Connected?</th>
+        <th>since</th>
+        <th>First Announced</th>
+        <th>Version</th>
+      </tr>
+      <tr n:pattern="item" n:render="service_row">
+        <td class="service-service-name"><n:slot name="service_name"/></td>
+        <td class="nickname-and-peerid">
+          <div class="nickname"><n:slot name="nickname"/></div>
+          <div class="nodeid data-chars"><n:slot name="peerid"/></div></td>
+        <td>   
+          <n:attr name="class">service-connected connected-<n:slot name="connected-bool"/></n:attr>
+          <n:slot name="connected"/>
+        </td>
+        <td class="service-since">       <n:slot name="since"/></td>
+        <td class="service-announced">   <n:slot name="announced"/></td>
+        <td class="service-version">     <n:slot name="version"/></td>
+      </tr>
+      <tr n:pattern="empty"><td>no peers!</td></tr>
+    </table>
+  </div>
 </div>
 
-<h2>Other Resources</h2>
+<div class="section" id="other-resources">
+  <h2>Other Resources</h2>
 
-<div>Please visit the <a href="http://allmydata.org">Tahoe home page</a> for
-code updates and bug reporting.</div>
+  <div>Please visit the <a href="http://allmydata.org">Tahoe home page</a> for
+  code updates and bug reporting.</div>
 
-<div>The <a href="provisioning">provisioning tool</a> and <a
-href="reliability">reliability calculator</a> may also be useful.</div>
+  <div>The <a href="provisioning">provisioning tool</a> and <a
+  href="reliability">reliability calculator</a> may also be useful.</div>
 
-<div n:render="incident_button" />
+  <div n:render="incident_button" />
+</div>
 
-  </body>
-</html>
+</body></html>