platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
205
selective-vpn-gui/main_window/singbox/cards_mixin.py
Normal file
205
selective-vpn-gui/main_window/singbox/cards_mixin.py
Normal file
@@ -0,0 +1,205 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import QSize, Qt
|
||||
from PySide6.QtWidgets import QFrame, QLabel, QListWidgetItem, QVBoxLayout
|
||||
|
||||
from main_window.constants import SINGBOX_STATUS_ROLE
|
||||
from transport_protocol_summary import transport_protocol_summary
|
||||
|
||||
|
||||
class SingBoxCardsMixin:
|
||||
def _singbox_client_protocol_summary(self, client) -> str:
|
||||
protocol_txt = transport_protocol_summary(client)
|
||||
if protocol_txt == "n/a":
|
||||
cid = str(getattr(client, "id", "") or "").strip()
|
||||
if (
|
||||
cid
|
||||
and cid == str(self._singbox_editor_profile_client_id or "").strip()
|
||||
and str(self._singbox_editor_protocol or "").strip()
|
||||
):
|
||||
protocol_txt = str(self._singbox_editor_protocol).strip().lower()
|
||||
return protocol_txt
|
||||
|
||||
def _make_singbox_profile_card_widget(
|
||||
self,
|
||||
*,
|
||||
name: str,
|
||||
protocol_txt: str,
|
||||
status: str,
|
||||
latency_txt: str,
|
||||
cid: str,
|
||||
) -> QFrame:
|
||||
frame = QFrame()
|
||||
frame.setObjectName("singboxProfileCard")
|
||||
lay = QVBoxLayout(frame)
|
||||
lay.setContentsMargins(10, 8, 10, 8)
|
||||
lay.setSpacing(2)
|
||||
|
||||
lbl_name = QLabel(name)
|
||||
lbl_name.setObjectName("cardName")
|
||||
lbl_name.setAlignment(Qt.AlignHCenter)
|
||||
lay.addWidget(lbl_name)
|
||||
|
||||
lbl_proto = QLabel(protocol_txt)
|
||||
lbl_proto.setObjectName("cardProto")
|
||||
lbl_proto.setAlignment(Qt.AlignHCenter)
|
||||
lay.addWidget(lbl_proto)
|
||||
|
||||
lbl_state = QLabel(f"{str(status or '').upper()} · {latency_txt}")
|
||||
lbl_state.setObjectName("cardState")
|
||||
lbl_state.setAlignment(Qt.AlignHCenter)
|
||||
lay.addWidget(lbl_state)
|
||||
|
||||
frame.setToolTip(f"{cid}\n{protocol_txt}\nstatus={status}")
|
||||
return frame
|
||||
|
||||
def _style_singbox_profile_card_widget(
|
||||
self,
|
||||
card: QFrame,
|
||||
*,
|
||||
active: bool,
|
||||
selected: bool,
|
||||
) -> None:
|
||||
if active and selected:
|
||||
bg = "#c7f1d5"
|
||||
border = "#208f47"
|
||||
name_color = "#11552e"
|
||||
meta_color = "#1f6f43"
|
||||
elif active:
|
||||
bg = "#eafaf0"
|
||||
border = "#2f9e44"
|
||||
name_color = "#14532d"
|
||||
meta_color = "#1f6f43"
|
||||
elif selected:
|
||||
bg = "#e8f1ff"
|
||||
border = "#2f80ed"
|
||||
name_color = "#1b2f50"
|
||||
meta_color = "#28568a"
|
||||
else:
|
||||
bg = "#f7f7f7"
|
||||
border = "#c9c9c9"
|
||||
name_color = "#202020"
|
||||
meta_color = "#666666"
|
||||
|
||||
card.setStyleSheet(
|
||||
f"""
|
||||
QFrame#singboxProfileCard {{
|
||||
border: 1px solid {border};
|
||||
border-radius: 6px;
|
||||
background: {bg};
|
||||
}}
|
||||
QLabel#cardName {{
|
||||
color: {name_color};
|
||||
font-weight: 600;
|
||||
}}
|
||||
QLabel#cardProto {{
|
||||
color: {meta_color};
|
||||
}}
|
||||
QLabel#cardState {{
|
||||
color: {meta_color};
|
||||
}}
|
||||
"""
|
||||
)
|
||||
|
||||
def _refresh_singbox_profile_card_styles(self) -> None:
|
||||
current_id = self._selected_transport_engine_id()
|
||||
for i in range(self.lst_singbox_profile_cards.count()):
|
||||
item = self.lst_singbox_profile_cards.item(i)
|
||||
cid = str(item.data(Qt.UserRole) or "").strip()
|
||||
status = str(item.data(SINGBOX_STATUS_ROLE) or "").strip().lower()
|
||||
card = self.lst_singbox_profile_cards.itemWidget(item)
|
||||
if not isinstance(card, QFrame):
|
||||
continue
|
||||
self._style_singbox_profile_card_widget(
|
||||
card,
|
||||
active=(status == "up"),
|
||||
selected=bool(current_id and cid == current_id),
|
||||
)
|
||||
|
||||
def _render_singbox_profile_cards(self) -> None:
|
||||
current_id = self._selected_transport_engine_id()
|
||||
self.lst_singbox_profile_cards.blockSignals(True)
|
||||
self.lst_singbox_profile_cards.clear()
|
||||
selected_item = None
|
||||
|
||||
if not self._transport_api_supported:
|
||||
item = QListWidgetItem("Transport API unavailable")
|
||||
item.setFlags(item.flags() & ~Qt.ItemIsEnabled & ~Qt.ItemIsSelectable)
|
||||
self.lst_singbox_profile_cards.addItem(item)
|
||||
self.lst_singbox_profile_cards.blockSignals(False)
|
||||
return
|
||||
|
||||
if not self._transport_clients:
|
||||
item = QListWidgetItem("No SingBox profiles configured")
|
||||
item.setFlags(item.flags() & ~Qt.ItemIsEnabled & ~Qt.ItemIsSelectable)
|
||||
self.lst_singbox_profile_cards.addItem(item)
|
||||
self.lst_singbox_profile_cards.blockSignals(False)
|
||||
return
|
||||
|
||||
for c in self._transport_clients:
|
||||
cid = str(getattr(c, "id", "") or "").strip()
|
||||
if not cid:
|
||||
continue
|
||||
name = str(getattr(c, "name", "") or "").strip() or cid
|
||||
status, latency, _last_error, _last_check = self._transport_live_health_for_client(c)
|
||||
latency_txt = f"{latency}ms" if latency > 0 else "no ping"
|
||||
protocol_txt = self._singbox_client_protocol_summary(c)
|
||||
item = QListWidgetItem("")
|
||||
item.setData(Qt.UserRole, cid)
|
||||
item.setData(SINGBOX_STATUS_ROLE, status)
|
||||
item.setSizeHint(QSize(228, 78))
|
||||
self.lst_singbox_profile_cards.addItem(item)
|
||||
self.lst_singbox_profile_cards.setItemWidget(
|
||||
item,
|
||||
self._make_singbox_profile_card_widget(
|
||||
name=name,
|
||||
protocol_txt=protocol_txt,
|
||||
status=status,
|
||||
latency_txt=latency_txt,
|
||||
cid=cid,
|
||||
),
|
||||
)
|
||||
if current_id and cid == current_id:
|
||||
selected_item = item
|
||||
|
||||
if selected_item is not None:
|
||||
self.lst_singbox_profile_cards.setCurrentItem(selected_item)
|
||||
elif self.lst_singbox_profile_cards.count() > 0:
|
||||
self.lst_singbox_profile_cards.setCurrentRow(0)
|
||||
self.lst_singbox_profile_cards.blockSignals(False)
|
||||
self._refresh_singbox_profile_card_styles()
|
||||
|
||||
def _sync_singbox_profile_card_selection(self, cid: str) -> None:
|
||||
if self._syncing_singbox_selection:
|
||||
return
|
||||
self._syncing_singbox_selection = True
|
||||
try:
|
||||
self.lst_singbox_profile_cards.blockSignals(True)
|
||||
self.lst_singbox_profile_cards.clearSelection()
|
||||
target = str(cid or "").strip()
|
||||
if target:
|
||||
for i in range(self.lst_singbox_profile_cards.count()):
|
||||
item = self.lst_singbox_profile_cards.item(i)
|
||||
if str(item.data(Qt.UserRole) or "").strip() == target:
|
||||
self.lst_singbox_profile_cards.setCurrentItem(item)
|
||||
break
|
||||
self.lst_singbox_profile_cards.blockSignals(False)
|
||||
finally:
|
||||
self._syncing_singbox_selection = False
|
||||
self._refresh_singbox_profile_card_styles()
|
||||
|
||||
def _select_transport_engine_by_id(self, cid: str) -> bool:
|
||||
target = str(cid or "").strip()
|
||||
if not target:
|
||||
return False
|
||||
idx = self.cmb_transport_engine.findData(target)
|
||||
if idx < 0:
|
||||
return False
|
||||
if idx != self.cmb_transport_engine.currentIndex():
|
||||
self.cmb_transport_engine.setCurrentIndex(idx)
|
||||
else:
|
||||
self._sync_singbox_profile_card_selection(target)
|
||||
self._sync_selected_singbox_profile_link(silent=True)
|
||||
self._load_singbox_editor_for_selected(silent=True)
|
||||
self._update_transport_engine_view()
|
||||
return True
|
||||
Reference in New Issue
Block a user