Add superadmin instance stats card (#2404)
Closes #2401 https://github.com/user-attachments/assets/cbd288d7-8e9c-4e86-ae87-6a308f6bdd58
This commit is contained in:
parent
f1dc790ab4
commit
905fe059a4
1
frontend/src/features/admin/index.ts
Normal file
1
frontend/src/features/admin/index.ts
Normal file
@ -0,0 +1 @@
|
||||
import "./stats";
|
219
frontend/src/features/admin/stats.ts
Normal file
219
frontend/src/features/admin/stats.ts
Normal file
@ -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<string>();
|
||||
const activeUsersSet = new Set<string>();
|
||||
|
||||
// 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`<ul
|
||||
class="mb-4 grid grid-cols-[auto_1fr] items-baseline justify-items-end gap-x-2 p-3 text-xl *:contents md:rounded-lg md:border md:bg-white md:px-8"
|
||||
>
|
||||
<li>
|
||||
<sl-tooltip placement="left">
|
||||
<span class="font-bold">${this.localize.number(orgs.active)}</span>
|
||||
<span
|
||||
slot="content"
|
||||
class="grid grid-cols-[1fr_auto] gap-x-1 text-right text-neutral-300"
|
||||
>
|
||||
${msg("Total orgs")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(orgs.all)}`}</span
|
||||
>
|
||||
${msg("Inactive orgs")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(orgs.all - orgs.active)}`}</span
|
||||
>
|
||||
</span>
|
||||
</sl-tooltip>
|
||||
<span class="justify-self-start text-xs text-neutral-600">
|
||||
${msg("Active Orgs")}
|
||||
<sl-tooltip content=${msg("Orgs that are not read-only")}
|
||||
><sl-icon class="align-[-2px]" name="info-circle"></sl-icon
|
||||
></sl-tooltip>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<sl-tooltip placement="left">
|
||||
<span class="font-bold">${this.localize.number(users.active)}</span>
|
||||
<span
|
||||
slot="content"
|
||||
class="grid grid-cols-[1fr_auto] gap-x-1 text-right text-neutral-300"
|
||||
>
|
||||
${msg("Total users")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(users.all)}`}</span
|
||||
>
|
||||
${msg("Inactive users")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(
|
||||
users.all - users.active,
|
||||
)}`}</span
|
||||
>
|
||||
</span>
|
||||
</sl-tooltip>
|
||||
<span class="justify-self-start text-xs text-neutral-600">
|
||||
${msg("Active Users")}
|
||||
<sl-tooltip content=${msg("Users in orgs that are not read-only")}
|
||||
><sl-icon class="align-[-2px]" name="info-circle"></sl-icon
|
||||
></sl-tooltip>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<sl-tooltip placement="left">
|
||||
<span class="font-bold"
|
||||
>${this.localize.number(subscriptions.active)}
|
||||
</span>
|
||||
<span
|
||||
slot="content"
|
||||
class="grid grid-cols-[1fr_auto] gap-x-1 text-right text-neutral-300"
|
||||
>
|
||||
${msg("Active subscriptions")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(subscriptions.active)}`}</span
|
||||
>
|
||||
${msg("Trialing subscriptions")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(subscriptions.trialing)}`}</span
|
||||
>
|
||||
${msg("Cancelled trialing subscriptions")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(
|
||||
subscriptions.trialingCancelled,
|
||||
)}`}</span
|
||||
>
|
||||
${msg("Paused (payment failed) subscriptions")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(
|
||||
subscriptions.pausedPaymentFailed,
|
||||
)}`}</span
|
||||
>
|
||||
${msg("Cancelled subscriptions")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(subscriptions.cancelled)}`}</span
|
||||
>
|
||||
<hr class="col-span-2 -mx-2 my-1 border-neutral-500" />
|
||||
${msg("Total subscriptions (all states)")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.number(subscriptions.total)}`}</span
|
||||
>
|
||||
</span>
|
||||
</sl-tooltip>
|
||||
<span class="justify-self-start text-xs text-neutral-600">
|
||||
${msg("Active Subscriptions")}
|
||||
<sl-tooltip
|
||||
content=${msg(
|
||||
"Orgs with active subscriptions (including with future cancellation dates)",
|
||||
)}
|
||||
><sl-icon class="align-[-2px]" name="info-circle"></sl-icon
|
||||
></sl-tooltip>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<sl-tooltip placement="left">
|
||||
<span class="text-xl font-bold"
|
||||
>${this.localize.bytes(storage.total)}
|
||||
</span>
|
||||
<span
|
||||
slot="content"
|
||||
class="grid grid-cols-[1fr_auto] gap-x-1 text-right text-neutral-300"
|
||||
>
|
||||
${msg("Storage in active orgs")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.bytes(storage.active)}`}</span
|
||||
>
|
||||
${msg("Storage in inactive orgs")}:
|
||||
<span class="text-left font-bold text-white"
|
||||
>${html`${this.localize.bytes(
|
||||
storage.total - storage.active,
|
||||
)}`}</span
|
||||
>
|
||||
</span>
|
||||
</sl-tooltip>
|
||||
<span class="justify-self-start text-xs text-neutral-600">
|
||||
${msg("Data Stored")}
|
||||
<sl-tooltip content=${msg("Across all orgs")}
|
||||
><sl-icon class="align-[-2px]" name="info-circle"></sl-icon
|
||||
></sl-tooltip>
|
||||
</span>
|
||||
</li>
|
||||
</ul>`;
|
||||
});
|
||||
}
|
||||
}
|
@ -5,3 +5,5 @@ import "./collections";
|
||||
import "./crawl-workflows";
|
||||
import "./org";
|
||||
import "./qa";
|
||||
|
||||
import("./admin");
|
||||
|
@ -156,6 +156,9 @@ export class Admin extends BtrixElement {
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-span-3 md:col-span-1">
|
||||
<btrix-instance-stats
|
||||
.orgList=${this.orgList ?? []}
|
||||
></btrix-instance-stats>
|
||||
<section class="p-3 md:rounded-lg md:border md:bg-white md:p-8">
|
||||
<h2 class="mb-3 text-lg font-medium">
|
||||
${msg("Invite User to Org")}
|
||||
|
Loading…
Reference in New Issue
Block a user