터치 패널 그래프와 상태 점멸 기준 추가
This commit is contained in:
1 parent
e99fe1021a
commit
209b587eac
2 files changed
+248
-27
No files matched your search
@@ -4,6 +4,29 @@ declare(strict_types=1);
|
|||||||
define('CONTROL_API_LIBRARY', true);
|
define('CONTROL_API_LIBRARY', true);
|
||||||
require_once __DIR__ . '/../public/api.php';
|
require_once __DIR__ . '/../public/api.php';
|
||||||
|
|
||||||
|
function touch_panel_io_counters(): array
|
||||||
|
{
|
||||||
|
$reads = 0;
|
||||||
|
$writes = 0;
|
||||||
|
foreach (@file('/proc/diskstats', FILE_IGNORE_NEW_LINES) ?: [] as $line) {
|
||||||
|
$parts = preg_split('/\s+/', trim($line));
|
||||||
|
if (count($parts) < 14) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = (string)$parts[2];
|
||||||
|
if (preg_match('/^(loop|ram|zram|dm-)/', $name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$reads += (int)$parts[3];
|
||||||
|
$writes += (int)$parts[7];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'reads' => $reads,
|
||||||
|
'writes' => $writes,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function touch_panel_snapshot(array $snapshot): array
|
function touch_panel_snapshot(array $snapshot): array
|
||||||
{
|
{
|
||||||
$network = is_array($snapshot['network'] ?? null) ? $snapshot['network'] : [];
|
$network = is_array($snapshot['network'] ?? null) ? $snapshot['network'] : [];
|
||||||
@@ -25,6 +48,10 @@ function touch_panel_snapshot(array $snapshot): array
|
|||||||
'tx_errors' => $row['tx_errors'] ?? 0,
|
'tx_errors' => $row['tx_errors'] ?? 0,
|
||||||
'rx_dropped' => $row['rx_dropped'] ?? 0,
|
'rx_dropped' => $row['rx_dropped'] ?? 0,
|
||||||
'tx_dropped' => $row['tx_dropped'] ?? 0,
|
'tx_dropped' => $row['tx_dropped'] ?? 0,
|
||||||
|
'rx_bytes' => $row['rx_bytes'] ?? 0,
|
||||||
|
'tx_bytes' => $row['tx_bytes'] ?? 0,
|
||||||
|
'rx_packets' => $row['rx_packets'] ?? 0,
|
||||||
|
'tx_packets' => $row['tx_packets'] ?? 0,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +90,27 @@ function touch_panel_snapshot(array $snapshot): array
|
|||||||
'touch_display' => [
|
'touch_display' => [
|
||||||
'connected' => $touch['connected'] ?? false,
|
'connected' => $touch['connected'] ?? false,
|
||||||
],
|
],
|
||||||
|
'io' => touch_panel_io_counters(),
|
||||||
|
'history' => array_map(static function (array $row): array {
|
||||||
|
return [
|
||||||
|
'time' => $row['time'] ?? null,
|
||||||
|
'temp_c' => $row['temp_c'] ?? null,
|
||||||
|
'fan_rpm' => $row['fan_rpm'] ?? null,
|
||||||
|
'fan_pwm' => $row['fan_pwm'] ?? null,
|
||||||
|
'cpu_load_1' => $row['cpu_load_1'] ?? null,
|
||||||
|
'mem_percent' => $row['mem_percent'] ?? null,
|
||||||
|
];
|
||||||
|
}, array_slice(array_values(array_filter($snapshot['history'] ?? [], 'is_array')), -80)),
|
||||||
|
'network_history' => array_map(static function (array $row): array {
|
||||||
|
return [
|
||||||
|
'time' => $row['time'] ?? null,
|
||||||
|
'iface' => $row['iface'] ?? null,
|
||||||
|
'rx_mbps' => $row['rx_mbps'] ?? 0,
|
||||||
|
'tx_mbps' => $row['tx_mbps'] ?? 0,
|
||||||
|
'rx_pps' => $row['rx_pps'] ?? 0,
|
||||||
|
'tx_pps' => $row['tx_pps'] ?? 0,
|
||||||
|
];
|
||||||
|
}, array_slice(array_values(array_filter($snapshot['network_history'] ?? [], 'is_array')), -240)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+200
-27
@@ -4,11 +4,12 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer
|
from PyQt5.QtCore import QPointF, Qt, QTimer
|
||||||
from PyQt5.QtGui import QColor, QFont, QPainter
|
from PyQt5.QtGui import QColor, QFont, QPainter, QPainterPath, QPen
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
QFrame,
|
QFrame,
|
||||||
|
QGridLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
@@ -118,12 +119,105 @@ class FanButton(QPushButton):
|
|||||||
self.setMinimumHeight(44)
|
self.setMinimumHeight(44)
|
||||||
|
|
||||||
|
|
||||||
|
class MetricCard(QFrame):
|
||||||
|
def __init__(self, title):
|
||||||
|
super().__init__()
|
||||||
|
self.setObjectName("metricCard")
|
||||||
|
self.title = QLabel(title)
|
||||||
|
self.title.setObjectName("metricTitle")
|
||||||
|
self.value = QLabel("-")
|
||||||
|
self.value.setObjectName("metricValue")
|
||||||
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(10, 8, 10, 8)
|
||||||
|
layout.setSpacing(2)
|
||||||
|
layout.addWidget(self.title)
|
||||||
|
layout.addWidget(self.value)
|
||||||
|
|
||||||
|
def set_value(self, text):
|
||||||
|
self.value.setText(text)
|
||||||
|
|
||||||
|
|
||||||
|
class Sparkline(QFrame):
|
||||||
|
def __init__(self, title, color="#38bdf8", second_color=None):
|
||||||
|
super().__init__()
|
||||||
|
self.setObjectName("chartCard")
|
||||||
|
self.color = QColor(color)
|
||||||
|
self.second_color = QColor(second_color) if second_color else None
|
||||||
|
self.values = []
|
||||||
|
self.second_values = []
|
||||||
|
self.title = QLabel(title)
|
||||||
|
self.title.setObjectName("chartTitle")
|
||||||
|
self.caption = QLabel("-")
|
||||||
|
self.caption.setObjectName("chartCaption")
|
||||||
|
self.canvas = QWidget()
|
||||||
|
self.canvas.paintEvent = self.paint_chart
|
||||||
|
self.canvas.setMinimumHeight(58)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(10, 8, 10, 8)
|
||||||
|
layout.setSpacing(4)
|
||||||
|
top = QHBoxLayout()
|
||||||
|
top.addWidget(self.title)
|
||||||
|
top.addStretch(1)
|
||||||
|
top.addWidget(self.caption)
|
||||||
|
layout.addLayout(top)
|
||||||
|
layout.addWidget(self.canvas, 1)
|
||||||
|
|
||||||
|
def set_values(self, values, caption, second_values=None):
|
||||||
|
self.values = [float(v or 0) for v in values][-80:]
|
||||||
|
self.second_values = [float(v or 0) for v in (second_values or [])][-80:]
|
||||||
|
self.caption.setText(caption)
|
||||||
|
self.canvas.update()
|
||||||
|
|
||||||
|
def paint_chart(self, event):
|
||||||
|
del event
|
||||||
|
painter = QPainter(self.canvas)
|
||||||
|
painter.setRenderHint(QPainter.Antialiasing)
|
||||||
|
rect = self.canvas.rect().adjusted(2, 4, -2, -4)
|
||||||
|
painter.fillRect(rect, QColor("#0f172a"))
|
||||||
|
painter.setPen(QPen(QColor("#263650"), 1))
|
||||||
|
painter.drawLine(rect.left(), rect.bottom(), rect.right(), rect.bottom())
|
||||||
|
|
||||||
|
all_values = self.values + self.second_values
|
||||||
|
if len(all_values) < 2:
|
||||||
|
return
|
||||||
|
low = min(all_values)
|
||||||
|
high = max(all_values)
|
||||||
|
if high <= low:
|
||||||
|
high = low + 1
|
||||||
|
|
||||||
|
self.draw_series(painter, rect, self.values, low, high, self.color, 2)
|
||||||
|
if self.second_color and self.second_values:
|
||||||
|
self.draw_series(painter, rect, self.second_values, low, high, self.second_color, 2)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def draw_series(painter, rect, values, low, high, color, width):
|
||||||
|
if len(values) < 2:
|
||||||
|
return
|
||||||
|
path = QPainterPath()
|
||||||
|
for index, value in enumerate(values):
|
||||||
|
x = rect.left() + (rect.width() * index / max(1, len(values) - 1))
|
||||||
|
ratio = (float(value) - low) / (high - low)
|
||||||
|
y = rect.bottom() - (rect.height() * ratio)
|
||||||
|
point = QPointF(x, y)
|
||||||
|
if index == 0:
|
||||||
|
path.moveTo(point)
|
||||||
|
else:
|
||||||
|
path.lineTo(point)
|
||||||
|
painter.setPen(QPen(color, width))
|
||||||
|
painter.drawPath(path)
|
||||||
|
|
||||||
|
|
||||||
class TouchPanel(QMainWindow):
|
class TouchPanel(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.snapshot = {}
|
self.snapshot = {}
|
||||||
self.rows = {}
|
self.rows = {}
|
||||||
self.mode_buttons = {}
|
self.mode_buttons = {}
|
||||||
|
self.metrics = {}
|
||||||
|
self.charts = {}
|
||||||
|
self.last_counters = {}
|
||||||
self.current_mode = "auto"
|
self.current_mode = "auto"
|
||||||
self.current_pwm = 120
|
self.current_pwm = 120
|
||||||
self.in_flight = False
|
self.in_flight = False
|
||||||
@@ -170,14 +264,13 @@ class TouchPanel(QMainWindow):
|
|||||||
left_layout.addWidget(row)
|
left_layout.addWidget(row)
|
||||||
left_layout.addStretch(1)
|
left_layout.addStretch(1)
|
||||||
|
|
||||||
right = QFrame()
|
right = QWidget()
|
||||||
right.setObjectName("panel")
|
|
||||||
right_layout = QVBoxLayout(right)
|
right_layout = QVBoxLayout(right)
|
||||||
right_layout.setContentsMargins(16, 14, 16, 14)
|
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
right_layout.setSpacing(12)
|
right_layout.setSpacing(8)
|
||||||
|
|
||||||
header = QHBoxLayout()
|
header = QHBoxLayout()
|
||||||
fan_title = QLabel("팬")
|
fan_title = QLabel("Control")
|
||||||
fan_title.setObjectName("sectionTitle")
|
fan_title.setObjectName("sectionTitle")
|
||||||
self.clock = QLabel("--:--:--")
|
self.clock = QLabel("--:--:--")
|
||||||
self.clock.setObjectName("clock")
|
self.clock.setObjectName("clock")
|
||||||
@@ -186,15 +279,19 @@ class TouchPanel(QMainWindow):
|
|||||||
header.addWidget(self.clock)
|
header.addWidget(self.clock)
|
||||||
right_layout.addLayout(header)
|
right_layout.addLayout(header)
|
||||||
|
|
||||||
self.fan_rpm = QLabel("- RPM")
|
metric_grid = QGridLayout()
|
||||||
self.fan_rpm.setObjectName("bigValue")
|
metric_grid.setHorizontalSpacing(8)
|
||||||
self.fan_temp = QLabel("- C")
|
metric_grid.setVerticalSpacing(8)
|
||||||
self.fan_temp.setObjectName("subValue")
|
for index, (key, title) in enumerate([
|
||||||
self.fan_pwm = QLabel("- PWM")
|
("rpm", "팬 RPM"),
|
||||||
self.fan_pwm.setObjectName("subValue")
|
("temp", "판단 온도"),
|
||||||
right_layout.addWidget(self.fan_rpm)
|
("pwm", "PWM"),
|
||||||
right_layout.addWidget(self.fan_temp)
|
("wan", "WAN"),
|
||||||
right_layout.addWidget(self.fan_pwm)
|
]):
|
||||||
|
card = MetricCard(title)
|
||||||
|
self.metrics[key] = card
|
||||||
|
metric_grid.addWidget(card, index // 4, index % 4)
|
||||||
|
right_layout.addLayout(metric_grid)
|
||||||
|
|
||||||
mode_layout = QHBoxLayout()
|
mode_layout = QHBoxLayout()
|
||||||
for mode in ["auto", "manual", "off"]:
|
for mode in ["auto", "manual", "off"]:
|
||||||
@@ -221,6 +318,19 @@ class TouchPanel(QMainWindow):
|
|||||||
slider_wrap.addWidget(plus)
|
slider_wrap.addWidget(plus)
|
||||||
right_layout.addLayout(slider_wrap)
|
right_layout.addLayout(slider_wrap)
|
||||||
|
|
||||||
|
chart_grid = QGridLayout()
|
||||||
|
chart_grid.setHorizontalSpacing(8)
|
||||||
|
chart_grid.setVerticalSpacing(8)
|
||||||
|
for index, (key, chart) in enumerate([
|
||||||
|
("temp", Sparkline("온도 / RPM", "#f97316", "#38bdf8")),
|
||||||
|
("wan", Sparkline("WAN 트래픽", "#38bdf8", "#22c55e")),
|
||||||
|
("lan", Sparkline("LAN 트래픽", "#38bdf8", "#22c55e")),
|
||||||
|
("wifi", Sparkline("WiFi 트래픽", "#38bdf8", "#22c55e")),
|
||||||
|
]):
|
||||||
|
self.charts[key] = chart
|
||||||
|
chart_grid.addWidget(chart, index // 2, index % 2)
|
||||||
|
right_layout.addLayout(chart_grid, 1)
|
||||||
|
|
||||||
self.notice = QLabel("프로토타입")
|
self.notice = QLabel("프로토타입")
|
||||||
self.notice.setObjectName("notice")
|
self.notice.setObjectName("notice")
|
||||||
self.notice.setWordWrap(True)
|
self.notice.setWordWrap(True)
|
||||||
@@ -275,6 +385,9 @@ class TouchPanel(QMainWindow):
|
|||||||
network = data.get("network", {})
|
network = data.get("network", {})
|
||||||
wifi = data.get("wifi", {})
|
wifi = data.get("wifi", {})
|
||||||
touch = data.get("touch_display", {})
|
touch = data.get("touch_display", {})
|
||||||
|
io = data.get("io", {})
|
||||||
|
history = data.get("history", [])
|
||||||
|
network_history = data.get("network_history", [])
|
||||||
|
|
||||||
interfaces = {row.get("name"): row for row in network.get("interfaces", [])}
|
interfaces = {row.get("name"): row for row in network.get("interfaces", [])}
|
||||||
clients = wifi.get("clients", [])
|
clients = wifi.get("clients", [])
|
||||||
@@ -291,25 +404,74 @@ class TouchPanel(QMainWindow):
|
|||||||
w24 = interfaces.get("wlan0") or {}
|
w24 = interfaces.get("wlan0") or {}
|
||||||
w5 = interfaces.get("wlan1") or {}
|
w5 = interfaces.get("wlan1") or {}
|
||||||
|
|
||||||
io_active = bool(touch.get("connected")) and bool(fan.get("policy_ok", True))
|
io_active = bool(touch.get("connected")) and bool(fan.get("policy_ok", True)) and self.counter_activity(
|
||||||
|
"io",
|
||||||
|
int(io.get("reads") or 0),
|
||||||
|
int(io.get("writes") or 0),
|
||||||
|
)
|
||||||
io_detail = f"{temp:.1f} C"
|
io_detail = f"{temp:.1f} C"
|
||||||
self.rows["io"].update_state(io_active, io_detail, temp >= 75)
|
self.rows["io"].update_state(io_active, io_detail, temp >= 75)
|
||||||
self.rows["wan"].update_state(self.iface_up(wan), self.iface_detail(wan), self.iface_warn(wan))
|
self.rows["wan"].update_state(self.iface_activity("wan", wan), self.iface_detail(wan), self.iface_warn(wan))
|
||||||
self.rows["lan"].update_state(self.iface_up(lan), f"{len(lan_clients)}대 {self.iface_detail(lan)}", self.iface_warn(lan))
|
self.rows["lan"].update_state(self.iface_activity("lan", lan), f"{len(lan_clients)}대 {self.iface_detail(lan)}", self.iface_warn(lan))
|
||||||
self.rows["wifi24"].update_state(self.iface_up(w24), f"{len(wifi24)}대 {self.iface_detail(w24)}", self.iface_warn(w24))
|
self.rows["wifi24"].update_state(self.iface_activity("wifi24", w24), f"{len(wifi24)}대 {self.iface_detail(w24)}", self.iface_warn(w24))
|
||||||
self.rows["wifi5"].update_state(self.iface_up(w5), f"{len(wifi5)}대 {self.iface_detail(w5)}", self.iface_warn(w5))
|
self.rows["wifi5"].update_state(self.iface_activity("wifi5", w5), f"{len(wifi5)}대 {self.iface_detail(w5)}", self.iface_warn(w5))
|
||||||
|
|
||||||
self.current_mode = mode
|
self.current_mode = mode
|
||||||
self.current_pwm = pwm
|
self.current_pwm = pwm
|
||||||
self.fan_rpm.setText(f"{rpm:,} RPM")
|
control_temp = float(fan.get("control_temp_c") or temp)
|
||||||
self.fan_temp.setText(f"판단 온도 {float(fan.get('control_temp_c') or temp):.1f} C")
|
self.metrics["rpm"].set_value(f"{rpm:,}")
|
||||||
self.fan_pwm.setText(f"{pwm} PWM / {float(fan.get('percent') or 0):.1f}%")
|
self.metrics["temp"].set_value(f"{control_temp:.1f} C")
|
||||||
|
self.metrics["pwm"].set_value(f"{pwm} / {float(fan.get('percent') or 0):.1f}%")
|
||||||
|
self.metrics["wan"].set_value(self.traffic_caption(wan))
|
||||||
self.slider.blockSignals(True)
|
self.slider.blockSignals(True)
|
||||||
self.slider.setValue(pwm)
|
self.slider.setValue(pwm)
|
||||||
self.slider.blockSignals(False)
|
self.slider.blockSignals(False)
|
||||||
for key, btn in self.mode_buttons.items():
|
for key, btn in self.mode_buttons.items():
|
||||||
btn.setChecked(key == mode)
|
btn.setChecked(key == mode)
|
||||||
self.clock.setText(str(data.get("generated_at", "--:--:--")).split(" ")[-1])
|
self.clock.setText(str(data.get("generated_at", "--:--:--")).split(" ")[-1])
|
||||||
|
self.update_charts(history, network_history, network.get("default_interface") or "eth0")
|
||||||
|
|
||||||
|
def iface_activity(self, key, row):
|
||||||
|
if not self.iface_up(row):
|
||||||
|
self.last_counters[key] = None
|
||||||
|
return False
|
||||||
|
return self.counter_activity(
|
||||||
|
key,
|
||||||
|
int(row.get("rx_bytes") or 0),
|
||||||
|
int(row.get("tx_bytes") or 0),
|
||||||
|
int(row.get("rx_packets") or 0),
|
||||||
|
int(row.get("tx_packets") or 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
def counter_activity(self, key, *values):
|
||||||
|
current = tuple(values)
|
||||||
|
previous = self.last_counters.get(key)
|
||||||
|
self.last_counters[key] = current
|
||||||
|
return previous is not None and current != previous
|
||||||
|
|
||||||
|
def update_charts(self, history, network_history, wan_iface):
|
||||||
|
temps = [row.get("temp_c") for row in history if isinstance(row, dict)]
|
||||||
|
rpms = [float(row.get("fan_rpm") or 0) / 100 for row in history if isinstance(row, dict)]
|
||||||
|
self.charts["temp"].set_values(temps, "C / RPMx100", rpms)
|
||||||
|
|
||||||
|
wan_rows = self.iface_history(network_history, [wan_iface])
|
||||||
|
lan_rows = self.iface_history(network_history, ["br0", "eth1"])
|
||||||
|
wifi_rows = self.iface_history(network_history, ["wlan0", "wlan1"])
|
||||||
|
self.set_traffic_chart("wan", wan_rows)
|
||||||
|
self.set_traffic_chart("lan", lan_rows)
|
||||||
|
self.set_traffic_chart("wifi", wifi_rows)
|
||||||
|
|
||||||
|
def set_traffic_chart(self, key, rows):
|
||||||
|
rx = [float(row.get("rx_mbps") or 0) for row in rows]
|
||||||
|
tx = [float(row.get("tx_mbps") or 0) for row in rows]
|
||||||
|
latest_rx = rx[-1] if rx else 0
|
||||||
|
latest_tx = tx[-1] if tx else 0
|
||||||
|
self.charts[key].set_values(rx, f"{latest_rx:.2f}/{latest_tx:.2f} Mbps", tx)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def iface_history(rows, names):
|
||||||
|
names = set(names)
|
||||||
|
return [row for row in rows if isinstance(row, dict) and row.get("iface") in names]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def iface_up(row):
|
def iface_up(row):
|
||||||
@@ -327,20 +489,31 @@ class TouchPanel(QMainWindow):
|
|||||||
tx_drop = int(row.get("tx_dropped") or 0)
|
tx_drop = int(row.get("tx_dropped") or 0)
|
||||||
return f"{row.get('state', '-')} D{rx_drop + tx_drop}"
|
return f"{row.get('state', '-')} D{rx_drop + tx_drop}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def traffic_caption(row):
|
||||||
|
if not row:
|
||||||
|
return "-"
|
||||||
|
rx = int(row.get("rx_bytes") or 0)
|
||||||
|
tx = int(row.get("tx_bytes") or 0)
|
||||||
|
return f"{rx / 1024 / 1024:.0f}/{tx / 1024 / 1024:.0f}MB"
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
app.setStyleSheet(
|
app.setStyleSheet(
|
||||||
"""
|
"""
|
||||||
QWidget { background: #0b1220; color: #e5edf7; font-family: "Noto Sans CJK KR", "Noto Sans KR", sans-serif; }
|
QWidget { background: #0b1220; color: #e5edf7; font-family: "Noto Sans CJK KR", "Noto Sans CJK KR Regular", "Noto Sans KR", sans-serif; }
|
||||||
#panel { background: #111a2e; border: 1px solid #27364f; border-radius: 16px; }
|
#panel { background: #111a2e; border: 1px solid #27364f; border-radius: 16px; }
|
||||||
#statusPanel { background: #101827; border-right: 1px solid #263650; }
|
#statusPanel { background: #101827; border-right: 1px solid #263650; }
|
||||||
#sectionTitle { font-size: 20px; font-weight: 700; }
|
#sectionTitle { font-size: 20px; font-weight: 700; }
|
||||||
#statusTile { background: #d7d9dc; border: 2px groove #f4f4f4; border-radius: 2px; }
|
#statusTile { background: #d7d9dc; border: 2px groove #f4f4f4; border-radius: 2px; }
|
||||||
#statusTitle { font-size: 13px; font-weight: 700; color: #111827; background: transparent; }
|
#statusTitle { font-size: 13px; font-weight: 700; color: #111827; background: transparent; }
|
||||||
#statusDetail { font-size: 10px; color: #475569; background: transparent; }
|
#statusDetail { font-size: 10px; color: #475569; background: transparent; }
|
||||||
#bigValue { font-size: 62px; font-weight: 800; color: #f8fafc; }
|
#metricCard, #chartCard { background: #111a2e; border: 1px solid #27364f; border-radius: 12px; }
|
||||||
#subValue { font-size: 22px; color: #b8c5d8; }
|
#metricTitle { color: #8fa7c5; font-size: 13px; background: transparent; }
|
||||||
|
#metricValue { color: #f8fafc; font-size: 24px; font-weight: 800; background: transparent; }
|
||||||
|
#chartTitle { color: #dce8f8; font-size: 14px; font-weight: 700; background: transparent; }
|
||||||
|
#chartCaption { color: #93a4bd; font-size: 11px; background: transparent; }
|
||||||
#clock { font-size: 18px; color: #8fb3ff; font-weight: 700; }
|
#clock { font-size: 18px; color: #8fb3ff; font-weight: 700; }
|
||||||
#notice { padding: 10px; background: #17233a; border: 1px solid #2b3c59; border-radius: 12px; color: #a8b8d0; }
|
#notice { padding: 10px; background: #17233a; border: 1px solid #2b3c59; border-radius: 12px; color: #a8b8d0; }
|
||||||
QPushButton { min-height: 42px; border: 1px solid #33445f; border-radius: 14px; background: #1a2942; color: #dce8f8; font-size: 18px; font-weight: 700; }
|
QPushButton { min-height: 42px; border: 1px solid #33445f; border-radius: 14px; background: #1a2942; color: #dce8f8; font-size: 18px; font-weight: 700; }
|
||||||
|
|||||||
Reference in new issue
Block a user