fix: Update org status banner on quota reached (#1956)
Fixes https://github.com/webrecorder/browsertrix/issues/1954 ### Changes - Refactors app state to include org data - Fixes banner not showing if storage or execution minutes is exceeded after page load - Disables closing banners - Refreshes org when tab changes --------- Co-authored-by: emma <hi@emma.cafe>
This commit is contained in:
parent
b35669af8d
commit
08147ec77d
@ -1,17 +1,16 @@
|
||||
import { localized, msg, str } from "@lit/localize";
|
||||
import { differenceInDays } from "date-fns/fp";
|
||||
import { html, type PropertyValues, type TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { html, type TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { TailwindElement } from "@/classes/TailwindElement";
|
||||
import { NavigateController } from "@/controllers/navigate";
|
||||
import { OrgReadOnlyReason, type OrgData } from "@/types/org";
|
||||
import { OrgReadOnlyReason } from "@/types/org";
|
||||
import { formatISODateString } from "@/utils/localization";
|
||||
import appState, { use } from "@/utils/state";
|
||||
|
||||
type Alert = {
|
||||
test: () => boolean;
|
||||
persist?: boolean;
|
||||
content: () => {
|
||||
title: string | TemplateResult;
|
||||
detail: string | TemplateResult;
|
||||
@ -21,64 +20,37 @@ type Alert = {
|
||||
@localized()
|
||||
@customElement("btrix-org-status-banner")
|
||||
export class OrgStatusBanner extends TailwindElement {
|
||||
@property({ type: Object })
|
||||
org?: OrgData;
|
||||
|
||||
@use()
|
||||
appState = appState;
|
||||
|
||||
@state()
|
||||
isAlertOpen = false;
|
||||
|
||||
private readonly navigate = new NavigateController(this);
|
||||
|
||||
private alert?: Alert;
|
||||
|
||||
protected willUpdate(_changedProperties: PropertyValues): void {
|
||||
if (_changedProperties.has("org") && this.org) {
|
||||
this.alert = this.alerts.find(({ test }) => test());
|
||||
|
||||
if (this.alert) {
|
||||
this.isAlertOpen = true;
|
||||
}
|
||||
}
|
||||
private get org() {
|
||||
return this.appState.org;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.org) return;
|
||||
|
||||
const alert = this.alerts.find(({ test }) => test());
|
||||
|
||||
if (!alert) return;
|
||||
|
||||
const content = alert.content();
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="${this.isAlertOpen
|
||||
? "bg-slate-100 border-b py-5"
|
||||
: ""} transition-all"
|
||||
>
|
||||
<div id="banner" class="border-b bg-slate-100 py-5">
|
||||
<div class="mx-auto box-border w-full max-w-screen-desktop px-3">
|
||||
<sl-alert
|
||||
variant="danger"
|
||||
?closable=${!this.alert?.persist}
|
||||
?open=${this.isAlertOpen}
|
||||
@sl-after-hide=${() => (this.isAlertOpen = false)}
|
||||
>
|
||||
<sl-alert variant="danger" open>
|
||||
<sl-icon slot="icon" name="exclamation-triangle-fill"></sl-icon>
|
||||
${this.renderContent()}
|
||||
<strong class="block font-semibold">${content.title}</strong>
|
||||
${content.detail}
|
||||
</sl-alert>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderContent() {
|
||||
if (!this.alert || !this.org) return;
|
||||
|
||||
const content = this.alert.content();
|
||||
|
||||
return html`
|
||||
<strong class="block font-semibold">${content.title}</strong>
|
||||
${content.detail}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerts ordered by priority
|
||||
*/
|
||||
@ -105,7 +77,7 @@ export class OrgStatusBanner extends TailwindElement {
|
||||
{
|
||||
test: () =>
|
||||
!readOnly && !readOnlyOnCancel && !!subscription?.futureCancelDate,
|
||||
persist: true,
|
||||
|
||||
content: () => {
|
||||
const daysDiff = differenceInDays(
|
||||
new Date(),
|
||||
@ -147,7 +119,7 @@ export class OrgStatusBanner extends TailwindElement {
|
||||
{
|
||||
test: () =>
|
||||
!readOnly && readOnlyOnCancel && !!subscription?.futureCancelDate,
|
||||
persist: true,
|
||||
|
||||
content: () => {
|
||||
const daysDiff = differenceInDays(
|
||||
new Date(),
|
||||
@ -187,7 +159,7 @@ export class OrgStatusBanner extends TailwindElement {
|
||||
{
|
||||
test: () =>
|
||||
!!readOnly && readOnlyReason === OrgReadOnlyReason.SubscriptionPaused,
|
||||
persist: true,
|
||||
|
||||
content: () => ({
|
||||
title: msg(str`Your org has been set to read-only mode`),
|
||||
detail: msg(
|
||||
@ -200,7 +172,7 @@ export class OrgStatusBanner extends TailwindElement {
|
||||
test: () =>
|
||||
!!readOnly &&
|
||||
readOnlyReason === OrgReadOnlyReason.SubscriptionCancelled,
|
||||
persist: true,
|
||||
|
||||
content: () => ({
|
||||
title: msg(str`This org has been set to read-only mode`),
|
||||
detail: msg(
|
||||
@ -210,7 +182,7 @@ export class OrgStatusBanner extends TailwindElement {
|
||||
},
|
||||
{
|
||||
test: () => !!readOnly,
|
||||
persist: true,
|
||||
|
||||
content: () => ({
|
||||
title: msg(str`This org has been set to read-only mode`),
|
||||
detail: msg(`Please contact Browsertrix support to renew your plan.`),
|
||||
|
||||
@ -84,7 +84,7 @@ export class Dashboard extends LiteElement {
|
||||
() =>
|
||||
html` <sl-icon-button
|
||||
href=${`${this.orgBasePath}/settings`}
|
||||
class="text-lg"
|
||||
class="size-8 text-lg"
|
||||
name="gear"
|
||||
label=${msg("Edit org settings")}
|
||||
@click=${this.navLink}
|
||||
@ -355,24 +355,26 @@ export class Dashboard extends LiteElement {
|
||||
}
|
||||
|
||||
private renderCrawlingMeter(_metrics: Metrics) {
|
||||
if (!this.org) return;
|
||||
|
||||
let quotaSeconds = 0;
|
||||
|
||||
if (this.org!.quotas.maxExecMinutesPerMonth) {
|
||||
quotaSeconds = this.org!.quotas.maxExecMinutesPerMonth * 60;
|
||||
if (this.org.quotas.maxExecMinutesPerMonth) {
|
||||
quotaSeconds = this.org.quotas.maxExecMinutesPerMonth * 60;
|
||||
}
|
||||
|
||||
let quotaSecondsAllTypes = quotaSeconds;
|
||||
|
||||
let quotaSecondsExtra = 0;
|
||||
if (this.org!.extraExecSecondsAvailable) {
|
||||
quotaSecondsExtra = this.org!.extraExecSecondsAvailable;
|
||||
quotaSecondsAllTypes += this.org!.extraExecSecondsAvailable;
|
||||
if (this.org.extraExecSecondsAvailable) {
|
||||
quotaSecondsExtra = this.org.extraExecSecondsAvailable;
|
||||
quotaSecondsAllTypes += this.org.extraExecSecondsAvailable;
|
||||
}
|
||||
|
||||
let quotaSecondsGifted = 0;
|
||||
if (this.org!.giftedExecSecondsAvailable) {
|
||||
quotaSecondsGifted = this.org!.giftedExecSecondsAvailable;
|
||||
quotaSecondsAllTypes += this.org!.giftedExecSecondsAvailable;
|
||||
if (this.org.giftedExecSecondsAvailable) {
|
||||
quotaSecondsGifted = this.org.giftedExecSecondsAvailable;
|
||||
quotaSecondsAllTypes += this.org.giftedExecSecondsAvailable;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
@ -381,8 +383,8 @@ export class Dashboard extends LiteElement {
|
||||
const currentPeriod = `${currentYear}-${currentMonth}` as YearMonth;
|
||||
|
||||
let usageSeconds = 0;
|
||||
if (this.org!.monthlyExecSeconds) {
|
||||
const actualUsage = this.org!.monthlyExecSeconds[currentPeriod];
|
||||
if (this.org.monthlyExecSeconds) {
|
||||
const actualUsage = this.org.monthlyExecSeconds[currentPeriod];
|
||||
if (actualUsage) {
|
||||
usageSeconds = actualUsage;
|
||||
}
|
||||
@ -393,21 +395,21 @@ export class Dashboard extends LiteElement {
|
||||
}
|
||||
|
||||
let usageSecondsAllTypes = 0;
|
||||
if (this.org!.crawlExecSeconds) {
|
||||
const actualUsage = this.org!.crawlExecSeconds[currentPeriod];
|
||||
if (this.org.crawlExecSeconds) {
|
||||
const actualUsage = this.org.crawlExecSeconds[currentPeriod];
|
||||
if (actualUsage) {
|
||||
usageSecondsAllTypes = actualUsage;
|
||||
}
|
||||
}
|
||||
|
||||
let usageSecondsExtra = 0;
|
||||
if (this.org!.extraExecSeconds) {
|
||||
const actualUsageExtra = this.org!.extraExecSeconds[currentPeriod];
|
||||
if (this.org.extraExecSeconds) {
|
||||
const actualUsageExtra = this.org.extraExecSeconds[currentPeriod];
|
||||
if (actualUsageExtra) {
|
||||
usageSecondsExtra = actualUsageExtra;
|
||||
}
|
||||
}
|
||||
const maxExecSecsExtra = this.org!.quotas.extraExecMinutes * 60;
|
||||
const maxExecSecsExtra = this.org.quotas.extraExecMinutes * 60;
|
||||
// Cap usage at quota for display purposes
|
||||
if (usageSecondsExtra > maxExecSecsExtra) {
|
||||
usageSecondsExtra = maxExecSecsExtra;
|
||||
@ -419,13 +421,13 @@ export class Dashboard extends LiteElement {
|
||||
}
|
||||
|
||||
let usageSecondsGifted = 0;
|
||||
if (this.org!.giftedExecSeconds) {
|
||||
const actualUsageGifted = this.org!.giftedExecSeconds[currentPeriod];
|
||||
if (this.org.giftedExecSeconds) {
|
||||
const actualUsageGifted = this.org.giftedExecSeconds[currentPeriod];
|
||||
if (actualUsageGifted) {
|
||||
usageSecondsGifted = actualUsageGifted;
|
||||
}
|
||||
}
|
||||
const maxExecSecsGifted = this.org!.quotas.giftedExecMinutes * 60;
|
||||
const maxExecSecsGifted = this.org.quotas.giftedExecMinutes * 60;
|
||||
// Cap usage at quota for display purposes
|
||||
if (usageSecondsGifted > maxExecSecsGifted) {
|
||||
usageSecondsGifted = maxExecSecsGifted;
|
||||
@ -447,9 +449,9 @@ export class Dashboard extends LiteElement {
|
||||
|
||||
const hasExtra =
|
||||
usageSecondsExtra ||
|
||||
this.org!.extraExecSecondsAvailable ||
|
||||
this.org.extraExecSecondsAvailable ||
|
||||
usageSecondsGifted ||
|
||||
this.org!.giftedExecSecondsAvailable;
|
||||
this.org.giftedExecSecondsAvailable;
|
||||
|
||||
const renderBar = (
|
||||
/** Time in Seconds */
|
||||
@ -502,14 +504,14 @@ export class Dashboard extends LiteElement {
|
||||
</div>
|
||||
`,
|
||||
() =>
|
||||
hasQuota
|
||||
hasQuota && this.org
|
||||
? html`
|
||||
<span class="inline-flex items-center">
|
||||
${humanizeExecutionSeconds(
|
||||
quotaSeconds -
|
||||
usageSeconds +
|
||||
this.org!.extraExecSecondsAvailable +
|
||||
this.org!.giftedExecSecondsAvailable,
|
||||
this.org.extraExecSecondsAvailable +
|
||||
this.org.giftedExecSecondsAvailable,
|
||||
{ style: "short", round: "down" },
|
||||
)}
|
||||
<span class="ml-1">${msg("remaining")}</span>
|
||||
@ -519,12 +521,12 @@ export class Dashboard extends LiteElement {
|
||||
)}
|
||||
</div>
|
||||
${when(
|
||||
hasQuota,
|
||||
() => html`
|
||||
hasQuota && this.org,
|
||||
(org) => html`
|
||||
<div class="mb-2">
|
||||
<btrix-meter
|
||||
value=${this.org!.giftedExecSecondsAvailable ||
|
||||
this.org!.extraExecSecondsAvailable ||
|
||||
value=${org.giftedExecSecondsAvailable ||
|
||||
org.extraExecSecondsAvailable ||
|
||||
isReached
|
||||
? quotaSecondsAllTypes
|
||||
: usageSeconds}
|
||||
@ -540,29 +542,25 @@ export class Dashboard extends LiteElement {
|
||||
hasExtra ? true : false,
|
||||
),
|
||||
)}
|
||||
${when(
|
||||
usageSecondsGifted || this.org!.giftedExecSecondsAvailable,
|
||||
() =>
|
||||
renderBar(
|
||||
usageSecondsGifted > quotaSecondsGifted
|
||||
? quotaSecondsGifted
|
||||
: usageSecondsGifted,
|
||||
quotaSecondsGifted,
|
||||
msg("Gifted Execution Time Used"),
|
||||
"blue",
|
||||
),
|
||||
${when(usageSecondsGifted || org.giftedExecSecondsAvailable, () =>
|
||||
renderBar(
|
||||
usageSecondsGifted > quotaSecondsGifted
|
||||
? quotaSecondsGifted
|
||||
: usageSecondsGifted,
|
||||
quotaSecondsGifted,
|
||||
msg("Gifted Execution Time Used"),
|
||||
"blue",
|
||||
),
|
||||
)}
|
||||
${when(
|
||||
usageSecondsExtra || this.org!.extraExecSecondsAvailable,
|
||||
() =>
|
||||
renderBar(
|
||||
usageSecondsExtra > quotaSecondsExtra
|
||||
? quotaSecondsExtra
|
||||
: usageSecondsExtra,
|
||||
quotaSecondsExtra,
|
||||
msg("Extra Execution Time Used"),
|
||||
"red",
|
||||
),
|
||||
${when(usageSecondsExtra || org.extraExecSecondsAvailable, () =>
|
||||
renderBar(
|
||||
usageSecondsExtra > quotaSecondsExtra
|
||||
? quotaSecondsExtra
|
||||
: usageSecondsExtra,
|
||||
quotaSecondsExtra,
|
||||
msg("Extra Execution Time Used"),
|
||||
"red",
|
||||
),
|
||||
)}
|
||||
<div slot="available" class="flex-1">
|
||||
<sl-tooltip class="text-center">
|
||||
@ -664,13 +662,15 @@ export class Dashboard extends LiteElement {
|
||||
`;
|
||||
|
||||
private readonly hasMonthlyTime = () =>
|
||||
Object.keys(this.org!.monthlyExecSeconds!).length;
|
||||
this.org?.monthlyExecSeconds &&
|
||||
Object.keys(this.org.monthlyExecSeconds).length;
|
||||
|
||||
private readonly hasExtraTime = () =>
|
||||
Object.keys(this.org!.extraExecSeconds!).length;
|
||||
this.org?.extraExecSeconds && Object.keys(this.org.extraExecSeconds).length;
|
||||
|
||||
private readonly hasGiftedTime = () =>
|
||||
Object.keys(this.org!.giftedExecSeconds!).length;
|
||||
this.org?.giftedExecSeconds &&
|
||||
Object.keys(this.org.giftedExecSeconds).length;
|
||||
|
||||
private renderUsageHistory() {
|
||||
if (!this.org) return;
|
||||
@ -750,34 +750,36 @@ export class Dashboard extends LiteElement {
|
||||
// Sort latest
|
||||
.reverse()
|
||||
.map(([mY, crawlTime]) => {
|
||||
let monthlySecondsUsed = this.org!.monthlyExecSeconds?.[mY] || 0;
|
||||
if (!this.org) return [];
|
||||
|
||||
let monthlySecondsUsed = this.org.monthlyExecSeconds?.[mY] || 0;
|
||||
let maxMonthlySeconds = 0;
|
||||
if (this.org!.quotas.maxExecMinutesPerMonth) {
|
||||
maxMonthlySeconds = this.org!.quotas.maxExecMinutesPerMonth * 60;
|
||||
if (this.org.quotas.maxExecMinutesPerMonth) {
|
||||
maxMonthlySeconds = this.org.quotas.maxExecMinutesPerMonth * 60;
|
||||
}
|
||||
if (monthlySecondsUsed > maxMonthlySeconds) {
|
||||
monthlySecondsUsed = maxMonthlySeconds;
|
||||
}
|
||||
|
||||
let extraSecondsUsed = this.org!.extraExecSeconds?.[mY] || 0;
|
||||
let extraSecondsUsed = this.org.extraExecSeconds?.[mY] || 0;
|
||||
let maxExtraSeconds = 0;
|
||||
if (this.org!.quotas.extraExecMinutes) {
|
||||
maxExtraSeconds = this.org!.quotas.extraExecMinutes * 60;
|
||||
if (this.org.quotas.extraExecMinutes) {
|
||||
maxExtraSeconds = this.org.quotas.extraExecMinutes * 60;
|
||||
}
|
||||
if (extraSecondsUsed > maxExtraSeconds) {
|
||||
extraSecondsUsed = maxExtraSeconds;
|
||||
}
|
||||
|
||||
let giftedSecondsUsed = this.org!.giftedExecSeconds?.[mY] || 0;
|
||||
let giftedSecondsUsed = this.org.giftedExecSeconds?.[mY] || 0;
|
||||
let maxGiftedSeconds = 0;
|
||||
if (this.org!.quotas.giftedExecMinutes) {
|
||||
maxGiftedSeconds = this.org!.quotas.giftedExecMinutes * 60;
|
||||
if (this.org.quotas.giftedExecMinutes) {
|
||||
maxGiftedSeconds = this.org.quotas.giftedExecMinutes * 60;
|
||||
}
|
||||
if (giftedSecondsUsed > maxGiftedSeconds) {
|
||||
giftedSecondsUsed = maxGiftedSeconds;
|
||||
}
|
||||
|
||||
let totalSecondsUsed = this.org!.crawlExecSeconds?.[mY] || 0;
|
||||
let totalSecondsUsed = this.org.crawlExecSeconds?.[mY] || 0;
|
||||
const totalMaxQuota =
|
||||
maxMonthlySeconds + maxExtraSeconds + maxGiftedSeconds;
|
||||
if (totalSecondsUsed > totalMaxQuota) {
|
||||
|
||||
@ -24,6 +24,7 @@ import type { AuthState } from "@/utils/AuthService";
|
||||
import { DEFAULT_MAX_SCALE } from "@/utils/crawler";
|
||||
import LiteElement, { html } from "@/utils/LiteElement";
|
||||
import { isAdmin, isCrawler, type OrgData } from "@/utils/orgs";
|
||||
import { AppStateService } from "@/utils/state";
|
||||
|
||||
import "./workflow-detail";
|
||||
import "./workflows-list";
|
||||
@ -113,30 +114,12 @@ export class Org extends LiteElement {
|
||||
@property({ type: Number })
|
||||
maxScale: number = DEFAULT_MAX_SCALE;
|
||||
|
||||
@state()
|
||||
private orgStorageQuotaReached = false;
|
||||
|
||||
@state()
|
||||
private showReadOnlyAlert = false;
|
||||
|
||||
@state()
|
||||
private showStorageQuotaAlert = false;
|
||||
|
||||
@state()
|
||||
private orgExecutionMinutesQuotaReached = false;
|
||||
|
||||
@state()
|
||||
private showExecutionMinutesQuotaAlert = false;
|
||||
|
||||
@state()
|
||||
private openDialogName?: ResourceName;
|
||||
|
||||
@state()
|
||||
private isCreateDialogVisible = false;
|
||||
|
||||
@state()
|
||||
private org?: OrgData | null;
|
||||
|
||||
get userOrg() {
|
||||
if (!this.userInfo) return null;
|
||||
return this.userInfo.orgs.find(({ slug }) => slug === this.slug)!;
|
||||
@ -146,6 +129,10 @@ export class Org extends LiteElement {
|
||||
return this.userOrg?.id || "";
|
||||
}
|
||||
|
||||
get org() {
|
||||
return this.appState.org;
|
||||
}
|
||||
|
||||
get isAdmin() {
|
||||
const userOrg = this.userOrg;
|
||||
if (userOrg) return isAdmin(userOrg.role);
|
||||
@ -168,7 +155,6 @@ export class Org extends LiteElement {
|
||||
"btrix-storage-quota-update",
|
||||
this.onStorageQuotaUpdate,
|
||||
);
|
||||
this.addEventListener("", () => {});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@ -202,6 +188,9 @@ export class Org extends LiteElement {
|
||||
|
||||
return;
|
||||
}
|
||||
} else if (changedProperties.has("orgTab") && this.orgId) {
|
||||
// Get most up to date org data
|
||||
void this.updateOrg();
|
||||
}
|
||||
if (changedProperties.has("openDialogName")) {
|
||||
// Sync URL to create dialog
|
||||
@ -231,15 +220,11 @@ export class Org extends LiteElement {
|
||||
|
||||
if (!this.userInfo || !this.orgId) return;
|
||||
try {
|
||||
this.org = await this.getOrg(this.orgId);
|
||||
const org = await this.getOrg(this.orgId);
|
||||
|
||||
this.showReadOnlyAlert = Boolean(
|
||||
this.org?.readOnly || this.org?.subscription?.futureCancelDate,
|
||||
);
|
||||
|
||||
this.checkStorageQuota();
|
||||
this.checkExecutionMinutesQuota();
|
||||
} catch {
|
||||
AppStateService.updateOrg(org);
|
||||
} catch (e) {
|
||||
console.debug(e);
|
||||
this.notify({
|
||||
message: msg("Sorry, couldn't retrieve organization at this time."),
|
||||
variant: "danger",
|
||||
@ -319,7 +304,7 @@ export class Org extends LiteElement {
|
||||
|
||||
return html`
|
||||
<div class="flex min-h-full flex-col">
|
||||
<btrix-org-status-banner .org=${this.org}></btrix-org-status-banner>
|
||||
<btrix-org-status-banner></btrix-org-status-banner>
|
||||
${this.renderOrgNavBar()}
|
||||
<main
|
||||
class="${noMaxWidth
|
||||
@ -523,7 +508,7 @@ export class Org extends LiteElement {
|
||||
.authState=${this.authState!}
|
||||
userId=${this.userInfo!.id}
|
||||
orgId=${this.orgId}
|
||||
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
||||
?orgStorageQuotaReached=${this.org?.storageQuotaReached}
|
||||
?isCrawler=${this.isCrawler}
|
||||
itemType=${ifDefined(params.itemType || undefined)}
|
||||
@select-new-dialog=${this.onSelectNewDialog}
|
||||
@ -543,9 +528,8 @@ export class Org extends LiteElement {
|
||||
class="col-span-5 mt-6"
|
||||
.authState=${this.authState!}
|
||||
orgId=${this.orgId}
|
||||
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
||||
?orgExecutionMinutesQuotaReached=${this
|
||||
.orgExecutionMinutesQuotaReached}
|
||||
?orgStorageQuotaReached=${this.org?.storageQuotaReached}
|
||||
?orgExecutionMinutesQuotaReached=${this.org?.execMinutesQuotaReached}
|
||||
workflowId=${workflowId}
|
||||
openDialogName=${this.viewStateData?.dialog}
|
||||
?isEditing=${isEditing}
|
||||
@ -566,8 +550,8 @@ export class Org extends LiteElement {
|
||||
.initialWorkflow=${workflow}
|
||||
.initialSeeds=${seeds}
|
||||
jobType=${ifDefined(params.jobType)}
|
||||
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
||||
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
|
||||
?orgStorageQuotaReached=${this.org?.storageQuotaReached}
|
||||
?orgExecutionMinutesQuotaReached=${this.org?.execMinutesQuotaReached}
|
||||
@select-new-dialog=${this.onSelectNewDialog}
|
||||
></btrix-workflows-new>`;
|
||||
}
|
||||
@ -575,8 +559,8 @@ export class Org extends LiteElement {
|
||||
return html`<btrix-workflows-list
|
||||
.authState=${this.authState!}
|
||||
orgId=${this.orgId}
|
||||
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
||||
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
|
||||
?orgStorageQuotaReached=${this.org?.storageQuotaReached}
|
||||
?orgExecutionMinutesQuotaReached=${this.org?.execMinutesQuotaReached}
|
||||
userId=${this.userInfo!.id}
|
||||
?isCrawler=${this.isCrawler}
|
||||
@select-new-dialog=${this.onSelectNewDialog}
|
||||
@ -686,22 +670,26 @@ export class Org extends LiteElement {
|
||||
|
||||
private async onStorageQuotaUpdate(e: CustomEvent<QuotaUpdateDetail>) {
|
||||
e.stopPropagation();
|
||||
|
||||
const { reached } = e.detail;
|
||||
this.orgStorageQuotaReached = reached;
|
||||
if (reached) {
|
||||
this.showStorageQuotaAlert = true;
|
||||
}
|
||||
|
||||
AppStateService.partialUpdateOrg({
|
||||
id: this.orgId,
|
||||
storageQuotaReached: reached,
|
||||
});
|
||||
}
|
||||
|
||||
private async onExecutionMinutesQuotaUpdate(
|
||||
e: CustomEvent<QuotaUpdateDetail>,
|
||||
) {
|
||||
e.stopPropagation();
|
||||
|
||||
const { reached } = e.detail;
|
||||
this.orgExecutionMinutesQuotaReached = reached;
|
||||
if (reached) {
|
||||
this.showExecutionMinutesQuotaAlert = true;
|
||||
}
|
||||
|
||||
AppStateService.partialUpdateOrg({
|
||||
id: this.orgId,
|
||||
execMinutesQuotaReached: reached,
|
||||
});
|
||||
}
|
||||
|
||||
private async onUserRoleChange(e: UserRoleChangeEvent) {
|
||||
@ -723,7 +711,9 @@ export class Org extends LiteElement {
|
||||
variant: "success",
|
||||
icon: "check2-circle",
|
||||
});
|
||||
this.org = await this.getOrg(this.orgId);
|
||||
const org = await this.getOrg(this.orgId);
|
||||
|
||||
AppStateService.updateOrg(org);
|
||||
} catch (e) {
|
||||
console.debug(e);
|
||||
|
||||
@ -742,14 +732,14 @@ export class Org extends LiteElement {
|
||||
}
|
||||
|
||||
private async removeMember(member: Member) {
|
||||
if (!this.org) return;
|
||||
const org = this.org;
|
||||
if (!org) return;
|
||||
|
||||
const isSelf = member.email === this.userInfo!.email;
|
||||
if (
|
||||
isSelf &&
|
||||
!window.confirm(
|
||||
msg(
|
||||
str`Are you sure you want to remove yourself from ${this.org.name}?`,
|
||||
),
|
||||
msg(str`Are you sure you want to remove yourself from ${org.name}?`),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
@ -766,7 +756,7 @@ export class Org extends LiteElement {
|
||||
this.notify({
|
||||
message: msg(
|
||||
str`Successfully removed ${member.name || member.email} from ${
|
||||
this.org.name
|
||||
org.name
|
||||
}.`,
|
||||
),
|
||||
variant: "success",
|
||||
@ -776,7 +766,9 @@ export class Org extends LiteElement {
|
||||
// FIXME better UX, this is the only page currently that doesn't require org...
|
||||
this.navTo("/account/settings");
|
||||
} else {
|
||||
this.org = await this.getOrg(this.orgId);
|
||||
const org = await this.getOrg(this.orgId);
|
||||
|
||||
AppStateService.updateOrg(org);
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug(e);
|
||||
@ -794,14 +786,4 @@ export class Org extends LiteElement {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
checkStorageQuota() {
|
||||
this.orgStorageQuotaReached = !!this.org?.storageQuotaReached;
|
||||
this.showStorageQuotaAlert = this.orgStorageQuotaReached;
|
||||
}
|
||||
|
||||
checkExecutionMinutesQuota() {
|
||||
this.orgExecutionMinutesQuotaReached = !!this.org?.execMinutesQuotaReached;
|
||||
this.showExecutionMinutesQuotaAlert = this.orgExecutionMinutesQuotaReached;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { locked, options, use } from "lit-shared-state";
|
||||
import { persist } from "./persist";
|
||||
|
||||
import type { AppSettings } from "@/types/app";
|
||||
import type { OrgData } from "@/types/org";
|
||||
import type { CurrentUser } from "@/types/user";
|
||||
|
||||
export { use };
|
||||
@ -20,6 +21,7 @@ type SlugLookup = Record<string, string>;
|
||||
class AppState {
|
||||
settings: AppSettings | null = null;
|
||||
userInfo: CurrentUser | null = null;
|
||||
org: OrgData | null | undefined = undefined;
|
||||
|
||||
@options(persist(window.localStorage))
|
||||
orgSlug: string | null = null;
|
||||
@ -57,6 +59,23 @@ export class AppStateService {
|
||||
appState.userInfo = userInfo;
|
||||
});
|
||||
};
|
||||
static updateOrg = (org: AppState["org"]) => {
|
||||
unlock(() => {
|
||||
appState.org = org;
|
||||
});
|
||||
};
|
||||
static partialUpdateOrg = (org: { id: string } & Partial<OrgData>) => {
|
||||
unlock(() => {
|
||||
if (org.id && appState.org?.id === org.id) {
|
||||
appState.org = {
|
||||
...appState.org,
|
||||
...org,
|
||||
};
|
||||
} else {
|
||||
console.warn("no matching org in app state");
|
||||
}
|
||||
});
|
||||
};
|
||||
static updateOrgSlug = (orgSlug: AppState["orgSlug"]) => {
|
||||
unlock(() => {
|
||||
appState.orgSlug = orgSlug;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user