diff --git a/web/src/api/admin_overview.ts b/web/src/api/admin_overview.ts deleted file mode 100644 index 28185eb3d..000000000 --- a/web/src/api/admin_overview.ts +++ /dev/null @@ -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 { - return DefaultClient.fetch(["admin", "overview"]); - } - -} diff --git a/web/src/api/version.ts b/web/src/api/version.ts new file mode 100644 index 000000000..f747bccb8 --- /dev/null +++ b/web/src/api/version.ts @@ -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 { + return DefaultClient.fetch(["admin", "version"]); + } + + toString(): string { + return this.version_current; + } + +} diff --git a/web/src/elements/sidebar/Sidebar.ts b/web/src/elements/sidebar/Sidebar.ts index 54f191323..bf639b84e 100644 --- a/web/src/elements/sidebar/Sidebar.ts +++ b/web/src/elements/sidebar/Sidebar.ts @@ -75,10 +75,10 @@ export class SidebarItem { } return html`
  • ${this.path ? - html` + html` ${this.name} ` : - html` + html` ${this.name} diff --git a/web/src/pages/admin-overview/AdminOverviewPage.ts b/web/src/pages/admin-overview/AdminOverviewPage.ts index 8ea91f457..3825738ae 100644 --- a/web/src/pages/admin-overview/AdminOverviewPage.ts +++ b/web/src/pages/admin-overview/AdminOverviewPage.ts @@ -1,20 +1,22 @@ import { gettext } from "django"; import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; -import { AdminOverview } from "../../api/admin_overview"; import { DefaultClient } from "../../api/client"; import { User } from "../../api/user"; import { COMMON_STYLES } from "../../common/styles"; -import { SpinnerSize } from "../../elements/Spinner"; import "../../elements/AdminLoginsChart"; +import "../../elements/cards/AggregatePromiseCard"; 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") export class AdminOverviewPage extends LitElement { - @property({attribute: false}) - data?: AdminOverview; - @property({attribute: false}) users?: Promise; @@ -23,7 +25,6 @@ export class AdminOverviewPage extends LitElement { } firstUpdated(): void { - AdminOverview.get().then(value => this.data = value); this.users = User.count(); } @@ -51,19 +52,10 @@ export class AdminOverviewPage extends LitElement { headerLink="#/administration/users/" .promise=${this.users}> - - - ${this.data ? - this.data?.worker_count < 1 ? - html`

    - ${this.data?.worker_count} -

    -

    ${gettext("No workers connected.")}

    ` : - html`

    - ${this.data?.worker_count} -

    ` - : html``} -
    + + + + diff --git a/web/src/pages/admin-overview/OverviewCards.ts b/web/src/pages/admin-overview/OverviewCards.ts deleted file mode 100644 index c35bd98c6..000000000 --- a/web/src/pages/admin-overview/OverviewCards.ts +++ /dev/null @@ -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; - - abstract getStatus(counter: number): Promise; - - @property({type: Number}) - counter = 0; - - renderInner(): TemplateResult { - return html`

    - ${until(this.getPrimaryCounter().then((c) => { - this.counter = c; - return this.getStatus(c); - }).then((status) => { - return html`

    - ${this.counter} -

    - ${status.message ? html`

    ${status.message}

    ` : html``}`; - }), html``)} -

    `; - } -} - -@customElement("ak-admin-status-card-provider") -export class ProviderStatusCard extends AdminStatusCard { - - getPrimaryCounter(): Promise { - return Provider.list({ - "application__isnull": true - }).then((response) => { - return response.pagination.count; - }); - } - - getStatus(counter: number): Promise { - if (counter > 0) { - return Promise.resolve({ - icon: "fa fa-exclamation-triangle", - message: gettext("Warning: At least one Provider has no application assigned."), - }); - } else { - return Promise.resolve({ - icon: "fa fa-check-circle" - }); - } - } - -} - -@customElement("ak-admin-status-card-policy-unbound") -export class PolicyUnboundStatusCard extends AdminStatusCard { - - getPrimaryCounter(): Promise { - return Policy.list({ - "bindings__isnull": true, - "promptstage__isnull": true, - }).then((response) => { - return response.pagination.count; - }); - } - - getStatus(counter: number): Promise { - if (counter > 0) { - return Promise.resolve({ - icon: "fa fa-exclamation-triangle", - message: gettext("Policies without binding exist."), - }); - } else { - return Promise.resolve({ - icon: "fa fa-check-circle" - }); - } - } - -} - -@customElement("ak-admin-status-card-policy-cache") -export class PolicyCacheStatusCard extends AdminStatusCard { - - getPrimaryCounter(): Promise { - return Policy.cached(); - } - - getStatus(counter: number): Promise { - if (counter < 1) { - return Promise.resolve({ - icon: "fa fa-exclamation-triangle", - message: gettext("No policies cached. Users may experience slow response times."), - }); - } else { - return Promise.resolve({ - icon: "fa fa-check-circle" - }); - } - } - - renderHeaderLink(): TemplateResult { - return html` -
    - - -
    - `; - } - -} - -@customElement("ak-admin-status-card-flow-cache") -export class FlowCacheStatusCard extends AdminStatusCard { - - getPrimaryCounter(): Promise { - return Flow.cached(); - } - - getStatus(counter: number): Promise { - if (counter < 1) { - return Promise.resolve({ - icon: "fa fa-exclamation-triangle", - message: gettext("No flows cached."), - }); - } else { - return Promise.resolve({ - icon: "fa fa-check-circle" - }); - } - } - - renderHeaderLink(): TemplateResult { - return html` - - - -
    -
    `; - } - -} diff --git a/web/src/pages/admin-overview/cards/AdminStatusCard.ts b/web/src/pages/admin-overview/cards/AdminStatusCard.ts new file mode 100644 index 000000000..c27c3afb3 --- /dev/null +++ b/web/src/pages/admin-overview/cards/AdminStatusCard.ts @@ -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 extends AggregateCard { + + abstract getPrimaryValue(): Promise; + + abstract getStatus(value: T): Promise; + + value?: T; + + renderInner(): TemplateResult { + return html`

    + ${until(this.getPrimaryValue().then((v) => { + this.value = v; + return this.getStatus(v); + }).then((status) => { + return html`

    + ${this.value} +

    + ${status.message ? html`

    ${status.message}

    ` : html``}`; + }), html``)} +

    `; + } +} + diff --git a/web/src/pages/admin-overview/cards/FlowCacheStatusCard.ts b/web/src/pages/admin-overview/cards/FlowCacheStatusCard.ts new file mode 100644 index 000000000..1b34310c2 --- /dev/null +++ b/web/src/pages/admin-overview/cards/FlowCacheStatusCard.ts @@ -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 { + + getPrimaryValue(): Promise { + return Flow.cached(); + } + + getStatus(value: number): Promise { + if (value < 1) { + return Promise.resolve({ + icon: "fa fa-exclamation-triangle", + message: gettext("No flows cached."), + }); + } else { + return Promise.resolve({ + icon: "fa fa-check-circle" + }); + } + } + + renderHeaderLink(): TemplateResult { + return html` + + + +
    +
    `; + } + +} diff --git a/web/src/pages/admin-overview/cards/PolicyCacheStatusCard.ts b/web/src/pages/admin-overview/cards/PolicyCacheStatusCard.ts new file mode 100644 index 000000000..85920a551 --- /dev/null +++ b/web/src/pages/admin-overview/cards/PolicyCacheStatusCard.ts @@ -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 { + + getPrimaryValue(): Promise { + return Policy.cached(); + } + + getStatus(value: number): Promise { + if (value < 1) { + return Promise.resolve({ + icon: "fa fa-exclamation-triangle", + message: gettext("No policies cached. Users may experience slow response times."), + }); + } else { + return Promise.resolve({ + icon: "fa fa-check-circle" + }); + } + } + + renderHeaderLink(): TemplateResult { + return html` + + + +
    +
    `; + } + +} diff --git a/web/src/pages/admin-overview/cards/PolicyUnboundStatusCard.ts b/web/src/pages/admin-overview/cards/PolicyUnboundStatusCard.ts new file mode 100644 index 000000000..37a74a598 --- /dev/null +++ b/web/src/pages/admin-overview/cards/PolicyUnboundStatusCard.ts @@ -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 { + + getPrimaryValue(): Promise { + return Policy.list({ + "bindings__isnull": true, + "promptstage__isnull": true, + }).then((response) => { + return response.pagination.count; + }); + } + + getStatus(value: number): Promise { + if (value > 0) { + return Promise.resolve({ + icon: "fa fa-exclamation-triangle", + message: gettext("Policies without binding exist."), + }); + } else { + return Promise.resolve({ + icon: "fa fa-check-circle" + }); + } + } + +} diff --git a/web/src/pages/admin-overview/cards/ProviderStatusCard.ts b/web/src/pages/admin-overview/cards/ProviderStatusCard.ts new file mode 100644 index 000000000..b19404dd6 --- /dev/null +++ b/web/src/pages/admin-overview/cards/ProviderStatusCard.ts @@ -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 { + + getPrimaryValue(): Promise { + return Provider.list({ + "application__isnull": true + }).then((response) => { + return response.pagination.count; + }); + } + + getStatus(value: number): Promise { + if (value > 0) { + return Promise.resolve({ + icon: "fa fa-exclamation-triangle", + message: gettext("Warning: At least one Provider has no application assigned."), + }); + } else { + return Promise.resolve({ + icon: "fa fa-check-circle" + }); + } + } + +} diff --git a/web/src/pages/admin-overview/cards/VersionStatusCard.ts b/web/src/pages/admin-overview/cards/VersionStatusCard.ts new file mode 100644 index 000000000..a61e6abd9 --- /dev/null +++ b/web/src/pages/admin-overview/cards/VersionStatusCard.ts @@ -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 { + + getPrimaryValue(): Promise { + return Version.get(); + } + + getStatus(value: Version): Promise { + if (value.outdated) { + return Promise.resolve({ + icon: "fa fa-exclamation-triangle", + message: gettext(`${value.version_latest} is available!`), + }); + } else { + return Promise.resolve({ + icon: "fa fa-check-circle", + message: gettext("Up-to-date!") + }); + } + } + +} diff --git a/web/src/pages/admin-overview/cards/WorkerStatusCard.ts b/web/src/pages/admin-overview/cards/WorkerStatusCard.ts new file mode 100644 index 000000000..fab55d433 --- /dev/null +++ b/web/src/pages/admin-overview/cards/WorkerStatusCard.ts @@ -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 { + + getPrimaryValue(): Promise { + return DefaultClient.fetch>(["admins", "workers"]).then((r) => { + return r.pagination.count; + }); + } + + getStatus(value: number): Promise { + if (value < 1) { + return Promise.resolve({ + icon: "fa fa-exclamation-triangle", + message: gettext("No workers connected. Background tasks will not run."), + }); + } else { + return Promise.resolve({ + icon: "fa fa-check-circle" + }); + } + } + +}