web: re-organise frontend and cleanup common code (#3572)
* fix repo in api client Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: re-organise files to match their interface Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: include version in script tags Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * cleanup maybe broken Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * revert rename Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: get rid of Client.ts Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * move more to common Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * more moving Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * format Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * unfuck files that vscode fucked, thanks Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * move more Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * finish moving (maybe) Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * ok more moving Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix more stuff that vs code destroyed Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * get rid "web" prefix for virtual package Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix locales Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * use custom base element Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix css file Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * don't run autoDetectLanguage when importing locale Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix circular dependencies Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: fix build Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
223
web/src/admin/admin-overview/AdminOverviewPage.ts
Normal file
223
web/src/admin/admin-overview/AdminOverviewPage.ts
Normal file
@ -0,0 +1,223 @@
|
||||
import "@goauthentik/admin/admin-overview/TopApplicationsTable";
|
||||
import "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
|
||||
import "@goauthentik/admin/admin-overview/cards/SystemStatusCard";
|
||||
import "@goauthentik/admin/admin-overview/cards/VersionStatusCard";
|
||||
import "@goauthentik/admin/admin-overview/cards/WorkerStatusCard";
|
||||
import "@goauthentik/admin/admin-overview/charts/FlowStatusChart";
|
||||
import "@goauthentik/admin/admin-overview/charts/GroupCountStatusChart";
|
||||
import "@goauthentik/admin/admin-overview/charts/LDAPSyncStatusChart";
|
||||
import "@goauthentik/admin/admin-overview/charts/OutpostStatusChart";
|
||||
import "@goauthentik/admin/admin-overview/charts/PolicyStatusChart";
|
||||
import "@goauthentik/admin/admin-overview/charts/UserCountStatusChart";
|
||||
import { me } from "@goauthentik/common/users";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/PageHeader";
|
||||
import "@goauthentik/elements/cards/AggregatePromiseCard";
|
||||
import "@goauthentik/elements/charts/AdminLoginsChart";
|
||||
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
|
||||
@customElement("ak-admin-overview")
|
||||
export class AdminOverviewPage extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFGrid,
|
||||
PFPage,
|
||||
PFContent,
|
||||
PFList,
|
||||
AKGlobal,
|
||||
css`
|
||||
.row-divider {
|
||||
margin-top: -4px;
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
.graph-container {
|
||||
height: 20em;
|
||||
}
|
||||
.big-graph-container {
|
||||
height: 35em;
|
||||
}
|
||||
.card-container {
|
||||
max-height: 10em;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<ak-page-header icon="" header="" description=${t`General system status`}>
|
||||
<span slot="header">
|
||||
${until(
|
||||
me().then((user) => {
|
||||
let name = user.user.username;
|
||||
if (user.user.name !== "") {
|
||||
name = user.user.name;
|
||||
}
|
||||
return t`Welcome, ${name}.`;
|
||||
}),
|
||||
)}
|
||||
</span>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section">
|
||||
<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-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="fa fa-share"
|
||||
header=${t`Quick actions`}
|
||||
.isCenter=${false}
|
||||
>
|
||||
<ul class="pf-c-list">
|
||||
<li>
|
||||
<a
|
||||
class="pf-u-mb-xl"
|
||||
href=${paramURL("/core/applications", {
|
||||
createForm: true,
|
||||
})}
|
||||
>${t`Create a new application`}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pf-u-mb-xl" href=${paramURL("/events/log")}
|
||||
>${t`Check the logs`}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="pf-u-mb-xl"
|
||||
target="_blank"
|
||||
href="https://goauthentik.io/integrations/"
|
||||
>${t`Explore integrations`}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="pf-icon pf-icon-process-automation"
|
||||
header=${t`Flows`}
|
||||
headerLink="#/flow/flows"
|
||||
>
|
||||
<ak-admin-status-chart-flow></ak-admin-status-chart-flow>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="pf-icon pf-icon-zone"
|
||||
header=${t`Outpost status`}
|
||||
headerLink="#/outpost/outposts"
|
||||
>
|
||||
<ak-admin-status-chart-outpost></ak-admin-status-chart-outpost>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="pf-icon pf-icon-user"
|
||||
header=${t`Users`}
|
||||
headerLink="#/identity/users"
|
||||
>
|
||||
<ak-admin-status-chart-user-count></ak-admin-status-chart-user-count>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="pf-icon pf-icon-users"
|
||||
header=${t`Groups`}
|
||||
headerLink="#/identity/groups"
|
||||
>
|
||||
<ak-admin-status-chart-group-count></ak-admin-status-chart-group-count>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
|
||||
>
|
||||
<ak-aggregate-card
|
||||
icon="fa fa-sync-alt"
|
||||
header=${t`LDAP Sync status`}
|
||||
headerLink="#/core/sources"
|
||||
>
|
||||
<ak-admin-status-chart-ldap-sync></ak-admin-status-chart-ldap-sync>
|
||||
</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-4-col-on-xl card-container"
|
||||
>
|
||||
<ak-admin-status-system
|
||||
icon="pf-icon pf-icon-server"
|
||||
header=${t`System status`}
|
||||
>
|
||||
</ak-admin-status-system>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-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-4-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-xl pf-m-8-col-on-2xl 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-xl pf-m-4-col-on-2xl 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>`;
|
||||
}
|
||||
}
|
||||
87
web/src/admin/admin-overview/DashboardUserPage.ts
Normal file
87
web/src/admin/admin-overview/DashboardUserPage.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/PageHeader";
|
||||
import "@goauthentik/elements/cards/AggregatePromiseCard";
|
||||
import "@goauthentik/elements/charts/AdminModelPerDay";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
|
||||
import { EventActions } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-admin-dashboard-users")
|
||||
export class DashboardUserPage extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFGrid,
|
||||
PFPage,
|
||||
PFContent,
|
||||
PFList,
|
||||
AKGlobal,
|
||||
css`
|
||||
.row-divider {
|
||||
margin-top: -4px;
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
.graph-container {
|
||||
height: 20em;
|
||||
}
|
||||
.big-graph-container {
|
||||
height: 35em;
|
||||
}
|
||||
.card-container {
|
||||
max-height: 10em;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<ak-page-header icon="pf-icon pf-icon-user" header=${t`User statistics`}>
|
||||
</ak-page-header>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-12-col-on-2xl big-graph-container"
|
||||
>
|
||||
<ak-aggregate-card header=${t`Users created per day in the last month`}>
|
||||
<ak-charts-admin-model-per-day
|
||||
.query=${{
|
||||
context__model__app: "authentik_core",
|
||||
context__model__model_name: "user",
|
||||
}}
|
||||
>
|
||||
</ak-charts-admin-model-per-day>
|
||||
</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-12-col pf-m-6-col-on-xl pf-m-6-col-on-2xl big-graph-container"
|
||||
>
|
||||
<ak-aggregate-card header=${t`Logins per day in the last month`}>
|
||||
<ak-charts-admin-model-per-day action=${EventActions.Login}>
|
||||
</ak-charts-admin-model-per-day>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
<div
|
||||
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-6-col-on-2xl big-graph-container"
|
||||
>
|
||||
<ak-aggregate-card header=${t`Failed Logins per day in the last month`}>
|
||||
<ak-charts-admin-model-per-day action=${EventActions.LoginFailed}>
|
||||
</ak-charts-admin-model-per-day>
|
||||
</ak-aggregate-card>
|
||||
</div>
|
||||
</div>
|
||||
</section> `;
|
||||
}
|
||||
}
|
||||
64
web/src/admin/admin-overview/TopApplicationsTable.ts
Normal file
64
web/src/admin/admin-overview/TopApplicationsTable.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/Spinner";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFTable from "@patternfly/patternfly/components/Table/table.css";
|
||||
|
||||
import { EventTopPerUser, EventsApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-top-applications-table")
|
||||
export class TopApplicationsTable extends AKElement {
|
||||
@property({ attribute: false })
|
||||
topN?: EventTopPerUser[];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFTable, AKGlobal];
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
new EventsApi(DEFAULT_CONFIG)
|
||||
.eventsEventsTopPerUserList({
|
||||
action: "authorize_application",
|
||||
topN: 11,
|
||||
})
|
||||
.then((events) => {
|
||||
this.topN = events;
|
||||
});
|
||||
}
|
||||
|
||||
renderRow(event: EventTopPerUser): TemplateResult {
|
||||
return html`<tr role="row">
|
||||
<td role="cell">${event.application.name}</td>
|
||||
<td role="cell">${event.countedEvents}</td>
|
||||
<td role="cell">
|
||||
<progress
|
||||
value="${event.countedEvents}"
|
||||
max="${this.topN ? this.topN[0].countedEvents : 0}"
|
||||
></progress>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<table class="pf-c-table pf-m-compact" role="grid">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th role="columnheader" scope="col">${t`Application`}</th>
|
||||
<th role="columnheader" scope="col">${t`Logins`}</th>
|
||||
<th role="columnheader" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody role="rowgroup">
|
||||
${this.topN
|
||||
? this.topN.map((e) => this.renderRow(e))
|
||||
: html`<ak-spinner></ak-spinner>`}
|
||||
</tbody>
|
||||
</table>`;
|
||||
}
|
||||
}
|
||||
49
web/src/admin/admin-overview/cards/AdminStatusCard.ts
Normal file
49
web/src/admin/admin-overview/cards/AdminStatusCard.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { PFSize } from "@goauthentik/elements/Spinner";
|
||||
import { AggregateCard } from "@goauthentik/elements/cards/AggregateCard";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
export interface AdminStatus {
|
||||
icon: string;
|
||||
message?: TemplateResult;
|
||||
}
|
||||
|
||||
export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
abstract getPrimaryValue(): Promise<T>;
|
||||
|
||||
abstract getStatus(value: T): Promise<AdminStatus>;
|
||||
|
||||
value?: T;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener(EVENT_REFRESH, () => {
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
renderValue(): TemplateResult {
|
||||
return html`${this.value}`;
|
||||
}
|
||||
|
||||
renderInner(): TemplateResult {
|
||||
return html`<p class="center-value">
|
||||
${until(
|
||||
this.getPrimaryValue()
|
||||
.then((v) => {
|
||||
this.value = v;
|
||||
return this.getStatus(v);
|
||||
})
|
||||
.then((status) => {
|
||||
return html`<p><i class="${status.icon}"></i> ${this.renderValue()}</p>
|
||||
${status.message
|
||||
? html`<p class="subtext">${status.message}</p>`
|
||||
: html``}`;
|
||||
}),
|
||||
html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`,
|
||||
)}
|
||||
</p>`;
|
||||
}
|
||||
}
|
||||
83
web/src/admin/admin-overview/cards/SystemStatusCard.ts
Normal file
83
web/src/admin/admin-overview/cards/SystemStatusCard.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import {
|
||||
AdminStatus,
|
||||
AdminStatusCard,
|
||||
} from "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { AdminApi, OutpostsApi, System } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-admin-status-system")
|
||||
export class SystemStatusCard extends AdminStatusCard<System> {
|
||||
now?: Date;
|
||||
|
||||
header = t`OK`;
|
||||
|
||||
async getPrimaryValue(): Promise<System> {
|
||||
this.now = new Date();
|
||||
let status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||
if (status.embeddedOutpostHost === "" || !status.embeddedOutpostHost.includes("http")) {
|
||||
// First install, ensure the embedded outpost host is set
|
||||
// also run when outpost host does not contain http
|
||||
// (yes it's called host and requires a URL, i know)
|
||||
await this.setOutpostHost();
|
||||
status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Called on fresh installations and whenever the embedded outpost is deleted
|
||||
// automatically send the login URL when the user first visits the admin dashboard.
|
||||
async setOutpostHost(): Promise<void> {
|
||||
const outposts = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesList({
|
||||
managedIexact: "goauthentik.io/outposts/embedded",
|
||||
});
|
||||
if (outposts.results.length < 1) {
|
||||
return;
|
||||
}
|
||||
const outpost = outposts.results[0];
|
||||
outpost.config["authentik_host"] = window.location.origin;
|
||||
await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesUpdate({
|
||||
uuid: outpost.pk,
|
||||
outpostRequest: outpost,
|
||||
});
|
||||
}
|
||||
|
||||
getStatus(value: System): Promise<AdminStatus> {
|
||||
if (value.embeddedOutpostHost === "") {
|
||||
this.header = t`Warning`;
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: html`${t`Embedded outpost is not configured correctly.`}
|
||||
<a href="#/outpost/outposts">${t`Check outposts.`}</a>`,
|
||||
});
|
||||
}
|
||||
if (!value.httpIsSecure && document.location.protocol === "https:") {
|
||||
this.header = t`Warning`;
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: html`${t`HTTPS is not detected correctly`}`,
|
||||
});
|
||||
}
|
||||
const timeDiff = value.serverTime.getTime() - (this.now || new Date()).getTime();
|
||||
if (timeDiff > 5000 || timeDiff < -5000) {
|
||||
this.header = t`Warning`;
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: html`${t`Server and client are further than 5 seconds apart.`}`,
|
||||
});
|
||||
}
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success",
|
||||
message: html`${t`Everything is ok.`}`,
|
||||
});
|
||||
}
|
||||
|
||||
renderValue(): TemplateResult {
|
||||
return html`${this.header}`;
|
||||
}
|
||||
}
|
||||
50
web/src/admin/admin-overview/cards/VersionStatusCard.ts
Normal file
50
web/src/admin/admin-overview/cards/VersionStatusCard.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
AdminStatus,
|
||||
AdminStatusCard,
|
||||
} from "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { AdminApi, Version } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-admin-status-version")
|
||||
export class VersionStatusCard extends AdminStatusCard<Version> {
|
||||
getPrimaryValue(): Promise<Version> {
|
||||
return new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
|
||||
}
|
||||
|
||||
getStatus(value: Version): Promise<AdminStatus> {
|
||||
if (value.buildHash) {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success",
|
||||
message: html`
|
||||
${t`Build hash: `}
|
||||
<a
|
||||
href="https://github.com/goauthentik/authentik/commit/${value.buildHash}"
|
||||
target="_blank"
|
||||
>
|
||||
${value.buildHash?.substring(0, 7)}
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
}
|
||||
if (value.outdated) {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||
message: html`${t`${value.versionLatest} is available!`}`,
|
||||
});
|
||||
}
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success",
|
||||
message: html`${t`Up-to-date!`}`,
|
||||
});
|
||||
}
|
||||
|
||||
renderValue(): TemplateResult {
|
||||
return html`${this.value?.versionCurrent}`;
|
||||
}
|
||||
}
|
||||
34
web/src/admin/admin-overview/cards/WorkerStatusCard.ts
Normal file
34
web/src/admin/admin-overview/cards/WorkerStatusCard.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import {
|
||||
AdminStatus,
|
||||
AdminStatusCard,
|
||||
} from "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { AdminApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-admin-status-card-workers")
|
||||
export class WorkersStatusCard extends AdminStatusCard<number> {
|
||||
getPrimaryValue(): Promise<number> {
|
||||
return new AdminApi(DEFAULT_CONFIG).adminWorkersRetrieve().then((workers) => {
|
||||
return workers.count;
|
||||
});
|
||||
}
|
||||
|
||||
getStatus(value: number): Promise<AdminStatus> {
|
||||
if (value < 1) {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-times-circle pf-m-danger",
|
||||
message: html`${t`No workers connected. Background tasks will not run.`}`,
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve<AdminStatus>({
|
||||
icon: "fa fa-check-circle pf-m-success",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
61
web/src/admin/admin-overview/charts/FlowStatusChart.ts
Normal file
61
web/src/admin/admin-overview/charts/FlowStatusChart.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import "@goauthentik/elements/forms/ConfirmationForm";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { FlowsApi } from "@goauthentik/api";
|
||||
|
||||
interface FlowMetrics {
|
||||
count: number;
|
||||
cached: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-chart-flow")
|
||||
export class PolicyStatusChart 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.flowsInstancesCacheInfoRetrieve()).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],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
64
web/src/admin/admin-overview/charts/GroupCountStatusChart.ts
Normal file
64
web/src/admin/admin-overview/charts/GroupCountStatusChart.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { CoreApi } from "@goauthentik/api";
|
||||
|
||||
interface GroupMetrics {
|
||||
count: number;
|
||||
superusers: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-chart-group-count")
|
||||
export class GroupCountStatusChart extends AKChart<GroupMetrics> {
|
||||
getChartType(): string {
|
||||
return "doughnut";
|
||||
}
|
||||
|
||||
getOptions(): ChartOptions {
|
||||
return {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
}
|
||||
|
||||
async apiRequest(): Promise<GroupMetrics> {
|
||||
const api = new CoreApi(DEFAULT_CONFIG);
|
||||
const count = (
|
||||
await api.coreGroupsList({
|
||||
pageSize: 1,
|
||||
})
|
||||
).pagination.count;
|
||||
const superusers = (
|
||||
await api.coreGroupsList({
|
||||
isSuperuser: true,
|
||||
})
|
||||
).pagination.count;
|
||||
this.centerText = count.toString();
|
||||
return {
|
||||
count: count - superusers,
|
||||
superusers,
|
||||
};
|
||||
}
|
||||
|
||||
getChartData(data: GroupMetrics): ChartData {
|
||||
return {
|
||||
labels: [t`Total groups`, t`Superuser-groups`],
|
||||
datasets: [
|
||||
{
|
||||
backgroundColor: ["#2b9af3", "#3e8635"],
|
||||
spanGaps: true,
|
||||
data: [data.count, data.superusers],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
88
web/src/admin/admin-overview/charts/LDAPSyncStatusChart.ts
Normal file
88
web/src/admin/admin-overview/charts/LDAPSyncStatusChart.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import "@goauthentik/elements/forms/ConfirmationForm";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { SourcesApi, TaskStatusEnum } from "@goauthentik/api";
|
||||
|
||||
interface LDAPSyncStats {
|
||||
healthy: number;
|
||||
failed: number;
|
||||
unsynced: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-chart-ldap-sync")
|
||||
export class LDAPSyncStatusChart 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({});
|
||||
const metrics: { [key: string]: number } = {
|
||||
healthy: 0,
|
||||
failed: 0,
|
||||
unsynced: 0,
|
||||
};
|
||||
await Promise.all(
|
||||
sources.results.map(async (element) => {
|
||||
// Each source should have 3 successful tasks, so the worst task overwrites
|
||||
let sourceKey = "healthy";
|
||||
try {
|
||||
const health = await api.sourcesLdapSyncStatusList({
|
||||
slug: element.slug,
|
||||
});
|
||||
|
||||
health.forEach((task) => {
|
||||
if (task.status !== TaskStatusEnum.Successful) {
|
||||
sourceKey = "failed";
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
const maxDelta = 3600000; // 1 hour
|
||||
if (!health || now - task.taskFinishTimestamp.getTime() > maxDelta) {
|
||||
sourceKey = "unsynced";
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
sourceKey = "unsynced";
|
||||
}
|
||||
metrics[sourceKey] += 1;
|
||||
}),
|
||||
);
|
||||
this.centerText = sources.pagination.count.toString();
|
||||
return {
|
||||
healthy: sources.pagination.count === 0 ? -1 : metrics.healthy,
|
||||
failed: metrics.failed,
|
||||
unsynced: metrics.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],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
78
web/src/admin/admin-overview/charts/OutpostStatusChart.ts
Normal file
78
web/src/admin/admin-overview/charts/OutpostStatusChart.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import "@goauthentik/elements/forms/ConfirmationForm";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { OutpostsApi } from "@goauthentik/api";
|
||||
|
||||
interface OutpostStats {
|
||||
healthy: number;
|
||||
outdated: number;
|
||||
unhealthy: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-chart-outpost")
|
||||
export class OutpostStatusChart 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.outpostsInstancesHealthList({
|
||||
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: outposts.pagination.count === 0 ? -1 : 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],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
71
web/src/admin/admin-overview/charts/PolicyStatusChart.ts
Normal file
71
web/src/admin/admin-overview/charts/PolicyStatusChart.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import "@goauthentik/elements/forms/ConfirmationForm";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { PoliciesApi } from "@goauthentik/api";
|
||||
|
||||
interface PolicyMetrics {
|
||||
count: number;
|
||||
cached: number;
|
||||
unbound: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-chart-policy")
|
||||
export class PolicyStatusChart 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.policiesAllCacheInfoRetrieve()).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 {
|
||||
// If we have more cache than total policies, only show that
|
||||
// otherwise show count without unbound
|
||||
count: cached >= count ? cached : count - 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],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
64
web/src/admin/admin-overview/charts/UserCountStatusChart.ts
Normal file
64
web/src/admin/admin-overview/charts/UserCountStatusChart.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { CoreApi } from "@goauthentik/api";
|
||||
|
||||
interface UserMetrics {
|
||||
count: number;
|
||||
superusers: number;
|
||||
}
|
||||
|
||||
@customElement("ak-admin-status-chart-user-count")
|
||||
export class UserCountStatusChart extends AKChart<UserMetrics> {
|
||||
getChartType(): string {
|
||||
return "doughnut";
|
||||
}
|
||||
|
||||
getOptions(): ChartOptions {
|
||||
return {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
}
|
||||
|
||||
async apiRequest(): Promise<UserMetrics> {
|
||||
const api = new CoreApi(DEFAULT_CONFIG);
|
||||
const count = (
|
||||
await api.coreUsersList({
|
||||
pageSize: 1,
|
||||
})
|
||||
).pagination.count;
|
||||
const superusers = (
|
||||
await api.coreUsersList({
|
||||
isSuperuser: true,
|
||||
})
|
||||
).pagination.count;
|
||||
this.centerText = count.toString();
|
||||
return {
|
||||
count: count - superusers,
|
||||
superusers,
|
||||
};
|
||||
}
|
||||
|
||||
getChartData(data: UserMetrics): ChartData {
|
||||
return {
|
||||
labels: [t`Total users`, t`Superusers`],
|
||||
datasets: [
|
||||
{
|
||||
backgroundColor: ["#2b9af3", "#3e8635"],
|
||||
spanGaps: true,
|
||||
data: [data.count, data.superusers],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user