feat(panel): xray metrics dashboard with observatory probe history
Polls xray's /debug/vars on the 2s status tick, stores memstats and per-outbound observatory delay in the metric history ring buffer, and exposes them through a new XrayMetricsModal opened from the Charts card. Restructures the dashboard to consolidate uptime, usage, version, and Telegram link into stat-style or action-style cards consistent with the existing AntD aesthetic.
This commit is contained in:
@@ -23,9 +23,10 @@ var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
|
||||
type ServerController struct {
|
||||
BaseController
|
||||
|
||||
serverService service.ServerService
|
||||
settingService service.SettingService
|
||||
panelService service.PanelService
|
||||
serverService service.ServerService
|
||||
settingService service.SettingService
|
||||
panelService service.PanelService
|
||||
xrayMetricsService service.XrayMetricsService
|
||||
|
||||
lastStatus *service.Status
|
||||
|
||||
@@ -47,6 +48,10 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||
g.GET("/status", a.status)
|
||||
g.GET("/cpuHistory/:bucket", a.getCpuHistoryBucket)
|
||||
g.GET("/history/:metric/:bucket", a.getMetricHistoryBucket)
|
||||
g.GET("/xrayMetricsState", a.getXrayMetricsState)
|
||||
g.GET("/xrayMetricsHistory/:metric/:bucket", a.getXrayMetricsHistoryBucket)
|
||||
g.GET("/xrayObservatory", a.getXrayObservatory)
|
||||
g.GET("/xrayObservatoryHistory/:tag/:bucket", a.getXrayObservatoryHistoryBucket)
|
||||
g.GET("/getXrayVersion", a.getXrayVersion)
|
||||
g.GET("/getPanelUpdateInfo", a.getPanelUpdateInfo)
|
||||
g.GET("/getConfigJson", a.getConfigJson)
|
||||
@@ -75,7 +80,9 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||
func (a *ServerController) refreshStatus() {
|
||||
a.lastStatus = a.serverService.GetStatus(a.lastStatus)
|
||||
if a.lastStatus != nil {
|
||||
a.serverService.AppendStatusSample(time.Now(), a.lastStatus)
|
||||
now := time.Now()
|
||||
a.serverService.AppendStatusSample(now, a.lastStatus)
|
||||
a.xrayMetricsService.Sample(now)
|
||||
// Broadcast status update via WebSocket
|
||||
websocket.BroadcastStatus(a.lastStatus)
|
||||
}
|
||||
@@ -143,6 +150,42 @@ func (a *ServerController) getMetricHistoryBucket(c *gin.Context) {
|
||||
jsonObj(c, a.serverService.AggregateSystemMetric(metric, bucket, 60), nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getXrayMetricsState(c *gin.Context) {
|
||||
jsonObj(c, a.xrayMetricsService.State(), nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getXrayMetricsHistoryBucket(c *gin.Context) {
|
||||
metric := c.Param("metric")
|
||||
if !slices.Contains(service.XrayMetricKeys, metric) {
|
||||
jsonMsg(c, "invalid metric", fmt.Errorf("unknown metric"))
|
||||
return
|
||||
}
|
||||
bucket, err := strconv.Atoi(c.Param("bucket"))
|
||||
if err != nil || bucket <= 0 || !allowedHistoryBuckets[bucket] {
|
||||
jsonMsg(c, "invalid bucket", fmt.Errorf("unsupported bucket"))
|
||||
return
|
||||
}
|
||||
jsonObj(c, a.xrayMetricsService.AggregateMetric(metric, bucket, 60), nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getXrayObservatory(c *gin.Context) {
|
||||
jsonObj(c, a.xrayMetricsService.ObservatorySnapshot(), nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getXrayObservatoryHistoryBucket(c *gin.Context) {
|
||||
tag := c.Param("tag")
|
||||
if !a.xrayMetricsService.HasObservatoryTag(tag) {
|
||||
jsonMsg(c, "invalid tag", fmt.Errorf("unknown observatory tag"))
|
||||
return
|
||||
}
|
||||
bucket, err := strconv.Atoi(c.Param("bucket"))
|
||||
if err != nil || bucket <= 0 || !allowedHistoryBuckets[bucket] {
|
||||
jsonMsg(c, "invalid bucket", fmt.Errorf("unsupported bucket"))
|
||||
return
|
||||
}
|
||||
jsonObj(c, a.xrayMetricsService.AggregateObservatory(tag, bucket, 60), nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getXrayVersion(c *gin.Context) {
|
||||
const cacheTTLSeconds = 15 * 60
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ func (h *metricHistory) aggregate(metric string, bucketSeconds int, maxPoints in
|
||||
var (
|
||||
systemMetrics = newMetricHistory()
|
||||
nodeMetrics = newMetricHistory()
|
||||
xrayMetrics = newMetricHistory()
|
||||
)
|
||||
|
||||
// SystemMetricKeys lists the metric names ServerService writes on every
|
||||
@@ -141,3 +142,11 @@ var SystemMetricKeys = []string{
|
||||
|
||||
// NodeMetricKeys lists the per-node metric names NodeHeartbeatJob writes.
|
||||
var NodeMetricKeys = []string{"cpu", "mem"}
|
||||
|
||||
// XrayMetricKeys lists series sourced from xray's /debug/vars expvar
|
||||
// endpoint. Populated by XrayMetricsService.Sample on the same 2s cadence
|
||||
// as the system metrics, but only when the xray config has a `metrics`
|
||||
// block configured.
|
||||
var XrayMetricKeys = []string{
|
||||
"xrAlloc", "xrSys", "xrHeapObjects", "xrNumGC", "xrPauseNs",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/logger"
|
||||
)
|
||||
|
||||
type xrayMetricsState struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Listen string `json:"listen"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type ObsTagSnapshot struct {
|
||||
Tag string `json:"tag"`
|
||||
Alive bool `json:"alive"`
|
||||
Delay int64 `json:"delay"`
|
||||
LastSeenTime int64 `json:"lastSeenTime"`
|
||||
LastTryTime int64 `json:"lastTryTime"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type XrayMetricsService struct {
|
||||
settingService SettingService
|
||||
|
||||
mu sync.RWMutex
|
||||
state xrayMetricsState
|
||||
client *http.Client
|
||||
obsByTag map[string]ObsTagSnapshot
|
||||
}
|
||||
|
||||
var validObsTag = regexp.MustCompile(`^[a-zA-Z0-9._\-]+$`)
|
||||
|
||||
func obsHistoryKey(tag string) string {
|
||||
return "xrObs." + tag + ".delay"
|
||||
}
|
||||
|
||||
func newXrayMetricsClient() *http.Client {
|
||||
return &http.Client{Timeout: 1500 * time.Millisecond}
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) getClient() *http.Client {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.client == nil {
|
||||
s.client = newXrayMetricsClient()
|
||||
}
|
||||
return s.client
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) State() xrayMetricsState {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.state
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) AggregateMetric(metric string, bucketSeconds, maxPoints int) []map[string]any {
|
||||
return xrayMetrics.aggregate(metric, bucketSeconds, maxPoints)
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) ObservatorySnapshot() []ObsTagSnapshot {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
out := make([]ObsTagSnapshot, 0, len(s.obsByTag))
|
||||
for _, v := range s.obsByTag {
|
||||
out = append(out, v)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Tag < out[j].Tag })
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) HasObservatoryTag(tag string) bool {
|
||||
if !validObsTag.MatchString(tag) {
|
||||
return false
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
_, ok := s.obsByTag[tag]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) AggregateObservatory(tag string, bucketSeconds, maxPoints int) []map[string]any {
|
||||
if !validObsTag.MatchString(tag) {
|
||||
return []map[string]any{}
|
||||
}
|
||||
return xrayMetrics.aggregate(obsHistoryKey(tag), bucketSeconds, maxPoints)
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) discoverListen() (string, error) {
|
||||
tmpl, err := s.settingService.GetXrayConfigTemplate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var parsed struct {
|
||||
Metrics *struct {
|
||||
Listen string `json:"listen"`
|
||||
} `json:"metrics"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(tmpl), &parsed); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if parsed.Metrics == nil || strings.TrimSpace(parsed.Metrics.Listen) == "" {
|
||||
return "", nil
|
||||
}
|
||||
return strings.TrimSpace(parsed.Metrics.Listen), nil
|
||||
}
|
||||
|
||||
type rawObsEntry struct {
|
||||
Alive bool `json:"alive"`
|
||||
Delay int64 `json:"delay"`
|
||||
LastSeenTime int64 `json:"last_seen_time"`
|
||||
LastTryTime int64 `json:"last_try_time"`
|
||||
OutboundTag string `json:"outbound_tag"`
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) Sample(t time.Time) {
|
||||
listen, err := s.discoverListen()
|
||||
if err != nil {
|
||||
s.setState(xrayMetricsState{Reason: err.Error()})
|
||||
return
|
||||
}
|
||||
if listen == "" {
|
||||
s.setState(xrayMetricsState{Reason: "metrics block not configured in xray template"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond)
|
||||
defer cancel()
|
||||
url := fmt.Sprintf("http://%s/debug/vars", listen)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
s.setState(xrayMetricsState{Listen: listen, Reason: err.Error()})
|
||||
return
|
||||
}
|
||||
resp, err := s.getClient().Do(req)
|
||||
if err != nil {
|
||||
s.setState(xrayMetricsState{Listen: listen, Reason: err.Error()})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
s.setState(xrayMetricsState{Listen: listen, Reason: fmt.Sprintf("HTTP %d", resp.StatusCode)})
|
||||
return
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
MemStats struct {
|
||||
HeapAlloc uint64 `json:"HeapAlloc"`
|
||||
Sys uint64 `json:"Sys"`
|
||||
HeapObjects uint64 `json:"HeapObjects"`
|
||||
NumGC uint32 `json:"NumGC"`
|
||||
PauseNs [256]uint64 `json:"PauseNs"`
|
||||
} `json:"memstats"`
|
||||
Observatory map[string]rawObsEntry `json:"observatory"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||
s.setState(xrayMetricsState{Listen: listen, Reason: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
xrayMetrics.append("xrAlloc", t, float64(payload.MemStats.HeapAlloc))
|
||||
xrayMetrics.append("xrSys", t, float64(payload.MemStats.Sys))
|
||||
xrayMetrics.append("xrHeapObjects", t, float64(payload.MemStats.HeapObjects))
|
||||
xrayMetrics.append("xrNumGC", t, float64(payload.MemStats.NumGC))
|
||||
var lastPause uint64
|
||||
if payload.MemStats.NumGC > 0 {
|
||||
idx := (payload.MemStats.NumGC + 255) % 256
|
||||
lastPause = payload.MemStats.PauseNs[idx]
|
||||
}
|
||||
xrayMetrics.append("xrPauseNs", t, float64(lastPause))
|
||||
|
||||
s.applyObservatory(t, payload.Observatory)
|
||||
s.setState(xrayMetricsState{Enabled: true, Listen: listen})
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) applyObservatory(t time.Time, entries map[string]rawObsEntry) {
|
||||
next := make(map[string]ObsTagSnapshot, len(entries))
|
||||
for key, e := range entries {
|
||||
tag := e.OutboundTag
|
||||
if tag == "" {
|
||||
tag = key
|
||||
}
|
||||
if !validObsTag.MatchString(tag) {
|
||||
continue
|
||||
}
|
||||
snap := ObsTagSnapshot{
|
||||
Tag: tag,
|
||||
Alive: e.Alive,
|
||||
Delay: e.Delay,
|
||||
LastSeenTime: e.LastSeenTime,
|
||||
LastTryTime: e.LastTryTime,
|
||||
UpdatedAt: t.Unix(),
|
||||
}
|
||||
next[tag] = snap
|
||||
xrayMetrics.append(obsHistoryKey(tag), t, float64(e.Delay))
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
for tag := range s.obsByTag {
|
||||
if _, kept := next[tag]; !kept {
|
||||
xrayMetrics.drop(obsHistoryKey(tag))
|
||||
}
|
||||
}
|
||||
s.obsByTag = next
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *XrayMetricsService) setState(st xrayMetricsState) {
|
||||
s.mu.Lock()
|
||||
s.state = st
|
||||
s.mu.Unlock()
|
||||
if !st.Enabled && st.Reason != "" {
|
||||
logger.Debugf("xray metrics unavailable: %s", st.Reason)
|
||||
}
|
||||
}
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "حصل خطأ أثناء تشغيل Xray",
|
||||
"operationHours": "مدة التشغيل",
|
||||
"systemHistoryTitle": "تاريخ النظام",
|
||||
"charts": "الرسوم البيانية",
|
||||
"xrayMetricsTitle": "مقاييس Xray",
|
||||
"xrayMetricsDisabled": "نقطة نهاية مقاييس Xray غير مهيأة",
|
||||
"xrayMetricsHint": "أضف كتلة metrics على المستوى الأعلى في إعدادات xray مع tag باسم metrics_out و listen على 127.0.0.1:11111، ثم أعد تشغيل xray.",
|
||||
"xrayObservatoryEmpty": "لا توجد بيانات Observatory بعد",
|
||||
"xrayObservatoryHint": "أضف كتلة observatory إلى إعدادات xray مع قائمة وسوم outbound للفحص، ثم أعد تشغيل xray.",
|
||||
"xrayObservatoryTagPlaceholder": "اختر outbound",
|
||||
"xrayObservatoryAlive": "نشط",
|
||||
"xrayObservatoryDead": "غير متصل",
|
||||
"xrayObservatoryLastSeen": "آخر مشاهدة",
|
||||
"xrayObservatoryLastTry": "آخر محاولة",
|
||||
"trendLast2Min": "آخر دقيقتين",
|
||||
"systemLoad": "تحميل النظام",
|
||||
"systemLoadDesc": "متوسط تحميل النظام في الدقائق 1, 5, و15",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "An error occurred while running Xray",
|
||||
"operationHours": "Uptime",
|
||||
"systemHistoryTitle": "System History",
|
||||
"charts": "Charts",
|
||||
"xrayMetricsTitle": "Xray Metrics",
|
||||
"xrayMetricsDisabled": "Xray metrics endpoint not configured",
|
||||
"xrayMetricsHint": "Add a top-level metrics block to the xray config with tag metrics_out and listen 127.0.0.1:11111, then restart xray.",
|
||||
"xrayObservatoryEmpty": "No observatory data yet",
|
||||
"xrayObservatoryHint": "Add an observatory block to the xray config listing the outbound tags to probe, then restart xray.",
|
||||
"xrayObservatoryTagPlaceholder": "Select outbound",
|
||||
"xrayObservatoryAlive": "Alive",
|
||||
"xrayObservatoryDead": "Down",
|
||||
"xrayObservatoryLastSeen": "Last seen",
|
||||
"xrayObservatoryLastTry": "Last try",
|
||||
"trendLast2Min": "Last 2 minutes",
|
||||
"systemLoad": "System Load",
|
||||
"systemLoadDesc": "System load average for the past 1, 5, and 15 minutes",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "Se produjo un error al ejecutar Xray",
|
||||
"operationHours": "Tiempo de Funcionamiento",
|
||||
"systemHistoryTitle": "Historial del Sistema",
|
||||
"charts": "Gráficos",
|
||||
"xrayMetricsTitle": "Métricas de Xray",
|
||||
"xrayMetricsDisabled": "Endpoint de métricas de Xray no configurado",
|
||||
"xrayMetricsHint": "Añade un bloque metrics de nivel superior a la configuración de xray con tag metrics_out y listen 127.0.0.1:11111, luego reinicia xray.",
|
||||
"xrayObservatoryEmpty": "Aún no hay datos de Observatory",
|
||||
"xrayObservatoryHint": "Añade un bloque observatory a la configuración de xray listando los tags de outbound a sondear, luego reinicia xray.",
|
||||
"xrayObservatoryTagPlaceholder": "Seleccionar outbound",
|
||||
"xrayObservatoryAlive": "Activo",
|
||||
"xrayObservatoryDead": "Caído",
|
||||
"xrayObservatoryLastSeen": "Visto por última vez",
|
||||
"xrayObservatoryLastTry": "Último intento",
|
||||
"trendLast2Min": "Últimos 2 minutos",
|
||||
"systemLoad": "Carga del Sistema",
|
||||
"systemLoadDesc": "promedio de carga del sistema en los últimos 1, 5 y 15 minutos",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "خطا در هنگام اجرای Xray رخ داد",
|
||||
"operationHours": "مدتکارکرد",
|
||||
"systemHistoryTitle": "تاریخچه سیستم",
|
||||
"charts": "نمودارها",
|
||||
"xrayMetricsTitle": "متریکهای Xray",
|
||||
"xrayMetricsDisabled": "نقطه پایانی متریکهای Xray پیکربندی نشده",
|
||||
"xrayMetricsHint": "یک بلاک metrics در سطح بالای پیکربندی xray با tag برابر metrics_out و listen برابر 127.0.0.1:11111 اضافه کنید، سپس xray را راهاندازی مجدد کنید.",
|
||||
"xrayObservatoryEmpty": "هنوز دادهای از Observatory دریافت نشده",
|
||||
"xrayObservatoryHint": "یک بلاک observatory در پیکربندی xray اضافه کنید و outbound tagهایی که میخواهید بررسی شوند را لیست کنید، سپس xray را راهاندازی مجدد کنید.",
|
||||
"xrayObservatoryTagPlaceholder": "انتخاب outbound",
|
||||
"xrayObservatoryAlive": "فعال",
|
||||
"xrayObservatoryDead": "غیرفعال",
|
||||
"xrayObservatoryLastSeen": "آخرین مشاهده",
|
||||
"xrayObservatoryLastTry": "آخرین تلاش",
|
||||
"trendLast2Min": "۲ دقیقه اخیر",
|
||||
"systemLoad": "بارسیستم",
|
||||
"systemLoadDesc": "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "Terjadi kesalahan saat menjalankan Xray",
|
||||
"operationHours": "Waktu Aktif",
|
||||
"systemHistoryTitle": "Riwayat Sistem",
|
||||
"charts": "Grafik",
|
||||
"xrayMetricsTitle": "Metrik Xray",
|
||||
"xrayMetricsDisabled": "Endpoint metrik Xray belum dikonfigurasi",
|
||||
"xrayMetricsHint": "Tambahkan blok metrics tingkat atas ke konfigurasi xray dengan tag metrics_out dan listen 127.0.0.1:11111, lalu mulai ulang xray.",
|
||||
"xrayObservatoryEmpty": "Belum ada data Observatory",
|
||||
"xrayObservatoryHint": "Tambahkan blok observatory ke konfigurasi xray yang mencantumkan tag outbound untuk diuji, lalu mulai ulang xray.",
|
||||
"xrayObservatoryTagPlaceholder": "Pilih outbound",
|
||||
"xrayObservatoryAlive": "Aktif",
|
||||
"xrayObservatoryDead": "Mati",
|
||||
"xrayObservatoryLastSeen": "Terakhir terlihat",
|
||||
"xrayObservatoryLastTry": "Percobaan terakhir",
|
||||
"trendLast2Min": "2 menit terakhir",
|
||||
"systemLoad": "Beban Sistem",
|
||||
"systemLoadDesc": "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "Xrayの実行中にエラーが発生しました",
|
||||
"operationHours": "システム稼働時間",
|
||||
"systemHistoryTitle": "システム履歴",
|
||||
"charts": "チャート",
|
||||
"xrayMetricsTitle": "Xray メトリクス",
|
||||
"xrayMetricsDisabled": "Xray メトリクスエンドポイントが設定されていません",
|
||||
"xrayMetricsHint": "xray 設定にトップレベルの metrics ブロック(tag: metrics_out、listen: 127.0.0.1:11111)を追加し、xray を再起動してください。",
|
||||
"xrayObservatoryEmpty": "Observatory データはまだありません",
|
||||
"xrayObservatoryHint": "xray 設定に observatory ブロックを追加し、プローブする outbound タグを列挙してから xray を再起動してください。",
|
||||
"xrayObservatoryTagPlaceholder": "Outbound を選択",
|
||||
"xrayObservatoryAlive": "稼働中",
|
||||
"xrayObservatoryDead": "停止",
|
||||
"xrayObservatoryLastSeen": "最終確認",
|
||||
"xrayObservatoryLastTry": "最終試行",
|
||||
"trendLast2Min": "直近2分",
|
||||
"systemLoad": "システム負荷",
|
||||
"systemLoadDesc": "過去1、5、15分間のシステム平均負荷",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "Ocorreu um erro ao executar o Xray",
|
||||
"operationHours": "Tempo de Atividade",
|
||||
"systemHistoryTitle": "Histórico do Sistema",
|
||||
"charts": "Gráficos",
|
||||
"xrayMetricsTitle": "Métricas do Xray",
|
||||
"xrayMetricsDisabled": "Endpoint de métricas do Xray não configurado",
|
||||
"xrayMetricsHint": "Adicione um bloco metrics de nível superior à configuração do xray com tag metrics_out e listen 127.0.0.1:11111, depois reinicie o xray.",
|
||||
"xrayObservatoryEmpty": "Ainda não há dados do Observatory",
|
||||
"xrayObservatoryHint": "Adicione um bloco observatory à configuração do xray listando as tags de outbound a sondar, depois reinicie o xray.",
|
||||
"xrayObservatoryTagPlaceholder": "Selecionar outbound",
|
||||
"xrayObservatoryAlive": "Ativo",
|
||||
"xrayObservatoryDead": "Inativo",
|
||||
"xrayObservatoryLastSeen": "Visto pela última vez",
|
||||
"xrayObservatoryLastTry": "Última tentativa",
|
||||
"trendLast2Min": "Últimos 2 minutos",
|
||||
"systemLoad": "Carga do Sistema",
|
||||
"systemLoadDesc": "Média de carga do sistema nos últimos 1, 5 e 15 minutos",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "Ошибка при запуске Xray",
|
||||
"operationHours": "Время работы системы",
|
||||
"systemHistoryTitle": "История системы",
|
||||
"charts": "Графики",
|
||||
"xrayMetricsTitle": "Метрики Xray",
|
||||
"xrayMetricsDisabled": "Конечная точка метрик Xray не настроена",
|
||||
"xrayMetricsHint": "Добавьте блок metrics верхнего уровня в конфигурацию xray с tag metrics_out и listen 127.0.0.1:11111, затем перезапустите xray.",
|
||||
"xrayObservatoryEmpty": "Данных Observatory пока нет",
|
||||
"xrayObservatoryHint": "Добавьте блок observatory в конфигурацию xray с указанием тегов outbound для проверки, затем перезапустите xray.",
|
||||
"xrayObservatoryTagPlaceholder": "Выберите outbound",
|
||||
"xrayObservatoryAlive": "Активен",
|
||||
"xrayObservatoryDead": "Недоступен",
|
||||
"xrayObservatoryLastSeen": "Последняя активность",
|
||||
"xrayObservatoryLastTry": "Последняя попытка",
|
||||
"trendLast2Min": "Последние 2 минуты",
|
||||
"systemLoad": "Нагрузка на систему",
|
||||
"systemLoadDesc": "Средняя загрузка системы за последние 1, 5 и 15 минут",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "Xray çalıştırılırken bir hata oluştu",
|
||||
"operationHours": "Çalışma Süresi",
|
||||
"systemHistoryTitle": "Sistem Geçmişi",
|
||||
"charts": "Grafikler",
|
||||
"xrayMetricsTitle": "Xray Metrikleri",
|
||||
"xrayMetricsDisabled": "Xray metrik uç noktası yapılandırılmadı",
|
||||
"xrayMetricsHint": "xray yapılandırmasına tag metrics_out ve listen 127.0.0.1:11111 olan üst düzey bir metrics bloğu ekleyin, sonra xray'i yeniden başlatın.",
|
||||
"xrayObservatoryEmpty": "Henüz Observatory verisi yok",
|
||||
"xrayObservatoryHint": "xray yapılandırmasına test edilecek outbound etiketlerini listeleyen bir observatory bloğu ekleyin, sonra xray'i yeniden başlatın.",
|
||||
"xrayObservatoryTagPlaceholder": "Outbound seç",
|
||||
"xrayObservatoryAlive": "Aktif",
|
||||
"xrayObservatoryDead": "Kapalı",
|
||||
"xrayObservatoryLastSeen": "Son görülme",
|
||||
"xrayObservatoryLastTry": "Son deneme",
|
||||
"trendLast2Min": "Son 2 dakika",
|
||||
"systemLoad": "Sistem Yükü",
|
||||
"systemLoadDesc": "Geçmiş 1, 5 ve 15 dakika için sistem yük ortalaması",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "Під час роботи Xray сталася помилка",
|
||||
"operationHours": "Час роботи",
|
||||
"systemHistoryTitle": "Історія системи",
|
||||
"charts": "Графіки",
|
||||
"xrayMetricsTitle": "Метрики Xray",
|
||||
"xrayMetricsDisabled": "Кінцева точка метрик Xray не налаштована",
|
||||
"xrayMetricsHint": "Додайте блок metrics верхнього рівня до конфігурації xray з tag metrics_out і listen 127.0.0.1:11111, потім перезапустіть xray.",
|
||||
"xrayObservatoryEmpty": "Даних Observatory ще немає",
|
||||
"xrayObservatoryHint": "Додайте блок observatory до конфігурації xray зі списком outbound тегів для перевірки, потім перезапустіть xray.",
|
||||
"xrayObservatoryTagPlaceholder": "Виберіть outbound",
|
||||
"xrayObservatoryAlive": "Активний",
|
||||
"xrayObservatoryDead": "Недоступний",
|
||||
"xrayObservatoryLastSeen": "Остання активність",
|
||||
"xrayObservatoryLastTry": "Остання спроба",
|
||||
"trendLast2Min": "Останні 2 хвилини",
|
||||
"systemLoad": "Завантаження системи",
|
||||
"systemLoadDesc": "Середнє завантаження системи за останні 1, 5 і 15 хвилин",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "Đã xảy ra lỗi khi chạy Xray",
|
||||
"operationHours": "Thời gian hoạt động",
|
||||
"systemHistoryTitle": "Lịch sử hệ thống",
|
||||
"charts": "Biểu đồ",
|
||||
"xrayMetricsTitle": "Chỉ số Xray",
|
||||
"xrayMetricsDisabled": "Điểm cuối chỉ số Xray chưa được cấu hình",
|
||||
"xrayMetricsHint": "Thêm khối metrics cấp cao nhất vào cấu hình xray với tag là metrics_out và listen là 127.0.0.1:11111, sau đó khởi động lại xray.",
|
||||
"xrayObservatoryEmpty": "Chưa có dữ liệu Observatory",
|
||||
"xrayObservatoryHint": "Thêm khối observatory vào cấu hình xray liệt kê các tag outbound cần kiểm tra, sau đó khởi động lại xray.",
|
||||
"xrayObservatoryTagPlaceholder": "Chọn outbound",
|
||||
"xrayObservatoryAlive": "Hoạt động",
|
||||
"xrayObservatoryDead": "Ngừng",
|
||||
"xrayObservatoryLastSeen": "Lần cuối thấy",
|
||||
"xrayObservatoryLastTry": "Lần thử cuối",
|
||||
"trendLast2Min": "2 phút gần nhất",
|
||||
"systemLoad": "Tải hệ thống",
|
||||
"systemLoadDesc": "trung bình tải hệ thống trong 1, 5 và 15 phút qua",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "运行Xray时发生错误",
|
||||
"operationHours": "系统正常运行时间",
|
||||
"systemHistoryTitle": "系统历史",
|
||||
"charts": "图表",
|
||||
"xrayMetricsTitle": "Xray 指标",
|
||||
"xrayMetricsDisabled": "未配置 Xray 指标端点",
|
||||
"xrayMetricsHint": "在 xray 配置中添加顶级 metrics 块,tag 为 metrics_out,listen 为 127.0.0.1:11111,然后重启 xray。",
|
||||
"xrayObservatoryEmpty": "暂无 Observatory 数据",
|
||||
"xrayObservatoryHint": "在 xray 配置中添加 observatory 块,列出要探测的出站 tag,然后重启 xray。",
|
||||
"xrayObservatoryTagPlaceholder": "选择出站",
|
||||
"xrayObservatoryAlive": "在线",
|
||||
"xrayObservatoryDead": "离线",
|
||||
"xrayObservatoryLastSeen": "最后在线",
|
||||
"xrayObservatoryLastTry": "最后尝试",
|
||||
"trendLast2Min": "最近 2 分钟",
|
||||
"systemLoad": "系统负载",
|
||||
"systemLoadDesc": "过去 1、5 和 15 分钟的系统平均负载",
|
||||
|
||||
@@ -143,6 +143,17 @@
|
||||
"xrayErrorPopoverTitle": "執行Xray時發生錯誤",
|
||||
"operationHours": "系統正常執行時間",
|
||||
"systemHistoryTitle": "系統歷史",
|
||||
"charts": "圖表",
|
||||
"xrayMetricsTitle": "Xray 指標",
|
||||
"xrayMetricsDisabled": "未設定 Xray 指標端點",
|
||||
"xrayMetricsHint": "在 xray 設定中加入頂層 metrics 區塊,tag 為 metrics_out,listen 為 127.0.0.1:11111,然後重啟 xray。",
|
||||
"xrayObservatoryEmpty": "尚無 Observatory 資料",
|
||||
"xrayObservatoryHint": "在 xray 設定中加入 observatory 區塊,列出要探測的出站 tag,然後重啟 xray。",
|
||||
"xrayObservatoryTagPlaceholder": "選擇出站",
|
||||
"xrayObservatoryAlive": "在線",
|
||||
"xrayObservatoryDead": "離線",
|
||||
"xrayObservatoryLastSeen": "最後在線",
|
||||
"xrayObservatoryLastTry": "最後嘗試",
|
||||
"trendLast2Min": "最近 2 分鐘",
|
||||
"systemLoad": "系統負載",
|
||||
"systemLoadDesc": "過去 1、5 和 15 分鐘的系統平均負載",
|
||||
|
||||
Reference in New Issue
Block a user