refine account selection and detail tabs

This commit is contained in:
2026-09-11 10:27:01 +08:00
parent ec07e5e10e
commit 90647d25d7
7 changed files with 208 additions and 154 deletions
+22
View File
@@ -0,0 +1,22 @@
# UI 布局调整记录
## 需求
1. 移除页面顶部提示文字。
2. 将日志按天保存提示与自动滚动操作放在同一行。
3. 左侧账号树默认占约 30%,分隔条可拖动调整。
4. 为左侧账号树区域增加浅色背景,减少空旷感。
## 实现
- 修改 `src/accounts_app.py` 的 `Window` 布局:删除顶部说明 `QLabel`。
- 将 `log_info` 放入日志控制栏,并关闭换行,和自动滚动、打开日志目录控件共用一行。
- 使用 `QSplitter` 保留可拖动分隔条,设置 8px 宽度、悬停高亮、水平分隔光标和拖动提示;默认尺寸按 30/70 设置,伸缩因子作为回退。
- 左侧账号树容器增加浅灰背景、边框和圆角;滚动区域及内容宿主设为透明,以显示容器背景并保留账号卡片样式。
## 验证
- `QT_QPA_PLATFORM=offscreen .venv/bin/python -m pytest -q src/test_onboarding_ui.py`:9 passed。
- `PYTHONPATH=src .venv/bin/python -m py_compile src/accounts_app.py`:通过。
- Offscreen UI 探针确认:左侧比例 `365/1216 = 30%`、分隔条宽度 8、可拖动光标启用、顶部提示不存在、日志提示和自动滚动控件同属一行。
- 未执行 Windows 远程部署或真实账号业务操作。
@@ -0,0 +1,30 @@
# Windows UI 布局调整远程部署记录
## 连接与范围
- 目标:通过项目配置的 `windows-ui` MCP 连接 Windows 主机。
- 传输:本机临时 HTTP 服务仅提供本次源码压缩包,Windows 端下载后本地解压。
- 未执行自动登录、账号恢复、关注、私信或其他真实业务写操作。
## 执行步骤
1. 先检查 Windows 窗口状态,确认目标应用未运行。
2. 将当前项目源码打包并下载到:
`C:\Users\Rogee\douyin-pc-ui-20260909\repo.zip`
3. 在 Windows 端解压源码。
4. 检查发现编译依赖缺失,执行:
`python -m pip install -r C:\Users\Rogee\douyin-pc-ui-20260909\requirements-build-windows.txt`
5. 依赖导入验证通过后,在 Windows x64 原生环境执行:
`python build_windows.py`
6. 生成并运行便携版程序:
`C:\Users\Rogee\douyin-pc-ui-20260909\dist\DouyinAccounts\DouyinAccounts.exe`
## 验收结果
- Windows 原生构建完成,`dist\DouyinAccounts\DouyinAccounts.exe` 存在。
- Inno Setup 安装器未生成,因此本次使用 PyInstaller 便携版直接运行查看效果。
- 窗口标题为“抖音账号助手 · 自动操作默认关闭”。
- 顶部提示文字已移除。
- 日志按天保存提示、自动滚动和打开日志文件夹位于同一行。
- 左侧账号树区域约占 30%,分隔条约 8px 且可拖动;左侧背景样式已生效。
- 当前应用保持运行,供人工查看界面效果。
@@ -0,0 +1,21 @@
# 大号选择与分组启动调整记录
## 需求
大号卡片已有选中边框,左侧复选框与“大号选中即选中分组”的交互重复,应移除账号卡片复选框。
## 实现
- 移除 `AccountCard` 的账号复选框及 `checked` 信号。
- `AccountCardTree` 不再维护勾选组,保留卡片选中边框作为当前分组选择状态。
- 移除“启动当前组”和“恢复上次选择”顶部按钮。
- 大号右键菜单保留“启动本组”,仅通过右键触发该分组启动;作品卡片和账号卡片的当前选中状态继续由边框表示。
- 保留作品卡片中的“监控”复选框;该复选框与大号选择无关。
## 验证
- `src/test_onboarding_ui.py`:9 passed。
- Qt 定向检查确认大号/小号账号卡片均无账号复选框。
- 定向检查确认选中大号 `m` 时启动请求为 `["m"]`。
- `py_compile` 和 `git diff --check` 通过。
- 未执行真实启动、关注、私信或登录操作。
@@ -0,0 +1,23 @@
# 最新 UI 版本 Windows 远程构建记录
## 范围
将最新 `src/accounts_app.py` 部署到 Windows 原生构建目录并运行查看界面。未执行登录、恢复业务、关注、私信或其他真实业务操作。
## 执行
- Windows 目标应用启动前状态已检查,未发现抖音账号助手窗口。
- 通过项目配置的 `windows-ui` MCP 对应主机下载最新源码文件,源码 SHA-256:`7d996a...f47e`。
- 在已有 Windows 构建环境执行:
`Set-Location C:\Users\Rogee\douyin-pc-ui-20260909; python build_windows.py`
- 生成便携版:
`C:\Users\Rogee\douyin-pc-ui-20260909\dist\DouyinAccounts\DouyinAccounts.exe`
- 运行上述便携版程序。
## 验收
- 窗口成功启动,标题为“抖音账号助手 · 自动操作默认关闭”。
- 顶部仅保留“添加大号”“添加小号”“停止全部”“异常任务”“设置”;“恢复上次选择”和“启动当前组”已移除。
- 大号卡片保留右键菜单入口“启动本组”,账号卡片未显示左侧复选框。
- 日志提示、自动滚动和打开日志文件夹保持同一行;左侧树约 30%,分隔条可拖动,背景样式正常。
- 应用已保持运行,供人工查看效果。
@@ -0,0 +1,22 @@
# 账号入口与异常任务布局调整记录
## 需求
- 顶部“添加大号”改为“添加账号”。
- 移除顶部“添加小号”,改从大号右键菜单添加并自动归属当前大号。
- 移除顶部“异常任务”,将异常任务按大号放入大号详情 Tab。
## 实现
- 顶部按钮仅保留“添加账号”“停止全部”“设置”;“添加账号”继续创建大号。
- 大号右键菜单增加“添加小号”,打开添加小号对话框时预选当前大号。
- 删除独立异常任务工作台、全局大号/小号筛选和异步 `task_query` UI 路由。
- 大号详情新增“异常任务”Tab,直接从当前快照筛选 `status == unknown` 且 `source` 等于当前大号 ID 的任务;小号详情隐藏该 Tab。
- 异常任务仍为只读记录,不提供人工核定、重发、重置、改派或状态修改。
## 验证
- `src/test_onboarding_ui.py`:9 passed。
- Python 编译和 `git diff --check` 通过。
- Offscreen UI 检查确认顶部按钮为“添加账号/停止全部/设置”,详情 Tab 包含“异常任务”。
- 未执行真实登录、启动、关注或私信操作。
@@ -0,0 +1,26 @@
# Windows SSH 远程构建与验证记录
## 范围
将最新账号入口与异常任务 Tab 调整部署到 Windows 并启动供人工验证。未执行登录、启动业务、关注、私信或其他真实账号操作。
## 执行
- 通过 SSH 直连 Windows 主机,先检查 `DouyinAccounts.exe` 进程,确认未运行。
- 上传 `src/accounts_app.py` 到:
`C:\Users\Rogee\douyin-pc-ui-20260909\src\accounts_app.py`
- 校验本地与远端 SHA-256 一致:
`e6f7e52e52618a4c6108c3914b8cf8f3e66eca164c05352152a2494a0f3528b1`
- Windows 原生执行:
`cd /d C:\Users\Rogee\douyin-pc-ui-20260909 && python build_windows.py`
- 静默安装生成的 `DouyinAccounts-0.1.8-Windows-x64-Setup.exe`。
- 通过 Windows UI 在交互桌面启动已安装程序,避免 SSH 会话的 GUI 隔离。
## 验收
- 窗口成功启动,标题为“抖音账号助手 · 自动操作默认关闭”。
- 顶部显示“添加账号”“停止全部”“设置”。
- 大号右键菜单入口可用;账号卡片无左侧复选框。
- 选中大号后,详情页显示“历史事件”“作品列表”“异常任务”Tab。
- 日志提示、自动滚动和打开日志文件夹保持同一行;左侧约 30%,分隔条和背景样式正常。
- 应用已保持运行供人工查看。
+64 -154
View File
@@ -40,7 +40,6 @@ from PySide6.QtWidgets import (
QSizePolicy,
QSpinBox,
QSplitter,
QStackedWidget,
QTableWidget,
QTableWidgetItem,
QTabWidget,
@@ -369,7 +368,6 @@ def dialog(title, parent):
class AccountCard(QFrame):
selected = Signal(object)
context_requested = Signal(object, object)
checked = Signal(str, bool)
expand_requested = Signal()
def __init__(
@@ -379,7 +377,6 @@ class AccountCard(QFrame):
meta,
status,
color,
checkable=False,
expandable=False,
parent=None,
):
@@ -405,14 +402,6 @@ class AccountCard(QFrame):
layout.addWidget(self.toggle_button)
self.set_expandable(expandable)
self.check_box = QCheckBox() if checkable else None
if self.check_box is not None:
self.check_box.setToolTip("勾选后可批量启动该大号")
self.check_box.toggled.connect(
lambda checked: self.checked.emit(self.account_id, checked)
)
layout.addWidget(self.check_box)
self.status_dot = QLabel("●")
self.status_dot.setStyleSheet(f"color: {color}; font-size: 15px;")
layout.addWidget(self.status_dot)
@@ -460,7 +449,7 @@ class AccountCard(QFrame):
return True
if (
event.button() == Qt.MouseButton.LeftButton
and watched not in (self.check_box, self.toggle_button)
and watched is not self.toggle_button
and self.account_id is not None
):
self.selected.emit(self.account_id)
@@ -543,7 +532,6 @@ class AccountCardTree(QScrollArea):
self.layout_box.addStretch()
self.setWidget(self.host)
self.cards = {}
self.groups = {}
self.nodes = {}
self._selected_id = None
self.viewport().installEventFilter(self)
@@ -563,10 +551,9 @@ class AccountCardTree(QScrollArea):
card.selected.connect(self._select)
card.context_requested.connect(self.context_menu_requested)
def set_accounts(self, accounts, display, checked, selected_id):
def set_accounts(self, accounts, display, selected_id):
self._clear()
self.cards = {}
self.groups = {}
self.nodes = {}
self._selected_id = selected_id
main_nodes = {}
@@ -576,16 +563,12 @@ class AccountCardTree(QScrollArea):
card = AccountCard(
account["id"],
**display[account["id"]],
checkable=True,
parent=self.host,
)
if card.check_box is not None:
card.check_box.setChecked(account["id"] in checked)
self._connect(card)
node = AccountTreeNode(card, self.host)
self._add_root(node)
self.cards[account["id"]] = card
self.groups[account["id"]] = card
self.nodes[account["id"]] = node
main_nodes[account["id"]] = node
@@ -637,13 +620,6 @@ class AccountCardTree(QScrollArea):
def set_current_id(self, account_id):
self._select(account_id)
def checked_groups(self):
return [
account_id
for account_id, card in self.groups.items()
if card.check_box is not None and card.check_box.isChecked()
]
def eventFilter(self, watched, event):
if watched is self.viewport() and event.type() == QEvent.Type.ContextMenu:
self.context_menu_requested.emit(None, event.globalPos())
@@ -664,7 +640,6 @@ class Window(QMainWindow):
"browser": {},
"browser_overrides": {},
}
self.selection_loaded = False
self.allow_close = False
self.quitting = False
self.signature = None
@@ -675,8 +650,6 @@ class Window(QMainWindow):
self.detail_account_id = None
self.history_payload = None
self.works_payload = None
self.exception_tasks = []
self.exception_names = {}
self.work_cover_cache = Path(self.backend.root) / "work_covers"
self.network = QNetworkAccessManager(self)
self.setWindowTitle("抖音账号助手 · 自动操作默认关闭")
@@ -684,40 +657,43 @@ class Window(QMainWindow):
central = QWidget()
self.setCentralWidget(central)
layout = QVBoxLayout(central)
note = QLabel(
"左侧账号树只显示别名、名称和状态;点击账号查看详情,账号操作请使用右键菜单。"
"恢复选择不会启动业务,启动前会先检查旧 pending 任务。"
)
note.setWordWrap(True)
layout.addWidget(note)
row = QHBoxLayout()
layout.addLayout(row)
self.buttons = []
for text, handler in [
("添加大号", lambda: self.add("main")),
("添加小号", lambda: self.add("worker")),
("恢复上次选择", self.restore_selection),
("启动勾选组", self.start_groups),
("添加账号", lambda: self.add("main")),
("停止全部", lambda: self.send("stop")),
]:
button = self.button(row, text, handler)
if text == "停止全部":
self.stop_button = button
self.exception_button = QPushButton("异常任务")
self.exception_button.clicked.connect(self.show_exception_tasks)
row.addWidget(self.exception_button)
self.button(row, "设置", lambda: self.browser_settings(False))
row.addStretch()
self.workspace_stack = QStackedWidget()
accounts = QWidget()
account_layout = QHBoxLayout(accounts)
splitter = QSplitter(Qt.Orientation.Horizontal)
splitter.setHandleWidth(8)
splitter.setChildrenCollapsible(False)
splitter.setStyleSheet(
"QSplitter::handle:horizontal { background: #d9dde5; margin: 4px 0; }"
"QSplitter::handle:horizontal:hover { background: #1677ff; }"
)
account_layout.addWidget(splitter)
left = QWidget()
left.setObjectName("accountTreePane")
left.setStyleSheet(
"#accountTreePane {"
"background: #f5f7fa; border: 1px solid #e5e7eb; border-radius: 8px;"
"}"
)
left_layout = QVBoxLayout(left)
left_layout.setContentsMargins(8, 8, 8, 8)
self.card_table = AccountCardTree()
self.card_table.setMinimumWidth(320)
self.card_table.setStyleSheet("background: transparent; border: 0;")
self.card_table.viewport().setStyleSheet("background: transparent;")
self.card_table.host.setStyleSheet("background: transparent;")
self.card_table.context_menu_requested.connect(self.account_menu)
self.card_table.selection_changed.connect(self.show_profile)
left_layout.addWidget(self.card_table)
@@ -876,24 +852,30 @@ class Window(QMainWindow):
works_select.addStretch()
main_works_layout.addLayout(works_select)
exception_tab = self._build_exception_tab()
self.detail_tabs.addTab(main_history_tab, "历史事件")
self.detail_tabs.addTab(main_works_tab, "作品列表")
self.detail_tabs.addTab(exception_tab, "异常任务")
right_layout.addWidget(self.detail_tabs, 1)
splitter.addWidget(right)
splitter.setStretchFactor(0, 2)
splitter.setStretchFactor(1, 5)
splitter.setSizes([390, 840])
self.account_page = accounts
self.workspace_stack.addWidget(accounts)
self.exception_page = self._build_exception_page()
self.workspace_stack.addWidget(self.exception_page)
splitter.setStretchFactor(0, 3)
splitter.setStretchFactor(1, 7)
try:
splitter.setSizes([int(self.width() * 0.3), int(self.width() * 0.7)])
except (TypeError, ValueError):
splitter.setStretchFactor(0, 3)
splitter.setStretchFactor(1, 7)
splitter.handle(1).setCursor(Qt.CursorShape.SplitHCursor)
splitter.handle(1).setToolTip("拖动调整左侧账号树宽度")
logs = QWidget()
logs_layout = QVBoxLayout(logs)
self.log_info = QLabel("正在加载日志;按天保存完整记录,界面仅保留最近 2000 条")
self.log_info.setWordWrap(True)
logs_layout.addWidget(self.log_info)
controls = QHBoxLayout()
self.log_info = QLabel("正在加载日志;按天保存完整记录,界面仅保留最近 2000 条")
self.log_info.setWordWrap(False)
self.log_info.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
)
controls.addWidget(self.log_info, 1)
self.auto_scroll = QCheckBox("自动滚动到最新记录")
self.auto_scroll.setChecked(True)
controls.addWidget(self.auto_scroll)
@@ -907,7 +889,7 @@ class Window(QMainWindow):
self.log.setMaximumBlockCount(UI_LIMIT)
logs_layout.addWidget(self.log)
vertical = QSplitter(Qt.Orientation.Vertical)
vertical.addWidget(self.workspace_stack)
vertical.addWidget(accounts)
vertical.addWidget(logs)
vertical.setStretchFactor(0, 5)
vertical.setStretchFactor(1, 2)
@@ -1043,37 +1025,20 @@ class Window(QMainWindow):
self.works_table.setDisabled(checked)
self.works_card_area.setDisabled(checked)
def _build_exception_page(self):
page = QWidget()
layout = QVBoxLayout(page)
heading = QHBoxLayout()
heading.addWidget(QLabel("异常任务(仅显示 unknown,只读)"))
back = QPushButton("返回账号工作台")
back.clicked.connect(
lambda: self.workspace_stack.setCurrentWidget(self.account_page)
)
heading.addWidget(back)
layout.addLayout(heading)
filters = QHBoxLayout()
filters.addWidget(QLabel("原所属大号"))
self.exception_source_filter = QComboBox()
filters.addWidget(self.exception_source_filter)
filters.addWidget(QLabel("原执行小号"))
self.exception_worker_filter = QComboBox()
filters.addWidget(self.exception_worker_filter)
self.button(filters, "刷新异常任务", self.query_exception_tasks)
filters.addStretch()
layout.addLayout(filters)
def _build_exception_tab(self):
tab = QWidget()
layout = QVBoxLayout(tab)
layout.addWidget(QLabel("异常任务(当前大号所属分组;仅显示 unknown,只读)"))
layout.addWidget(
QLabel(
"unknown 只保留查询和保护占用,不提供人工核定、重发、重置、改派或状态修改。"
"结果不确定仅作只读记录,不提供人工核定、重发、重置、改派或状态修改。"
)
)
self.exception_table = self._task_table(checkable=False)
layout.addWidget(self.exception_table, 1)
self.exception_counts = QLabel("结果不确定 0 条")
layout.addWidget(self.exception_counts)
return page
return tab
def _task_names(self):
return self.data.get("task_names") or {
@@ -1117,55 +1082,16 @@ class Window(QMainWindow):
)
self.history_task_summary.setText(f"历史任务 {len(history)} 条")
def refresh_exception_filters(self):
source = self.exception_source_filter.currentData()
worker = self.exception_worker_filter.currentData()
accounts = self.data.get("accounts", [])
self.exception_source_filter.blockSignals(True)
self.exception_worker_filter.blockSignals(True)
self.exception_source_filter.clear()
self.exception_source_filter.addItem("全部原组", "")
for account in accounts:
if account["role"] == "main":
self.exception_source_filter.addItem(account["name"], account["id"])
self.exception_worker_filter.clear()
self.exception_worker_filter.addItem("全部原执行小号", "")
for ident, name in sorted(
self._task_names().items(), key=lambda value: value[1]
):
if any(a["id"] == ident and a["role"] == "main" for a in accounts):
continue
self.exception_worker_filter.addItem(name, ident)
self.exception_source_filter.setCurrentIndex(
max(0, self.exception_source_filter.findData(source))
)
self.exception_worker_filter.setCurrentIndex(
max(0, self.exception_worker_filter.findData(worker))
)
self.exception_source_filter.blockSignals(False)
self.exception_worker_filter.blockSignals(False)
def show_exception_tasks(self):
self.workspace_stack.setCurrentWidget(self.exception_page)
self.refresh_exception_filters()
self.query_exception_tasks()
def query_exception_tasks(self):
self.send(
"task_query",
{
"source": self.exception_source_filter.currentData() or None,
"worker": self.exception_worker_filter.currentData() or None,
},
)
def render_exception_tasks(self, payload):
self.exception_tasks = payload.get("tasks", [])
self.exception_names = payload.get("task_names") or self._task_names()
self._fill_task_table(
self.exception_table, self.exception_tasks, self.exception_names
)
self.exception_counts.setText(f"结果不确定 {len(self.exception_tasks)} 条")
def _refresh_detail_exceptions(self, account):
tasks = []
if account and account["role"] == "main":
tasks = [
task
for task in self.data.get("tasks", [])
if task["source"] == account["id"] and task["status"] == "unknown"
]
self._fill_task_table(self.exception_table, tasks, self._task_names())
self.exception_counts.setText(f"结果不确定 {len(tasks)} 条")
def history_full(self):
account = self.selected()
@@ -1340,7 +1266,7 @@ class Window(QMainWindow):
data["_request_id"] = request_id
if data.get("id"):
self.latest_requests[(name, data["id"])] = request_id
if name in ("task_query", "start_preview"):
if name == "start_preview":
self.latest_global_requests[name] = request_id
for button in self.buttons:
button.setEnabled(False)
@@ -1418,7 +1344,7 @@ class Window(QMainWindow):
):
return
if (
name in ("task_query", "start_preview")
name == "start_preview"
and request_id
and self.latest_global_requests.get(name) != request_id
):
@@ -1427,8 +1353,6 @@ class Window(QMainWindow):
self.history_dialog(payload)
elif name in ("works_open", "works_refresh", "works_full"):
self.works_dialog(payload)
elif name == "task_query":
self.render_exception_tasks(payload)
elif name == "start_preview":
self.show_start_preview(payload)
elif name == "cancel_pending":
@@ -1648,12 +1572,6 @@ class Window(QMainWindow):
load_cover(item)
self._toggle_works_selection(self.works_all_mode.isChecked())
def restore_selection(self):
self.selection_loaded = False
self.signature = None
self.refresh(self.data)
self.statusBar().showMessage("已恢复上次选择;不会启动业务")
def account_menu(self, account_id, global_pos):
if account_id:
self.card_table.set_current_id(account_id)
@@ -1663,6 +1581,7 @@ class Window(QMainWindow):
menu.addAction("登录 / 刷新账号信息", lambda: self.selected_command("open"))
menu.addAction("打开账号浏览器", lambda: self.selected_command("open"))
if account["role"] == "main":
menu.addAction("添加小号", lambda: self.add("worker", account["id"]))
menu.addAction(
"启动本组", lambda: self.start_group_request([account["id"]])
)
@@ -1697,6 +1616,7 @@ class Window(QMainWindow):
self.history_table.setRowCount(0)
self.works_table.setRowCount(0)
self._clear_layout(self.works_card_grid)
self._refresh_detail_exceptions(None)
return
account_changed = self.detail_account_id != account["id"]
if account_changed:
@@ -1755,6 +1675,7 @@ class Window(QMainWindow):
f"{self._sync_text(sync.get('notices'), '历史事件')} · {self._sync_text(sync.get('works'), '作品缓存')}"
)
self._refresh_detail_tasks(account)
self._refresh_detail_exceptions(account)
def selected_command(self, name):
account = self.selected()
@@ -1763,18 +1684,6 @@ class Window(QMainWindow):
else:
QMessageBox.information(self, "提示", "请先选择一个账号。")
def checked_groups(self):
return self.card_table.checked_groups()
def start_groups(self):
groups = self.checked_groups()
if not groups:
QMessageBox.information(
self, "提示", "请勾选至少一个大号。上次选择会在重启后自动勾选。"
)
return
self.start_group_request(groups)
def stop_selected_group(self):
account = self.selected()
if account:
@@ -1807,7 +1716,7 @@ class Window(QMainWindow):
{"id": account_box.currentData(), "owner": owner_box.currentData()},
)
def add(self, role):
def add(self, role, owner_id=None):
win, form, buttons = dialog("添加大号" if role == "main" else "添加小号", self)
name = QLineEdit()
name.setPlaceholderText("例如:主账号、客服一号")
@@ -1819,6 +1728,10 @@ class Window(QMainWindow):
owner.addItem(a["name"], a["id"])
if role == "worker":
form.addRow("归属大号", owner)
if owner_id is not None:
index = owner.findData(owner_id)
if index >= 0:
owner.setCurrentIndex(index)
restore = QPushButton("恢复已删除小号")
def open_restore():
@@ -2048,7 +1961,6 @@ class Window(QMainWindow):
new_accounts = [a for a in data["accounts"] if a["id"] not in known]
if new_accounts:
selected_id = new_accounts[-1]["id"]
checked = self.checked_groups() if self.selection_loaded else data["selected"]
self.data = data
self.log_info.setText(
data.get("log_file_error")
@@ -2059,7 +1971,6 @@ class Window(QMainWindow):
)
)
self.signature = signature
self.selection_loaded = True
display = {}
for account in data["accounts"]:
identity = account.get("identity", "")
@@ -2078,9 +1989,8 @@ class Window(QMainWindow):
"status": f"{'监听' if account['role'] == 'main' else '执行'}:{state}",
"color": color,
}
self.card_table.set_accounts(data["accounts"], display, checked, selected_id)
self.card_table.set_accounts(data["accounts"], display, selected_id)
self.show_profile()
self.refresh_exception_filters()
def closeEvent(self, event):
if self.allow_close: