forked from GithubBackups/healthchecks
Show the number of downtimes and total downtime minutes in "Check Details" page.
This commit is contained in:
parent
b7320b1b69
commit
b2ebce6cf9
@ -4,7 +4,8 @@ All notable changes to this project will be documented in this file.
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
- Show the number of outages and total downtime in monthly reports. (#104)
|
- Show the number of downtimes and total downtime minutes in monthly reports (#104)
|
||||||
|
- Show the number of downtimes and total downtime minutes in "Check Details" page
|
||||||
|
|
||||||
|
|
||||||
## 1.8.0 - 2019-07-08
|
## 1.8.0 - 2019-07-08
|
||||||
@ -13,8 +14,8 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Add the `prunetokenbucket` management command
|
- Add the `prunetokenbucket` management command
|
||||||
- Show check counts in JSON "badges" (#251)
|
- Show check counts in JSON "badges" (#251)
|
||||||
- Webhooks support HTTP PUT (#249)
|
- Webhooks support HTTP PUT (#249)
|
||||||
- Webhooks can use different req. bodies and headers for "up" and "down" events. (#249)
|
- Webhooks can use different req. bodies and headers for "up" and "down" events (#249)
|
||||||
- Show check's code instead of full URL on 992px - 1200px wide screens. (#253)
|
- Show check's code instead of full URL on 992px - 1200px wide screens (#253)
|
||||||
- Add WhatsApp integration (uses Twilio same as the SMS integration)
|
- Add WhatsApp integration (uses Twilio same as the SMS integration)
|
||||||
- Webhooks support the $TAGS placeholder
|
- Webhooks support the $TAGS placeholder
|
||||||
- Don't include ping URLs in API responses when the read-only key is used
|
- Don't include ping URLs in API responses when the read-only key is used
|
||||||
@ -59,7 +60,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
- Database schema: add uniqueness constraint to Check.code
|
- Database schema: add uniqueness constraint to Check.code
|
||||||
- Database schema: add Ping.kind field. Remove "start" and "fail" fields.
|
- Database schema: add Ping.kind field. Remove "start" and "fail" fields
|
||||||
- Add "Email Settings..." dialog and "Subject Must Contain" setting
|
- Add "Email Settings..." dialog and "Subject Must Contain" setting
|
||||||
- Database schema: add the Project model
|
- Database schema: add the Project model
|
||||||
- Move project-specific settings to a new "Project Settings" page
|
- Move project-specific settings to a new "Project Settings" page
|
||||||
|
@ -246,8 +246,8 @@ class Check(models.Model):
|
|||||||
ping.body = body[:10000]
|
ping.body = body[:10000]
|
||||||
ping.save()
|
ping.save()
|
||||||
|
|
||||||
def outages_by_month(self, months=2):
|
def downtimes(self, months=2):
|
||||||
""" Calculate the number of outages and downtime minutes per month.
|
""" Calculate the number of downtimes and downtime minutes per month.
|
||||||
|
|
||||||
Returns a list of (datetime, downtime_in_secs, number_of_outages) tuples.
|
Returns a list of (datetime, downtime_in_secs, number_of_outages) tuples.
|
||||||
|
|
||||||
|
@ -166,24 +166,24 @@ class CheckModelTestCase(BaseTestCase):
|
|||||||
d = check.to_dict()
|
d = check.to_dict()
|
||||||
self.assertEqual(d["next_ping"], "2000-01-01T01:00:00+00:00")
|
self.assertEqual(d["next_ping"], "2000-01-01T01:00:00+00:00")
|
||||||
|
|
||||||
def test_outages_by_month_handles_no_flips(self):
|
def test_downtimes_handles_no_flips(self):
|
||||||
check = Check.objects.create(project=self.project)
|
check = Check.objects.create(project=self.project)
|
||||||
r = check.outages_by_month(10)
|
r = check.downtimes(10)
|
||||||
self.assertEqual(len(r), 10)
|
self.assertEqual(len(r), 10)
|
||||||
for dt, downtime, outages in r:
|
for dt, downtime, outages in r:
|
||||||
self.assertEqual(downtime.total_seconds(), 0)
|
self.assertEqual(downtime.total_seconds(), 0)
|
||||||
self.assertEqual(outages, 0)
|
self.assertEqual(outages, 0)
|
||||||
|
|
||||||
def test_outages_by_month_handles_currently_down_check(self):
|
def test_downtimes_handles_currently_down_check(self):
|
||||||
check = Check.objects.create(project=self.project, status="down")
|
check = Check.objects.create(project=self.project, status="down")
|
||||||
|
|
||||||
r = check.outages_by_month(10)
|
r = check.downtimes(10)
|
||||||
self.assertEqual(len(r), 10)
|
self.assertEqual(len(r), 10)
|
||||||
for dt, downtime, outages in r:
|
for dt, downtime, outages in r:
|
||||||
self.assertEqual(outages, 1)
|
self.assertEqual(outages, 1)
|
||||||
|
|
||||||
@patch("hc.api.models.timezone.now")
|
@patch("hc.api.models.timezone.now")
|
||||||
def test_outages_by_month_handles_flip_one_day_ago(self, mock_now):
|
def test_downtimes_handles_flip_one_day_ago(self, mock_now):
|
||||||
mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc)
|
mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc)
|
||||||
|
|
||||||
check = Check.objects.create(project=self.project, status="down")
|
check = Check.objects.create(project=self.project, status="down")
|
||||||
@ -193,7 +193,7 @@ class CheckModelTestCase(BaseTestCase):
|
|||||||
flip.new_status = "down"
|
flip.new_status = "down"
|
||||||
flip.save()
|
flip.save()
|
||||||
|
|
||||||
r = check.outages_by_month(10)
|
r = check.downtimes(10)
|
||||||
self.assertEqual(len(r), 10)
|
self.assertEqual(len(r), 10)
|
||||||
for dt, downtime, outages in r:
|
for dt, downtime, outages in r:
|
||||||
if dt.month == 7:
|
if dt.month == 7:
|
||||||
@ -204,7 +204,7 @@ class CheckModelTestCase(BaseTestCase):
|
|||||||
self.assertEqual(outages, 0)
|
self.assertEqual(outages, 0)
|
||||||
|
|
||||||
@patch("hc.api.models.timezone.now")
|
@patch("hc.api.models.timezone.now")
|
||||||
def test_outages_by_month_handles_flip_two_months_ago(self, mock_now):
|
def test_downtimes_handles_flip_two_months_ago(self, mock_now):
|
||||||
mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc)
|
mock_now.return_value = datetime(2019, 7, 19, tzinfo=timezone.utc)
|
||||||
|
|
||||||
check = Check.objects.create(project=self.project, status="down")
|
check = Check.objects.create(project=self.project, status="down")
|
||||||
@ -214,7 +214,7 @@ class CheckModelTestCase(BaseTestCase):
|
|||||||
flip.new_status = "down"
|
flip.new_status = "down"
|
||||||
flip.save()
|
flip.save()
|
||||||
|
|
||||||
r = check.outages_by_month(10)
|
r = check.downtimes(10)
|
||||||
self.assertEqual(len(r), 10)
|
self.assertEqual(len(r), 10)
|
||||||
for dt, downtime, outages in r:
|
for dt, downtime, outages in r:
|
||||||
if dt.month == 7:
|
if dt.month == 7:
|
||||||
|
@ -58,6 +58,7 @@ VALID_SORT_VALUES = ("name", "-name", "last_ping", "-last_ping", "created")
|
|||||||
STATUS_TEXT_TMPL = get_template("front/log_status_text.html")
|
STATUS_TEXT_TMPL = get_template("front/log_status_text.html")
|
||||||
LAST_PING_TMPL = get_template("front/last_ping_cell.html")
|
LAST_PING_TMPL = get_template("front/last_ping_cell.html")
|
||||||
EVENTS_TMPL = get_template("front/details_events.html")
|
EVENTS_TMPL = get_template("front/details_events.html")
|
||||||
|
DOWNTIMES_TMPL = get_template("front/details_downtimes.html")
|
||||||
ONE_HOUR = td(hours=1)
|
ONE_HOUR = td(hours=1)
|
||||||
TWELVE_HOURS = td(hours=12)
|
TWELVE_HOURS = td(hours=12)
|
||||||
|
|
||||||
@ -474,6 +475,7 @@ def details(request, code):
|
|||||||
"check": check,
|
"check": check,
|
||||||
"channels": channels,
|
"channels": channels,
|
||||||
"timezones": pytz.all_timezones,
|
"timezones": pytz.all_timezones,
|
||||||
|
"downtimes": check.downtimes(months=3),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "front/details.html", ctx)
|
return render(request, "front/details.html", ctx)
|
||||||
@ -523,6 +525,7 @@ def status_single(request, code):
|
|||||||
|
|
||||||
if updated != request.GET.get("u"):
|
if updated != request.GET.get("u"):
|
||||||
doc["events"] = EVENTS_TMPL.render({"check": check, "events": events})
|
doc["events"] = EVENTS_TMPL.render({"check": check, "events": events})
|
||||||
|
doc["downtimes"] = DOWNTIMES_TMPL.render({"downtimes": check.downtimes(3)})
|
||||||
|
|
||||||
return JsonResponse(doc)
|
return JsonResponse(doc)
|
||||||
|
|
||||||
|
@ -80,3 +80,18 @@
|
|||||||
color: #d43f3a;
|
color: #d43f3a;
|
||||||
background: #FFF;
|
background: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#downtimes table {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#downtimes tr:first-child td, #downtimes tr:first-child th {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#downtimes th {
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
@ -66,6 +66,10 @@ $(function () {
|
|||||||
switchDateFormat(lastFormat);
|
switchDateFormat(lastFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.downtimes) {
|
||||||
|
$("#downtimes").html(data.downtimes);
|
||||||
|
}
|
||||||
|
|
||||||
if (document.title != data.title) {
|
if (document.title != data.title) {
|
||||||
document.title = data.title;
|
document.title = data.title;
|
||||||
}
|
}
|
||||||
|
@ -62,10 +62,10 @@
|
|||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% for boundary, seconds, count in check.outages_by_month %}
|
{% for boundary, seconds, count in check.downtimes %}
|
||||||
{% if count %}
|
{% if count %}
|
||||||
<td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif;">
|
<td style="border-top: 1px solid #EDEFF2; padding: 16px 8px; font-family: Helvetica, Arial, sans-serif;">
|
||||||
{{ count }} outage{{ count|pluralize }},
|
{{ count }} downtime{{ count|pluralize }},
|
||||||
<br />
|
<br />
|
||||||
{{ seconds|hc_approx_duration }} total
|
{{ seconds|hc_approx_duration }} total
|
||||||
</td>
|
</td>
|
||||||
|
@ -87,8 +87,14 @@
|
|||||||
<td>
|
<td>
|
||||||
<span id="log-status-icon" class="status icon-{{ check.get_status }}"></span>
|
<span id="log-status-icon" class="status icon-{{ check.get_status }}"></span>
|
||||||
</td>
|
</td>
|
||||||
<td id="log-status-text">
|
<td >
|
||||||
{% include "front/log_status_text.html" %}
|
<p id="log-status-text">{% include "front/log_status_text.html" %}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td id="downtimes">
|
||||||
|
{% include "front/details_downtimes.html" %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@ -196,7 +202,6 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="events" class="col-sm-7">
|
<div id="events" class="col-sm-7">
|
||||||
<h2>
|
<h2>
|
||||||
Log
|
Log
|
||||||
|
16
templates/front/details_downtimes.html
Normal file
16
templates/front/details_downtimes.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% load hc_extras %}
|
||||||
|
<table class="table">
|
||||||
|
{% for boundary, seconds, count in downtimes reversed %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ boundary|date:"N Y"}}</th>
|
||||||
|
<td>
|
||||||
|
{% if count %}
|
||||||
|
{{ count }} downtime{{ count|pluralize }},
|
||||||
|
{{ seconds|hc_approx_duration }} total
|
||||||
|
{% else %}
|
||||||
|
All good!
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
Loading…
x
Reference in New Issue
Block a user