CI / test (push) Canceled after 0s
## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
197 lines
8.3 KiB
Python
197 lines
8.3 KiB
Python
"""Right-hand "Chi tiet su kien" detail panel shared by the Security Events /
|
||
MCP Call History / Action Logs tabs — the full record behind whichever row is
|
||
selected in an :class:`~.event_table.EventTable`. Extracted verbatim from
|
||
``ui/monitoring_tab.py``.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Dict, List, Tuple
|
||
|
||
from PySide6.QtCore import Qt, QTimer, Signal
|
||
from PySide6.QtGui import QGuiApplication
|
||
from PySide6.QtWidgets import (
|
||
QHBoxLayout, QLabel, QPushButton, QScrollArea, QVBoxLayout, QWidget,
|
||
)
|
||
|
||
from ....core import agent_roles
|
||
from ....i18n import tr
|
||
from ....ui.icons import icon
|
||
from .badges import agent_badge_name, action_label, apply_badge, severity_info, status_info
|
||
from .formatters import event_id, fmt_event_time_full
|
||
from .layout_helpers import kv_row
|
||
|
||
# Cosmetic label only — no real policy-versioning system exists yet.
|
||
_STATIC_POLICY_LABEL = "security_policy_v2"
|
||
|
||
|
||
class EventDetailPanel(QWidget):
|
||
"""Laid out as three labelled sections, a terminal-style block quote for
|
||
the detail text, and a metadata footer, closed by the header close
|
||
button, the footer button, Esc, or a click outside the table/panel (see
|
||
:class:`~.event_table.ClickOutsideCloser`)."""
|
||
|
||
closed = Signal()
|
||
|
||
def __init__(self):
|
||
"""Panel chi tiết hiện đầy đủ một sự kiện đang chọn trong bảng."""
|
||
super().__init__()
|
||
self.setObjectName("monSection")
|
||
self._detail_text = ""
|
||
outer = QVBoxLayout(self)
|
||
|
||
hdr = QHBoxLayout()
|
||
self._title_lbl = QLabel()
|
||
self._title_lbl.setStyleSheet("font-weight:700;")
|
||
hdr.addWidget(self._title_lbl, 1)
|
||
self._close_btn = QPushButton()
|
||
self._close_btn.setIcon(icon("close"))
|
||
self._close_btn.setFlat(True)
|
||
self._close_btn.setFixedWidth(28)
|
||
self._close_btn.setCursor(Qt.PointingHandCursor)
|
||
self._close_btn.clicked.connect(self.closed.emit)
|
||
hdr.addWidget(self._close_btn)
|
||
outer.addLayout(hdr)
|
||
|
||
scroll = QScrollArea()
|
||
scroll.setWidgetResizable(True)
|
||
scroll.setFrameShape(QScrollArea.NoFrame)
|
||
body = QWidget()
|
||
self._body_lay = QVBoxLayout(body)
|
||
self._body_lay.setContentsMargins(0, 0, 4, 0)
|
||
scroll.setWidget(body)
|
||
outer.addWidget(scroll, 1)
|
||
|
||
self._section_hdrs: List[Tuple[str, QLabel]] = []
|
||
self._rows: Dict[str, Tuple[QLabel, QLabel]] = {}
|
||
|
||
def _section(key: str) -> None:
|
||
"""Thêm một dòng tiêu đề mục vào panel."""
|
||
lbl = QLabel()
|
||
lbl.setObjectName("detailSectionHdr")
|
||
self._body_lay.addWidget(lbl)
|
||
self._section_hdrs.append((key, lbl))
|
||
|
||
def _field(key: str) -> QLabel:
|
||
"""Thêm một hàng nhãn–giá trị và ghi nhớ để cập nhật sau."""
|
||
lbl, val = kv_row(self._body_lay)
|
||
self._rows[key] = (lbl, val)
|
||
return val
|
||
|
||
_section("general")
|
||
_field("time")
|
||
self._agent_val = _field("agent")
|
||
_field("account")
|
||
self._machine_val = _field("machine")
|
||
self._machine_val.setObjectName("monoChip")
|
||
|
||
_section("action")
|
||
self._type_val = _field("type")
|
||
self._type_val.setObjectName("neutralTag")
|
||
self._status_val = _field("status")
|
||
|
||
_section("block")
|
||
code_box = QWidget()
|
||
code_box.setObjectName("detailCodeBlock")
|
||
code_lay = QHBoxLayout(code_box)
|
||
code_lay.setContentsMargins(8, 6, 8, 6)
|
||
self._code_text = QLabel()
|
||
self._code_text.setObjectName("detailCodeText")
|
||
self._code_text.setWordWrap(True)
|
||
self._code_text.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
||
code_lay.addWidget(self._code_text, 1)
|
||
self._copy_btn = QPushButton()
|
||
self._copy_btn.setObjectName("detailCopyBtn")
|
||
self._copy_btn.setCursor(Qt.PointingHandCursor)
|
||
self._copy_btn.clicked.connect(self._copy_detail)
|
||
code_lay.addWidget(self._copy_btn, 0, Qt.AlignVCenter)
|
||
self._body_lay.addWidget(code_box)
|
||
|
||
_section("metadata")
|
||
self._event_id_val = _field("event_id")
|
||
self._event_id_val.setObjectName("monoChip")
|
||
self._policy_val = _field("policy")
|
||
self._severity_val = _field("severity")
|
||
|
||
self._body_lay.addStretch(1)
|
||
|
||
footer = QHBoxLayout()
|
||
footer.setContentsMargins(0, 6, 0, 0)
|
||
self._footer_close_btn = QPushButton()
|
||
self._footer_close_btn.setObjectName("primary")
|
||
self._footer_close_btn.setCursor(Qt.PointingHandCursor)
|
||
self._footer_close_btn.clicked.connect(self.closed.emit)
|
||
footer.addWidget(self._footer_close_btn, 1)
|
||
outer.addLayout(footer)
|
||
|
||
def retranslate(self) -> None:
|
||
"""Áp lại chữ theo ngôn ngữ đang chọn."""
|
||
self._title_lbl.setText(tr("monitoring.security_detail_title"))
|
||
self._close_btn.setToolTip(tr("monitoring.security_detail_close"))
|
||
self._footer_close_btn.setText(tr("monitoring.security_detail_close"))
|
||
self._footer_close_btn.setIcon(icon("close"))
|
||
section_keys = {
|
||
"general": "monitoring.detail_section_general",
|
||
"action": "monitoring.detail_section_action",
|
||
"block": "monitoring.col_detail_block",
|
||
"metadata": "monitoring.detail_section_metadata",
|
||
}
|
||
for key, lbl in self._section_hdrs:
|
||
lbl.setText(tr(section_keys[key]).upper())
|
||
self._rows["time"][0].setText(tr("monitoring.col_time"))
|
||
self._rows["agent"][0].setText(tr("monitoring.col_agent"))
|
||
self._rows["account"][0].setText(tr("monitoring.col_account"))
|
||
self._rows["machine"][0].setText(tr("monitoring.col_machine"))
|
||
self._rows["type"][0].setText(tr("monitoring.detail_type"))
|
||
self._rows["status"][0].setText(tr("monitoring.detail_status"))
|
||
self._rows["event_id"][0].setText(tr("monitoring.detail_event_id"))
|
||
self._rows["policy"][0].setText(tr("monitoring.detail_policy"))
|
||
self._rows["severity"][0].setText(tr("monitoring.detail_severity"))
|
||
if not self._copy_btn.text() or self._copy_btn.text() != tr("monitoring.detail_copied"):
|
||
self._reset_copy_btn()
|
||
|
||
def show_event(self, ev: dict, row: int) -> None:
|
||
"""Hiện chi tiết một sự kiện; trường trống hiển thị dấu "—" thay vì để rỗng."""
|
||
na = "—"
|
||
self._rows["time"][1].setText(fmt_event_time_full(ev.get("ts", "")) or na)
|
||
|
||
agent_label = agent_roles.label_for(ev.get("agent_role", "")) or na
|
||
self._agent_val.setText(agent_label)
|
||
apply_badge(self._agent_val,
|
||
agent_badge_name(agent_label) if agent_label != na else "badge")
|
||
|
||
self._rows["account"][1].setText(ev.get("account", "") or na)
|
||
self._machine_val.setText(ev.get("machine", "") or na)
|
||
|
||
name = ev.get("name", "")
|
||
kind = ev.get("kind", "security_block")
|
||
ok = ev.get("ok", False)
|
||
self._type_val.setText(action_label(name) or na)
|
||
status_badge, status_key = status_info(name, kind, ok)
|
||
self._status_val.setText(tr(status_key))
|
||
apply_badge(self._status_val, status_badge)
|
||
|
||
self._detail_text = ev.get("detail", "") or na
|
||
self._code_text.setText(self._detail_text)
|
||
self._reset_copy_btn()
|
||
|
||
self._event_id_val.setText(event_id(ev.get("ts", ""), row))
|
||
# The static policy label names a real enforcement ruleset — only
|
||
# meaningful for a Security Events row; MCP calls/generic actions
|
||
# were never evaluated against it.
|
||
self._policy_val.setText(_STATIC_POLICY_LABEL if kind == "security_block" else na)
|
||
severity_badge, severity_key = severity_info(name, kind, ok)
|
||
self._severity_val.setText(tr(severity_key))
|
||
apply_badge(self._severity_val, severity_badge)
|
||
|
||
def _copy_detail(self) -> None:
|
||
"""Chép toàn bộ chi tiết vào clipboard và đổi nhãn nút để báo đã chép."""
|
||
QGuiApplication.clipboard().setText(self._detail_text)
|
||
self._copy_btn.setText(tr("monitoring.detail_copied"))
|
||
self._copy_btn.setIcon(icon("check"))
|
||
QTimer.singleShot(1500, self._reset_copy_btn)
|
||
|
||
def _reset_copy_btn(self) -> None:
|
||
"""Trả nút Sao chép về nhãn ban đầu sau vài giây."""
|
||
self._copy_btn.setText(tr("monitoring.detail_copy"))
|
||
self._copy_btn.setIcon(icon("document"))
|