Skip to content

Commit 18a554a

Browse files
committed
rfeat: add csl status to header
and rework method of scraping the TJCSL status page
1 parent eb3cbd9 commit 18a554a

File tree

7 files changed

+180
-46
lines changed

7 files changed

+180
-46
lines changed

docs/sourcedoc/intranet.apps.templatetags.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ intranet.apps.templatetags.paginate module
6060
:undoc-members:
6161
:show-inheritance:
6262

63+
intranet.apps.templatetags.status\_helper module
64+
------------------------------------------------
65+
66+
.. automodule:: intranet.apps.templatetags.status_helper
67+
:members:
68+
:undoc-members:
69+
:show-inheritance:
70+
6371
intranet.apps.templatetags.strings module
6472
-----------------------------------------
6573

intranet/apps/emerg/views.py

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import json
21
import logging
32
import time
3+
from typing import Tuple
44

55
import requests
66
from bs4 import BeautifulSoup
@@ -14,20 +14,19 @@
1414

1515

1616
def check_emerg():
17-
"""Fetch from FCPS and CSL emergency announcement pages.
17+
"""Fetch from FCPS emergency announcement pages.
1818
19-
URLs defined in settings.FCPS_EMERGENCY_PAGE and settings.CSL_STATUS_PAGE
19+
URLs defined in settings.FCPS_EMERGENCY_PAGE
2020
2121
Request timeout defined in settings.FCPS_EMERGENCY_TIMEOUT
2222
2323
"""
2424
fcps_page = settings.FCPS_EMERGENCY_PAGE
25-
csl_page = settings.CSL_STATUS_PAGE
2625
announcements = []
2726

2827
if settings.EMERGENCY_MESSAGE:
2928
return True, settings.EMERGENCY_MESSAGE
30-
if not fcps_page or not csl_page:
29+
if not fcps_page:
3130
return None, None
3231

3332
timeout = settings.EMERGENCY_TIMEOUT
@@ -67,44 +66,10 @@ def check_emerg():
6766

6867
announcements.append({"title": f"<a target='_blank' href=\"{get_domain_name(fcps_page)}\">{title}</a>", "body": body})
6968

70-
try:
71-
r = requests.get(csl_page, timeout=timeout)
72-
except requests.exceptions.Timeout:
73-
pass
74-
75-
try:
76-
csl_status = json.loads(r.text)
77-
except json.decoder.JSONDecodeError:
78-
return False, None
79-
80-
for system in csl_status["systems"]:
81-
if system["status"] != "ok":
82-
status = True
83-
issues = system["unresolvedIssues"]
84-
for issue in issues:
85-
desc = requests.get(issue["permalink"], timeout=timeout).text
86-
soup = BeautifulSoup(desc, "html.parser")
87-
88-
text = soup.find_all(["p", "hr"])
89-
desc = text[2 : len(text) - 5]
90-
a = {
91-
"title": f"<a target='_blank' href=\"{get_domain_name(csl_page)}\">{issue['title']}</a>",
92-
"body": "".join(d.prettify() for d in desc),
93-
}
94-
if a not in announcements and issue["severity"] != "notice":
95-
announcements.append(a)
96-
97-
# Not needed due to the filtering of "p" elements, but as a backup:
98-
bad_text = [
99-
'<p><strong class="bold">© tjCSL Status, 2022</strong>&nbsp; • &nbsp; <a href="#">Back to top</a></p>',
100-
"<p>We continuously monitor the status of our services and if there are any interruptions, a note will be posted here.</p>",
101-
]
102-
10369
message = "".join(
10470
[
10571
f"<h3><i class='fas fa-exclamation-triangle'></i>&nbsp; {announcement['title']}</h3><hr />{announcement['body']}\n"
10672
for announcement in announcements
107-
if announcement not in bad_text
10873
]
10974
)
11075

@@ -117,12 +82,12 @@ def get_emerg_result(*, custom_logger=None):
11782
custom_logger = logger
11883

11984
status, message = check_emerg()
120-
custom_logger.debug("Fetched emergency info from FCPS and CSL status")
85+
custom_logger.debug("Fetched emergency info from FCPS")
12186
return {"status": status, "message": message}
12287

12388

12489
def get_emerg():
125-
"""Get the cached FCPS emergency page and CSL status page, or check it again.
90+
"""Get the cached FCPS emergency page, or check it again.
12691
12792
Timeout defined in settings.CACHE_AGE["emerg"]
12893
@@ -155,3 +120,36 @@ def update_emerg_cache(*, custom_logger=None) -> None:
155120
key = f"emerg:{timezone.localdate()}"
156121
result = get_emerg_result(custom_logger=custom_logger)
157122
cache.set(key, result, timeout=settings.CACHE_AGE["emerg"])
123+
124+
125+
def get_csl_status() -> Tuple[str, bool]:
126+
"""Get the cached status of the TJCSL status page.
127+
128+
Returns:
129+
Tuple with a string consisting of the aggregate status
130+
of the TJ computer systems lab and a bool indicating whether
131+
the status cache was updated
132+
133+
The string of the tuple will be one of the following: "error" (parse error), "operational", "downtime", "degraded", "maintenance"
134+
"""
135+
136+
status = cache.get("emerg:csl_status")
137+
updated = False
138+
139+
if not status:
140+
response = requests.get(settings.CSL_STATUS_PAGE)
141+
if response.status_code != 200:
142+
status = "error"
143+
logger.error("Could not fetch status page")
144+
145+
else:
146+
try:
147+
status = response.json()["data"]["attributes"]["aggregate_state"]
148+
updated = True
149+
except KeyError as e:
150+
status = "error"
151+
logger.error("Unexpected status page JSON format. %s", e)
152+
153+
cache.set("emerg:csl_status", status, settings.CACHE_AGE["csl_status"])
154+
155+
return status, updated
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from urllib.parse import urlparse, urlunparse
2+
3+
from django import template
4+
from django.core.cache import cache
5+
6+
from intranet import settings
7+
from intranet.apps.emerg.views import get_csl_status
8+
9+
register = template.Library()
10+
11+
12+
@register.simple_tag
13+
def get_cache(key):
14+
return cache.get(key)
15+
16+
17+
class GetCSLStatusNode(template.Node):
18+
def render(self, context):
19+
context["csl_status"] = get_csl_status()[0]
20+
return ""
21+
22+
23+
@register.tag
24+
def get_csl_status_from_cache(parser, token):
25+
tokens = token.contents.split()
26+
if len(tokens) == 1:
27+
return GetCSLStatusNode()
28+
else:
29+
raise template.TemplateSyntaxError("Usage: {% get_csl_status_from_cache %} {{ csl_status }}")
30+
31+
32+
@register.simple_tag
33+
def get_csl_status_page_url():
34+
parsed_url = urlparse(settings.CSL_STATUS_PAGE)
35+
36+
return urlunparse((parsed_url.scheme, parsed_url.netloc, "", "", "", ""))

intranet/settings/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ def get_month_seconds():
527527
"users_list": int(datetime.timedelta(hours=24).total_seconds()),
528528
"printers_list": int(datetime.timedelta(minutes=10).total_seconds()),
529529
"emerg": int(datetime.timedelta(minutes=5).total_seconds()),
530+
"csl_status": int(datetime.timedelta(minutes=5).total_seconds()),
530531
"sports_school_events": int(datetime.timedelta(hours=1).total_seconds()),
531532
}
532533

@@ -882,11 +883,12 @@ def get_log(name): # pylint: disable=redefined-outer-name; 'name' is used as th
882883
# The address for FCPS' Emergency Announcement page
883884
FCPS_EMERGENCY_PAGE = "https://www.fcps.edu/alert_msg_feed" # type: str
884885

886+
# The address for the CSL's BetterUptime status page
887+
CSL_STATUS_PAGE = "https://status.tjhsst.edu/index.json"
888+
885889
# The timeout for the request to FCPS' emergency page (in seconds)
886890
EMERGENCY_TIMEOUT = 5
887891

888-
CSL_STATUS_PAGE = "https://status.tjhsst.edu/index.json"
889-
890892
# How frequently the emergency announcement cache should be updated by the Celerybeat task.
891893
# This should be less than CACHE_AGE["emerg"].
892894
FCPS_EMERGENCY_CACHE_UPDATE_INTERVAL = CACHE_AGE["emerg"] - 30

intranet/static/css/page_base.scss

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,32 @@ h1 {
211211
}
212212
}
213213

214+
.status-link {
215+
align-items: center;
216+
border-radius: 4px;
217+
flex-shrink: 1;
218+
height: 30px;
219+
text-decoration: none;
220+
display: inline-flex;
221+
vertical-align: middle;
222+
margin-bottom: 3px;
223+
margin-right: 10px;
224+
color: white;
225+
}
226+
227+
.status-link:hover {
228+
background-color: rgba(120, 211, 246, 0.4);
229+
}
230+
231+
.status-icon {
232+
margin-right: 2px;
233+
display: inline-block;
234+
line-height: 0;
235+
text-align: center;
236+
width: 22px;
237+
height: 22px;
238+
}
239+
214240
.header .username {
215241
margin-right: 10px;
216242
}

intranet/static/css/responsive.core.scss

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ body.disable-scroll {
1010
overflow: hidden;
1111
}
1212

13-
@media (max-width: 700px) {
13+
@media (max-width: 800px) {
1414
.header .search input[type="text"] {
1515
width: 158px;
1616
font-size: 12px;
1717
padding-right: 3px;
1818
}
1919
}
2020

21-
@media (max-width: 550px) {
21+
@media (max-width: 680px) {
2222
/* absence notification */
2323
ul.dropdown-menu.absence-notification {
2424
position: fixed;
@@ -34,11 +34,27 @@ body.disable-scroll {
3434
.badged-item {
3535
position: absolute !important;
3636
top: 9px;
37+
left: 95px;
38+
}
39+
40+
.status-link .user-name {
41+
display: none;
42+
}
43+
44+
.status-link {
45+
position: absolute !important;
46+
top: 4px;
3747
left: 60px;
3848
}
3949
}
4050

41-
@media (max-width: 550px) {
51+
@media (max-width: 960px) {
52+
.status-link .user-name {
53+
display: none;
54+
}
55+
}
56+
57+
@media (max-width: 680px) {
4258
/* FIXED HEADER */
4359
h1 {
4460
margin-right: 0;

intranet/templates/page_with_header.html

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{% extends "page_base.html" %}
22
{% load static %}
33
{% load pipeline %}
4+
{% load status_helper %}
45

56
{% block css %}
67
{{ block.super }}
@@ -84,6 +85,53 @@ <h1>Ion</h1>
8485
</li>
8586
{% endif %}
8687

88+
{% get_csl_status_from_cache %}
89+
<a class="status-link" href="{% get_csl_status_page_url %}" target="_blank">
90+
<div class="status-icon">
91+
{% if csl_status == "operational" %}
92+
<svg xmlns="http://www.w3.org/2000/svg" class="status-circle" width="22" height="22" viewBox="0 0 18 18" fill="none">
93+
<circle cx="9" cy="9" r="9" fill="currentColor" fill-opacity="0.2"></circle>
94+
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 15C10.5913 15 12.1174 14.3679 13.2426 13.2426C14.3679 12.1174 15 10.5913 15 9C15 7.4087 14.3679 5.88258 13.2426 4.75736C12.1174 3.63214 10.5913 3 9 3C7.4087 3 5.88258 3.63214 4.75736 4.75736C3.63214 5.88258 3 7.4087 3 9C3 10.5913 3.63214 12.1174 4.75736 13.2426C5.88258 14.3679 7.4087 15 9 15ZM11.8245 7.50675C11.8779 7.45573 11.9208 7.39469 11.9506 7.32711C11.9805 7.25952 11.9967 7.18673 11.9984 7.11287C12.0001 7.03901 11.9873 6.96554 11.9606 6.89665C11.9339 6.82776 11.8939 6.76481 11.8429 6.71137C11.7919 6.65794 11.7308 6.61508 11.6632 6.58524C11.5956 6.5554 11.5229 6.53917 11.449 6.53746C11.3751 6.53575 11.3017 6.54861 11.2328 6.5753C11.1639 6.60199 11.1009 6.64198 11.0475 6.693C9.91968 7.77061 8.9301 8.98415 8.1015 10.3058L6.96 9.165C6.9085 9.10974 6.8464 9.06541 6.7774 9.03466C6.7084 9.00392 6.63392 8.98739 6.55839 8.98606C6.48286 8.98472 6.40784 8.99862 6.3378 9.02691C6.26776 9.0552 6.20414 9.09731 6.15072 9.15072C6.09731 9.20414 6.0552 9.26776 6.02691 9.3378C5.99862 9.40784 5.98472 9.48286 5.98606 9.55839C5.98739 9.63392 6.00392 9.7084 6.03466 9.7774C6.06541 9.8464 6.10973 9.9085 6.165 9.96L7.815 11.6108C7.8761 11.6719 7.95045 11.7181 8.0323 11.7458C8.11415 11.7736 8.2013 11.7821 8.28696 11.7707C8.37263 11.7593 8.45452 11.7283 8.52629 11.6802C8.59805 11.632 8.65775 11.568 8.70075 11.493C9.54556 10.0214 10.5976 8.67891 11.8245 7.50675Z" fill="currentColor"></path>
95+
</svg>
96+
{% elif csl_status == "downtime" %}
97+
<svg xmlns="http://www.w3.org/2000/svg" id="root" class="text-statuspage-red inline-block" width="22" height="22" viewBox="0 0 18 18" fill="none">
98+
<circle cx="9" cy="9" r="9" fill="currentColor" fill-opacity="0.2"></circle>
99+
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 9C15 10.5913 14.3679 12.1174 13.2426 13.2426C12.1174 14.3679 10.5913 15 9 15C7.4087 15 5.88258 14.3679 4.75736 13.2426C3.63214 12.1174 3 10.5913 3 9C3 7.4087 3.63214 5.88258 4.75736 4.75736C5.88258 3.63214 7.4087 3 9 3C10.5913 3 12.1174 3.63214 13.2426 4.75736C14.3679 5.88258 15 7.4087 15 9ZM9.75 12C9.75 12.1989 9.67098 12.3897 9.53033 12.5303C9.38968 12.671 9.19891 12.75 9 12.75C8.80109 12.75 8.61032 12.671 8.46967 12.5303C8.32902 12.3897 8.25 12.1989 8.25 12C8.25 11.8011 8.32902 11.6103 8.46967 11.4697C8.61032 11.329 8.80109 11.25 9 11.25C9.19891 11.25 9.38968 11.329 9.53033 11.4697C9.67098 11.6103 9.75 11.8011 9.75 12ZM9 5.25C8.80109 5.25 8.61032 5.32902 8.46967 5.46967C8.32902 5.61032 8.25 5.80109 8.25 6V9C8.25 9.19891 8.32902 9.38968 8.46967 9.53033C8.61032 9.67098 8.80109 9.75 9 9.75C9.19891 9.75 9.38968 9.67098 9.53033 9.53033C9.67098 9.38968 9.75 9.19891 9.75 9V6C9.75 5.80109 9.67098 5.61032 9.53033 5.46967C9.38968 5.32902 9.19891 5.25 9 5.25Z" fill="currentColor"></path>
100+
</svg>
101+
{% elif csl_status == "degraded" %}
102+
<svg xmlns="http://www.w3.org/2000/svg" id="root" class="text-statuspage-yellow inline-block" width="22" height="22" viewBox="0 0 18 18" fill="none">
103+
<circle cx="9" cy="9" r="9" fill="currentColor" fill-opacity="0.2"></circle>
104+
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 9C15 10.5913 14.3679 12.1174 13.2426 13.2426C12.1174 14.3679 10.5913 15 9 15C7.4087 15 5.88258 14.3679 4.75736 13.2426C3.63214 12.1174 3 10.5913 3 9C3 7.4087 3.63214 5.88258 4.75736 4.75736C5.88258 3.63214 7.4087 3 9 3C10.5913 3 12.1174 3.63214 13.2426 4.75736C14.3679 5.88258 15 7.4087 15 9ZM9.75 12C9.75 12.1989 9.67098 12.3897 9.53033 12.5303C9.38968 12.671 9.19891 12.75 9 12.75C8.80109 12.75 8.61032 12.671 8.46967 12.5303C8.32902 12.3897 8.25 12.1989 8.25 12C8.25 11.8011 8.32902 11.6103 8.46967 11.4697C8.61032 11.329 8.80109 11.25 9 11.25C9.19891 11.25 9.38968 11.329 9.53033 11.4697C9.67098 11.6103 9.75 11.8011 9.75 12ZM9 5.25C8.80109 5.25 8.61032 5.32902 8.46967 5.46967C8.32902 5.61032 8.25 5.80109 8.25 6V9C8.25 9.19891 8.32902 9.38968 8.46967 9.53033C8.61032 9.67098 8.80109 9.75 9 9.75C9.19891 9.75 9.38968 9.67098 9.53033 9.53033C9.67098 9.38968 9.75 9.19891 9.75 9V6C9.75 5.80109 9.67098 5.61032 9.53033 5.46967C9.38968 5.32902 9.19891 5.25 9 5.25Z" fill="currentColor"></path>
105+
</svg>
106+
{% elif csl_status == "maintenance" %}
107+
<svg xmlns="http://www.w3.org/2000/svg" id="root" class="text-statuspage-blue inline-block" width="22" height="22" viewBox="0 0 18 18" fill="none">
108+
<circle cx="9" cy="9" r="9" fill="currentColor" fill-opacity="0.2"></circle>
109+
<mask id="icon">
110+
<circle cx="9" cy="9" r="6" fill="white"></circle>
111+
<path d="M6.96375 8.09363C7.05752 8.18739 7.1847 8.24007 7.3173 8.24007H7.74005C8.0162 8.24007 8.24005 8.01621 8.24005 7.74007V7.31732C8.24005 7.18471 8.18738 7.05754 8.09361 6.96377L7.42189 6.29205C7.17787 6.04803 7.25484 5.63692 7.5969 5.59128C7.8219 5.56126 8.05148 5.56513 8.27787 5.60397C8.73843 5.68299 9.16317 5.90286 9.4936 6.23328C9.82403 6.56371 10.0439 6.98846 10.1229 7.44902C10.1819 7.79284 10.1602 8.144 10.0617 8.47536C10.0013 8.67846 10.035 8.90517 10.1848 9.055L12.1946 11.0647C12.3444 11.2145 12.4286 11.4178 12.4286 11.6296C12.4286 11.8415 12.3444 12.0447 12.1946 12.1946C12.0447 12.3444 11.8415 12.4286 11.6296 12.4286C11.4177 12.4286 11.2145 12.3444 11.0647 12.1946L9.05498 10.1849C8.90515 10.035 8.67844 10.0013 8.47535 10.0617C8.14399 10.1603 7.79282 10.1819 7.449 10.1229C6.98844 10.0439 6.5637 9.82405 6.23327 9.49362C5.90284 9.16319 5.68298 8.73845 5.60396 8.27788C5.56511 8.05149 5.56124 7.82192 5.59126 7.59692C5.6369 7.25485 6.04801 7.17789 6.29203 7.42191L6.96375 8.09363Z" fill="black"></path>
112+
</mask>
113+
<circle cx="9" cy="9" r="6" fill="currentColor" mask="url(#icon)"></circle>
114+
</svg>
115+
{% else %}
116+
<circle cx="9" cy="9" r="9" fill="currentColor" fill-opacity="0.2"></circle>
117+
<i class="fa fa-question-circle" style="margin-top: 0.281rem;"></i>
118+
{% endif %}
119+
</div>
120+
<div class="user-name">
121+
{% if csl_status == "operational" %}
122+
All services operational
123+
{% elif csl_status == "downtime" %}
124+
Some services are down
125+
{% elif csl_status == "degraded" %}
126+
Some services are degraded
127+
{% elif csl_status == "maintenance" %}
128+
Services are undergoing maintenance
129+
{% else %}
130+
View status
131+
{% endif %}
132+
</div>
133+
</a>
134+
87135
{% if csl_apps|length > 0 %}
88136
<li class="csl-apps has-dropdown">
89137
<div class="dropdown-item-wrapper">

0 commit comments

Comments
 (0)