lablup / lablup/backend.ai-webui
[wayfinder] Add image from NGC catalog URL / canonical via single-image rescan — WebUI spec (map)
- Dominant language
- TypeScript
- Stars
- 133
- Forks
- 81
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 355
Description
## Destination
A WebUI spec for **"Add image"** — a superadmin-only modal on Environments › Images that takes pasted NGC catalog URLs / `docker pull` lines / canonicals (several per submission), resolves each line to a registered container registry, and registers each image through the existing `POST /admin/images/rescan` (`canonical` + `architecture`) — ready to ticket for implementation with no design decision left. The map is done when (1) the modal's look and behaviour are decided by prototype, (2) the input grammar and registry-matching rules are fixed, and (3) the multi-line submission / result / close semantics and copy are written into the spec.
## Notes
- **Premise (driver's decision, 2026-09-14):** the existing REST endpoint `POST /admin/images/rescan` is **assumed** to register images that are not yet in the DB. Today it does not — `api/rest/image/handler.py:88` → `scan_and_upsert_image` resolves the image from the DB first and raises `ImageNotFound`; only `backend.ai mgr image rescan ` and the session-commit bgtask take the DB-independent path (`db_source.rescan_images(registry_or_image)` → `_filter_by_img_canonical`). The core fix is a **prerequisite tracked outside this map** (lablup/backend.ai). The API shape this spec targets is the existing one: body `{canonical, architecture`}, `superadmin_required`, synchronous, returns `{item, errors`}.
- **Origin:** USC customer request (Teams, 2026-09-14) to use NGC catalog images (~300 NIMs, ~510 AI containers, new versions about every two weeks) without asking admins each time. Core team's finding: NGC cannot be catalog-scanned; only single-image rescans work, and today only from the manager CLI.
- **Baseline decisions from the charting session (driver):**
- Superadmin only. Regular-user self-service is out of scope.
- Entry point: Environments › **Images** tab header, an "Add image" button beside "Install image", opening a modal.
- Input: one smart **multi-line** text area. Each line is an NGC catalog URL, a `docker pull …` line (prefix stripped), or a canonical. Live per-line parse preview: registry / project / image / tag, plus the match against registered registries.
- Tag: taken from the URL's last segment when it is not `-`; otherwise a **required** input. Never defaulted to `latest` — NGC official images have no such tag (tritonserver 0 of 2,585 tags, pytorch 0 of 640).
- Architecture: default `x86_64`, editable in a collapsed "Advanced" field.
- Unregistered registry: open the existing **Add registry** modal on top of this modal, pre-filled from the parsed prefix, and continue after it closes (driver chose a nested modal over navigating away and back).
- Result: all lines succeed → toast + close + refetch the Images list. No search filter, no row highlight (Astryx has a row-status marker column, not a background highlight — excluded). Partial-failure semantics are fixed by the prototype.
- **Facts gathered while charting:**
- NGC catalog URL canonical form is `orgs/{org}/{team|-}/containers/{name}/{tag|-`}; short forms 302 to it (`?_lr=1`); `-` is a placeholder; a team maps into the project path (`nvcr.io/{org}/{team}/{name`}).
- The catalog page has **no `docker pull` copy button**: its copy field carries the bare reference and, on a tag-less (`/-`) page, already substitutes a concrete tag (`nvcr.io/nvidia/pytorch:26.08-py3`). The dominant paste is therefore a tagged canonical; `docker pull …` lines come from READMEs. NIM images **do** have a `latest` tag (unlike NVIDIA official images), so `latest` cannot be special-cased per org — the "tag required when missing" rule stands.
- nvcr.io hands out an anonymous pull token and serves `tags/list` server-side, but **browser CORS is blocked everywhere**: nvcr.io and Docker Hub send no `Access-Control-Allow-Origin`; `api.ngc.nvidia.com` allowlists `https://catalog.ngc.nvidia.com` only (403 for other origins).
- Registry rows are `(registry_name, project)`: one `nvcr.io` + `nvidia` row covers every `nvcr.io/nvidia/**` image; `_filter_by_img_canonical` matches `canonical.startswith(registry_name/project + "/")`. Images are rows in `images` keyed `(registry, project, name, tag, architecture)`.
- Current UI: `react/src/components/ImageList.tsx` (header: Refresh, "Install image"), `ContainerRegistryList.tsx` (row action "Rescan images" via the GraphQL bgtask mutation; "Add registry" modal), `pages/EnvironmentPage.tsx` tabs. `backend.ai-client` has no wrapper for `/admin/images/rescan` yet.
- Plan, don't do. Issues and comments in English. Resolve = resolution comment + Done + one line appended here. Skills to consult: `prototype`, `jira-workflow`, `gh-stack`, `i18n-patterns`.
- Wayfinder convention on this repo: map = Jira Epic labelled `wayfinder:map`, tickets = child Tasks labelled `wayfinder:`, GitHub clones linked as sub-issues with native blocked-by (see FR-3881 / #9532).
## Decisions so far
- [[research] Input grammar for Add image](https://lablup.atlassian.net/browse/FR-3934) — NGC URLs (canonical form, `teams/` short form, `/tags/{tag`} deep links, legacy `org:team:name`), `docker|podman pull` lines and bare canonicals all normalise to `{registryHost, project, imageName, tag`}; the project/name split is **resolved by literal prefix match against registered `registry_name[/project]` rows** — 0 matches → Add registry, ≥2 matches → block (`registry_ambiguous`: the backend raises `RuntimeError` on overlapping rows, `db_source.py:497-503`); no host heuristic (`parse_image_str` knows a registry only by equality or IP literal; Backend.AI default repo is `lablup`, not `library`) so host-less lines like `python:3.12` are rejected; `@sha256:` digests rejected in the UI (the core `rsplit(":")` misparses them silently); 12 reject codes, 63 fixture-ready test vectors. Note: https://github.com/lablup/backend.ai-webui/blob/research/add-image-input-grammar/docs/research/add-image-input-grammar.md
- [[research] WebUI plumbing facts for the Add-image modal](https://lablup.atlassian.net/browse/FR-3935) — call `POST /admin/images/rescan` from React via `baiSignedRequestWithPromise` + `useTanMutation` (precedent `ImportFromHuggingFaceModal.tsx`; no `backend.ai-client` wrapper — its resource files are `@ts-nocheck`); gate the button with `is_superadmin && supports('scan-image-by-canonical')` and hide it in the Images list action row (`ImageList.tsx:593-608`, not the card `extra`); nested `BAIModal` stacking is precedented (`dialogLevelStack.ts`, a dozen call sites, tested) but `ContainerRegistryEditorModal` needs a new create-mode `initialValues` prop and a way to hand back the created registry; refresh via `updateFetchKey()`, no list filter hides a new image; i18n keys under `environment.AddImage` in `resources/i18n/en.json`; `terminology.json` does not constrain "Add" vs "Install". Core follow-up: `RescanImagesResponse.errors` is never populated and a missing tag crashes with HTTP 500 (`base.py:363-366` → `service.py:388`). Note: https://github.com/lablup/backend.ai-webui/blob/research/add-image-webui-plumbing/docs/research/add-image-webui-plumbing.md
- Driver decision (2026-09-14): **skip the prototype and spec rounds and implement directly.** [FR-3936 prototype](https://lablup.atlassian.net/browse/FR-3936) and [FR-3937 spec grilling](https://lablup.atlassian.net/browse/FR-3937) closed as superseded; the remaining decisions (per-line result UX, copy, version gate) are taken in the implementation PRs — [FR-3939](https://lablup.atlassian.net/browse/FR-3939) ContainerRegistryEditorModal pre-fill (stack bottom) and [FR-3940](https://lablup.atlassian.net/browse/FR-3940) Add image modal (stack top). Version gate: `supports('scan-image-by-canonical')` keyed to core 26.3.0 (when the REST route appeared), to be bumped to the release that ships the DB-absent fix; until then a 404 `image_read_not-found` is shown as "this manager cannot register new images yet". Core prerequisite filed as a lablup/backend.ai issue (linked from FR-3940).
- Implementation landed as a ready-for-review stack (2026-09-14): [FR-3939](https://lablup.atlassian.net/browse/FR-3939) → lablup/backend.ai-webui#9675, [FR-3940](https://lablup.atlassian.net/browse/FR-3940) → #9676 (stack #9677). Ready gate: two Copilot passes each; 9 threads fixed and resolved, 1 judged by the driver (no manager-version flag — the supported floor is 26.4, button gated on `is_superadmin` only), 1 Copilot claim disproved (`BAIModal` renders no children while closed, `BAIModal.tsx:441`). Reviewer server: http://fr-3940-pr9676-add-image-modal.jongeun.10-82-0-159.sslip.io (top of the stack, endpoint dogbowl). Core prerequisite: lablup/backend.ai#14612. The map is done once the stack merges; remaining fog stays listed below for follow-ups.
## Not yet specified
- Tag-list lookup through a **manager proxy API** — the only CORS-safe path; the backend request is drafted once the prototype shows where a picker would sit.
- Terminology: "Add image" vs the existing "Install image" (which pulls to agents) — label and i18n keys under the terminology-precedence rule; likely settled in the prototype.
- Manager-version gating of the button (`baiClient.supports(...)` flag vs probing the endpoint) — depends on which core release ships the prerequisite.
- A registry-scoped entry point (Registries tab row action opening the same modal with the registry preselected).
## Out of scope
- Regular-user self-service (relaxing the endpoint's role gate + an admin switch) — the customer's end goal, needs a backend decision first.
- Automatic tracking of the newest NGC tag (periodic tag polling).
- Direct browser tag lookup (Electron header injection or any other route) — driver ruled it out.
- Docker Hub catalog URLs as input (a `docker.io/...` canonical still works).
- The core change itself (making `/admin/images/rescan` accept DB-absent images) — prerequisite, tracked in lablup/backend.ai.
- Row highlight of the just-added image in the list.
JIRA Issue: FR-3933
[FR-3939]: https://lablup.atlassian.net/browse/FR-3939?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Contributor guide
No contributing guide indexed for this repository
Research direction
Review the implementation stack in #9675, #9676, and #9677, starting with react/src/components/ImageList.tsx and the ContainerRegistryEditorModal changes. Check the related research notes and the existing POST /admin/images/rescan flow; this map is done when the implementation stack merges and the backend prerequisite lablup/backend.ai#14612 is handled.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 15/100