feat(ui): 콘솔 재디자인 적용 (Toss 스타일) + Pico v2 호환 수정
디자인 핸드오프(design_handoff_console_redesign)를 실제 앱에 반영.
CSS 드롭인 교체 + 최소 마크업 추가, HTMX 훅·id·data 속성은 모두 보존.
- app.css: 재디자인 토큰/컴포넌트로 교체(요약카드 호버 리프트, 표
제브라, 중앙 경고형 배포 모달, secondary/ghost/danger 버튼 체계,
진행률·상태칩·스켈레톤 등).
- base.html: 브랜드 로고 큐브(.brand-mark) 추가 + 데모 배너.
- repo_list: 요약 카드 .card-top/.card-ico + 검색 돋보기 아이콘.
- table_wrap: 회전형 정렬 인디케이터, 동기화 버튼 아이콘.
- progress: .sync-head/.sync-pct/.is-active/.indeterminate 구조.
- deploy_modal: 중앙 경고형 + 현재→대상 프리뷰 카드 + 타입-투-컨펌
체크/X 시각 상태.
- deploy_success: 그려지는 체크 원 배너.
- version_list: 표 → .version-item 카드 리스트.
Pico v2 호환(미리보기는 pico-shim 기준이라 안 드러났던 것):
- Pico 의 `:is(...).secondary`(0,2,0)·hover(0,4,0) 특정성을 이기도록
오버라이드를 :is() 형태로 상향 → 동기화 버튼 흰배경+파란글씨 유지,
hover 시 어두워지던 문제 해결.
- `input[type=search]` 의 Pico 기본 돋보기 배경 제거(아이콘 중복).
- `td` 배경을 투명 처리해 tr 제브라/hover 가 보이도록(Pico 의
`td{background:var(--pico-background-color)}` 가 덮던 문제), 줄무늬
색을 --bg-alt 로 또렷하게.
- .row-action 을 40px 고정 → 버튼↔진행률↔배지 교체 시 높이 흔들림·
위아래 테두리 잘림 해소.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
237
design_handoff_console_redesign/README.md
Normal file
237
design_handoff_console_redesign/README.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Handoff: 폐쇄망 Pulp 운영 콘솔 — 시각 디자인 개선
|
||||
|
||||
## Overview
|
||||
한국은행 IT전략부 클라우드팀의 폐쇄망(인터넷 차단) Pulp 3 관리 웹 UI의 **시각 디자인 개선**입니다.
|
||||
운영자가 저장소를 동기화 → 버전 스냅샷 확인 → 검증 → 운영 배포하는 도구로, "관공서 기본 폼"
|
||||
느낌을 벗고 Toss 스타일의 신뢰감 있고 정돈된 운영 도구로 끌어올리는 것이 목표입니다.
|
||||
|
||||
기능/스택 변경 없이 **CSS(토큰 + 컴포넌트) + 최소한의 마크업 추가**만으로 개선했습니다.
|
||||
|
||||
## About the Design Files
|
||||
이 번들의 파일은 **HTML로 만든 디자인 레퍼런스**입니다. 다만 이 프로젝트는 특수하게도
|
||||
**타깃 환경이 이미 정해져 있고, 산출물이 그 환경에 그대로 들어가는 CSS**입니다:
|
||||
|
||||
- `app.css` — **실제 산출물.** 기존 앱의 `app/static/app.css`를 이 파일로 교체하면 됩니다.
|
||||
(Pico.css v2 뒤에 로드되어 변수를 오버라이드하는 커스텀 스타일. 순수 CSS, 외부 의존 0.)
|
||||
- `preview.html` — **렌더링 참고용.** 개선안이 실제로 어떻게 보이는지 확인하는 하니스입니다.
|
||||
앱에 넣지 마세요.
|
||||
- `pico-shim.css` — **미리보기 전용, 앱에 넣지 마세요.** 폐쇄망에서 CDN 없이 preview.html을
|
||||
렌더링하려고 Pico의 최소 기본 스타일만 흉내 낸 파일입니다. 실제 앱은 진짜 Pico.css v2를 씁니다.
|
||||
|
||||
**작업의 본질:** `app.css`를 교체하고, 아래 "컴포넌트별 변경 + 필요 마크업"에 적힌 소량의
|
||||
템플릿 마크업(Jinja2)을 추가/조정하는 것입니다. 프레임워크를 바꾸지 않습니다.
|
||||
|
||||
## Fidelity
|
||||
**High-fidelity.** 최종 색/타이포/간격/인터랙션이 확정된 목업입니다. 아래 값 그대로 구현하세요.
|
||||
|
||||
## 절대 지켜야 할 기술 제약
|
||||
1. **빌드 단계 없음.** React/Vue/Vite/npm/Tailwind 금지. 순수 HTML + HTMX + Jinja2 +
|
||||
Pico.css v2 + 커스텀 CSS(`app.css`)만.
|
||||
2. **폐쇄망.** 런타임에 외부 네트워크(구글폰트/CDN/원격 아이콘/fetch) 참조 금지. 모든 자원 로컬 동봉.
|
||||
새 `@import`(원격)/CDN `<link>`/`<script>` 추가 금지. **아이콘은 인라인 SVG.**
|
||||
3. **폰트는 이미 로컬 동봉:** 본문 `Pretendard Variable`, 코드/숫자 `JetBrains Mono`
|
||||
(`@font-face`로 `/static/fonts/*.woff2` 로드). 새 폰트 추가 금지. (`app.css`에 @font-face 포함됨)
|
||||
4. **크롬에 장식용 이모지 금지.** 상태는 색 있는 chip/dot으로.
|
||||
5. 뷰는 HTML 조각(fragment)을 반환, HTMX가 swap. 라이트 모드 기본, 다크는 토글 옵션.
|
||||
|
||||
### 보존해야 하는 훅 (바꾸면 기능 깨짐)
|
||||
- 모든 HTMX 속성: `hx-get/hx-post/hx-target/hx-swap/hx-trigger/hx-include/hx-vals/hx-disabled-elt`
|
||||
(값·타깃 셀렉터 유지).
|
||||
- element id: `#repo-list`, `#repo-table-wrap`, `#repo-search`, `#modal`, `#flash`,
|
||||
`#sync-{uuid}`, `#deploy-confirm-btn`.
|
||||
- data 속성: `data-state`(정렬), `data-confirm`(타입-투-컨펌), `data-theme`(테마).
|
||||
- 배포 확정 버튼의 `oninput` 비활성화 로직, 테마 토글/FOUC 방지 인라인 스크립트.
|
||||
- 클래스명을 리네임하려면 CSS와 템플릿 **양쪽 다** 맞춰 바꿀 것.
|
||||
|
||||
> 주: `preview.html`의 검색 input은 데모라 id가 없습니다. 실제 앱에서는 `#repo-search`를
|
||||
> 그대로 두고 `.repo-search` 클래스만 유지하세요.
|
||||
|
||||
---
|
||||
|
||||
## 컴포넌트별 변경 + 필요 마크업
|
||||
대부분은 기존 마크업 그대로 두고 CSS만 바뀌면 됩니다. 아래는 **새 요소/구조가 필요한 부분**만
|
||||
정리했습니다. 각 스니펫은 `preview.html`에서 실제로 렌더되는 형태입니다.
|
||||
|
||||
### 0. 상단 내비 브랜드 마크 (선택)
|
||||
차분한 단색 사각 로고 큐브. 인라인 SVG 없이 CSS만으로.
|
||||
```html
|
||||
<p class="brand"><span class="brand-mark">P</span>Pulp <span class="accent">Console</span></p>
|
||||
```
|
||||
|
||||
### 1. 요약 카드 (대시보드)
|
||||
카드 상단에 **라벨 + 아이콘 칩** 한 줄(`.card-top`)을 추가. 값은 그대로 `.value`.
|
||||
`.is-accent`(전체 저장소=파랑), `.is-success`(GPG 통과=민트)로 강조.
|
||||
```html
|
||||
<div class="card is-accent">
|
||||
<div class="card-top">
|
||||
<span class="label">전체 저장소</span>
|
||||
<span class="card-ico"><svg class="icon" viewBox="0 0 24 24" ...>…</svg></span>
|
||||
</div>
|
||||
<div class="value is-accent">18</div>
|
||||
</div>
|
||||
```
|
||||
호버 시 살짝 떠오름(translateY -2px)+그림자. (reduced-motion에서 자동 비활성)
|
||||
|
||||
### 2. 검색 툴바
|
||||
`.list-toolbar`를 relative 래퍼로 두고 왼쪽에 돋보기 인라인 SVG(`.search-ico`).
|
||||
input(`#repo-search`, `.repo-search`)의 왼쪽 패딩을 아이콘 자리만큼(40px) 확보(→ CSS가 처리).
|
||||
```html
|
||||
<div class="list-toolbar">
|
||||
<svg class="search-ico" viewBox="0 0 24 24" ...><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<input id="repo-search" class="repo-search" type="search" placeholder="저장소 이름으로 검색…"
|
||||
hx-get="..." hx-target="#repo-table-wrap" hx-trigger="input changed delay:300ms" ...>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 3. 저장소 표
|
||||
- 카드로 감싸기(`.table-card`) — 기존 유지.
|
||||
- 확정안 = **제브라 스트라이프**: 짝수 행에 옅은 톤(CSS `tbody tr:nth-child(even)`), hover는 틴티드 블루.
|
||||
- 정렬 헤더: `a.th-sort`(활성 시 `.is-active`), 방향 인디케이터 `.sort-ind`(desc면 `.th-sort.is-active.desc`가 화살표 회전).
|
||||
- 검증 배지: `.badge.badge-pass|warn|unset|deployed`(dot + 라벨, 이모지 없음).
|
||||
- 저장소명 셀: `.repo-name > a`(모노, hover 시 accent).
|
||||
- 액션 셀: 폭·높이 고정 박스 `.row-action`(버튼↔진행률↔배지로 내용이 바뀌어도 레이아웃 안 흔들림).
|
||||
```html
|
||||
<td><div class="row-action"> …버튼/진행률/배지… </div></td>
|
||||
```
|
||||
|
||||
### 4. 버튼 색 체계 (중요)
|
||||
- **동기화(반복 노출)** = `.secondary` → **흰 배경 + 파란 글씨 + 옅은 파란 테두리.** 표에 여러 개 떠도 안 번잡.
|
||||
- **새로고침 등 유틸리티** = `.ghost` → 중립(투명 배경 + 회색 테두리). 동기화와 확실히 구분.
|
||||
- **위험(배포 확정)** = `.danger` → 빨강 채움 + 흰 글씨.
|
||||
- 공통: press 시 `scale(0.97)`, hover 시 옅은 그림자.
|
||||
- **로딩 상태**: 버튼에 `.is-loading` 클래스를 붙이면 텍스트가 스피너로 대체됨. HTMX 요청 시
|
||||
`hx-disabled-elt`와 함께 `.is-loading`을 토글하면 자연스럽다.
|
||||
```html
|
||||
<button class="secondary btn-sm btn-icon" hx-post="/sync/{{ repo.id }}"
|
||||
hx-target="#sync-{{ repo.id }}" hx-disabled-elt="this">
|
||||
<svg class="icon" ...>…</svg> 동기화
|
||||
</button>
|
||||
```
|
||||
|
||||
### 5. 동기화 진행률
|
||||
```html
|
||||
<div class="sync-progress is-active">
|
||||
<div class="sync-head"><small>동기화 중</small><small class="sync-pct">64%</small></div>
|
||||
<progress value="64" max="100"></progress>
|
||||
</div>
|
||||
```
|
||||
값을 모르는 초기 단계엔 `.sync-progress.indeterminate`(움직이는 인디터미닛 바)를 쓸 수 있음.
|
||||
|
||||
### 6. 배포 확인 모달 (`<dialog id="modal">`) — 위험 강조 + 타입-투-컨펌
|
||||
확정안 = **중앙 경고형.** 헤더 밴드 대신 상단 중앙에 원형 경고 아이콘.
|
||||
```html
|
||||
<dialog id="modal">
|
||||
<article class="deploy-modal">
|
||||
<div class="modal-head">
|
||||
<span class="warn-ico"><svg viewBox="0 0 24 24" ...>△!</svg></span>
|
||||
<div><h3>운영 배포 확인</h3><span class="head-sub">되돌리기 어려운 작업입니다</span></div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="deploy-note" style="text-align:center"><span class="repo-hl">rhel8-baseos</span> 저장소를 운영에 배포합니다.</p>
|
||||
|
||||
<!-- 현재 → 대상 카드형 프리뷰 -->
|
||||
<div class="deploy-preview">
|
||||
<div class="pv-box"><div class="pv-label">현재 (운영)</div><div class="pv-ver">v140</div><div class="pv-pkg">9,001 pkgs</div></div>
|
||||
<div class="pv-arrow"><svg ...>→</svg></div>
|
||||
<div class="pv-box pv-target"><div class="pv-label">대상</div><div class="pv-ver">v142</div><div class="pv-pkg">9,204 pkgs · <span class="delta-add">+203</span></div></div>
|
||||
</div>
|
||||
|
||||
<!-- 타입-투-컨펌: 이름이 정확히 일치해야 확정 버튼 열림 -->
|
||||
<label class="confirm-label">확인을 위해 저장소 이름 <span class="repo-hl">rhel8-baseos</span> 을 입력하세요.
|
||||
<div class="confirm-field" data-confirm="rhel8-baseos">
|
||||
<input type="text" autocomplete="off" placeholder="rhel8-baseos"
|
||||
oninput="document.getElementById('deploy-confirm-btn').disabled = (this.value.trim() !== this.closest('.confirm-field').dataset.confirm)">
|
||||
<svg class="confirm-state ok" ...>✓</svg>
|
||||
<svg class="confirm-state bad" ...>✕</svg>
|
||||
</div>
|
||||
</label>
|
||||
<!-- 힌트 문구 없음: 상태는 초록 체크/초록 테두리(일치) · 빨강 X/빨강 테두리(불일치)로만 표시 -->
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="ghost" onclick="this.closest('dialog').close()">취소</button>
|
||||
<button class="danger" id="deploy-confirm-btn" disabled>운영에 배포</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</dialog>
|
||||
```
|
||||
입력 상태 시각화는 **`.confirm-field`에 `.is-valid` / `.is-invalid` 클래스**로 제어됩니다.
|
||||
- 순수 오조작 방지만 필요하면 위 `oninput`처럼 `#deploy-confirm-btn.disabled`만 토글해도 됨.
|
||||
- 체크/X 아이콘·테두리 색까지 원하면 값 일치 여부에 따라 `.confirm-field`에
|
||||
`is-valid`/`is-invalid`도 함께 토글(입력이 비면 둘 다 제거).
|
||||
- 열릴 때 배경 스크롤 잠금은 CSS `html:has(dialog[open]){overflow:hidden}` + 진입 애니메이션을
|
||||
translate 없이 scale/opacity로만 처리해 **순간 스크롤바 깜빡임이 없음.**
|
||||
|
||||
### 7. 버전 목록 (저장소 상세 — 저장소명 클릭 시 화면)
|
||||
```html
|
||||
<a href="{{ url_for('dashboard') }}" role="button" class="ghost btn-sm btn-icon back-btn">
|
||||
<svg class="icon" ...>←</svg> 대시보드
|
||||
</a>
|
||||
<div class="verify-detail"><span class="badge badge-pass">GPG 통과</span><small>서명 검증 통과 · … · 키 0x3A9F…</small></div>
|
||||
<div class="version-list">
|
||||
<div class="version-item">
|
||||
<span class="ver-no">v142</span>
|
||||
<span class="badge badge-deployed">운영 중</span>
|
||||
<span class="ver-meta">2026-07-09 04:12 · 9,204 pkgs</span>
|
||||
<span class="spacer"></span>
|
||||
<button class="ghost btn-sm" disabled>현재 운영 버전</button>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="ver-no">v141</span>
|
||||
<span class="ver-meta">… · <span class="delta-add">+40</span> <span class="delta-del">−16</span></span>
|
||||
<span class="spacer"></span>
|
||||
<button class="secondary btn-sm" hx-get="/deploy_modal/{{ repo }}/{{ v }}" hx-target="#modal">이 버전 배포</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 8. 상태 조각들
|
||||
- **배포 완료 배너 `#flash`** (`.flash-banner`): 성공 체크 원(`.flash-check`, 그려지는 SVG 체크) +
|
||||
본문 + `.flash-sub` + `.flash-warn`(되돌리기 안내). 등장 시 페이드-업.
|
||||
- **데모 배너** (`.demo-banner`): 가짜 데이터 경고, 앰버 톤, 펄스 dot.
|
||||
- **연결 상태 칩** (`.status-line.status-ok|bad|loading|demo`): dot + 텍스트. ok는 펄스, loading은 blink.
|
||||
- **빈 상태** (`.empty-note` + `.empty-ico`/`.empty-title`/`.empty-sub`).
|
||||
- **에러 상태** (`.error-note`): 경고 아이콘 + 메시지, danger 톤.
|
||||
- **스켈레톤** (`.skeleton.skeleton-row`): 셔머 로딩. 표 초기/재조회 자리표시.
|
||||
- HTMX 스왑 중 페이드: `.htmx-swapping{opacity:.4}` 자동 적용(이미 CSS에 포함).
|
||||
|
||||
---
|
||||
|
||||
## Design Tokens (라이트 / `:root`)
|
||||
색은 `app.css`의 `:root`(라이트)와 `[data-theme="dark"]`(다크) 두 곳에서 정의되고, 컴포넌트는
|
||||
전부 이 변수를 참조하므로 **토큰만 손보면 라이트/다크가 함께 따라옵니다.**
|
||||
|
||||
- 배경: `--bg #ffffff` · `--bg-alt #f9fafb` · `--surface #f2f4f6` · `--surface-2 #e8f3ff`(틴티드 블루)
|
||||
- 텍스트: `--text #191f28` · `--text-secondary #333d4b` · `--text-muted #6b7684` · `--text-dim #8b95a1`
|
||||
- 테두리: `--border #e5e8eb` · `--border-strong #d1d6db`
|
||||
- 액센트(Toss Blue를 살짝 톤다운): `--accent oklch(0.63 0.147 255)` · `--accent-hover oklch(0.55 0.16 255)`
|
||||
· `--accent-soft #e8f3ff`
|
||||
- 상태: `--success-text #00a37a` · `--warning-text #c5700a` · `--danger #f04452` (+ `*-soft` 반투명 배경)
|
||||
- 라운드: 카드 `--radius-card 12px`, 버튼 8px(`--pico-border-radius`)
|
||||
- 그림자: `--shadow-card`(얇게) · `--shadow-card-hover` · `--shadow-modal`(무겁게)
|
||||
- 모션: `--ease cubic-bezier(0.32,0.72,0,1)` · `--dur-fast .12s` · `--dur .2s` · `--dur-slow .32s`
|
||||
- 타이포: 본문 `Pretendard Variable`, 숫자/버전/시각/저장소명은 `JetBrains Mono`(+ `tnum` tabular).
|
||||
헤더 th는 한글 라벨이라 모노 제외.
|
||||
|
||||
**모든 애니메이션은 `prefers-reduced-motion: reduce`에서 자동 비활성**(파일 맨 끝 미디어쿼리).
|
||||
|
||||
## Interactions & Behavior
|
||||
- 버튼: hover 그림자, active `scale(0.97)`. `.is-loading`으로 스피너 교체.
|
||||
- 표: 행 hover 배경 전환, 정렬 화살표 회전, 검증 dot(pass)·연결 dot(ok) 은은한 펄스.
|
||||
- 진행률: 결정형 `<progress>` width 트랜지션, 초기엔 인디터미닛 바.
|
||||
- 모달: 배경 페이드 + 본문 scale/opacity 진입, 배경 스크롤 잠금(스크롤바 깜빡임 방지),
|
||||
타입-투-컨펌(이름 일치 시에만 확정 활성 + 초록 체크 / 불일치 시 빨강 X).
|
||||
- 성공 배너: 체크 SVG stroke-dash 그리기 애니메이션.
|
||||
|
||||
## 적용 순서 (Claude Code에게)
|
||||
1. 기존 `app/static/app.css`를 이 번들의 `app.css`로 교체.
|
||||
2. 위 "컴포넌트별 변경 + 필요 마크업"에 따라 각 Jinja2 템플릿에 새 요소(.card-top/.card-ico,
|
||||
.search-ico, .modal-head/.warn-ico/.pv-box/.confirm-state 등)를 추가. **HTMX 훅·id·data 속성은 그대로.**
|
||||
3. `preview.html`을 브라우저로 열어 렌더 기준과 대조(라이트/다크 토글).
|
||||
4. `PULP_DEMO=true`로 데모 데이터 띄워 표/검색/정렬/모달 실동작 확인.
|
||||
5. 회귀 확인: `pytest -q`(현행 58 통과), `ruff check app`.
|
||||
|
||||
## Files
|
||||
- `app.css` — 실제 산출물 (drop-in).
|
||||
- `preview.html` — 렌더 참고 하니스 (앱에 넣지 말 것).
|
||||
- `pico-shim.css` — 미리보기 전용 Pico 최소 흉내 (앱에 넣지 말 것).
|
||||
1234
design_handoff_console_redesign/app.css
Normal file
1234
design_handoff_console_redesign/app.css
Normal file
File diff suppressed because it is too large
Load Diff
142
design_handoff_console_redesign/pico-shim.css
Normal file
142
design_handoff_console_redesign/pico-shim.css
Normal file
@@ -0,0 +1,142 @@
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
pico-shim.css — 미리보기 전용 (배포 대상 아님)
|
||||
실제 앱은 Pico.css v2를 로드합니다. 폐쇄망 미리보기에서
|
||||
외부 CDN 없이 화면을 재현하려고, app.css가 참조하는 --pico-*
|
||||
변수 위에서 동작하는 최소 기본 스타일만 담았습니다.
|
||||
───────────────────────────────────────────────────────────── */
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--pico-font-family, system-ui, sans-serif);
|
||||
font-size: 16px;
|
||||
line-height: var(--pico-line-height, 1.5);
|
||||
background: var(--pico-background-color);
|
||||
color: var(--pico-color);
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
margin-inline: auto;
|
||||
padding-inline: 20px;
|
||||
}
|
||||
h1, h2, h3, h4 {
|
||||
margin: 0 0 0.4em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
h1 { color: var(--pico-h1-color); }
|
||||
h2 { color: var(--pico-h2-color); }
|
||||
h3 { color: var(--pico-h3-color); }
|
||||
p { margin: 0 0 1rem; }
|
||||
a { color: var(--accent); }
|
||||
small { font-size: 0.8125em; }
|
||||
|
||||
/* 폼 요소 */
|
||||
input, button, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
}
|
||||
input[type="search"],
|
||||
input[type="text"],
|
||||
input:not([type]) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: calc(var(--pico-form-element-spacing-vertical, 0.75rem)) 0.875rem;
|
||||
background: var(--pico-form-element-background-color, var(--surface));
|
||||
color: var(--pico-form-element-color, var(--text));
|
||||
border: 1px solid var(--pico-form-element-border-color, var(--border));
|
||||
border-radius: var(--pico-border-radius, 7px);
|
||||
outline: none;
|
||||
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
||||
}
|
||||
input::placeholder { color: var(--text-dim); }
|
||||
input:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
|
||||
}
|
||||
|
||||
/* 버튼 기본 (app.css가 세부를 오버라이드).
|
||||
버튼 요소에서 --pico-background-color 등을 primary 로 "리셋"해 두면,
|
||||
app.css 의 .secondary/.ghost/.danger 가 이 변수를 덮어써서 실제 Pico처럼 동작. */
|
||||
button,
|
||||
[role="button"] {
|
||||
display: inline-block;
|
||||
padding: 11px 18px;
|
||||
--pico-background-color: var(--pico-primary-background, var(--accent));
|
||||
--pico-border-color: var(--pico-primary-border, var(--accent));
|
||||
--pico-color: var(--pico-primary, #fff);
|
||||
background: var(--pico-background-color);
|
||||
color: var(--pico-color);
|
||||
border: 1px solid var(--pico-border-color);
|
||||
border-radius: var(--pico-border-radius, 7px);
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
button:hover,
|
||||
[role="button"]:hover {
|
||||
--pico-background-color: var(--pico-primary-hover-background, var(--accent-hover));
|
||||
--pico-border-color: var(--pico-primary-hover-border, var(--accent-hover));
|
||||
}
|
||||
|
||||
/* 표 */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
text-align: left;
|
||||
}
|
||||
th, td {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
/* 진행바 */
|
||||
progress {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: var(--pico-progress-background-color, var(--surface));
|
||||
overflow: hidden;
|
||||
}
|
||||
progress::-webkit-progress-bar {
|
||||
background: var(--pico-progress-background-color, var(--surface));
|
||||
border-radius: 999px;
|
||||
}
|
||||
progress::-webkit-progress-value {
|
||||
background: var(--accent);
|
||||
border-radius: 999px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
progress::-moz-progress-bar {
|
||||
background: var(--accent);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
/* 카드/아티클 */
|
||||
article {
|
||||
background: var(--pico-card-background-color, var(--bg));
|
||||
border: 1px solid var(--pico-card-border-color, var(--border));
|
||||
border-radius: var(--pico-border-radius, 7px);
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
/* 다이얼로그 */
|
||||
dialog {
|
||||
margin: auto;
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: var(--pico-color);
|
||||
max-width: 90vw;
|
||||
}
|
||||
dialog::backdrop {
|
||||
background: rgba(15, 20, 30, 0.45);
|
||||
}
|
||||
425
design_handoff_console_redesign/preview.html
Normal file
425
design_handoff_console_redesign/preview.html
Normal file
@@ -0,0 +1,425 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>폐쇄망 콘솔 — 재디자인 미리보기</title>
|
||||
<!-- FOUC 방지 + 테마 복원 (실제 base.html 의 인라인 스크립트와 동일 동작) -->
|
||||
<script>
|
||||
(function () {
|
||||
var t = localStorage.getItem("theme");
|
||||
if (t) document.documentElement.setAttribute("data-theme", t);
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="pico-shim.css" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<style>
|
||||
/* 미리보기 하니스 전용 (배포 CSS 아님) */
|
||||
.demo-note {
|
||||
max-width: 1040px;
|
||||
margin: 28px auto 0;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.demo-note .callout {
|
||||
background: var(--surface-2);
|
||||
color: var(--accent-hover);
|
||||
border-radius: 12px;
|
||||
padding: 14px 18px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.state-grid { display: grid; gap: 18px; }
|
||||
hr.sep { border: none; border-top: 1px solid var(--border); margin: 0 0 28px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 데모 배너 -->
|
||||
<div class="demo-banner">
|
||||
<span class="demo-dot"></span>
|
||||
<span><strong>데모 모드</strong> · 가짜 저장소 데이터입니다. 실제 Pulp 서버와 연동되어 있지 않습니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- 상단 내비 -->
|
||||
<nav class="app-nav">
|
||||
<div class="container">
|
||||
<p class="brand"><span class="brand-mark">P</span>Pulp <span class="accent">Console</span></p>
|
||||
<button class="theme-toggle" id="theme-toggle" aria-label="테마 전환">
|
||||
<svg class="icon moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||||
<svg class="icon sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="demo-note">
|
||||
<div class="callout">
|
||||
<b>재디자인 미리보기 (확정안).</b> 요약 카드 = 화이트+호버 리프트, 저장소 표 = 제브라, 배포 모달 = 중앙 경고형.
|
||||
동기화 버튼은 흰 배경·파란 글씨로, 새로고침(중립 아웃라인)과 구분됩니다. 우상단 아이콘으로 라이트·다크를 전환해 보세요.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="container">
|
||||
|
||||
<!-- ═══════════ 대시보드 ═══════════ -->
|
||||
<section>
|
||||
<div class="status-row" style="margin-bottom:24px">
|
||||
<span class="status-label">연결 상태</span>
|
||||
<span class="status-line status-ok">Pulp 서버 연결됨</span>
|
||||
<span class="status-line status-demo">데모 데이터</span>
|
||||
</div>
|
||||
|
||||
<div class="summary">
|
||||
<div class="card is-accent">
|
||||
<div class="card-top">
|
||||
<span class="label">전체 저장소</span>
|
||||
<span class="card-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M4 12h16M4 17h16"/></svg></span>
|
||||
</div>
|
||||
<div class="value is-accent">18</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<span class="label">동기화 중</span>
|
||||
<span class="card-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/></svg></span>
|
||||
</div>
|
||||
<div class="value">2</div>
|
||||
</div>
|
||||
<div class="card is-success">
|
||||
<div class="card-top">
|
||||
<span class="label">GPG 통과</span>
|
||||
<span class="card-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 4 5v6c0 5 3.4 8.5 8 10 4.6-1.5 8-5 8-10V5z"/><path d="m9 12 2 2 4-4"/></svg></span>
|
||||
</div>
|
||||
<div class="value is-success">16</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<span class="label">총 패키지</span>
|
||||
<span class="card-ico"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="m3.3 7 8.7 5 8.7-5M12 22V12"/></svg></span>
|
||||
</div>
|
||||
<div class="value">48,120</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-head">
|
||||
<h2>저장소</h2>
|
||||
<button class="ghost btn-sm btn-icon" id="refresh-btn">
|
||||
<svg class="icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/></svg>
|
||||
새로고침
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<svg class="search-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<input class="repo-search" type="search" placeholder="저장소 이름으로 검색…" />
|
||||
</div>
|
||||
|
||||
<div class="table-card" id="repo-table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a class="th-sort is-active" href="#">저장소 <span class="sort-ind">▲</span></a></th>
|
||||
<th>검증</th>
|
||||
<th><a class="th-sort" href="#">최신 버전</a></th>
|
||||
<th class="num"><a class="th-sort" href="#">패키지</a></th>
|
||||
<th><a class="th-sort" href="#">마지막 동기화</a></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="repo-list">
|
||||
<tr>
|
||||
<td class="repo-name"><a href="#versions">rhel8-baseos</a></td>
|
||||
<td><span class="badge badge-pass">GPG 통과</span></td>
|
||||
<td class="mono">v142</td>
|
||||
<td class="num">9,204</td>
|
||||
<td class="mono">2026-07-09 04:12</td>
|
||||
<td>
|
||||
<div class="row-action">
|
||||
<button class="secondary btn-sm btn-icon" onclick="document.getElementById('modal').showModal()">
|
||||
<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/></svg>
|
||||
동기화
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="repo-name"><a href="#versions">rhel8-appstream</a></td>
|
||||
<td><span class="badge badge-pass">GPG 통과</span></td>
|
||||
<td class="mono">v98</td>
|
||||
<td class="num">12,880</td>
|
||||
<td class="mono">동기화 중…</td>
|
||||
<td>
|
||||
<div class="row-action">
|
||||
<div class="sync-progress is-active">
|
||||
<div class="sync-head">
|
||||
<small>동기화 중</small>
|
||||
<small class="sync-pct">64%</small>
|
||||
</div>
|
||||
<progress value="64" max="100"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="repo-name"><a href="#versions">ubuntu2204-main</a></td>
|
||||
<td><span class="badge badge-warn">서명 확인 필요</span></td>
|
||||
<td class="mono">v51</td>
|
||||
<td class="num">6,412</td>
|
||||
<td class="mono">2026-07-08 23:40</td>
|
||||
<td>
|
||||
<div class="row-action">
|
||||
<button class="secondary btn-sm btn-icon" onclick="document.getElementById('modal').showModal()">
|
||||
<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/></svg>
|
||||
동기화
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="repo-name"><a href="#versions">epel8-extras</a></td>
|
||||
<td><span class="badge badge-deployed">운영 배포됨</span></td>
|
||||
<td class="mono">v220</td>
|
||||
<td class="num">3,051</td>
|
||||
<td class="mono">2026-07-09 01:05</td>
|
||||
<td>
|
||||
<div class="row-action">
|
||||
<button class="secondary btn-sm btn-icon" onclick="document.getElementById('modal').showModal()">
|
||||
<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/></svg>
|
||||
동기화
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="repo-name"><a href="#versions">centos-stream9</a></td>
|
||||
<td><span class="badge badge-unset">미검증</span></td>
|
||||
<td class="mono">v7</td>
|
||||
<td class="num">1,120</td>
|
||||
<td class="mono">2026-07-07 18:22</td>
|
||||
<td>
|
||||
<div class="row-action">
|
||||
<button class="secondary btn-sm btn-icon" onclick="document.getElementById('modal').showModal()">
|
||||
<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/></svg>
|
||||
동기화
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pager">
|
||||
<button class="ghost btn-sm" disabled>이전</button>
|
||||
<span class="mono">1–5 / 18</span>
|
||||
<button class="ghost btn-sm">다음</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="sep" />
|
||||
|
||||
<!-- ═══════════ 상태 · 인터랙션 데모 ═══════════ -->
|
||||
<section>
|
||||
<div class="section-head"><h2>상태 · 인터랙션 데모</h2></div>
|
||||
<div class="state-grid">
|
||||
<div class="flash-banner">
|
||||
<span class="flash-check"><svg viewBox="0 0 24 24"><path d="m5 13 4 4L19 7"/></svg></span>
|
||||
<span>운영 배포 완료 · <span class="mono">rhel8-baseos v142</span></span>
|
||||
<span class="flash-sub">3,204개 패키지 반영</span>
|
||||
<span class="flash-warn">되돌리기: 이전 버전 재배포</span>
|
||||
</div>
|
||||
|
||||
<div class="table-card" style="padding:20px">
|
||||
<div style="display:flex;align-items:center;gap:16px;flex-wrap:wrap">
|
||||
<button class="secondary" id="sync-demo-btn">동기화 시작 (진행 애니메이션)</button>
|
||||
<div class="sync-progress" id="sync-demo" style="width:220px;display:none">
|
||||
<div class="sync-head"><small id="sync-demo-label">준비 중</small><small class="sync-pct" id="sync-demo-pct">0%</small></div>
|
||||
<progress id="sync-demo-bar" value="0" max="100"></progress>
|
||||
</div>
|
||||
<button class="secondary" style="width:auto" id="loading-demo">로딩 상태</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<div class="empty-note">
|
||||
<span class="empty-ico"><svg class="icon" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg></span>
|
||||
<div class="empty-title">검색 결과가 없습니다</div>
|
||||
<div class="empty-sub"><span class="mono">"nginx"</span> 와 일치하는 저장소가 없어요. 다른 키워드로 검색해 보세요.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-note">
|
||||
<svg class="icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/></svg>
|
||||
<span>Pulp 서버에 연결할 수 없습니다. 네트워크 및 서비스 상태를 확인하세요.</span>
|
||||
</div>
|
||||
|
||||
<div class="table-card" style="padding:18px">
|
||||
<div style="display:flex;flex-direction:column;gap:14px">
|
||||
<div class="skeleton skeleton-row" style="width:40%"></div>
|
||||
<div class="skeleton skeleton-row" style="width:70%"></div>
|
||||
<div class="skeleton skeleton-row" style="width:55%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="sep" />
|
||||
|
||||
<!-- ═══════════ 버전 목록 (저장소 상세 — 저장소 이름 클릭 시 화면) ═══════════ -->
|
||||
<section id="versions">
|
||||
<a href="#" role="button" class="ghost btn-sm btn-icon back-btn">
|
||||
<svg class="icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||||
대시보드
|
||||
</a>
|
||||
<div class="section-head">
|
||||
<h2 class="mono">rhel8-baseos</h2>
|
||||
<button class="secondary btn-sm btn-icon" onclick="document.getElementById('modal').showModal()">
|
||||
<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/></svg>
|
||||
동기화
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="verify-detail">
|
||||
<span class="badge badge-pass">GPG 통과</span>
|
||||
<small>서명 검증 통과 · 2026-07-09 04:12 · 키 0x3A9F…C21</small>
|
||||
</div>
|
||||
|
||||
<div class="version-list">
|
||||
<div class="version-item">
|
||||
<span class="ver-no">v142</span>
|
||||
<span class="badge badge-deployed">운영 중</span>
|
||||
<span class="ver-meta">2026-07-09 04:12 · 9,204 pkgs</span>
|
||||
<span class="spacer"></span>
|
||||
<button class="ghost btn-sm" disabled>현재 운영 버전</button>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="ver-no">v141</span>
|
||||
<span class="ver-meta">2026-07-08 22:03 · 9,180 pkgs · <span class="delta-add">+40</span> <span class="delta-del">−16</span></span>
|
||||
<span class="spacer"></span>
|
||||
<button class="secondary btn-sm" onclick="document.getElementById('modal').showModal()">이 버전 배포</button>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="ver-no">v140</span>
|
||||
<span class="ver-meta">2026-07-08 09:15 · 9,001 pkgs · <span class="delta-add">+112</span></span>
|
||||
<span class="spacer"></span>
|
||||
<button class="secondary btn-sm" onclick="document.getElementById('modal').showModal()">이 버전 배포</button>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="ver-no">v139</span>
|
||||
<span class="ver-meta">2026-07-07 20:41 · 8,889 pkgs</span>
|
||||
<span class="spacer"></span>
|
||||
<button class="secondary btn-sm" onclick="document.getElementById('modal').showModal()">이 버전 배포</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- ═══════════ 배포 확인 모달 (중앙 경고형) ═══════════ -->
|
||||
<dialog id="modal">
|
||||
<article class="deploy-modal">
|
||||
<div class="modal-head">
|
||||
<span class="warn-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/></svg></span>
|
||||
<div>
|
||||
<h3>운영 배포 확인</h3>
|
||||
<span class="head-sub">되돌리기 어려운 작업입니다</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="deploy-note" style="text-align:center"><span class="repo-hl">rhel8-baseos</span> 저장소를 운영에 배포합니다.</p>
|
||||
<div class="deploy-preview">
|
||||
<div class="pv-box">
|
||||
<div class="pv-label">현재 (운영)</div>
|
||||
<div class="pv-ver">v140</div>
|
||||
<div class="pv-pkg">9,001 pkgs</div>
|
||||
</div>
|
||||
<div class="pv-arrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></div>
|
||||
<div class="pv-box pv-target">
|
||||
<div class="pv-label">대상</div>
|
||||
<div class="pv-ver">v142</div>
|
||||
<div class="pv-pkg">9,204 pkgs · <span class="delta-add">+203</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="confirm-label">확인을 위해 저장소 이름 <span class="repo-hl">rhel8-baseos</span> 을 입력하세요.
|
||||
<div class="confirm-field" data-confirm="rhel8-baseos">
|
||||
<input type="text" autocomplete="off" placeholder="rhel8-baseos" />
|
||||
<svg class="confirm-state ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m5 13 4 4L19 7"/></svg>
|
||||
<svg class="confirm-state bad" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||||
</div>
|
||||
</label>
|
||||
<div class="modal-actions">
|
||||
<button class="ghost" onclick="this.closest('dialog').close()">취소</button>
|
||||
<button class="danger" id="deploy-confirm-btn" disabled>운영에 배포</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
// 테마 토글 (실제 base.html 동작과 동일)
|
||||
document.getElementById("theme-toggle").addEventListener("click", function () {
|
||||
var el = document.documentElement;
|
||||
var next = el.getAttribute("data-theme") === "dark" ? "light" : "dark";
|
||||
el.setAttribute("data-theme", next);
|
||||
localStorage.setItem("theme", next);
|
||||
});
|
||||
|
||||
// 타입-투-컨펌: 입력값이 data-confirm 과 일치하면 확정 버튼 활성 + 시각 상태
|
||||
(function () {
|
||||
var field = document.querySelector("#modal .confirm-field");
|
||||
var input = field.querySelector("input");
|
||||
var target = field.getAttribute("data-confirm");
|
||||
var btn = document.getElementById("deploy-confirm-btn");
|
||||
input.addEventListener("input", function () {
|
||||
var v = input.value.trim();
|
||||
field.classList.remove("is-valid", "is-invalid");
|
||||
if (v === "") { btn.disabled = true; return; }
|
||||
if (v === target) {
|
||||
field.classList.add("is-valid");
|
||||
btn.disabled = false;
|
||||
} else {
|
||||
field.classList.add("is-invalid");
|
||||
btn.disabled = true;
|
||||
}
|
||||
});
|
||||
btn.addEventListener("click", function () {
|
||||
if (btn.disabled) return;
|
||||
btn.classList.add("is-loading");
|
||||
btn.textContent = "배포 중";
|
||||
setTimeout(function () {
|
||||
btn.closest("dialog").close();
|
||||
btn.classList.remove("is-loading");
|
||||
btn.textContent = "운영에 배포";
|
||||
btn.disabled = true;
|
||||
field.classList.remove("is-valid", "is-invalid");
|
||||
input.value = "";
|
||||
}, 1400);
|
||||
});
|
||||
})();
|
||||
|
||||
// 진행바 데모
|
||||
document.getElementById("sync-demo-btn").addEventListener("click", function () {
|
||||
var wrap = document.getElementById("sync-demo");
|
||||
var bar = document.getElementById("sync-demo-bar");
|
||||
var pct = document.getElementById("sync-demo-pct");
|
||||
var label = document.getElementById("sync-demo-label");
|
||||
wrap.style.display = "flex";
|
||||
wrap.classList.add("indeterminate");
|
||||
label.textContent = "연결 중…";
|
||||
var v = 0;
|
||||
setTimeout(function () {
|
||||
wrap.classList.remove("indeterminate");
|
||||
label.textContent = "패키지 내려받는 중";
|
||||
var t = setInterval(function () {
|
||||
v += Math.max(1, Math.round((100 - v) * 0.12));
|
||||
if (v >= 100) { v = 100; clearInterval(t); label.textContent = "완료"; }
|
||||
bar.value = v;
|
||||
pct.textContent = v + "%";
|
||||
}, 240);
|
||||
}, 900);
|
||||
});
|
||||
|
||||
// 로딩 버튼 데모
|
||||
document.getElementById("loading-demo").addEventListener("click", function () {
|
||||
var b = this;
|
||||
b.classList.add("is-loading");
|
||||
setTimeout(function () { b.classList.remove("is-loading"); }, 1600);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user