2026-10-26 12:15:48 +00:00
/* hal/ui/public/ops.css — 운영자 콘솔 스타일. 상태는 글자+기호로 구분(색은 보조). 760px 이하에서 메뉴가 접히고 표가 카드로 바뀐다(360px 조회·승인·중지·재시도 가능). */
: root { --bg : #f3f5f8 ; --fg : #1b2430 ; --mut : #5b6672 ; --line : #d9dee6 ; --pri : #1f4fb0 ; --ok : #1a7f4b ; --warn : #b26a00 ; --bad : #b3261e ; --card : #fff ; --rad : 10 px ; font-family : - apple-system , "Segoe UI" , "Noto Sans KR" , sans-serif }
* { box-sizing : border-box } body { margin : 0 ; background : var ( -- bg ); color : var ( -- fg ); font-size : 14 px ; line-height : 1.5 } a { color : var ( -- pri )}
. skip { position : absolute ; left : -999 px }. skip : focus { left : 8 px ; top : 8 px ; background : #fff ; padding : 6 px ; z-index : 99 }
. top { display : flex ; align-items : center ; gap : 12 px ; padding : 8 px 14 px ; background : #101826 ; color : #fff ; position : sticky ; top : 0 ; z-index : 20 }. top a { color : #fff }. brand { font-weight : 700 ; text-decoration : none }. ver { font-size : 12 px ; opacity : .9 }. menu-btn { display : none ; font-size : 20 px ; background : none ; border : 0 ; color : #fff }
. top-right { margin-left : auto ; display : flex ; gap : 12 px ; align-items : center }. top-right . muted { color : #c7d0dc }
. shell { display : flex ; min-height : calc ( 100 vh - 48 px )}. side { width : 210 px ; background : #fff ; border-right : 1 px solid var ( -- line ); padding : 10 px 8 px }. side . nav { display : block ; padding : 8 px 10 px ; border-radius : 8 px ; color : var ( -- fg ); text-decoration : none }. side . nav . on { background : #e6edfb ; color : var ( -- pri ); font-weight : 600 }
. main { flex : 1 ; padding : 14 px 18 px ; max-width : 1400 px ; min-width : 0 } h1 { font-size : 20 px ; margin : 4 px 0 10 px } h2 { font-size : 16 px ; margin : 18 px 0 8 px } h3 { font-size : 15 px ; margin : 14 px 0 6 px }
. alerts { display : flex ; flex-direction : column ; gap : 6 px ; margin-bottom : 10 px }. alert { display : block ; padding : 9 px 12 px ; border-radius : 8 px ; border : 1 px solid ; text-decoration : none ; color : var ( -- fg )}. alert . warning { background : #fff7e6 ; border-color : #f0c36d }. alert . critical { background : #fdecea ; border-color : #f3a09a }. alert . info { background : #e8effc ; border-color : #b9cdf2 }
. card { background : var ( -- card ); border : 1 px solid var ( -- line ); border-radius : var ( -- rad ); padding : 14 px ; margin-bottom : 12 px ; min-width : 0 }. cards { display : grid ; grid-template-columns : repeat ( auto - fit , minmax ( 260 px , 1 fr )); gap : 12 px }. card h2 { margin-top : 0 }. comp { display : flex ; flex-direction : column ; gap : 4 px }
. kv { display : grid ; grid-template-columns : max-content 1 fr ; gap : 5 px 12 px ; margin : 8 px 0 }. kv dt { color : var ( -- mut )}. kv dd { margin : 0 ; word-break : break-word }
. btn { display : inline-block ; padding : 7 px 12 px ; border : 1 px solid var ( -- line ); background : #fff ; border-radius : 8 px ; cursor : pointer ; font-size : 13 px ; color : var ( -- fg ); text-decoration : none }. btn . primary { background : var ( -- pri ); color : #fff ; border-color : var ( -- pri )}. btn . danger { color : var ( -- bad ); border-color : var ( -- bad )}. btn . small { padding : 4 px 8 px ; font-size : 12 px }. btn . on { background : #e6edfb ; border-color : var ( -- pri )}. btn : disabled { opacity : .5 ; cursor : not-allowed }. actions { display : flex ; gap : 6 px ; flex-wrap : wrap ; margin : 8 px 0 }
. badge { display : inline-block ; padding : 1 px 8 px ; border-radius : 12 px ; font-size : 12 px ; border : 1 px solid var ( -- line ); background : #fff ; white-space : nowrap }. badge . ok , . badge . done { border-color : var ( -- ok ); color : var ( -- ok )}. badge . failed { border-color : var ( -- bad ); color : var ( -- bad )}. badge . warn , . badge . stale { border-color : var ( -- warn ); color : var ( -- warn )}. badge . running , . badge . waiting , . badge . info { border-color : var ( -- pri ); color : var ( -- pri )}
table { width : 100 % ; border-collapse : collapse ; background : #fff ; border : 1 px solid var ( -- line ); border-radius : var ( -- rad ); overflow : hidden ; margin-bottom : 10 px } th , td { text-align : left ; padding : 8 px 10 px ; border-bottom : 1 px solid var ( -- line ); vertical-align : top ; font-size : 13 px } th { background : #f7f9fc ; font-weight : 600 } tr : last-child td { border-bottom : 0 }
. aw-state { padding : 20 px ; text-align : center ; color : var ( -- mut ); background : #fff ; border : 1 px dashed var ( -- line ); border-radius : var ( -- rad ); margin : 8 px 0 }. aw-state . failed , . aw-state . forbidden { border-color : var ( -- bad ); color : var ( -- bad )}. aw-state . stale , . aw-state . partial { border-color : var ( -- warn ); color : var ( -- warn )}
. row { display : flex ; gap : 8 px ; align-items : center ; flex-wrap : wrap ; margin : 6 px 0 } input , select , textarea { font : inherit ; padding : 7 px 9 px ; border : 1 px solid var ( -- line ); border-radius : 8 px ; width : 100 % ; max-width : 420 px } label { display : block ; font-weight : 600 ; margin : 8 px 0 2 px }. row label { margin : 0 ; font-weight : 400 }
. steps { list-style : none ; padding : 0 ; margin : 6 px 0 }. steps li { display : flex ; gap : 8 px ; padding : 4 px 0 ; border-bottom : 1 px solid var ( -- line )}. steps . s { width : 20 px ; text-align : center }. steps . done . s { color : var ( -- ok )}. steps . running . s { color : var ( -- pri )}. steps . failed . s { color : var ( -- bad )}. steps . waiting . s , . steps . needs_user . s { color : var ( -- warn )}
. flow { display : flex ; flex-wrap : wrap ; gap : 4 px ; padding : 0 ; list-style : none ; counter-reset : f }. flow li { padding : 3 px 8 px ; border : 1 px solid var ( -- line ); border-radius : 8 px ; font-size : 12 px ; color : var ( -- mut )}. flow li . done { border-color : var ( -- ok ); color : var ( -- ok )}. flow li . now { border-color : var ( -- pri ); color : var ( -- pri ); font-weight : 600 }
. pre { background : #f7f9fc ; border : 1 px solid var ( -- line ); border-radius : 8 px ; padding : 8 px ; white-space : pre-wrap ; word-break : break-word ; font-size : 12 px ; max-height : 320 px ; overflow : auto }. muted { color : var ( -- mut )}. err { color : var ( -- bad )}. mono { font-family : ui-monospace , Menlo , monospace ; font-size : 12 px }. small { font-size : 12 px }
. chart { width : 100 % ; height : auto ; background : #fff ; border : 1 px solid var ( -- line ); border-radius : 8 px }. chart . line { stroke : var ( -- pri ); stroke-width : 1.5 }. chart . axis { stroke : var ( -- line )}. chart . lbl { font-size : 11 px ; fill : var ( -- mut )}
. toast { position : fixed ; bottom : 14 px ; left : 50 % ; transform : translateX ( -50 % ); background : #101826 ; color : #fff ; padding : 10 px 16 px ; border-radius : 8 px ; display : none ; z-index : 50 ; max-width : 92 vw }. toast . show { display : block }
. modal { position : fixed ; inset : 0 ; background : rgba ( 0 , 0 , 0 , .5 ); display : flex ; align-items : center ; justify-content : center ; z-index : 40 ; padding : 14 px }. modal [ hidden ] { display : none }. modal . box { background : #fff ; border-radius : 12 px ; padding : 18 px ; max-width : 600 px ; width : 100 % ; max-height : 92 vh ; overflow : auto }. modal . box h2 { margin-top : 0 }. once { background : #fff7e6 ; border : 1 px dashed var ( -- warn ); padding : 10 px ; border-radius : 8 px ; word-break : break-all }
. tabs { display : flex ; gap : 4 px ; border-bottom : 1 px solid var ( -- line ); margin-bottom : 10 px }. tabs a { padding : 8 px 12 px ; text-decoration : none ; color : var ( -- fg ); border-bottom : 2 px solid transparent }. tabs a . on { border-bottom-color : var ( -- pri ); color : var ( -- pri ); font-weight : 600 }
. pending { list-style : none ; padding : 0 ; display : flex ; gap : 14 px ; flex-wrap : wrap }. pending li { background : #fff ; border : 1 px solid var ( -- line ); border-radius : 8 px ; padding : 8 px 12 px }
body . login { display : grid ; place-items : center ; min-height : 100 vh }. login-box { background : #fff ; border : 1 px solid var ( -- line ); border-radius : 12 px ; padding : 24 px ; width : min ( 420 px , 94 vw )}
@ media ( max-width : 760px ) {. menu-btn { display : block }. side { position : fixed ; left : -230 px ; top : 48 px ; bottom : 0 ; transition : left .2 s ; z-index : 30 ; overflow : auto }. side . open { left : 0 }. main { padding : 10 px 12 px }. top-right . muted { display : none }
table . r { display : block ; border : 0 ; background : none } table . r thead { display : none } table . r tr { display : block ; background : #fff ; border : 1 px solid var ( -- line ); border-radius : var ( -- rad ); margin-bottom : 8 px ; padding : 6 px 10 px } table . r td { display : flex ; justify-content : space-between ; gap : 8 px ; border : 0 ; padding : 5 px 0 ; flex-wrap : wrap } table . r td :: before { content : attr ( data - l ); color : var ( -- mut ); font-size : 12 px ; min-width : 70 px }. kv { grid-template-columns : 1 fr }}
HAL AI 채팅룸 도입(0.3.0): 운영자 콘솔 오른쪽 패널에 통합 방(첫 탭 고정) + 모델별 개별 방, 모델 연결(로그인·OAuth PKCE·API 키, 비밀은 Vault), 모델별 고유 정보(이름·역할·특성), HAL 제어·감시 스킬 문서·도구 29종(읽기 즉시 / 쓰기·고위험은 제안 → 운영자 승인(재인증·3단계 확인)), 통합 방 규칙(@이름 지정, 모델→모델 자동 1회 전달), MCP stdio 서버(읽기 전용), 개발 지시 v0.3.0(Oz), docs/HAL-AI.md, VERSION_HISTORY 0.3.0 — test/hal-ai.test.mjs RED 0/11 → GREEN 11/11, 전체 26파일 182건 통과
2026-10-28 17:41:50 +00:00
/* 0.3.0 AI 채팅룸 패널: 본문 오른쪽 고정, 너비 조절, 접기. 1100px 이하에서는 본문 아래 전체 너비. 상태는 글자+배지로 구분 */
. ai-panel { width : var ( -- ai - w , 400 px ); min-width : 280 px ; max-width : 60 vw ; flex : 0 0 auto ; border-left : 1 px solid var ( -- line ); background : #fff ; display : flex ; flex-direction : column ; position : sticky ; top : 48 px ; height : calc ( 100 vh - 48 px ); min-height : 0 }
. ai-panel . closed { display : none }. ai-resize { position : absolute ; left : 0 ; top : 0 ; bottom : 0 ; width : 6 px ; cursor : col-resize ; z-index : 2 }. ai-panel { position : sticky }. ai-panel > . ai-resize { position : absolute }. ai-panel { isolation : isolate }
. ai-head { border-bottom : 1 px solid var ( -- line ); padding : 6 px 6 px 0 10 px }. ai-tabs { display : flex ; flex-wrap : wrap ; gap : 2 px }
. ai-tab { border : 1 px solid transparent ; border-bottom : 0 ; background : none ; padding : 6 px 10 px ; border-radius : 8 px 8 px 0 0 ; cursor : pointer ; font-size : 13 px ; color : var ( -- fg ); text-decoration : none }. ai-tab . on { background : #e6edfb ; color : var ( -- pri ); font-weight : 600 ; border-color : var ( -- line )}. ai-tab . fixed { font-weight : 600 }. ai-tab . off { color : var ( -- mut )}. ai-tab . add { color : var ( -- mut )}. ai-dot { color : var ( -- warn )}
. ai-status { padding : 4 px 10 px ; border-bottom : 1 px solid var ( -- line ); font-size : 12 px ; min-height : 22 px }
. ai-log { flex : 1 ; overflow : auto ; padding : 8 px 10 px ; display : flex ; flex-direction : column ; gap : 8 px ; min-height : 0 }
. ai-msg { border : 1 px solid var ( -- line ); border-radius : 10 px ; padding : 8 px 10 px ; background : #fff ; font-size : 13 px }. ai-msg header { margin-bottom : 4 px }. ai-msg . user { background : #e8effc ; border-color : #b9cdf2 }. ai-msg . assistant { background : #fff }. ai-msg . tool { background : #f7f9fc }. ai-msg . system { background : #fff7e6 ; border-color : #f0c36d }. ai-msg . failed { border-color : var ( -- bad )}. ai-msg . relay { border-style : dashed }
. ai-text { white-space : pre-wrap ; word-break : break-word }. ai-tool { margin : 4 px 0 ; font-size : 12 px }. ai-result summary { cursor : pointer ; font-size : 12 px }. ai-result . pre { max-height : 200 px }
. ai-proposal { border : 1 px solid var ( -- warn ); background : #fff7e6 ; border-radius : 8 px ; padding : 8 px ; margin-top : 6 px }. ai-proposal . executed { border-color : var ( -- ok ); background : #eef8f1 }. ai-proposal . rejected , . ai-proposal . cancelled { border-color : var ( -- line ); background : #f7f9fc }. ai-proposal . failed { border-color : var ( -- bad ); background : #fdecea }
. ai-compose { border-top : 1 px solid var ( -- line ); padding : 8 px 10 px ; display : flex ; flex-direction : column ; gap : 6 px }. ai-compose textarea { max-width : none ; resize : vertical }. ai-compose-row { display : flex ; gap : 8 px ; align-items : center ; justify-content : flex-end ; flex-wrap : wrap }. ai-compose-row label { margin-right : auto ; font-weight : 400 }
. badge . neutral { border-color : var ( -- line ); color : var ( -- mut )}
@ media ( max-width : 1100px ) {. shell { flex-wrap : wrap }. ai-panel { position : static ; width : 100 % !important ; max-width : none ; height : 60 vh ; border-left : 0 ; border-top : 1 px solid var ( -- line )}. ai-resize { display : none }}
HAL 0.3.2: AI 연동 편의성 + 도구 기본 설정·승인 요청 — 공급자 프리셋(Claude·ChatGPT·Gemini·OpenRouter + 기타 OpenAI 호환, Gemini 자체 API 어댑터), 사전 준비 안내, OAuth 저장 직후 로그인 창 자동(완료 페이지 postMessage·닫기, Gemini Google 프리셋, OpenRouter PKCE 키 발급), CLI 로그인용 텍스트 셸(허용 명령만 PTY, 재인증, 입력 미저장, 미로그인 시 자동 열기), 연결별 도구 정책(기본 전부)·로컬 CLI 에 HAL MCP 설정 자동 주입·MCP 설정 스니펫, MCP 쓰기·고위험 호출은 승인 요청(제안)으로 + hal_action_status/hal_actions_pending, 개발 지시 v0.3.2(Oz)·docs/HAL-AI.md §7·VERSION_HISTORY 0.3.2 — test/hal-ai-032.test.mjs RED 0/6 → GREEN 6/6, 전체 27파일 189건 통과
2026-10-29 01:43:02 +00:00
/* 0.3.2 텍스트 셸·도구 정책 */
. ai-shell-out { background : #101826 ; color : #e6edf3 ; font-family : ui-monospace , Menlo , monospace ; font-size : 12 px ; padding : 10 px ; border-radius : 8 px ; min-height : 220 px ; max-height : 50 vh ; overflow : auto ; white-space : pre-wrap ; word-break : break-all }. ai-shell-head { margin : 6 px 0 }
. ai-tool-group { margin : 6 px 0 }. ai-tool-list { display : flex ; flex-wrap : wrap ; gap : 4 px 12 px ; margin : 4 px 0 }. ai-tool-list label { font-weight : 400 ; margin : 0 }
HAL 0.3.9: 로그인 터미널·텍스트 셸·모델 선택·슬래시 명령·점검 수정 — claude 실제 로그인 명령 auth login(대화형 세션·신뢰 설정도 허용, 작업 폴더 HOME), 텍스트 셸 범용화(순수 단말 에뮬레이터 term.mjs 로 TUI 렌더, 원시 키 전달·키 단추·붙여넣기·단말 크기), 유령 세션 한도 오류 수정(닫기=종료, 살아 있는 프로세스만 계산, 한도 3, 모두 종료·세션 목록), 모델 선택(확인된 목록·PATCH·/model), 슬래시 명령 20개(/help /status /order /deploy …, 원문 기록·확장 지시), 패널 로그인·재인증 단추, 개발 지시 v0.3.9(Oz)·docs/HAL-AI.md §8·VERSION_HISTORY 0.3.9 — test/hal-ai-039.test.mjs RED 0/6 → GREEN 6/6, 전체 28파일 195건 통과
2026-10-29 02:22:21 +00:00
/* 0.3.9 단말 */
. ai-term { background : #101826 ; color : #e6edf3 ; font-family : ui-monospace , Menlo , monospace ; font-size : 12 px ; line-height : 1.25 ; padding : 8 px ; border-radius : 8 px ; min-height : 240 px ; max-height : 55 vh ; overflow : auto ; white-space : pre ; outline : none ; margin : 0 }. ai-term : focus { box-shadow : 0 0 0 2 px var ( -- pri )}. term-cur { background : #e6edf3 ; color : #101826 }. ai-keys { gap : 4 px }. ai-model-pick { width : auto ; max-width : 220 px ; padding : 3 px 6 px ; font-size : 12 px }
2026-10-29 02:49:46 +00:00
/* 0.3.10 셸 복사·주소 */
. ai-term { user-select : text ; cursor : text }. ai-urls { list-style : none ; padding : 0 ; margin : 4 px 0 }. ai-urls li { display : flex ; gap : 6 px ; align-items : center ; flex-wrap : wrap ; margin : 3 px 0 }. ai-url { word-break : break-all ; background : #f7f9fc ; border : 1 px solid var ( -- line ); border-radius : 6 px ; padding : 2 px 6 px ; font-size : 12 px }. ai-shell-tools { margin : 6 px 0 }. ai-shell-textview { width : 100 % ; max-width : none ; font-family : ui-monospace , Menlo , monospace ; font-size : 12 px ; margin-top : 6 px }
HAL 0.3.17: HawkEye 이름 변경(observability → hawkeye, 별칭·마이그레이션), 역할별 통합 배포(server.provision: webmaker·webhosting·arimail·backup·portal 프로필, 순서 배포, 역할·프로필·슬롯 기록, 포털 싱글턴·포털 이전 portal.relocate, 슬롯 기반 배치), 화면 부분 갱신(dom-patch, 운영 현황·서버·작업·AI 기록·dock), 배포 릴리스 화면 재구성(HAL 배포 제외 hal_not_deployable, HAL 먼저 규칙 완화 ≥, 모듈별 최신 릴리스·설명·필요한 것·제공·설치 방법·현재 배치·상호 연결·프로필, 과거 버전 보기), 작업 진행 창(dock), agent 배포 "처리 대기" 원인 수정(빈 대상 → 역할 전체, 없으면 즉시 no_targets), 서버 라디오/체크 선택 목록, 개발 지시 v0.3.17(Oz)·VERSION_HISTORY 0.3.17 — test/hal-0317.test.mjs RED → GREEN 5/5, 전체 30파일 204건 통과
2026-10-29 03:32:46 +00:00
/* 0.3.17 작업 진행 창(dock)·서버 선택 목록·릴리스 카드 */
. ops-dock { margin-top : 18 px ; border : 1 px solid var ( -- line ); border-radius : var ( -- rad ); background : #fff }. ops-dock-head { display : flex ; align-items : center ; gap : 8 px ; padding : 6 px 10 px ; border-bottom : 1 px solid var ( -- line ); flex-wrap : wrap }. ops-dock-head . muted { margin-left : auto }. ops-dock-body { padding : 8 px 10 px ; max-height : 260 px ; overflow : auto }. ops-dock . closed . ops-dock-body { display : none }
. dock-job { display : flex ; gap : 8 px ; align-items : center ; flex-wrap : wrap ; padding : 5 px 0 ; border-bottom : 1 px solid var ( -- line )}. dock-job : last-child { border-bottom : 0 }. dock-bar { display : inline-block ; width : 120 px ; height : 8 px ; background : #eef1f5 ; border-radius : 4 px ; overflow : hidden ; vertical-align : middle }. dock-bar-in { display : block ; height : 100 % ; background : var ( -- pri )}
. choice-list { display : flex ; flex-direction : column ; gap : 4 px ; max-height : 280 px ; overflow : auto ; border : 1 px solid var ( -- line ); border-radius : 8 px ; padding : 6 px }. choice { display : flex ; gap : 8 px ; align-items : center ; font-weight : 400 ; margin : 0 ; padding : 4 px 6 px ; border-radius : 6 px }. choice : hover { background : #f7f9fc }. choice . off { opacity : .55 }
. rel-cards { grid-template-columns : repeat ( auto - fit , minmax ( 320 px , 1 fr ))}. rel-card h3 { margin-top : 0 }. rel-history { margin : 12 px 0 }