this.runNow()}
>
@@ -1107,13 +1122,17 @@ export class WorkflowDetail extends LiteElement {
this.runNow()}
>
@@ -1594,6 +1613,10 @@ export class WorkflowDetail extends LiteElement {
if (e.isApiError && e.statusCode === 403) {
if (e.details === "storage_quota_reached") {
message = msg("Your org does not have enough storage to run crawls.");
+ } else if (e.details === "exec_minutes_quota_reached") {
+ message = msg(
+ "Your org has used all of its execution minutes for this month."
+ );
} else {
message = msg("You do not have permission to run crawls.");
}
diff --git a/frontend/src/pages/org/workflow-editor.ts b/frontend/src/pages/org/workflow-editor.ts
index 67b19e7738..26aea10bbe 100644
--- a/frontend/src/pages/org/workflow-editor.ts
+++ b/frontend/src/pages/org/workflow-editor.ts
@@ -242,6 +242,12 @@ export class CrawlConfigEditor extends LiteElement {
@property({ type: Array })
initialSeeds?: Seed[];
+ @property({ type: Boolean })
+ orgStorageQuotaReached = false;
+
+ @property({ type: Boolean })
+ orgExecutionMinutesQuotaReached = false;
+
@state()
private tagOptions: string[] = [];
@@ -535,7 +541,10 @@ export class CrawlConfigEditor extends LiteElement {
lang: this.initialWorkflow.config.lang,
scheduleType: defaultFormState.scheduleType,
scheduleFrequency: defaultFormState.scheduleFrequency,
- runNow: defaultFormState.runNow,
+ runNow:
+ this.orgStorageQuotaReached || this.orgExecutionMinutesQuotaReached
+ ? false
+ : defaultFormState.runNow,
tags: this.initialWorkflow.tags,
autoAddCollections: this.initialWorkflow.autoAddCollections,
jobName: this.initialWorkflow.name || defaultFormState.jobName,
@@ -864,6 +873,8 @@ export class CrawlConfigEditor extends LiteElement {
{
this.updateFormState(
{
@@ -2158,37 +2169,28 @@ https://archiveweb.page/images/${"logo.svg"}`}
body: JSON.stringify(config),
}));
- const crawlId = data.run_now_job;
+ const crawlId = data.run_now_job || data.started || null;
const storageQuotaReached = data.storageQuotaReached;
+ const executionMinutesQuotaReached = data.execMinutesQuotaReached;
- if (crawlId && storageQuotaReached) {
- this.notify({
- title: msg("Workflow saved without starting crawl."),
- message: msg(
- "Could not run crawl with new workflow settings due to storage quota."
- ),
- variant: "warning",
- icon: "exclamation-circle",
- duration: 12000,
- });
- } else {
- let message = msg("Workflow created.");
- if (crawlId) {
- message = msg("Crawl started with new workflow settings.");
- } else if (this.configId) {
- message = msg("Workflow updated.");
- }
-
- this.notify({
- message,
- variant: "success",
- icon: "check2-circle",
- });
+ let message = msg("Workflow created.");
+ if (crawlId) {
+ message = msg("Crawl started with new workflow settings.");
+ } else if (this.configId) {
+ message = msg("Workflow updated.");
}
+ this.notify({
+ message,
+ variant: "success",
+ icon: "check2-circle",
+ });
+
this.navTo(
`${this.orgBasePath}/workflows/crawl/${this.configId || data.id}${
- crawlId && !storageQuotaReached ? "#watch" : ""
+ crawlId && !storageQuotaReached && !executionMinutesQuotaReached
+ ? "#watch"
+ : ""
}`
);
} catch (e: any) {
diff --git a/frontend/src/pages/org/workflows-list.ts b/frontend/src/pages/org/workflows-list.ts
index d81d212852..14b3db710e 100644
--- a/frontend/src/pages/org/workflows-list.ts
+++ b/frontend/src/pages/org/workflows-list.ts
@@ -74,6 +74,9 @@ export class WorkflowsList extends LiteElement {
@property({ type: Boolean })
orgStorageQuotaReached = false;
+ @property({ type: Boolean })
+ orgExecutionMinutesQuotaReached = false;
+
@property({ type: String })
userId!: string;
@@ -440,7 +443,8 @@ export class WorkflowsList extends LiteElement {
() => html`
this.runNow(workflow)}
>
@@ -798,6 +802,10 @@ export class WorkflowsList extends LiteElement {
if (e.isApiError && e.statusCode === 403) {
if (e.details === "storage_quota_reached") {
message = msg("Your org does not have enough storage to run crawls.");
+ } else if (e.details === "exec_minutes_quota_reached") {
+ message = msg(
+ "Your org has used all of its execution minutes for this month."
+ );
} else {
message = msg("You do not have permission to run crawls.");
}
diff --git a/frontend/src/pages/org/workflows-new.ts b/frontend/src/pages/org/workflows-new.ts
index d32d9315df..8a28aaf3a0 100644
--- a/frontend/src/pages/org/workflows-new.ts
+++ b/frontend/src/pages/org/workflows-new.ts
@@ -56,6 +56,12 @@ export class WorkflowsNew extends LiteElement {
@property({ type: String })
jobType?: JobType;
+ @property({ type: Boolean })
+ orgStorageQuotaReached = false;
+
+ @property({ type: Boolean })
+ orgExecutionMinutesQuotaReached = false;
+
// Use custom property accessor to prevent
// overriding default Workflow values
@property({ type: Object })
@@ -116,6 +122,9 @@ export class WorkflowsNew extends LiteElement {
jobType=${jobType}
orgId=${this.orgId}
.authState=${this.authState}
+ ?orgStorageQuotaReached=${this.orgStorageQuotaReached}
+ ?orgExecutionMinutesQuotaReached=${this
+ .orgExecutionMinutesQuotaReached}
@reset=${async (e: Event) => {
await (e.target as LitElement).updateComplete;
this.dispatchEvent(
diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts
index 0baf97ed5b..18f7bd4745 100644
--- a/frontend/src/routes.ts
+++ b/frontend/src/routes.ts
@@ -17,7 +17,7 @@ export const ROUTES = {
"(/items(/:itemType(/:itemId)))",
"(/collections(/new)(/view/:collectionId(/:collectionTab))(/edit/:collectionId))",
"(/browser-profiles(/profile(/browser/:browserId)(/:browserProfileId)))",
- "(/settings(/members))",
+ "(/settings(/:settingsTab))",
].join(""),
users: "/users",
usersInvite: "/users/invite",
diff --git a/frontend/src/types/crawler.ts b/frontend/src/types/crawler.ts
index 0a1b28210f..3fa472119e 100644
--- a/frontend/src/types/crawler.ts
+++ b/frontend/src/types/crawler.ts
@@ -135,6 +135,7 @@ export type Crawl = CrawlConfig & {
collectionIds: string[];
collections: { id: string; name: string }[];
type?: "crawl" | "upload" | null;
+ crawlExecSeconds: number;
};
export type Upload = Omit<
@@ -146,6 +147,7 @@ export type Upload = Omit<
| "stopping"
| "firstSeed"
| "seedCount"
+ | "crawlExecSeconds"
> & {
type: "upload";
};
diff --git a/frontend/src/types/org.ts b/frontend/src/types/org.ts
index 0c2c6fd793..586ffb23cc 100644
--- a/frontend/src/types/org.ts
+++ b/frontend/src/types/org.ts
@@ -22,6 +22,8 @@ export type OrgData = {
// Keyed by {4-digit year}-{2-digit month}
[key: string]: number;
} | null;
+ storageQuotaReached?: boolean;
+ execMinutesQuotaReached?: boolean;
users?: {
[id: string]: {
role: (typeof AccessCode)[UserRole];
diff --git a/frontend/src/utils/LiteElement.ts b/frontend/src/utils/LiteElement.ts
index 26adf1db92..1cc6535fbb 100644
--- a/frontend/src/utils/LiteElement.ts
+++ b/frontend/src/utils/LiteElement.ts
@@ -139,6 +139,7 @@ export default class LiteElement extends LitElement {
if (resp.ok) {
const body = await resp.json();
const storageQuotaReached = body.storageQuotaReached;
+ const executionMinutesQuotaReached = body.executionMinutesQuotaReached;
if (typeof storageQuotaReached === "boolean") {
this.dispatchEvent(
new CustomEvent("storage-quota-update", {
@@ -147,6 +148,14 @@ export default class LiteElement extends LitElement {
})
);
}
+ if (typeof executionMinutesQuotaReached === "boolean") {
+ this.dispatchEvent(
+ new CustomEvent("execution-minutes-quota-update", {
+ detail: { reached: executionMinutesQuotaReached },
+ bubbles: true,
+ })
+ );
+ }
return body;
}
@@ -175,6 +184,16 @@ export default class LiteElement extends LitElement {
errorMessage = msg("Storage quota reached");
break;
}
+ if (errorDetail === "exec_minutes_quota_reached") {
+ this.dispatchEvent(
+ new CustomEvent("execution-minutes-quota-update", {
+ detail: { reached: true },
+ bubbles: true,
+ })
+ );
+ errorMessage = msg("Monthly execution minutes quota reached");
+ break;
+ }
}
case 404: {
errorMessage = msg("Not found");