Skip to content

Commit 3f3e808

Browse files
committed
Admins can view all regions and switch at will
1 parent 20f1e66 commit 3f3e808

File tree

3 files changed

+54
-21
lines changed

3 files changed

+54
-21
lines changed

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@ export type Region = {
1212
location?: string;
1313
staticIPs?: string | null;
1414
isDefault: boolean;
15+
isHidden: boolean;
1516
};
1617

1718
export class RegionsPresenter extends BasePresenter {
18-
public async call({ userId, projectSlug }: { userId: User["id"]; projectSlug: Project["slug"] }) {
19+
public async call({
20+
userId,
21+
projectSlug,
22+
isAdmin = false,
23+
}: {
24+
userId: User["id"];
25+
projectSlug: Project["slug"];
26+
isAdmin?: boolean;
27+
}) {
1928
const project = await this._replica.project.findFirst({
2029
select: {
2130
id: true,
@@ -56,16 +65,18 @@ export class RegionsPresenter extends BasePresenter {
5665
cloudProvider: true,
5766
location: true,
5867
staticIPs: true,
68+
hidden: true,
5969
},
60-
where:
61-
// Hide hidden unless they're allowed to use them
70+
where: isAdmin
71+
? undefined
72+
: // Hide hidden unless they're allowed to use them
6273
project.allowedMasterQueues.length > 0
63-
? {
64-
masterQueue: { in: project.allowedMasterQueues },
65-
}
66-
: {
67-
hidden: false,
68-
},
74+
? {
75+
masterQueue: { in: project.allowedMasterQueues },
76+
}
77+
: {
78+
hidden: false,
79+
},
6980
orderBy: {
7081
name: "asc",
7182
},
@@ -79,6 +90,7 @@ export class RegionsPresenter extends BasePresenter {
7990
location: region.location ?? undefined,
8091
staticIPs: region.staticIPs ?? undefined,
8192
isDefault: region.id === defaultWorkerInstanceGroupId,
93+
isHidden: region.hidden,
8294
}));
8395

8496
if (project.defaultWorkerGroupId) {
@@ -90,6 +102,7 @@ export class RegionsPresenter extends BasePresenter {
90102
cloudProvider: true,
91103
location: true,
92104
staticIPs: true,
105+
hidden: true,
93106
},
94107
where: { id: project.defaultWorkerGroupId },
95108
});
@@ -109,6 +122,7 @@ export class RegionsPresenter extends BasePresenter {
109122
location: defaultWorkerGroup.location ?? undefined,
110123
staticIPs: defaultWorkerGroup.staticIPs ?? undefined,
111124
isDefault: true,
125+
isHidden: defaultWorkerGroup.hidden,
112126
});
113127
}
114128
}

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ import {
4646
} from "~/components/primitives/Table";
4747
import { TextLink } from "~/components/primitives/TextLink";
4848
import { useOrganization } from "~/hooks/useOrganizations";
49+
import { useHasAdminAccess } from "~/hooks/useUser";
4950
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
5051
import { findProjectBySlug } from "~/models/project.server";
5152
import { type Region, RegionsPresenter } from "~/presenters/v3/RegionsPresenter.server";
52-
import { requireUserId } from "~/services/session.server";
53+
import { requireUser, requireUserId } from "~/services/session.server";
5354
import {
5455
docsPath,
5556
EnvironmentParamSchema,
@@ -60,14 +61,15 @@ import {
6061
import { SetDefaultRegionService } from "~/v3/services/setDefaultRegion.server";
6162

6263
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
63-
const userId = await requireUserId(request);
64+
const user = await requireUser(request);
6465
const { projectParam } = ProjectParamSchema.parse(params);
6566

6667
const presenter = new RegionsPresenter();
6768
const [error, result] = await tryCatch(
6869
presenter.call({
69-
userId,
70+
userId: user.id,
7071
projectSlug: projectParam,
72+
isAdmin: user.admin || user.isImpersonating,
7173
})
7274
);
7375

@@ -86,10 +88,10 @@ const FormSchema = z.object({
8688
});
8789

8890
export const action = async ({ request, params }: ActionFunctionArgs) => {
89-
const userId = await requireUserId(request);
91+
const user = await requireUser(request);
9092
const { organizationSlug, projectParam, envParam } = EnvironmentParamSchema.parse(params);
9193

92-
const project = await findProjectBySlug(organizationSlug, projectParam, userId);
94+
const project = await findProjectBySlug(organizationSlug, projectParam, user.id);
9395

9496
const redirectPath = regionsPath(
9597
{ slug: organizationSlug },
@@ -113,6 +115,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
113115
service.call({
114116
projectId: project.id,
115117
regionId: parsedFormData.data.regionId,
118+
isAdmin: user.admin || user.isImpersonating,
116119
})
117120
);
118121

@@ -126,6 +129,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
126129
export default function Page() {
127130
const { regions, isPaying } = useTypedLoaderData<typeof loader>();
128131
const organization = useOrganization();
132+
const isAdmin = useHasAdminAccess();
129133

130134
return (
131135
<PageContainer>
@@ -162,6 +166,7 @@ export default function Page() {
162166
<TableHeaderCell>Cloud Provider</TableHeaderCell>
163167
<TableHeaderCell>Location</TableHeaderCell>
164168
<TableHeaderCell>Static IPs</TableHeaderCell>
169+
{isAdmin && <TableHeaderCell>Admin</TableHeaderCell>}
165170
<TableHeaderCell
166171
alignment="right"
167172
tooltip={
@@ -240,6 +245,9 @@ export default function Page() {
240245
"Not available"
241246
)}
242247
</TableCell>
248+
{isAdmin && (
249+
<TableCell>{region.isHidden ? "Hidden" : "Visible"}</TableCell>
250+
)}
243251
{region.isDefault ? (
244252
<TableCell alignment="right">
245253
<Badge variant="small" className="inline-grid">
@@ -259,8 +267,9 @@ export default function Page() {
259267
);
260268
})
261269
)}
270+
262271
<TableRow className="h-[3.125rem]">
263-
<TableCell colSpan={4}>
272+
<TableCell colSpan={isAdmin ? 5 : 4}>
264273
<Paragraph variant="extra-small">Suggest a new region</Paragraph>
265274
</TableCell>
266275
<TableCellMenu

apps/webapp/app/v3/services/setDefaultRegion.server.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { BaseService, ServiceValidationError } from "./baseService.server";
22

33
export class SetDefaultRegionService extends BaseService {
4-
public async call({ projectId, regionId }: { projectId: string; regionId: string }) {
4+
public async call({
5+
projectId,
6+
regionId,
7+
isAdmin = false,
8+
}: {
9+
projectId: string;
10+
regionId: string;
11+
isAdmin?: boolean;
12+
}) {
513
const workerGroup = await this._prisma.workerInstanceGroup.findFirst({
614
where: {
715
id: regionId,
@@ -23,12 +31,14 @@ export class SetDefaultRegionService extends BaseService {
2331
}
2432

2533
// If their project is restricted, only allow them to set default regions that are allowed
26-
if (project.allowedMasterQueues.length > 0) {
27-
if (!project.allowedMasterQueues.includes(workerGroup.masterQueue)) {
28-
throw new ServiceValidationError("You're not allowed to set this region as default");
34+
if (!isAdmin) {
35+
if (project.allowedMasterQueues.length > 0) {
36+
if (!project.allowedMasterQueues.includes(workerGroup.masterQueue)) {
37+
throw new ServiceValidationError("You're not allowed to set this region as default");
38+
}
39+
} else if (workerGroup.hidden) {
40+
throw new ServiceValidationError("This region is not available to you");
2941
}
30-
} else if (workerGroup.hidden) {
31-
throw new ServiceValidationError("This region is not available to you");
3242
}
3343

3444
await this._prisma.project.update({

0 commit comments

Comments
 (0)