모델 게이트웨이 화면(포탈 컨트롤센터 콘솔) 작업 인수인계 — 2026-09-26
다른 세션에서 이어서 작업하기 위한 노트입니다. 결론부터: 이 화면은 antd 스타일이 아니라 자체 div + 자체 CSS 박스로 만들어져 있습니다. 다음 단계는 콘솔이 이미 쓰고 있는
@hohyun94/portal-foundation(내부적으로 antd 6 래퍼) 컴포넌트로 화면을 재구성하는 것입니다.
0. 한눈에 보기
| 항목 | 값 |
|---|---|
| 대상 | 관리자 콘솔 → 탭 모델 게이트웨이 관리 (ModelGatewayDesk) |
| 화면 코드 | client/src/components/ModelGatewayDesk.tsx — 현재 1006줄(작업 전 859줄) |
| 화면 스타일 | client/src/styles/modelGatewayDesk.css — 현재 117줄(작업 전 69줄, 이번에 재작성) |
| 마운트 지점 | client/src/pages/DirectoryAdmin.tsx:534 → {tab === "model-gateway" && <ModelGatewayDesk onNotice={setNotice} />} |
| 콘솔 서버 | server/modelGateway.ts(게이트웨이 /admin/api 서버간 프록시) + server/routers.ts:104 의 gateway: 라우터 |
| 게이트웨이 쪽 API | ModelGateway/src/admin/routes.ts (state / providers / keys / verify / settings / usage) |
| UI 기반 | @hohyun94/portal-foundation@0.6.156 → 의존성에 antd ^6.6.3(설치본 6.6.5), @ant-design/icons ^6.3.4 |
| antd 직접 사용 | client/src·server 전체에서 from "antd" 0건 — 모든 UI는 portal-foundation 래퍼 + 자체 CSS(reference-portal-frame.css + 데스크별 CSS) |
| 이번 세션 검증 | npx tsc --noEmit → exit 0 / prettier --check → warn (HEAD 원본도 동일하게 warn, 기존 상태) / vitest·build 결과는 §4 |
| 커밋·배포 | 미커밋 · 미배포. git status = 위 2개 파일 수정만 |
작업 디렉터리 / 리포지토리 주의(중요)
- 로컬 리포지토리:
/home/hohyun/projects/PortalConsole, remote =git@github.com:hohyun94/PortalConsole.git - 배포(Coolify)는 다른 리포지토리를 본다: 앱
directory-service:main-h3igjh2lszlqoegqinwitlyxgit_repository = hohyun94/DirectoryService,git_branch = main,build_pack = railpack,fqdn = https://directory.rlab.monster- 배포 번들(
/app/dist/public/assets/index-Drf04BCZ.js)에모델 게이트웨이 관리문자열이 있어 이 콘솔 UI가 그 앱에 포함됨을 확인함 - ⇒ 푸시 대상 확인 필요: 로컬 remote가
PortalConsole인데 Coolify는DirectoryService를 본다. 둘 중 어느 쪽에 푸시해야 반영되는지 먼저 확인할 것(리포 이름 변경/동기화 여부).
1. 지금 이 화면이 하는 일(동작은 그대로 유지됨)
- 상단 상태 카드 4개 — 게이트웨이 ON/OFF·주소, 프로바이더 수(활성/기본), 키 암호화(AES-256-GCM), 배포 프로필 +
상태 새로고침 - 프로바이더와 API 키 — 프로바이더별: 키 힌트 목록(사용/중지 토글, 삭제), 키 추가(8자 이상),
연결 테스트(발견 모델 반영), 편집/삭제 - 프로바이더 추가·편집 폼 — 프리셋, ID, 종류(openai-compatible/anthropic), 표시 이름, API 주소, 모델 목록, 사용 여부, 기본 프로바이더
- 실행 설정 — 기본/허용 모델,
purpose 매핑행 편집,단가표(USD/1M)override 행(입력/출력/캐시 읽기/피크 배율/휴일), 분당 토큰 한도, 쿼터 예약, 요청 타임아웃 - 사용량·비용 — 포털 ID/purpose/기간(1~30일) 필터 → 합계 카드 5개(요청·실패·토큰·평균 지연·금액 USD) + 모델별/일별 분해(단가 미입력 행에
단가 행 추가)
tRPC 프로시저(변경 없음): gateway.status, gateway.state, gateway.saveProvider, gateway.deleteProvider, gateway.addKey, gateway.setKeyEnabled, gateway.deleteKey, gateway.verifyProvider, gateway.saveSettings, gateway.usage.
2. 이번 세션에서 바꾼 것 (전부 "레이아웃·문구·테마 정리" 범위, 동작 로직 변경 없음)
2-1. CSS 재작성 — client/src/styles/modelGatewayDesk.css
형제 데스크(fileStoreDesk.css:1-66, ontologyDesk.css:1-40)의 관례에 맞춰 처음부터 다시 작성했습니다.
- 모든 선택자를
.directory-admin-content안으로 스코프. (수정 전에는 사용량 관련.model-gateway-usage-*가 스코프 밖이라 다른 화면과 충돌 가능) - 라이트 1블록 + 다크(네이비) 1블록으로 통합(수정 전엔 테마 규칙이 파일 중간·끝 두 곳에 흩어져 있었음)
- 라이트 색은 형제와 동일 팔레트: line
#d6d0c5/ surface#fffdf8/ muted surface#f6f3ec/ text#796d5f/ accent#81553f/ ok#407c62/ danger#8c4130 - 다크는 기존 CSS 변수 사용:
--directory-dark-line,--directory-dark-panel,--directory-navy-800,--directory-ice,--directory-dark-muted,--directory-accent - 새 규칙 추가
- 뱃지:
.model-gateway-badges span+is-on/is-off/is-default(점선) — FileStore 데스크의.file-store-badges span과 같은 모양 - 링크형 보조 액션:
.model-gateway-action(+.is-danger,:disabled) — FileStore.file-store-action과 동일 패턴 .model-gateway-actions(액션 줄),.model-gateway-form-actions(폼 저장 버튼 줄),.model-gateway-key-empty(키 없음 문구),.model-gateway-subheading(프레임의.secondary-heading과!important충돌 회피용 소제목),.model-gateway-verify(+.is-error)- 사용량 막대 색:
var(--pf-accent)→ 테마별 색(라이트#81553f, 다크var(--directory-accent))
- 뱃지:
2-2. 문구·구조 정리 — client/src/components/ModelGatewayDesk.tsx
- i18n 적용: 이 파일은
const text = (ko, en) => locale === "en" ? en : ko헬퍼를 정의만 하고 거의 쓰지 않았습니다(전부 하드코딩 한글). 이번에 화면에 보이는 모든 라벨·설명·카드 문구·옵션 값에text()를 적용했습니다(형제FileStoreDesk.tsx와 동일 방식).onNotice(...)토스트 문구는FileStoreDesk관례대로 한글 유지. - 뱃지 정리:
portal-access-status(전역 알약, 변형 없음) →.model-gateway-badges+is-on/is-off/is-default. 사용량의단가 미입력도 같은 뱃지로 이관. - 액션 위계 정리
- 주요 액션 유지:
키 저장(PortalButton),조회(PortalButton) /연결 테스트는quiet-button으로 낮춤 - 보조 액션을 링크형으로:
편집,삭제(프로바이더), 키사용/중지, 키삭제,목적/단가 행 삭제,발견 모델 반영 - 파괴적 액션에 확인창 추가(형제 관례
window.confirm): 프로바이더 삭제, 키 삭제
- 주요 액션 유지:
- 폼 정리: 프로바이더 폼·실행 설정 폼의 저장 버튼을 그리드 밖
.model-gateway-form-actions로 이동(형제.file-store-form-actions패턴). 키 입력 필드에 8자 제약 설명 추가. - 소제목 교체:
purpose 매핑·단가표의 중첩.secondary-heading→.model-gateway-subheading(프레임이.secondary-heading을!important로 잡아 배경·보더가 겹치던 문제 회피) - 문구 명확화: 단가 필드 라벨
입력/출력/캐시 히트→입력 (USD/1M) / 출력 (USD/1M) / 캐시 읽기 (USD/1M),공휴일→휴일, 실효 단가 힌트현재 …→현재 적용: 입력 … / 출력 … / 캐시 … · 피크 ×n,purpose매핑 설명에서 내부 용어(예약값 auto) 제거, 사용량 필터에 "비우면 전체 …" 설명 추가 - 상태 표시 추가:
state.isLoading일 때 안내 한 줄(게이트웨이 상태를 불러오는 중입니다…)
2-3. 파일 크기 변화
작업 전: ModelGatewayDesk.tsx 859줄 / modelGatewayDesk.css 69줄
작업 후: ModelGatewayDesk.tsx 1006줄 / modelGatewayDesk.css 117줄(줄 증가는 대부분 text(ko, en) 2줄 포맷과 설명 문장 분리 때문)
3. 다음에 할 일 — 우선순위대로
3-1. ⭐ 방향 전환 제안: "자체 CSS 박스" → portal-foundation(antd 6) 컴포넌트
이번 세션에서 한 정리는 기존 자체 CSS 구조를 유지한 채 스코프·테마·문구를 맞춘 것입니다. 그런데 화면 자체가 antd 스타일이 아니라는 지적이 있었고, 사실 확인 결과:
- 콘솔의 UI 라이브러리는
@hohyun94/portal-foundation@0.6.156이고, 이 패키지가 antd ^6.6.3 / @ant-design/icons ^6.3.4를 의존성으로 가집니다. - 그런데
client/src에는from "antd"가 0건입니다. 즉 콘솔은 antd를 직접 쓰지 않고 래퍼만 씁니다. ModelGatewayDesk는 래퍼 필드 4개(PortalButton,PortalSelect,PortalTextField,PortalTextArea)만 쓰고, 나머지 영역은 자체div+ 자체 CSS 클래스로 만든 화면입니다. → 그래서 antd스럽지 않습니다.
다음 세션에서는 아래 이미 내보내고 있는 컴포넌트로 재구성하는 것을 권합니다(이름은 설치본 node_modules/@hohyun94/portal-foundation/dist/*.d.ts에서 확인).
| 용도 | 후보 컴포넌트 |
|---|---|
| 구역(섹션) 틀 | AdminSection, DataListShell, DataDetailShell, DataStatePanel |
| 카드/요약 카드 | PortalCard, DataStatusBadge, OperationalMetricGrid (현재 ops-cards 수동 마크업 대체) |
| 표 | PortalDataTable (+ antdTableQueryToPortalQuery, normalizePortalFilters) |
| 상세/요약 | PortalDescriptions |
| 빈 상태/오류 | PortalEmptyState, AdminEmptyState, PortalErrorState, DataStatePanel |
| 확인창/모달 | PortalConfirm, PortalDialog (현재 window.confirm 대체) |
| 피드백 | ActionFeedback, AdminFeedback, FeedbackMessage |
| 폼 요소 | PortalButton, PortalSelect, PortalTextField, PortalTextArea, PortalCheckbox |
| 기타 | PortalBreadcrumbs, PortalAiSummaryCard, PortalAntdIcon/getPortalAntdIcon(아이콘 일관화) |
작업 방식 제안: ① 화면만 먼저 컴포넌트로 치환(데이터·tRPC 호출은 그대로) → ② 치환 후 modelGatewayDesk.css에서 대체된 규칙 삭제 → ③ 콘솔 다른 데스크(FileStore/Ontology)도 같은 방향으로 가는지 확인해 한 화면만 다른 스타일이 되지 않게 할 것.
⚠️ 주의: reference-portal-frame.css는 .admin-secondary, .secondary-heading, .ops-cards > div, .settings-security-note 등을 !important로 덮습니다. portal-foundation 컴포넌트로 바꾸면 이 전역 규칙과 충돌할 수 있으니, 치환 시 이 프레임 파일도 함께 봐야 합니다(현재 CSS 파일은 그 충돌을 피해 작성되어 있음).
3-2. 남은 정리 작업(작은 것)
- [ ] 프로바이더 카드의
키 저장입력+버튼이 한 줄에 몰려 있음 →PortalTextArea/PortalCard기반 재배치 검토 - [ ]
usage필터에 드롭다운 선택지 제공(현재 자유 입력):KNOWN_PURPOSES(컴포넌트 상단 상수) 재사용, 포털 목록은 tRPC로 조회 가능한지 확인 - [ ] 기간 프리셋(1/7/30일) 버튼 추가 여부 결정
- [ ] 영어 로케일 확인:
useLocale()로en전환 후 화면 문구가 모두 영어인지 점검(이번에text()적용했지만 실제 브라우저 확인은 미실시) - [ ] 다크/라이트 두 테마에서 실제 렌더 확인(이번 변경의 핵심이 테마 정리이므로 스크린샷 확인 필요)
- [ ] 접근성: 링크형 액션(
.model-gateway-action)은<button>이라 키보드 포커스는 되지만,aria-label/아이콘만 있는 액션 문구 점검
3-3. 커밋 전 체크리스트
- [ ]
npx prettier --write client/src/components/ModelGatewayDesk.tsx client/src/styles/modelGatewayDesk.css(※ HEAD 원본도 prettier warn 상태 — 프로젝트가pnpm format을 상시 적용하지는 않는 것으로 보임. 팀 규칙 확인 후 결정) - [ ]
npx tsc --noEmit(현재 통과) /npx vitest run/npx vite build - [ ] 화면 실제 확인(
pnpm dev또는 배포 URL) - [ ] 커밋 메시지 제안:
refactor(console): 모델 게이트웨이 화면의 레이아웃·문구·테마를 데스크 관례에 맞춘다
4. 검증 현황 (이번 세션에서 실제로 돌린 결과)
cd /home/hohyun/projects/PortalConsole
npx tsc --noEmit → exit 0 (타입 오류 없음)
npx prettier --check <두 파일> → warn (HEAD 원본도 동일하게 warn → 기존 상태, 이번 변경이 만든 문제 아님)
npx vitest run → Test Files 74 passed | 2 skipped (76) · Tests 403 passed | 4 skipped (407) · 27.4s
npx vite build → ✓ built in 8.40s
dist/public/assets/index-HWYMzOQ4.css 321.11 kB
dist/public/assets/index-BKC3_1zi.js 1,484.42 kB
(500 kB 초과 chunk 경고만 있음 — 기존과 동일한 경고)아직 하지 않은 검증: 브라우저에서의 실제 렌더(라이트/다크, 영어 로케일), 배포 환경 확인.
자주 쓰는 명령
cd /home/hohyun/projects/PortalConsole
pnpm dev # 개발 서버
pnpm check # tsc --noEmit
pnpm test # vitest run
pnpm build # vite build + esbuild 서버 번들
pnpm verify # check + build5. 배포 경로와 주의사항
- 콘솔(이 화면이 들어가는 앱)은 Coolify 앱
directory-service:main-h3igjh2lszlqoegqinwitlyx- uuid
h3igjh2lszlqoegqinwitlyx· fqdnhttps://directory.rlab.monster· build_packrailpack· branchmain - 보고 있는 리포지토리:
hohyun94/DirectoryService← 로컬 remote(hohyun94/PortalConsole)와 다름. 푸시 대상 확인 필요
- uuid
- Coolify 서버 접근:
ssh rlab(Oracle, 공인 168.138.33.231) · Coolify 4.3.23 · APIhttp://127.0.0.1:8000/api/v1- 배포 트리거는 POST
/api/v1/deploy, body{"uuid":"h3igjh2lszlqoegqinwitlyx","force":false}(GET은405 This endpoint has changed to a POST request.) - API 토큰은 admin(권한
root) 계정으로 발급해야 하며, 앞선 세션에서 쓴 토큰은 폐기됨(personal_access_tokens비어 있음) → 필요하면 새로 발급 - 자동 배포(auto deploy) 설정은 확인되지 않음 → 푸시 후 Coolify에서 Redeploy 필요
- 배포 트리거는 POST
- 배포 앱에 필요한 환경변수(이미 설정됨):
MODEL_GATEWAY_URL,MODEL_GATEWAY_ADMIN_TOKEN(게이트웨이https://model-gateway.rlab.monster),PORTAL_ALLOW_INSECURE_PLATFORM_URL등 - 화면 확인 경로:
https://directory.rlab.monster로그인 → 관리자 콘솔 → 좌측 모델 게이트웨이 관리 탭
6. 롤백
- 이번 변경은 커밋 전이므로 가장 안전한 롤백은 그대로 되돌리기:bash
cd /home/hohyun/projects/PortalConsole git checkout -- client/src/components/ModelGatewayDesk.tsx client/src/styles/modelGatewayDesk.css - CSS만 되돌리려면 이전 파일 백업이
/tmp/modelGatewayDesk.css.bak에 있습니다(세션 임시 파일이라 오래 남지 않을 수 있음 → 위git checkout권장). - 이미 배포했다면 Coolify Deployment 목록에서 직전 배포로 재배포하거나
git revert후 재배포합니다.
7. 다음 세션에서 바로 쓰는 시작 프롬프트(복붙용)
포탈 컨트롤센터(/home/hohyun/projects/PortalConsole)의 '모델 게이트웨이 관리' 화면 작업을 이어서 하자.
먼저 docs/MODEL_GATEWAY_DESK_HANDOFF_2026-09-26.md 를 읽어줘.
대상: client/src/components/ModelGatewayDesk.tsx , client/src/styles/modelGatewayDesk.css
방향: 자체 div+CSS 박스 구조를 @hohyun94/portal-foundation(antd 6 래퍼) 컴포넌트로 재구성
(PortalCard / PortalDataTable / PortalDescriptions / DataStatusBadge / OperationalMetricGrid /
PortalConfirm / PortalEmptyState / PortalErrorState / AdminSection 등)
제약: tRPC 호출·서버 코드(server/modelGateway.ts, server/routers.ts)는 이번엔 건드리지 말고 화면만.
reference-portal-frame.css 의 !important 규칙과 충돌하지 않는지 확인.
검증: npx tsc --noEmit / npx vitest run / npx vite build, 라이트·다크 두 테마 렌더 확인.
현재 상태: 미커밋 2개 파일 수정, tsc·vitest·build 통과, 배포 안 됨.8. 열려 있는 질문 (사용자 결정 필요)
푸시 대상 리포지토리: 로컬은→ 해소(2026-09-26): 같은 리포지토리입니다.hohyun94/PortalConsole, Coolify는hohyun94/DirectoryService— 어디에 푸시하면directory.rlab.monster에 반영되나요? (리포 이름 변경/미러 여부)gh api repos/hohyun94/DirectoryService가full_name: hohyun94/PortalConsole을 돌려주고 HEAD SHA도ac2562c…로 동일합니다(GitHub 리포 이름 변경 리다이렉트). Coolify는 옛 이름으로 계속 받아도 되고, 로컬origin(hohyun94/PortalConsole)으로 푸시하면 그대로 배포됩니다.- antd 방향의 범위: 이 화면만 portal-foundation 컴포넌트로 바꾸면 다른 데스크(FileStore·Ontology 등)와 스타일이 혼재합니다. 화면 단위로 계속 전환할지, 이번엔 문구·테마 정리로 마무리할지?
- 영어 문안 검수: 이번에 영어 문구를 임의로 작성했습니다(예: 화면 제목
Model access is managed here.). 검수 필요 여부. - 커밋/배포 시점: 이번 변경을 바로 커밋할지, 리뷰 후 커밋·배포할지.
참고: 이번 세션에서 손대지 않은 것
- 게이트웨이 리포지토리(
/home/hohyun/projects/ModelGateway)와 콘솔 서버 코드(프록시·tRPC) → 변경 없음 - 다른 데스크(FileStore·Ontology·DocumentLibrary 등)와 전역 CSS(
reference-portal-frame.css,index.css) → 변경 없음
변경된 파일 목록(그대로 git status와 일치)
M client/src/components/ModelGatewayDesk.tsx
M client/src/styles/modelGatewayDesk.css
?? docs/MODEL_GATEWAY_DESK_HANDOFF_2026-09-26.md ← 이 문서(신규)9. 2026-09-26 2차 세션 — 자체 div+CSS 박스 → portal-foundation(antd 6) 컴포넌트 전환 (완료·미커밋)
§3-1의 방향 전환 제안을 실행했습니다. 화면만 바꿨고 서버 코드(server/modelGateway.ts, server/routers.ts)· tRPC 프로시저·전역 프레임 CSS(reference-portal-frame.css, navy-base.css)·다른 데스크는 손대지 않았습니다.
9-1. 치환표
| 이전(자체 div+CSS) | 지금(portal-foundation) |
|---|---|
.ops-cards 상태 카드 4개 / 사용량 합계 5개 | OperationalMetricGrid(+tone) |
article.model-gateway-provider | PortalCard(variant bordered) |
| 프로바이더 id·kind·baseUrl·models 한 줄 | PortalDescriptions |
.model-gateway-key-row(힌트·상태·시각·링크형 액션) | PortalDataTable + DataStatusBadge + PortalButton |
.model-gateway-badges span(is-on/is-off/is-default) | DataStatusBadge(info/success/neutral/warning/danger) |
.model-gateway-subheading(purpose·단가 소제목) | PortalCard title/description |
.model-gateway-usage-block + 자체 행 | PortalCard + PortalDataTable(모델별·일별) |
window.confirm | PortalConfirm(danger) |
빈 목록·단가 미설정 안내 <p class="settings-security-note"> | PortalEmptyState |
| 로딩 안내 줄 / 오류 안내 줄 | PortalLoadingState / DataStatePanel tone="danger" + 재시도 버튼 |
미설정 안내 <ul class="model-gateway-checklist"> | PortalDescriptions(env 2행) |
.model-gateway-action(링크형 버튼) | PortalButton tone="ghost"/"danger" size="sm" |
유지(모든 데스크가 공유하는 프레임 표면): .admin-desk·.desk-intro·.admin-secondary·.secondary-heading· .settings-security-note(내부망 http 경고), 그리고 순수 레이아웃 규칙(자동 배치 폼 그리드, 편집 행 배치, 추세 막대).
9-2. CSS 축소 — modelGatewayDesk.css 117 → 46줄
라이트/다크 색 블록을 전부 삭제했습니다. 색·테두리·다크 모드는 이제 --pf-* 토큰과 antd 테마 (PortalConfigProvider가 mode에 따라 default/dark 알고리즘을 고릅니다)가 처리합니다. 남긴 것은 레이아웃과 일별 추세 막대 색 2규칙뿐입니다. ⇒ 이 화면의 다크 모드 정합성은 data-portal-mode와 antd 테마가 함께 책임집니다.
9-3. 검증 (실제로 돌린 값)
npx tsc --noEmit → exit 0
npx vitest run → Test Files 74 passed | 2 skipped (76) · Tests 403 passed | 4 skipped (407) · 21.3s (1차 세션과 동일)
npx vite build → ✓ built in 7.94s
dist/public/assets/index-CiNt6vw6.css 312.87 kB (gzip 45.89) ← 321.11 → 312.87 kB
dist/public/assets/index-zNY-bB67.js 1,670.93 kB (gzip 498.51) ← 1,484.42 → 1,670.93 kB
npx prettier --check <두 파일> → warn (HEAD 원본과 같은 기존 상태 — 이번 변경이 만든 문제 아님)- ⚠️ 번들 +186 kB: antd
Table·Descriptions·Card·Tag·Alert·Popconfirm이 새로 번들에 들어옵니다(CSS는 8 kB 감소). 단일 번들이라 500 kB chunk 경고는 기존과 동일하게 남습니다. - 헤드리스 렌더 확인(브라우저 없는 환경): 임시 SSR 스모크 테스트로 확인하고 임시 파일 2개는 삭제했습니다 (
client/src/components/ModelGatewayDesk.render.test.ts,vitest.render.config.ts). tRPC·LocaleContext만 스텁한 뒤 라이트/다크 × 한/영에서 두 분기(미설정 안내 / 운영 화면)를renderToStaticMarkup으로 그려 —pf-panel(5)·pf-table(3)·pf-descriptions·pf-data-status-badge·pf-operational-metric(9)가 실제로 나오는 것, 영어 렌더에 남은 한글이 테스트 픽스처(OpenAI 호환)뿐인 것(화면 문구 누수 없음), 이전 클래스 흔적이 없는 것을 확인했습니다. antdDescriptions의span/column경고는 span 제거로 해소했습니다. 남은 경고[antd: Button] 'iconPosition' is deprecated는PortalButton이 항상iconPosition을 넘겨서 생기는 라이브러리 문제입니다(이 화면만의 문제 아님). - 실제 브라우저 라이트/다크 스크린샷 확인은 여전히 미실시(§3-2의 남은 항목 그대로).
9-4. 다음 사람이 알아야 할 것
- 기본
vitest.config.ts에는 React 플러그인이 없어 JSX가 classic 런타임으로 컴파일됩니다 →.tsx컴포넌트를 직접 렌더하는 테스트는 지금 구조에서 불가(React is not defined). 컴포넌트 테스트를 상시화하려면vitest.config.ts에plugins: [react()]를 추가해야 합니다(이번엔 공유 설정을 건드리지 않으려고 임시 설정을 쓰고 지웠습니다). PortalDataTable의 기본 빈 문구는 한국어(데이터가 없습니다.)이고PortalErrorState의 재시도 라벨도 한국어입니다 → 이 화면은 항상emptyState를 넘기고, 오류는DataStatePanel+text()재시도 버튼을 써서 영어 누수를 피했습니다. 다른 데스크를 전환할 때도 같은 함정을 조심하세요.PortalButton의danger는 채워진 빨간 버튼입니다(키·프로바이더 삭제에 사용). 이전 링크형 액션보다 눈에 띄므로, 톤이 과하면tone="ghost"+PortalConfirm으로 낮추면 됩니다.- 파일 크기:
ModelGatewayDesk.tsx1006 → 1110줄,modelGatewayDesk.css117 → 46줄. - 같은 방향으로 남은 화면:
FileStoreDesk,OntologyDesk,DocumentLibraryDesk(모두 아직 래퍼 필드 4개 + 자체 CSS). 전환하면 데스크별 CSS의 라이트/다크 색 블록을 같은 방식으로 지울 수 있습니다.
9-5. 배포 결과 (2026-09-26)
- 푸시 → Coolify 배포가 실제로 돌았습니다(자동). 커밋
cd0a69b푸시 직후application_deployment_queues에 배포 행이 생겨 11:48:33 시작 → 11:52:42 finished, 앱 컨테이너 이미지 태그가 커밋 SHA(h3igjh2lszlqoegqinwitlyx:cd0a69be85b6…)로 교체되고Up (healthy)가 됐습니다. (배포 행 id 417, 앱 uuidh3igjh2lszlqoegqinwitlyx) - 라이브 확인:
https://directory.rlab.monster/가assets/index-DkKNNVaX.js(1,683,921 B) ·assets/index-DPjCsOk8.css(313,022 B)를 서빙합니다. 배포 번들에 새 컴포넌트 마커(pf-operational-metric·pf-data-status-badge·pf-table-wrap·pf-descriptions)가 있고, 폐기한 클래스(model-gateway-key-row·-checklist·-usage-block·-purpose-editor·-subheading·-models)는 js·css 모두 0건입니다. (로컬 빌드 해시index-zNY-bB67.js와는 해시가 다릅니다 — 빌드 환경 차이이고 크기는 1.67 MB로 같은 수준입니다.) - 트리거 경로 주의: 리포지토리 웹훅은 1건뿐이고(
id 677001117) 그 URL이…/api/v1/deploy?uuid=…&force=false인데 401을 돌려줍니다(토큰 없음). 그런데도 배포가 돌았으므로 실제 트리거는 Coolify의 GitHub App 이벤트(github_apps.app_id = 4892926)입니다. → 남은 정리: 401 웹훅은 지우거나 API 토큰을 붙여 고치고, Coolify API 토큰(personal_access_tokens= 0건)은 필요할 때만 admin으로 재발급합니다. - 이 배포 결과 문서는 로컬 커밋만 했습니다(푸시하면 문서 한 줄 때문에 4분짜리 재빌드·재배포가 돌아서). 다음 푸시에 함께 올라갑니다.
10. 2026-09-26 3차 — 히어로를 portal-foundation 표준(PortalHero)으로 교체
10-1. 바꾼 것
ModelGatewayDesk가 손으로 만들던.desk-intro(h1 +<em>+ 긴<p>)를 **표준PortalHero**로 교체했습니다.- 운영 화면:
title= "모델 접근을 콘솔에서 직접 관리합니다." + 설명, 그리고 히어로 액션 슬롯(pf-portal-hero__actions)에상태 새로고침버튼. - 미설정 안내:
title= "모델 접근을 한 지점에서 관리합니다." + 설명(액션 없음).
- 운영 화면:
- 데스크 CSS는 변경 없습니다(히어로 관련 규칙이 없었음 → 여전히 46줄). 프레임 CSS도 손대지 않았습니다.
10-2. 표준 경로와 설치본의 간극(다음 사람 필독)
portal-foundation의 표준 히어로는 셸이 nav 항목의 hero 설정으로 렌더하는 구조입니다.
PortalShell:activeHero(navigationItems, activeNavigationId)→PortalMain자식으로<PortalHero {...hero} />(=pf-shell-main__inner안,pf-shell-main__header스트립 바로 아래).pf-shell-main__header는 파란 그라데이션 스트립이고, 여기 들어가는 것은mainHeader/headerTitle용도입니다(히어로 자리 아님).- 타입:
PortalNavigationLeaf.hero: PortalHeroConfig = { title, description, summary?, action?: { href, label } }.PortalHero는theme.heroVisible === false면null을 돌려줍니다. PortalAdminMenuItem.description은 "선택된 관리자 메뉴의 Hero에 표시할 설명"으로 문서화되어 있습니다.
그런데 설치본 0.6.156의 PortalAdminShell.toNavigationItem이 hero/headerTitle/description을 변환에서 누락합니다 ({ id, label, icon, href, active, onSelect }만 넘김). 그래서 관리자 콘솔에서는 nav 항목 hero 경로를 쓸 수 없습니다. 이번에는 데스크가 PortalHero를 같은 위치(본문 최상단) 에 직접 렌더해 표준 컴포넌트·표준 스타일·표준 heroVisible 동작을 그대로 쓰는 방식으로 처리했습니다. 라이브러리를 올려 toNavigationItem이 hero를 매핑하면, adminMenuItems[].description(또는 hero)만 채우고 데스크에서는 히어로를 빼면 됩니다.
- 부작용(표준 동작):
heroVisible === false면 히어로가 통째로 사라지므로 히어로 액션인상태 새로고침도 함께 숨습니다. 현재 콘솔에는 ThemePicker가 없고App.tsx기본값이heroVisible: true라 실제로는 항상 보입니다. 나중에 테마 설정 UI를 붙인다면 새로고침을 본문(지표 줄)으로 내리는 편이 안전합니다. - 다른 데스크(
FileStoreDesk·OntologyDesk·DocumentLibraryDesk)는 아직.desk-intro를 씁니다. 같은 방향으로 옮기면 화면 간 히어로가 통일됩니다.
10-3. 검증
npx tsc --noEmit → exit 0
npx vitest run → 74 passed | 2 skipped (76) · 403 passed | 4 skipped (407) (기존과 동일)
npx vite build → exit 0 · dist/public/assets/index-BVwc3DPP.js 1,670.06 kB (gzip 498.39) · index-CiNt6vw6.css 312.87 kB- 헤드리스 SSR 스모크(임시 파일 2개, 확인 후 삭제): 라이트/다크 × 한/영에서
pf-portal-hero+pf-portal-hero__actions가 렌더되고.desk-intro가 없는 것,pf-operational-metric/pf-panel/pf-table/pf-data-status-badge가 함께 나오는 것, 영어 렌더에 한글이 0건인 것,heroVisible=false에서는 히어로(과 그 액션)만 사라지고 본문은 그대로인 것을 확인했습니다. - 배포본 확인은
assets/index-*.js에 새 히어로 문구("모델 접근을 콘솔에서 직접 관리합니다." / "Model access is managed here.")가 있는지로 합니다.
11. 2026-09-26 4차 — 경고 안내 문구의 표준과 스케일 (결론: AdminFeedback)
11-1. 플랫폼의 "알림/경고" 표준 (사용자 질문에 대한 답)
antd의 표준은 **Alert**이고, portal-foundation이 그걸 아래 래퍼로 노출합니다(근거: portal-foundation/docs/PHASE_3_REMAINING_COMPONENT_PLAN.md:95에 FeedbackMessage | Alert, Button으로 명시).
| 쓰임 | 컴포넌트 | 실제 DOM | 이 콘솔에서의 판단 |
|---|---|---|---|
| 안내·경고 문구(+닫기) | FeedbackMessage (tone, title, children, dismiss?) | .pf-feedback-message--<tone> > antd Alert type=… showIcon | ❌ 포털 스케일(래퍼 12px + Alert 14px·패딩 20×24·아이콘 24px, 박스 두 겹) |
| 데이터 상태(오류·빈 상태·로딩) | DataStatePanel (tone, title, description, action) | .pf-data-state-panel--<tone> > antd Alert/Spin | ✅ 오류·로딩에 사용 중(스케일도 무난) |
| 액션 결과(성공/실패/진행) | ActionFeedback (state) | 결과 문구 한 줄 | – |
| 아이콘 없는 톤 문구(단일 박스) | AdminFeedback (tone) | .pf-admin-feedback--<tone> (<p>, 12px, padding:10px 12px, antd 아님) | ✅ 이 콘솔 스케일과 일치 — 경고 안내에 채택 |
11-2. 왜 갈아탔나 — 스케일 불일치(실측)
이 콘솔은 reference-portal-frame.css/navy-base.css에서 foundation 셸 스타일을 !important(총 335개)로 눌러 더 작고 촘촘한 자체 섀시를 씁니다. 그래서 포털용 foundation 기본 타이포가 여기서만 커 보입니다.
| 요소 | foundation 기본 | 콘솔 섀시 |
|---|---|---|
| 히어로 제목 | .pf-portal-hero__copy h2 = clamp(24px, 3vw, 38px) + letter-spacing -.065em + text-shadow | .desk-intro h1 = 25px(모바일 20px), 본문 13px |
| 경고 문구 | 래퍼 12px + antd Alert 14px·패딩 20×24px·아이콘 24px (박스 두 겹) | .settings-security-note = 12~13px 한 줄 문단 |
| 본문 기준 | antd 토큰 기본 14px | --common-content-size = 13px |
11-3. 최종 결정(사용자 선택: B)
- 경고 안내 →
AdminFeedback tone="warning"로 교체. 렌더 결과는 단일<p>:<p class="pf-admin-feedback pf-admin-feedback--warning" role="status"><strong>내부망 http 허용이 켜져 있습니다</strong> PORTAL_ALLOW_INSECURE_PLATFORM_URL — …</p>→ antd Alert 없음(두 겹 박스 없음), 12px, 아이콘 없음.settings-security-note·pf-feedback-message·ant-alert모두 이 화면에서 0건. - 히어로는 foundation 기본(38px) 유지. 콘솔 25px로 낮추는 오버라이드(A안)는 하지 않기로 함. 나중에 "히어로가 크다"는 판단이 바뀌면
.directory-admin-content .pf-portal-hero__copy h2 { font-size:25px; letter-spacing:-.03em; text-shadow:none }처럼 스코프 오버라이드(선택자 특이도로 이김,!important불필요)로 낮출 수 있습니다. d3489c6(FeedbackMessage 버전)은 이미 배포됐고, 이 변경은 아직 미커밋·미푸시입니다(사용자 지시). 다음 푸시에 함께 올라갑니다.
11-4. 같은 배치에서 반영한 시각 조정(사용자 피드백)
modelGatewayDesk.css에만 추가했고, 전부 이 화면(.model-gateway-desk/.model-gateway-cards) 스코프입니다.
| 피드백 | 조치(규칙) | 근거 |
|---|---|---|
| 지표 카드 위 초록 강조 띠가 불필요 | .model-gateway-cards .pf-operational-metric{border-top-width:1px;border-top-color:var(--pf-border-subtle)} | foundation 기본이 .pf-operational-metric{padding:13px;border-top-width:3px} + 톤별 border-top-color(success=#43835d)를 그림 |
| 카드 제목이 작다 | .model-gateway-cards .pf-operational-metric>span{font-size:12px} (힌트 small은 10px, 값 strong은 17px 유지) | 기본 라벨 10px |
| 섹션마다 테두리·면이 불필요 | .model-gateway-desk .admin-secondary{border:0} + 라이트/다크 각각 background:transparent!important(:root[data-portal-mode="light"] … / :root:not([data-portal-mode="light"]) …) | 테두리 베이스는 index.css:147의 .admin-secondary{…border:1px solid #d6d0c5;background:#fffdf8}. 채움은 라이트에서 프레임이 background … !important(특이도 0,4,0)로, 다크에서 navy-base가 --directory-navy-800(#0E365C)로 준다 → 다크(캔버스 슬레이트 ≈#2c3646)에서 큰 파란 박스로 보임. :root[...]로 특이도 (0,5,0) + !important로 덮음 |
| 섹션 제목의 아이콘 불필요 + 수직정렬 어긋남 | 5개 섹션 제목에서 아이콘 제거(<ServerCog/>·<Plus/>·<Save/>·<RefreshCw/>·미설정 안내의 <CircleAlert/>), .model-gateway-desk .secondary-heading h2{margin:0 0 6px;line-height:1.35} | 프레임 기본 .secondary-heading h2{margin:9px 0 5px}(비대칭) + 인라인 svg가 텍스트 베이스라인에 붙어 어긋나 보임. ServerCog는 이 화면에서 유일한 사용처라 import에서도 제거 |
- 두 지표 그리드(상태 4개·사용량 5개)가 같은
.model-gateway-cards를 써서 함께 적용됩니다. 다른 데스크의.admin-secondary는 그대로입니다. - 섹션의 배경 채움과 제목 스트립(
.secondary-heading)의 하단 구분선은 남겨 뒀습니다(요청은 테두리만). 필요하면 각각 한 줄로 더 걷어낼 수 있습니다. - 섹션 제목 띠(
.secondary-heading)도 열린 제목으로 바꿨습니다: 채움 제거(라이트·다크 각각background:transparent!important) +padding:0 0 12px(제목을 내용과 같은 x축으로 — 프레임 기본 20px 패딩 때문에 20px 안쪽으로 밀려 있었다) +margin-bottom:16px, 하단 1px 구분선은 유지(프레임이 테마별 색을 준다). 섹션 사이 간격은 28px(margin-top, 프레임 기본 14px). - 검증:
vite build0, 빌드 산출물(index-BuxqOdi7.css)에admin-secondary>.secondary-heading패딩·배경 규칙과margin-top:28px!important존재 확인.
12. 2026-09-26 5차 — 현황판 아래 세부 + 프로바이더 카드 클릭 편집·추가 레이어 팝업 (구조 개편)
사용자 요청: ① 현황판(4개 지표 박스) 아래에 현황별 세부내용이 있어야 하는데 게이트웨이 ON에 대한 정보가 없음 ② 프로바이더는 복수일 수 있으니 등록 현황을 카드(또는 목록)로 ③ 카드/행을 누르면 편집이 레이어 팝업 ④ 프로바이더 추가도 버튼 → 등록 레이어 팝업.
12-1. 바꾼 구조
| 위치 | 이전 | 지금 |
|---|---|---|
| 현황판 아래 | 곧바로 프로바이더 섹션 | 게이트웨이 상세 카드(PortalCard + PortalDescriptions 8필드): 상태(ON 배지)·주소·업스트림·배포 프로필·키 암호화·등록된 프로바이더 집계·등록된 API 키 수·등록된 모델 수 |
| 프로바이더 | 카드 목록(편집은 본문 인라인 폼) | 카드 목록 유지 + 카드를 누르면 편집 팝업, 섹션 헤딩과 빈 상태에 프로바이더 추가 버튼(둘 다 같은 팝업) |
| 추가·편집 폼 | 본문 인라인 admin-secondary 섹션 | PortalDialog(size="lg") 레이어 팝업 — 같은 필드 그리드(.model-gateway-form) + 하단 취소/저장 |
| 키 관리 | 카드 안 | 그대로 카드 안(운영 동작이라 팝업 대신 즉시 처리). 카드 클릭과 충돌하지 않게 model-gateway-provider-keys로 감싸 stopPropagation |
12-2. 구현 메모(라이브러리 제약)
PortalDialog(0.6.156):getContainer={false}라 포털이 아니라 인라인 렌더 → 기존.directory-admin-content스코프 CSS가 팝업 안에서도 그대로 적용됩니다(추가 CSS 불필요).open이 false면null을 반환하고, 모달은useEffect로 마운트된 뒤에 열립니다 → SSR 스모크에서는 오버레이 div만 렌더되고 팝업 내용은 브라우저에서만 보입니다(그래서 아래 검증도 그 범위).PortalCard의interactive/onClickprop은 0.6.156에서 동작하지 않습니다:PortalPanel이 알려진 prop만 쓰고 나머지를<section>에 전달하지 않기 때문입니다(onClick이 버려짐). 그래서 카드 클릭은 래퍼div(role="button" + tabIndex + Enter/Space 처리) 로 구현했습니다. 라이브러리가 prop을 전달하도록 고쳐지면 래퍼를 없앨 수 있습니다.- 팝업을 닫으면 편집 상태를 비웁니다(
closeProviderDialog): 취소·ESC·오버레이 클릭·X 모두onOpenChange(false)→ 같은 정리 경로를 탑니다. - 저장 성공 시
saveProvider.onSuccess에서 팝업을 닫습니다.
12-3. 알려진 한계 / 남은 결정
- 팝업의 시각적 확인은 브라우저가 필요합니다(SSR은 오버레이까지만). 현재 콘솔은
PortalDialog의 첫 사용처입니다. - 카드 클릭 영역은 헤더·식별정보 표이며, 키 관리 영역은 제외했습니다. 카드 전체(여백 포함)를 클릭 영역으로 넓히려면 래퍼 클릭 + 내부 컨트롤
stopPropagation을 더 넓히면 됩니다. - 래퍼 도입으로 그 카드 블록의 JSX 들여쓰기가 한 단계 얕습니다(prettier 미적용 저장소라 그대로 둠).
- 섹션 구조(실행 설정·사용량·비용)는 그대로입니다. 세부내용을 더 늘리려면(예: 키 암호화 상세, 사용량 요약을 현황판 아래로) 알려 주세요.
13. 2026-09-26 6차 — 조치 불가 상태 항목 정리: 키 암호화는 미설정일 때만 경고
13-1. 사실 확인(사용자 질문: "키 암호화는 관리자가 뭘 할 게 있나?")
masterKeyConfigured는 읽기 전용입니다. 값은 게이트웨이 배포 envMODEL_GATEWAY_CONFIG_MASTER_KEY에서 오고 (ModelGateway/src/admin/routes.ts:63→Boolean(config.configMasterKey)), 콘솔 tRPC에는 이 값을 바꾸는 프로시저가 없습니다 (gateway.status/state/saveProvider/deleteProvider/addKey/setKeyEnabled/deleteKey/verifyProvider/saveSettings/usage뿐).- 그럼에도 의미가 있었던 이유: 미설정이면 게이트웨이가 키를 암·복호화할 수 없어 API 키 저장 자체가 실패합니다 (
ModelGateway/src/configStore.ts:51→"provider 키를 저장하려면 MODEL_GATEWAY_CONFIG_MASTER_KEY가 필요합니다."). 즉 "지금 키를 추가해도 되는 상태인가"를 알려주는 준비 상태 신호였습니다.
13-2. 결정(사용자 선택: A) 과 구현
- 정상(
masterKeyConfigured: true)이면 현황판 타일·게이트웨이 상세 필드 모두 숨김 → 현황판은 3개(게이트웨이·프로바이더·배포 프로필). - 미설정일 때만 경고 배너(
AdminFeedback tone="warning") 로 노출하며, 게이트웨이 env 이름과 "콘솔에서 바꿀 수 없음"을 함께 적었습니다. - 경고 두 종류(내부망 http · 마스터 키 미설정)를
.model-gateway-notices(grid,gap:8px,margin-bottom:14px)로 묶어 겹칠 때 간격을 유지합니다. - 남겨 둔 것: 배포 프로필·업스트림 구성도 같은 성격(게이트웨이 env, 콘솔에서 조치 불가)이지만 운영 맥락(어느 배포로 도는지)에 유용해 현황판에 남겼습니다. 같은 기준으로 정리하려면 알려 주세요.
13-3. 검증
- 헤드리스 스모크 2/2 통과: ① 정상 상태 → 마크업에
키 암호화·AES-256-GCM·env 이름 0건, 현황판 타일 3개 ② 미설정 상태 → 경고 배너 + env 이름 노출, 타일은 여전히 3개. tsc --noEmit·vitest run·vite build결과는 이 커밋의 게이트 로그와 같습니다(아래 §14에 통합 기록).
14. 2026-09-26 7차 — 배포 프로필을 콘솔에서 설정(게이트웨이 + 콘솔 서버/화면)
사용자 지적: "배포 프로필도 클라우드인지 아닌지 여기서 설정하면 안 돼? 환경변수값 읽어오는 것 같은데" → 정확했습니다. 다만 저장 경로가 없던 값이라, 게이트웨이·콘솔 서버·화면을 함께 고쳐 콘솔에서 설정할 수 있게 했습니다.
14-1. 게이트웨이 (/home/hohyun/projects/ModelGateway, 커밋·푸시 대기)
configStore.ts:RuntimeSettings.deploymentProfile: DeploymentProfile | null추가 +SETTING_KEYS포함 →PUT /admin/api/settings로 저장되고 DB(model_gateway_settingskey/value)에 영속한다(스키마 변경 불필요).routing.ts:EffectiveSettings.deploymentProfile = stored ?? config(env 시드).resolveTarget(..., deploymentProfile)가 실효값으로allowFallback을 정한다 (CLOUD = 레거시 폴백 허용 / ON_PREM = 금지).admin/routes.ts:/state가 실효 프로필을 내려주고,PUT /settings는CLOUD|ON_PREM만 받는다(그 외 400,null은 저장값 제거 → env 시드로 복귀).server.ts: 감사 기록(401·429·성공)과/v1/health가 실효 프로필을 쓴다.- 테스트 추가(
tests/resilience.test.ts): env 시드가 CLOUD여도 저장값 ON_PREM이면 폴백이 막히는지, 잘못된 값이 400인지,/state가 ON_PREM을 돌려주는지. - README: 관리자 API 목록에 배포 프로필 추가 + "env는 시드, 저장값 우선, ON_PREM은 외부 폴백 금지" 문단.
14-2. 콘솔
- 서버:
ModelGatewaySettings.deploymentProfile추가, tRPCgateway.saveSettings입력에z.enum(["CLOUD","ON_PREM"]).nullable().optional(). - 화면(실행 설정): 배포 프로필
PortalSelect—CLOUD — 레거시 폴백 허용/ON_PREM — 외부 폴백 금지+ 라우팅 영향 설명. 편집값이 적용값과 다르면 저장 버튼이PortalConfirm으로 확인을 받는다(폴백 동작이 바뀌는 값이라서). 폼 시작값은저장값 > 실효값(env 시드)순(다른 설정과 같은 규칙). - 현황판·게이트웨이 상세 카드의 '배포 프로필'은 이제 저장값을 반영한다(게이트웨이
/state가 실효값을 내려주므로 클라이언트 추가 변경 없음).
14-3. 배포 순서 / 주의
- 게이트웨이 먼저 배포(새 필드 수용) → 그 다음 콘솔. 반대여도 콘솔은 필드를 보내고 구게이트웨이가 화이트리스트로 무시하지만, 순서를 지키는 편이 안전하다.
- 게이트웨이 컨테이너 확인 결과
DATABASE_URL=set·DEPLOYMENT_PROFILE=CLOUD→ 저장값이 재시작 후에도 유지된다(메모리 저장소 구성이면 재시작 시 env 시드로 복귀). ON_PREM으로 바꾸면 업스트림 장애 시 외부 폴백 URL로 넘기지 않는다(502가 그대로 노출 — 의도된 동작).- 두 리포 모두 아직 미커밋·미푸시입니다(사용자 지시 대기).
14-5. 실행 설정 섹션 요약 타일(같은 세션 추가분)
사용자 지적: "실행설정이나 사용량비용 섹션도 섹션이 있으면 위에 요약타일이 있어야하지 않나?" → 섹션 패턴(요약 타일 → 세부)을 맞추되, 저장값이 아니라 실효값을 보여주기 위해(env 시드가 적용 중이면 저장값이 없어 "0/미설정"으로 잘못 보이는 문제) 게이트웨이 /state에 effective 블록을 추가하고 그 값으로 타일을 채웠다.
- 게이트웨이:
/state.effective=effectiveSettings(config, stored)의 요약(기본·허용 모델, purpose 매핑, 분당 한도, 쿼터 예약, 타임아웃, 배포 프로필). 기존pricing(실효 단가표)과 같은 방식이다. 테스트(tests/resilience.test.ts)에 "저장값이 시드를 덮은 실효값이 내려온다"는 단언을 추가했다. - 콘솔 화면(실행 설정): 섹션 헤딩 바로 아래에
OperationalMetricGrid7개 타일 — 기본 모델 / 허용 모델(전체 or n개) / purpose 매핑 n개 / 단가 override n개(힌트: 실효 단가표 n개 모델) / 분당 토큰 한도(0=제한 없음) / 요청 타임아웃(초) / 배포 프로필(CLOUD·ON_PREM). 타일 값은state.effective(없으면 저장값) → 편집 초안이 아니라 적용 중인 값이다. - 사용량·비용 섹션은 이미 자체 5개 타일(요청·실패·토큰·평균 지연·금액)이 있어 그대로 두었다(필터 → 타일 → 분해 순서 유지). 타일을 필터 위로 올리는 정리는 아직 하지 않았다.
- 검증: 콘솔 헤드리스 스모크 1/1(
effective픽스처 값으로 7개 타일 값·힌트 렌더 확인) + 아래 게이트.
게이트웨이: npx tsc --noEmit -p tsconfig.json → 0 · npx vitest run → 8파일/75건 통과 · npx tsc -p tsconfig.json(빌드) → 0
콘솔: npx tsc --noEmit → 0 · npx vitest run → 76파일/403건 통과 · npx vite build → 0
(index-DApVOAbW.js 1,675.85 kB · index-COHn970v.css 313.69 kB)
헤드리스 스모크 2/2: 실행 설정에 `gateway-deployment-profile` select + 설명 렌더, 저장 액션 유지, 저장값 ON_PREM이 마크업에 반영15. 2026-09-26 8차 — IA 재배치: "라우팅·요금"은 프로바이더 하위, 나머지는 "게이트웨이 운영 설정"
사용자 지적 — "실행설정은 프로바이더의 하위항목같은거 같은데". 맞습니다. 설정 항목을 성격으로 나눠 섹션을 갈랐습니다(사용자 선택: A안).
| 항목 | 성격 | 새 위치 |
|---|---|---|
| 기본 모델 · 허용 모델 · purpose 매핑 · 단가 override | 등록된 모델/프로바이더로 라우팅·요금 | 프로바이더와 API 키 섹션의 하위 블록 "라우팅·요금"(h3) |
| 분당 토큰 한도 · 쿼터 예약 · 요청 타임아웃 · 배포 프로필 | 게이트웨이 전역 파라미터 | 새 섹션 "게이트웨이 운영 설정" |
- 저장 버튼 2개(같은 폼 상태 공유 — 게이트웨이 API는
PUT /admin/api/settings부분 갱신):submitRoutingSettings(기본/허용 모델·purpose·단가만 전송) ·submitOperationsSettings(한도·쿼터·타임아웃·프로필만 전송, 프로필이 바뀌면PortalConfirm확인). 기존submitSettings(8개 전부 전송)는 제거했다. - 요약 타일도 따라 이동: 라우팅·요금 4개(기본 모델·허용 모델·purpose 매핑·단가 override) / 운영 설정 3개(분당 한도·타임아웃·배포 프로필).
- 하위 위계 표현(이 화면 스코프 CSS): 하위 블록 제목은
h316px — 섹션 제목(19px + 하단 구분선)과 구분. 블록 위 간격 20px(섹션 간 28px). 선택자.model-gateway-subsection, 라우팅 섹션은.model-gateway-routing-section(=margin-top:20px!important, 프레임 규칙과 동률을 피하려고:root접두). - 히어로 문구도 "…프로바이더·API 키·라우팅/요금·운영 설정은…"으로 갱신(옛 "실행 설정" 표현 제거).
- 구현 방식: 섹션을 통째로 옮기지 않고 섹션 경계만 재조정 — 라우팅 섹션의
secondary-heading을 하위 소제목으로 바꾸고, 전역 필드(프로필·한도·쿼터·타임아웃)만 새 섹션으로 옮겼다(큰 purpose/단가 카드는 그대로). DOM 순서는 "프로바이더 카드(→ 서브 블록) → 운영 설정 → 사용량·비용". - 검증: 헤드리스 스모크 1/1 — 섹션 순서(프로바이더와 API 키 < 라우팅·요금 < 게이트웨이 운영 설정),
<h3>라우팅·요금</h3>, 블록별 필드(gateway-default-model·gateway-allowed-models·gateway-purpose-known·gateway-pricing-known/gateway-deployment-profile·gateway-per-minute·gateway-quota-reserve·gateway-timeout), 저장 라벨("라우팅·요금 저장"·"운영 설정 저장"),<h2>실행 설정</h2>부재. (임시vitest.render.config.ts+SectionSplitSmoke.render.test.ts는 확인 후 삭제 — 기본vitest.config.ts에는 React 플러그인이 없다.)
콘솔: npx tsc --noEmit → 0 · npx vitest run → 76파일/403건 통과 · npx vite build → 0
(index-B9uS07OH.js 1,679.02 kB · index-CJt5JO_A.css 314.82 kB)16. 2026-09-26 9차 — 버튼 아이콘 통일 + 크기 고정
사용자 지적 — "버튼이 아이콘이 있는게 있고 없는게 있고 일관성이 없어. 아이콘이 있는건 좋은데 아이콘의 크기는 또 너무커." 원인은 두 가지였다.
- 전달 경로가 섞여 있었다 — 히어로 새로고침만
PortalButton의iconprop을 쓰고, 나머지 버튼은 아이콘을 자식(<Plus /> {텍스트})으로 넣었다. 자식으로 넣으면 antd가.ant-btn-icon으로 감싸지 않아 간격·정렬이 제각각이고, 아이콘이 라벨 흐름에 그대로 섞인다. - lucide 기본 크기(24px) —
PortalButton(antd 경로)에는 아이콘 크기 규칙이 없다. 프레임워크의 아이콘 규칙은 레거시 버튼(.pf-button:not(.ant-btn) .pf-button__icon, foundation dist styles.css:733–734)에만 있어서 antd 경로에서는 svg가 24px로 그려진다. 데스크 CSS에.model-gateway-key-hint svg{14px}패치가 있던 것도 같은 이유다.
수정:
- 이 화면 모든 버튼을
iconprop 경로로 통일하고, 아이콘이 없던 버튼(연결 테스트·편집·조회·다시 시도·취소·키 사용/중지·발견 모델 반영)에 아이콘을 채웠다.
| 버튼 | 아이콘 | 버튼 | 아이콘 |
|---|---|---|---|
| 상태 새로고침 · 다시 시도(2곳) | RefreshCw | 목적 추가 · 단가 행 추가(2곳) · 프로바이더 추가(2곳) | Plus |
| 키 저장 | KeyRound | 삭제 — 프로바이더·키·목적·단가 | Trash2 |
| 연결 테스트 | PlugZap | 저장 — 라우팅·요금 · 운영 설정 · 프로바이더 | Save |
| 편집 | Pencil | 발견 모델 반영 | Download |
| 키 사용/중지 | Power | 조회(사용량) | Search |
| 취소(프로바이더 팝업) | X |
- 크기 고정(화면 스코프 CSS):
.pf-button .ant-btn-icon svg{width:16px;height:16px!important}· 작은 버튼(.ant-btn-sm) 14px. 프레임워크가 svg 크기를 잡아 주지 않으므로(위 2번) 이 화면에서 정한다 — 특이도 (0,5,1)+!important라 프레임·섀시 규칙보다 우선한다. - 사용량 표의 안내 문구도 §15 재배치에 맞춰 "실행 설정" → "라우팅·요금 설정"으로 바꿨다.
- 검증: 헤드리스 스모크 2/2 — (a) 렌더된 버튼 전부가
.ant-btn-icon안에 svg를 갖는다(아이콘 없는 버튼 0 · 자식 아이콘 0), (b) 스타일시트에 16px/14px 규칙이 있다. 임시 파일은 확인 후 삭제. - 남은 것:
PortalConfirm팝오버 내부 확인/취소 버튼은 프레임워크가 그려 아이콘이 없다(컴포넌트 개선 필요). antd 6은iconPosition을 폐기 예정으로 경고한다 (PortalButton이 그대로 넘긴다 — 프레임워크에서iconPlacement로 매핑하면 사라진다). 아이콘 없는 버튼이 남아 있는 다른 데스크(FileStore·Ontology·DocumentLibrary)도 같은 정리가 필요하다.
콘솔: npx tsc --noEmit → 0 · npx vitest run → 76파일/403건 통과 · npx vite build → 0
(index-AlJf7gIz.js 1,680.25 kB · index-V9eY9lZ0.css 315.05 kB)
변경 파일: client/src/components/ModelGatewayDesk.tsx · client/src/styles/modelGatewayDesk.css · docs(이 문서)17. 2026-09-26 10차 — 버튼 표준을 프레임워크로 올림 (portal-foundation 0.6.159)
사용자 지적 — "버튼의 폰트크기나 아이콘의 크기 버튼의 세로높이 등은 표준에 맞춰서 포탈안에서 다 통일이 되었으면 좋겠는데." §16은 이 화면만 CSS로 맞춘 상태라 화면마다 값이 갈릴 여지가 있었다. 표준을 프레임워크 한 곳에 두고 콘솔이 새 빌드를 vendoring하는 방식(A안)으로 확정했다.
표준
| 크기 | 높이 | 폰트 | 아이콘 | 좌우 패딩 |
|---|---|---|---|---|
| md(기본) | 36px | 13px 600 | 16px | 14px |
| sm | 30px | 12px 600 | 14px | 10px |
| lg | 42px | 13px 600 | 18px | 18px |
- 아이콘-라벨 간격 7px · radius 8px(
--pf-radius-sm) · 아이콘은 항상PortalButton의iconprop.
왜 갈렸나 (조사 결과)
buildPortalAntdTheme가controlHeight: 36·borderRadius: 8·색만 정하고 폰트는 antd 기본 14px였다 → 본문 13px와 어긋남.- antd 파생값은 sm 27px·lg 44px이라 프레임워크 레거시 버튼(sm 30·lg 42)과 어긋남.
- 아이콘 규칙은 레거시 버튼 전용(
.pf-button:not(.ant-btn) .pf-button__icon) → antd 경로(lucide)는 24px 그대로. PortalConfigProvider가themeprop을Omit해 포털이fontSize·controlHeightSM을 넘길 수 없다 → 표준은 프레임워크에 있어야 한다.
프레임워크 변경 (0.6.158 → 0.6.159, /home/hohyun/projects/portal-foundation)
packages/ui-react/src/providers/antdTheme.ts:controlHeightSM = controlHeight − 6·controlHeightLG = controlHeight + 6(±6 파생,controlHeight만 바꾸면 비율 유지) +components.Button = { iconGap: 7, contentFontSize: 13, contentFontSizeSM: 12, contentFontSizeLG: 13, paddingInline: 14, paddingInlineSM: 10, paddingInlineLG: 18 }. antd 컴포넌트 토큰으로만 준다 — 같은 값을 CSS로 덮으면 antd가 런타임에 주입하는 스타일과 순서 경쟁이 생긴다.packages/ui-react/styles/styles.css:.pf-button.ant-btn .ant-btn-icon svg→ md 16px · sm 14px · lg 18px (+display:inline-flex). 토큰에 없는 값(자체 svg 크기를 가진 lucide)만 CSS가 담당한다.packages/ui-react/src/providers/antdTheme.test.ts: 표준값 + ±6 파생 단언 2건 추가(총 5건).- 버전: 루트·
packages/ui-react0.6.159 →npm pack→hohyun94-portal-foundation-0.6.159.tgz(142.8 kB). 검사:tsc -p tsconfig.build.json0 ·vitest run0 ·npm run build0(dist/version.js= 0.6.159). - ⚠️ 프레임워크 저장소 변경은 아직 커밋 전이다. 다른 포탈(Process Center 등)이 이 표준을 받으려면 커밋 + GitHub Packages 게시 후 각 포탈이 버전을 올려야 한다(콘솔은 tgz vendoring이라 게시 없이 반영됨).
콘솔 반영
package.json:file:vendor/hohyun94-portal-foundation-0.6.159.tgz(0.6.156 → 0.6.159). 이번 vendoring으로 0.6.157·0.6.158 변경도 함께 들어온다(둘 다 릴리스 커밋).pnpm-lock.yaml: 6곳을 손으로 맞췄다(importer specifier/version · packages 엔트리 key/integrity/version · snapshots key). integrity는 tgz의 sha512-base64를 계산해 넣었고npm pack이 보고한 값과 일치 확인(sha512-/kT3Ed3WsiZxG…qn6GNf/1VdyWQ==).modelGatewayDesk.css: §16에서 넣은 화면별 아이콘 크기 규칙 삭제 — 이제 프레임워크가 정본이고 화면별 예외를 두지 않는다(주석으로 표준 위치만 남김).- 새 상시 테스트
client/src/lib/vendoredButtonStandard.test.ts: vendored dist에 표준(토큰 5종 + 아이콘 규칙 3종)이 들어 있는지 확인한다 — 버전만 올리고 tgz를 안 바꾼 경우를 잡는다. - 로컬 검증:
npx tsc --noEmit0 ·npx vitest run0(76파일/404건) ·npx vite build0(index-U1s_GTot.css316.31 kB ·index-DzXskJAA.js1,681.38 kB).
⚠️ 로컬 설치는 npm, CI는 pnpm (사고 기록)
- 이 작업 중
pnpm install --offline을 돌렸더니 npm이 설치해 둔 node_modules를node_modules/.ignored로 옮기고 실패했다(오프라인 스토어에playwright-core없음). 즉시 되돌려 복구했다(항목 510개 ·.bin41개 · 프레임워크 0.6.156 → 이후 tgz를 풀어 0.6.159로 교체). pnpm install --lockfile-only --offline도 실패한다(ERR_PNPM_NO_OFFLINE_META— 메타 캐시에@builder.io/vite-plugin-jsx-loc없음) → 락파일은 수동 갱신했다.- 저장소는
packageManager: pnpm@10.4.1+pnpm-lock.yaml이고 CI는 pnpm으로 설치한다. 로컬 개발 트리에서는pnpm install(전체 설치)을 돌리지 말 것 — 의존성을 바꿀 때는 tgz 직접 반영 또는--lockfile-only(네트워크 되는 곳)만 쓴다.