Skip to content

Commit 4733ef8

Browse files
committed
Surface vCPU limits in admin UI
1 parent bf2c626 commit 4733ef8

4 files changed

Lines changed: 47 additions & 2 deletions

File tree

controlplane/admin/ui/src/pages/OrgDetail.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import type { ManagedWarehouse, OrgUpdate } from "@/types/api";
3434
interface FormState {
3535
max_workers: string;
3636
max_connections: string;
37+
max_vcpus: string;
3738
default_worker_cpu: string;
3839
default_worker_memory: string;
3940
default_worker_ttl: string;
@@ -44,6 +45,7 @@ interface FormState {
4445
function orgToForm(o: {
4546
max_workers: number;
4647
max_connections: number;
48+
max_vcpus: number;
4749
default_worker_cpu: string;
4850
default_worker_memory: string;
4951
default_worker_ttl: string;
@@ -53,6 +55,7 @@ function orgToForm(o: {
5355
return {
5456
max_workers: String(o.max_workers),
5557
max_connections: String(o.max_connections),
58+
max_vcpus: String(o.max_vcpus),
5659
default_worker_cpu: o.default_worker_cpu,
5760
default_worker_memory: o.default_worker_memory,
5861
default_worker_ttl: o.default_worker_ttl,
@@ -103,6 +106,7 @@ export function OrgDetail() {
103106
const body: OrgUpdate = {
104107
max_workers: Number(form.max_workers) || 0,
105108
max_connections: Number(form.max_connections) || 0,
109+
max_vcpus: Number(form.max_vcpus) || 0,
106110
default_worker_cpu: form.default_worker_cpu,
107111
default_worker_memory: form.default_worker_memory,
108112
default_worker_ttl: form.default_worker_ttl,
@@ -160,6 +164,14 @@ export function OrgDetail() {
160164
onChange={(e) => set("max_connections", e.target.value)}
161165
/>
162166
</Field>
167+
<Field label="Max vCPUs (0 = unbounded)">
168+
<Input
169+
type="number"
170+
min={0}
171+
value={form.max_vcpus}
172+
onChange={(e) => set("max_vcpus", e.target.value)}
173+
/>
174+
</Field>
163175
<Field label="Default worker CPU">
164176
<Input
165177
value={form.default_worker_cpu}

controlplane/admin/ui/src/pages/Orgs.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ export function Orgs() {
5656
return <span className="tabular-nums">{v === 0 ? "∞" : fmtInt(v)}</span>;
5757
},
5858
},
59+
{
60+
accessorKey: "max_vcpus",
61+
header: "Max vCPUs",
62+
cell: ({ getValue }) => {
63+
const v = getValue() as number;
64+
return <span className="tabular-nums">{v === 0 ? "∞" : fmtInt(v)}</span>;
65+
},
66+
},
5967
{
6068
id: "users",
6169
header: "Users",

controlplane/admin/ui/src/pages/Users.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
useUserSecrets,
2828
useUsers,
2929
} from "@/hooks/useApi";
30-
import { fmtTime } from "@/lib/format";
30+
import { fmtInt, fmtTime } from "@/lib/format";
3131
import type { OrgUser } from "@/types/api";
3232

3333
export function UsersPage() {
@@ -66,6 +66,14 @@ export function UsersPage() {
6666
return v ? <Badge variant="secondary">{v}</Badge> : <span className="text-muted-foreground"></span>;
6767
},
6868
},
69+
{
70+
accessorKey: "max_vcpus",
71+
header: "Max vCPUs",
72+
cell: ({ getValue }) => {
73+
const v = getValue() as number;
74+
return <span className="tabular-nums">{v === 0 ? "∞" : fmtInt(v)}</span>;
75+
},
76+
},
6977
{
7078
accessorKey: "created_at",
7179
header: "Created",
@@ -125,7 +133,7 @@ export function UsersPage() {
125133
<PageBody>
126134
<Card className="overflow-hidden">
127135
{users.isLoading ? (
128-
<TableSkeleton cols={6} />
136+
<TableSkeleton cols={7} />
129137
) : users.isError ? (
130138
<ErrorState error={users.error} onRetry={() => users.refetch()} />
131139
) : (
@@ -186,6 +194,7 @@ function CreateUserDialog({ onClose }: { onClose: () => void }) {
186194
const [password, setPassword] = useState("");
187195
const [passthrough, setPassthrough] = useState(false);
188196
const [iceberg, setIceberg] = useState(false);
197+
const [maxVCPUs, setMaxVCPUs] = useState("0");
189198
const [err, setErr] = useState<string | null>(null);
190199

191200
const submit = async () => {
@@ -197,6 +206,7 @@ function CreateUserDialog({ onClose }: { onClose: () => void }) {
197206
password,
198207
passthrough,
199208
default_catalog: iceberg ? "iceberg" : "",
209+
max_vcpus: Number(maxVCPUs) || 0,
200210
});
201211
onClose();
202212
} catch (e) {
@@ -226,6 +236,10 @@ function CreateUserDialog({ onClose }: { onClose: () => void }) {
226236
<Label>Password</Label>
227237
<Input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
228238
</div>
239+
<div className="space-y-1">
240+
<Label>Max vCPUs (0 = unbounded)</Label>
241+
<Input type="number" min={0} value={maxVCPUs} onChange={(e) => setMaxVCPUs(e.target.value)} />
242+
</div>
229243
<div className="flex items-center gap-6">
230244
<label className="flex items-center gap-2 text-sm">
231245
<Switch checked={passthrough} onCheckedChange={setPassthrough} /> Passthrough
@@ -254,6 +268,7 @@ function EditUserDialog({ user, onClose }: { user: OrgUser; onClose: () => void
254268
const [password, setPassword] = useState("");
255269
const [passthrough, setPassthrough] = useState(user.passthrough);
256270
const [iceberg, setIceberg] = useState(user.default_catalog === "iceberg");
271+
const [maxVCPUs, setMaxVCPUs] = useState(String(user.max_vcpus));
257272
const [err, setErr] = useState<string | null>(null);
258273

259274
const submit = async () => {
@@ -266,6 +281,7 @@ function EditUserDialog({ user, onClose }: { user: OrgUser; onClose: () => void
266281
...(password ? { password } : {}),
267282
passthrough,
268283
default_catalog: iceberg ? "iceberg" : "",
284+
max_vcpus: Number(maxVCPUs) || 0,
269285
},
270286
});
271287
onClose();
@@ -290,6 +306,10 @@ function EditUserDialog({ user, onClose }: { user: OrgUser; onClose: () => void
290306
<Label>New password</Label>
291307
<Input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="unchanged" />
292308
</div>
309+
<div className="space-y-1">
310+
<Label>Max vCPUs (0 = unbounded)</Label>
311+
<Input type="number" min={0} value={maxVCPUs} onChange={(e) => setMaxVCPUs(e.target.value)} />
312+
</div>
293313
<div className="flex items-center gap-6">
294314
<label className="flex items-center gap-2 text-sm">
295315
<Switch checked={passthrough} onCheckedChange={setPassthrough} /> Passthrough

controlplane/admin/ui/src/types/api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface Org {
4646
hostname_alias: string | null;
4747
max_workers: number;
4848
max_connections: number;
49+
max_vcpus: number;
4950
default_worker_cpu: string;
5051
default_worker_memory: string;
5152
default_worker_ttl: string;
@@ -60,6 +61,7 @@ export interface Org {
6061
export interface OrgUpdate {
6162
max_workers?: number;
6263
max_connections?: number;
64+
max_vcpus?: number;
6365
default_worker_cpu?: string;
6466
default_worker_memory?: string;
6567
default_worker_ttl?: string;
@@ -74,6 +76,7 @@ export interface OrgUser {
7476
username: string;
7577
passthrough: boolean;
7678
default_catalog?: string;
79+
max_vcpus: number;
7780
created_at: string;
7881
updated_at: string;
7982
}
@@ -84,12 +87,14 @@ export interface CreateUserBody {
8487
org_id: string;
8588
passthrough?: boolean;
8689
default_catalog?: string;
90+
max_vcpus?: number;
8791
}
8892

8993
export interface UpdateUserBody {
9094
password?: string;
9195
passthrough?: boolean;
9296
default_catalog?: string;
97+
max_vcpus?: number;
9398
}
9499

95100
// GET /api/v1/orgs/:id/users/:username/secrets → { secrets: OrgUserSecret[] }.

0 commit comments

Comments
 (0)