From d0914ec07f78d9ac489896aba77a0724d363872b Mon Sep 17 00:00:00 2001 From: leeja042499 Date: Fri, 11 Sep 2026 15:39:40 +0000 Subject: [PATCH 1/5] refactor(console): drop the unreachable image-build page and global-role hook; correct the roles note --- docs/admin/images.md | 16 +-- docs/admin/users-manage.md | 2 +- docs/admin/users.md | 6 +- docs/getting-started.md | 7 +- docs/user/sessions-create.md | 2 +- frontend/src/api/hooks/useImages.ts | 31 +---- frontend/src/api/hooks/useUsers.ts | 14 --- frontend/src/features/admin/Images.tsx | 108 +----------------- frontend/src/i18n/locales/en.json | 2 +- frontend/src/i18n/locales/ko.json | 2 +- frontend/src/lib/imageRef.ts | 21 ---- test/e2e/ux/routes.js | 2 - .../current/admin/images.md | 13 ++- .../current/admin/users-manage.md | 2 +- .../current/admin/users.md | 5 +- .../current/getting-started.md | 6 +- .../current/user/sessions-create.md | 2 +- 17 files changed, 37 insertions(+), 204 deletions(-) delete mode 100644 frontend/src/lib/imageRef.ts diff --git a/docs/admin/images.md b/docs/admin/images.md index 3431641..4caa8a0 100644 --- a/docs/admin/images.md +++ b/docs/admin/images.md @@ -38,17 +38,19 @@ Two things decide whether it will actually run: image built for 12.4 does not make it work on a Blackwell card — it makes the failure happen later and less clearly. -## Building +## Builds -![Build](/img/screens/admin-image-build.png) +![Builds tab](/img/screens/admin-image-build.png) -**Build image** starts a console-driven build from an inline Dockerfile or a public git -repository, pushes the result to `api.buildRegistry`, and registers it automatically. +The **Builds** tab follows image builds run through the platform: +`queued → building → pushing → scanning → succeeded`, with the log of each. -The build runs asynchronously and the page follows it: -`queued → building → pushing → scanning → succeeded`. A failure keeps the log for you to read. +A build is started through the API (`POST /api/v1/image-builds`, from an inline Dockerfile or a +public git repository); the console has no form for it. It also needs the chart's +`operator.kanikoImage` to be set — without it the operator fails builds immediately. The result +is pushed to `api.buildRegistry` and registered automatically. -Members can also build their own images (up to 20 each); those are private to the owner and +Images a member imported or built themselves are private to that member (up to 20 each) and tagged **My image** in the wizard. ## Seeded images diff --git a/docs/admin/users-manage.md b/docs/admin/users-manage.md index 7be843d..b9bd12a 100644 --- a/docs/admin/users-manage.md +++ b/docs/admin/users-manage.md @@ -23,7 +23,7 @@ Sessions already running are unaffected until they end. **Roles are not set here.** Group and organization administrators are appointed from the [group](./groups.md#group-administrators) and [organization](./organizations.md#organization-administrators) pages, and a role can only be granted at or below your own, within your scope. Global roles -(super_admin) are a super_admin action. +(super_admin) are a super_admin action, done through the API — the console has no control for them. The dialog also shows the account's **usage** — sessions, GPU and host resources held, volumes, wallet — so you can see what deactivating or deleting would affect before you do it. diff --git a/docs/admin/users.md b/docs/admin/users.md index 2a3ab41..cdbe836 100644 --- a/docs/admin/users.md +++ b/docs/admin/users.md @@ -26,10 +26,10 @@ the group each belongs to. | **Created** | When the account was made | | **Actions** | Approve (pending accounts), edit, suspend/activate, delete | -Roles are **granted elsewhere** — the list only shows them: global roles under -[System](./system.md), organization administrators on the +Roles are **granted elsewhere** — the list only shows them: organization administrators on the [organization](./organizations.md#organization-administrators), group roles on the -[group](./groups.md#group-administrators). +[group](./groups.md#group-administrators). The global role (super_admin) has no console +control; it is set through the API (`PUT /api/v1/users/{id}/global-role`). ![Filters](/img/screens/admin-users-toolbar.png) diff --git a/docs/getting-started.md b/docs/getting-started.md index 3050dc8..4baf9e4 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -182,9 +182,10 @@ an administrator you only edit and extend it, from the console or through The seeded rates are suggestions — adjust them in the console. - An **image** is the session container image. The GShare session images ship JupyterLab, a web terminal, and code-server. -- Users are not limited to the shared catalogue: from **내 이미지 / My images** any member can - build an image (an inline Dockerfile or a public git repository) or import a public registry - reference. Those rows are private to their owner, and one member may hold at most 20 of them. +- Users are not limited to the shared catalogue: a member can register images of their own + through the API (`POST /api/v1/images/import` for a public registry reference, + `POST /api/v1/image-builds` for a build). Those rows are private to their owner, tagged + **My image** in the wizard, and one member may hold at most 20 of them. The operator reports GPU inventory automatically, so nodes and devices (for example RTX 4090) appear on the infrastructure screens without further setup. diff --git a/docs/user/sessions-create.md b/docs/user/sessions-create.md index 9180cd0..4e5e11e 100644 --- a/docs/user/sessions-create.md +++ b/docs/user/sessions-create.md @@ -165,7 +165,7 @@ The tags on each tile tell you what you are getting: and `import torch` then fails. Pick a CUDA image unless you intend to install the toolkit yourself. -Images marked **My image** are ones you built or imported yourself; see +Images marked **My image** are ones registered for you alone rather than for everyone; see [Images](../admin/images.md) for how the catalogue is filled. --- diff --git a/frontend/src/api/hooks/useImages.ts b/frontend/src/api/hooks/useImages.ts index 70ab6c1..b6d10be 100644 --- a/frontend/src/api/hooks/useImages.ts +++ b/frontend/src/api/hooks/useImages.ts @@ -1,7 +1,6 @@ import { fetchRestOfPages, pageTotal, PAGE_MAX } from '@/api/paging'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { api, idemKey } from '@/api/client'; -import type { components } from '@/api/schema'; +import { api } from '@/api/client'; // The image and template registry, plus image builds. // The list response envelope is { data, pagination }. @@ -127,32 +126,4 @@ export function useImageBuilds(filter: ImageBuildFilter = {}) { }); } -export interface CreateBuildBody { - group_id: string; - name: string; - source: 'dockerfile' | 'git'; - dockerfile?: string; - git_url?: string; - git_ref?: string; - context?: string; - build_args?: Record; - target_tag?: string; -} -// POST /image-builds — start a build from a Dockerfile or a git source; asynchronous, returns 202. -export function useCreateBuild() { - const qc = useQueryClient(); - return useMutation({ - mutationFn: async (body: CreateBuildBody) => { - // git_ref and context have backend defaults (main and .), so they are optional here and cast - // to satisfy the schema, which marks them required. - const { data } = await api.POST('/api/v1/image-builds', { - body: body as components['schemas']['BuildCreate'], - headers: { 'Idempotency-Key': idemKey() }, - }); - return data; - }, - onSuccess: () => - qc.invalidateQueries({ queryKey: ['image-builds'] }), - }); -} diff --git a/frontend/src/api/hooks/useUsers.ts b/frontend/src/api/hooks/useUsers.ts index 56c1da8..7b1e406 100644 --- a/frontend/src/api/hooks/useUsers.ts +++ b/frontend/src/api/hooks/useUsers.ts @@ -215,20 +215,6 @@ export function useDeleteUser() { }); } -// PUT /users/{id}/global-role — grant global roles. super_admin only, and several may be granted. -export function useSetGlobalRole() { - const qc = useQueryClient(); - return useMutation({ - mutationFn: async ({ id, global_roles }: { id: string; global_roles: string[] }) => { - const { data } = await raw.PUT('/api/v1/users/{user_id}/global-role', { - params: { path: { user_id: id } }, - body: { global_roles }, - }); - return data as AdminUser; - }, - onSuccess: () => qc.invalidateQueries({ queryKey: userKeys.all }), - }); -} // GET /users/{id}/usage — live resource footprint for the admin drawer. export interface UserUsage { diff --git a/frontend/src/features/admin/Images.tsx b/frontend/src/features/admin/Images.tsx index bc2aa1c..12dc842 100644 --- a/frontend/src/features/admin/Images.tsx +++ b/frontend/src/features/admin/Images.tsx @@ -1,17 +1,13 @@ import { useCallback, useMemo, useState } from 'react'; import { Select } from '@/components/Select'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; import { useImages, useImageBuilds, useImportImage, useUpdateImage, - useCreateBuild, type ImportImageBody, - type CreateBuildBody, } from '@/api/hooks/useImages'; -import { useProjects } from '@/api/hooks/useGroups'; import { useGpuAvailability, useOfferings } from '@/api/hooks/useResources'; import { cudaCompatible } from '@/lib/cuda'; import { Table, TableToolbar, sortAccessor, type Column } from '@/components/Table'; @@ -29,7 +25,7 @@ import { Hammer, Package, Plus } from '@/components/icons'; import { StatusPill } from '@/components/StatusPill'; import { Tabs } from '@/components/Tabs'; -// The image and template registry plus image builds (/images, /images/import, /image-builds). +// The image and template registry plus the image-build list (/images, /images/import, /image-builds). interface ImageRow { id: string; @@ -449,105 +445,3 @@ function ImportImageForm({ onDone }: { onDone: () => void }) { ); } - -// Image build, at /admin/images/build. -export function BuildImagePage() { - const { t } = useTranslation(); - const navigate = useNavigate(); - const groups = useProjects().data ?? []; - const [projectId, setProjectId] = useState(''); - const effGroupId = projectId || groups[0]?.id || ''; - const [name, setName] = useState(''); - const [source, setSource] = useState<'dockerfile' | 'git'>('git'); - const [gitUrl, setGitUrl] = useState(''); - const [gitRef, setGitRef] = useState('main'); - const [dockerfile, setDockerfile] = useState(''); - const build = useCreateBuild(); - const pushToast = useUiStore((s) => s.pushToast); - - const submit = () => { - const body: CreateBuildBody = { - group_id: effGroupId, - name: name.trim(), - source, - ...(source === 'git' - ? { git_url: gitUrl.trim(), git_ref: gitRef.trim() || 'main' } - : { dockerfile }), - }; - build.mutate(body, { - onSuccess: () => { guard.clear(); pushToast('success', t('admin.images.buildStarted', { name })); navigate('/admin/images'); }, - onError: (e) => pushToast('error', humanizeError(asApiError(e))), - }); - }; - - const valid = - effGroupId.length > 0 && - name.trim().length > 0 && - (source === 'git' ? gitUrl.trim().length > 0 : dockerfile.trim().length > 0); - const guard = useFormGuard(build.isPending); - - return ( -
- -
{ e.preventDefault(); if (valid) submit(); }}> -
- - - - {source === 'git' ? ( - <> - - - - ) : ( -