feat: design 2-column layout for report list and details, click report cards to render immediately
This commit is contained in:
@@ -773,78 +773,71 @@ def page_history():
|
|||||||
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
# 세션 상태에 선택된 분석 ID 저장 및 동기화 초기화
|
# 세션 상태 보정 (현재 필터링된 목록에 세션 ID가 없으면 첫 번째 항목으로 강제 세팅)
|
||||||
if "selected_analysis_id" not in st.session_state:
|
valid_ids = [r["id"] for r in records]
|
||||||
st.session_state["selected_analysis_id"] = records[0]["id"] if records else 1
|
if st.session_state["selected_analysis_id"] not in valid_ids and records:
|
||||||
|
st.session_state["selected_analysis_id"] = records[0]["id"]
|
||||||
|
|
||||||
# 이력 테이블
|
# 2컬럼 대시보드 레이아웃 구성
|
||||||
status_emoji = {"critical": "🔴", "warning": "🟡", "healthy": "🟢"}
|
col_list, col_detail = st.columns([1, 2.5])
|
||||||
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]
|
|
||||||
|
|
||||||
# st.dataframe의 selection 기능을 사용하여 마우스 행 선택 감지 및 Rerun 트리거
|
# 1. 왼쪽 컬럼: 분석 이력 카드 버튼 목록 (마우스로 편하게 누르는 영역)
|
||||||
df_display = pd.DataFrame(table_data)
|
with col_list:
|
||||||
selection_event = st.dataframe(
|
st.write("### 📋 보고서 목록")
|
||||||
df_display,
|
st.caption("아래 목록을 클릭하시면 상세 내역이 오른쪽에 즉시 표시됩니다.")
|
||||||
use_container_width=True,
|
status_emoji = {"critical": "🔴", "warning": "🟡", "healthy": "🟢"}
|
||||||
hide_index=True,
|
|
||||||
selection_mode="single-row",
|
with st.container(height=650):
|
||||||
on_select="rerun",
|
for r in records:
|
||||||
key="history_table"
|
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 갱신
|
# 2. 오른쪽 컬럼: 선택된 보고서의 상세 분석 내용 렌더링
|
||||||
selected_rows = selection_event.get("selection", {}).get("rows", [])
|
with col_detail:
|
||||||
if selected_rows:
|
selected_id = st.session_state["selected_analysis_id"]
|
||||||
selected_row_idx = selected_rows[0]
|
detail = get_analysis_detail(selected_id)
|
||||||
if selected_row_idx < len(records):
|
|
||||||
st.session_state["selected_analysis_id"] = records[selected_row_idx]["id"]
|
if not detail:
|
||||||
else:
|
st.warning(f"ID {selected_id}에 해당하는 분석 결과가 없습니다.")
|
||||||
# 선택 해제되었거나 최초 로딩 시, 기본적으로 가장 최근 분석(첫 번째 행)을 기본값으로 세팅
|
else:
|
||||||
valid_ids = [r["id"] for r in records]
|
st.write(f"### 🔎 상세 분석 결과 (보고서 ID: {selected_id})")
|
||||||
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
|
# 메모 편집 (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만)
|
||||||
st.markdown("---")
|
if perms["can_delete"]:
|
||||||
selected_id = st.session_state["selected_analysis_id"]
|
if st.button("🗑️ 이 분석 기록 삭제", type="secondary"):
|
||||||
st.subheader(f"🔎 상세 조회 (보고서 ID: {selected_id})")
|
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:
|
render_status_badge(detail["overall_status"])
|
||||||
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"])
|
|
||||||
|
|
||||||
dc1, dc2, dc3, dc4 = st.columns(4)
|
dc1, dc2, dc3, dc4 = st.columns(4)
|
||||||
dc1.metric("파일명", detail["filename"][:30])
|
dc1.metric("파일명", detail["filename"][:30])
|
||||||
|
|||||||
Reference in New Issue
Block a user