Skip to content

Commit

Permalink
Merge pull request #4407 from systeminit/feat/improve-status-indicator
Browse files Browse the repository at this point in the history
feat: adds real time platform status indicator to UI
  • Loading branch information
johnrwatson authored Aug 23, 2024
2 parents 5617cbc + 44530de commit ef644a2
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 2 deletions.
2 changes: 0 additions & 2 deletions app/web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
</template>
<template v-else>
<CachedAppNotification />
<RealtimeConnectionStatus />
<RouterView :key="selectedWorkspace?.pk" />
<Teleport to="body">
<canvas
Expand All @@ -42,7 +41,6 @@ import { useCustomFontsLoadedProvider } from "./utils/useFontLoaded";
import { useAuthStore } from "./store/auth.store";
import { useWorkspacesStore } from "./store/workspaces.store";
import { useRealtimeStore } from "./store/realtime/realtime.store";
import RealtimeConnectionStatus from "./components/RealtimeConnectionStatus.vue";
import CachedAppNotification from "./components/CachedAppNotification.vue";

useCustomFontsLoadedProvider();
Expand Down
62 changes: 62 additions & 0 deletions app/web/src/components/RealtimeStatusPageState.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<template>
<div
v-if="status !== 'operational' && status !== 'unknown'"
id="status_container"
>
<div
class="realtime-status-page-state"
:class="
clsx({
'bg-destructive-400': status === 'unavailable',
'bg-warning-600': status === 'degraded',
'bg-action-600': status === 'maintenance',
'bg-success-400': status === 'operational',
})
"
></div>
<div class="text-xs realtime-status-page-description">
Currently {{ status === "maintenance" ? "in maintenance" : status }}, see
<a
class="text-action-600"
href="https://status.systeminit.com"
target="_blank"
>Status</a
>
for more information
</div>
<div id="clear"></div>
</div>
</template>

<script lang="ts" setup>
import { computed } from "vue";
import clsx from "clsx";
import { useRealtimeStore } from "@/store/realtime/realtime.store";
const realtimeStore = useRealtimeStore();
const status = computed(() => realtimeStore.applicationStatus);
</script>

<style>
.realtime-status-page-state {
margin-left: 0.5em;
margin-right: 0.2em;
width: 5px;
height: 5px;
border-radius: 100%;
float: left;
}
#status_container {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.realtime-status-page-description {
width: 350px;
float: left;
flex: 1;
}
.clear {
clear: both;
}
</style>
3 changes: 3 additions & 0 deletions app/web/src/components/StatusBar/StatusBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
>
<div class="flex text-sm items-center pl-xs mr-lg w-full">
System&nbsp;Initiative
<RealtimeStatusPageState />
</div>

<div class="border-l border-shade-100">
<StatusBarDiffSummary v-if="!changeSetStore.headSelected" />
</div>
Expand All @@ -28,6 +30,7 @@ import * as _ from "lodash-es";
import clsx from "clsx";
import { useChangeSetsStore } from "@/store/change_sets.store";
import RealtimeStatusPageState from "@/components/RealtimeStatusPageState.vue";
import StatusBarDiffSummary from "./StatusBarDiffSummary.vue";
import StatusBarResourceSummary from "./StatusBarResourceSummary.vue";
import StatusBarQualificationSummary from "./StatusBarQualificationSummary.vue";
Expand Down
67 changes: 67 additions & 0 deletions app/web/src/store/realtime/realtime.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,72 @@ export const useRealtimeStore = defineStore("realtime", () => {
}
});

// TODO(johnrwatson): Fetching status from a public status page JSON representation
// I have a DNS record set up for this but it's giving me grief, I'll come back and amend to
// status-data.systeminit.com
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function fetchStatusPage(): Promise<any> {
const response = await fetch(
"https://nhzefkyp7l.execute-api.us-east-1.amazonaws.com/data/payload.json",
);
const data = await response.json();
return data;
}

// Custom sort function to sort incidents by severity
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function sortIncidentsBySeverity(incidents: any[]): any[] {
const severityOrder: { [key: string]: number } = {
MAINTENANCE: 4,
UNAVAILABLE: 3,
DEGRADED: 2,
OPERATIONAL: 1,
};

return incidents.sort((a, b) => {
const severityA = severityOrder[a.severitySlug?.toUpperCase()] || 5;
const severityB = severityOrder[b.severitySlug?.toUpperCase()] || 5;
return severityA - severityB;
});
}

const applicationStatus = ref<string>("operational");

// Check whether there is a degraded or outage state against the public statuspage
const statusPageState = async () => {
applicationStatus.value = "operational";

try {
const statusData = await fetchStatusPage();

const incidents = sortIncidentsBySeverity(statusData.incidents);

// Loop through each incident after sorting
for (const incident of incidents) {
const resolvedTimestamp = incident.timestamps?.resolved;

if (incident.components) {
for (const component of incident.components) {
// Check if the incident is unresolved and its severity is relevant
if (
!resolvedTimestamp &&
["UNAVAILABLE", "DEGRADED", "MAINTENANCE"].includes(
component.condition.toUpperCase(),
)
) {
applicationStatus.value = component.condition.toLowerCase(); // Return the lowercased version of severity and break the loop
break;
}
}
}
}
} catch (error) {
reportError(error);
}
};

setInterval(statusPageState, 30 * 1000);

// track subscriptions w/ topics, subscribers, etc
let subCounter = 0;
// const topicSubscriptionCounter = {} as Record<SubscriptionTopic, number>;
Expand Down Expand Up @@ -234,6 +300,7 @@ export const useRealtimeStore = defineStore("realtime", () => {
});

return {
applicationStatus,
connectionStatus,
sendMessage,
// subscriptions, // can expose here to show in devtools
Expand Down

0 comments on commit ef644a2

Please sign in to comment.