Skip to content

Commit 3ed14d4

Browse files
committed
remove static presets & fetch from backend instead
1 parent fdd983d commit 3ed14d4

File tree

5 files changed

+90
-142
lines changed

5 files changed

+90
-142
lines changed

backend/btrixcloud/models.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,11 +1856,6 @@ class Plan(BaseModel):
18561856
id: str
18571857
name: str
18581858
org_quotas: OrgQuotas
1859-
stripe_product_ids: list[str] = []
1860-
stripe_price_ids: list[str] = []
1861-
stripe_portal_config_id: str | None = None
1862-
hubspot_product_id: str | None = None
1863-
hubspot_deal_amount: float | None = None
18641859
testmode: bool = False
18651860

18661861
# ============================================================================

backend/btrixcloud/orgs.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,11 +1617,12 @@ async def rename_org(
16171617

16181618
return {"updated": True}
16191619

1620-
@router.get("/plans", tags=["settings"], response_model=PlansResponse)
1621-
async def get_plans(user: User = Depends(user_dep)) -> PlansResponse:
1620+
@app.get("/orgs/plans", tags=["organizations"], response_model=PlansResponse)
1621+
async def get_plans(user: User = Depends(user_dep)):
16221622
if not user.is_superuser:
16231623
raise HTTPException(status_code=403, detail="Not Allowed")
1624-
plans_json = os.environ.get("BTRIX_PLANS")
1624+
plans_json = os.environ.get("AVAILABLE_PLANS")
1625+
print("DEBUG plans_json:", plans_json)
16251626
if not plans_json:
16261627
print("Info: Plans not configured")
16271628
return PlansResponse(plans=[])

chart/templates/configmap.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ data:
107107

108108
CLEANUP_FILES_AFTER_MINUTES: "{{ .Values.cleanup_files_after_minutes | default 1440 }}"
109109

110-
BTRIX_PLANS: "{{ .Values.available_plans }}"
110+
AVAILABLE_PLANS: {{ .Values.available_plans | toJson }}
111111

112112
---
113113
apiVersion: v1

chart/values.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,6 @@ ingress:
492492
# alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
493493
# alb.ingress.kubernetes.io/certificate-arn: "certificate-arn"
494494

495-
496495
ingress_class: nginx
497496

498497
# Optional: Front-end injected script

frontend/src/features/admin/org-quota-editor.ts

Lines changed: 85 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -3,93 +3,33 @@ import { type SlDialog } from "@shoelace-style/shoelace";
33
import { html, type PropertyValues } from "lit";
44
import { customElement, property, state } from "lit/decorators.js";
55
import { createRef, ref, type Ref } from "lit/directives/ref.js";
6+
import { until } from "lit/directives/until.js";
67
import { when } from "lit/directives/when.js";
78
import { isEqual } from "lodash";
89
import { type Entries } from "type-fest";
10+
import z from "zod";
911

1012
import { BtrixElement } from "@/classes/BtrixElement";
1113
import { type RowEditEventDetail } from "@/components/ui/data-grid/data-grid-row";
1214
import {
1315
GridColumnType,
1416
type GridColumn,
1517
} from "@/components/ui/data-grid/types";
16-
import { type OrgData, type OrgQuotas } from "@/utils/orgs";
18+
import { orgQuotasSchema, type OrgData, type OrgQuotas } from "@/utils/orgs";
1719
import { pluralOf } from "@/utils/pluralize";
1820

19-
// These were manually copied over from Cashew on 2025-09-15 — please update if necessary
20-
const PRESETS = {
21-
Starter: {
22-
quotas: {
23-
maxConcurrentCrawls: 1,
24-
maxPagesPerCrawl: 2000,
25-
storageQuota: 100_000_000_000,
26-
maxExecMinutesPerMonth: 180,
27-
},
28-
subscriptionIds: ["starter", "starterTest"],
29-
},
30-
Standard: {
31-
quotas: {
32-
maxConcurrentCrawls: 2,
33-
maxPagesPerCrawl: 5000,
34-
storageQuota: 220_000_000_000,
35-
maxExecMinutesPerMonth: 360,
36-
},
37-
subscriptionIds: ["standard", "standardTest"],
38-
},
39-
Plus: {
40-
quotas: {
41-
maxConcurrentCrawls: 3,
42-
maxPagesPerCrawl: 10000,
43-
storageQuota: 500_000_000_000,
44-
maxExecMinutesPerMonth: 720,
45-
},
46-
subscriptionIds: ["plus", "plusTest"],
47-
},
48-
"Pro Standard": {
49-
quotas: {
50-
maxConcurrentCrawls: 4,
51-
maxPagesPerCrawl: 50_000,
52-
storageQuota: 1_000_000_000_000,
53-
maxExecMinutesPerMonth: 50 * 60,
54-
},
55-
subscriptionIds: ["pro-standard-monthly", "pro-standard-yearly"],
56-
},
57-
"Pro Teams": {
58-
quotas: {
59-
maxConcurrentCrawls: 5,
60-
maxPagesPerCrawl: 100_000,
61-
storageQuota: 3_000_000_000_000,
62-
maxExecMinutesPerMonth: 80 * 60,
63-
},
64-
subscriptionIds: ["pro-teams-monthly", "pro-teams-yearly"],
65-
},
66-
"Pro Plus": {
67-
quotas: {
68-
maxConcurrentCrawls: 10,
69-
maxPagesPerCrawl: 400_000,
70-
storageQuota: 5_000_000_000_000,
71-
maxExecMinutesPerMonth: 150 * 60,
72-
},
73-
subscriptionIds: ["pro-plus-monthly", "pro-plus-yearly"],
74-
},
75-
Unset: {
76-
quotas: {
77-
maxConcurrentCrawls: 0,
78-
maxPagesPerCrawl: 0,
79-
storageQuota: 0,
80-
maxExecMinutesPerMonth: 0,
81-
},
82-
subscriptionIds: [],
83-
},
84-
} as const satisfies Record<
85-
string,
86-
{
87-
quotas: {
88-
[key in keyof OrgQuotas]?: number;
89-
};
90-
subscriptionIds?: string[];
91-
}
92-
>;
21+
const PlanSchema = z.object({
22+
id: z.string(),
23+
name: z.string(),
24+
org_quotas: orgQuotasSchema,
25+
testmode: z.boolean(),
26+
});
27+
28+
const PlansResponseSchema = z.object({
29+
plans: z.array(PlanSchema),
30+
});
31+
32+
type PlansResponse = z.infer<typeof PlansResponseSchema>;
9333

9434
const LABELS = {
9535
maxConcurrentCrawls: {
@@ -136,6 +76,9 @@ export class OrgQuotaEditor extends BtrixElement {
13676

13777
dialog: Ref<SlDialog> = createRef();
13878

79+
@state()
80+
plans = this.api.fetch<PlansResponse>("/orgs/plans");
81+
13982
show() {
14083
void this.dialog.value?.show();
14184
}
@@ -243,65 +186,75 @@ export class OrgQuotaEditor extends BtrixElement {
243186
>
244187
<btrix-overflow-scroll class="-mx-4 part-[content]:px-4">
245188
<sl-button-group id="org-quota-presets">
246-
${(Object.entries(PRESETS) as Entries<typeof PRESETS>).map(
247-
([key, value]) => {
248-
const isCurrentSubscription = (
249-
value.subscriptionIds as string[]
250-
).includes(this.activeOrg?.subscription?.planId ?? "");
251-
return html`<btrix-popover placement="top">
252-
<sl-button
253-
@click=${() => {
254-
const newQuota: Partial<OrgQuotas> = {};
255-
(
256-
Object.entries(value.quotas) as Entries<
257-
typeof value.quotas
258-
>
259-
).forEach(([k, v]) => {
260-
newQuota[k] = v - quotas[k];
261-
});
262-
this.orgQuotaAdjustments = { ...newQuota };
263-
}}
264-
>
265-
${key}
266-
${isCurrentSubscription
267-
? html`<sl-icon
268-
name="credit-card"
269-
slot="prefix"
270-
></sl-icon>`
271-
: null}
272-
</sl-button>
273-
<div slot="content">
274-
<header class="mb-2 font-medium">
275-
${key}${isCurrentSubscription
276-
? html` -
277-
<b class="text-primary-600"
278-
>${msg("This is the current subscription.")}</b
279-
>`
189+
${until(
190+
this.plans.then(({ plans }) =>
191+
plans.map(({ id, name, org_quotas }) => {
192+
const isCurrentSubscription =
193+
id === this.activeOrg?.subscription?.planId;
194+
const presets: Omit<
195+
OrgQuotas,
196+
`${"extra" | "gifted"}ExecMinutes`
197+
> = {
198+
maxConcurrentCrawls: org_quotas.maxConcurrentCrawls,
199+
maxExecMinutesPerMonth: org_quotas.maxExecMinutesPerMonth,
200+
maxPagesPerCrawl: org_quotas.maxPagesPerCrawl,
201+
storageQuota: org_quotas.storageQuota,
202+
};
203+
return html`<btrix-popover placement="top">
204+
<sl-button
205+
@click=${() => {
206+
const newQuota: Partial<OrgQuotas> = {};
207+
208+
(
209+
Object.entries(presets) as Entries<typeof presets>
210+
).forEach(([k, v]) => {
211+
newQuota[k] = v - quotas[k];
212+
});
213+
this.orgQuotaAdjustments = { ...newQuota };
214+
}}
215+
>
216+
${name}
217+
${isCurrentSubscription
218+
? html`<sl-icon
219+
name="credit-card"
220+
slot="prefix"
221+
></sl-icon>`
280222
: null}
281-
</header>
223+
</sl-button>
224+
<div slot="content">
225+
<header class="mb-2 font-medium">
226+
${name}${isCurrentSubscription
227+
? html` -
228+
<b class="text-primary-600"
229+
>${msg(
230+
"This is the current subscription.",
231+
)}</b
232+
>`
233+
: null}
234+
</header>
282235
283-
<hr class="my-2" />
284-
<table>
285-
<tbody>
286-
${(
287-
Object.entries(value.quotas) as Entries<
288-
typeof value.quotas
289-
>
290-
).map(
291-
([key, value]) => html`
292-
<tr>
293-
<td class="pr-2">${LABELS[key].label}</td>
294-
<td class="pr-2">
295-
${this.format(value, LABELS[key].type)}
296-
</td>
297-
</tr>
298-
`,
299-
)}
300-
</tbody>
301-
</table>
302-
</div>
303-
</btrix-popover>`;
304-
},
236+
<hr class="my-2" />
237+
<table>
238+
<tbody>
239+
${(
240+
Object.entries(presets) as Entries<typeof presets>
241+
).map(
242+
([key, value]) => html`
243+
<tr>
244+
<td class="pr-2">${LABELS[key].label}</td>
245+
<td class="pr-2">
246+
${this.format(value, LABELS[key].type)}
247+
</td>
248+
</tr>
249+
`,
250+
)}
251+
</tbody>
252+
</table>
253+
</div>
254+
</btrix-popover>`;
255+
}),
256+
),
257+
msg("Loading plans..."),
305258
)}
306259
</sl-button-group>
307260
</btrix-overflow-scroll>

0 commit comments

Comments
 (0)