web/admin: rewrite overview page
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
|
||||
import { css, CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
|
||||
|
||||
import "../../elements/charts/AdminLoginsChart";
|
||||
import "../../elements/cards/AggregatePromiseCard";
|
||||
@ -7,18 +7,19 @@ import "./TopApplicationsTable";
|
||||
|
||||
import "./cards/AdminStatusCard";
|
||||
import "./cards/BackupStatusCard";
|
||||
import "./cards/FlowCacheStatusCard";
|
||||
import "./cards/LDAPSyncStatusCardContainer";
|
||||
import "./cards/PolicyCacheStatusCard";
|
||||
import "./cards/PolicyUnboundStatusCard";
|
||||
import "./cards/ProviderStatusCard";
|
||||
import "./cards/UserCountStatusCard";
|
||||
import "./cards/VersionStatusCard";
|
||||
import "./cards/WorkerStatusCard";
|
||||
|
||||
import "./graphs/FlowStatusCard";
|
||||
import "./graphs/LDAPSyncStatusCard";
|
||||
import "./graphs/OutpostStatusCard";
|
||||
import "./graphs/PolicyStatusCard";
|
||||
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
import AKGlobal from "../../authentik.css";
|
||||
import "../../elements/PageHeader";
|
||||
|
||||
@ -26,7 +27,21 @@ import "../../elements/PageHeader";
|
||||
export class AdminOverviewPage extends LitElement {
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFGallery, PFPage, PFContent, AKGlobal];
|
||||
return [PFGrid, PFPage, PFContent, AKGlobal, css`
|
||||
.row-divider {
|
||||
margin-top: -4px;
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
.graph-container {
|
||||
height: 20em;
|
||||
}
|
||||
.big-graph-container {
|
||||
height: 35em;
|
||||
}
|
||||
.card-container {
|
||||
height: 10em;
|
||||
}
|
||||
`];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
@ -37,31 +52,62 @@ export class AdminOverviewPage extends LitElement {
|
||||
description=${t`General system status`}>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-gallery pf-m-gutter">
|
||||
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header=${t`Logins over the last 24 hours`} style="grid-column-end: span 3;grid-row-end: span 2;">
|
||||
<ak-charts-admin-login></ak-charts-admin-login>
|
||||
</ak-aggregate-card>
|
||||
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header=${t`Apps with most usage`} style="grid-column-end: span 2;grid-row-end: span 3;">
|
||||
<ak-top-applications-table></ak-top-applications-table>
|
||||
</ak-aggregate-card>
|
||||
<ak-admin-status-card-provider class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header=${t`Providers without application`} headerLink="#/core/providers">
|
||||
</ak-admin-status-card-provider>
|
||||
<ak-admin-status-card-policy-unbound class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-infrastructure" header=${t`Unbound policies`} headerLink="#/policy/policies">
|
||||
</ak-admin-status-card-policy-unbound>
|
||||
<ak-admin-status-card-user-count class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-user" header=${t`Users`} headerLink="#/identity/users">
|
||||
</ak-admin-status-card-user-count>
|
||||
<ak-admin-status-version class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-bundle" header=${t`Version`} headerLink="https://github.com/goauthentik/authentik/releases">
|
||||
</ak-admin-status-version>
|
||||
<ak-admin-status-card-workers class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header=${t`Workers`}>
|
||||
</ak-admin-status-card-workers>
|
||||
<ak-admin-status-card-policy-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header=${t`Cached Policies`}>
|
||||
</ak-admin-status-card-policy-cache>
|
||||
<ak-admin-status-card-flow-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header=${t`Cached Flows`}>
|
||||
</ak-admin-status-card-flow-cache>
|
||||
<ak-admin-status-card-backup class="pf-l-gallery__item pf-m-4-col" icon="fa fa-database" header=${t`Backup status`} headerLink="#/administration/system-tasks">
|
||||
</ak-admin-status-card-backup>
|
||||
<ak-admin-status-card-ldap-sync-container >
|
||||
</ak-admin-status-card-ldap-sync-container>
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
<!-- row 1 -->
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl graph-container">
|
||||
<ak-aggregate-card icon="pf-icon pf-icon-infrastructure" header=${t`Policies`} headerLink="#/policy/policies">
|
||||
<ak-admin-status-card-policy></ak-admin-status-card-policy>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl graph-container">
|
||||
<ak-aggregate-card icon="pf-icon pf-icon-server" header=${t`Flows`} headerLink="#/flow/flows">
|
||||
<ak-admin-status-card-flow></ak-admin-status-card-flow>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl graph-container">
|
||||
<ak-aggregate-card icon="fa fa-sync-alt" header=${t`LDAP Sync status`} headerLink="#/core/sources">
|
||||
<ak-admin-status-card-ldap-sync></ak-admin-status-card-ldap-sync>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl graph-container">
|
||||
<ak-aggregate-card icon="fa fa-sync-alt" header=${t`Outpost status`} headerLink="#/outpost/outposts">
|
||||
<ak-admin-status-card-outpost></ak-admin-status-card-outpost>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-12-col row-divider">
|
||||
<hr>
|
||||
</div>
|
||||
<!-- row 2 -->
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl card-container">
|
||||
<ak-admin-status-version icon="pf-icon pf-icon-bundle" header=${t`Version`} headerLink="https://github.com/goauthentik/authentik/releases">
|
||||
</ak-admin-status-version>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl card-container">
|
||||
<ak-admin-status-card-backup icon="fa fa-database" header=${t`Backup status`} headerLink="#/administration/system-tasks">
|
||||
</ak-admin-status-card-backup>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl card-container">
|
||||
<ak-admin-status-card-user-count icon="pf-icon pf-icon-user" header=${t`Users`} headerLink="#/identity/users">
|
||||
</ak-admin-status-card-user-count>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-3-col-on-xl card-container">
|
||||
<ak-admin-status-card-workers icon="pf-icon pf-icon-server" header=${t`Workers`}>
|
||||
</ak-admin-status-card-workers>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-12-col row-divider">
|
||||
<hr>
|
||||
</div>
|
||||
<!-- row 3 -->
|
||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-md pf-m-6-col-on-xl big-graph-container">
|
||||
<ak-aggregate-card icon="pf-icon pf-icon-server" header=${t`Logins over the last 24 hours`}>
|
||||
<ak-charts-admin-login></ak-charts-admin-login>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-md pf-m-6-col-on-xl big-graph-container">
|
||||
<ak-aggregate-card icon="pf-icon pf-icon-server" header=${t`Apps with most usage`}>
|
||||
<ak-top-applications-table></ak-top-applications-table>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ export class TopApplicationsTable extends LitElement {
|
||||
firstUpdated(): void {
|
||||
new EventsApi(DEFAULT_CONFIG).eventsEventsTopPerUser({
|
||||
action: "authorize_application",
|
||||
topN: 11,
|
||||
}).then((events) => {
|
||||
this.topN = events;
|
||||
});
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement, html, TemplateResult } from "lit-element";
|
||||
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
|
||||
import { FlowsApi } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import "../../../elements/forms/ConfirmationForm";
|
||||
|
||||
@customElement("ak-admin-status-card-flow-cache")
|
||||
export class FlowCacheStatusCard extends AdminStatusCard<number> {
|
||||
|
||||
getPrimaryValue(): Promise<number> {
|
||||
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesCacheInfo().then((value) => {
|
||||
return value.count || 0;
|
||||
});
|
||||
}
|
||||
|
||||
getStatus(value: number): Promise<AdminStatus> {
|
||||
if (value < 1) {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: t`No flows cached.`,
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderHeaderLink(): TemplateResult {
|
||||
return html`<ak-forms-confirm
|
||||
successMessage=${t`Successfully cleared flow cache`}
|
||||
errorMessage=${t`Failed to delete flow cache`}
|
||||
action=${t`Clear cache`}
|
||||
.onConfirm=${() => {
|
||||
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesCacheClear();
|
||||
}}>
|
||||
<span slot="header">
|
||||
${t`Clear Flow cache`}
|
||||
</span>
|
||||
<p slot="body">
|
||||
${t`Are you sure you want to clear the flow cache?
|
||||
This will cause all flows to be re-evaluated on their next usage.`}
|
||||
</p>
|
||||
<a slot="trigger">
|
||||
<i class="fa fa-trash"> </i>
|
||||
</a>
|
||||
<div slot="modal"></div>
|
||||
</ak-forms-confirm>`;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
|
||||
import { SourcesApi, Task, TaskStatusEnum } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import "../../../elements/forms/ConfirmationForm";
|
||||
|
||||
@customElement("ak-admin-status-card-ldap-sync")
|
||||
export class LDAPSyncStatusCard extends AdminStatusCard<Task> {
|
||||
|
||||
@property()
|
||||
slug!: string;
|
||||
|
||||
getPrimaryValue(): Promise<Task> {
|
||||
return new SourcesApi(DEFAULT_CONFIG).sourcesLdapSyncStatus({
|
||||
slug: this.slug
|
||||
}).then((value) => {
|
||||
return value;
|
||||
}).catch(() => {
|
||||
return { status: TaskStatusEnum.Error } as Task;
|
||||
});
|
||||
}
|
||||
|
||||
renderValue(): TemplateResult {
|
||||
return html`${t`Last sync: ${this.value?.taskFinishTimestamp?.toLocaleTimeString() || "-"}`}`;
|
||||
}
|
||||
|
||||
getStatus(value: Task): Promise<AdminStatus> {
|
||||
if (value.status !== TaskStatusEnum.Successful) {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fas fa-times-circle pf-m-danger",
|
||||
message: t`Sync failed.`,
|
||||
});
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
const maxDelta = 3600000; // 1 hour
|
||||
if (!value || (now - value.taskFinishTimestamp.getTime()) > maxDelta) {
|
||||
// No sync or last sync was over maxDelta ago
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: t`Not synced.`,
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderHeaderLink(): TemplateResult {
|
||||
return html`<a href="#/core/sources/${this.slug}">
|
||||
<i class="fa fa-external-link-alt"> </i>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import { SourcesApi } from "authentik-api";
|
||||
import { customElement, html, LitElement, TemplateResult } from "lit-element";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import "./LDAPSyncStatusCard";
|
||||
import { t } from "@lingui/macro";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
|
||||
@customElement("ak-admin-status-card-ldap-sync-container")
|
||||
export class LDAPSyncStatusCardContainer extends LitElement {
|
||||
|
||||
createRenderRoot(): Element | ShadowRoot {
|
||||
return this;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
${until(new SourcesApi(DEFAULT_CONFIG).sourcesLdapList({}).then(sources => {
|
||||
return sources.results.map(source => {
|
||||
return html`<ak-admin-status-card-ldap-sync
|
||||
class="pf-l-gallery__item pf-m-4-col"
|
||||
icon="fa fa-sync-alt"
|
||||
header=${t`LDAP Sync status ${source.name}`}
|
||||
slug=${source.slug}>
|
||||
</ak-admin-status-card-ldap-sync>`;
|
||||
});
|
||||
}))}
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement } from "lit-element";
|
||||
import { TemplateResult, html } from "lit-html";
|
||||
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
|
||||
import { PoliciesApi } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import "../../../elements/forms/ConfirmationForm";
|
||||
|
||||
@customElement("ak-admin-status-card-policy-cache")
|
||||
export class PolicyCacheStatusCard extends AdminStatusCard<number> {
|
||||
|
||||
getPrimaryValue(): Promise<number> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesAllCacheInfo().then((value) => {
|
||||
return value.count || 0;
|
||||
});
|
||||
}
|
||||
|
||||
getStatus(value: number): Promise<AdminStatus> {
|
||||
if (value < 1) {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: t`No policies cached. Users may experience slow response times.`,
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderHeaderLink(): TemplateResult {
|
||||
return html`<ak-forms-confirm
|
||||
successMessage=${t`Successfully cleared policy cache`}
|
||||
errorMessage=${t`Failed to delete policy cache`}
|
||||
action=${t`Clear cache`}
|
||||
.onConfirm=${() => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesAllCacheClear();
|
||||
}}>
|
||||
<span slot="header">
|
||||
${t`Clear Policy cache`}
|
||||
</span>
|
||||
<p slot="body">
|
||||
${t`Are you sure you want to clear the policy cache?
|
||||
This will cause all policies to be re-evaluated on their next usage.`}
|
||||
</p>
|
||||
<a slot="trigger">
|
||||
<i class="fa fa-trash"> </i>
|
||||
</a>
|
||||
<div slot="modal"></div>
|
||||
</ak-forms-confirm>`;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement } from "lit-element";
|
||||
import { PoliciesApi } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
|
||||
|
||||
@customElement("ak-admin-status-card-policy-unbound")
|
||||
export class PolicyUnboundStatusCard extends AdminStatusCard<number> {
|
||||
|
||||
getPrimaryValue(): Promise<number> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesAllList({
|
||||
bindingsIsnull: "true",
|
||||
promptstageIsnull: "true",
|
||||
}).then((value) => {
|
||||
return value.pagination.count;
|
||||
});
|
||||
}
|
||||
|
||||
getStatus(value: number): Promise<AdminStatus> {
|
||||
if (value > 0) {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: t`Policies without binding exist.`,
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
67
web/src/pages/admin-overview/graphs/FlowStatusCard.ts
Normal file
67
web/src/pages/admin-overview/graphs/FlowStatusCard.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement } from "lit-element";
|
||||
import { FlowsApi } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import "../../../elements/forms/ConfirmationForm";
|
||||
import { AKChart } from "../../../elements/charts/Chart";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
interface FlowMetrics {
|
||||
count: number;
|
||||
cached: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-card-flow")
|
||||
export class PolicyStatusCard extends AKChart<FlowMetrics> {
|
||||
|
||||
getChartType(): string {
|
||||
return "doughnut";
|
||||
}
|
||||
|
||||
getOptions(): ChartOptions {
|
||||
return {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
}
|
||||
|
||||
async apiRequest(): Promise<FlowMetrics> {
|
||||
const api = new FlowsApi(DEFAULT_CONFIG);
|
||||
const cached = (await api.flowsInstancesCacheInfo()).count || 0;
|
||||
const count = (await api.flowsInstancesList({
|
||||
pageSize: 1
|
||||
})).pagination.count;
|
||||
this.centerText = count.toString();
|
||||
return {
|
||||
count: count - cached,
|
||||
cached: cached,
|
||||
};
|
||||
}
|
||||
|
||||
getChartData(data: FlowMetrics): ChartData {
|
||||
return {
|
||||
labels: [
|
||||
t`Total flows`,
|
||||
t`Cached flows`,
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
backgroundColor: [
|
||||
"#2b9af3",
|
||||
"#3e8635",
|
||||
],
|
||||
spanGaps: true,
|
||||
data: [
|
||||
data.count,
|
||||
data.cached,
|
||||
],
|
||||
},
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
91
web/src/pages/admin-overview/graphs/LDAPSyncStatusCard.ts
Normal file
91
web/src/pages/admin-overview/graphs/LDAPSyncStatusCard.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement } from "lit-element";
|
||||
import { SourcesApi, TaskStatusEnum } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import "../../../elements/forms/ConfirmationForm";
|
||||
import { AKChart } from "../../../elements/charts/Chart";
|
||||
import { ChartOptions, ChartData } from "chart.js";
|
||||
|
||||
interface LDAPSyncStats {
|
||||
healthy: number;
|
||||
failed: number;
|
||||
unsynced: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-card-ldap-sync")
|
||||
export class LDAPSyncStatusCard extends AKChart<LDAPSyncStats> {
|
||||
|
||||
getChartType(): string {
|
||||
return "doughnut";
|
||||
}
|
||||
|
||||
getOptions(): ChartOptions {
|
||||
return {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
}
|
||||
|
||||
async apiRequest(): Promise<LDAPSyncStats> {
|
||||
const api = new SourcesApi(DEFAULT_CONFIG);
|
||||
const sources = await api.sourcesLdapList({});
|
||||
let healthy = 0;
|
||||
let failed = 0;
|
||||
let unsynced = 0;
|
||||
await Promise.all(sources.results.map(async (element) => {
|
||||
try {
|
||||
const health = await api.sourcesLdapSyncStatus({
|
||||
slug: element.slug,
|
||||
});
|
||||
if (health.status !== TaskStatusEnum.Successful) {
|
||||
failed += 1;
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
const maxDelta = 3600000; // 1 hour
|
||||
if (!health || (now - health.taskFinishTimestamp.getTime()) > maxDelta) {
|
||||
unsynced += 1;
|
||||
} else {
|
||||
healthy += 1;
|
||||
}
|
||||
} catch {
|
||||
unsynced += 1;
|
||||
}
|
||||
}));
|
||||
this.centerText = sources.pagination.count.toString();
|
||||
return {
|
||||
healthy,
|
||||
failed,
|
||||
unsynced
|
||||
};
|
||||
}
|
||||
|
||||
getChartData(data: LDAPSyncStats): ChartData {
|
||||
return {
|
||||
labels: [
|
||||
t`Healthy sources`,
|
||||
t`Failed sources`,
|
||||
t`Unsynced sources`,
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
backgroundColor: [
|
||||
"#3e8635",
|
||||
"#C9190B",
|
||||
"#2b9af3",
|
||||
],
|
||||
spanGaps: true,
|
||||
data: [
|
||||
data.healthy,
|
||||
data.failed,
|
||||
data.unsynced
|
||||
],
|
||||
},
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
87
web/src/pages/admin-overview/graphs/OutpostStatusCard.ts
Normal file
87
web/src/pages/admin-overview/graphs/OutpostStatusCard.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement } from "lit-element";
|
||||
import { OutpostsApi } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import "../../../elements/forms/ConfirmationForm";
|
||||
import { AKChart } from "../../../elements/charts/Chart";
|
||||
import { ChartOptions, ChartData } from "chart.js";
|
||||
|
||||
interface OutpostStats {
|
||||
healthy: number;
|
||||
outdated: number;
|
||||
unhealthy: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-card-outpost")
|
||||
export class OutpostStatusCard extends AKChart<OutpostStats> {
|
||||
|
||||
getChartType(): string {
|
||||
return "doughnut";
|
||||
}
|
||||
|
||||
getOptions(): ChartOptions {
|
||||
return {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
}
|
||||
|
||||
async apiRequest(): Promise<OutpostStats> {
|
||||
const api = new OutpostsApi(DEFAULT_CONFIG);
|
||||
const outposts = await api.outpostsInstancesList({});
|
||||
let healthy = 0;
|
||||
let outdated = 0;
|
||||
let unhealthy = 0;
|
||||
await Promise.all(outposts.results.map(async (element) => {
|
||||
const health = await api.outpostsOutpostsHealth({
|
||||
uuid: element.pk || "",
|
||||
});
|
||||
if (health.length === 0) {
|
||||
unhealthy += 1;
|
||||
}
|
||||
health.forEach(h => {
|
||||
if (h.versionOutdated) {
|
||||
outdated += 1;
|
||||
} else {
|
||||
healthy += 1;
|
||||
}
|
||||
});
|
||||
}));
|
||||
this.centerText = outposts.pagination.count.toString();
|
||||
return {
|
||||
healthy,
|
||||
outdated,
|
||||
unhealthy
|
||||
};
|
||||
}
|
||||
|
||||
getChartData(data: OutpostStats): ChartData {
|
||||
return {
|
||||
labels: [
|
||||
t`Healthy outposts`,
|
||||
t`Outdated outposts`,
|
||||
t`Unhealthy outposts`,
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
backgroundColor: [
|
||||
"#3e8635",
|
||||
"#f0ab00",
|
||||
"#C9190B",
|
||||
],
|
||||
spanGaps: true,
|
||||
data: [
|
||||
data.healthy,
|
||||
data.outdated,
|
||||
data.unhealthy
|
||||
],
|
||||
},
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
76
web/src/pages/admin-overview/graphs/PolicyStatusCard.ts
Normal file
76
web/src/pages/admin-overview/graphs/PolicyStatusCard.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement } from "lit-element";
|
||||
import { PoliciesApi } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import "../../../elements/forms/ConfirmationForm";
|
||||
import { AKChart } from "../../../elements/charts/Chart";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
interface PolicyMetrics {
|
||||
count: number;
|
||||
cached: number;
|
||||
unbound: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-card-policy")
|
||||
export class PolicyStatusCard extends AKChart<PolicyMetrics> {
|
||||
|
||||
getChartType(): string {
|
||||
return "doughnut";
|
||||
}
|
||||
|
||||
getOptions(): ChartOptions {
|
||||
return {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
}
|
||||
|
||||
async apiRequest(): Promise<PolicyMetrics> {
|
||||
const api = new PoliciesApi(DEFAULT_CONFIG);
|
||||
const cached = (await api.policiesAllCacheInfo()).count || 0;
|
||||
const count = (await api.policiesAllList({
|
||||
pageSize: 1
|
||||
})).pagination.count;
|
||||
const unbound = (await api.policiesAllList({
|
||||
bindingsIsnull: "true",
|
||||
promptstageIsnull: "true",
|
||||
})).pagination.count;
|
||||
this.centerText = count.toString();
|
||||
return {
|
||||
count: count - cached - unbound,
|
||||
cached: cached,
|
||||
unbound: unbound,
|
||||
};
|
||||
}
|
||||
|
||||
getChartData(data: PolicyMetrics): ChartData {
|
||||
return {
|
||||
labels: [
|
||||
t`Total policies`,
|
||||
t`Cached policies`,
|
||||
t`Unbound policies`,
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
backgroundColor: [
|
||||
"#2b9af3",
|
||||
"#3e8635",
|
||||
"#f0ab00",
|
||||
],
|
||||
spanGaps: true,
|
||||
data: [
|
||||
data.count,
|
||||
data.cached,
|
||||
data.unbound
|
||||
],
|
||||
},
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user