diff --git a/agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/code_review_cloud_G07_0.log b/agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/code_review_cloud_G07_0.log new file mode 100644 index 0000000..b2bdee2 --- /dev/null +++ b/agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/code_review_cloud_G07_0.log @@ -0,0 +1,222 @@ + + +# Code Review Reference - JENKINS_IA_OPS + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. +> Finalization is review-agent-only. + +## 개요 + +date=2026-07-03 +task=m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops, plan=0, tag=JENKINS_IA_OPS + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-jenkins-menu-ia-code-sync.md` +- Task ids: + - `execution-ops-menu-sync`: executions/runners/settings 화면이 `Pipeline Overview`, `Console Output`, `Artifacts`, `Build History`, `Nodes/Agents`, `Manage OTO` 중심으로 동기화된다. + - `preview-deploy`: 동기화된 Flutter web build를 원격 preview에 배포한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [JENKINS_IA_OPS-1] Pipeline Graph Run Detail | [x] | +| [JENKINS_IA_OPS-2] Nodes And Agents Runners Surface | [x] | +| [JENKINS_IA_OPS-3] Manage OTO Settings | [x] | +| [JENKINS_IA_OPS-4] Preview Deployment Smoke | [x] | + +## 구현 체크리스트 + +- [x] [JENKINS_IA_OPS-1] executions run detail을 Pipeline Graph View/Stage View 및 run-context left task menu 중심 구조로 개편한다. +- [x] [JENKINS_IA_OPS-2] runners를 Jenkins Nodes/Agents style 운영 목록으로 개편한다. +- [x] [JENKINS_IA_OPS-3] settings fallback을 Manage OTO category 구조로 개편한다. +- [x] [JENKINS_IA_OPS-4] console/client tests를 갱신하고 원격 preview를 재배포한다. +- [x] `cd packages/flutter/oto_console && flutter analyze && flutter test`, `cd apps/client && flutter analyze && flutter test`, `git diff --check`, preview `curl -I`를 실행한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## Agent UI Completion + +- Mode: review-pass-status-update +- Agent UI docs: + - `agent-ui/definition/index.md` + - `agent-ui/definition/views/index.md` + - `agent-ui/definition/components/index.md` +- Actual code evidence: + - [x] `packages/flutter/oto_console/lib/src/oto_executions_surface.dart`: `_buildExecutionDetail`가 `_RunTaskMenu`(Console Output/Pipeline Overview/Artifacts/Build History 좌측 task menu)와 `_PipelineStageGraph`(`Stage Graph`, Checkout/Build/Package/Publish 상태 chip, run state 기반 secondary note)를 primary region으로 렌더한다. `Stage summary unavailable` 단독 placeholder는 제거했고, 기존 Logs Preview pane은 `Console Output` 타이틀로 통합했다. + - [x] `packages/flutter/oto_console/lib/src/oto_runners_surface.dart`: `_NodesTableHeader`(Nodes/Executor/Current job/Current run/Manage 컬럼 헤더)와 `_NodeTableRow`(node id/alias, executor status, current job/run, manage 아이콘, 기존 message/self-update panel 유지)로 Runners를 Nodes/Agents 운영 목록으로 재구성했다. + - [x] `packages/flutter/oto_console/lib/src/oto_console_shell.dart`: settings fallback을 `_ManageOtoSettingsSurface`(`Manage OTO` 타이틀 + `System`/`Security` category card)로 교체했다. System 카드는 기존 Runtime endpoints(HTTP API/Wire URL) 값을, Security 카드는 기존 Browser policy(Browser API policy, CORS 안내) 값을 그대로 유지한다. +- Implementation verification evidence: + - [x] `cd packages/flutter/oto_console && flutter analyze && flutter test`: `No issues found!` / `00:01 +14: All tests passed!` + - [x] `cd apps/client && flutter analyze && flutter test`: `No issues found!` / `00:03 +27: All tests passed!` + - [x] `curl -I http://toki-labs.com:13020/`: `HTTP/1.0 200 OK` (Server: SimpleHTTP/0.6 Python/3.9.6) +- Status updates requested on PASS: + - [x] `agent-ui/definition/views/index.md`: `executions`, `runners`, `settings` Jenkins IA criteria `계획` -> `구현됨` + - [x] `agent-ui/definition/components/index.md`: ops component criteria `계획` -> `구현됨` +- Review finalization: + - [x] Review agent updated the listed agent-ui docs and code evidence. + - [x] Review agent ran `validate-agent-ui scope=jenkins-ia-executions-ops` after the status/evidence update. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] active review/plan 파일을 `.log`로 아카이브한다. +- [x] PASS이면 `complete.log` 작성 후 `agent-task/archive/YYYY/MM/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/`로 이동한다. +- [x] PASS split 작업이면 빈 active parent를 제거하거나 남은 sibling 때문에 유지했다고 확인한다. +- [ ] WARN/FAIL이면 후속 active plan/review 또는 정당한 `USER_REVIEW.md`를 작성한다. + +## 계획 대비 변경 사항 + +- `rsync`가 로컬 환경(sandbox)에 없어 계획의 `rsync -a --delete` 대신 `tar czf - . | ssh ... tar xzf - -C "$preview_dir"`로 동일한 결과(신규 preview dir에 build/web 전체 배포)를 만들었다. 목적지 디렉터리는 계획대로 새 `oto-ui-preview--jenkins-ia` 경로이며 기존 checkout은 건드리지 않았다. +- `apps/client/test/widget_test.dart:1729` 부근 실행 시 `_DetailPane` 헤더 Row(`Row(mainAxisAlignment: spaceBetween)`)가 새 run-context 좌측 task menu 폭(168px)만큼 줄어든 콘텐츠 폭에서 `RenderFlex` overflow를 냈다. `packages/flutter/oto_console/lib/src/oto_executions_surface.dart`의 `_DetailPane` title을 `Expanded(overflow: ellipsis)`로 감싸 action 버튼과 공존 가능하게 수정했다(계획에 명시되지 않은 부수 수정, `_DetailPane` 자체는 계획 대상 파일 범위 안). +- Settings Manage OTO 카테고리는 계획 체크리스트가 예시로 든 `System/Security/Tools/Nodes/Browser policy` 중 실제 데이터가 있는 `System`(Runtime endpoints)과 `Security`(Browser policy 하위 그룹)만 구현했다. `Tools`/`Nodes`는 OTO Core에 대응 데이터가 없어 뼈대만 있는 placeholder 카테고리를 추가하지 않았다(범위 결정 근거의 "OTO contract에 맞는 항목만 표시" 원칙 적용). 필요한 텍스트(`Manage OTO`/`System`/`Security`/`Browser policy`)는 모두 노출된다. +- Runners 테이블은 계획의 "Nodes, Executor, Current job, Current run, Manage" 컬럼 헤더를 그대로 추가했지만, `apps/client/test/widget_test.dart`가 OPS-2 범위 밖이라 기존 row 텍스트(`ID: $runnerID`, `Alias: ...`, `Message: ...`, `Job: ...`/`Execution: ...`)는 그대로 보존했다. `Manage` 컬럼은 아이콘만 표시하고 기존 Self Update 폼은 행 하단에 그대로 유지했다(플랜이 요구한 콜백/검증 로직 변경 없음). + +## 주요 설계 결정 + +- Run-context 좌측 task menu(`_RunTaskMenu`)는 별도 per-item view가 없어 Jenkins build 페이지 사이드바처럼 정적 라벨 목록으로 구현했다(클릭 동작 없음). Core에 stage/console/artifact를 개별 뷰로 분리할 데이터 소스가 없는 현재 범위에서 과도한 상태 관리를 피하기 위한 선택이다. +- Stage Graph는 실제 stage 데이터가 없으므로 `Checkout/Build/Package/Publish` 고정 스켈레톤을 실행 `state`(succeeded/failed/running/canceled/queued)에서 결정적으로 매핑했다. `Stage summary unavailable`는 제거하고 그래프 하단의 secondary note(“Per-stage telemetry is not reported yet …”)로 대체했다. +- Manage OTO Security 카드는 기존 Browser policy 상세 그룹(값 불변)을 그대로 재사용해 `Security`(카테고리)와 `Browser policy`(하위 그룹) 두 라벨이 자연스럽게 함께 노출되도록 구성했다. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `Stage summary unavailable` 단독 placeholder가 사라지고 `Stage Graph`와 run-context left task menu가 primary run detail이 되었는지 확인한다. +- Runners가 generic card list가 아니라 Nodes/Agents operational list로 보이는지 확인한다. +- Settings fallback이 Manage OTO category menu/surface인지 확인한다. +- Preview deployment가 dirty remote checkout을 source로 사용하지 않았는지 확인한다. + +## 검증 결과 + +### JENKINS_IA_OPS-1 중간 검증 +```bash +$ cd packages/flutter/oto_console && flutter test test/oto_console_test.dart +00:01 +9: OtoExecutionsSurface renders loading/empty/error/data states and expanded detail +00:01 +14: All tests passed! +``` +(첫 실행 시 apps/client 쪽에서 좌측 task menu로 줄어든 폭 때문에 `_DetailPane` 헤더 Row가 RenderFlex overflow를 냈다. `_DetailPane` title을 `Expanded(overflow: ellipsis)`로 수정한 뒤 재실행하여 통과했다 — 상세는 `계획 대비 변경 사항` 참조.) + +### JENKINS_IA_OPS-2 중간 검증 +```bash +$ cd packages/flutter/oto_console && flutter test test/oto_console_test.dart +00:00 +6: OtoRunnersSurface renders data state with runners list +00:01 +7: OtoRunnersSurface renders runner action states +00:01 +14: All tests passed! +``` + +### JENKINS_IA_OPS-3 중간 검증 +```bash +$ cd packages/flutter/oto_console && flutter test test/oto_console_test.dart +00:00 +2: renders section surfaces with empty states +00:01 +14: All tests passed! +``` + +### JENKINS_IA_OPS-4 중간 검증 +```bash +$ cd apps/client && flutter build web --dart-define=OTO_SERVER_HTTP_URL=http://toki-labs.com:18020 +Compiling lib/main.dart for the Web... 31.9s +✓ Built build/web + +$ preview_dir="/Users/toki/agent-work/oto-ui-preview-20260703142823-jenkins-ia" +$ ssh toki@toki-labs.com "mkdir -p '$preview_dir'" +(성공, 출력 없음) + +# 로컬 sandbox에 rsync가 없어 tar 파이프로 대체(계획 대비 변경 사항 참조): +$ cd apps/client/build/web && tar czf - . | ssh toki@toki-labs.com "tar xzf - -C '$preview_dir'" +$ ssh toki@toki-labs.com "ls '$preview_dir' | head -5" +assets +canvaskit +favicon.png +flutter_bootstrap.js +flutter_service_worker.js + +$ ssh toki@toki-labs.com "pkill -f 'python3 -m http.server 13020' || true; sleep 1; cd '$preview_dir' && nohup python3 -m http.server 13020 >/tmp/oto-ui-preview.log 2>&1 & disown; sleep 1; echo started" +started + +$ curl -I http://toki-labs.com:13020/ +HTTP/1.0 200 OK +Server: SimpleHTTP/0.6 Python/3.9.6 +Date: Fri, 03 Jul 2026 14:28:47 GMT +Content-type: text/html +Content-Length: 1540 +``` + +### 최종 검증 +```bash +$ find agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/01_definition_guardrails -name "complete.log" +agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/01_definition_guardrails/complete.log +# (predecessor 01은 이미 archive로 이동되어 완료되었음 — 02+01_shell_jobs도 archive에서 complete.log 확인됨) + +$ cd packages/flutter/oto_console && flutter analyze && flutter test +Analyzing oto_console... +No issues found! (ran in 3.4s) +00:01 +14: All tests passed! + +$ cd apps/client && flutter analyze && flutter test +Analyzing client... +No issues found! (ran in 2.4s) +00:03 +27: All tests passed! + +$ git diff --check +(출력 없음, exit code 0) + +$ cd apps/client && flutter build web --dart-define=OTO_SERVER_HTTP_URL=http://toki-labs.com:18020 +✓ Built build/web + +$ curl -I http://toki-labs.com:13020/ +HTTP/1.0 200 OK +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** + +## 코드리뷰 결과 + +- 종합 판정: PASS +- 차원별 평가: + - correctness: Pass + - completeness: Pass + - test coverage: Pass + - API contract: Pass + - code quality: Pass + - implementation deviation: Pass + - verification trust: Pass + - spec conformance: Pass (Roadmap Targets 확인, SDD 불필요) +- 발견된 문제: 없음 +- 검증 근거: + - `git diff --check`: 출력 없음, exit code 0 + - `cd packages/flutter/oto_console && flutter analyze && flutter test`: `No issues found!`, `+14: All tests passed!` + - `cd apps/client && flutter analyze && flutter test`: `No issues found!`, `+27: All tests passed!` + - 원격 runner 임시 작업트리 `/Users/toki/agent-work/oto-review-20260703144007-jenkins-ia`: console/client analyze/test 및 `flutter build web --dart-define=OTO_SERVER_HTTP_URL=http://toki-labs.com:18020` PASS + - `curl -I --max-time 15 http://toki-labs.com:13020/`: `HTTP/1.0 200 OK` + - `validate-agent-ui scope=jenkins-ia-executions-ops`: PASS (필수 root 문서 존재, USER_REVIEW 없음, code evidence 경로 존재, listed view/component status 반영 확인) +- 다음 단계: PASS이므로 active plan/review를 `.log`로 아카이브하고 `complete.log` 작성 후 task directory를 archive로 이동한다. diff --git a/agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/complete.log b/agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/complete.log new file mode 100644 index 0000000..7c13b95 --- /dev/null +++ b/agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/complete.log @@ -0,0 +1,58 @@ +# Complete - m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops + +## 완료 일시 + +2026-07-03 + +## 요약 + +Jenkins IA executions/ops 화면 동기화 구현을 1회차 리뷰에서 PASS로 종료했다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | executions stage graph/run context menu, runners Nodes/Agents surface, Manage OTO settings, preview smoke 검증 완료 | + +## 구현/정리 내용 + +- `packages/flutter/oto_console/lib/src/oto_executions_surface.dart`에 run-context task menu와 deterministic `Stage Graph`를 추가하고 `Console Output`/`Artifacts` detail pane 구조로 정리했다. +- `packages/flutter/oto_console/lib/src/oto_runners_surface.dart`를 Nodes/Agents 운영 목록 형태로 재구성하고 기존 self-update action 흐름을 유지했다. +- `packages/flutter/oto_console/lib/src/oto_console_shell.dart`의 settings fallback을 `Manage OTO` category surface로 교체했다. +- `packages/flutter/oto_console/test/oto_console_test.dart`와 `apps/client/test/widget_test.dart`를 Jenkins IA visible primitive 기준으로 갱신했다. +- `agent-ui/definition/index.md`, `agent-ui/definition/views/index.md`, `agent-ui/definition/components/index.md`의 executions/ops 구현 상태와 code evidence를 PASS 기준으로 반영했다. + +## 최종 검증 + +- `git diff --check` - PASS; 출력 없음, exit code 0 +- `cd packages/flutter/oto_console && flutter analyze && flutter test` - PASS; `No issues found!`, `+14: All tests passed!` +- `cd apps/client && flutter analyze && flutter test` - PASS; `No issues found!`, `+27: All tests passed!` +- 원격 runner 임시 작업트리 `/Users/toki/agent-work/oto-review-20260703144007-jenkins-ia`에서 `cd packages/flutter/oto_console && flutter analyze && flutter test`, `cd apps/client && flutter analyze && flutter test`, `flutter build web --dart-define=OTO_SERVER_HTTP_URL=http://toki-labs.com:18020` - PASS; console/client tests 통과, web build 성공 +- `cd apps/client && flutter build web --dart-define=OTO_SERVER_HTTP_URL=http://toki-labs.com:18020` - PASS; `Built build/web` +- `curl -I --max-time 15 http://toki-labs.com:13020/` - PASS; `HTTP/1.0 200 OK` +- `validate-agent-ui scope=jenkins-ia-executions-ops` - PASS; 필수 root 문서 존재, USER_REVIEW 없음, code evidence 경로 존재, listed view/component status 반영 확인 + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-jenkins-menu-ia-code-sync.md` +- Completed task ids: + - `execution-ops-menu-sync`: PASS; evidence=`agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/plan_cloud_G07_0.log`, `agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/code_review_cloud_G07_0.log`; verification=`cd packages/flutter/oto_console && flutter analyze && flutter test`, `cd apps/client && flutter analyze && flutter test`, `validate-agent-ui scope=jenkins-ia-executions-ops` + - `preview-deploy`: PASS; evidence=`agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/plan_cloud_G07_0.log`, `agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/code_review_cloud_G07_0.log`; verification=`cd apps/client && flutter build web --dart-define=OTO_SERVER_HTTP_URL=http://toki-labs.com:18020`, `curl -I --max-time 15 http://toki-labs.com:13020/` +- Not completed task ids: 없음 + +## Agent UI Completion + +- Agent UI docs: + - `agent-ui/definition/index.md`: `구현됨` 근거 반영; code evidence=`packages/flutter/oto_console/lib/src/oto_executions_surface.dart`, `packages/flutter/oto_console/lib/src/oto_runners_surface.dart`, `packages/flutter/oto_console/lib/src/oto_console_shell.dart`; implementation verification=`cd packages/flutter/oto_console && flutter analyze && flutter test`, `cd apps/client && flutter analyze && flutter test`; validation=`validate-agent-ui scope=jenkins-ia-executions-ops` PASS + - `agent-ui/definition/views/index.md`: `executions`, `runners`, `settings` Jenkins IA criteria `구현됨`; code evidence=`packages/flutter/oto_console/lib/src/oto_executions_surface.dart`, `packages/flutter/oto_console/lib/src/oto_runners_surface.dart`, `packages/flutter/oto_console/lib/src/oto_console_shell.dart`; implementation verification=`cd packages/flutter/oto_console && flutter analyze && flutter test`, `cd apps/client && flutter analyze && flutter test`; validation=`validate-agent-ui scope=jenkins-ia-executions-ops` PASS + - `agent-ui/definition/components/index.md`: `section-surface`, `record-list`, `execution-action-toolbar` ops component criteria `구현됨`; code evidence=`packages/flutter/oto_console/lib/src/oto_console_shell.dart`, `packages/flutter/oto_console/lib/src/oto_runners_surface.dart`, `packages/flutter/oto_console/lib/src/oto_executions_surface.dart`; implementation verification=`cd packages/flutter/oto_console && flutter analyze && flutter test`, `cd apps/client && flutter analyze && flutter test`; validation=`validate-agent-ui scope=jenkins-ia-executions-ops` PASS +- Status updates: 완료 +- Remaining agent-ui issues: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/PLAN-cloud-G07.md b/agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/plan_cloud_G07_0.log similarity index 100% rename from agent-task/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/PLAN-cloud-G07.md rename to agent-task/archive/2026/07/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/plan_cloud_G07_0.log diff --git a/agent-task/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/CODE_REVIEW-cloud-G07.md b/agent-task/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/CODE_REVIEW-cloud-G07.md deleted file mode 100644 index 5e281e7..0000000 --- a/agent-task/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/CODE_REVIEW-cloud-G07.md +++ /dev/null @@ -1,165 +0,0 @@ - - -# Code Review Reference - JENKINS_IA_OPS - -> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** -> The task is NOT complete until every implementation-owned section below is filled in. -> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. -> Fill implementation-owned sections, then stop with active files in place and report ready for review. -> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. -> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. -> Finalization is review-agent-only. - -## 개요 - -date=2026-07-03 -task=m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops, plan=0, tag=JENKINS_IA_OPS - -## Roadmap Targets - -- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/control-plane-jenkins-menu-ia-code-sync.md` -- Task ids: - - `execution-ops-menu-sync`: executions/runners/settings 화면이 `Pipeline Overview`, `Console Output`, `Artifacts`, `Build History`, `Nodes/Agents`, `Manage OTO` 중심으로 동기화된다. - - `preview-deploy`: 동기화된 Flutter web build를 원격 preview에 배포한다. -- Completion mode: check-on-pass - -## 이 파일을 읽는 리뷰 에이전트에게 - -> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [JENKINS_IA_OPS-1] Pipeline Graph Run Detail | [ ] | -| [JENKINS_IA_OPS-2] Nodes And Agents Runners Surface | [ ] | -| [JENKINS_IA_OPS-3] Manage OTO Settings | [ ] | -| [JENKINS_IA_OPS-4] Preview Deployment Smoke | [ ] | - -## 구현 체크리스트 - -- [ ] [JENKINS_IA_OPS-1] executions run detail을 Pipeline Graph View/Stage View 및 run-context left task menu 중심 구조로 개편한다. -- [ ] [JENKINS_IA_OPS-2] runners를 Jenkins Nodes/Agents style 운영 목록으로 개편한다. -- [ ] [JENKINS_IA_OPS-3] settings fallback을 Manage OTO category 구조로 개편한다. -- [ ] [JENKINS_IA_OPS-4] console/client tests를 갱신하고 원격 preview를 재배포한다. -- [ ] `cd packages/flutter/oto_console && flutter analyze && flutter test`, `cd apps/client && flutter analyze && flutter test`, `git diff --check`, preview `curl -I`를 실행한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. - -## Agent UI Completion - -- Mode: review-pass-status-update -- Agent UI docs: - - `agent-ui/definition/index.md` - - `agent-ui/definition/views/index.md` - - `agent-ui/definition/components/index.md` -- Actual code evidence: - - [ ] `packages/flutter/oto_console/lib/src/oto_executions_surface.dart`: - - [ ] `packages/flutter/oto_console/lib/src/oto_runners_surface.dart`: - - [ ] `packages/flutter/oto_console/lib/src/oto_console_shell.dart`: -- Implementation verification evidence: - - [ ] `cd packages/flutter/oto_console && flutter analyze && flutter test`: - - [ ] `cd apps/client && flutter analyze && flutter test`: - - [ ] `curl -I http://toki-labs.com:13020/`: -- Status updates requested on PASS: - - [ ] `agent-ui/definition/views/index.md`: `executions`, `runners`, `settings` Jenkins IA criteria `계획` -> `구현됨` - - [ ] `agent-ui/definition/components/index.md`: ops component criteria `계획` -> `구현됨` -- Review finalization: - - [ ] Review agent updated the listed agent-ui docs and code evidence. - - [ ] Review agent ran `validate-agent-ui scope=jenkins-ia-executions-ops` after the status/evidence update. - -## 코드리뷰 전용 체크리스트 - -> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. - -- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. -- [ ] active review/plan 파일을 `.log`로 아카이브한다. -- [ ] PASS이면 `complete.log` 작성 후 `agent-task/archive/YYYY/MM/m-control-plane-jenkins-menu-ia-code-sync/03+01_executions_ops/`로 이동한다. -- [ ] PASS split 작업이면 빈 active parent를 제거하거나 남은 sibling 때문에 유지했다고 확인한다. -- [ ] WARN/FAIL이면 후속 active plan/review 또는 정당한 `USER_REVIEW.md`를 작성한다. - -## 계획 대비 변경 사항 - -_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ - -## 주요 설계 결정 - -_구현 에이전트가 주요 설계 결정 사항을 기록한다._ - -## 사용자 리뷰 요청 - -_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ - -- 상태: 없음 -- 사유 유형: 없음 -- 연결 대상: 없음 -- 결정 필요: 없음 -- 차단 근거: 없음 -- 실행한 검증/명령: 없음 -- 자동 후속 불가 이유: 없음 -- 재개 조건: 없음 - -## 리뷰어를 위한 체크포인트 - -- `Stage summary unavailable` 단독 placeholder가 사라지고 `Stage Graph`와 run-context left task menu가 primary run detail이 되었는지 확인한다. -- Runners가 generic card list가 아니라 Nodes/Agents operational list로 보이는지 확인한다. -- Settings fallback이 Manage OTO category menu/surface인지 확인한다. -- Preview deployment가 dirty remote checkout을 source로 사용하지 않았는지 확인한다. - -## 검증 결과 - -### JENKINS_IA_OPS-1 중간 검증 -```bash -$ cd packages/flutter/oto_console && flutter test test/oto_console_test.dart -(output) -``` - -### JENKINS_IA_OPS-2 중간 검증 -```bash -$ cd packages/flutter/oto_console && flutter test test/oto_console_test.dart -(output) -``` - -### JENKINS_IA_OPS-3 중간 검증 -```bash -$ cd packages/flutter/oto_console && flutter test test/oto_console_test.dart -(output) -``` - -### JENKINS_IA_OPS-4 중간 검증 -```bash -$ cd apps/client && flutter build web --dart-define=OTO_SERVER_HTTP_URL=http://toki-labs.com:18020 -(output) -$ preview_dir="/Users/toki/agent-work/oto-ui-preview-$(date -u +%Y%m%d%H%M%S)-jenkins-ia" -$ ssh toki@toki-labs.com "mkdir -p '$preview_dir'" -(output) -$ rsync -a --delete apps/client/build/web/ "toki@toki-labs.com:$preview_dir/" -(output) -$ ssh toki@toki-labs.com "pkill -f 'python3 -m http.server 13020' || true; cd '$preview_dir' && nohup python3 -m http.server 13020 >/tmp/oto-ui-preview.log 2>&1 &" -(output) -$ curl -I http://toki-labs.com:13020/ -(output) -``` - -### 최종 검증 -```bash -$ test -f agent-task/m-control-plane-jenkins-menu-ia-code-sync/01_definition_guardrails/complete.log -(output) -$ cd packages/flutter/oto_console && flutter analyze && flutter test -(output) -$ cd apps/client && flutter analyze && flutter test -(output) -$ git diff --check -(output) -$ cd apps/client && flutter build web --dart-define=OTO_SERVER_HTTP_URL=http://toki-labs.com:18020 -(output) -$ curl -I http://toki-labs.com:13020/ -(output) -``` - ---- - -> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** diff --git a/agent-ui/definition/components/index.md b/agent-ui/definition/components/index.md index 57d0c4d..0c37c89 100644 --- a/agent-ui/definition/components/index.md +++ b/agent-ui/definition/components/index.md @@ -29,17 +29,17 @@ components: |--------------|--------|----------|-------------| | `console-shell` | 구현됨 | `oto_console_shell.dart` + Jenkins IA shell/jobs gate | overall console scaffold with integrated header, breadcrumb, action slot, left menu, and selected section content. | | `console-rail` | 계획 | `oto_console_shell.dart` + Jenkins IA gate | section navigation rail. `New Item`/`Build History`와 job context menu는 구현됨. `Nodes`, `Manage OTO`, run context menu는 03 후속 구현 대기. | -| `section-surface` | 계획 | `oto_console_shell.dart` + Jenkins IA gate | generic titled empty/detail section shell. 필수 primitive: `Manage OTO` 관리 category grouping. Jenkins IA gate 미충족, 후속 구현 대기. | +| `section-surface` | 구현됨 | `oto_console_shell.dart` + Jenkins IA executions/ops gate | generic titled empty/detail section shell. `Manage OTO` 관리 category grouping과 `System`/`Security`/`Browser policy` primitive가 widget 테스트로 확인됨. | | `state-panel` | 구현됨 | surface files + Jenkins reference baseline | loading, empty, error state blocks. Jenkins monitor/banner-style state 반영 | | `status-badge` | 구현됨 | overview/runners/jobs/executions surface files + Jenkins reference baseline | compact status label badge. Jenkins result/severity vocabulary 정렬 반영 | | `endpoint-tile` | 구현됨 | `oto_console_overview.dart` | endpoint or config value tile | | `connection-status-grid` | 구현됨 | `oto_console_overview.dart` | health/readiness status grid | | `auth-token-reference` | 구현됨 | `oto_console_overview.dart` | display-only auth token reference | -| `record-list` | 구현됨 | jobs/runners/executions/artifacts surface files + Jenkins IA shell/jobs gate | repeated entity list rows. Pipelines job table은 Jenkins Dashboard density로 구현됨. Build History/Nodes density 보강은 03에서 이어간다. | +| `record-list` | 구현됨 | jobs/runners/executions/artifacts surface files + Jenkins IA shell/jobs/executions/ops gate | repeated entity list rows. Pipelines job table, Build History run detail, Nodes/Agents runner row density가 widget 테스트로 확인됨. | | `action-feedback` | 구현됨 | runners/jobs/executions surface files + Jenkins reference baseline | success/deferred/failed action message. Jenkins action outcome 정렬 반영 | | `runner-self-update-form` | 구현됨 | `oto_runners_surface.dart` + Jenkins reference baseline | runner self-update draft form. Jenkins update action density 정렬 반영 | | `job-create-form` | 구현됨 | `oto_jobs_surface.dart` + Jenkins reference baseline | job create draft form. Jenkins New Item flow 정렬 반영 | -| `execution-action-toolbar` | 계획 | `oto_executions_surface.dart` + Jenkins IA gate | cancel/report/log/artifact execution actions. 필수 primitive: run detail의 `Pipeline Overview`/`Stage Graph`, `Console Output` 연결 action. Jenkins IA gate 미충족, 후속 구현 대기. | +| `execution-action-toolbar` | 구현됨 | `oto_executions_surface.dart` + Jenkins IA executions/ops gate | cancel/report/log/artifact execution actions. run detail의 `Pipeline Overview`/`Stage Graph`, `Console Output`, `Artifacts` 연결 primitive가 widget 테스트로 확인됨. | | `authorization-state` | 계획 | security/audit SDD | operator/admin allow/deny, disabled, and error state rules for write actions | | `audit-action-summary` | 계획 | security/audit SDD | display-safe action metadata summary for audit-oriented UI exposure | | `log-preview` | 구현됨 | `oto_executions_surface.dart` + Jenkins reference baseline | bounded execution log preview. Jenkins Console Output/live log pane 정렬 반영 | diff --git a/agent-ui/definition/index.md b/agent-ui/definition/index.md index ae31b56..2b8cba7 100644 --- a/agent-ui/definition/index.md +++ b/agent-ui/definition/index.md @@ -55,7 +55,7 @@ OTO Control Plane Flutter 콘솔의 운영 화면, 반복 component, 상태 표 ## Target Layout Status - Shell/jobs 범위는 통합 header/breadcrumb/action, Dashboard-level `New Item`/`Build History` menu, pipelines dashboard job table, job context `Status`/`Build History`/`Stages` primitive가 코드와 widget 테스트로 확인되어 구현 근거를 갖는다. -- Executions/runners/settings 범위의 run stage graph, `Console Output`, Nodes/Agents, `Manage OTO` 구조는 후속 03 구현 전까지 `계획` 상태로 둔다. +- Executions/runners/settings 범위의 run stage graph, `Console Output`, Nodes/Agents, `Manage OTO` 구조는 03 구현에서 코드와 widget 테스트로 확인되어 구현 근거를 갖는다. - 목표는 pixel parity가 아니라 header/action/status/list/detail/log/artifact의 정보 배치와 운영 밀도를 Jenkins 최신 UI 흐름에 맞추는 것이다. - 보안/감사 source of truth처럼 문서 계획만 있는 항목은 계속 `계획` 상태로 두며, Jenkins IA gate 미충족 항목과 섞지 않는다. @@ -98,3 +98,4 @@ Jenkins 정렬 대상 view/component는 아래 structural primitive가 코드와 - 2026-07-03: Jenkins 정렬 레이아웃 패스 구현과 검증 근거를 바탕으로 대상 view/component status를 `구현됨`으로 갱신했다. - 2026-07-03: Jenkins reference를 러프 정렬에서 `Jenkins IA gate` structural acceptance로 강화하고, gate 미충족 대상 view/component status를 `계획`으로 정정했다. 후속 구현(02/03)이 primitive를 테스트로 확인하면 `구현됨`으로 승격한다. - 2026-07-03: 02 shell/jobs 구현에서 통합 header/breadcrumb/action, `New Item`/`Build History` menu, pipelines dashboard job table과 job context menu primitive를 코드와 widget 테스트로 확인했다. Executions/runners/settings primitive는 03 범위로 남겼다. +- 2026-07-03: 03 executions/ops 구현에서 run context menu(`Pipeline Overview`, `Console Output`, `Artifacts`, `Build History`), `Stage Graph`, Nodes/Agents table, `Manage OTO` category surface를 코드와 widget 테스트로 확인했다. diff --git a/agent-ui/definition/views/index.md b/agent-ui/definition/views/index.md index df7ec94..2f1d16a 100644 --- a/agent-ui/definition/views/index.md +++ b/agent-ui/definition/views/index.md @@ -17,12 +17,12 @@ views: | View ID | Status | Evidence | Description | |---------|--------|----------|-------------| | `overview` | 계획 | `OtoConsoleOverview` + Jenkins IA gate | Control Plane endpoint, connection, authentication reference overview. 필수: 통합 header/breadcrumb/action(`Dashboard` root)이 shell/header에 존재한다. Jenkins IA gate 미충족, 후속 구현 대기. | -| `runners` | 계획 | `OtoRunnersSurface` + Jenkins IA gate | runner registry state and self-update action surface. 필수: Jenkins Nodes/Agents 목록 구조로 node 상태/현재 작업/연결 메시지를 스캔한다. Jenkins IA gate 미충족, 후속 구현 대기. | +| `runners` | 구현됨 | `OtoRunnersSurface` + Jenkins IA executions/ops gate | runner registry state and self-update action surface. Nodes/Agents 목록 구조와 node 상태/현재 작업/연결 메시지 primitive가 widget 테스트로 확인됨. | | `pipelines` | 구현됨 | `OtoJobsSurface` + Jenkins IA shell/jobs gate | pipeline/job queue list and create job action surface. Dashboard job table density, `New Item` 생성 entry, job context `Status`/`Build History`/`Stages` primitive가 widget 테스트로 확인됨. | -| `executions` | 계획 | `OtoExecutionsSurface` + Jenkins IA gate | execution list, detail expansion, write actions, log/artifact preview. 필수: Pipeline Graph View 스타일 `Pipeline Overview`/`Stage Graph`, `Console Output`, `Build History`가 run detail에 존재한다. Jenkins IA gate 미충족, 후속 구현 대기. | +| `executions` | 구현됨 | `OtoExecutionsSurface` + Jenkins IA executions/ops gate | execution list, detail expansion, write actions, log/artifact preview. `Pipeline Overview`/`Stage Graph`, `Console Output`, `Artifacts`, `Build History` run context primitive가 widget 테스트로 확인됨. | | `artifacts` | 구현됨 | `OtoArtifactsSurface` + Jenkins reference baseline | artifact event list. Jenkins artifact browser-style list 반영 | | `agent` | 구현됨 | `OtoAgentPanel` | embeddable agent-shell panel for OTO capabilities | -| `settings` | 계획 | `OtoConsoleShell` fallback section + Jenkins IA gate | runtime endpoint and browser policy details. 필수: `Manage OTO`(Manage Jenkins 대응) 관리 category grouping이 존재한다. Jenkins IA gate 미충족, 후속 구현 대기. | +| `settings` | 구현됨 | `OtoConsoleShell` Manage OTO fallback + Jenkins IA executions/ops gate | runtime endpoint and browser policy details. `Manage OTO` 관리 category grouping과 `System`/`Security`/`Browser policy` primitive가 widget 테스트로 확인됨. | ## Required Visible Primitives diff --git a/apps/client/test/widget_test.dart b/apps/client/test/widget_test.dart index 2a009cc..5ea3c9f 100644 --- a/apps/client/test/widget_test.dart +++ b/apps/client/test/widget_test.dart @@ -1730,8 +1730,14 @@ void _registerJobsExecutionsTests() { await tester.pumpAndSettle(); expect(find.text('Run exec-99'), findsOneWidget); - expect(find.text('Stage summary unavailable'), findsOneWidget); - expect(find.text('Logs Preview'), findsOneWidget); + expect(find.text('Stage Graph'), findsOneWidget); + expect(find.text('Checkout'), findsOneWidget); + expect(find.text('Build'), findsOneWidget); + expect(find.text('Package'), findsOneWidget); + expect(find.text('Publish'), findsOneWidget); + // 'Console Output' appears once in the run-context task menu and once + // as the logs pane title. + expect(find.text('Console Output'), findsNWidgets(2)); expect(find.textContaining('Task 1 done'), findsOneWidget); expect(find.text('release.tar.gz'), findsOneWidget); diff --git a/packages/flutter/oto_console/lib/src/oto_console_shell.dart b/packages/flutter/oto_console/lib/src/oto_console_shell.dart index 6cc184e..ad1a9aa 100644 --- a/packages/flutter/oto_console/lib/src/oto_console_shell.dart +++ b/packages/flutter/oto_console/lib/src/oto_console_shell.dart @@ -115,40 +115,7 @@ class _OtoConsoleShellState extends State { OtoConsoleSection.agent => widget.agent ?? OtoAgentPanel(capabilities: widget.capabilities), OtoConsoleSection.settings => - widget.settings ?? - OtoConsoleSectionSurface( - icon: Icons.tune, - title: 'Settings', - emptyTitle: 'No local preferences', - emptyMessage: 'Settings are using runtime defaults.', - detailGroups: [ - OtoConsoleSectionDetailGroup( - title: 'Runtime endpoints', - details: [ - if (widget.config != null) - OtoConsoleSectionDetail( - label: 'HTTP API', - value: widget.config!.serverHttpUrl, - ), - if (widget.config != null) - OtoConsoleSectionDetail( - label: 'Wire URL', - value: widget.config!.serverWireUrl, - ), - ], - ), - const OtoConsoleSectionDetailGroup( - title: 'Browser policy', - details: [ - OtoConsoleSectionDetail( - label: 'Browser API policy', - value: 'CORS headers or a same-origin proxy are required.', - ), - ], - ), - ], - theme: theme, - ), + widget.settings ?? _ManageOtoSettingsSurface(theme: theme, config: widget.config), }; } } @@ -588,6 +555,172 @@ class _LeftMenuButton extends StatelessWidget { } } +/// Jenkins Manage Jenkins-aligned settings surface: category cards limited +/// to the OTO contract data actually available today (runtime endpoints, +/// browser access policy) instead of a generic empty-state fallback. +class _ManageOtoSettingsSurface extends StatelessWidget { + final OtoConsoleThemeAdapter theme; + final OtoConsoleConfig? config; + + const _ManageOtoSettingsSurface({required this.theme, this.config}); + + @override + Widget build(BuildContext context) { + return Container( + color: theme.backgroundColor, + child: Padding( + padding: const EdgeInsets.all(24), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 820), + child: ListView( + shrinkWrap: true, + children: [ + Row( + children: [ + Icon(Icons.tune, color: theme.primaryColor, size: 24), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Manage OTO', + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: theme.textColor, + fontSize: 24, + fontWeight: FontWeight.w800, + ), + ), + ), + ], + ), + const SizedBox(height: 20), + _ManageCategoryCard( + icon: Icons.dns_outlined, + title: 'System', + description: + 'Runtime endpoints this console connects to.', + theme: theme, + details: [ + if (config != null) + OtoConsoleSectionDetail( + label: 'HTTP API', + value: config!.serverHttpUrl, + ), + if (config != null) + OtoConsoleSectionDetail( + label: 'Wire URL', + value: config!.serverWireUrl, + ), + ], + ), + const SizedBox(height: 16), + _ManageCategoryCard( + icon: Icons.security_outlined, + title: 'Security', + description: 'Browser access policy enforced for this console.', + theme: theme, + detailGroups: const [ + OtoConsoleSectionDetailGroup( + title: 'Browser policy', + details: [ + OtoConsoleSectionDetail( + label: 'Browser API policy', + value: 'CORS headers or a same-origin proxy are required.', + ), + ], + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} + +/// A single Manage OTO category card. Supports either a flat `details` list +/// or nested `detailGroups` so categories can subdivide (e.g. Security -> +/// Browser policy) without a new card type. +class _ManageCategoryCard extends StatelessWidget { + final IconData icon; + final String title; + final String description; + final OtoConsoleThemeAdapter theme; + final List details; + final List detailGroups; + + const _ManageCategoryCard({ + required this.icon, + required this.title, + required this.description, + required this.theme, + this.details = const [], + this.detailGroups = const [], + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme.railBackgroundColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: theme.borderColor), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, color: theme.primaryColor, size: 20), + const SizedBox(width: 10), + Text( + title, + style: TextStyle( + color: theme.textColor, + fontSize: 16, + fontWeight: FontWeight.w800, + ), + ), + ], + ), + const SizedBox(height: 6), + Text( + description, + style: TextStyle(color: theme.textSecondaryColor, fontSize: 12), + ), + if (detailGroups.isNotEmpty) ...[ + for (final group in detailGroups) ...[ + const SizedBox(height: 14), + Text( + group.title, + style: TextStyle( + color: theme.textSecondaryColor, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + for (final detail in group.details) ...[ + _SectionDetailTile(detail: detail, theme: theme), + const SizedBox(height: 8), + ], + ], + ] else if (details.isNotEmpty) ...[ + const SizedBox(height: 14), + for (final detail in details) ...[ + _SectionDetailTile(detail: detail, theme: theme), + const SizedBox(height: 8), + ], + ], + ], + ), + ); + } +} + class OtoConsoleSectionDetail { final String label; final String value; diff --git a/packages/flutter/oto_console/lib/src/oto_executions_surface.dart b/packages/flutter/oto_console/lib/src/oto_executions_surface.dart index 44a888a..bbe3f52 100644 --- a/packages/flutter/oto_console/lib/src/oto_executions_surface.dart +++ b/packages/flutter/oto_console/lib/src/oto_executions_surface.dart @@ -326,40 +326,53 @@ class OtoExecutionsSurface extends StatelessWidget { borderRadius: BorderRadius.circular(8), border: Border.all(color: theme.borderColor), ), - child: Column( + child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildRunHeader(theme, exec), - const SizedBox(height: 16), - _buildActionsSection(context, theme, exec), - const SizedBox(height: 16), - _buildStageSummaryPlaceholder(theme), - const SizedBox(height: 16), - _DetailPane( - title: 'Logs Preview', - theme: theme, - child: _LogPreviewBox( - theme: theme, - child: _buildLogsContent(theme), - ), - ), - const SizedBox(height: 16), - _DetailPane( - title: 'Artifacts', - theme: theme, - action: onViewArtifactsSection == null - ? null - : TextButton.icon( - onPressed: onViewArtifactsSection, - icon: const Icon(Icons.arrow_forward, size: 14), - label: const Text('View Artifacts Tab'), - style: TextButton.styleFrom( - foregroundColor: theme.primaryColor, - padding: EdgeInsets.zero, - visualDensity: VisualDensity.compact, - ), + _RunTaskMenu(theme: theme), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildRunHeader(theme, exec), + const SizedBox(height: 16), + _buildActionsSection(context, theme, exec), + const SizedBox(height: 16), + _DetailPane( + title: 'Pipeline Overview', + theme: theme, + child: _PipelineStageGraph(theme: theme, exec: exec), + ), + const SizedBox(height: 16), + _DetailPane( + title: 'Console Output', + theme: theme, + child: _LogPreviewBox( + theme: theme, + child: _buildLogsContent(theme), ), - child: _buildArtifactsContent(theme), + ), + const SizedBox(height: 16), + _DetailPane( + title: 'Artifacts', + theme: theme, + action: onViewArtifactsSection == null + ? null + : TextButton.icon( + onPressed: onViewArtifactsSection, + icon: const Icon(Icons.arrow_forward, size: 14), + label: const Text('View Artifacts Tab'), + style: TextButton.styleFrom( + foregroundColor: theme.primaryColor, + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + ), + ), + child: _buildArtifactsContent(theme), + ), + ], + ), ), ], ), @@ -403,34 +416,6 @@ class OtoExecutionsSurface extends StatelessWidget { ); } - Widget _buildStageSummaryPlaceholder(OtoConsoleThemeAdapter theme) { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: theme.backgroundColor, - borderRadius: BorderRadius.circular(6), - border: Border.all(color: theme.borderColor), - ), - child: Row( - children: [ - Icon( - Icons.account_tree_outlined, - color: theme.textSecondaryColor, - size: 16, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Stage summary unavailable', - style: TextStyle(color: theme.textSecondaryColor, fontSize: 12), - ), - ), - ], - ), - ); - } - Widget _buildActionsSection( BuildContext ctx, OtoConsoleThemeAdapter theme, @@ -882,14 +867,16 @@ class _DetailPane extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - title, - style: TextStyle( - color: theme.textColor, - fontSize: 14, - fontWeight: FontWeight.bold, + Expanded( + child: Text( + title, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: theme.textColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ), ), ), ?action, @@ -925,6 +912,186 @@ class _LogPreviewBox extends StatelessWidget { } } +/// Jenkins build-page-style left task menu for the selected run. Presentational +/// only: OTO has no per-item view (console/artifacts/history all render +/// together below), so entries are static labels rather than tab switches. +class _RunTaskMenu extends StatelessWidget { + final OtoConsoleThemeAdapter theme; + + const _RunTaskMenu({required this.theme}); + + static const _items = [ + (Icons.terminal, 'Console Output'), + (Icons.account_tree_outlined, 'Pipeline Overview'), + (Icons.inventory_2_outlined, 'Artifacts'), + (Icons.history, 'Build History'), + ]; + + @override + Widget build(BuildContext context) { + return Container( + width: 168, + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: theme.backgroundColor, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: theme.borderColor), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final (icon, label) in _items) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + child: Row( + children: [ + Icon(icon, color: theme.textSecondaryColor, size: 15), + const SizedBox(width: 8), + Expanded( + child: Text( + label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: theme.textSecondaryColor, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +enum _StageStatus { succeeded, running, failed, canceled, skipped, pending } + +typedef _StageNode = (String name, _StageStatus status); + +/// Deterministic Pipeline Graph View placeholder. OTO Core does not report +/// per-stage telemetry yet, so a fixed `Checkout/Build/Package/Publish` +/// skeleton is colored from the run's overall state instead of leaving the +/// region empty. +class _PipelineStageGraph extends StatelessWidget { + final OtoConsoleThemeAdapter theme; + final OtoExecutionViewModel exec; + + const _PipelineStageGraph({required this.theme, required this.exec}); + + static const _stageNames = ['Checkout', 'Build', 'Package', 'Publish']; + + List<_StageNode> get _stages { + switch (exec.state.toLowerCase()) { + case 'succeeded': + return [for (final name in _stageNames) (name, _StageStatus.succeeded)]; + case 'failed': + return [ + (_stageNames[0], _StageStatus.succeeded), + (_stageNames[1], _StageStatus.failed), + (_stageNames[2], _StageStatus.skipped), + (_stageNames[3], _StageStatus.skipped), + ]; + case 'running': + return [ + (_stageNames[0], _StageStatus.succeeded), + (_stageNames[1], _StageStatus.running), + (_stageNames[2], _StageStatus.pending), + (_stageNames[3], _StageStatus.pending), + ]; + case 'canceled': + return [ + (_stageNames[0], _StageStatus.succeeded), + (_stageNames[1], _StageStatus.canceled), + (_stageNames[2], _StageStatus.skipped), + (_stageNames[3], _StageStatus.skipped), + ]; + case 'queued': + default: + return [for (final name in _stageNames) (name, _StageStatus.pending)]; + } + } + + Color _colorFor(_StageStatus status) { + switch (status) { + case _StageStatus.succeeded: + return theme.primaryColor; + case _StageStatus.running: + return theme.accentColor; + case _StageStatus.failed: + return Colors.red; + case _StageStatus.canceled: + return Colors.orange; + case _StageStatus.skipped: + case _StageStatus.pending: + return theme.textSecondaryColor; + } + } + + @override + Widget build(BuildContext context) { + final stages = _stages; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Stage Graph', + style: TextStyle( + color: theme.textColor, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 12, + runSpacing: 8, + children: [ + for (final stage in stages) _StageChip(stage: stage, color: _colorFor(stage.$2)), + ], + ), + const SizedBox(height: 8), + Text( + 'Per-stage telemetry is not reported yet; the graph reflects the overall run state.', + style: TextStyle(color: theme.textSecondaryColor, fontSize: 11), + ), + ], + ); + } +} + +class _StageChip extends StatelessWidget { + final _StageNode stage; + final Color color; + + const _StageChip({required this.stage, required this.color}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(6), + border: Border.all(color: color.withValues(alpha: 0.5)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.circle, color: color, size: 8), + const SizedBox(width: 6), + Text( + stage.$1, + style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.w700), + ), + ], + ), + ); + } +} + /// Shown in the detail region when no run is selected in the list. class _RunDetailPlaceholder extends StatelessWidget { final OtoConsoleThemeAdapter theme; diff --git a/packages/flutter/oto_console/lib/src/oto_runners_surface.dart b/packages/flutter/oto_console/lib/src/oto_runners_surface.dart index f84d204..9babbdd 100644 --- a/packages/flutter/oto_console/lib/src/oto_runners_surface.dart +++ b/packages/flutter/oto_console/lib/src/oto_runners_surface.dart @@ -191,13 +191,22 @@ class _OtoRunnersSurfaceState extends State { OtoConsoleThemeAdapter theme, List runners, ) { - return ListView.separated( - itemCount: runners.length, - separatorBuilder: (context, index) => const SizedBox(height: 12), - itemBuilder: (context, index) { - final runner = runners[index]; - return _buildRunnerRow(context, theme, runner); - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _NodesTableHeader(theme: theme), + const SizedBox(height: 8), + Expanded( + child: ListView.separated( + itemCount: runners.length, + separatorBuilder: (context, index) => const SizedBox(height: 12), + itemBuilder: (context, index) { + final runner = runners[index]; + return _buildRunnerRow(context, theme, runner); + }, + ), + ), + ], ); } @@ -219,15 +228,12 @@ class _OtoRunnersSurfaceState extends State { urlIsHttps && widget.onSelfUpdateRunner != null; - return _RecordRow( - title: runner.runnerID, - subtitle: runner.alias, + return _NodeTableRow( + runnerID: runner.runnerID, + alias: runner.alias, status: _StatusBadge(status: runner.status, theme: theme), - metadata: _RunnerContext( - jobID: runner.currentJobID, - executionID: runner.currentExecutionID, - theme: theme, - ), + currentJobID: runner.currentJobID, + currentExecutionID: runner.currentExecutionID, message: runner.message, theme: theme, actionPanel: widget.onSelfUpdateRunner == null @@ -375,20 +381,58 @@ class _OtoRunnersSurfaceState extends State { } } -class _RecordRow extends StatelessWidget { - final String title; - final String subtitle; +/// Jenkins Nodes/Agents-style table header for the runners list. +class _NodesTableHeader extends StatelessWidget { + final OtoConsoleThemeAdapter theme; + + const _NodesTableHeader({required this.theme}); + + @override + Widget build(BuildContext context) { + final style = TextStyle( + color: theme.textSecondaryColor, + fontSize: 12, + fontWeight: FontWeight.w800, + ); + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + color: theme.railBackgroundColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: theme.borderColor), + ), + child: Row( + children: [ + Expanded(flex: 3, child: Text('Nodes', style: style)), + Expanded(flex: 2, child: Text('Executor', style: style)), + Expanded(flex: 2, child: Text('Current job', style: style)), + Expanded(flex: 2, child: Text('Current run', style: style)), + Expanded(flex: 2, child: Text('Manage', style: style)), + ], + ), + ); + } +} + +/// Jenkins Nodes/Agents-style operational row: node identity, executor +/// status, current job/run context, and a manage affordance above the +/// existing Self Update panel. +class _NodeTableRow extends StatelessWidget { + final String runnerID; + final String alias; final Widget status; - final Widget? metadata; + final String currentJobID; + final String currentExecutionID; final String message; final Widget? actionPanel; final OtoConsoleThemeAdapter theme; - const _RecordRow({ - required this.title, - required this.subtitle, + const _NodeTableRow({ + required this.runnerID, + required this.alias, required this.status, - this.metadata, + required this.currentJobID, + required this.currentExecutionID, required this.message, this.actionPanel, required this.theme, @@ -407,43 +451,66 @@ class _RecordRow extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: Text( - 'ID: $title', - style: TextStyle( - color: theme.textColor, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'ID: $runnerID', + style: TextStyle( + color: theme.textColor, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + if (alias.isNotEmpty) ...[ + const SizedBox(height: 6), + Text( + 'Alias: $alias', + style: TextStyle(color: theme.textColor, fontSize: 14), + ), + ], + ], + ), + ), + Expanded(flex: 2, child: status), + Expanded( + flex: 2, + child: Text( + 'Job: ${currentJobID.isNotEmpty ? currentJobID : "None"}', + overflow: TextOverflow.ellipsis, + style: TextStyle(color: theme.textColor, fontSize: 12), + ), + ), + Expanded( + flex: 2, + child: Text( + 'Execution: ${currentExecutionID.isNotEmpty ? currentExecutionID : "None"}', + overflow: TextOverflow.ellipsis, + style: TextStyle(color: theme.textColor, fontSize: 12), + ), + ), + Expanded( + flex: 2, + child: Icon( + Icons.build_circle_outlined, + color: theme.primaryColor, + size: 18, ), ), - const SizedBox(width: 8), - status, ], ), - if (subtitle.isNotEmpty) ...[ - const SizedBox(height: 8), - Text( - 'Alias: $subtitle', - style: TextStyle(color: theme.textColor, fontSize: 14), - ), - ], if (message.isNotEmpty) ...[ - const SizedBox(height: 8), + const SizedBox(height: 10), Text( 'Message: $message', - style: TextStyle( - color: theme.textSecondaryColor, - fontSize: 14, - ), + style: TextStyle(color: theme.textSecondaryColor, fontSize: 14), ), ], - if (metadata != null) ...[ - const SizedBox(height: 12), - metadata!, - ], if (actionPanel != null) ...[ const SizedBox(height: 12), actionPanel!, @@ -454,56 +521,6 @@ class _RecordRow extends StatelessWidget { } } -class _RunnerContext extends StatelessWidget { - final String jobID; - final String executionID; - final OtoConsoleThemeAdapter theme; - - const _RunnerContext({ - required this.jobID, - required this.executionID, - required this.theme, - }); - - @override - Widget build(BuildContext context) { - if (jobID.isEmpty && executionID.isEmpty) return const SizedBox.shrink(); - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - decoration: BoxDecoration( - color: theme.primaryColor.withValues(alpha: 0.08), - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: theme.primaryColor.withValues(alpha: 0.2), - ), - ), - child: Row( - children: [ - Icon( - Icons.play_circle_outline, - color: theme.primaryColor, - size: 16, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Job: ${jobID.isNotEmpty ? jobID : "None"} | Execution: ${executionID.isNotEmpty ? executionID : "None"}', - style: TextStyle( - color: theme.textColor, - fontSize: 12, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ); - } -} - class _StatusBadge extends StatelessWidget { final String status; final OtoConsoleThemeAdapter theme; diff --git a/packages/flutter/oto_console/test/oto_console_test.dart b/packages/flutter/oto_console/test/oto_console_test.dart index 4f6bf5e..303130f 100644 --- a/packages/flutter/oto_console/test/oto_console_test.dart +++ b/packages/flutter/oto_console/test/oto_console_test.dart @@ -117,7 +117,6 @@ void main() { 'Pipelines': 'No pipeline jobs', 'Executions': 'No executions', 'Artifacts': 'No artifacts', - 'Settings': 'No local preferences', }; for (final entry in sections.entries) { @@ -127,9 +126,15 @@ void main() { expect(find.text(entry.value), findsOneWidget); } + // Settings is no longer a generic empty-state fallback: it renders the + // Manage OTO category surface (System, Security -> Browser policy). await tester.tap(find.byTooltip('Settings')); await tester.pumpAndSettle(); + expect(find.text('Manage OTO'), findsOneWidget); + expect(find.text('System'), findsOneWidget); + expect(find.text('Security'), findsOneWidget); + expect(find.text('Browser policy'), findsOneWidget); expect(find.text('Browser API policy'), findsOneWidget); expect(find.textContaining('same-origin proxy'), findsOneWidget); }); @@ -204,6 +209,13 @@ void main() { ), ); + // Jenkins Nodes/Agents-style table header. + expect(find.text('Nodes'), findsOneWidget); + expect(find.text('Executor'), findsOneWidget); + expect(find.text('Current job'), findsOneWidget); + expect(find.text('Current run'), findsOneWidget); + expect(find.text('Manage'), findsOneWidget); + expect(find.text('ID: runner-1'), findsOneWidget); expect(find.text('Alias: Main Runner'), findsOneWidget); expect(find.text('ACTIVE'), findsOneWidget); @@ -533,12 +545,21 @@ void main() { ), ), ); - // Selected run detail: run header + stage summary placeholder + panes. + // Selected run detail: run header + run-context task menu + stage + // graph + console output/artifacts panes. expect(find.text('Run exec-1'), findsOneWidget); - expect(find.text('Stage summary unavailable'), findsOneWidget); - expect(find.text('Logs Preview'), findsOneWidget); + // 'Console Output', 'Pipeline Overview', and 'Artifacts' each appear + // once in the run-context task menu and once as a detail pane title. + expect(find.text('Console Output'), findsNWidgets(2)); + expect(find.text('Pipeline Overview'), findsNWidgets(2)); + expect(find.text('Build History'), findsOneWidget); + expect(find.text('Stage Graph'), findsOneWidget); + expect(find.text('Checkout'), findsOneWidget); + expect(find.text('Build'), findsOneWidget); + expect(find.text('Package'), findsOneWidget); + expect(find.text('Publish'), findsOneWidget); expect(find.textContaining('Starting build...'), findsOneWidget); - expect(find.text('Artifacts'), findsOneWidget); + expect(find.text('Artifacts'), findsNWidgets(2)); expect(find.text('binary.exe'), findsOneWidget); }, );