diff --git a/web/src/admin/admin-overview/charts/OutpostStatusChart.ts b/web/src/admin/admin-overview/charts/OutpostStatusChart.ts index ed2f33078c..ba64a6de40 100644 --- a/web/src/admin/admin-overview/charts/OutpostStatusChart.ts +++ b/web/src/admin/admin-overview/charts/OutpostStatusChart.ts @@ -1,3 +1,5 @@ +import { AKElement } from "#elements/Base"; +import { actionToColor } from "#elements/charts/EventChart"; import { SummarizedSyncStatus } from "@goauthentik/admin/admin-overview/charts/SyncStatusChart"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKChart } from "@goauthentik/elements/charts/Chart"; @@ -5,78 +7,104 @@ import "@goauthentik/elements/forms/ConfirmationForm"; import { ChartData, ChartOptions } from "chart.js"; import { msg } from "@lit/localize"; -import { customElement } from "lit/decorators.js"; +import { css, html, nothing } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { EventActions, OutpostsApi } from "@goauthentik/api"; -import { actionToColor } from "#elements/charts/EventChart"; + +export type OutpostState = "healthy" | "outdated" | "unhealthy"; + +export interface OutpostStatus { + state: OutpostState; + label: string; +} @customElement("ak-admin-status-chart-outpost") -export class OutpostStatusChart extends AKChart { - getChartType(): string { - return "doughnut"; +export class OutpostStatusChart extends AKElement { + @state() + data?: OutpostStatus[]; + + static get styles() { + return [ + PFBase, + css` + :host { + --square-size: 3rem; + --square-border: 1px; + } + .container { + } + .grid { + display: grid; + grid-template-columns: repeat( + auto-fill, + calc(var(--square-size) + var(--square-border)) + ); + } + .square { + width: var(--square-size); + height: var(--square-size); + margin: var(--square-border); + margin-top: 0; + } + .healthy { + background-color: #4cb140; + } + .outdated { + background-color: #0060c0; + } + .unhealthy { + background-color: #c46100; + } + `, + ]; } - getOptions(): ChartOptions { - return { - plugins: { - legend: { - display: false, - }, - }, - maintainAspectRatio: false, - }; - } - - async apiRequest(): Promise { + async apiRequest(): Promise { const api = new OutpostsApi(DEFAULT_CONFIG); const outposts = await api.outpostsInstancesList({}); - const outpostStats: SummarizedSyncStatus[] = []; + const outpostStats: OutpostStatus[] = []; await Promise.all( outposts.results.map(async (element) => { const health = await api.outpostsInstancesHealthList({ - uuid: element.pk || "", + uuid: element.pk, }); - const singleStats: SummarizedSyncStatus = { - unsynced: 0, - healthy: 0, - failed: 0, - total: health.length, - label: element.name, - }; - if (health.length === 0) { - singleStats.unsynced += 1; - } health.forEach((h) => { + const singleStats: OutpostStatus = { + label: `${element.name} - ${h.hostname}`, + state: "unhealthy", + }; if (h.versionOutdated) { - singleStats.failed += 1; + singleStats.state = "outdated"; + } else if (h.lastSeen.getTime() <= new Date().getTime() - 60 * 10 * 1000) { + singleStats.state = "unhealthy"; } else { - singleStats.healthy += 1; + singleStats.state = "healthy"; } + outpostStats.push(singleStats); }); - outpostStats.push(singleStats); }), ); - this.centerText = outposts.pagination.count.toString(); + outpostStats.sort((a, b) => a.label.localeCompare(b.label)); return outpostStats; } - getChartData(data: SummarizedSyncStatus[]): ChartData { - return { - labels: [msg("Healthy outposts"), msg("Outdated outposts"), msg("Unhealthy outposts")], - datasets: data.map((d) => { - return { - backgroundColor: [ - actionToColor(EventActions.Login), - actionToColor(EventActions.SuspiciousRequest), - actionToColor(EventActions.AuthorizeApplication), - ], - spanGaps: true, - data: [d.healthy, d.failed, d.unsynced], - label: d.label, - }; - }), - }; + firstUpdated() { + this.apiRequest().then((data) => (this.data = data)); + } + + render() { + if (!this.data) { + return nothing; + } + return html`
+ ${this.data.map((d) => { + return html`
`; + })} +
`; } }