Combine traffic metrics into one card

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-20 16:55:58 +08:00
parent 95958696ba
commit 03bddcbe7e
3 changed files with 138 additions and 112 deletions
+12 -36
View File
@@ -3072,30 +3072,6 @@ TRANSLATION = {
"ZH": "正在检测...",
"isReviewed": "True"
},
"Connect to a proxy to view endpoint information.": {
"source": [
"Furious.Widget.EndpointInfoWidget"
],
"RU": "Подключитесь к прокси, чтобы просмотреть информацию о выходном узле.",
"ZH": "连接代理后可查看出口信息。",
"isReviewed": "True"
},
"Connecting...": {
"source": [
"Furious.Widget.EndpointInfoWidget"
],
"RU": "Подключение...",
"ZH": "正在连接...",
"isReviewed": "True"
},
"Proxy endpoint information is unavailable.": {
"source": [
"Furious.Widget.EndpointInfoWidget"
],
"RU": "Информация о выходном узле прокси недоступна.",
"ZH": "代理出口信息不可用。",
"isReviewed": "True"
},
"Approximate location unavailable": {
"source": [
"Furious.Widget.EndpointInfoWidget"
@@ -3141,7 +3117,7 @@ TRANSLATION = {
"Furious.Widget.EndpointInfoWidget"
],
"RU": "Местоположение приблизительно определено по публичному IP и может быть неточным.",
"ZH": "位置信息根据公网 IP 估算,可能不准确。",
"ZH": "位置信息根据公网IP估算,可能不准确。",
"isReviewed": "True"
},
"Not available": {
@@ -3160,14 +3136,6 @@ TRANSLATION = {
"ZH": "未知",
"isReviewed": "True"
},
"Endpoint inspection is disabled.": {
"source": [
"Furious.Widget.EndpointInfoWidget"
],
"RU": "Проверка конечной точки отключена.",
"ZH": "代理端点检测已禁用。",
"isReviewed": "True"
},
"Enable Proxy Endpoint Information": {
"source": [
"Furious.Window.SettingsPage"
@@ -3213,7 +3181,7 @@ TRANSLATION = {
"Furious.Window.SettingsPage"
],
"RU": "<b>Публичный IP-адрес</b><br>\nПубличные IPv4- и IPv6-адреса прокси проверяются<br>\nчерез активное прокси-соединение с помощью Cloudflare,<br>\nа ipify используется как резервный сервис.<br>\nЭти сервисы могут видеть публичный IP-адрес прокси.",
"ZH": "<b>公网 IP</b><br>\n系统通过当前代理连接<br>\n使用 Cloudflare 检查代理的公网 IPv4 和 IPv6 地址,<br>\n并以 ipify 作为备用服务。<br>\n这些服务可以看到代理的公网 IP。",
"ZH": "<b>公网 IP</b><br>\n系统通过当前代理连接<br>\n使用Cloudflare检查代理的公网IPv4和IPv6地址,<br>\n并以ipify作为备用服务。<br>\n这些服务可以看到代理的公网IP。",
"isReviewed": "True"
},
"<b>Approximate Location</b><br>\nThe detected public IP is sent to ipapi.co<br>\nto estimate country, city, region, and network organization.<br>\nIP-based location can be inaccurate.": {
@@ -3221,7 +3189,7 @@ TRANSLATION = {
"Furious.Window.SettingsPage"
],
"RU": "<b>Приблизительное местоположение</b><br>\nОбнаруженный публичный IP-адрес отправляется в ipapi.co<br>\nдля определения страны, города, региона и сетевой организации.<br>\nГеолокация по IP может быть неточной.",
"ZH": "<b>大致位置</b><br>\n系统将检测到的公网 IP 发送至 ipapi.co,<br>\n用于估算国家、城市、地区和网络运营组织。<br>\n基于 IP 的位置可能不准确。",
"ZH": "<b>大致位置</b><br>\n系统将检测到的公网IP发送至ipapi.co,<br>\n用于估算国家、城市、地区和网络运营组织。<br>\n基于IP的位置可能不准确。",
"isReviewed": "True"
},
"<b>Map</b><br>\nMap styles and tiles for the approximate area are loaded<br>\nfrom OpenFreeMap, using OpenStreetMap data.<br>\nOpenFreeMap receives these map requests.": {
@@ -3229,7 +3197,15 @@ TRANSLATION = {
"Furious.Window.SettingsPage"
],
"RU": "<b>Карта</b><br>\nСтили и тайлы карты приблизительной области<br>\nзагружаются из OpenFreeMap с использованием данных OpenStreetMap.<br>\nOpenFreeMap получает эти запросы карты.",
"ZH": "<b>地图</b><br>\n大致区域的地图样式和图块从 OpenFreeMap 加载,<br>\n并使用 OpenStreetMap 数据。<br>\nOpenFreeMap 会接收这些地图请求。",
"ZH": "<b>地图</b><br>\n大致区域的地图样式和图块从OpenFreeMap加载,<br>\n并使用OpenStreetMap数据。<br>\nOpenFreeMap会接收这些地图请求。",
"isReviewed": "True"
},
"Traffic Statistics": {
"source": [
"Furious.Window.MetricsPage"
],
"RU": "Статистика трафика",
"ZH": "流量统计",
"isReviewed": "True"
}
}
+5 -2
View File
@@ -262,13 +262,16 @@ def baseStyleSheet(palette):
image: none;
}}
QFrame#MetricsSection {{
QFrame#MetricsSection,
QFrame#TrafficMetricsCard {{
border: 1px solid {palette['border']};
border-radius: 10px;
background-color: {palette['panel']};
}}
QLabel#MetricsSectionTitle {{
QLabel#MetricsSectionTitle,
QLabel#TrafficMetricsCardTitle,
QLabel#MetricsDirectionTitle {{
color: {palette['text_strong']};
font-size: 12pt;
font-weight: 600;
+121 -74
View File
@@ -70,38 +70,104 @@ class _MetricCard(QFrame):
self.graph.setMetricLabel(title)
class _MetricsSection(QFrame):
"""Group speed and traffic-usage graphs for one direction."""
class _TrafficMetricsCard(QFrame):
"""Group selectors and all traffic graphs in one metrics card."""
def __init__(self, speedGraph, usageGraph, parent=None):
"""Initialize a two-card metrics section."""
def __init__(
self,
downloadSpeedGraph,
downloadUsageGraph,
uploadSpeedGraph,
uploadUsageGraph,
parent=None,
):
"""Initialize the combined download and upload metrics card."""
super().__init__(parent)
self.setObjectName('MetricsSection')
self.setObjectName('TrafficMetricsCard')
self.titleLabel = AppQLabel(translatable=False, parent=self)
self.titleLabel.setObjectName('MetricsSectionTitle')
self.titleLabel = AppQLabel(_('Traffic Statistics'), parent=self)
self.titleLabel.setObjectName('TrafficMetricsCardTitle')
self.speedCard = _MetricCard(speedGraph, parent=self)
self.usageCard = _MetricCard(usageGraph, parent=self)
self.timeRangeLabel, self.granularityLabel = (
AppQLabel(_('Time Range'), parent=self),
AppQLabel(_('Granularity'), parent=self),
)
graphLayout = QHBoxLayout()
graphLayout.setContentsMargins(0, 0, 0, 0)
graphLayout.setSpacing(12)
graphLayout.addWidget(self.speedCard, 1)
graphLayout.addWidget(self.usageCard, 1)
self.timeRangeComboBox, self.granularityComboBox = (
AppQComboBox(parent=self),
AppQComboBox(parent=self),
)
self.timeRangeComboBox.setMinimumWidth(160)
self.granularityComboBox.setMinimumWidth(130)
self.downloadTitleLabel, self.uploadTitleLabel = (
AppQLabel(translatable=False, parent=self),
AppQLabel(translatable=False, parent=self),
)
for titleLabel in (self.downloadTitleLabel, self.uploadTitleLabel):
titleLabel.setObjectName('MetricsDirectionTitle')
self.downloadSpeedCard, self.downloadUsageCard = (
_MetricCard(downloadSpeedGraph, parent=self),
_MetricCard(downloadUsageGraph, parent=self),
)
self.uploadSpeedCard, self.uploadUsageCard = (
_MetricCard(uploadSpeedGraph, parent=self),
_MetricCard(uploadUsageGraph, parent=self),
)
headerLayout = QHBoxLayout()
headerLayout.setContentsMargins(0, 0, 0, 0)
headerLayout.setSpacing(8)
headerLayout.addWidget(self.titleLabel)
headerLayout.addStretch(1)
headerLayout.addWidget(self.timeRangeLabel)
headerLayout.addWidget(self.timeRangeComboBox)
headerLayout.addSpacing(8)
headerLayout.addWidget(self.granularityLabel)
headerLayout.addWidget(self.granularityComboBox)
downloadLayout = QHBoxLayout()
downloadLayout.setContentsMargins(0, 0, 0, 0)
downloadLayout.setSpacing(12)
downloadLayout.addWidget(self.downloadSpeedCard, 1)
downloadLayout.addWidget(self.downloadUsageCard, 1)
uploadLayout = QHBoxLayout()
uploadLayout.setContentsMargins(0, 0, 0, 0)
uploadLayout.setSpacing(12)
uploadLayout.addWidget(self.uploadSpeedCard, 1)
uploadLayout.addWidget(self.uploadUsageCard, 1)
layout = QVBoxLayout(self)
layout.setContentsMargins(16, 14, 16, 16)
layout.setSpacing(10)
layout.addWidget(self.titleLabel)
layout.addLayout(graphLayout, 1)
layout.addLayout(headerLayout)
layout.addWidget(self.downloadTitleLabel)
layout.addLayout(downloadLayout, 1)
layout.addWidget(self.uploadTitleLabel)
layout.addLayout(uploadLayout, 1)
def setTitles(self, sectionTitle, speedTitle, usageTitle):
"""Set translated section and graph-card titles."""
self.titleLabel.setText(sectionTitle)
self.speedCard.setTitle(speedTitle)
self.usageCard.setTitle(usageTitle)
def setTitles(
self,
downloadTitle,
downloadSpeedTitle,
downloadUsageTitle,
uploadTitle,
uploadSpeedTitle,
uploadUsageTitle,
):
"""Set translated direction and graph-card titles."""
self.downloadTitleLabel.setText(downloadTitle)
self.downloadSpeedCard.setTitle(downloadSpeedTitle)
self.downloadUsageCard.setTitle(downloadUsageTitle)
self.uploadTitleLabel.setText(uploadTitle)
self.uploadSpeedCard.setTitle(uploadSpeedTitle)
self.uploadUsageCard.setTitle(uploadUsageTitle)
class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
@@ -136,34 +202,8 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
self._dirty = True
self._renderRevision = 0
(
self.pageTitleLabel,
self.timeRangeLabel,
self.granularityLabel,
self.timeRangeComboBox,
self.granularityComboBox,
) = (
AppQLabel(_('Network Metrics')),
AppQLabel(_('Time Range')),
AppQLabel(_('Granularity')),
AppQComboBox(),
AppQComboBox(),
)
self.pageTitleLabel = AppQLabel(_('Network Metrics'))
self.pageTitleLabel.setObjectName('MetricsPageTitle')
self.timeRangeComboBox.setMinimumWidth(160)
self.granularityComboBox.setMinimumWidth(130)
self._populateComboBox(
self.timeRangeComboBox,
self._timeRangeOptions(),
self.DefaultTimeRange,
)
self._populateComboBox(
self.granularityComboBox,
self._granularityOptions(),
self.DefaultGranularity,
)
(
self.downloadSpeedGraph,
@@ -189,15 +229,35 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
),
)
self.downloadSection, self.uploadSection = (
_MetricsSection(
self.downloadSpeedGraph,
self.downloadUsageGraph,
),
_MetricsSection(
self.uploadSpeedGraph,
self.uploadUsageGraph,
),
self.metricsCard = _TrafficMetricsCard(
self.downloadSpeedGraph,
self.downloadUsageGraph,
self.uploadSpeedGraph,
self.uploadUsageGraph,
parent=self,
)
(
self.timeRangeLabel,
self.granularityLabel,
self.timeRangeComboBox,
self.granularityComboBox,
) = (
self.metricsCard.timeRangeLabel,
self.metricsCard.granularityLabel,
self.metricsCard.timeRangeComboBox,
self.metricsCard.granularityComboBox,
)
self._populateComboBox(
self.timeRangeComboBox,
self._timeRangeOptions(),
self.DefaultTimeRange,
)
self._populateComboBox(
self.granularityComboBox,
self._granularityOptions(),
self.DefaultGranularity,
)
self.endpointInfoWidget = EndpointInfoWidget(
@@ -210,26 +270,14 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
self._endpointInfoEnabledChanged
)
controlsLayout = QHBoxLayout()
controlsLayout.setContentsMargins(0, 0, 0, 0)
controlsLayout.setSpacing(8)
controlsLayout.addWidget(self.pageTitleLabel)
controlsLayout.addStretch(1)
controlsLayout.addWidget(self.timeRangeLabel)
controlsLayout.addWidget(self.timeRangeComboBox)
controlsLayout.addSpacing(8)
controlsLayout.addWidget(self.granularityLabel)
controlsLayout.addWidget(self.granularityComboBox)
contentWidget = QWidget()
contentWidget.setObjectName('MetricsPageContent')
contentLayout = QVBoxLayout(contentWidget)
contentLayout.setContentsMargins(20, 18, 20, 20)
contentLayout.setSpacing(14)
contentLayout.addLayout(controlsLayout)
contentLayout.addWidget(self.downloadSection, 1)
contentLayout.addWidget(self.uploadSection, 1)
contentLayout.addWidget(self.pageTitleLabel)
contentLayout.addWidget(self.metricsCard, 1)
contentLayout.addWidget(self.endpointInfoWidget)
self.scrollArea = QScrollArea()
@@ -237,6 +285,7 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setFrameShape(QFrame.Shape.NoFrame)
self.scrollArea.setWidget(contentWidget)
self.setCentralWidget(self.scrollArea)
self._renderTimer = QtCore.QTimer(self)
@@ -401,12 +450,10 @@ class MetricsPage(Mixins.QTranslatable, Mixins.ThemeAware, QMainWindow):
def retranslate(self):
"""Refresh composite section, graph, and endpoint presentation text."""
self.downloadSection.setTitles(
self.metricsCard.setTitles(
_('Download'),
_('Download Speed'),
_('Download Traffic Usage'),
)
self.uploadSection.setTitles(
_('Upload'),
_('Upload Speed'),
_('Upload Traffic Usage'),