refs #699: align HMM portal audit experience
This commit is contained in:
190
poc4_active_source_20260714/src/agent_console/audit.py
Normal file
190
poc4_active_source_20260714/src/agent_console/audit.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""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)),
|
||||
)
|
||||
@@ -67,6 +67,26 @@ def apply_console_theme(st: Any, profile: AppProfile) -> None:
|
||||
[data-testid="stExpander"] summary * {{
|
||||
color:var(--console-text) !important;
|
||||
-webkit-text-fill-color:var(--console-text) !important; }}
|
||||
[data-baseweb="tab-list"], [data-testid="stTabs"] [role="tablist"] {{
|
||||
border-bottom:1px solid var(--console-border) !important; }}
|
||||
[data-baseweb="tab-list"] [role="tab"], [data-testid="stTab"] {{
|
||||
background:#fff !important; color:var(--console-text) !important;
|
||||
-webkit-text-fill-color:var(--console-text) !important;
|
||||
border-radius:4px 4px 0 0 !important; }}
|
||||
[data-baseweb="tab-list"] [role="tab"] *, [data-testid="stTab"] * {{
|
||||
color:var(--console-text) !important;
|
||||
-webkit-text-fill-color:var(--console-text) !important; }}
|
||||
[data-baseweb="tab-list"] [role="tab"]:hover, [data-testid="stTab"]:hover {{
|
||||
background:#f6f8fa !important; }}
|
||||
[data-baseweb="tab-list"] [role="tab"][aria-selected="true"],
|
||||
[data-testid="stTab"][aria-selected="true"] {{
|
||||
color:var(--console-primary) !important;
|
||||
-webkit-text-fill-color:var(--console-primary) !important;
|
||||
border-bottom:3px solid var(--console-primary) !important; }}
|
||||
[data-baseweb="tab-list"] [role="tab"][aria-selected="true"] *,
|
||||
[data-testid="stTab"][aria-selected="true"] * {{
|
||||
color:var(--console-primary) !important;
|
||||
-webkit-text-fill-color:var(--console-primary) !important; }}
|
||||
.console-header {{ margin:0 0 28px; padding:0 0 22px; border-bottom:1px solid var(--console-border); }}
|
||||
.console-wordmark {{ color:var(--console-primary); font-size:1.35rem; font-weight:800; letter-spacing:.08em; }}
|
||||
.console-header h1 {{ margin:10px 0 8px; font-size:1.7rem; }}
|
||||
|
||||
Reference in New Issue
Block a user