refactor(traffic): drop all-time traffic tracking

Removes the AllTime field from Inbound and ClientTraffic and migrates
existing DBs by dropping the all_time columns on startup. The counter
duplicated up+down without adding signal, and the per-event accumulator
ran on every traffic write.

Frontend: drop the All-time column from the inbound list and the
client-row table, the All-time row from the client info modal, and the
All-Time Total Usage tile from the inbounds summary card. The
allTimeTraffic/allTimeTrafficUsage i18n keys are removed across every
locale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MHSanaei 2026-05-17 09:01:04 +02:00
parent 8a4101a96b
commit f315ed269e
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
22 changed files with 24 additions and 130 deletions

View file

@ -49,7 +49,6 @@ type Inbound struct {
Up int64 `json:"up" form:"up"` // Upload traffic in bytes Up int64 `json:"up" form:"up"` // Upload traffic in bytes
Down int64 `json:"down" form:"down"` // Download traffic in bytes Down int64 `json:"down" form:"down"` // Download traffic in bytes
Total int64 `json:"total" form:"total"` // Total traffic limit in bytes Total int64 `json:"total" form:"total"` // Total traffic limit in bytes
AllTime int64 `json:"allTime" form:"allTime" gorm:"default:0"` // All-time traffic usage
Remark string `json:"remark" form:"remark"` // Human-readable remark Remark string `json:"remark" form:"remark"` // Human-readable remark
Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"` // Whether the inbound is enabled Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"` // Whether the inbound is enabled
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp

View file

@ -10,7 +10,6 @@ export class DBInbound {
this.up = 0; this.up = 0;
this.down = 0; this.down = 0;
this.total = 0; this.total = 0;
this.allTime = 0;
this.remark = ""; this.remark = "";
this.enable = true; this.enable = true;
this.expiryTime = 0; this.expiryTime = 0;

View file

@ -203,13 +203,6 @@ function close() {
</td> </td>
</tr> </tr>
<tr>
<td>{{ t('pages.inbounds.allTimeTraffic') || 'All-time' }}</td>
<td>
<a-tag>{{ SizeFormatter.sizeFormat(traffic?.allTime || used) }}</a-tag>
</td>
</tr>
<tr> <tr>
<td>{{ t('pages.inbounds.expireDate') || 'Expiry' }}</td> <td>{{ t('pages.inbounds.expireDate') || 'Expiry' }}</td>
<td> <td>

View file

@ -86,14 +86,6 @@ function getRem(email) {
const r = s.total - s.up - s.down; const r = s.total - s.up - s.down;
return r > 0 ? r : 0; return r > 0 ? r : 0;
} }
function getAllTime(email) {
const s = statsFor(email);
if (!s) return 0;
// allTime is the cumulative-historical counter; never let it dip
// below up+down (manual edits / partial migrations can push it under).
const current = (s.up || 0) + (s.down || 0);
return s.allTime > current ? s.allTime : current;
}
function isClientDepleted(email) { function isClientDepleted(email) {
const s = statsFor(email); const s = statsFor(email);
if (!s) return false; if (!s) return false;
@ -286,7 +278,6 @@ function confirmBulkDelete() {
<div class="cell cell-client">{{ t('pages.inbounds.client') }}</div> <div class="cell cell-client">{{ t('pages.inbounds.client') }}</div>
<div class="cell cell-traffic">{{ t('pages.inbounds.traffic') }}</div> <div class="cell cell-traffic">{{ t('pages.inbounds.traffic') }}</div>
<div class="cell cell-remained">{{ t('remained') }}</div> <div class="cell cell-remained">{{ t('remained') }}</div>
<div class="cell cell-alltime">{{ t('pages.inbounds.allTimeTraffic') }}</div>
<div class="cell cell-expiry">{{ t('pages.inbounds.expireDate') }}</div> <div class="cell cell-expiry">{{ t('pages.inbounds.expireDate') }}</div>
</div> </div>
@ -388,10 +379,6 @@ function confirmBulkDelete() {
</a-tag> </a-tag>
</div> </div>
<div class="cell cell-alltime">
<a-tag>{{ SizeFormatter.sizeFormat(getAllTime(client.email)) }}</a-tag>
</div>
<div class="cell cell-expiry"> <div class="cell cell-expiry">
<template v-if="client.expiryTime !== 0 && client.reset > 0"> <template v-if="client.expiryTime !== 0 && client.reset > 0">
<a-popover> <a-popover>
@ -499,10 +486,6 @@ function confirmBulkDelete() {
{{ SizeFormatter.sizeFormat(getRem(statsClient.email)) }} {{ SizeFormatter.sizeFormat(getRem(statsClient.email)) }}
</a-tag> </a-tag>
</div> </div>
<div class="stat-row">
<span class="stat-label">{{ t('pages.inbounds.allTimeTraffic') }}</span>
<a-tag>{{ SizeFormatter.sizeFormat(getAllTime(statsClient.email)) }}</a-tag>
</div>
<div class="stat-row"> <div class="stat-row">
<span class="stat-label">{{ t('online') }}</span> <span class="stat-label">{{ t('online') }}</span>
<a-tag v-if="statsClient.enable && isClientOnline(statsClient.email)" color="green">{{ t('online') <a-tag v-if="statsClient.enable && isClientOnline(statsClient.email)" color="green">{{ t('online')
@ -571,8 +554,6 @@ function confirmBulkDelete() {
minmax(160px, 2fr) minmax(160px, 2fr)
/* traffic */ /* traffic */
130px 130px
/* all-time */
130px
/* remained */ /* remained */
140px; 140px;
/* expiry */ /* expiry */
@ -597,8 +578,6 @@ function confirmBulkDelete() {
minmax(160px, 2fr) minmax(160px, 2fr)
/* traffic */ /* traffic */
130px 130px
/* all-time */
130px
/* remained */ /* remained */
140px; 140px;
/* expiry */ /* expiry */
@ -628,7 +607,6 @@ function confirmBulkDelete() {
.cell-actions, .cell-actions,
.cell-enable, .cell-enable,
.cell-online, .cell-online,
.cell-alltime,
.cell-remained { .cell-remained {
text-align: center; text-align: center;
display: inline-flex; display: inline-flex;

View file

@ -189,7 +189,6 @@ const sortFns = {
port: (a, b) => a.port - b.port, port: (a, b) => a.port - b.port,
protocol: (a, b) => a.protocol.localeCompare(b.protocol), protocol: (a, b) => a.protocol.localeCompare(b.protocol),
traffic: (a, b) => (a.up + a.down) - (b.up + b.down), traffic: (a, b) => (a.up + a.down) - (b.up + b.down),
allTimeInbound: (a, b) => (a.allTime || 0) - (b.allTime || 0),
expiryTime: (a, b) => (a.expiryTime || Infinity) - (b.expiryTime || Infinity), expiryTime: (a, b) => (a.expiryTime || Infinity) - (b.expiryTime || Infinity),
node: (a, b) => { node: (a, b) => {
const nameA = props.nodesById.get(a.nodeId)?.name ?? (a.nodeId == null ? '\uffff' : `node #${a.nodeId}`); const nameA = props.nodesById.get(a.nodeId)?.name ?? (a.nodeId == null ? '\uffff' : `node #${a.nodeId}`);
@ -244,7 +243,6 @@ const desktopColumns = computed(() => {
sortableCol({ title: t('pages.inbounds.protocol'), key: 'protocol', align: 'left', width: 130 }, 'protocol'), sortableCol({ title: t('pages.inbounds.protocol'), key: 'protocol', align: 'left', width: 130 }, 'protocol'),
sortableCol({ title: t('clients'), key: 'clients', align: 'left', width: 50 }, 'clients'), sortableCol({ title: t('clients'), key: 'clients', align: 'left', width: 50 }, 'clients'),
sortableCol({ title: t('pages.inbounds.traffic'), key: 'traffic', align: 'center', width: 90 }, 'traffic'), sortableCol({ title: t('pages.inbounds.traffic'), key: 'traffic', align: 'center', width: 90 }, 'traffic'),
sortableCol({ title: t('pages.inbounds.allTimeTraffic'), key: 'allTimeInbound', align: 'center', width: 95 }, 'allTimeInbound'),
sortableCol({ title: t('pages.inbounds.expireDate'), key: 'expiryTime', align: 'center', width: 40 }, 'expiryTime'), sortableCol({ title: t('pages.inbounds.expireDate'), key: 'expiryTime', align: 'center', width: 40 }, 'expiryTime'),
); );
return cols; return cols;
@ -515,10 +513,6 @@ function showQrCodeMenu(dbInbound) {
<InfinityIcon v-else /> <InfinityIcon v-else />
</a-tag> </a-tag>
</div> </div>
<div class="stat-row">
<span class="stat-label">{{ t('pages.inbounds.allTimeTraffic') }}</span>
<a-tag>{{ SizeFormatter.sizeFormat(statsRecord.allTime || 0) }}</a-tag>
</div>
<div v-if="clientCount[statsRecord.id]" class="stat-row"> <div v-if="clientCount[statsRecord.id]" class="stat-row">
<span class="stat-label">{{ t('clients') }}</span> <span class="stat-label">{{ t('clients') }}</span>
<a-tag color="green" class="client-count-tag">{{ clientCount[statsRecord.id].clients }}</a-tag> <a-tag color="green" class="client-count-tag">{{ clientCount[statsRecord.id].clients }}</a-tag>
@ -735,11 +729,6 @@ function showQrCodeMenu(dbInbound) {
</a-popover> </a-popover>
</template> </template>
<!-- ============== All-time inbound traffic ============== -->
<template v-else-if="column.key === 'allTimeInbound'">
<a-tag>{{ SizeFormatter.sizeFormat(record.allTime || 0) }}</a-tag>
</template>
<!-- ============== Expiry ============== --> <!-- ============== Expiry ============== -->
<template v-else-if="column.key === 'expiryTime'"> <template v-else-if="column.key === 'expiryTime'">
<a-popover v-if="record.expiryTime > 0"> <a-popover v-if="record.expiryTime > 0">

View file

@ -5,7 +5,6 @@ import { Modal, message } from 'ant-design-vue';
import { import {
SwapOutlined, SwapOutlined,
PieChartOutlined, PieChartOutlined,
HistoryOutlined,
BarsOutlined, BarsOutlined,
TeamOutlined, TeamOutlined,
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
@ -582,14 +581,6 @@ function onRowAction({ key, dbInbound }) {
</template> </template>
</CustomStatistic> </CustomStatistic>
</a-col> </a-col>
<a-col :xs="12" :sm="12" :md="5">
<CustomStatistic :title="t('pages.inbounds.allTimeTrafficUsage')"
:value="SizeFormatter.sizeFormat(totals.allTime)">
<template #prefix>
<HistoryOutlined />
</template>
</CustomStatistic>
</a-col>
<a-col :xs="12" :sm="12" :md="5"> <a-col :xs="12" :sm="12" :md="5">
<CustomStatistic :title="t('pages.inbounds.inboundCount')" :value="String(dbInbounds.length)"> <CustomStatistic :title="t('pages.inbounds.inboundCount')" :value="String(dbInbounds.length)">
<template #prefix> <template #prefix>

View file

@ -195,7 +195,6 @@ export function useInbounds() {
if (!upd) continue; if (!upd) continue;
if (typeof upd.up === 'number') ib.up = upd.up; if (typeof upd.up === 'number') ib.up = upd.up;
if (typeof upd.down === 'number') ib.down = upd.down; if (typeof upd.down === 'number') ib.down = upd.down;
if (typeof upd.allTime === 'number') ib.allTime = upd.allTime;
if (typeof upd.total === 'number') ib.total = upd.total; if (typeof upd.total === 'number') ib.total = upd.total;
if (typeof upd.enable === 'boolean') ib.enable = upd.enable; if (typeof upd.enable === 'boolean') ib.enable = upd.enable;
touched = true; touched = true;
@ -216,7 +215,6 @@ export function useInbounds() {
if (typeof upd.up === 'number') stat.up = upd.up; if (typeof upd.up === 'number') stat.up = upd.up;
if (typeof upd.down === 'number') stat.down = upd.down; if (typeof upd.down === 'number') stat.down = upd.down;
if (typeof upd.total === 'number') stat.total = upd.total; if (typeof upd.total === 'number') stat.total = upd.total;
if (typeof upd.allTime === 'number') stat.allTime = upd.allTime;
if (typeof upd.expiryTime === 'number') stat.expiryTime = upd.expiryTime; if (typeof upd.expiryTime === 'number') stat.expiryTime = upd.expiryTime;
if (typeof upd.enable === 'boolean') stat.enable = upd.enable; if (typeof upd.enable === 'boolean') stat.enable = upd.enable;
touched = true; touched = true;
@ -283,12 +281,9 @@ export function useInbounds() {
} }
} }
// Aggregate totals shown in the dashboard summary card. allTime falls
// back to up+down when the per-inbound counter isn't populated yet.
const totals = computed(() => { const totals = computed(() => {
let up = 0; let up = 0;
let down = 0; let down = 0;
let allTime = 0;
let clients = 0; let clients = 0;
const deactive = []; const deactive = [];
const depleted = []; const depleted = [];
@ -297,7 +292,6 @@ export function useInbounds() {
for (const ib of dbInbounds.value) { for (const ib of dbInbounds.value) {
up += ib.up || 0; up += ib.up || 0;
down += ib.down || 0; down += ib.down || 0;
allTime += ib.allTime || (ib.up + ib.down) || 0;
const c = clientCount.value[ib.id]; const c = clientCount.value[ib.id];
if (c) { if (c) {
clients += c.clients; clients += c.clients;
@ -307,7 +301,7 @@ export function useInbounds() {
online.push(...c.online); online.push(...c.online);
} }
} }
return { up, down, allTime, clients, deactive, depleted, expiring, online }; return { up, down, clients, deactive, depleted, expiring, online };
}); });
// ObjectUtil reference is wired at module load — keeping a no-op import // ObjectUtil reference is wired at module load — keeping a no-op import

View file

@ -1700,7 +1700,6 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
ExpiryTime: snapIb.ExpiryTime, ExpiryTime: snapIb.ExpiryTime,
Up: snapIb.Up, Up: snapIb.Up,
Down: snapIb.Down, Down: snapIb.Down,
AllTime: snapIb.AllTime,
} }
if err := tx.Create(&newIb).Error; err != nil { if err := tx.Create(&newIb).Error; err != nil {
logger.Warning("setRemoteTraffic: create central inbound for tag", snapIb.Tag, "failed:", err) logger.Warning("setRemoteTraffic: create central inbound for tag", snapIb.Tag, "failed:", err)
@ -1730,9 +1729,6 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
updates["up"] = snapIb.Up updates["up"] = snapIb.Up
updates["down"] = snapIb.Down updates["down"] = snapIb.Down
} }
if snapIb.AllTime > c.AllTime {
updates["all_time"] = snapIb.AllTime
}
if c.Settings != snapIb.Settings || if c.Settings != snapIb.Settings ||
c.Remark != snapIb.Remark || c.Remark != snapIb.Remark ||
@ -1792,7 +1788,6 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
Reset: cs.Reset, Reset: cs.Reset,
Up: cs.Up, Up: cs.Up,
Down: cs.Down, Down: cs.Down,
AllTime: cs.AllTime,
LastOnline: cs.LastOnline, LastOnline: cs.LastOnline,
}).Error; err != nil { }).Error; err != nil {
return false, err return false, err
@ -1808,17 +1803,12 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
structuralChange = true structuralChange = true
} }
allTime := existing.AllTime
if cs.AllTime > allTime {
allTime = cs.AllTime
}
if inGrace && cs.Up+cs.Down > 0 { if inGrace && cs.Up+cs.Down > 0 {
if err := tx.Exec( if err := tx.Exec(
`UPDATE client_traffics `UPDATE client_traffics
SET enable = ?, total = ?, expiry_time = ?, reset = ?, all_time = ? SET enable = ?, total = ?, expiry_time = ?, reset = ?
WHERE inbound_id = ? AND email = ?`, WHERE inbound_id = ? AND email = ?`,
cs.Enable, cs.Total, cs.ExpiryTime, cs.Reset, allTime, c.Id, cs.Email, cs.Enable, cs.Total, cs.ExpiryTime, cs.Reset, c.Id, cs.Email,
).Error; err != nil { ).Error; err != nil {
return false, err return false, err
} }
@ -1828,9 +1818,9 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
if err := tx.Exec( if err := tx.Exec(
`UPDATE client_traffics `UPDATE client_traffics
SET up = ?, down = ?, enable = ?, total = ?, expiry_time = ?, reset = ?, SET up = ?, down = ?, enable = ?, total = ?, expiry_time = ?, reset = ?,
all_time = ?, last_online = MAX(last_online, ?) last_online = MAX(last_online, ?)
WHERE inbound_id = ? AND email = ?`, WHERE inbound_id = ? AND email = ?`,
cs.Up, cs.Down, cs.Enable, cs.Total, cs.ExpiryTime, cs.Reset, allTime, cs.Up, cs.Down, cs.Enable, cs.Total, cs.ExpiryTime, cs.Reset,
cs.LastOnline, c.Id, cs.Email, cs.LastOnline, c.Id, cs.Email,
).Error; err != nil { ).Error; err != nil {
return false, err return false, err
@ -1930,9 +1920,8 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
if traffic.IsInbound { if traffic.IsInbound {
err = tx.Model(&model.Inbound{}).Where("tag = ? AND node_id IS NULL", traffic.Tag). err = tx.Model(&model.Inbound{}).Where("tag = ? AND node_id IS NULL", traffic.Tag).
Updates(map[string]any{ Updates(map[string]any{
"up": gorm.Expr("up + ?", traffic.Up), "up": gorm.Expr("up + ?", traffic.Up),
"down": gorm.Expr("down + ?", traffic.Down), "down": gorm.Expr("down + ?", traffic.Down),
"all_time": gorm.Expr("COALESCE(all_time, 0) + ?", traffic.Up+traffic.Down),
}).Error }).Error
if err != nil { if err != nil {
return err return err
@ -1987,7 +1976,6 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
} }
dbClientTraffics[dbTraffic_index].Up += t.Up dbClientTraffics[dbTraffic_index].Up += t.Up
dbClientTraffics[dbTraffic_index].Down += t.Down dbClientTraffics[dbTraffic_index].Down += t.Down
dbClientTraffics[dbTraffic_index].AllTime += t.Up + t.Down
if t.Up+t.Down > 0 { if t.Up+t.Down > 0 {
dbClientTraffics[dbTraffic_index].LastOnline = now dbClientTraffics[dbTraffic_index].LastOnline = now
} }
@ -3449,19 +3437,18 @@ func (s *InboundService) GetAllClientTraffics() ([]*xray.ClientTraffic, error) {
} }
type InboundTrafficSummary struct { type InboundTrafficSummary struct {
Id int `json:"id"` Id int `json:"id"`
Up int64 `json:"up"` Up int64 `json:"up"`
Down int64 `json:"down"` Down int64 `json:"down"`
Total int64 `json:"total"` Total int64 `json:"total"`
AllTime int64 `json:"allTime"` Enable bool `json:"enable"`
Enable bool `json:"enable"`
} }
func (s *InboundService) GetInboundsTrafficSummary() ([]InboundTrafficSummary, error) { func (s *InboundService) GetInboundsTrafficSummary() ([]InboundTrafficSummary, error) {
db := database.GetDB() db := database.GetDB()
var summaries []InboundTrafficSummary var summaries []InboundTrafficSummary
if err := db.Model(&model.Inbound{}). if err := db.Model(&model.Inbound{}).
Select("id, up, down, total, all_time, enable"). Select("id, up, down, total, enable").
Find(&summaries).Error; err != nil { Find(&summaries).Error; err != nil {
return nil, err return nil, err
} }
@ -3489,9 +3476,8 @@ func (s *InboundService) UpdateClientTrafficByEmail(email string, upload int64,
err := db.Model(xray.ClientTraffic{}). err := db.Model(xray.ClientTraffic{}).
Where("email = ?", email). Where("email = ?", email).
Updates(map[string]any{ Updates(map[string]any{
"up": upload, "up": upload,
"down": download, "down": download,
"all_time": gorm.Expr("CASE WHEN COALESCE(all_time, 0) < ? THEN ? ELSE all_time END", upload+download, upload+download),
}).Error }).Error
if err != nil { if err != nil {
logger.Warningf("Error updating ClientTraffic with email %s: %v", email, err) logger.Warningf("Error updating ClientTraffic with email %s: %v", email, err)
@ -3664,23 +3650,15 @@ func (s *InboundService) MigrationRequirements() {
} }
}() }()
// Calculate and backfill all_time from up+down for inbounds and clients if tx.Migrator().HasColumn(&model.Inbound{}, "all_time") {
err = tx.Exec(` if err = tx.Migrator().DropColumn(&model.Inbound{}, "all_time"); err != nil {
UPDATE inbounds return
SET all_time = IFNULL(up, 0) + IFNULL(down, 0) }
WHERE IFNULL(all_time, 0) = 0 AND (IFNULL(up, 0) + IFNULL(down, 0)) > 0
`).Error
if err != nil {
return
} }
err = tx.Exec(` if tx.Migrator().HasColumn(&xray.ClientTraffic{}, "all_time") {
UPDATE client_traffics if err = tx.Migrator().DropColumn(&xray.ClientTraffic{}, "all_time"); err != nil {
SET all_time = IFNULL(up, 0) + IFNULL(down, 0) return
WHERE IFNULL(all_time, 0) = 0 AND (IFNULL(up, 0) + IFNULL(down, 0)) > 0 }
`).Error
if err != nil {
return
} }
// Fix inbounds based problems // Fix inbounds based problems

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "لا توجد مصادر geo مخصصة بعد — انقر على «إضافة» لإنشاء واحد" "customGeoEmpty": "لا توجد مصادر geo مخصصة بعد — انقر على «إضافة» لإنشاء واحد"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "إجمالي حركة المرور",
"allTimeTrafficUsage": "إجمالي الاستخدام طوال الوقت",
"title": "الإدخالات", "title": "الإدخالات",
"totalDownUp": "إجمالي المرسل/المستقبل", "totalDownUp": "إجمالي المرسل/المستقبل",
"totalUsage": "إجمالي الاستخدام", "totalUsage": "إجمالي الاستخدام",

View file

@ -238,8 +238,6 @@
"getConfigError": "An error occurred while retrieving the config file." "getConfigError": "An error occurred while retrieving the config file."
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "All-time Traffic",
"allTimeTrafficUsage": "All-Time Total Usage",
"title": "Inbounds", "title": "Inbounds",
"totalDownUp": "Total Sent/Received", "totalDownUp": "Total Sent/Received",
"totalUsage": "Total Usage", "totalUsage": "Total Usage",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "Aún no hay fuentes geo personalizadas — haz clic en Añadir para crear una" "customGeoEmpty": "Aún no hay fuentes geo personalizadas — haz clic en Añadir para crear una"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "Tráfico Total",
"allTimeTrafficUsage": "Uso de datos histórico",
"title": "Entradas", "title": "Entradas",
"totalDownUp": "Subidas/Descargas Totales", "totalDownUp": "Subidas/Descargas Totales",
"totalUsage": "Uso Total", "totalUsage": "Uso Total",

View file

@ -240,8 +240,6 @@
"node": "نود", "node": "نود",
"deployTo": "استقرار روی", "deployTo": "استقرار روی",
"localPanel": "پنل لوکال", "localPanel": "پنل لوکال",
"allTimeTraffic": "کل ترافیک",
"allTimeTrafficUsage": "کل استفاده در تمام مدت",
"title": "کاربران", "title": "کاربران",
"totalDownUp": "دریافت/ارسال کل", "totalDownUp": "دریافت/ارسال کل",
"totalUsage": "‌‌‌مصرف کل", "totalUsage": "‌‌‌مصرف کل",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "Belum ada sumber geo kustom — klik Tambah untuk membuatnya" "customGeoEmpty": "Belum ada sumber geo kustom — klik Tambah untuk membuatnya"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "Total Lalu Lintas",
"allTimeTrafficUsage": "Total Penggunaan Sepanjang Waktu",
"title": "Masuk", "title": "Masuk",
"totalDownUp": "Total Terkirim/Diterima", "totalDownUp": "Total Terkirim/Diterima",
"totalUsage": "Penggunaan Total", "totalUsage": "Penggunaan Total",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "カスタム geo ソースはまだありません — 「追加」をクリックして作成してください" "customGeoEmpty": "カスタム geo ソースはまだありません — 「追加」をクリックして作成してください"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "総トラフィック",
"allTimeTrafficUsage": "これまでの総使用量",
"title": "インバウンド一覧", "title": "インバウンド一覧",
"totalDownUp": "総アップロード / ダウンロード", "totalDownUp": "総アップロード / ダウンロード",
"totalUsage": "総使用量", "totalUsage": "総使用量",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "Ainda não há fontes geo personalizadas — clique em Adicionar para criar uma" "customGeoEmpty": "Ainda não há fontes geo personalizadas — clique em Adicionar para criar uma"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "Tráfego Total",
"allTimeTrafficUsage": "Uso total de todos os tempos",
"title": "Inbounds", "title": "Inbounds",
"totalDownUp": "Total Enviado/Recebido", "totalDownUp": "Total Enviado/Recebido",
"totalUsage": "Uso Total", "totalUsage": "Uso Total",

View file

@ -237,8 +237,6 @@
"getConfigError": "Произошла ошибка при получении конфигурационного файла" "getConfigError": "Произошла ошибка при получении конфигурационного файла"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "Общий трафик",
"allTimeTrafficUsage": "Общее использование за все время",
"title": "Подключения", "title": "Подключения",
"totalDownUp": "Отправлено/получено", "totalDownUp": "Отправлено/получено",
"totalUsage": "Всего трафика", "totalUsage": "Всего трафика",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "Henüz özel geo kaynağı yok — oluşturmak için Ekle'ye tıklayın" "customGeoEmpty": "Henüz özel geo kaynağı yok — oluşturmak için Ekle'ye tıklayın"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "Toplam Trafik",
"allTimeTrafficUsage": "Tüm Zamanların Toplam Kullanımı",
"title": "Gelenler", "title": "Gelenler",
"totalDownUp": "Toplam Gönderilen/Alınan", "totalDownUp": "Toplam Gönderilen/Alınan",
"totalUsage": "Toplam Kullanım", "totalUsage": "Toplam Kullanım",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "Користувацьких джерел geo поки немає — натисніть «Додати», щоб створити" "customGeoEmpty": "Користувацьких джерел geo поки немає — натисніть «Додати», щоб створити"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "Загальний трафік",
"allTimeTrafficUsage": "Загальне використання за весь час",
"title": "Вхідні", "title": "Вхідні",
"totalDownUp": "Всього надісланих/отриманих", "totalDownUp": "Всього надісланих/отриманих",
"totalUsage": "Всього використанно", "totalUsage": "Всього використанно",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "Chưa có nguồn geo tùy chỉnh nào — nhấp Thêm để tạo" "customGeoEmpty": "Chưa có nguồn geo tùy chỉnh nào — nhấp Thêm để tạo"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "Tổng Lưu Lượng",
"allTimeTrafficUsage": "Tổng mức sử dụng mọi lúc",
"title": "Điểm vào (Inbounds)", "title": "Điểm vào (Inbounds)",
"totalDownUp": "Tổng tải lên/tải xuống", "totalDownUp": "Tổng tải lên/tải xuống",
"totalUsage": "Tổng sử dụng", "totalUsage": "Tổng sử dụng",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "暂无自定义 geo 源 — 点击「添加」以创建" "customGeoEmpty": "暂无自定义 geo 源 — 点击「添加」以创建"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "累计总流量",
"allTimeTrafficUsage": "所有时间总使用量",
"title": "入站列表", "title": "入站列表",
"totalDownUp": "总上传 / 下载", "totalDownUp": "总上传 / 下载",
"totalUsage": "总用量", "totalUsage": "总用量",

View file

@ -237,8 +237,6 @@
"customGeoEmpty": "尚無自訂 geo 來源 — 點擊「新增」以建立" "customGeoEmpty": "尚無自訂 geo 來源 — 點擊「新增」以建立"
}, },
"inbounds": { "inbounds": {
"allTimeTraffic": "累計總流量",
"allTimeTrafficUsage": "所有时间总使用量",
"title": "入站列表", "title": "入站列表",
"totalDownUp": "總上傳 / 下載", "totalDownUp": "總上傳 / 下載",
"totalUsage": "總用量", "totalUsage": "總用量",

View file

@ -11,7 +11,6 @@ type ClientTraffic struct {
SubId string `json:"subId" form:"subId" gorm:"-"` SubId string `json:"subId" form:"subId" gorm:"-"`
Up int64 `json:"up" form:"up"` Up int64 `json:"up" form:"up"`
Down int64 `json:"down" form:"down"` Down int64 `json:"down" form:"down"`
AllTime int64 `json:"allTime" form:"allTime"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
Total int64 `json:"total" form:"total"` Total int64 `json:"total" form:"total"`
Reset int `json:"reset" form:"reset" gorm:"default:0"` Reset int `json:"reset" form:"reset" gorm:"default:0"`