public attributes::
service_name: the type of service provided, like 'storage'
- announcement_time: when we first heard about this service
last_connect_time: when we last established a connection
last_loss_time: when we last lost a connection
the their version information. I remember information about when we were
last connected too, even if we aren't currently connected.
- @ivar announcement_time: when we first heard about this service
@ivar last_connect_time: when we last established a connection
@ivar last_loss_time: when we last lost a connection
self._long_description = tubid_s
self._short_description = tubid_s[:6]
- self.announcement_time = time.time()
self.last_connect_time = None
self.last_loss_time = None
self.remote_host = None
return self.last_connect_time
def get_last_loss_time(self):
return self.last_loss_time
- def get_announcement_time(self):
- return self.announcement_time
+ def get_last_received_data_time(self):
+ if self.rref is None:
+ return None
+ else:
+ return self.rref.getDataLastReceivedAt()
def get_available_space(self):
version = self.get_version()
def test_parse_date(self):
self.failUnlessEqual(time_format.parse_date("2010-02-21"), 1266710400)
+ def test_format_delta(self):
+ time_1 = 1389812723
+ time_5s_delta = 1389812728
+ time_28m7s_delta = 1389814410
+ time_1h_delta = 1389816323
+ time_1d21h46m49s_delta = 1389977532
+ TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
+ time_1_isostr = time.strftime(TIME_FORMAT, time.localtime(time_1))
+
+ self.failUnlessEqual(
+ time_format.format_delta(time_1, time_5s_delta),
+ (time_1_isostr, '5s'))
+ self.failUnlessEqual(
+ time_format.format_delta(time_1, time_28m7s_delta),
+ (time_1_isostr, '28m7s'))
+ self.failUnlessEqual(
+ time_format.format_delta(time_1, time_1h_delta),
+ (time_1_isostr, '1h0m0s'))
+ self.failUnlessEqual(
+ time_format.format_delta(time_1, time_1d21h46m49s_delta),
+ (time_1_isostr, '1d21h46m49s'))
+
+ # time_1 with a decimal fraction will make the delta 1s less
+ time_1decimal = 1389812723.383963
+
+ self.failUnlessEqual(
+ time_format.format_delta(time_1decimal, time_5s_delta),
+ (time_1_isostr, '4s'))
+ self.failUnlessEqual(
+ time_format.format_delta(time_1decimal, time_28m7s_delta),
+ (time_1_isostr, '28m6s'))
+ self.failUnlessEqual(
+ time_format.format_delta(time_1decimal, time_1h_delta),
+ (time_1_isostr, '59m59s'))
+ self.failUnlessEqual(
+ time_format.format_delta(time_1decimal, time_1d21h46m49s_delta),
+ (time_1_isostr, '1d21h46m48s'))
+
class CacheDir(unittest.TestCase):
def test_basic(self):
basedir = "test_util/CacheDir/test_basic"
return []
class FakeDisplayableServer(StubServer):
- def __init__(self, serverid, nickname):
+ def __init__(self, serverid, nickname, connected,
+ last_connect_time, last_lost_time, last_rx):
StubServer.__init__(self, serverid)
self.announcement = {"my-version": "allmydata-tahoe-fake",
"service-name": "storage",
"nickname": nickname}
+ self.connected = connected
+ self.last_lost_time = last_lost_time
+ self.last_rx = last_rx
+ self.last_connect_time = last_connect_time
def is_connected(self):
- return True
+ return self.connected
def get_permutation_seed(self):
return ""
def get_remote_host(self):
return ""
def get_last_loss_time(self):
- return None
- def get_announcement_time(self):
- return None
+ return self.last_lost_time
+ def get_last_received_data_time(self):
+ return self.last_rx
+ def get_last_connect_time(self):
+ return self.last_connect_time
def get_announcement(self):
return self.announcement
def get_nickname(self):
self.storage_broker = StorageFarmBroker(None, permute_peers=True)
# fake knowledge of another server
self.storage_broker.test_add_server("other_nodeid",
- FakeDisplayableServer("other_nodeid", u"other_nickname \u263B"))
+ FakeDisplayableServer(
+ serverid="other_nodeid", nickname=u"other_nickname \u263B", connected = True,
+ last_connect_time = 10, last_lost_time = 20, last_rx = 30))
+ self.storage_broker.test_add_server("disconnected_nodeid",
+ FakeDisplayableServer(
+ serverid="other_nodeid", nickname=u"disconnected_nickname \u263B", connected = False,
+ last_connect_time = 15, last_lost_time = 25, last_rx = 35))
self.introducer_client = None
self.history = FakeHistory()
self.uploader = FakeUploader()
self.s.startService()
self.staticdir = self.mktemp()
self.clock = Clock()
+ self.fakeTime = 86460 # 1d 0h 1m 0s
self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
- clock=self.clock)
+ clock=self.clock, now=lambda:self.fakeTime)
self.ws.setServiceParent(self.s)
self.webish_port = self.ws.getPortnum()
self.webish_url = self.ws.getURL()
class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
+
def test_create(self):
pass
res_u = res.decode('utf-8')
self.failUnlessIn(u'<td>fake_nickname \u263A</td>', res_u)
self.failUnlessIn(u'<div class="nickname">other_nickname \u263B</div>', res_u)
+ self.failUnlessIn('Connected to <span>1</span>\n of <span>2</span> known storage servers', res_u)
+ self.failUnlessIn('<div class="status-indicator service-Connected"></div>\n<div class="status-description">Connected<br /><a class="timestamp" title="00:00:10 01-Jan-1970">1d0h0m50s</a></div></td>', res_u)
+ self.failUnlessIn('<div class="status-indicator service-Disconnected"></div>\n<div class="status-description">Disconnected<br /><a class="timestamp" title="00:00:25 01-Jan-1970">1d0h0m35s</a></div></td>', res_u)
+ self.failUnlessIn('<td class="service-last-received-data"><a class="timestamp" title="00:00:30 01-Jan-1970">1d0h0m30s</a></td>', res)
+ self.failUnlessIn('<td class="service-last-received-data"><a class="timestamp" title="00:00:35 01-Jan-1970">1d0h0m25s</a></td>', res)
self.failUnlessIn(u'\u00A9 <a href="https://tahoe-lafs.org/">Tahoe-LAFS Software Foundation', res_u)
self.failUnlessIn('<td><h3>Available</h3></td>', res)
self.failUnlessIn('123.5kB', res)
# day
return int(iso_utc_time_to_seconds(s + "T00:00:00"))
+def format_delta(time_1, time_2):
+ TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
+ if time_1 is None:
+ absolute_str, relative_str = "N/A", "N/A"
+ else:
+ delta = int( time_2 - time_1 )
+ seconds = delta % 60
+ delta -= seconds
+ minutes = (delta / 60) % 60
+ delta -= minutes * 60
+ hours = delta / (60*60) % 24
+ delta -= hours * 24
+ days = delta / (24*60*60)
+ if not days:
+ if not hours:
+ if not minutes:
+ relative_str = "%ss" % (seconds)
+ else:
+ relative_str = "%sm%ss" % (minutes, seconds)
+ else:
+ relative_str = "%sh%sm%ss" % (hours, minutes, seconds)
+ else:
+ relative_str = "%sd%sh%sm%ss" % (days, hours, minutes, seconds)
+ absolute_str = time.strftime(TIME_FORMAT, time.localtime(time_1))
+ return absolute_str, relative_str
from allmydata.web import storage
from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
get_arg, RenderMixin, get_format, get_mutable_type, TIME_FORMAT
+from allmydata.util.time_format import format_delta
class URIHandler(RenderMixin, rend.Page):
addSlash = True
docFactory = getxmlfile("welcome.xhtml")
- def __init__(self, client, clock=None):
+ def __init__(self, client, clock=None, now=None):
rend.Page.__init__(self, client)
self.client = client
# If set, clock is a twisted.internet.task.Clock that the tests
# use to test ophandle expiration.
self.child_operations = operations.OphandleTable(clock)
+ self.now = now
+ if self.now is None:
+ self.now = time.time
try:
s = client.getServiceNamed("storage")
except KeyError:
ctx.fillSlots("peerid", server.get_longname())
ctx.fillSlots("nickname", server.get_nickname())
rhost = server.get_remote_host()
- if rhost:
+ if server.is_connected():
if nodeid == self.client.nodeid:
rhost_s = "(loopback)"
elif isinstance(rhost, address.IPv4Address):
else:
rhost_s = str(rhost)
addr = rhost_s
- connected = "yes"
- since = server.get_last_connect_time()
+ service_connection_status = "Connected"
+ service_connection_status_abs_time, service_connection_status_rel_time = format_delta(server.get_last_connect_time(), self.now())
else:
addr = "N/A"
- connected = "no"
- since = server.get_last_loss_time()
- announced = server.get_announcement_time()
+ service_connection_status = "Disconnected"
+ service_connection_status_abs_time, service_connection_status_rel_time = format_delta(server.get_last_loss_time(), self.now())
+
+ last_received_data_abs_time, last_received_data_rel_time = format_delta(server.get_last_received_data_time(), self.now())
+
announcement = server.get_announcement()
version = announcement["my-version"]
- service_name = announcement["service-name"]
available_space = server.get_available_space()
if available_space is None:
available_space = "N/A"
else:
available_space = abbreviate_size(available_space)
ctx.fillSlots("address", addr)
- ctx.fillSlots("connected", connected)
- ctx.fillSlots("connected-bool", bool(rhost))
- ctx.fillSlots("since", time.strftime(TIME_FORMAT,
- time.localtime(since)))
- ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
- time.localtime(announced)))
+ ctx.fillSlots("service_connection_status", service_connection_status)
+ ctx.fillSlots("service_connection_status_abs_time", service_connection_status_abs_time)
+ ctx.fillSlots("service_connection_status_rel_time", service_connection_status_rel_time)
+ ctx.fillSlots("last_received_data_abs_time", last_received_data_abs_time)
+ ctx.fillSlots("last_received_data_rel_time", last_received_data_rel_time)
ctx.fillSlots("version", version)
- ctx.fillSlots("service_name", service_name)
ctx.fillSlots("available_space", available_space)
return ctx.tag
.grid-status .status-indicator {
margin-top: 5px;
+ float: left;
}
.furl {
width: 50%;
}
+.service-connection-status {
+ white-space: nowrap;
+}
+
+.service-connection-string {
+ display: inline-block;
+}
+
.status-indicator {
- float: left;
+ display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
display: inline-block;
- vertical-align: middle;
+ vertical-align: top;
margin-right: 4px;
margin-top: 2px;
}
+.status-description {
+ display:inline-block;
+}
+
+.status-description .timestamp {
+ font-size: smaller;
+}
+
+a.timestamp {
+ color: inherit;
+ text-decoration:none;
+}
-.connected-yes {
+.connected-yes, .service-Connected {
background: #468847;
}
-.connected-no {
+.connected-no, .service-Disconnected {
background: #B94A48;
}
-<!DOCTYPE html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
+ Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en" xmlns:n="http://nevow.com/ns/nevow/0.1">
<head>
<meta charset="utf-8"/>
<table class="table table-striped table-bordered peer-status" n:render="sequence" n:data="services">
<thead>
<tr n:pattern="header">
+ <td><h3>Status</h3></td>
<td><h3>Nickname</h3></td>
<td><h3>Address</h3></td>
- <td><h3>Service</h3></td>
- <td><h3>Since</h3></td>
- <td><h3>Announced</h3></td>
+ <td><h3>Last RX</h3></td>
<td><h3>Version</h3></td>
<td><h3>Available</h3></td>
</tr>
</thead>
<tr n:pattern="item" n:render="service_row">
+ <td class="service-connection-status">
+ <div><n:attr name="class">status-indicator service-<n:slot name="service_connection_status"/></n:attr></div>
+<div class="status-description"><n:slot name="service_connection_status"/><br/><a class="timestamp"><n:attr name="title"><n:slot name="service_connection_status_abs_time"/></n:attr><n:slot name="service_connection_status_rel_time"/></a></div></td>
<td class="nickname-and-peerid">
- <div><n:attr name="class">status-indicator connected-<n:slot name="connected"/></n:attr></div>
<div class="nickname"><n:slot name="nickname"/></div>
<div class="nodeid"><n:slot name="peerid"/></div>
</td>
<td class="address"><n:slot name="address"/></td>
- <td class="service-service-name"><n:slot name="service_name"/></td>
- <td class="service-since timestamp"><n:slot name="since"/></td>
- <td class="service-announced timestamp"><n:slot name="announced"/></td>
+ <td class="service-last-received-data"><a class="timestamp"><n:attr name="title"><n:slot name="last_received_data_abs_time"/></n:attr><n:slot name="last_received_data_rel_time"/></a></td>
<td class="service-version"><n:slot name="version"/></td>
<td class="service-available-space"><n:slot name="available_space"/></td>
</tr>
- <tr n:pattern="empty"><td>You are not presently connected to any peers</td></tr>
+ <tr n:pattern="empty"><td colspan="6">You are not presently connected to any peers</td></tr>
</table>
</div><!--/span-->
</div><!--/row-->
name = "webish"
def __init__(self, client, webport, nodeurl_path=None, staticdir=None,
- clock=None):
+ clock=None, now=None):
service.MultiService.__init__(self)
# the 'data' argument to all render() methods default to the Client
# the 'clock' argument to root.Root is, if set, a
# twisted.internet.task.Clock that is provided by the unit tests
# so that they can test features that involve the passage of
# time in a deterministic manner.
- self.root = root.Root(client, clock)
+ self.root = root.Root(client, clock, now)
self.buildServer(webport, nodeurl_path, staticdir)
if self.root.child_operations:
self.site.remember(self.root.child_operations, IOpHandleTable)