PageSourceSearch

https://cdn.mst.edu/emctest/itstatus/itstatus.js

js mst.edu collected 2026-09-24 08:32:40 UTC 9,277 bytes, 318 lines download raw bytes

1(async ()=>{
2	// Simple, self-contained IT Status bar script
3	// for v3 and v4 main templates
4
5	let status = false;
6
7	// Define the colors for the status indicators
8	const colors = {
9		none: "#72BF44",
10		maintenance: "#2DCCD3",
11		minor: "#FDD923",
12		major: "#E77724",
13		critical: "#AD2327"
14	};
15
16	// Define the status messages
17	const messages = {
18		none: "Operational",
19		maintenance: "Maintenance",
20		minor: "Degraded",
21		major: "Partial outage",
22		critical: "Major outage"
23	};
24
25	// Define the icons
26	const icons = {
27		none: "fa-check",
28		maintenance: "fa-wrench",
29		minor: "fa-exclamation-triangle",
30		major: "fa-circle-xmark",
31		critical: "fa-square-minus"
32	};
33
34	/**
35	 * Fetch the status from StatusPage
36	 * @async
37	 * @returns {object|boolean} The status object, or false if fetch failed, invalid response, or unhandled status code
38	 */
39	async function fetchStatus() {
40		let status = await fetch("https://status.mst.edu/api/v2/status.json");
41
42		// If the fetch failed, return false
43		if (!status) {
44			console.log("IT Status Bar: Failed to fetch from StatusPage.");
45			return false;
46		}
47
48		// If the fetch succeeded, but the response was invalid, return false
49		try {
50			status = await status.json();
51		} catch (e) {
52			console.log("IT Status Bar: Received invalid response from StatusPage.");
53			return false;
54		}
55
56		// If the status code isn't handled by this script, return false
57		if (!status.status.indicator.match(/none|maintenance|minor|major|critical/)) {
58			console.log("IT Status Bar: " + status.status.indicator + " is not a handled status code.");
59			return false;
60		}
61		
62		return status;
63	}
64
65	/**
66	 * Retrieves the firstChild DOM node of a container DOM node, skipping whitespace
67	 * @param {Element} el the container element
68	 * @returns {Element} the firstChild
69	 */
70	function getFirstChild(el) {
71		let firstChild = el.firstChild;
72		while(firstChild != null && firstChild.nodeType === 3) {
73			firstChild = firstChild.nextSibling;
74		}
75		return firstChild;
76	}
77
78	/**
79	 * Determine via cookies if this particular status update has been dismissed
80	 * @returns {boolean} true if this status update has been dismissed; false if new update or not yet dismissed
81	 */
82	function isBarDismissed() {
83		let cookies = document.cookie.split("; ");
84		let lastUpdate = new Date(status.page.updated_at);
85		let dismissedUpdate = false;
86		for (let cookie of cookies) {
87			cookie = cookie.split("=");
88			if (cookie[0] === "it-status-hide-update") dismissedUpdate = new Date(parseInt(cookie[1]));
89		}
90
91		// If the it-status-hide-update cookie is set, then the user has dismissed an update
92		// Is it this update?
93		if (dismissedUpdate) {
94			if (dismissedUpdate.getTime() === lastUpdate.getTime()) {
95				// The user did dismiss this particular update. Don't continue to bar creation.
96				return true;
97			} else {
98				// This is a new message the user hasn't seen yet. Delete the cookie.
99				document.cookie = "it-status-hide-update=; Max-Age=0; domain=mst.edu";
100				return false;
101			}
102		}
103
104		return false;
105	}
106
107	/**
108	 * Determine if the status bar is required
109	 * @uses isBarDismissed()
110	 * @returns {boolean} true if the status bar is required; false if dismissed or status is "none" or "minor"
111	 */
112	function isBarRequired() {
113		// If the bar has been dismissed, don't continue
114		if (isBarDismissed()) return false;
115
116		// If the status is "none", "minor", or "maintenance" don't continue
117		if (!!status.status.indicator.match(/none|minor|maintenance/)) return false;
118
119		return true;
120	}
121
122	/**
123	 * Builds the footer link DOM element
124	 */
125	function buildLink() {
126		// Is there already a link? If so, delete it
127		let existingLink = document.querySelector("#it-status-link");
128		if (existingLink) existingLink.parentNode.removeChild(existingLink);
129		
130		// Determine where to inject the status link
131		let v3LinkAfterSelector = document.querySelector("footer .footer_legal_row > .column:nth-child(2) .column:nth-child(2) ul li:last-child");
132		let v4LinkAfterSelector = document.querySelector("#footer #footer__quick-links-4 ul li:first-child");
133		let linkAfter = v3LinkAfterSelector ? v3LinkAfterSelector : v4LinkAfterSelector;
134		if (!linkAfter) {
135			console.log("IT Status Bar: Incompatible template. Cannot insert footer link.");
136			return;
137		};
138
139		// Create the link
140		let link = document.createElement("li");
141		link.id = "it-status-link";
142		link.innerHTML = `<a href='https://status.mst.edu' target='_blank'>IT Systems: ${ messages[status.status.indicator] }</a>&ensp;<i class="fa-solid ${ icons[status.status.indicator] }" style="color: ${ colors[status.status.indicator] };"></i>`;
143
144		// Inject the link
145		linkAfter.parentNode.insertBefore(link, linkAfter.nextSibling);
146	}
147
148	/**
149	 * Builds the status bar DOM element
150	 */
151	function buildBar() {
152		// Is there already a bar? If so, delete it
153		let existingBar = document.querySelector("#it-status-bar");
154		if (existingBar) existingBar.parentNode.removeChild(existingBar);
155
156		// Parse the time values
157		let lastUpdate = new Date(status.page.updated_at);
158		let lastUpdatePretty = new Intl.DateTimeFormat("en-US", { weekday: "short", month: "short", day: "
158numeric", hour: "numeric", minute: "numeric", hour12: true, timeZone: "America/Chicago" }).format(lastUpdate);
159
160		// Create the bar
161		let barTemplate = document.createElement("div");
162		barTemplate.innerHTML = `
163			<div
164				id="it-status-bar"
165				aria-label="IT status bar"
166				role="status"
167				style="
168					position: fixed;
169					box-sizing: border-box;
170					bottom: 0px;
171					left: 0px;
172					width: 100%;
173					background-color: rgb(255, 255, 255);
174					border-top: 5px solid rgb(0, 122, 51);
175					z-index: 99;
176					display: flex;
177					align-items: center;
178					justify-content: center;
179					font-family: &quot;Libre Franklin&quot;, sans-serif;
180					font-size: 1rem;
181					color: rgb(0, 0, 0);
182					text-align: left;
183					line-height: 1.5rem;
184					padding: 1.5rem 3.5rem 2rem 2rem;
185			">
186				<!-- Grid holder -->
187				<div style="
188					display: grid;
189					grid-template-areas:
190						&quot;date date date&quot;
191						&quot;icon label description&quot;;
192					font-size: 1.2rem;
193					gap: 0.25rem 0;
194				">
195
196					<!-- Incident icon -->
197					<i
198						class="fa-solid ${ icons[status.status.indicator] }"
199						style="
200							color: ${ colors[status.status.indicator] };
201							margin-right: 0.5rem;
202							grid-area: icon;
203							margin-top: 0.125em;
204					"></i>
205
206					<!-- Incident text -->
207					<span style="
208						font-weight: 700;
209						margin-right: 0.5rem;
210						grid-area: label;
211					">
212						${ messages[status.status.indicator] }:
213					</span>
214
215					<!-- StatusPage redirect message -->
216					<span style="
217						color: #444;
218						grid-area: description;
219					">
220						One or more IT services may be experiencing issues. For more information please&thinsp;<a href="https://status.mst.edu" target="_blank">see the IT Status Page</a>.
221					</span>
222					
223					<!-- Incident timestamp -->
224					<span style="
225						font-size: 0.925rem;
226						display: block;
227						grid-area: date;
228						color: #63666A;
229					">
230						${ lastUpdatePretty }
231					</span>
232				</div>
233
234				<!-- Close button -->
235				<span
236					role="button"
237					tabindex="0"
238					title="Dismiss"
239					style="
240						position: absolute;
241						top: 50%;
242						transform: translateY(-50%);
243						right: 1rem;
244						width: 2rem;
245						height: 2rem;
246						line-height: 2rem;
247						text-align: center;
248						cursor: pointer;
249						font-size: 3rem;
250						color: rgb(0 0 0 / 79%);
251				">
252					×
253				</span>
254			</div>
255		`;
256		let bar = getFirstChild(barTemplate);
257
258		// Inject the bar
259		document.body.appendChild(bar);
260
261		// Attach a click listener to the span with title Dismiss
262		document.querySelector("#it-status-bar span[title='Dismiss']").addEventListener("click", ()=>{
263			document.cookie = `it-status-hide-update=${ lastUpdate.getTime() }; Max-Age=31536000; domain=mst.edu; path=/`;
264			document.querySelector("#it-status-bar").parentNode.removeChild(document.querySelector("#it-status-bar"));
265		});
266	}
267
268	/**
269	 * Trigger a fake IT status update for testing purposes
270	 * @param {string} severity The severity of the fake status update (none, minor, major, or critical)
271	 */
272	function triggerFakeITStatus(severity) {
273		if (!severity.match(/none|maintenance|minor|major|critical/)) return;
274
275		// create a fake status object
276		status = {
277			page: {
278				updated_at: "2023-08-15T13:27:40.360-05:00"
279			},
280			status: {
281				indicator: severity
282			}
283		};
284
285		// clear any cookies
286		document.cookie = "it-status-hide-update=; Max-Age=0; domain=mst.edu; path=/";
287
288		// delete any bars
289		let existingBar = document.querySelector("#it-status-bar");
290		if (existingBar) existingBar.parentNode.removeChild(existingBar);
291
292		// cancel the update interval
293		clearInterval(itStatusUpdateInterval);
294
295		// build the link and bar
296		buildLink();
297		if (isBarRequired()) buildBar();
298	}
299	window.triggerFakeITStatus = triggerFakeITStatus;
300
301	/**
302	 * Main function, updates status, runs on interval
303	 * @async
304	 */
305	async function update() {
306		// Fetch the status
307		status = await fetchStatus();
308		if (!status) return;
309
310		// Build the link and bar
311		buildLink();
312		if (isBarRequired()) buildBar();
313	}
314
315	// Update the status every 5 minutes
316	await update();
317	window.itStatusUpdateInterval = setInterval(async ()=>{ await update(); }, 1000 * 60 * 5);
318})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.