feat(dashboard): more System History metrics, persistence & localized labels

- Sample swap %, TCP/UDP connection counts and disk-usage % on the host ticker
- System History: Swap overlaid on the RAM tab, plus new Connections and Disk Usage tabs
- Persist the host time-series across restarts: gob snapshot beside the DB, written on a timer and at shutdown, restored on boot
- Live-refresh the open chart (2s for short ranges, 10s for longer)
- Localize CPU/RAM/Swap and the new tab/chart titles across all 13 languages and route legend series names through i18n
This commit is contained in:
MHSanaei 2026-06-03 12:16:31 +02:00
parent 4b11c54206
commit d4c020f365
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
18 changed files with 203 additions and 33 deletions

View file

@ -3,12 +3,14 @@ import type { ReactNode } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Modal, Select, Tabs } from 'antd'; import { Modal, Select, Tabs } from 'antd';
import { import {
ApiOutlined,
DashboardOutlined, DashboardOutlined,
DatabaseOutlined, DatabaseOutlined,
DeploymentUnitOutlined, DeploymentUnitOutlined,
GlobalOutlined, GlobalOutlined,
HddOutlined, HddOutlined,
LineChartOutlined, LineChartOutlined,
PieChartOutlined,
TeamOutlined, TeamOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
@ -43,11 +45,13 @@ interface MetricDef {
} }
const METRICS: MetricDef[] = [ const METRICS: MetricDef[] = [
{ key: 'cpu', tab: 'CPU', title: 'pages.index.historyTitleCpu', icon: <DashboardOutlined />, valueMax: 100, unit: '%', stroke: '' }, { key: 'cpu', tab: 'CPU', tabKey: 'pages.index.cpu', title: 'pages.index.historyTitleCpu', icon: <DashboardOutlined />, valueMax: 100, unit: '%', stroke: '' },
{ key: 'mem', tab: 'RAM', title: 'pages.index.historyTitleMem', icon: <DatabaseOutlined />, valueMax: 100, unit: '%', stroke: '#7c4dff' }, { key: 'mem', tab: 'RAM', tabKey: 'pages.index.memory', title: 'pages.index.historyTitleMem', icon: <DatabaseOutlined />, valueMax: 100, unit: '%', stroke: '#7c4dff', key2: 'swap', stroke2: '#ffa940', name1: 'pages.index.memory', name2: 'pages.index.swap' },
{ key: 'netUp', tab: 'Bandwidth', tabKey: 'pages.index.historyTabBandwidth', title: 'pages.index.historyTitleNetwork', icon: <GlobalOutlined />, valueMax: null, unit: 'B/s', stroke: '#1890ff', key2: 'netDown', stroke2: '#13c2c2', name1: 'Up', name2: 'Down' }, { key: 'netUp', tab: 'Bandwidth', tabKey: 'pages.index.historyTabBandwidth', title: 'pages.index.historyTitleNetwork', icon: <GlobalOutlined />, valueMax: null, unit: 'B/s', stroke: '#1890ff', key2: 'netDown', stroke2: '#13c2c2', name1: 'Up', name2: 'Down' },
{ key: 'pktUp', tab: 'Packets', tabKey: 'pages.index.historyTabPackets', title: 'pages.index.historyTitlePackets', icon: <DeploymentUnitOutlined />, valueMax: null, unit: 'pkt/s', stroke: '#2f54eb', key2: 'pktDown', stroke2: '#36cfc9', name1: 'Up', name2: 'Down' }, { key: 'pktUp', tab: 'Packets', tabKey: 'pages.index.historyTabPackets', title: 'pages.index.historyTitlePackets', icon: <DeploymentUnitOutlined />, valueMax: null, unit: 'pkt/s', stroke: '#2f54eb', key2: 'pktDown', stroke2: '#36cfc9', name1: 'Up', name2: 'Down' },
{ key: 'tcpCount', tab: 'Connections', tabKey: 'pages.index.historyTabConnections', title: 'pages.index.historyTitleConnections', icon: <ApiOutlined />, valueMax: null, unit: '', stroke: '#597ef7', key2: 'udpCount', stroke2: '#73d13d', name1: 'TCP', name2: 'UDP' },
{ key: 'diskRead', tab: 'Disk I/O', tabKey: 'pages.index.historyTabDisk', title: 'pages.index.historyTitleDisk', icon: <HddOutlined />, valueMax: null, unit: 'B/s', stroke: '#eb2f96', key2: 'diskWrite', stroke2: '#722ed1', name1: 'Read', name2: 'Write' }, { key: 'diskRead', tab: 'Disk I/O', tabKey: 'pages.index.historyTabDisk', title: 'pages.index.historyTitleDisk', icon: <HddOutlined />, valueMax: null, unit: 'B/s', stroke: '#eb2f96', key2: 'diskWrite', stroke2: '#722ed1', name1: 'Read', name2: 'Write' },
{ key: 'diskUsage', tab: 'Disk Usage', tabKey: 'pages.index.historyTabDiskUsage', title: 'pages.index.historyTitleDiskUsage', icon: <PieChartOutlined />, valueMax: 100, unit: '%', stroke: '#13c2c2' },
{ key: 'online', tab: 'Online', tabKey: 'pages.index.historyTabOnline', title: 'pages.index.historyTitleOnline', icon: <TeamOutlined />, valueMax: null, unit: '', stroke: '#52c41a' }, { key: 'online', tab: 'Online', tabKey: 'pages.index.historyTabOnline', title: 'pages.index.historyTitleOnline', icon: <TeamOutlined />, valueMax: null, unit: '', stroke: '#52c41a' },
{ key: 'load1', tab: 'Load', tabKey: 'pages.index.historyTabLoad', title: 'pages.index.historyTitleLoad', icon: <LineChartOutlined />, valueMax: null, unit: '', stroke: '#fa8c16', key2: 'load5', stroke2: '#f5222d', name1: '1m', name2: '5m', key3: 'load15', stroke3: '#a0d911', name3: '15m' }, { key: 'load1', tab: 'Load', tabKey: 'pages.index.historyTabLoad', title: 'pages.index.historyTitleLoad', icon: <LineChartOutlined />, valueMax: null, unit: '', stroke: '#fa8c16', key2: 'load5', stroke2: '#f5222d', name1: '1m', name2: '5m', key3: 'load15', stroke3: '#a0d911', name3: '15m' },
]; ];
@ -64,7 +68,9 @@ function unitFormatter(unit: string, activeKey: string): (v: number) => string {
} }
return (v) => { return (v) => {
const n = Number(v) || 0; const n = Number(v) || 0;
if (activeKey === 'online') return String(Math.round(n)); if (activeKey === 'online' || activeKey === 'tcpCount' || activeKey === 'udpCount') {
return Math.round(n).toLocaleString();
}
return n.toFixed(2); return n.toFixed(2);
}; };
} }
@ -97,6 +103,7 @@ export default function SystemHistoryModal({ open, status, onClose }: SystemHist
const [timestamps, setTimestamps] = useState<number[]>([]); const [timestamps, setTimestamps] = useState<number[]>([]);
const activeMetric = useMemo(() => METRICS.find((m) => m.key === activeKey), [activeKey]); const activeMetric = useMemo(() => METRICS.find((m) => m.key === activeKey), [activeKey]);
const trName = (n?: string) => (n && n.startsWith('pages.') ? t(n) : n);
const strokeColor = activeMetric?.stroke || status?.cpu?.color || '#008771'; const strokeColor = activeMetric?.stroke || status?.cpu?.color || '#008771';
const yFormatter = useMemo( const yFormatter = useMemo(
() => unitFormatter(activeMetric?.unit ?? '', activeKey), () => unitFormatter(activeMetric?.unit ?? '', activeKey),
@ -178,6 +185,13 @@ export default function SystemHistoryModal({ open, status, onClose }: SystemHist
if (open) fetchBucket(); if (open) fetchBucket();
}, [open, activeKey, bucket, fetchBucket]); }, [open, activeKey, bucket, fetchBucket]);
useEffect(() => {
if (!open) return undefined;
const ms = bucket <= 30 ? 2000 : 10000;
const id = window.setInterval(() => fetchBucket(), ms);
return () => window.clearInterval(id);
}, [open, bucket, fetchBucket]);
return ( return (
<Modal <Modal
open={open} open={open}
@ -226,9 +240,9 @@ export default function SystemHistoryModal({ open, status, onClose }: SystemHist
data3={activeMetric?.key3 ? points3 : undefined} data3={activeMetric?.key3 ? points3 : undefined}
stroke2={activeMetric?.stroke2} stroke2={activeMetric?.stroke2}
stroke3={activeMetric?.stroke3} stroke3={activeMetric?.stroke3}
name1={activeMetric?.name1} name1={trName(activeMetric?.name1)}
name2={activeMetric?.name2} name2={trName(activeMetric?.name2)}
name3={activeMetric?.name3} name3={trName(activeMetric?.name3)}
labels={labels} labels={labels}
height={260} height={260}
stroke={strokeColor} stroke={strokeColor}

View file

@ -33,6 +33,7 @@ type ServerController struct {
// NewServerController creates a new ServerController, initializes routes, and starts background tasks. // NewServerController creates a new ServerController, initializes routes, and starts background tasks.
func NewServerController(g *gin.RouterGroup) *ServerController { func NewServerController(g *gin.RouterGroup) *ServerController {
a := &ServerController{} a := &ServerController{}
service.RestoreSystemMetrics()
a.initRouter(g) a.initRouter(g)
a.startTask() a.startTask()
return a return a
@ -84,6 +85,11 @@ func (a *ServerController) startTask() {
a.xrayMetricsService.Sample(time.Now()) a.xrayMetricsService.Sample(time.Now())
websocket.BroadcastStatus(status) websocket.BroadcastStatus(status)
}) })
c.AddFunc("@every 1m", func() {
if err := service.PersistSystemMetrics(); err != nil {
logger.Warning("persist system metrics failed:", err)
}
})
} }
// status returns the current server status information. // status returns the current server status information.

View file

@ -1,8 +1,14 @@
package service package service
import ( import (
"encoding/gob"
"os"
"path/filepath"
"sync" "sync"
"time" "time"
"github.com/mhsanaei/3x-ui/v3/config"
"github.com/mhsanaei/3x-ui/v3/logger"
) )
// MetricSample is one point of any time-series we keep in memory. // MetricSample is one point of any time-series we keep in memory.
@ -59,6 +65,34 @@ func (h *metricHistory) drop(metric string) {
h.mu.Unlock() h.mu.Unlock()
} }
// snapshot returns a deep copy of every series, safe to serialize without
// holding the lock during disk I/O.
func (h *metricHistory) snapshot() map[string][]MetricSample {
h.mu.Lock()
defer h.mu.Unlock()
out := make(map[string][]MetricSample, len(h.metrics))
for k, v := range h.metrics {
cp := make([]MetricSample, len(v))
copy(cp, v)
out[k] = cp
}
return out
}
// restore replaces the in-memory series with a previously persisted set,
// re-applying the per-series capacity cap so a tampered or oversized file
// can't grow the working set unbounded.
func (h *metricHistory) restore(data map[string][]MetricSample) {
h.mu.Lock()
defer h.mu.Unlock()
for k, v := range data {
if len(v) > metricCapacityDefault {
v = v[len(v)-metricCapacityDefault:]
}
h.metrics[k] = v
}
}
// aggregate returns up to maxPoints buckets of size bucketSeconds, // aggregate returns up to maxPoints buckets of size bucketSeconds,
// each bucket carrying the arithmetic mean of the underlying samples. // each bucket carrying the arithmetic mean of the underlying samples.
// Bucket alignment is to absolute Unix-second boundaries so two // Bucket alignment is to absolute Unix-second boundaries so two
@ -137,7 +171,7 @@ var (
// status sample. Exposed for documentation/test purposes; the // status sample. Exposed for documentation/test purposes; the
// controller validates incoming names against an allow-list. // controller validates incoming names against an allow-list.
var SystemMetricKeys = []string{ var SystemMetricKeys = []string{
"cpu", "mem", "netUp", "netDown", "pktUp", "pktDown", "diskRead", "diskWrite", "online", "load1", "load5", "load15", "cpu", "mem", "swap", "netUp", "netDown", "pktUp", "pktDown", "diskRead", "diskWrite", "diskUsage", "tcpCount", "udpCount", "online", "load1", "load5", "load15",
} }
// NodeMetricKeys lists the per-node metric names NodeHeartbeatJob writes. // NodeMetricKeys lists the per-node metric names NodeHeartbeatJob writes.
@ -150,3 +184,54 @@ var NodeMetricKeys = []string{"cpu", "mem"}
var XrayMetricKeys = []string{ var XrayMetricKeys = []string{
"xrAlloc", "xrSys", "xrHeapObjects", "xrNumGC", "xrPauseNs", "xrAlloc", "xrSys", "xrHeapObjects", "xrNumGC", "xrPauseNs",
} }
// systemMetricsStorePath is where the host time-series is persisted between
// restarts. It lives next to the database so a single volume mount carries
// both. Only systemMetrics is persisted — node and xray series are cheap to
// rebuild and tied to live connections.
func systemMetricsStorePath() string {
return filepath.Join(config.GetDBFolderPath(), "system_metrics.gob")
}
// PersistSystemMetrics writes the host time-series to disk via a temp file +
// rename so a crash mid-write can't corrupt the previous snapshot. Called on a
// timer and at shutdown.
func PersistSystemMetrics() error {
path := systemMetricsStorePath()
tmp := path + ".tmp"
f, err := os.Create(tmp)
if err != nil {
return err
}
if err := gob.NewEncoder(f).Encode(systemMetrics.snapshot()); err != nil {
f.Close()
os.Remove(tmp)
return err
}
if err := f.Close(); err != nil {
os.Remove(tmp)
return err
}
return os.Rename(tmp, path)
}
// RestoreSystemMetrics loads a previously persisted host time-series on startup.
// A missing file is not an error (first boot). Aggregation already windows by
// time, so any gap from downtime is handled by the readers.
func RestoreSystemMetrics() {
path := systemMetricsStorePath()
f, err := os.Open(path)
if err != nil {
if !os.IsNotExist(err) {
logger.Warning("restore system metrics failed:", err)
}
return
}
defer f.Close()
var data map[string][]MetricSample
if err := gob.NewDecoder(f).Decode(&data); err != nil {
logger.Warning("decode system metrics failed:", err)
return
}
systemMetrics.restore(data)
}

View file

@ -565,12 +565,22 @@ func (s *ServerService) AppendStatusSample(t time.Time, status *Status) {
if status.Mem.Total > 0 { if status.Mem.Total > 0 {
systemMetrics.append("mem", t, float64(status.Mem.Current)*100.0/float64(status.Mem.Total)) systemMetrics.append("mem", t, float64(status.Mem.Current)*100.0/float64(status.Mem.Total))
} }
if status.Swap.Total > 0 {
systemMetrics.append("swap", t, float64(status.Swap.Current)*100.0/float64(status.Swap.Total))
} else {
systemMetrics.append("swap", t, 0)
}
systemMetrics.append("netUp", t, float64(status.NetIO.Up)) systemMetrics.append("netUp", t, float64(status.NetIO.Up))
systemMetrics.append("netDown", t, float64(status.NetIO.Down)) systemMetrics.append("netDown", t, float64(status.NetIO.Down))
systemMetrics.append("diskRead", t, float64(status.DiskIO.Read)) systemMetrics.append("diskRead", t, float64(status.DiskIO.Read))
systemMetrics.append("diskWrite", t, float64(status.DiskIO.Write)) systemMetrics.append("diskWrite", t, float64(status.DiskIO.Write))
if status.Disk.Total > 0 {
systemMetrics.append("diskUsage", t, float64(status.Disk.Current)*100.0/float64(status.Disk.Total))
}
systemMetrics.append("pktUp", t, float64(status.NetIO.PktUp)) systemMetrics.append("pktUp", t, float64(status.NetIO.PktUp))
systemMetrics.append("pktDown", t, float64(status.NetIO.PktDown)) systemMetrics.append("pktDown", t, float64(status.NetIO.PktDown))
systemMetrics.append("tcpCount", t, float64(status.TcpCount))
systemMetrics.append("udpCount", t, float64(status.UdpCount))
online := 0 online := 0
if p != nil && p.IsRunning() { if p != nil && p.IsRunning() {
online = len(p.GetOnlineClients()) online = len(p.GetOnlineClients())

View file

@ -128,12 +128,12 @@
}, },
"index": { "index": {
"title": "نظرة عامة", "title": "نظرة عامة",
"cpu": "CPU", "cpu": "المعالج",
"logicalProcessors": "المعالجات المنطقية", "logicalProcessors": "المعالجات المنطقية",
"frequency": "التردد", "frequency": "التردد",
"swap": "Swap", "swap": "التبديل",
"storage": "تخزين", "storage": "تخزين",
"memory": "RAM", "memory": "الذاكرة",
"threads": "خيوط", "threads": "خيوط",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "إيقاف", "stopXray": "إيقاف",
@ -162,11 +162,15 @@
"historyTitleDisk": "إدخال/إخراج القرص", "historyTitleDisk": "إدخال/إخراج القرص",
"historyTitleOnline": "العملاء المتصلون", "historyTitleOnline": "العملاء المتصلون",
"historyTitleLoad": "متوسط حمل النظام (1 / 5 / 15 دقيقة)", "historyTitleLoad": "متوسط حمل النظام (1 / 5 / 15 دقيقة)",
"historyTitleConnections": "الاتصالات النشطة (TCP / UDP)",
"historyTitleDiskUsage": "استخدام مساحة القرص",
"historyTabBandwidth": "عرض النطاق", "historyTabBandwidth": "عرض النطاق",
"historyTabPackets": "الحزم", "historyTabPackets": "الحزم",
"historyTabDisk": "Disk I/O", "historyTabDisk": "قرص I/O",
"historyTabOnline": "متصل", "historyTabOnline": "متصل",
"historyTabLoad": "الحِمل", "historyTabLoad": "الحِمل",
"historyTabConnections": "الاتصالات",
"historyTabDiskUsage": "استخدام القرص",
"charts": "الرسوم البيانية", "charts": "الرسوم البيانية",
"xrayMetricsTitle": "مقاييس Xray", "xrayMetricsTitle": "مقاييس Xray",
"xrayTitleHeap": "ذاكرة الكومة المخصصة", "xrayTitleHeap": "ذاكرة الكومة المخصصة",

View file

@ -162,11 +162,15 @@
"historyTitleDisk": "Disk I/O", "historyTitleDisk": "Disk I/O",
"historyTitleOnline": "Online Clients", "historyTitleOnline": "Online Clients",
"historyTitleLoad": "System Load Average (1m / 5m / 15m)", "historyTitleLoad": "System Load Average (1m / 5m / 15m)",
"historyTitleConnections": "Active Connections (TCP / UDP)",
"historyTitleDiskUsage": "Disk Space Usage",
"historyTabBandwidth": "Bandwidth", "historyTabBandwidth": "Bandwidth",
"historyTabPackets": "Packets", "historyTabPackets": "Packets",
"historyTabDisk": "Disk I/O", "historyTabDisk": "Disk I/O",
"historyTabOnline": "Online", "historyTabOnline": "Online",
"historyTabLoad": "Load", "historyTabLoad": "Load",
"historyTabConnections": "Connections",
"historyTabDiskUsage": "Disk Usage",
"charts": "Charts", "charts": "Charts",
"xrayMetricsTitle": "Xray Metrics", "xrayMetricsTitle": "Xray Metrics",
"xrayTitleHeap": "Allocated Heap Memory", "xrayTitleHeap": "Allocated Heap Memory",

View file

@ -133,7 +133,7 @@
"frequency": "Frecuencia", "frequency": "Frecuencia",
"swap": "Swap", "swap": "Swap",
"storage": "Almacenamiento", "storage": "Almacenamiento",
"memory": "RAM", "memory": "Memoria",
"threads": "Hilos", "threads": "Hilos",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "Detener", "stopXray": "Detener",
@ -162,11 +162,15 @@
"historyTitleDisk": "E/S de Disco", "historyTitleDisk": "E/S de Disco",
"historyTitleOnline": "Clientes en Línea", "historyTitleOnline": "Clientes en Línea",
"historyTitleLoad": "Carga Media del Sistema (1 / 5 / 15 min)", "historyTitleLoad": "Carga Media del Sistema (1 / 5 / 15 min)",
"historyTitleConnections": "Conexiones Activas (TCP / UDP)",
"historyTitleDiskUsage": "Uso del Espacio en Disco",
"historyTabBandwidth": "Ancho de Banda", "historyTabBandwidth": "Ancho de Banda",
"historyTabPackets": "Paquetes", "historyTabPackets": "Paquetes",
"historyTabDisk": "Disco I/O", "historyTabDisk": "Disco I/O",
"historyTabOnline": "En línea", "historyTabOnline": "En línea",
"historyTabLoad": "Carga", "historyTabLoad": "Carga",
"historyTabConnections": "Conexiones",
"historyTabDiskUsage": "Uso de Disco",
"charts": "Gráficos", "charts": "Gráficos",
"xrayMetricsTitle": "Métricas de Xray", "xrayMetricsTitle": "Métricas de Xray",
"xrayTitleHeap": "Memoria Heap Asignada", "xrayTitleHeap": "Memoria Heap Asignada",

View file

@ -128,12 +128,12 @@
}, },
"index": { "index": {
"title": "نمای کلی", "title": "نمای کلی",
"cpu": "CPU", "cpu": "پردازنده",
"logicalProcessors": "پردازنده‌های منطقی", "logicalProcessors": "پردازنده‌های منطقی",
"frequency": "فرکانس", "frequency": "فرکانس",
"swap": "Swap", "swap": "سواپ",
"storage": "ذخیره‌سازی", "storage": "ذخیره‌سازی",
"memory": "RAM", "memory": "حافظه",
"threads": "نخ‌ها", "threads": "نخ‌ها",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "توقف", "stopXray": "توقف",
@ -162,11 +162,15 @@
"historyTitleDisk": "ورودی/خروجی دیسک", "historyTitleDisk": "ورودی/خروجی دیسک",
"historyTitleOnline": "کاربران آنلاین", "historyTitleOnline": "کاربران آنلاین",
"historyTitleLoad": "میانگین بار سیستم (۱ / ۵ / ۱۵ دقیقه)", "historyTitleLoad": "میانگین بار سیستم (۱ / ۵ / ۱۵ دقیقه)",
"historyTitleConnections": "اتصالات فعال (TCP / UDP)",
"historyTitleDiskUsage": "مصرف فضای دیسک",
"historyTabBandwidth": "پهنای باند", "historyTabBandwidth": "پهنای باند",
"historyTabPackets": "بسته‌ها", "historyTabPackets": "بسته‌ها",
"historyTabDisk": "Disk I/O", "historyTabDisk": "دیسک I/O",
"historyTabOnline": "آنلاین", "historyTabOnline": "آنلاین",
"historyTabLoad": "بار", "historyTabLoad": "بار",
"historyTabConnections": "اتصالات",
"historyTabDiskUsage": "مصرف دیسک",
"charts": "نمودارها", "charts": "نمودارها",
"xrayMetricsTitle": "متریک‌های Xray", "xrayMetricsTitle": "متریک‌های Xray",
"xrayTitleHeap": "حافظه‌ی Heap تخصیص‌یافته", "xrayTitleHeap": "حافظه‌ی Heap تخصیص‌یافته",

View file

@ -133,7 +133,7 @@
"frequency": "Frekuensi", "frequency": "Frekuensi",
"swap": "Swap", "swap": "Swap",
"storage": "Penyimpanan", "storage": "Penyimpanan",
"memory": "RAM", "memory": "Memori",
"threads": "Thread", "threads": "Thread",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "Hentikan", "stopXray": "Hentikan",
@ -162,11 +162,15 @@
"historyTitleDisk": "I/O Disk", "historyTitleDisk": "I/O Disk",
"historyTitleOnline": "Klien Online", "historyTitleOnline": "Klien Online",
"historyTitleLoad": "Rata-rata Beban Sistem (1 / 5 / 15 mnt)", "historyTitleLoad": "Rata-rata Beban Sistem (1 / 5 / 15 mnt)",
"historyTitleConnections": "Koneksi Aktif (TCP / UDP)",
"historyTitleDiskUsage": "Penggunaan Ruang Disk",
"historyTabBandwidth": "Bandwidth", "historyTabBandwidth": "Bandwidth",
"historyTabPackets": "Paket", "historyTabPackets": "Paket",
"historyTabDisk": "Disk I/O", "historyTabDisk": "Disk I/O",
"historyTabOnline": "Online", "historyTabOnline": "Online",
"historyTabLoad": "Beban", "historyTabLoad": "Beban",
"historyTabConnections": "Koneksi",
"historyTabDiskUsage": "Penggunaan Disk",
"charts": "Grafik", "charts": "Grafik",
"xrayMetricsTitle": "Metrik Xray", "xrayMetricsTitle": "Metrik Xray",
"xrayTitleHeap": "Memori Heap Teralokasi", "xrayTitleHeap": "Memori Heap Teralokasi",

View file

@ -131,9 +131,9 @@
"cpu": "CPU", "cpu": "CPU",
"logicalProcessors": "論理プロセッサ", "logicalProcessors": "論理プロセッサ",
"frequency": "周波数", "frequency": "周波数",
"swap": "Swap", "swap": "スワップ",
"storage": "ストレージ", "storage": "ストレージ",
"memory": "RAM", "memory": "メモリ",
"threads": "スレッド", "threads": "スレッド",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "停止", "stopXray": "停止",
@ -162,11 +162,15 @@
"historyTitleDisk": "ディスク I/O", "historyTitleDisk": "ディスク I/O",
"historyTitleOnline": "オンラインクライアント", "historyTitleOnline": "オンラインクライアント",
"historyTitleLoad": "システム平均負荷1分 / 5分 / 15分", "historyTitleLoad": "システム平均負荷1分 / 5分 / 15分",
"historyTitleConnections": "アクティブな接続 (TCP / UDP)",
"historyTitleDiskUsage": "ディスク使用率",
"historyTabBandwidth": "帯域幅", "historyTabBandwidth": "帯域幅",
"historyTabPackets": "パケット", "historyTabPackets": "パケット",
"historyTabDisk": "ディスク I/O", "historyTabDisk": "ディスク I/O",
"historyTabOnline": "オンライン", "historyTabOnline": "オンライン",
"historyTabLoad": "負荷", "historyTabLoad": "負荷",
"historyTabConnections": "接続数",
"historyTabDiskUsage": "ディスク使用量",
"charts": "チャート", "charts": "チャート",
"xrayMetricsTitle": "Xray メトリクス", "xrayMetricsTitle": "Xray メトリクス",
"xrayTitleHeap": "割り当て済みヒープメモリ", "xrayTitleHeap": "割り当て済みヒープメモリ",

View file

@ -133,7 +133,7 @@
"frequency": "Frequência", "frequency": "Frequência",
"swap": "Swap", "swap": "Swap",
"storage": "Armazenamento", "storage": "Armazenamento",
"memory": "RAM", "memory": "Memória",
"threads": "Threads", "threads": "Threads",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "Parar", "stopXray": "Parar",
@ -162,11 +162,15 @@
"historyTitleDisk": "E/S de Disco", "historyTitleDisk": "E/S de Disco",
"historyTitleOnline": "Clientes Online", "historyTitleOnline": "Clientes Online",
"historyTitleLoad": "Média de Carga do Sistema (1 / 5 / 15 min)", "historyTitleLoad": "Média de Carga do Sistema (1 / 5 / 15 min)",
"historyTitleConnections": "Conexões Ativas (TCP / UDP)",
"historyTitleDiskUsage": "Uso do Espaço em Disco",
"historyTabBandwidth": "Largura de Banda", "historyTabBandwidth": "Largura de Banda",
"historyTabPackets": "Pacotes", "historyTabPackets": "Pacotes",
"historyTabDisk": "Disco I/O", "historyTabDisk": "Disco I/O",
"historyTabOnline": "Online", "historyTabOnline": "Online",
"historyTabLoad": "Carga", "historyTabLoad": "Carga",
"historyTabConnections": "Conexões",
"historyTabDiskUsage": "Uso de Disco",
"charts": "Gráficos", "charts": "Gráficos",
"xrayMetricsTitle": "Métricas do Xray", "xrayMetricsTitle": "Métricas do Xray",
"xrayTitleHeap": "Memória Heap Alocada", "xrayTitleHeap": "Memória Heap Alocada",

View file

@ -128,12 +128,12 @@
}, },
"index": { "index": {
"title": "Дашборд", "title": "Дашборд",
"cpu": "CPU", "cpu": "ЦП",
"logicalProcessors": "Логические процессоры", "logicalProcessors": "Логические процессоры",
"frequency": "Частота", "frequency": "Частота",
"swap": "Swap", "swap": "Подкачка",
"storage": "Диск", "storage": "Диск",
"memory": "RAM", "memory": "Память",
"threads": "Потоки", "threads": "Потоки",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "Стоп", "stopXray": "Стоп",
@ -162,11 +162,15 @@
"historyTitleDisk": "Дисковый ввод-вывод", "historyTitleDisk": "Дисковый ввод-вывод",
"historyTitleOnline": "Клиенты онлайн", "historyTitleOnline": "Клиенты онлайн",
"historyTitleLoad": "Средняя нагрузка системы (1 / 5 / 15 мин)", "historyTitleLoad": "Средняя нагрузка системы (1 / 5 / 15 мин)",
"historyTitleConnections": "Активные соединения (TCP / UDP)",
"historyTitleDiskUsage": "Использование дискового пространства",
"historyTabBandwidth": "Пропускная способность", "historyTabBandwidth": "Пропускная способность",
"historyTabPackets": "Пакеты", "historyTabPackets": "Пакеты",
"historyTabDisk": "Диск I/O", "historyTabDisk": "Диск I/O",
"historyTabOnline": "Онлайн", "historyTabOnline": "Онлайн",
"historyTabLoad": "Нагрузка", "historyTabLoad": "Нагрузка",
"historyTabConnections": "Соединения",
"historyTabDiskUsage": "Использование диска",
"charts": "Графики", "charts": "Графики",
"xrayMetricsTitle": "Метрики Xray", "xrayMetricsTitle": "Метрики Xray",
"xrayTitleHeap": "Выделенная память кучи", "xrayTitleHeap": "Выделенная память кучи",

View file

@ -131,9 +131,9 @@
"cpu": "CPU", "cpu": "CPU",
"logicalProcessors": "Mantıksal işlemciler", "logicalProcessors": "Mantıksal işlemciler",
"frequency": "Frekans", "frequency": "Frekans",
"swap": "Swap", "swap": "Takas",
"storage": "Depolama", "storage": "Depolama",
"memory": "RAM", "memory": "Bellek",
"threads": "İş parçacığı", "threads": "İş parçacığı",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "Durdur", "stopXray": "Durdur",
@ -162,11 +162,15 @@
"historyTitleDisk": "Disk G/Ç", "historyTitleDisk": "Disk G/Ç",
"historyTitleOnline": "Çevrimiçi İstemciler", "historyTitleOnline": "Çevrimiçi İstemciler",
"historyTitleLoad": "Sistem Yük Ortalaması (1d / 5d / 15d)", "historyTitleLoad": "Sistem Yük Ortalaması (1d / 5d / 15d)",
"historyTitleConnections": "Etkin Bağlantılar (TCP / UDP)",
"historyTitleDiskUsage": "Disk Alanı Kullanımı",
"historyTabBandwidth": "Bant Genişliği", "historyTabBandwidth": "Bant Genişliği",
"historyTabPackets": "Paketler", "historyTabPackets": "Paketler",
"historyTabDisk": "Disk G/Ç", "historyTabDisk": "Disk G/Ç",
"historyTabOnline": "Çevrimiçi", "historyTabOnline": "Çevrimiçi",
"historyTabLoad": "Yük", "historyTabLoad": "Yük",
"historyTabConnections": "Bağlantılar",
"historyTabDiskUsage": "Disk Kullanımı",
"charts": "Grafikler", "charts": "Grafikler",
"xrayMetricsTitle": "Xray Metrikleri", "xrayMetricsTitle": "Xray Metrikleri",
"xrayTitleHeap": "Ayrılan Yığın Belleği", "xrayTitleHeap": "Ayrılan Yığın Belleği",

View file

@ -128,12 +128,12 @@
}, },
"index": { "index": {
"title": "Огляд", "title": "Огляд",
"cpu": "CPU", "cpu": "ЦП",
"logicalProcessors": "Логічні процесори", "logicalProcessors": "Логічні процесори",
"frequency": "Частота", "frequency": "Частота",
"swap": "Swap", "swap": "Підкачка",
"storage": "Сховище", "storage": "Сховище",
"memory": "RAM", "memory": "Пам’ять",
"threads": "Потоки", "threads": "Потоки",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "Стоп", "stopXray": "Стоп",
@ -162,11 +162,15 @@
"historyTitleDisk": "Дисковий ввід-вивід", "historyTitleDisk": "Дисковий ввід-вивід",
"historyTitleOnline": "Клієнти онлайн", "historyTitleOnline": "Клієнти онлайн",
"historyTitleLoad": "Середнє навантаження системи (1 / 5 / 15 хв)", "historyTitleLoad": "Середнє навантаження системи (1 / 5 / 15 хв)",
"historyTitleConnections": "Активні з’єднання (TCP / UDP)",
"historyTitleDiskUsage": "Використання дискового простору",
"historyTabBandwidth": "Пропускна здатність", "historyTabBandwidth": "Пропускна здатність",
"historyTabPackets": "Пакети", "historyTabPackets": "Пакети",
"historyTabDisk": "Диск I/O", "historyTabDisk": "Диск I/O",
"historyTabOnline": "Онлайн", "historyTabOnline": "Онлайн",
"historyTabLoad": "Навантаження", "historyTabLoad": "Навантаження",
"historyTabConnections": "З’єднання",
"historyTabDiskUsage": "Використання диска",
"charts": "Графіки", "charts": "Графіки",
"xrayMetricsTitle": "Метрики Xray", "xrayMetricsTitle": "Метрики Xray",
"xrayTitleHeap": "Виділена пам’ять купи", "xrayTitleHeap": "Виділена пам’ять купи",

View file

@ -133,7 +133,7 @@
"frequency": "Tần số", "frequency": "Tần số",
"swap": "Swap", "swap": "Swap",
"storage": "Lưu trữ", "storage": "Lưu trữ",
"memory": "RAM", "memory": "Bộ nhớ",
"threads": "Luồng", "threads": "Luồng",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "Dừng", "stopXray": "Dừng",
@ -162,11 +162,15 @@
"historyTitleDisk": "I/O đĩa", "historyTitleDisk": "I/O đĩa",
"historyTitleOnline": "Máy khách trực tuyến", "historyTitleOnline": "Máy khách trực tuyến",
"historyTitleLoad": "Tải trung bình hệ thống (1 / 5 / 15 phút)", "historyTitleLoad": "Tải trung bình hệ thống (1 / 5 / 15 phút)",
"historyTitleConnections": "Kết nối đang hoạt động (TCP / UDP)",
"historyTitleDiskUsage": "Sử dụng dung lượng đĩa",
"historyTabBandwidth": "Băng thông", "historyTabBandwidth": "Băng thông",
"historyTabPackets": "Gói tin", "historyTabPackets": "Gói tin",
"historyTabDisk": "Đĩa I/O", "historyTabDisk": "Đĩa I/O",
"historyTabOnline": "Trực tuyến", "historyTabOnline": "Trực tuyến",
"historyTabLoad": "Tải", "historyTabLoad": "Tải",
"historyTabConnections": "Kết nối",
"historyTabDiskUsage": "Sử dụng đĩa",
"charts": "Biểu đồ", "charts": "Biểu đồ",
"xrayMetricsTitle": "Chỉ số Xray", "xrayMetricsTitle": "Chỉ số Xray",
"xrayTitleHeap": "Bộ nhớ Heap đã cấp phát", "xrayTitleHeap": "Bộ nhớ Heap đã cấp phát",

View file

@ -131,9 +131,9 @@
"cpu": "CPU", "cpu": "CPU",
"logicalProcessors": "逻辑处理器", "logicalProcessors": "逻辑处理器",
"frequency": "频率", "frequency": "频率",
"swap": "Swap", "swap": "交换空间",
"storage": "存储", "storage": "存储",
"memory": "RAM", "memory": "内存",
"threads": "线程", "threads": "线程",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "停止", "stopXray": "停止",
@ -162,11 +162,15 @@
"historyTitleDisk": "磁盘 I/O", "historyTitleDisk": "磁盘 I/O",
"historyTitleOnline": "在线客户端", "historyTitleOnline": "在线客户端",
"historyTitleLoad": "系统平均负载1 分钟 / 5 分钟 / 15 分钟)", "historyTitleLoad": "系统平均负载1 分钟 / 5 分钟 / 15 分钟)",
"historyTitleConnections": "活动连接 (TCP / UDP)",
"historyTitleDiskUsage": "磁盘空间使用率",
"historyTabBandwidth": "带宽", "historyTabBandwidth": "带宽",
"historyTabPackets": "数据包", "historyTabPackets": "数据包",
"historyTabDisk": "磁盘 I/O", "historyTabDisk": "磁盘 I/O",
"historyTabOnline": "在线", "historyTabOnline": "在线",
"historyTabLoad": "负载", "historyTabLoad": "负载",
"historyTabConnections": "连接数",
"historyTabDiskUsage": "磁盘使用量",
"charts": "图表", "charts": "图表",
"xrayMetricsTitle": "Xray 指标", "xrayMetricsTitle": "Xray 指标",
"xrayTitleHeap": "已分配的堆内存", "xrayTitleHeap": "已分配的堆内存",

View file

@ -131,9 +131,9 @@
"cpu": "CPU", "cpu": "CPU",
"logicalProcessors": "邏輯處理器", "logicalProcessors": "邏輯處理器",
"frequency": "頻率", "frequency": "頻率",
"swap": "Swap", "swap": "交換空間",
"storage": "儲存", "storage": "儲存",
"memory": "RAM", "memory": "記憶體",
"threads": "執行緒", "threads": "執行緒",
"xrayStatus": "Xray", "xrayStatus": "Xray",
"stopXray": "停止", "stopXray": "停止",
@ -162,11 +162,15 @@
"historyTitleDisk": "磁碟 I/O", "historyTitleDisk": "磁碟 I/O",
"historyTitleOnline": "線上用戶端", "historyTitleOnline": "線上用戶端",
"historyTitleLoad": "系統平均負載1 分鐘 / 5 分鐘 / 15 分鐘)", "historyTitleLoad": "系統平均負載1 分鐘 / 5 分鐘 / 15 分鐘)",
"historyTitleConnections": "使用中的連線 (TCP / UDP)",
"historyTitleDiskUsage": "磁碟空間使用率",
"historyTabBandwidth": "頻寬", "historyTabBandwidth": "頻寬",
"historyTabPackets": "封包", "historyTabPackets": "封包",
"historyTabDisk": "磁碟 I/O", "historyTabDisk": "磁碟 I/O",
"historyTabOnline": "線上", "historyTabOnline": "線上",
"historyTabLoad": "負載", "historyTabLoad": "負載",
"historyTabConnections": "連線數",
"historyTabDiskUsage": "磁碟使用量",
"charts": "圖表", "charts": "圖表",
"xrayMetricsTitle": "Xray 指標", "xrayMetricsTitle": "Xray 指標",
"xrayTitleHeap": "已配置的堆積記憶體", "xrayTitleHeap": "已配置的堆積記憶體",

View file

@ -469,6 +469,9 @@ func (s *Server) stop(stopXray bool, stopTgBot bool) error {
if s.cron != nil { if s.cron != nil {
s.cron.Stop() s.cron.Stop()
} }
if err := service.PersistSystemMetrics(); err != nil {
logger.Warning("persist system metrics on shutdown failed:", err)
}
if stopXray { if stopXray {
service.StopTrafficWriter() service.StopTrafficWriter()
} }