web: migrate admin overview cards to separate files

This commit is contained in:
Jens Langhammer 2020-12-16 22:57:43 +01:00
parent 48438e28fd
commit 2d9efe035e
12 changed files with 257 additions and 195 deletions

View File

@ -1,20 +0,0 @@
import { DefaultClient } from "./client";
export class AdminOverview {
version: string;
version_latest: string;
worker_count: number;
providers_without_application: number;
policies_without_binding: number;
cached_policies: number;
cached_flows: number;
constructor() {
throw Error();
}
static get(): Promise<AdminOverview> {
return DefaultClient.fetch<AdminOverview>(["admin", "overview"]);
}
}

21
web/src/api/version.ts Normal file
View File

@ -0,0 +1,21 @@
import { DefaultClient } from "./client";
export class Version {
version_current: string;
version_latest: string;
outdated: boolean;
constructor() {
throw Error();
}
static get(): Promise<Version> {
return DefaultClient.fetch<Version>(["admin", "version"]);
}
toString(): string {
return this.version_current;
}
}

View File

@ -75,10 +75,10 @@ export class SidebarItem {
} }
return html` <li class="pf-c-nav__item ${this.hasChildren() ? "pf-m-expandable pf-m-expanded" : ""}"> return html` <li class="pf-c-nav__item ${this.hasChildren() ? "pf-m-expandable pf-m-expanded" : ""}">
${this.path ? ${this.path ?
html`<a href="#${this.path}" class="pf-c-nav__link ${this.isActive(activePath) ? "pf-m-current" : ""}"> html`<a href="#${this.path}" class="pf-c-nav__link ${this.isActive(activePath) ? "pf-m-current" : ""}">
${this.name} ${this.name}
</a>` : </a>` :
html`<a class="pf-c-nav__link" aria-expanded="true"> html`<a class="pf-c-nav__link" aria-expanded="true">
${this.name} ${this.name}
<span class="pf-c-nav__toggle"> <span class="pf-c-nav__toggle">
<i class="fas fa-angle-right" aria-hidden="true"></i> <i class="fas fa-angle-right" aria-hidden="true"></i>

View File

@ -1,20 +1,22 @@
import { gettext } from "django"; import { gettext } from "django";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { AdminOverview } from "../../api/admin_overview";
import { DefaultClient } from "../../api/client"; import { DefaultClient } from "../../api/client";
import { User } from "../../api/user"; import { User } from "../../api/user";
import { COMMON_STYLES } from "../../common/styles"; import { COMMON_STYLES } from "../../common/styles";
import { SpinnerSize } from "../../elements/Spinner";
import "../../elements/AdminLoginsChart"; import "../../elements/AdminLoginsChart";
import "../../elements/cards/AggregatePromiseCard";
import "./TopApplicationsTable"; import "./TopApplicationsTable";
import "./OverviewCards"; import "./cards/AdminStatusCard";
import "./cards/FlowCacheStatusCard";
import "./cards/PolicyCacheStatusCard";
import "./cards/PolicyUnboundStatusCard";
import "./cards/ProviderStatusCard";
import "./cards/VersionStatusCard";
import "./cards/WorkerStatusCard";
@customElement("ak-admin-overview") @customElement("ak-admin-overview")
export class AdminOverviewPage extends LitElement { export class AdminOverviewPage extends LitElement {
@property({attribute: false})
data?: AdminOverview;
@property({attribute: false}) @property({attribute: false})
users?: Promise<number>; users?: Promise<number>;
@ -23,7 +25,6 @@ export class AdminOverviewPage extends LitElement {
} }
firstUpdated(): void { firstUpdated(): void {
AdminOverview.get().then(value => this.data = value);
this.users = User.count(); this.users = User.count();
} }
@ -51,19 +52,10 @@ export class AdminOverviewPage extends LitElement {
headerLink="#/administration/users/" headerLink="#/administration/users/"
.promise=${this.users}> .promise=${this.users}>
</ak-aggregate-card-promise> </ak-aggregate-card-promise>
<!-- Version card --> <ak-admin-status-version class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-bundle" header="Version">
<ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Workers"> </ak-admin-status-version>
${this.data ? <ak-admin-status-card-workers class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Workers">
this.data?.worker_count < 1 ? </ak-admin-status-card-workers>
html`<p class="ak-aggregate-card">
<i class="fa fa-exclamation-triangle"></i> ${this.data?.worker_count}
</p>
<p class="subtext">${gettext("No workers connected.")}</p>` :
html`<p class="ak-aggregate-card">
<i class="fa fa-check-circle"></i> ${this.data?.worker_count}
</p>`
: html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`}
</ak-aggregate-card>
<ak-admin-status-card-policy-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Cached Policies"> <ak-admin-status-card-policy-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Cached Policies">
</ak-admin-status-card-policy-cache> </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="Cached Flows"> <ak-admin-status-card-flow-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Cached Flows">

View File

@ -1,153 +0,0 @@
import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { until } from "lit-html/directives/until";
import { Flow } from "../../api/flow";
import { Policy } from "../../api/policy";
import { Provider } from "../../api/provider";
import { AggregateCard } from "../../elements/cards/AggregateCard";
import { SpinnerSize } from "../../elements/Spinner";
interface AdminStatus {
icon: string;
message?: string;
}
abstract class AdminStatusCard extends AggregateCard {
abstract getPrimaryCounter(): Promise<number>;
abstract getStatus(counter: number): Promise<AdminStatus>;
@property({type: Number})
counter = 0;
renderInner(): TemplateResult {
return html`<p class="center-value">
${until(this.getPrimaryCounter().then((c) => {
this.counter = c;
return this.getStatus(c);
}).then((status) => {
return html`<p class="ak-aggregate-card">
<i class="${status.icon}"></i> ${this.counter}
</p>
${status.message ? html`<p class="subtext">${status.message}</p>` : html``}`;
}), html`<ak-spinner size="${SpinnerSize.Large}"></ak-spinner>`)}
</p>`;
}
}
@customElement("ak-admin-status-card-provider")
export class ProviderStatusCard extends AdminStatusCard {
getPrimaryCounter(): Promise<number> {
return Provider.list({
"application__isnull": true
}).then((response) => {
return response.pagination.count;
});
}
getStatus(counter: number): Promise<AdminStatus> {
if (counter > 0) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("Warning: At least one Provider has no application assigned."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
}
@customElement("ak-admin-status-card-policy-unbound")
export class PolicyUnboundStatusCard extends AdminStatusCard {
getPrimaryCounter(): Promise<number> {
return Policy.list({
"bindings__isnull": true,
"promptstage__isnull": true,
}).then((response) => {
return response.pagination.count;
});
}
getStatus(counter: number): Promise<AdminStatus> {
if (counter > 0) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("Policies without binding exist."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
}
@customElement("ak-admin-status-card-policy-cache")
export class PolicyCacheStatusCard extends AdminStatusCard {
getPrimaryCounter(): Promise<number> {
return Policy.cached();
}
getStatus(counter: number): Promise<AdminStatus> {
if (counter < 1) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("No policies cached. Users may experience slow response times."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
renderHeaderLink(): TemplateResult {
return html`<ak-modal-button href="/administration/overview/cache/policy/">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</ak-modal-button>`;
}
}
@customElement("ak-admin-status-card-flow-cache")
export class FlowCacheStatusCard extends AdminStatusCard {
getPrimaryCounter(): Promise<number> {
return Flow.cached();
}
getStatus(counter: number): Promise<AdminStatus> {
if (counter < 1) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("No flows cached."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
renderHeaderLink(): TemplateResult {
return html`<ak-modal-button href="/administration/overview/cache/flow/">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</ak-modal-button>`;
}
}

View File

@ -0,0 +1,33 @@
import { html, TemplateResult } from "lit-html";
import { until } from "lit-html/directives/until";
import { AggregateCard } from "../../../elements/cards/AggregateCard";
import { SpinnerSize } from "../../../elements/Spinner";
export interface AdminStatus {
icon: string;
message?: string;
}
export abstract class AdminStatusCard<T> extends AggregateCard {
abstract getPrimaryValue(): Promise<T>;
abstract getStatus(value: T): Promise<AdminStatus>;
value?: T;
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 class="ak-aggregate-card">
<i class="${status.icon}"></i> ${this.value}
</p>
${status.message ? html`<p class="subtext">${status.message}</p>` : html``}`;
}), html`<ak-spinner size="${SpinnerSize.Large}"></ak-spinner>`)}
</p>`;
}
}

View File

@ -0,0 +1,36 @@
import { gettext } from "django";
import { customElement, html, TemplateResult } from "lit-element";
import { Flow } from "../../../api/flow";
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
import "../../../elements/buttons/ModalButton";
@customElement("ak-admin-status-card-flow-cache")
export class FlowCacheStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Flow.cached();
}
getStatus(value: number): Promise<AdminStatus> {
if (value < 1) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("No flows cached."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
renderHeaderLink(): TemplateResult {
return html`<ak-modal-button href="/administration/overview/cache/flow/">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</ak-modal-button>`;
}
}

View File

@ -0,0 +1,37 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { TemplateResult, html } from "lit-html";
import { Policy } from "../../../api/policy";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
import "../../../elements/buttons/ModalButton";
@customElement("ak-admin-status-card-policy-cache")
export class PolicyCacheStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Policy.cached();
}
getStatus(value: number): Promise<AdminStatus> {
if (value < 1) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("No policies cached. Users may experience slow response times."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
renderHeaderLink(): TemplateResult {
return html`<ak-modal-button href="/administration/overview/cache/policy/">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</ak-modal-button>`;
}
}

View File

@ -0,0 +1,31 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { Policy } from "../../../api/policy";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-card-policy-unbound")
export class PolicyUnboundStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Policy.list({
"bindings__isnull": true,
"promptstage__isnull": true,
}).then((response) => {
return response.pagination.count;
});
}
getStatus(value: number): Promise<AdminStatus> {
if (value > 0) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("Policies without binding exist."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
}

View File

@ -0,0 +1,30 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { Provider } from "../../../api/provider";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-card-provider")
export class ProviderStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return Provider.list({
"application__isnull": true
}).then((response) => {
return response.pagination.count;
});
}
getStatus(value: number): Promise<AdminStatus> {
if (value > 0) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("Warning: At least one Provider has no application assigned."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
}

View File

@ -0,0 +1,27 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { Version } from "../../../api/version";
import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
@customElement("ak-admin-status-version")
export class VersionStatusCard extends AdminStatusCard<Version> {
getPrimaryValue(): Promise<Version> {
return Version.get();
}
getStatus(value: Version): Promise<AdminStatus> {
if (value.outdated) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext(`${value.version_latest} is available!`),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle",
message: gettext("Up-to-date!")
});
}
}
}

View File

@ -0,0 +1,28 @@
import { gettext } from "django";
import { customElement } from "lit-element";
import { DefaultClient, PBResponse } from "../../../api/client";
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
@customElement("ak-admin-status-card-workers")
export class WorkersStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return DefaultClient.fetch<PBResponse<number>>(["admins", "workers"]).then((r) => {
return r.pagination.count;
});
}
getStatus(value: number): Promise<AdminStatus> {
if (value < 1) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle",
message: gettext("No workers connected. Background tasks will not run."),
});
} else {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle"
});
}
}
}