diff options
| author | Ali Golzar <57574919+aliglzr@users.noreply.github.com> | 2025-08-31 19:33:50 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-31 19:33:50 +0300 |
| commit | 4a0914cb1e271ab4f076cb1bd68c9f07cc025e92 (patch) | |
| tree | 95a15ed4255736b0bc7edaa509951d62c9c0254a /web | |
| parent | 664269d513f4c122c9f5a713d3293777872d3353 (diff) | |
feat: add "Last Online" column to client list and modal (Closes #3402) (#3405)
* feat: persist client last online and expose API
* feat(ui): show client last online in table and info modal
* i18n: add “Last Online” across locales
* chore: format timestamps as HH:mm:ss
Diffstat (limited to 'web')
| -rw-r--r-- | web/assets/js/util/date-util.js | 2 | ||||
| -rw-r--r-- | web/controller/api.go | 1 | ||||
| -rw-r--r-- | web/controller/inbound.go | 5 | ||||
| -rw-r--r-- | web/html/component/aClientTable.html | 17 | ||||
| -rw-r--r-- | web/html/inbounds.html | 18 | ||||
| -rw-r--r-- | web/html/modals/inbound_info_modal.html | 6 | ||||
| -rw-r--r-- | web/service/inbound.go | 15 | ||||
| -rw-r--r-- | web/translation/translate.ar_EG.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.en_US.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.es_ES.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.fa_IR.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.id_ID.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.ja_JP.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.pt_BR.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.ru_RU.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.tr_TR.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.uk_UA.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.vi_VN.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.zh_CN.toml | 1 | ||||
| -rw-r--r-- | web/translation/translate.zh_TW.toml | 1 |
20 files changed, 70 insertions, 7 deletions
diff --git a/web/assets/js/util/date-util.js b/web/assets/js/util/date-util.js index 9b4b0f81..bbca1272 100644 --- a/web/assets/js/util/date-util.js +++ b/web/assets/js/util/date-util.js @@ -134,7 +134,7 @@ class DateUtil { } static formatMillis(millis) { - return moment(millis).format('YYYY-M-D H:m:s'); + return moment(millis).format('YYYY-M-D HH:mm:ss'); } static firstDayOfMonth() { diff --git a/web/controller/api.go b/web/controller/api.go index 636035ba..32af934e 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -47,6 +47,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) { {"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics}, {"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients}, {"POST", "/onlines", a.inboundController.onlines}, + {"POST", "/lastOnline", a.inboundController.lastOnline}, {"POST", "/updateClientTraffic/:email", a.inboundController.updateClientTraffic}, } diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 851b4b6f..9ff2f302 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -340,6 +340,11 @@ func (a *InboundController) onlines(c *gin.Context) { jsonObj(c, a.inboundService.GetOnlineClients(), nil) } +func (a *InboundController) lastOnline(c *gin.Context) { + data, err := a.inboundService.GetClientsLastOnline() + jsonObj(c, data, err) +} + func (a *InboundController) updateClientTraffic(c *gin.Context) { email := c.Param("email") diff --git a/web/html/component/aClientTable.html b/web/html/component/aClientTable.html index 53ec27a3..a7279e50 100644 --- a/web/html/component/aClientTable.html +++ b/web/html/component/aClientTable.html @@ -33,12 +33,17 @@ <a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch> </template> <template slot="online" slot-scope="text, client, index"> - <template v-if="client.enable && isClientOnline(client.email)"> - <a-tag color="green">{{ i18n "online" }}</a-tag> - </template> - <template v-else> - <a-tag>{{ i18n "offline" }}</a-tag> - </template> + <a-popover :overlay-class-name="themeSwitcher.currentTheme"> + <template slot="content" > + {{ i18n "lastOnline" }}: [[ formatLastOnline(client.email) ]] + </template> + <template v-if="client.enable && isClientOnline(client.email)"> + <a-tag color="green">{{ i18n "online" }}</a-tag> + </template> + <template v-else> + <a-tag>{{ i18n "offline" }}</a-tag> + </template> + </a-popover> </template> <template slot="client" slot-scope="text, client"> <a-space direction="horizontal" :size="2"> diff --git a/web/html/inbounds.html b/web/html/inbounds.html index 1621807e..dfccdd70 100644 --- a/web/html/inbounds.html +++ b/web/html/inbounds.html @@ -807,6 +807,7 @@ defaultKey: '', clientCount: [], onlineClients: [], + lastOnlineMap: {}, isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false, refreshing: false, refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000, @@ -835,6 +836,7 @@ return; } + await this.getLastOnlineMap(); await this.getOnlineUsers(); this.setInbounds(msg.obj); @@ -849,6 +851,11 @@ } this.onlineClients = msg.obj != null ? msg.obj : []; }, + async getLastOnlineMap() { + const msg = await HttpUtil.post('/panel/api/inbounds/lastOnline'); + if (!msg.success || !msg.obj) return; + this.lastOnlineMap = msg.obj || {} + }, async getDefaultSettings() { const msg = await HttpUtil.post('/panel/setting/defaultSettings'); if (!msg.success) { @@ -1493,6 +1500,17 @@ isClientOnline(email) { return this.onlineClients.includes(email); }, + getLastOnline(email) { + return this.lastOnlineMap[email] || null + }, + formatLastOnline(email) { + const ts = this.getLastOnline(email) + if (!ts) return '-' + if (this.datepicker === 'gregorian') { + return DateUtil.formatMillis(ts) + } + return DateUtil.convertToJalalian(moment(ts)) + }, isRemovable(dbInboundId) { return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1; }, diff --git a/web/html/modals/inbound_info_modal.html b/web/html/modals/inbound_info_modal.html index fe7d7a82..a15172f3 100644 --- a/web/html/modals/inbound_info_modal.html +++ b/web/html/modals/inbound_info_modal.html @@ -217,6 +217,12 @@ </template> </td> </tr> + <tr> + <td>{{ i18n "lastOnline" }}</td> + <td> + <a-tag>[[ app.formatLastOnline(infoModal.clientSettings && infoModal.clientSettings.email ? infoModal.clientSettings.email : '') ]]</a-tag> + </td> + </tr> <tr v-if="infoModal.clientSettings.comment"> <td>{{ i18n "comment" }}</td> <td> diff --git a/web/service/inbound.go b/web/service/inbound.go index 0621cdea..b494d502 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -967,6 +967,7 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr // Add user in onlineUsers array on traffic if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 { onlineClients = append(onlineClients, traffics[traffic_index].Email) + dbClientTraffics[dbTraffic_index].LastOnline = time.Now().UnixMilli() } break } @@ -2187,6 +2188,20 @@ func (s *InboundService) GetOnlineClients() []string { return p.GetOnlineClients() } +func (s *InboundService) GetClientsLastOnline() (map[string]int64, error) { + db := database.GetDB() + var rows []xray.ClientTraffic + err := db.Model(&xray.ClientTraffic{}).Select("email, last_online").Find(&rows).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + result := make(map[string]int64, len(rows)) + for _, r := range rows { + result[r.Email] = r.LastOnline + } + return result, nil +} + func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, []string, error) { db := database.GetDB() diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml index bbf68822..dd5618cb 100644 --- a/web/translation/translate.ar_EG.toml +++ b/web/translation/translate.ar_EG.toml @@ -50,6 +50,7 @@ "fail" = "فشل" "comment" = "تعليق" "success" = "تم بنجاح" +"lastOnline" = "آخر متصل" "getVersion" = "جيب النسخة" "install" = "تثبيت" "clients" = "عملاء" diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 1531fe30..89f127a8 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -50,6 +50,7 @@ "fail" = "Failed" "comment" = "Comment" "success" = "Successfully" +"lastOnline" = "Last Online" "getVersion" = "Get Version" "install" = "Install" "clients" = "Clients" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index 6a2b8958..070b6b57 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -50,6 +50,7 @@ "fail" = "Falló"
"comment" = "Comentario"
"success" = "Éxito"
+"lastOnline" = "Última conexión"
"getVersion" = "Obtener versión"
"install" = "Instalar"
"clients" = "Clientes"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index fb6d9f02..5c949928 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -50,6 +50,7 @@ "fail" = "ناموفق" "comment" = "توضیحات" "success" = "موفق" +"lastOnline" = "آخرین فعالیت" "getVersion" = "دریافت نسخه" "install" = "نصب" "clients" = "کاربران" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml index d0d77dc3..4dc8e378 100644 --- a/web/translation/translate.id_ID.toml +++ b/web/translation/translate.id_ID.toml @@ -50,6 +50,7 @@ "fail" = "Gagal" "comment" = "Komentar" "success" = "Berhasil" +"lastOnline" = "Terakhir online" "getVersion" = "Dapatkan Versi" "install" = "Instal" "clients" = "Klien" diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml index 3f89cf0c..54479232 100644 --- a/web/translation/translate.ja_JP.toml +++ b/web/translation/translate.ja_JP.toml @@ -50,6 +50,7 @@ "fail" = "失敗" "comment" = "コメント" "success" = "成功" +"lastOnline" = "最終オンライン" "getVersion" = "バージョン取得" "install" = "インストール" "clients" = "クライアント" diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml index 3755c61e..a3aac778 100644 --- a/web/translation/translate.pt_BR.toml +++ b/web/translation/translate.pt_BR.toml @@ -50,6 +50,7 @@ "fail" = "Falhou" "comment" = "Comentário" "success" = "Com Sucesso" +"lastOnline" = "Última vez online" "getVersion" = "Obter Versão" "install" = "Instalar" "clients" = "Clientes" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 8efc4673..718edb51 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -50,6 +50,7 @@ "fail" = "Ошибка" "comment" = "Комментарий" "success" = "Успешно" +"lastOnline" = "Был(а) в сети" "getVersion" = "Узнать версию" "install" = "Установка" "clients" = "Клиенты" diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml index a298dd30..047d9d57 100644 --- a/web/translation/translate.tr_TR.toml +++ b/web/translation/translate.tr_TR.toml @@ -50,6 +50,7 @@ "fail" = "Başarısız" "comment" = "Yorum" "success" = "Başarılı" +"lastOnline" = "Son çevrimiçi" "getVersion" = "Sürümü Al" "install" = "Yükle" "clients" = "Müşteriler" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml index 02ed7352..3dc5b3e7 100644 --- a/web/translation/translate.uk_UA.toml +++ b/web/translation/translate.uk_UA.toml @@ -50,6 +50,7 @@ "fail" = "Помилка" "comment" = "Коментар" "success" = "Успішно" +"lastOnline" = "Був(ла) онлайн" "getVersion" = "Отримати версію" "install" = "Встановити" "clients" = "Клієнти" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 3f61874f..aa0009eb 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -50,6 +50,7 @@ "fail" = "Thất bại"
"comment" = "Bình luận"
"success" = "Thành công"
+"lastOnline" = "Lần online gần nhất"
"getVersion" = "Lấy phiên bản"
"install" = "Cài đặt"
"clients" = "Các khách hàng"
diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml index bf19cfdb..01844f13 100644 --- a/web/translation/translate.zh_CN.toml +++ b/web/translation/translate.zh_CN.toml @@ -50,6 +50,7 @@ "fail" = "失败" "comment" = "评论" "success" = "成功" +"lastOnline" = "上次在线" "getVersion" = "获取版本" "install" = "安装" "clients" = "客户端" diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml index dfd284d2..f3121c69 100644 --- a/web/translation/translate.zh_TW.toml +++ b/web/translation/translate.zh_TW.toml @@ -50,6 +50,7 @@ "fail" = "失敗" "comment" = "評論" "success" = "成功" +"lastOnline" = "上次上線" "getVersion" = "獲取版本" "install" = "安裝" "clients" = "客戶端" |
