191 lines
7.5 KiB
Python
191 lines
7.5 KiB
Python
"""Reusable audit-tab renderer with data loaders supplied by the application."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable
|
|
|
|
|
|
AuditInventoryLoader = Callable[[], list[dict[str, Any]]]
|
|
AuditEventsLoader = Callable[[int, int, str, str], list[dict[str, Any]]]
|
|
|
|
|
|
def render_hmm_audit_tab(
|
|
st: Any,
|
|
inventory_loader: AuditInventoryLoader,
|
|
events_loader: AuditEventsLoader,
|
|
error_type: type[Exception],
|
|
) -> None:
|
|
"""Render HMM access audit data without owning DB connection details."""
|
|
|
|
st.markdown(
|
|
'<div class="kb-section-title input" role="heading" aria-level="3">'
|
|
'감사로그 ( <strong>HMM 접근 관리</strong> )'
|
|
'</div>',
|
|
unsafe_allow_html=True,
|
|
)
|
|
st.markdown(
|
|
'<div class="kb-audit-lead">'
|
|
'HMM 백오피스의 사용자·그룹·역할·토큰·접근 정책 변경 이력을 시간순으로 확인합니다. '
|
|
'이벤트 유형과 처리 상태로 필터링해 운영 변경의 성공·실패를 추적할 수 있습니다.'
|
|
'</div>',
|
|
unsafe_allow_html=True,
|
|
)
|
|
try:
|
|
inventory = inventory_loader()
|
|
except error_type as exc:
|
|
st.error(str(exc))
|
|
return
|
|
|
|
event_types = tuple(
|
|
str(item.get("event_type") or "").strip()
|
|
for item in inventory
|
|
if str(item.get("event_type") or "").strip()
|
|
)
|
|
st.markdown('<div class="kb-audit-heading">조회 조건</div>', unsafe_allow_html=True)
|
|
with st.container(key="poc4_hmm_audit_filters"):
|
|
event_column, status_column = st.columns(2)
|
|
with event_column:
|
|
selected_event_type = st.selectbox(
|
|
"이벤트 유형",
|
|
options=("", *event_types),
|
|
format_func=lambda value: "전체 이벤트" if not value else value,
|
|
key="poc4_hmm_audit_event_filter",
|
|
)
|
|
with status_column:
|
|
selected_status = st.selectbox(
|
|
"처리 상태",
|
|
options=("", "SUCCESS", "FAILURE", "DENIED"),
|
|
format_func=lambda value: "전체 상태" if not value else value,
|
|
key="poc4_hmm_audit_status_filter",
|
|
)
|
|
days_column, limit_column, refresh_column = st.columns([1.5, 1, 0.8])
|
|
with days_column:
|
|
days = st.slider(
|
|
"조회 기간",
|
|
min_value=1,
|
|
max_value=90,
|
|
value=7,
|
|
format="%d일",
|
|
key="poc4_hmm_audit_days",
|
|
)
|
|
with limit_column:
|
|
row_limit = st.number_input(
|
|
"최대 건수",
|
|
min_value=10,
|
|
max_value=500,
|
|
value=100,
|
|
step=10,
|
|
key="poc4_hmm_audit_row_limit",
|
|
)
|
|
with refresh_column:
|
|
st.markdown('<div style="height: 28px"></div>', unsafe_allow_html=True)
|
|
if st.button(
|
|
"새로고침",
|
|
icon=":material/refresh:",
|
|
width="stretch",
|
|
key="poc4_hmm_audit_refresh",
|
|
):
|
|
inventory_loader.clear()
|
|
events_loader.clear()
|
|
st.rerun()
|
|
|
|
try:
|
|
events = events_loader(
|
|
int(days), int(row_limit), selected_event_type, selected_status
|
|
)
|
|
except error_type as exc:
|
|
st.error(str(exc))
|
|
return
|
|
|
|
success_count = sum(
|
|
1 for item in events if str(item.get("status") or "").upper() == "SUCCESS"
|
|
)
|
|
with st.container(key="poc4_hmm_audit_metrics"):
|
|
type_metric, event_metric, success_metric, failure_metric = st.columns(4)
|
|
type_metric.metric("이벤트 유형", len(inventory))
|
|
event_metric.metric("조회 이벤트", len(events))
|
|
success_metric.metric("성공", success_count)
|
|
failure_metric.metric("실패·거부", len(events) - success_count)
|
|
|
|
st.markdown(
|
|
'<div class="kb-audit-heading">이벤트 유형 현황</div>'
|
|
f'<div class="kb-audit-caption">현재 기록된 이벤트 유형 {len(inventory)}개 · '
|
|
'유형별 누적 건수와 최근 발생 시각을 확인합니다.</div>',
|
|
unsafe_allow_html=True,
|
|
)
|
|
with st.container(key="poc4_hmm_audit_inventory_panel"):
|
|
with st.expander("감사 이벤트 유형", expanded=True):
|
|
if inventory:
|
|
st.dataframe(
|
|
[
|
|
{
|
|
"이벤트 유형": str(item.get("event_type") or ""),
|
|
"누적 건수": int(item.get("event_count") or 0),
|
|
"최근 발생(KST)": str(item.get("latest_event_time") or ""),
|
|
}
|
|
for item in inventory
|
|
],
|
|
column_config={
|
|
"이벤트 유형": st.column_config.TextColumn(width="large"),
|
|
"누적 건수": st.column_config.NumberColumn(width="small"),
|
|
"최근 발생(KST)": st.column_config.TextColumn(width="medium"),
|
|
},
|
|
hide_index=True,
|
|
width="stretch",
|
|
height=min(360, 72 + 36 * len(inventory)),
|
|
)
|
|
else:
|
|
st.caption("아직 기록된 HMM 접근 관리 이벤트가 없습니다.")
|
|
|
|
st.markdown(
|
|
'<div class="kb-audit-heading">감사 이벤트</div>'
|
|
'<div class="kb-audit-caption">최신 이벤트부터 표시합니다. '
|
|
'처리 상태와 대상 식별자, 오류 메시지를 먼저 확인하세요.</div>',
|
|
unsafe_allow_html=True,
|
|
)
|
|
show_details = st.toggle(
|
|
"상세 메시지 표시",
|
|
value=True,
|
|
key="poc4_hmm_audit_show_details",
|
|
)
|
|
if not events:
|
|
st.info("선택한 조건에 해당하는 HMM 접근 관리 이벤트가 없습니다.")
|
|
return
|
|
|
|
display_rows: list[dict[str, Any]] = []
|
|
for event in events:
|
|
row: dict[str, Any] = {
|
|
"감사 ID": int(event.get("audit_id") or 0),
|
|
"발생시각(KST)": str(event.get("event_time") or ""),
|
|
"이벤트 유형": str(event.get("event_type") or ""),
|
|
"상태": str(event.get("status") or ""),
|
|
"토큰 Key ID": event.get("key_id"),
|
|
"대상 Object ID": event.get("object_id"),
|
|
"처리 행": event.get("row_count"),
|
|
"오류 코드": str(event.get("error_code") or ""),
|
|
}
|
|
if show_details:
|
|
row["메시지"] = str(event.get("message") or "")
|
|
display_rows.append(row)
|
|
|
|
column_config: dict[str, Any] = {
|
|
"감사 ID": st.column_config.NumberColumn(width="small"),
|
|
"발생시각(KST)": st.column_config.TextColumn(width="medium"),
|
|
"이벤트 유형": st.column_config.TextColumn(width="large"),
|
|
"상태": st.column_config.TextColumn(width="small"),
|
|
"토큰 Key ID": st.column_config.NumberColumn(width="small"),
|
|
"대상 Object ID": st.column_config.NumberColumn(width="small"),
|
|
"처리 행": st.column_config.NumberColumn(width="small"),
|
|
"오류 코드": st.column_config.TextColumn(width="medium"),
|
|
}
|
|
if show_details:
|
|
column_config["메시지"] = st.column_config.TextColumn(width="large")
|
|
with st.container(key="poc4_hmm_audit_event_panel"):
|
|
st.dataframe(
|
|
display_rows,
|
|
column_config=column_config,
|
|
hide_index=True,
|
|
width="stretch",
|
|
height=min(640, 104 + 38 * len(display_rows)),
|
|
)
|