diff --git a/frontend/src/components/orgs-list.ts b/frontend/src/components/orgs-list.ts
index 6078e80c..657fb5aa 100644
--- a/frontend/src/components/orgs-list.ts
+++ b/frontend/src/components/orgs-list.ts
@@ -1,16 +1,19 @@
import { localized, msg, str } from "@lit/localize";
import type { SlInput } from "@shoelace-style/shoelace";
-import { type TemplateResult } from "lit";
-import { customElement, property } from "lit/decorators.js";
+import { html, type TemplateResult } from "lit";
+import { customElement, property, query } from "lit/decorators.js";
import { when } from "lit/directives/when.js";
+import { TailwindElement } from "@/classes/TailwindElement";
+import type { Dialog } from "@/components/ui/dialog";
+import { APIController } from "@/controllers/api";
+import { NavigateController } from "@/controllers/navigate";
import type { CurrentUser, UserOrg } from "@/types/user";
-import LiteElement, { html } from "@/utils/LiteElement";
import type { OrgData } from "@/utils/orgs";
@localized()
@customElement("btrix-orgs-list")
-export class OrgsList extends LiteElement {
+export class OrgsList extends TailwindElement {
@property({ type: Object })
userInfo?: CurrentUser;
@@ -23,6 +26,15 @@ export class OrgsList extends LiteElement {
@property({ type: Object })
currOrg?: OrgData | null = null;
+ @query("#orgDeleteDialog")
+ orgDeleteDialog?: Dialog | null;
+
+ @query("#orgQuotaDialog")
+ orgQuotaDialog?: Dialog | null;
+
+ private readonly api = new APIController(this);
+ private readonly navigate = new NavigateController(this);
+
render() {
if (this.skeleton) {
return this.renderSkeleton();
@@ -33,17 +45,108 @@ export class OrgsList extends LiteElement {
return html`
${this.orgList?.map(this.renderOrg(defaultOrg))}
+ ${this.renderOrgDelete()}
${this.renderOrgQuotas()}
`;
}
+ private renderOrgDelete() {
+ return html`
+ (this.currOrg = null)}
+ >
+ ${when(this.currOrg, (org) => {
+ const confirmationStr = msg(str`Delete ${org.name}`);
+ return html`
+
+ ${msg(
+ html`Are you sure you want to delete
+
+ ${org.name}
+ ? This cannot be undone.`,
+ )}
+
+
+ - ${msg(str`Slug: ${org.slug}`)}
+ -
+ ${msg(
+ str`Members: ${Object.keys(org.users || {}).length.toLocaleString()}`,
+ )}
+
+
+
+ ${msg(
+ html`Deleting an org will delete all
+
+
+
+ of data associated with the org.`,
+ )}
+
+
+ -
+ ${msg(
+ html`Crawls:
+ `,
+ )}
+
+ -
+ ${msg(
+ html`Uploads:
+ `,
+ )}
+
+ -
+ ${msg(
+ html`Profiles:
+ `,
+ )}
+
+
+
+
+
+ ${msg(str`Type "${confirmationStr}" to confirm`)}
+
+
+ `;
+ })}
+
+ ${msg("Delete Org")}
+
+
+
+ `;
+ }
+
private renderOrgQuotas() {
return html`
(this.currOrg = null)}
+ @sl-after-hide=${() => (this.currOrg = null)}
>
${when(this.currOrg?.quotas, (quotas) =>
Object.entries(quotas).map(([key, value]) => {
@@ -72,6 +175,7 @@ export class OrgsList extends LiteElement {
label = msg("Unlabeled");
}
return html` {
- e.preventDefault();
- e.stopPropagation();
- this.currOrg = org;
- return false;
- };
-
- return stop;
}
private readonly renderOrg = (defaultOrg?: UserOrg) => (org: OrgData) => {
@@ -158,12 +250,31 @@ export class OrgsList extends LiteElement {
? msg(`1 member`)
: msg(str`${memberCount} members`)}
-
+ e.stopPropagation()}
+ >
+
+ {
+ this.currOrg = org;
+ void this.orgQuotaDialog?.show();
+ }}
+ >
+
+ ${msg("Edit Quotas")}
+
+ {
+ this.currOrg = org;
+ void this.orgDeleteDialog?.show();
+ }}
+ >
+
+ ${msg("Delete Org")}
+
+
+
`;
@@ -180,7 +291,7 @@ export class OrgsList extends LiteElement {
}
private makeOnOrgClick(org: OrgData) {
- const navigate = () => this.navTo(`/orgs/${org.slug}`);
+ const navigate = () => this.navigate.to(`/orgs/${org.slug}`);
if (typeof window.getSelection !== "undefined") {
return () => {
diff --git a/frontend/src/types/org.ts b/frontend/src/types/org.ts
index 890ab5c0..d3020d94 100644
--- a/frontend/src/types/org.ts
+++ b/frontend/src/types/org.ts
@@ -19,6 +19,9 @@ export type OrgData = {
slug: string;
quotas?: Record;
bytesStored: number;
+ bytesStoredCrawls: number;
+ bytesStoredUploads: number;
+ bytesStoredProfiles: number;
usage: { [key: YearMonth]: number } | null;
crawlExecSeconds?: { [key: YearMonth]: number };
monthlyExecSeconds?: { [key: YearMonth]: number };