diff --git a/frontend/src/features/admin/index.ts b/frontend/src/features/admin/index.ts new file mode 100644 index 00000000..526b8901 --- /dev/null +++ b/frontend/src/features/admin/index.ts @@ -0,0 +1 @@ +import "./stats"; diff --git a/frontend/src/features/admin/stats.ts b/frontend/src/features/admin/stats.ts new file mode 100644 index 00000000..b950cbf2 --- /dev/null +++ b/frontend/src/features/admin/stats.ts @@ -0,0 +1,219 @@ +import { localized, msg } from "@lit/localize"; +import { html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { guard } from "lit/directives/guard.js"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import { SubscriptionStatus } from "@/types/billing"; +import type { OrgData } from "@/types/org"; + +export function computeStats(orgData: OrgData[] = []) { + // orgs + const orgs = { all: orgData.length, active: 0 }; + + // users + const allUsersSet = new Set(); + const activeUsersSet = new Set(); + + // subscriptions + const subscriptions = { + total: 0, + active: 0, + trialing: 0, + trialingCancelled: 0, + pausedPaymentFailed: 0, + cancelled: 0, + }; + + // storage + const storage = { total: 0, active: 0 }; + + orgData.forEach((org) => { + Object.keys(org.users ?? {}).forEach((user) => allUsersSet.add(user)); + if (!org.readOnly) { + orgs.active++; + Object.keys(org.users ?? {}).forEach((user) => activeUsersSet.add(user)); + storage.active += org.bytesStored; + } + if (org.subscription) { + subscriptions.total++; + switch (org.subscription.status) { + case SubscriptionStatus.Active: + subscriptions.active++; + break; + case SubscriptionStatus.Trialing: + subscriptions.trialing++; + break; + case SubscriptionStatus.TrialingCanceled: + subscriptions.trialingCancelled++; + break; + case SubscriptionStatus.PausedPaymentFailed: + subscriptions.pausedPaymentFailed++; + break; + case SubscriptionStatus.Cancelled: + subscriptions.cancelled++; + break; + } + } + + storage.total += org.bytesStored; + }); + + return { + orgs, + users: { + all: allUsersSet.size, + active: activeUsersSet.size, + }, + subscriptions, + storage, + }; +} + +@customElement("btrix-instance-stats") +@localized() +export class Component extends BtrixElement { + @property({ type: Array }) + orgList: OrgData[] = []; + + render() { + return guard([this.orgList], () => { + const { orgs, users, subscriptions, storage } = computeStats( + this.orgList, + ); + + return html``; + }); + } +} diff --git a/frontend/src/features/index.ts b/frontend/src/features/index.ts index 3b0a50aa..635b4a96 100644 --- a/frontend/src/features/index.ts +++ b/frontend/src/features/index.ts @@ -5,3 +5,5 @@ import "./collections"; import "./crawl-workflows"; import "./org"; import "./qa"; + +import("./admin"); diff --git a/frontend/src/pages/admin.ts b/frontend/src/pages/admin.ts index cfa343e3..d7599d51 100644 --- a/frontend/src/pages/admin.ts +++ b/frontend/src/pages/admin.ts @@ -156,6 +156,9 @@ export class Admin extends BtrixElement {
+

${msg("Invite User to Org")}