Skip to content

Commit 644c1ef

Browse files
committed
Disable queues/org from admin API endpoint
1 parent 9b23981 commit 644c1ef

File tree

4 files changed

+115
-3
lines changed

4 files changed

+115
-3
lines changed

apps/webapp/app/components/billing/UpgradePrompt.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export function UpgradePrompt() {
3030
<Icon icon={ExclamationCircleIcon} className="h-5 w-5 text-error" />
3131
<Paragraph variant="small" className="text-error">
3232
You have exceeded the monthly $
33-
{(plan.v3Subscription?.plan?.limits.includedUsage ?? 500) / 100} free credits. No runs
34-
will execute in Prod until{" "}
33+
{(plan.v3Subscription?.plan?.limits.includedUsage ?? 500) / 100} free credits. Existing
34+
runs will be queued and new runs won't be created until{" "}
3535
<DateTime date={nextMonth} includeTime={false} timeZone="utc" />, or you upgrade.
3636
</Paragraph>
3737
</div>

apps/webapp/app/presenters/v3/EnvironmentQueuePresenter.server.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type Environment = {
88
queued: number;
99
concurrencyLimit: number;
1010
burstFactor: number;
11+
runsEnabled: boolean;
1112
};
1213

1314
export class EnvironmentQueuePresenter extends BasePresenter {
@@ -23,11 +24,25 @@ export class EnvironmentQueuePresenter extends BasePresenter {
2324
const running = (engineV1Executing ?? 0) + (engineV2Executing ?? 0);
2425
const queued = (engineV1Queued ?? 0) + (engineV2Queued ?? 0);
2526

27+
const organization = await this._replica.organization.findFirst({
28+
where: {
29+
id: environment.organizationId,
30+
},
31+
select: {
32+
runsEnabled: true,
33+
},
34+
});
35+
36+
if (!organization) {
37+
throw new Error("Organization not found");
38+
}
39+
2640
return {
2741
running,
2842
queued,
2943
concurrencyLimit: environment.maximumConcurrencyLimit,
3044
burstFactor: environment.concurrencyLimitBurstFactor.toNumber(),
45+
runsEnabled: environment.type === "DEVELOPMENT" || organization.runsEnabled,
3146
};
3247
}
3348
}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ export default function Page() {
285285
>
286286
View runs
287287
</LinkButton>
288-
<EnvironmentPauseResumeButton env={env} />
288+
{environment.runsEnabled ? <EnvironmentPauseResumeButton env={env} /> : null}
289289
</div>
290290
}
291291
valueClassName={env.paused ? "text-warning" : undefined}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
2+
import {
3+
type RuntimeEnvironment,
4+
type Organization,
5+
type Project,
6+
type RuntimeEnvironmentType,
7+
} from "@trigger.dev/database";
8+
import { z } from "zod";
9+
import { prisma } from "~/db.server";
10+
import { createEnvironment } from "~/models/organization.server";
11+
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
12+
import { updateEnvConcurrencyLimits } from "~/v3/runQueue.server";
13+
import { PauseEnvironmentService } from "~/v3/services/pauseEnvironment.server";
14+
15+
const ParamsSchema = z.object({
16+
organizationId: z.string(),
17+
});
18+
19+
const BodySchema = z.object({
20+
enable: z.boolean(),
21+
});
22+
23+
/**
24+
* It will enabled/disable runs
25+
*/
26+
export async function action({ request, params }: ActionFunctionArgs) {
27+
// Next authenticate the request
28+
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
29+
30+
if (!authenticationResult) {
31+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
32+
}
33+
34+
const user = await prisma.user.findUnique({
35+
where: {
36+
id: authenticationResult.userId,
37+
},
38+
});
39+
40+
if (!user) {
41+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
42+
}
43+
44+
if (!user.admin) {
45+
return json({ error: "You must be an admin to perform this action" }, { status: 403 });
46+
}
47+
48+
const { organizationId } = ParamsSchema.parse(params);
49+
const body = BodySchema.safeParse(await request.json());
50+
if (!body.success) {
51+
return json({ error: "Invalid request body", details: body.error }, { status: 400 });
52+
}
53+
54+
const organization = await prisma.organization.update({
55+
where: {
56+
id: organizationId,
57+
},
58+
data: {
59+
runsEnabled: body.data.enable,
60+
},
61+
});
62+
63+
if (!organization) {
64+
return json({ error: "Organization not found" }, { status: 404 });
65+
}
66+
67+
const environments = await prisma.runtimeEnvironment.findMany({
68+
where: {
69+
organizationId,
70+
type: {
71+
not: "DEVELOPMENT",
72+
},
73+
},
74+
include: {
75+
organization: true,
76+
project: true,
77+
},
78+
});
79+
80+
const pauseEnvironmentService = new PauseEnvironmentService();
81+
82+
// Set the organization.runsEnabled flag to false
83+
for (const environment of environments) {
84+
if (body.data.enable) {
85+
await pauseEnvironmentService.call({ ...environment, organization }, "resumed");
86+
} else {
87+
await pauseEnvironmentService.call({ ...environment, organization }, "paused");
88+
}
89+
}
90+
91+
return json({
92+
success: true,
93+
message: `${environments.length} environments updated to ${
94+
body.data.enable ? "enabled" : "disabled"
95+
}`,
96+
});
97+
}

0 commit comments

Comments
 (0)