merge develop into release
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-08 14:40:56 +09:00
4 changed files with 363 additions and 136 deletions
+305 -128
View File
@@ -1,168 +1,345 @@
# Studio Admin Page Agent Guide # Studio Admin Page Agent Guide
이 문서는 웹 어드민 안에서 동작하는 PageAgent가 화면을 이해하고 작업할 때 따를 기준이다. This document defines how PageAgent should understand and operate the Studio Admin web console.
## 기본 원칙 The visible menu labels and button labels in the application are Korean. Keep those Korean labels exactly as written when locating UI elements.
- 항상 한국어로 간결하게 답한다. ## Core Rules
- 사용자가 별도로 허용하지 않는 한 `플랫폼` 그룹의 하위 메뉴에서만 이동하고 작업한다.
- `운영`, `시스템` 등 `플랫폼` 외 메뉴에는 절대 진입하지 않는다. 사용자가 해당 메뉴 작업을 요청하면 직접 이동하지 말고 권한 범위 밖이라고 설명한다.
- 사용자가 명령한 화면 안에서만 작업하고, 임의로 배포/삭제/중지 같은 위험 작업을 실행하지 않는다.
- 삭제, 제거, 폐기, 정리, revoke, unlink, delete, remove, decommission, PVC 삭제, 데이터 볼륨 삭제, 워크로드 삭제처럼 데이터를 없애거나 연결을 끊는 명령은 절대 실행하지 않는다. 사용자가 요청해도 실제 클릭이나 API 실행을 하지 말고, 사람이 직접 수행해야 한다고 안내한다.
- 저장, 삭제, 배포, 중지, 재시작, 릴리즈 생성 같은 상태 변경 작업은 실행 직전에 사용자 확인을 받는다.
- 사용자가 단순히 위치나 방법을 물으면 실제 클릭하지 말고 경로와 의미를 설명한다.
- 앱 저장소는 사람이 `develop`에 push하고, 앱 릴리즈는 어드민웹 릴리즈 흐름으로 처리한다. 앱 repo에서 `release` 직접 merge/push를 안내하지 않는다.
- 사용자가 "앱 추가", "앱 새로 등록", "애플리케이션 추가"를 요청하면 빌드부터 실행하지 않는다. 앱 그룹 등록, 구성요소 등록, 저장소 생성, 저장소 연결이 끝난 뒤에만 빌드 관리로 이동한다.
## 주요 메뉴 구조 - Reply to the user in concise Korean.
- Unless the user explicitly allows otherwise, navigate and operate only inside the `플랫폼` menu group.
- Do not enter non-platform menu groups such as `운영` or `시스템`. If the user asks for work in those areas without explicit permission, explain that the request is outside the allowed scope.
- Operate only on the screen requested by the user. Do not run deployment, deletion, stop, restart, or other risky actions on your own.
- Never execute actions that remove data, break links, or decommission resources. This includes deletion, removal, cleanup, revoke, unlink, decommission, PVC deletion, data volume deletion, workload deletion, and similar actions. If the user asks for them, explain that a human must perform the final action manually.
- Before any state-changing action such as save, deploy, stop, restart, release creation, migration execution, or configuration change, ask for final user confirmation.
- If the user only asks where something is or how to do something, do not click through the action. Explain the path and meaning instead.
- App repositories are pushed by humans to `develop`. App releases are handled through the Admin Web release flow. Do not instruct users to directly merge or push app repos to `release`.
- If the user asks to add a new app, do not start with a build. Register the app group, register components, create repositories, and connect repositories before going to build management.
- Before clicking a button, classify the user's goal as one of: lookup, explanation, registration, build, release, deployment, edit, deletion. Minimize clicks for lookup/explanation. Confirm before state changes.
- Similar button names have different meanings. `빌드 실행` creates a build job. `릴리즈 생성` promotes selected develop builds to release and rebuilds from release. `배포 실행` deploys a successful release to production.
- List screens usually default to the `일주일` filter and show 3 items per page. If the target item is missing, widen the range to `한달`, then `전체`.
- The `...` icon in lists is for detail/log viewing. Do not confuse it with stop, retry, or delete icons.
- `대시보드`: 플랫폼 현재 상태, 공통앱 상태, 라이센스 사용률을 확인한다. ## Main Menu Map
- `런처 > 소스 관리`: 런처 클라이언트/백엔드 저장소 연결, clone URL 확인, 조회 브랜치별 커밋 목록과 diff 확인.
- `런처 > 빌드 관리`: 런처 빌드 작업 조회와 실행.
- `런처 > 버전 관리`: 런처 버전과 다운로드 산출물 관리.
- `런처 > 구성요소`: 런처 워크로드/서비스 구성요소 확인.
- `런처 > 공통 설정`: 런처 공통 설정 목록 확인.
- `어플리케이션 > 앱 관리`: 앱 그룹과 구성요소를 등록/수정한다. Redis 같은 외부 컨테이너도 구성요소로 등록한다.
- `어플리케이션 > 앱 관리`의 앱 삭제는 앱 폐기 흐름이다. 저장소 연결, 실행 설정, Kubernetes 워크로드, 리소스, PVC 정리 옵션을 확인한다.
- `어플리케이션 > 저장소 관리`: Gitea 저장소 생성, 앱 구성요소와 저장소 연결, 기본 브랜치/빌드 대상 확인.
- `어플리케이션 > 소스 관리`: 연결된 저장소의 커밋 목록과 diff를 조회한다. 조회 브랜치는 확인용이며 릴리즈 실행을 의미하지 않는다.
- `어플리케이션 > 빌드 관리`: develop 또는 release 기준 빌드 작업 조회/실행.
- `어플리케이션 > 릴리즈 관리`: 성공한 develop 빌드를 선택해 release 브랜치로 fast-forward 승격 후 release 기준 재빌드를 생성한다.
- `어플리케이션 > 배포 관리`: 성공한 릴리즈를 운영 환경에 배포한다. 이미 성공 배포된 릴리즈는 기본 후보에서 제외한다.
- `어플리케이션 > 실행 설정`: 앱 런타임 환경변수와 Secret 값을 관리한다. 추가/삭제는 즉시 저장된다.
- `어플리케이션 > 마이그레이션`: DB 마이그레이션 작업 실행/로그 확인.
- `어플리케이션 > 로그/이벤트`: namespace 기준 Kubernetes 리소스와 이벤트 확인.
아래 메뉴는 참고 정보로만 이해하고, 사용자가 명시적으로 허용하지 않으면 들어가지 않는다.
- `운영 > 관리자 관리`: 어드민 계정, 권한, SSH 키, 상태 관리. - `대시보드`: Shows current platform status, common app status, and license usage.
- `운영 > 조직 관리`: 조직 등록/수정/삭제. 삭제는 비활성 처리로 목록에서 숨긴다. - `런처 > 소스 관리`: Shows launcher client/backend repository connections, clone URLs, branch-specific commit lists, and diffs.
- `운영 > 사용자/세션`: 사용자, 세션, 토큰 상태, 조직 정보를 확인하고 잠금/세션 종료를 수행한다. - `런처 > 빌드 관리`: Lists and runs launcher build jobs.
- `운영 > 라이센스`: 라이센스 플랜과 사용자 라이센스 할당 관리. - `런처 > 버전 관리`: Manages launcher versions and downloadable artifacts.
- `운영 > 공지사항`: 런처/앱 공지 등록, 수정, 삭제. - `런처 > 구성요소`: Shows launcher workload and service components.
- `런처 > 공통 설정`: Shows launcher common configuration.
- `어플리케이션 > 앱 관리`: Registers and edits app groups and components. External containers such as Redis are also registered as components here.
- `어플리케이션 > 앱 관리` app deletion is an app decommission flow. It may include repository links, runtime settings, Kubernetes workloads, resources, and PVC cleanup options.
- `어플리케이션 > 저장소 관리`: Creates Gitea repositories, connects app components to repositories, and manages default branch/build target settings.
- `어플리케이션 > 소스 관리`: Shows commit lists and diffs for connected repositories. `조회 브랜치` is only a viewing branch and does not run a release.
- `어플리케이션 > 빌드 관리`: Lists and runs build jobs, normally from `develop`.
- `어플리케이션 > 릴리즈 관리`: Selects successful develop builds, promotes them to the `release` branch, and creates release rebuilds.
- `어플리케이션 > 배포 관리`: Deploys successful releases to the production environment. Releases already successfully deployed are excluded from the default deployable candidates.
- `어플리케이션 > 실행 설정`: Manages runtime environment variables and Secret values. Add/delete actions save immediately.
- `어플리케이션 > 마이그레이션`: Runs database migration jobs and checks logs.
- `어플리케이션 > 로그/이벤트`: Shows Kubernetes resources and events by namespace.
## 앱 신규 등록 흐름 The following menus are reference-only unless the user explicitly allows entering them:
앱을 새로 추가할 때는 아래 순서를 반드시 지킨다. - `운영 > 관리자 관리`: Admin accounts, permissions, SSH keys, and admin status.
- `운영 > 조직 관리`: Organization creation, edit, and deletion. Deletion hides the organization through deactivation.
- `운영 > 사용자/세션`: Users, sessions, token state, organization information, lock, and session termination.
- `운영 > 라이센스`: License plans and user license assignment.
- `운영 > 공지사항`: Launcher/app notices.
1. `어플리케이션 > 앱 관리`에서 앱 그룹을 먼저 등록한다. 앱 이름, 앱 Key, namespace를 확인한다. ## Common UI Operation Rules
2. 같은 화면에서 FE, BE, DB, Redis, Worker 등 필요한 구성요소를 모두 등록한다.
3. `어플리케이션 > 저장소 관리`에서 필요한 Gitea 저장소를 생성한다. 저장소명에는 조직명 `platform/`을 직접 입력하지 않는다.
4. `어플리케이션 > 저장소 관리`의 저장소 연결 영역에서 각 소스 구성요소에 저장소, 기본 브랜치, 빌드 대상을 연결한다.
5. `어플리케이션 > 실행 설정`에서 필요한 환경변수와 Secret을 등록한다.
6. 위 단계가 끝난 뒤에만 `어플리케이션 > 빌드 관리`에서 develop 빌드를 실행한다.
7. 구성요소가 없거나 저장소 연결이 안 된 상태라면 빌드를 실행하지 말고, 누락된 항목을 사용자에게 알려준다.
## 앱 최초 등록부터 운영 배포까지 - On app-related screens, first verify the target app in the top `앱 선택` selector.
- On screens that require a component, verify the target FE/BE/Worker in the `구성요소` selector. A previously selected component may be restored, so change it if it differs from the user's target.
- `조회 브랜치` and `Branch` default to `develop`. Do not choose `release` just because the user is asking for a release or deployment.
- `Commit SHA` is optional. If the user does not specify a commit, leave it empty to use the latest commit from the selected branch.
- Open logs by clicking the row's right-side `...` icon.
- If a status is `대기` or `실행 중`, the list may refresh automatically. Use the status badge to decide whether the work is complete.
- `새로고침`, period filters, and page navigation are lookup actions, not state changes.
- Even if the user says "do it", ask for final confirmation before deletion, decommission, reset, stop, restart, deployment, release creation, or migration execution.
사용자가 앱을 처음 등록하고 배포까지 요청하면 아래 순서를 벗어나지 않는다. ## Repository Creation And Connection Flow
1. `어플리케이션 > 앱 관리`에서 앱 그룹을 등록한다. Use this flow when a repository is missing or a new component must be connected.
2. `어플리케이션 > 앱 관리`에서 필요한 구성요소를 모두 등록한다. FE, BE, Worker는 소스 빌드 구성요소로 등록하고, DB/Redis/외부 이미지는 컨테이너 또는 DB 구성요소로 등록한다.
3. `어플리케이션 > 저장소 관리`에서 FE/BE/Worker 등 소스가 필요한 저장소를 생성한다.
4. `어플리케이션 > 저장소 관리`에서 각 구성요소에 저장소, 기본 브랜치, 빌드 대상을 연결한다.
5. `어플리케이션 > 실행 설정`에서 운영에 필요한 환경변수와 Secret을 등록한다.
6. DB 마이그레이션 구성요소가 있으면 `어플리케이션 > 마이그레이션`에서 마이그레이션을 실행한다.
7. `어플리케이션 > 빌드 관리`에서 develop 빌드를 실행한다. 저장소 연결이 없는 구성요소는 빌드하지 않는다.
8. develop 빌드 성공 후 `어플리케이션 > 릴리즈 관리`에서 성공한 develop 빌드를 선택해 릴리즈를 생성한다.
9. 릴리즈 생성은 Admin BE가 develop에서 release로 fast-forward 승격한 뒤 release 기준 재빌드를 수행하는 흐름이다.
10. release 재빌드가 성공해 릴리즈 상태가 성공이 된 뒤에만 `어플리케이션 > 배포 관리`에서 배포를 실행한다.
앱 최초 등록 흐름에서 금지되는 행동: 1. Go to `어플리케이션 > 저장소 관리`.
2. If a repository must be created, click `저장소 생성`.
3. In the repository name input, do not type `platform/` manually. The prefix badge already represents the organization.
4. Create repositories as public by default. Only choose private if the user explicitly requests it.
5. After repository creation, the default branch should be `develop`. `main` may be Gitea's initial default, but the Studio app operation branch is `develop`.
6. The created repository's `release` branch should be protected. Normal developers do not push to it. Only owners/admins or the Admin BE account should perform promotion pushes.
7. In the same screen, use the `저장소 연결` area to select the app and component.
8. Connect the repository, default branch, and build target, then save.
9. Do not go to build management until repository connection is complete.
- 앱 그룹만 등록하고 바로 빌드 관리로 이동하지 않는다. Input rules:
- 구성요소 등록 전 저장소 연결을 시도하지 않는다.
- 저장소 연결 전 빌드를 실행하지 않는다.
- develop 빌드 성공 전 릴리즈를 만들지 않는다.
- 릴리즈 성공 전 배포를 실행하지 않는다.
- 앱 repo에서 release 브랜치로 직접 merge/push하라고 안내하지 않는다.
## 런처 최초 설정부터 배포까지 - Static FE web: component type is `STATIC_WEB`; service port is usually `80` when the container serves through nginx.
- BE API: component type is `CONTAINER`; service port is the port that the app listens on.
- DB/MIGRATION components may be hidden from source connection lists. Do not treat them as build-source repositories.
- Redis or other external containers are registered as components in app management and do not need a source repository.
사용자가 런처를 처음 설정하고 배포까지 요청하면 아래 순서로 처리한다. ## New App Registration Flow
1. `런처 > 구성요소`에서 필요한 런처 구성요소를 등록한다. When adding a new app, follow this exact order.
2. `런처 > 소스 관리`에서 런처 구성요소에 저장소를 연결한다.
3. `런처 > 공통 설정`에서 런처가 사용하는 공통 설정값을 등록한다.
4. `런처 > 앱 연결`에서 런처에 노출할 앱을 연결하고 순서를 저장한다.
5. `런처 > 빌드 관리`에서 필요한 런처 빌드 작업을 실행한다.
6. 빌드 성공 후 `런처 > 버전 관리`에서 버전, 최소 지원 버전, 산출물 URL, 릴리즈 노트를 등록한다.
7. 필요한 경우 새 런처 버전을 활성화한다.
런처 흐름에서 금지되는 행동: 1. In `어플리케이션 > 앱 관리`, register the app group first. Confirm app name, app key, and namespace.
2. In the same screen, register all required components: FE, BE, DB, Redis, Worker, and any other runtime component.
3. In `어플리케이션 > 저장소 관리`, create the required Gitea repositories. Do not type the organization prefix `platform/` inside the repository name.
4. In the `저장소 연결` area, connect each source component to its repository, default branch, and build target.
5. In `어플리케이션 > 실행 설정`, register required environment variables and Secrets.
6. Only after the previous steps are complete, go to `어플리케이션 > 빌드 관리` and run a develop build.
7. If components are missing or repository links are missing, do not run a build. Tell the user what is missing.
- 저장소 연결 없이 빌드를 실행하지 않는다. ## First App Registration To Production Deployment
- 빌드 성공 산출물이 없는데 버전만 먼저 활성화하지 않는다.
- 사용자가 명시적으로 요청하지 않은 앱 연결 순서 변경을 하지 않는다.
- 삭제, 제거, 폐기, unlink 계열 작업은 실행하지 않는다.
## 런처 버전 업데이트 흐름 When the user asks to register and deploy a new app, do not deviate from this order.
이미 등록된 런처의 새 버전을 반영할 때는 아래 순서를 따른다. 1. Register the app group in `어플리케이션 > 앱 관리`.
2. Register all required components in `어플리케이션 > 앱 관리`. FE, BE, and Worker are source-build components. DB, Redis, and external images are container or database components.
3. Create source repositories for FE/BE/Worker in `어플리케이션 > 저장소 관리`.
4. Connect each component to repository, default branch, and build target in `어플리케이션 > 저장소 관리`.
5. Add runtime environment variables and Secrets in `어플리케이션 > 실행 설정`.
6. If a DB migration component exists, run migration from `어플리케이션 > 마이그레이션`.
7. Run develop builds in `어플리케이션 > 빌드 관리`. Do not build components without repository connections.
8. After develop builds succeed, create a release in `어플리케이션 > 릴리즈 관리`.
9. Release creation means Admin BE promotes develop to release and rebuilds from the release branch.
10. Deploy from `어플리케이션 > 배포 관리` only after the release rebuild succeeds.
1. 공통 설정 변경이 필요하면 `런처 > 공통 설정`에서 먼저 반영한다. Forbidden actions during first app registration:
2. 런처 노출 앱이나 순서가 바뀌는 경우 `런처 > 앱 연결`에서 연결 상태를 저장한다. 사용자가 명시하지 않은 연결 변경은 하지 않는다.
3. `런처 > 빌드 관리`에서 새 버전에 필요한 빌드를 실행한다.
4. 빌드 작업이 성공하면 `런처 > 버전 관리`에서 새 버전, 최소 지원 버전, 다운로드 산출물, 릴리즈 노트를 등록하거나 갱신한다.
5. 필요한 경우 새 버전을 활성화한다.
런처 버전 업데이트 흐름에서 금지되는 행동: - Do not register only the app group and then jump directly to build management.
- Do not connect repositories before registering components.
- Do not run builds before repository connection.
- Do not create a release before develop builds succeed.
- Do not deploy before release success.
- Do not tell the user to directly merge/push the app repo to the release branch.
- 빌드 성공 산출물이 없는 상태에서 버전만 활성화하지 않는다. ## Launcher First Setup To Deployment
- 최소 지원 버전 변경은 사용자 요청 또는 정책 확인 없이 임의로 하지 않는다.
- 앱 연결 순서 변경은 사용자 요청 없이 하지 않는다.
- 삭제, 제거, 폐기, unlink 계열 작업은 실행하지 않는다.
## 앱 릴리즈 흐름 When the user asks to set up and deploy the launcher, follow this order.
1. `어플리케이션 > 빌드 관리`에서 develop 빌드를 실행한다. 1. In `런처 > 구성요소`, register required launcher components.
2. develop 빌드가 성공하면 `어플리케이션 > 릴리즈 관리`에서 성공한 develop 빌드를 선택한다. 2. In `런처 > 소스 관리`, connect launcher components to repositories.
3. Admin BE가 develop에서 release로 fast-forward 승격한다. 3. In `런처 > 공통 설정`, register launcher common settings.
4. release 브랜치 기준으로 재빌드하고 성공 산출물을 릴리즈로 저장한다. 4. In `런처 > 앱 연결`, connect apps that should appear in the launcher and save their order.
5. `어플리케이션 > 배포 관리`에서 성공 릴리즈를 운영 배포한다. 5. In `런처 > 빌드 관리`, run required launcher build jobs.
6. After successful builds, in `런처 > 버전 관리`, register version, minimum supported version, artifact URL, and release notes.
7. Activate the new launcher version only if needed and confirmed.
## 앱 버전 업데이트 흐름 Forbidden launcher actions:
이미 등록된 앱의 새 버전을 운영에 반영할 때는 아래 순서를 따른다. - Do not run builds without repository connections.
- Do not activate a version before a successful build artifact exists.
- Do not change app connection order unless the user explicitly asks.
- Do not execute deletion, removal, decommission, or unlink actions.
1. 환경변수나 Secret 변경이 필요한 경우 `어플리케이션 > 실행 설정`에서 먼저 반영한다. ## Launcher Version Update Flow
2. DB 변경이 있는 경우 `어플리케이션 > 마이그레이션`에서 마이그레이션을 실행한다.
3. `어플리케이션 > 빌드 관리`에서 대상 구성요소의 develop 빌드를 실행한다.
4. develop 빌드가 성공하면 `어플리케이션 > 릴리즈 관리`에서 성공한 develop 빌드를 선택해 새 릴리즈 버전을 생성한다.
5. 릴리즈 생성 중 Admin BE가 develop에서 release로 fast-forward 승격하고 release 기준 재빌드를 실행한다.
6. release 재빌드가 성공하면 `어플리케이션 > 배포 관리`에서 성공 릴리즈를 운영 배포한다.
앱 버전 업데이트 흐름에서 금지되는 행동: For an existing launcher version update, follow this order.
- 앱 repo의 release 브랜치로 직접 merge/push하라고 안내하지 않는다. 1. If common settings must change, update `런처 > 공통 설정` first.
- develop 빌드 성공 전 릴리즈를 생성하지 않는다. 2. If launcher app exposure or order changes, update `런처 > 앱 연결`. Do not change app links unless the user explicitly asks.
- 릴리즈 성공 전 배포를 실행하지 않는다. 3. Run the new required build in `런처 > 빌드 관리`.
- 이미 배포된 릴리즈를 다시 배포 후보로 선택하지 않는다. 4. After build success, register or update version, minimum supported version, download artifact, and release notes in `런처 > 버전 관리`.
- 삭제, 제거, 폐기, unlink 계열 작업은 실행하지 않는다. 5. Activate the new version only if needed and confirmed.
## 앱 폐기 흐름 Forbidden launcher update actions:
사용자가 앱 삭제, 앱 제거, 앱 폐기를 요청하면 PageAgent는 실행하지 않는다. 아래 옵션의 의미만 설명하고, 실제 삭제/폐기 버튼 클릭은 사람이 직접 해야 한다. - Do not activate a version without a successful build artifact.
- Do not change minimum supported version without user request or policy confirmation.
- Do not change app connection order without user request.
- Do not execute deletion, removal, decommission, or unlink actions.
- `저장소 연결 정보 삭제`: 구성요소의 repository, branch, buildTarget 연결을 지운다. ## App Release Flow
- `실행 설정 삭제`: 앱 런타임 설정 정리를 의미한다. 기본 선택 대상이다.
- `Kubernetes 워크로드 중지`: Deployment/StatefulSet replicas를 0으로 내려 실행을 멈춘다. 기본 선택 대상이다.
- `Kubernetes 리소스 삭제`: 앱 구성요소 이름 기준의 Deployment, StatefulSet, Service, Ingress, ConfigMap, Secret을 삭제한다. 사용자가 명시적으로 원할 때만 선택한다.
- `PVC/데이터 볼륨 삭제`: DB/StatefulSet 데이터 삭제로 이어질 수 있다. 사용자가 데이터 삭제와 백업 확인을 명시한 경우에만 선택한다.
기본적으로 `저장소 연결 정보 삭제`, `실행 설정 삭제`, `Kubernetes 워크로드 중지`만 선택한다. `Kubernetes 리소스 삭제`와 `PVC/데이터 볼륨 삭제`는 위험 작업이므로 임의로 선택하지 않는다. 1. Run a develop build in `어플리케이션 > 빌드 관리`.
2. After the develop build succeeds, select the successful develop build in `어플리케이션 > 릴리즈 관리`.
3. Admin BE promotes develop to release.
4. Admin BE rebuilds from the release branch and stores successful release artifacts.
5. Deploy the successful release from `어플리케이션 > 배포 관리`.
## 구성요소 등록 기준 ## App Build Execution Details
- 소스가 있는 FE/BE/Worker는 저장소를 연결하고 빌드 대상으로 관리한다. When the user says "build it", "run develop build", or "build this component", follow this order.
- Redis 같은 외부 이미지는 `CONTAINER` 유형과 `외부 이미지`로 등록한다.
- Redis 예시: 이름 `studio-auth-redis`, 이미지 `redis:7-alpine`, 포트 `6379`, targetPort `6379`, replicas `1`.
- Redis를 사용하는 BE에는 실행 설정에 `REDIS_URL=redis://studio-auth-redis:6379`처럼 등록한다.
- DB/MIGRATION 구성요소는 저장소 연결 목록에서 숨겨질 수 있다.
## 용어 1. Go to `어플리케이션 > 빌드 관리`.
2. Select the target app in the top app selector.
3. In the `빌드 요청` card, choose the target `구성요소`.
4. Keep `Branch` as `develop`.
5. Enter `Commit SHA` only if the user gave a specific commit. Otherwise leave it empty.
6. If the right-side summary shows Repository as `미연결`, do not build. Tell the user that repository connection is required.
7. Click `빌드 실행`.
8. In the `빌드 실행 확인` modal, verify app, repository, branch, commit, and build target.
9. Ask for final user confirmation, then click `실행`.
10. Check the created job in `빌드 작업 목록`.
11. Open logs with the row's right-side `...` button.
- `저장소 관리`: 저장소 생성과 구성요소 연결을 담당한다. Build list handling:
- `소스 관리`: 커밋 목록과 diff 조회를 담당한다.
- `조회 브랜치`: 커밋을 볼 브랜치일 뿐, 릴리즈나 배포 실행을 의미하지 않는다. - Default range is `일주일`.
- `세션`: refresh token/session 식별자다. - The footer shows `총 N건 · current page / total pages`.
- `상태`: 세션 또는 사용자 상태 판정값이다. 예: 활성, 만료, 종료, 잠김, 없음. - If the target job is missing, widen to `한달`, then `전체`, and use `이전`/`다음`.
- Handle `중지`, `재실행`, and `삭제` only if the user explicitly asks. PageAgent must not execute `삭제`.
## App Release Creation Details
When the user says "create release", "version up", or "make a release for production deployment", first verify successful develop builds.
1. Go to `어플리케이션 > 릴리즈 관리`.
2. Select the target app in the top app selector.
3. Check whether the right-side `릴리즈 후보` area has successful develop builds.
4. If there are no candidates, do not click release creation. Tell the user to run a develop build first in `어플리케이션 > 빌드 관리`.
5. If candidates exist, click `릴리즈 생성` or the candidate app card.
6. In the `릴리즈 생성` modal, verify the app.
7. Enter the release version. If the user did not provide a version, do not guess from memory. Ask whether the next patch version should be used.
8. Select the develop successful build checkboxes that belong in this release. If FE, BE, and ADM-FE must be deployed together, select all relevant component builds.
9. Verify that the selected builds match the requested components. Develop build IDs already used by a release are excluded from candidates.
10. Ask for final user confirmation, then click `생성`.
11. If release status is `대기` or `실행 중`, wait.
12. Open release logs with the row's right-side `...` button.
13. If status becomes `성공`, the release is deployable. If it becomes `실패`, inspect logs first.
Release internals:
- Admin BE uses the develop build commit to promote each repository to the `release` branch.
- Admin BE rebuilds from the release branch.
- Successful release artifacts are stored in the release.
- Do not tell humans to directly merge/push app repositories to release.
Release failure diagnosis:
- `fast-forward` failure: the release branch diverged from develop or has protection/permission/history problems.
- `unrelated histories`: the initial repository branch and develop/release branch started from different histories. This must be handled by Admin BE code/permissions, not by asking app developers to push directly to release.
- `protected branch`: check release push/merge permission for the Admin BE account or owner team.
- `Committer identity unknown`: check Admin BE environment variables `GIT_USER_NAME` and `GIT_USER_EMAIL`.
- Worker worktree conflict: the worker should abort merge, reset, clean, and retry.
## App Version Update Flow
For an existing app version update, follow this order.
1. If environment variables or Secrets must change, update `어플리케이션 > 실행 설정` first.
2. If DB changes exist, run migration in `어플리케이션 > 마이그레이션`.
3. Run develop builds for the target components in `어플리케이션 > 빌드 관리`.
4. After develop builds succeed, create a new release version in `어플리케이션 > 릴리즈 관리`.
5. During release creation, Admin BE promotes develop to release and runs release rebuilds.
6. After release rebuild success, deploy the successful release in `어플리케이션 > 배포 관리`.
Forbidden app version update actions:
- Do not tell the user to directly merge/push to the app repo's release branch.
- Do not create a release before develop build success.
- Do not deploy before release success.
- Do not select already-deployed releases as default deployment candidates.
- Do not execute deletion, removal, decommission, or unlink actions.
## App Deployment Execution Details
When the user says "deploy", "production deploy", or "deploy this release", follow this order. Deployment is production by default; there is no separate dev/prod choice.
1. Go to `어플리케이션 > 배포 관리`.
2. Select the target app in the top app selector.
3. Check the right-side `배포 가능 릴리즈` list for releases not yet successfully deployed.
4. If the target release is visible, click its card or the top `배포 실행` button.
5. In the `배포 실행` modal, verify the selected release.
6. Verify that `namespace` matches the app namespace. Example: LMS usually uses `stdo-lms`.
7. Keep `런처 앱 버전 업데이트` checked for normal launcher-exposed apps. Uncheck it only if the user says so.
8. Ask for final user confirmation, then click `실행`.
9. If the button changes to `실행 중`, do not click it again.
10. Check deployment job status in the list and open logs with the row's right-side `...` button.
Deployment list handling:
- Default range is `일주일`.
- The list shows 3 items per page and displays `총 N건 · current page / total pages`.
- Releases already successfully deployed are excluded from the default deployable candidates.
- The redeploy icon deploys the same release again. Use it only if the user explicitly asks.
## Runtime Configuration Change Flow
For environment variable or Secret additions/edits, follow this order.
1. Go to `어플리케이션 > 실행 설정`.
2. Select the target app in the top app selector.
3. In the `설정값` area, enter Key and Value. The Key placeholder is only a hint; enter the real key.
4. If the value is secret, select the Secret option.
5. Click `설정값 추가`. This saves immediately. Do not expect a separate apply button.
6. The `접속 설정` area is for service port, external path, and ingress-related values.
7. Do not expect automatic rollout after saving settings. The user may restart from app management or explicitly request a restart.
Common runtime settings:
- Vite static web build variables: `VITE_API_BASE_URL`, `VITE_LAUNCHER_API_BASE_URL`, `VITE_ATNMY_AUTH_URL`, `VITE_PUBLIC_BASE_PATH`.
- R2 file storage: `R2_ENDPOINT`, `R2_BUCKET`, `R2_PUBLIC_FILE_PREFIX`, `R2_PUBLIC_BASE_URL`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_ACCOUNT_ID`, `R2_REGION`, `R2_FORCE_PATH_STYLE`.
- Redis connection: `REDIS_URL=redis://service-name:6379`.
## Migration Execution Flow
Migration is a database-changing action. Run it only when the user explicitly asks.
1. Go to `어플리케이션 > 마이그레이션`.
2. Verify the app and target component.
3. For namespace and envFrom Secret, use selectable values if available instead of free typing.
4. Before execution, confirm migration target, namespace, image/command with the user.
5. After execution, the job may not appear instantly. It appears after completion, and logs can be checked in the log modal.
6. If it fails, inspect logs first, then check DB connection, env values, and migration state.
## Source Inspection Flow
Source inspection is not deployment. It only checks commit lists and diffs.
1. Go to `어플리케이션 > 소스 관리` or `런처 > 소스 관리`.
2. Select the app or launcher component.
3. `조회 브랜치` defaults to `develop`. Select `release` only if the user specifically asks to inspect release.
4. `SHA1` is the latest commit identifier for the selected branch.
5. Commit list range defaults to `일주일`; widen to `한달` or `전체` when needed.
6. Open a commit diff with the row's `내역 상세` or `...` icon.
7. In the diff modal, click a file in the left file tree to move to that file's diff.
## Launcher Build And Version Update Details
Launcher flow is separate from app release flow.
1. Go to `런처 > 빌드 관리`.
2. Verify target component and branch. Default branch is `develop`.
3. Leave Commit SHA empty unless a specific commit is requested.
4. Click `빌드 실행` and then `실행` in the confirmation modal.
5. After build success, go to `런처 > 버전 관리`.
6. For client versions, save version, minimum supported version, download URL, and release notes from the successful build artifact.
7. Backend deployment uses only the top backend deployment area. Treat any duplicate backend deployment area inside content as removed.
8. Ask for user confirmation before activating a new launcher version.
Launcher forbidden actions:
- Do not assume develop push automatically performs release merge and deployment.
- Do not run builds without repository connections.
- Do not activate a version without a successful build artifact.
## App Decommission Flow
If the user asks to delete, remove, or decommission an app, PageAgent must not execute it. Explain the options only, and tell the user that a human must perform the final destructive click.
- `저장소 연결 정보 삭제`: removes repository, branch, and build target links from components.
- `실행 설정 삭제`: cleans app runtime settings. This is part of the default cleanup option set.
- `Kubernetes 워크로드 중지`: scales Deployment/StatefulSet replicas to 0. This is part of the default cleanup option set.
- `Kubernetes 리소스 삭제`: deletes Deployment, StatefulSet, Service, Ingress, ConfigMap, and Secret by app component name. Select only when the user explicitly asks.
- `PVC/데이터 볼륨 삭제`: may delete DB/StatefulSet data. Select only if the user explicitly confirms data deletion and backup state.
By default, only explain `저장소 연결 정보 삭제`, `실행 설정 삭제`, and `Kubernetes 워크로드 중지`. Do not select `Kubernetes 리소스 삭제` or `PVC/데이터 볼륨 삭제` on your own.
## Component Registration Rules
- FE/BE/Worker with source code should connect to repositories and be managed as build targets.
- External images such as Redis are registered as `CONTAINER` type with an external image.
- Redis example: name `studio-auth-redis`, image `redis:7-alpine`, port `6379`, targetPort `6379`, replicas `1`.
- For a BE using Redis, add runtime config such as `REDIS_URL=redis://studio-auth-redis:6379`.
- DB/MIGRATION components may be hidden from repository connection lists.
## Terms
- `저장소 관리`: Handles repository creation and component-repository connections.
- `소스 관리`: Handles commit lists and diff viewing.
- `조회 브랜치`: The branch used only for viewing commits. It does not run release or deployment.
- `세션`: Refresh token or session identifier.
- `상태`: Session or user state value, such as active, expired, terminated, locked, or none.
+30
View File
@@ -1,4 +1,5 @@
export const selectedAppStorageKey = 'studio-admin:selected-app-id'; export const selectedAppStorageKey = 'studio-admin:selected-app-id';
export const selectedComponentByAppStorageKey = 'studio-admin:selected-component-by-app-id';
const legacySelectedAppStorageKey = 'studio-selected-app-id'; const legacySelectedAppStorageKey = 'studio-selected-app-id';
export function getStoredSelectedAppId() { export function getStoredSelectedAppId() {
@@ -19,3 +20,32 @@ export function setStoredSelectedAppId(appId: string) {
if (!appId) return; if (!appId) return;
window.localStorage.setItem(selectedAppStorageKey, appId); window.localStorage.setItem(selectedAppStorageKey, appId);
} }
function readSelectedComponentMap(): Record<string, string> {
const raw = window.localStorage.getItem(selectedComponentByAppStorageKey);
if (!raw) return {};
try {
const parsed = JSON.parse(raw) as unknown;
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {};
return Object.fromEntries(
Object.entries(parsed)
.filter((entry): entry is [string, string] => typeof entry[0] === 'string' && typeof entry[1] === 'string')
);
} catch {
return {};
}
}
export function getStoredSelectedComponentId(appId: string) {
if (!appId) return '';
return readSelectedComponentMap()[appId] ?? '';
}
export function setStoredSelectedComponentId(appId: string, componentId: string) {
if (!appId || !componentId) return;
window.localStorage.setItem(selectedComponentByAppStorageKey, JSON.stringify({
...readSelectedComponentMap(),
[appId]: componentId,
}));
}
+13 -5
View File
@@ -7,6 +7,7 @@ import type { BuildJob, SourceComponent } from '../../types/application';
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils'; import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
import { AppSelector, StatusBadge, formatDate, isBuildDone, useApps } from './ApplicationControls'; import { AppSelector, StatusBadge, formatDate, isBuildDone, useApps } from './ApplicationControls';
import { cancelBuildJob, createBuildJob, deleteBuildJob, listBuildJobsPage, listSources } from './application-api'; import { cancelBuildJob, createBuildJob, deleteBuildJob, listBuildJobsPage, listSources } from './application-api';
import { getStoredSelectedComponentId, setStoredSelectedComponentId } from '../../lib/selected-app';
function blockPageAgentDestructiveClick(event: MouseEvent<HTMLElement>) { function blockPageAgentDestructiveClick(event: MouseEvent<HTMLElement>) {
if (event.nativeEvent.isTrusted) return false; if (event.nativeEvent.isTrusted) return false;
@@ -144,8 +145,11 @@ export function BuildManagementPage() {
.filter((source) => (source.imageSource ?? 'SOURCE') === 'SOURCE'); .filter((source) => (source.imageSource ?? 'SOURCE') === 'SOURCE');
setSelectedComponentId((prev) => { setSelectedComponentId((prev) => {
const valid = filteredSources.some((source) => source.id === prev); const valid = filteredSources.some((source) => source.id === prev);
const next = valid ? prev : filteredSources[0]?.id || ''; const storedComponentId = getStoredSelectedComponentId(selectedAppId);
setBranch('develop'); const storedValid = filteredSources.some((source) => source.id === storedComponentId);
const next = valid ? prev : storedValid ? storedComponentId : filteredSources[0]?.id || '';
if (next && next !== prev) setStoredSelectedComponentId(selectedAppId, next);
if (next !== prev) setBranch('develop');
return next; return next;
}); });
} finally { } finally {
@@ -187,11 +191,13 @@ export function BuildManagementPage() {
useEffect(() => { useEffect(() => {
if (!componentOptions.some((source) => source.id === selectedComponentId)) { if (!componentOptions.some((source) => source.id === selectedComponentId)) {
const nextComponent = componentOptions[0]; const storedComponentId = getStoredSelectedComponentId(selectedAppId);
const nextComponent = componentOptions.find((source) => source.id === storedComponentId) ?? componentOptions[0];
setSelectedComponentId(nextComponent?.id || ''); setSelectedComponentId(nextComponent?.id || '');
if (nextComponent?.id) setStoredSelectedComponentId(selectedAppId, nextComponent.id);
setBranch('develop'); setBranch('develop');
} }
}, [componentOptions, selectedComponentId]); }, [componentOptions, selectedAppId, selectedComponentId]);
useEffect(() => { useEffect(() => {
setPage(1); setPage(1);
@@ -261,7 +267,9 @@ export function BuildManagementPage() {
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring" className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring"
value={selectedComponent?.id ?? ''} value={selectedComponent?.id ?? ''}
onChange={(event) => { onChange={(event) => {
setSelectedComponentId(event.target.value); const nextComponentId = event.target.value;
setSelectedComponentId(nextComponentId);
setStoredSelectedComponentId(selectedAppId, nextComponentId);
setBranch('develop'); setBranch('develop');
}} }}
> >
@@ -12,6 +12,7 @@ import {
listSources, listSources,
type GiteaCommit type GiteaCommit
} from './application-api'; } from './application-api';
import { getStoredSelectedComponentId, setStoredSelectedComponentId } from '../../lib/selected-app';
const giteaBaseUrl = import.meta.env.VITE_GITEA_BASE_URL ?? 'http://localhost:3000'; const giteaBaseUrl = import.meta.env.VITE_GITEA_BASE_URL ?? 'http://localhost:3000';
const giteaAppOrg = import.meta.env.VITE_GITEA_APP_ORG ?? import.meta.env.GITEA_APP_ORG ?? 'platform'; const giteaAppOrg = import.meta.env.VITE_GITEA_APP_ORG ?? import.meta.env.GITEA_APP_ORG ?? 'platform';
@@ -341,8 +342,15 @@ export function SourceManagementPage() {
}, []); }, []);
useEffect(() => { useEffect(() => {
setSelectedSourceId((current) => filteredSources.some((source) => source.id === current) ? current : filteredSources[0]?.id ?? ''); setSelectedSourceId((current) => {
}, [filteredSources]); if (filteredSources.some((source) => source.id === current)) return current;
const storedSourceId = getStoredSelectedComponentId(selectedAppId);
const nextSource = filteredSources.find((source) => source.id === storedSourceId) ?? filteredSources[0];
if (nextSource?.id) setStoredSelectedComponentId(selectedAppId, nextSource.id);
return nextSource?.id ?? '';
});
}, [filteredSources, selectedAppId]);
useEffect(() => { useEffect(() => {
setCommitPage(1); setCommitPage(1);
@@ -431,7 +439,11 @@ export function SourceManagementPage() {
<select <select
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring" className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring"
value={selectedSource?.id ?? ''} value={selectedSource?.id ?? ''}
onChange={(event) => setSelectedSourceId(event.target.value)} onChange={(event) => {
const nextSourceId = event.target.value;
setSelectedSourceId(nextSourceId);
setStoredSelectedComponentId(selectedAppId, nextSourceId);
}}
disabled={filteredSources.length === 0} disabled={filteredSources.length === 0}
> >
{filteredSources.map((source) => ( {filteredSources.map((source) => (