@ -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 { SummarizedSyncStatus } from "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||||
@ -5,78 +7,104 @@ import "@goauthentik/elements/forms/ConfirmationForm";
|
|||||||
import { ChartData, ChartOptions } from "chart.js";
|
import { ChartData, ChartOptions } from "chart.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
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 { 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")
|
@customElement("ak-admin-status-chart-outpost")
|
||||||
export class OutpostStatusChart extends AKChart<SummarizedSyncStatus[]> {
|
export class OutpostStatusChart extends AKElement {
|
||||||
getChartType(): string {
|
@state()
|
||||||
return "doughnut";
|
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 {
|
async apiRequest(): Promise<OutpostStatus[]> {
|
||||||
return {
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async apiRequest(): Promise<SummarizedSyncStatus[]> {
|
|
||||||
const api = new OutpostsApi(DEFAULT_CONFIG);
|
const api = new OutpostsApi(DEFAULT_CONFIG);
|
||||||
const outposts = await api.outpostsInstancesList({});
|
const outposts = await api.outpostsInstancesList({});
|
||||||
const outpostStats: SummarizedSyncStatus[] = [];
|
const outpostStats: OutpostStatus[] = [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
outposts.results.map(async (element) => {
|
outposts.results.map(async (element) => {
|
||||||
const health = await api.outpostsInstancesHealthList({
|
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) => {
|
health.forEach((h) => {
|
||||||
|
const singleStats: OutpostStatus = {
|
||||||
|
label: `${element.name} - ${h.hostname}`,
|
||||||
|
state: "unhealthy",
|
||||||
|
};
|
||||||
if (h.versionOutdated) {
|
if (h.versionOutdated) {
|
||||||
singleStats.failed += 1;
|
singleStats.state = "outdated";
|
||||||
|
} else if (h.lastSeen.getTime() <= new Date().getTime() - 60 * 10 * 1000) {
|
||||||
|
singleStats.state = "unhealthy";
|
||||||
} else {
|
} 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));
|
outpostStats.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
return outpostStats;
|
return outpostStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
getChartData(data: SummarizedSyncStatus[]): ChartData {
|
firstUpdated() {
|
||||||
return {
|
this.apiRequest().then((data) => (this.data = data));
|
||||||
labels: [msg("Healthy outposts"), msg("Outdated outposts"), msg("Unhealthy outposts")],
|
}
|
||||||
datasets: data.map((d) => {
|
|
||||||
return {
|
render() {
|
||||||
backgroundColor: [
|
if (!this.data) {
|
||||||
actionToColor(EventActions.Login),
|
return nothing;
|
||||||
actionToColor(EventActions.SuspiciousRequest),
|
}
|
||||||
actionToColor(EventActions.AuthorizeApplication),
|
return html`<div class="container"><div class="grid">
|
||||||
],
|
${this.data.map((d) => {
|
||||||
spanGaps: true,
|
return html`<div class="square ${d.state}"></div>`;
|
||||||
data: [d.healthy, d.failed, d.unsynced],
|
})}
|
||||||
label: d.label,
|
</div></div>`;
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user