From 463b07db527324270530ae5b9f819cdb23e4d318 Mon Sep 17 00:00:00 2001
From: Sora39831 <540587985@qq.com>
Date: Fri, 3 Apr 2026 03:29:51 +0800
Subject: [PATCH] feat: add user dashboard with role-based access control
Add a simplified dashboard page for non-admin users showing username,
traffic usage, expiry time, and logout button. Implement role-based
routing so user-role accounts are redirected to their own dashboard
instead of the admin panel. Add getUserInfo API endpoint and i18n
translations across all 13 supported locales.
---
web/controller/inbound.go | 12 +++
web/controller/index.go | 7 +-
web/controller/xui.go | 17 ++-
web/html/user.html | 154 +++++++++++++++++++++++++++
web/translation/translate.ar_EG.toml | 9 ++
web/translation/translate.en_US.toml | 9 ++
web/translation/translate.es_ES.toml | 9 ++
web/translation/translate.fa_IR.toml | 9 ++
web/translation/translate.id_ID.toml | 9 ++
web/translation/translate.ja_JP.toml | 9 ++
web/translation/translate.pt_BR.toml | 9 ++
web/translation/translate.ru_RU.toml | 9 ++
web/translation/translate.tr_TR.toml | 9 ++
web/translation/translate.uk_UA.toml | 9 ++
web/translation/translate.vi_VN.toml | 9 ++
web/translation/translate.zh_CN.toml | 9 ++
web/translation/translate.zh_TW.toml | 9 ++
17 files changed, 305 insertions(+), 2 deletions(-)
create mode 100644 web/html/user.html
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index b012ec95..069ca188 100644
--- a/web/controller/inbound.go
+++ b/web/controller/inbound.go
@@ -48,6 +48,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients)
g.POST("/import", a.importInbound)
+ g.GET("/userInfo", a.getUserInfo)
g.POST("/onlines", a.onlines)
g.POST("/lastOnline", a.lastOnline)
g.POST("/updateClientTraffic/:email", a.updateClientTraffic)
@@ -454,3 +455,14 @@ func (a *InboundController) delInboundClientByEmail(c *gin.Context) {
a.xrayService.SetToNeedRestart()
}
}
+
+// getUserInfo returns client traffic information for the logged-in user.
+func (a *InboundController) getUserInfo(c *gin.Context) {
+ user := session.GetLoginUser(c)
+ traffic, err := a.inboundService.GetClientTrafficByEmail(user.Username)
+ if err != nil {
+ jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.trafficGetError"), err)
+ return
+ }
+ jsonObj(c, traffic, nil)
+}
diff --git a/web/controller/index.go b/web/controller/index.go
index bc371f14..1ada0f57 100644
--- a/web/controller/index.go
+++ b/web/controller/index.go
@@ -62,7 +62,12 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
// index handles the root route, redirecting logged-in users to the panel or showing the login page.
func (a *IndexController) index(c *gin.Context) {
if session.IsLogin(c) {
- c.Redirect(http.StatusTemporaryRedirect, "panel/")
+ user := session.GetLoginUser(c)
+ if user.Role == "admin" {
+ c.Redirect(http.StatusTemporaryRedirect, "panel/")
+ } else {
+ c.Redirect(http.StatusTemporaryRedirect, "panel/user")
+ }
return
}
html(c, "login.html", "pages.login.title", nil)
diff --git a/web/controller/xui.go b/web/controller/xui.go
index 51502900..a30b8034 100644
--- a/web/controller/xui.go
+++ b/web/controller/xui.go
@@ -1,6 +1,10 @@
package controller
import (
+ "net/http"
+
+ "github.com/mhsanaei/3x-ui/v2/web/session"
+
"github.com/gin-gonic/gin"
)
@@ -25,6 +29,7 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
g.Use(a.checkLogin)
g.GET("/", a.index)
+ g.GET("/user", a.user)
g.GET("/inbounds", a.inbounds)
g.GET("/settings", a.settings)
g.GET("/xray", a.xraySettings)
@@ -33,11 +38,21 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
a.xraySettingController = NewXraySettingController(g)
}
-// index renders the main panel index page.
+// index renders the main panel index page. Non-admin users are redirected to the user dashboard.
func (a *XUIController) index(c *gin.Context) {
+ user := session.GetLoginUser(c)
+ if user.Role != "admin" {
+ c.Redirect(http.StatusTemporaryRedirect, "user")
+ return
+ }
html(c, "index.html", "pages.index.title", nil)
}
+// user renders the user dashboard page.
+func (a *XUIController) user(c *gin.Context) {
+ html(c, "user.html", "pages.user.title", nil)
+}
+
// inbounds renders the inbounds management page.
func (a *XUIController) inbounds(c *gin.Context) {
html(c, "inbounds.html", "pages.inbounds.title", nil)
diff --git a/web/html/user.html b/web/html/user.html
new file mode 100644
index 00000000..7ea320c4
--- /dev/null
+++ b/web/html/user.html
@@ -0,0 +1,154 @@
+{{ template "page/head_start" .}}
+{{ template "page/head_end" .}}
+
+{{ template "page/body_start" .}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ i18n "pages.settings.language" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [[ username ]]
+
+
+ [[ traffic ? SizeFormatter.sizeFormat(traffic.up) : '-' ]]
+
+
+ [[ traffic ? SizeFormatter.sizeFormat(traffic.down) : '-' ]]
+
+
+
+
+ [[ SizeFormatter.sizeFormat(traffic.up + traffic.down) ]] / [[ SizeFormatter.sizeFormat(traffic.total) ]]
+
+
+
+ {{ i18n "unlimited" }}
+
+
+ -
+
+
+
+
+
+ [[ formatExpiryTime(traffic.expiryTime) ]]
+
+
+
+ {{ i18n "unlimited" }}
+
+
+ -
+
+
+
+ -
+
+
+
+
+ {{ i18n "menu.logout" }}
+
+
+
+
+
+
+
+
+
+{{template "page/body_scripts" .}}
+{{template "component/aThemeSwitch" .}}
+
+{{ template "page/body_end" .}}
diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml
index 4aa76642..e6a68915 100644
--- a/web/translation/translate.ar_EG.toml
+++ b/web/translation/translate.ar_EG.toml
@@ -312,6 +312,15 @@
"requestHeader" = "رأس الطلب"
"responseHeader" = "رأس الرد"
+[pages.user]
+"title" = "لوحة المستخدم"
+"username" = "اسم المستخدم"
+"upload" = "الرفع"
+"download" = "التحميل"
+"totalTraffic" = "إجمالي حركة المرور"
+"expiryTime" = "تاريخ الانتهاء"
+"status" = "الحالة"
+
[pages.settings]
"title" = "إعدادات البانل"
"save" = "حفظ"
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index 3a9c62d4..636e42eb 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -317,6 +317,15 @@
"requestHeader" = "Request Header"
"responseHeader" = "Response Header"
+[pages.user]
+"title" = "User Dashboard"
+"username" = "Username"
+"upload" = "Upload"
+"download" = "Download"
+"totalTraffic" = "Total Traffic"
+"expiryTime" = "Expiry Time"
+"status" = "Status"
+
[pages.settings]
"title" = "Panel Settings"
"save" = "Save"
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index 06247d67..5ec89b8c 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -312,6 +312,15 @@
"requestHeader" = "Encabezado de solicitud"
"responseHeader" = "Encabezado de respuesta"
+[pages.user]
+"title" = "Panel de Usuario"
+"username" = "Nombre de usuario"
+"upload" = "Subida"
+"download" = "Descarga"
+"totalTraffic" = "Tráfico Total"
+"expiryTime" = "Fecha de Expiración"
+"status" = "Estado"
+
[pages.settings]
"title" = "Configuraciones"
"save" = "Guardar"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 94a5e86e..964f65ed 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -312,6 +312,15 @@
"requestHeader" = "سربرگ درخواست"
"responseHeader" = "سربرگ پاسخ"
+[pages.user]
+"title" = "داشبورد کاربر"
+"username" = "نام کاربری"
+"upload" = "آپلود"
+"download" = "دانلود"
+"totalTraffic" = "ترافیک کل"
+"expiryTime" = "زمان انقضا"
+"status" = "وضعیت"
+
[pages.settings]
"title" = "تنظیمات پنل"
"save" = "ذخیره"
diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml
index d345403f..6399bf66 100644
--- a/web/translation/translate.id_ID.toml
+++ b/web/translation/translate.id_ID.toml
@@ -312,6 +312,15 @@
"requestHeader" = "Header Permintaan"
"responseHeader" = "Header Respons"
+[pages.user]
+"title" = "Dasbor Pengguna"
+"username" = "Nama Pengguna"
+"upload" = "Unggah"
+"download" = "Unduh"
+"totalTraffic" = "Total Lalu Lintas"
+"expiryTime" = "Waktu Kedaluwarsa"
+"status" = "Status"
+
[pages.settings]
"title" = "Pengaturan Panel"
"save" = "Simpan"
diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml
index a4936bbb..68ec64e8 100644
--- a/web/translation/translate.ja_JP.toml
+++ b/web/translation/translate.ja_JP.toml
@@ -312,6 +312,15 @@
"requestHeader" = "リクエストヘッダー"
"responseHeader" = "レスポンスヘッダー"
+[pages.user]
+"title" = "ユーザーダッシュボード"
+"username" = "ユーザー名"
+"upload" = "アップロード"
+"download" = "ダウンロード"
+"totalTraffic" = "合計トラフィック"
+"expiryTime" = "有効期限"
+"status" = "ステータス"
+
[pages.settings]
"title" = "パネル設定"
"save" = "保存"
diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml
index 77bbd9a8..593ae721 100644
--- a/web/translation/translate.pt_BR.toml
+++ b/web/translation/translate.pt_BR.toml
@@ -312,6 +312,15 @@
"requestHeader" = "Cabeçalho da Requisição"
"responseHeader" = "Cabeçalho da Resposta"
+[pages.user]
+"title" = "Painel do Usuário"
+"username" = "Nome de Usuário"
+"upload" = "Upload"
+"download" = "Download"
+"totalTraffic" = "Tráfego Total"
+"expiryTime" = "Data de Expiração"
+"status" = "Status"
+
[pages.settings]
"title" = "Configurações do Painel"
"save" = "Salvar"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index ed544bbb..352bfb2f 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -312,6 +312,15 @@
"requestHeader" = "Заголовок запроса"
"responseHeader" = "Заголовок ответа"
+[pages.user]
+"title" = "Панель пользователя"
+"username" = "Имя пользователя"
+"upload" = "Загрузка"
+"download" = "Скачивание"
+"totalTraffic" = "Общий трафик"
+"expiryTime" = "Срок действия"
+"status" = "Статус"
+
[pages.settings]
"title" = "Настройки"
"save" = "Сохранить"
diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml
index a1857a2a..ee66b725 100644
--- a/web/translation/translate.tr_TR.toml
+++ b/web/translation/translate.tr_TR.toml
@@ -312,6 +312,15 @@
"requestHeader" = "İstek Başlığı"
"responseHeader" = "Yanıt Başlığı"
+[pages.user]
+"title" = "Kullanıcı Paneli"
+"username" = "Kullanıcı Adı"
+"upload" = "Yükleme"
+"download" = "İndirme"
+"totalTraffic" = "Toplam Trafik"
+"expiryTime" = "Bitiş Tarihi"
+"status" = "Durum"
+
[pages.settings]
"title" = "Panel Ayarları"
"save" = "Kaydet"
diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml
index a7735af7..38bb276a 100644
--- a/web/translation/translate.uk_UA.toml
+++ b/web/translation/translate.uk_UA.toml
@@ -312,6 +312,15 @@
"requestHeader" = "Заголовок запиту"
"responseHeader" = "Заголовок відповіді"
+[pages.user]
+"title" = "Панель користувача"
+"username" = "Ім'я користувача"
+"upload" = "Завантаження"
+"download" = "Завантаження"
+"totalTraffic" = "Загальний трафік"
+"expiryTime" = "Термін дії"
+"status" = "Статус"
+
[pages.settings]
"title" = "Параметри панелі"
"save" = "Зберегти"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index c37c34ca..c8f18d7c 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -312,6 +312,15 @@
"requestHeader" = "Header yêu cầu"
"responseHeader" = "Header phản hồi"
+[pages.user]
+"title" = "Bảng điều khiển người dùng"
+"username" = "Tên người dùng"
+"upload" = "Tải lên"
+"download" = "Tải xuống"
+"totalTraffic" = "Tổng lưu lượng"
+"expiryTime" = "Thời gian hết hạn"
+"status" = "Trạng thái"
+
[pages.settings]
"title" = "Cài đặt"
"save" = "Lưu"
diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml
index 3252dcf6..2af36846 100644
--- a/web/translation/translate.zh_CN.toml
+++ b/web/translation/translate.zh_CN.toml
@@ -317,6 +317,15 @@
"requestHeader" = "请求头"
"responseHeader" = "响应头"
+[pages.user]
+"title" = "用户面板"
+"username" = "用户名"
+"upload" = "上传"
+"download" = "下载"
+"totalTraffic" = "总流量"
+"expiryTime" = "到期时间"
+"status" = "状态"
+
[pages.settings]
"title" = "面板设置"
"save" = "保存"
diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml
index 9000c73d..449c5c69 100644
--- a/web/translation/translate.zh_TW.toml
+++ b/web/translation/translate.zh_TW.toml
@@ -312,6 +312,15 @@
"requestHeader" = "請求頭"
"responseHeader" = "響應頭"
+[pages.user]
+"title" = "用戶面板"
+"username" = "用戶名"
+"upload" = "上傳"
+"download" = "下載"
+"totalTraffic" = "總流量"
+"expiryTime" = "到期時間"
+"status" = "狀態"
+
[pages.settings]
"title" = "面板設定"
"save" = "儲存"