Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer
2025-06-07 22:03:54 +02:00
parent e59877ebe5
commit c1f549a18b

View File

@ -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<SummarizedSyncStatus[]> {
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<SummarizedSyncStatus[]> {
async apiRequest(): Promise<OutpostStatus[]> {
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`<div class="container"><div class="grid">
${this.data.map((d) => {
return html`<div class="square ${d.state}"></div>`;
})}
</div></div>`;
}
}