From f98c7ff3a26b0f372fdb8cc19407c2bcc38a4845 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 24 Jun 2026 14:58:54 +0900 Subject: [PATCH] feat: design 2-column layout for report list and details, click report cards to render immediately --- app.py | 127 +++++++++++++++++++++++++++------------------------------ 1 file changed, 60 insertions(+), 67 deletions(-) diff --git a/app.py b/app.py index e261a4a..e7141d4 100644 --- a/app.py +++ b/app.py @@ -773,78 +773,71 @@ def page_history(): st.markdown("---") - # 세션 상태에 선택된 분석 ID 저장 및 동기화 초기화 - if "selected_analysis_id" not in st.session_state: - st.session_state["selected_analysis_id"] = records[0]["id"] if records else 1 + # 세션 상태 보정 (현재 필터링된 목록에 세션 ID가 없으면 첫 번째 항목으로 강제 세팅) + valid_ids = [r["id"] for r in records] + if st.session_state["selected_analysis_id"] not in valid_ids and records: + st.session_state["selected_analysis_id"] = records[0]["id"] - # 이력 테이블 - status_emoji = {"critical": "🔴", "warning": "🟡", "healthy": "🟢"} - table_data = [{ - "ID": r["id"], - "분석일시": r["created_at"], - "상태": f"{status_emoji.get(r['overall_status'], '')} {r['overall_status']}", - "파일명": r["filename"][:40], - "GPU 수": r["gpu_count"], - "XID": r["xid_count"], - "심각": r["critical_count"], - "경고": r["warning_count"], - "드라이버": r["driver_version"], - "메모": (r.get("memo") or "")[:50], - } for r in records] + # 2컬럼 대시보드 레이아웃 구성 + col_list, col_detail = st.columns([1, 2.5]) - # st.dataframe의 selection 기능을 사용하여 마우스 행 선택 감지 및 Rerun 트리거 - df_display = pd.DataFrame(table_data) - selection_event = st.dataframe( - df_display, - use_container_width=True, - hide_index=True, - selection_mode="single-row", - on_select="rerun", - key="history_table" - ) + # 1. 왼쪽 컬럼: 분석 이력 카드 버튼 목록 (마우스로 편하게 누르는 영역) + with col_list: + st.write("### 📋 보고서 목록") + st.caption("아래 목록을 클릭하시면 상세 내역이 오른쪽에 즉시 표시됩니다.") + status_emoji = {"critical": "🔴", "warning": "🟡", "healthy": "🟢"} + + with st.container(height=650): + for r in records: + emoji = status_emoji.get(r['overall_status'], '🟢') + is_selected = (r["id"] == st.session_state["selected_analysis_id"]) + + # 선택된 보고서는 강조 표시 + prefix = "▶️ " if is_selected else "" + btn_label = f"{prefix}{emoji} [ID: {r['id']}] {r['filename'][:23]}\n({r['created_at'].split()[0]})" + + # 버튼으로 구현하여 일반 마우스 클릭이 100% 동작하도록 처리 + if st.button( + btn_label, + key=f"btn_hist_{r['id']}", + use_container_width=True, + type="primary" if is_selected else "secondary" + ): + st.session_state["selected_analysis_id"] = r["id"] + st.rerun() - # 테이블에서 마우스 클릭으로 행을 선택했을 때 세션 상태 ID 갱신 - selected_rows = selection_event.get("selection", {}).get("rows", []) - if selected_rows: - selected_row_idx = selected_rows[0] - if selected_row_idx < len(records): - st.session_state["selected_analysis_id"] = records[selected_row_idx]["id"] - else: - # 선택 해제되었거나 최초 로딩 시, 기본적으로 가장 최근 분석(첫 번째 행)을 기본값으로 세팅 - valid_ids = [r["id"] for r in records] - if "selected_analysis_id" not in st.session_state or st.session_state["selected_analysis_id"] not in valid_ids: - st.session_state["selected_analysis_id"] = records[0]["id"] if records else 1 + # 2. 오른쪽 컬럼: 선택된 보고서의 상세 분석 내용 렌더링 + with col_detail: + selected_id = st.session_state["selected_analysis_id"] + detail = get_analysis_detail(selected_id) + + if not detail: + st.warning(f"ID {selected_id}에 해당하는 분석 결과가 없습니다.") + else: + st.write(f"### 🔎 상세 분석 결과 (보고서 ID: {selected_id})") + + # 메모 편집 (editor/admin만) + perms = get_perms() + current_memo = detail.get("memo", "") or "" + if perms["can_edit_memo"]: + new_memo = st.text_area("📝 메모 수정", value=current_memo, key=f"memo_{selected_id}") + if new_memo != current_memo: + update_memo(selected_id, new_memo) + st.success("메모가 업데이트되었습니다.") + elif current_memo: + st.text_area("📝 메모", value=current_memo, key=f"memo_{selected_id}", disabled=True) - # 상세 조회 - st.markdown("---") - selected_id = st.session_state["selected_analysis_id"] - st.subheader(f"🔎 상세 조회 (보고서 ID: {selected_id})") + # 삭제 버튼 (admin만) + if perms["can_delete"]: + if st.button("🗑️ 이 분석 기록 삭제", type="secondary"): + delete_analysis(selected_id) + st.success(f"ID {selected_id} 삭제 완료.") + # 삭제 후 첫 번째 보고서로 세션 초기화 및 리런 + st.session_state["selected_analysis_id"] = records[0]["id"] if len(records) > 1 else 1 + st.rerun() - detail = get_analysis_detail(selected_id) - if not detail: - st.warning(f"ID {selected_id}에 해당하는 분석 결과가 없습니다.") - return - - # 메모 편집 (editor/admin만) - perms = get_perms() - current_memo = detail.get("memo", "") or "" - if perms["can_edit_memo"]: - new_memo = st.text_area("📝 메모 수정", value=current_memo, key=f"memo_{selected_id}") - if new_memo != current_memo: - update_memo(selected_id, new_memo) - st.success("메모가 업데이트되었습니다.") - elif current_memo: - st.text_area("📝 메모", value=current_memo, key=f"memo_{selected_id}", disabled=True) - - # 삭제 버튼 (admin만) - if perms["can_delete"]: - if st.button("🗑️ 이 분석 기록 삭제", type="secondary"): - delete_analysis(selected_id) - st.success(f"ID {selected_id} 삭제 완료. 페이지를 새로고침하세요.") - return - - # 상세 내용 - render_status_badge(detail["overall_status"]) + # 상세 내용 렌더링 시작 + render_status_badge(detail["overall_status"]) dc1, dc2, dc3, dc4 = st.columns(4) dc1.metric("파일명", detail["filename"][:30])