diff --git a/apps/cmd/reset.go b/apps/cmd/reset.go index bdff36b..2861b3d 100644 --- a/apps/cmd/reset.go +++ b/apps/cmd/reset.go @@ -66,8 +66,8 @@ func runReset(cmd *cobra.Command, args []string) { tokenVersion = *admin.TokenVersion + 1 } if err = dao.UpdateAdminUser([]int64{*admin.Id}, map[string]interface{}{ - "username": username, - "password_hash": func() string { + "username": username, + "password_hash": func() string { hash, _ := util.HashPassword(password) return hash }(), diff --git a/apps/controller/admin_user.go b/apps/controller/admin_user.go index d83c368..a9e6f19 100644 --- a/apps/controller/admin_user.go +++ b/apps/controller/admin_user.go @@ -36,4 +36,3 @@ func AdminSecurity(c *gin.Context) { } vo.Success(gin.H{"forcePasswordChange": info.ForcePasswordChange}, c) } - diff --git a/apps/controller/dashboard.go b/apps/controller/dashboard.go index 48a79a6..8ef93f4 100644 --- a/apps/controller/dashboard.go +++ b/apps/controller/dashboard.go @@ -50,4 +50,3 @@ func DashboardSecurity(c *gin.Context) { } vo.Success(data, c) } - diff --git a/apps/controller/peer.go b/apps/controller/peer.go index a1f2253..0034dbc 100644 --- a/apps/controller/peer.go +++ b/apps/controller/peer.go @@ -222,4 +222,3 @@ func PeerClientConfig(c *gin.Context) { } vo.Success(data, c) } - diff --git a/apps/dao/admin_user.go b/apps/dao/admin_user.go index e8e06c3..853deaa 100644 --- a/apps/dao/admin_user.go +++ b/apps/dao/admin_user.go @@ -40,4 +40,3 @@ func UpdateAdminUser(ids []int64, updates map[string]interface{}) error { } return nil } - diff --git a/apps/dao/dashboard.go b/apps/dao/dashboard.go index 7d1202d..dda0a81 100644 --- a/apps/dao/dashboard.go +++ b/apps/dao/dashboard.go @@ -152,4 +152,3 @@ func DashboardSystemTimeseries(fromMs int64, toMs int64) ([]vo.DashboardSeriesPo } return rows, nil } - diff --git a/apps/dao/peer.go b/apps/dao/peer.go index 80a0cec..a1a8fe3 100644 --- a/apps/dao/peer.go +++ b/apps/dao/peer.go @@ -80,23 +80,3 @@ func PagePeer(peerPageDto dto.PeerPageDto) ([]entity.Peer, int64, error) { } return peers, total, nil } - -func UpdatePeerTraffic(name string, download int64, upload int64) error { - if upload == 0 && download == 0 { - return nil - } - updates := map[string]interface{}{} - if download != 0 { - updates["download_bytes"] = gorm.Expr("download_bytes + ?", download) - } - if upload != 0 { - updates["upload_bytes"] = gorm.Expr("upload_bytes + ?", upload) - } - updates["update_time"] = time.Now().Format("2006-01-02 15:04:05") - if tx := sqliteDB.Model(&entity.Peer{}).Where("name = ?", name).Updates(updates); tx.Error != nil { - logrus.Errorf("%v", tx.Error) - return errors.New(constant.SysError) - } - return nil -} - diff --git a/apps/dao/sqlite.go b/apps/dao/sqlite.go index 6e8f1ce..ab1be53 100644 --- a/apps/dao/sqlite.go +++ b/apps/dao/sqlite.go @@ -289,8 +289,8 @@ func migrateLegacyAccounts() error { if !tableExists("account") { return nil } - var accounts []entity.Account - if tx := sqliteDB.Model(&entity.Account{}).Order("id asc").Find(&accounts); tx.Error != nil { + var accounts []entity.LegacyAccount + if tx := sqliteDB.Model(&entity.LegacyAccount{}).Order("id asc").Find(&accounts); tx.Error != nil { logrus.Errorf("sqlite legacy account query err: %v", tx.Error) return errors.New("sqlite legacy account query err") } @@ -615,17 +615,17 @@ func CloseSqliteDB() error { } func IsSqliteReady() bool { - if sqliteDB == nil { - return false - } - db, err := sqliteDB.DB() - if err != nil { - return false - } - if err := db.Ping(); err != nil { - return false - } - return true + if sqliteDB == nil { + return false + } + db, err := sqliteDB.DB() + if err != nil { + return false + } + if err := db.Ping(); err != nil { + return false + } + return true } func Paginate(pageNum *int64, pageSize *int64) func(db *gorm.DB) *gorm.DB { @@ -641,4 +641,3 @@ func Paginate(pageNum *int64, pageSize *int64) func(db *gorm.DB) *gorm.DB { return db.Offset(int((num - 1) * size)).Limit(int(size)) } } - diff --git a/apps/dao/traffic.go b/apps/dao/traffic.go index cd725e5..eb83720 100644 --- a/apps/dao/traffic.go +++ b/apps/dao/traffic.go @@ -72,4 +72,3 @@ func UpsertTrafficAggregateDaily(peerId int64, dayStart int64, rxBytes int64, tx func gormExprAdd(column string, delta int64) interface{} { return gorm.Expr(fmt.Sprintf("%s + ?", column), delta) } - diff --git a/apps/frontend/src/lang/package/en.ts b/apps/frontend/src/lang/package/en.ts index 5164b76..d832df1 100644 --- a/apps/frontend/src/lang/package/en.ts +++ b/apps/frontend/src/lang/package/en.ts @@ -25,6 +25,18 @@ export default { stale: "Dashboard data is stale. Retrying automatically...", topPeers24h: "Top peers (24h)", refreshFailed: "Failed to refresh dashboard data", + cpu: "CPU", + ram: "RAM", + disk: "Disk", + peers: "Peers", + onlinePeers: "Online peers", + onlineDevices: "Online devices", + todayDownload: "Today download", + todayUpload: "Today upload", + trafficChart: "Traffic & System Timeseries", + download: "Download", + upload: "Upload", + total: "Total", }, admin: { changePasswordTitle: "Change password", @@ -102,8 +114,22 @@ export default { "I want to be a shooting star, cutting through the darkness, just to illuminate your dreams, good night🌛!", }, peer: { + name: "Peer", remark: "Remark", username: "Username", + secret: "Secret", + maxDevices: "Max devices", + disabled: "Disabled", + status: "Status", + traffic: "Traffic", + devices: "Devices", + lastConnectionAt: "Last connection", + overview: "Overview", + authId: "Auth ID", + clientUri: "Client URI", + clientQr: "Client QR", + unlimited: "Unlimited", + secretRotateConfirm: "Rotate peer secret? Existing client configurations will stop working until updated.", pass: "Pass", conPass: "ConPass", quota: "Quota", diff --git a/apps/frontend/src/lang/package/ru.ts b/apps/frontend/src/lang/package/ru.ts index b1159d8..1b9ce36 100644 --- a/apps/frontend/src/lang/package/ru.ts +++ b/apps/frontend/src/lang/package/ru.ts @@ -23,6 +23,18 @@ export default { stale: "Данные дашборда устарели. Выполняется автоматическая повторная попытка...", topPeers24h: "Топ пиров (24ч)", refreshFailed: "Не удалось обновить данные дашборда", + cpu: "CPU", + ram: "RAM", + disk: "Диск", + peers: "Пиры", + onlinePeers: "Пиры онлайн", + onlineDevices: "Устройства онлайн", + todayDownload: "Скачано за сегодня", + todayUpload: "Отдано за сегодня", + trafficChart: "Трафик и системные метрики", + download: "Скачано", + upload: "Отдано", + total: "Всего", }, admin: { changePasswordTitle: "Смена пароля", @@ -97,8 +109,22 @@ export default { greeting5: "Доброй ночи,", }, peer: { + name: "Пир", remark: "Комментарий", username: "Логин", + secret: "Секрет", + maxDevices: "Лимит устройств", + disabled: "Отключён", + status: "Статус", + traffic: "Трафик", + devices: "Устройства", + lastConnectionAt: "Последнее подключение", + overview: "Обзор", + authId: "Auth ID", + clientUri: "URI клиента", + clientQr: "QR клиента", + unlimited: "Безлимит", + secretRotateConfirm: "Сменить секрет пира? Текущие клиентские конфиги перестанут работать до обновления.", pass: "Пароль входа", conPass: "Пароль подключения", quota: "Квота", diff --git a/apps/frontend/src/types/components.d.ts b/apps/frontend/src/types/components.d.ts index 8ba1500..66a8e3c 100644 --- a/apps/frontend/src/types/components.d.ts +++ b/apps/frontend/src/types/components.d.ts @@ -14,6 +14,7 @@ declare module '@vue/runtime-core' { ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDivider: typeof import('element-plus/es')['ElDivider'] + ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] @@ -26,6 +27,9 @@ declare module '@vue/runtime-core' { ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] + ElProgress: typeof import('element-plus/es')['ElProgress'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] diff --git a/apps/frontend/src/views/dashboard/index.vue b/apps/frontend/src/views/dashboard/index.vue index ed55cc2..4994096 100644 --- a/apps/frontend/src/views/dashboard/index.vue +++ b/apps/frontend/src/views/dashboard/index.vue @@ -29,27 +29,48 @@ /> - CPU: {{ summary.system.cpuPercent }}% - RAM: {{ summary.system.memPercent }}% - Disk: {{ summary.system.diskPercent }}% - Peers: {{ summary.peers.total }} - Online peers: {{ summary.peers.onlinePeers }} - Online devices: {{ summary.peers.onlineDevices }} - Today download: {{ formatBytes(summary.traffic.todayDownloadBytes || 0) }} - Today upload: {{ formatBytes(summary.traffic.todayUploadBytes || 0) }} + {{ $t("dashboard.cpu") }}: {{ summary.system.cpuPercent }}% + {{ $t("dashboard.ram") }}: {{ summary.system.memPercent }}% + {{ $t("dashboard.disk") }}: {{ summary.system.diskPercent }}% + {{ $t("dashboard.peers") }}: {{ summary.peers.total }} + {{ $t("dashboard.onlinePeers") }}: {{ summary.peers.onlinePeers }} + {{ $t("dashboard.onlineDevices") }}: {{ summary.peers.onlineDevices }} + {{ $t("dashboard.todayDownload") }}: {{ formatBytes(summary.traffic.todayDownloadBytes || 0) }} + {{ $t("dashboard.todayUpload") }}: {{ formatBytes(summary.traffic.todayUploadBytes || 0) }} + + + + + + + + + + + - - + + - + - + @@ -60,9 +81,10 @@ + + diff --git a/apps/middleware/admin.go b/apps/middleware/admin.go index 370f4d2..5ee91ac 100644 --- a/apps/middleware/admin.go +++ b/apps/middleware/admin.go @@ -2,21 +2,27 @@ package middleware import ( "github.com/gin-gonic/gin" + "hy2xs-admin/model/bo" "hy2xs-admin/model/constant" "hy2xs-admin/model/vo" - "hy2xs-admin/service" "hy2xs-admin/util" ) func AdminHandler() gin.HandlerFunc { return func(c *gin.Context) { - myClaims, err := service.ParseToken(service.GetToken(c)) - if err != nil { - vo.Fail(err.Error(), c) + claimsRaw, ok := c.Get("adminClaims") + if !ok { + vo.Fail(constant.UnauthorizedError, c) c.Abort() return } - if !util.ArrContain(myClaims.Admin.Roles, "admin") { + claims, castOK := claimsRaw.(bo.AccountBo) + if !castOK { + vo.Fail(constant.IllegalTokenError, c) + c.Abort() + return + } + if !util.ArrContain(claims.Roles, "admin") { vo.Fail(constant.ForbiddenError, c) c.Abort() return diff --git a/apps/middleware/jwt.go b/apps/middleware/jwt.go index 26a19c2..e960bf4 100644 --- a/apps/middleware/jwt.go +++ b/apps/middleware/jwt.go @@ -48,6 +48,7 @@ func JWTHandler() gin.HandlerFunc { c.Abort() return } + c.Set("adminClaims", myClaims.Admin) c.Next() } } diff --git a/apps/model/bo/account.go b/apps/model/bo/account.go index 20dc32d..61e3d0f 100644 --- a/apps/model/bo/account.go +++ b/apps/model/bo/account.go @@ -1,7 +1,5 @@ package bo -import "time" - type AccountBo struct { Id int64 `json:"id"` Username string `json:"username"` @@ -10,26 +8,6 @@ type AccountBo struct { TokenVersion int64 `json:"tokenVersion"` } -type AccountExport struct { - Id int64 `json:"id"` - Username string `json:"username"` - Pass string `json:"pass"` - ConPass string `json:"conPass"` - Quota int64 `json:"quota"` - Download int64 `json:"download"` - Upload int64 `json:"upload"` - ExpireTime int64 `json:"expireTime"` - DeviceNo int64 `json:"deviceNo"` - KickUtilTime int64 `json:"kickUtilTime"` - Role string `json:"role"` - Deleted int64 `json:"deleted"` - CreateTime time.Time `json:"createTime"` - UpdateTime time.Time `json:"updateTime"` - LoginAt int64 `json:"loginAt"` - ConAt int64 `json:"conAt"` - Remark string `json:"remark"` -} - type PeerExport struct { Id int64 `json:"id,omitempty"` AuthId string `json:"authId,omitempty"` diff --git a/apps/model/dto/admin_user.go b/apps/model/dto/admin_user.go index fef84e9..a5fc22f 100644 --- a/apps/model/dto/admin_user.go +++ b/apps/model/dto/admin_user.go @@ -4,4 +4,3 @@ type AdminChangePasswordDto struct { OldPassword *string `json:"oldPassword" form:"oldPassword" validate:"required,min=6,max=64"` NewPassword *string `json:"newPassword" form:"newPassword" validate:"required,min=6,max=64"` } - diff --git a/apps/model/dto/auth.go b/apps/model/dto/auth.go index aa5f9ea..a19643f 100644 --- a/apps/model/dto/auth.go +++ b/apps/model/dto/auth.go @@ -4,4 +4,3 @@ type LoginDto struct { Username *string `json:"username" form:"username" validate:"required,min=6,max=32,validateStr"` Pass *string `json:"pass" form:"pass" validate:"required,min=6,max=64"` } - diff --git a/apps/model/dto/peer.go b/apps/model/dto/peer.go index a30341c..ee96497 100644 --- a/apps/model/dto/peer.go +++ b/apps/model/dto/peer.go @@ -31,4 +31,3 @@ type PeerUpdateDto struct { type PeerKickDto struct { BannedUntil *int64 `json:"bannedUntil" form:"bannedUntil" validate:"required,min=0"` } - diff --git a/apps/model/entity/account.go b/apps/model/entity/account.go index fe3da3d..2a1a204 100644 --- a/apps/model/entity/account.go +++ b/apps/model/entity/account.go @@ -1,6 +1,6 @@ package entity -type Account struct { +type LegacyAccount struct { Username *string `gorm:"column:username;default:''" json:"username"` Pass *string `gorm:"column:pass;default:''" json:"pass"` ConPass *string `gorm:"column:con_pass;default:''" json:"conPass"` @@ -19,3 +19,7 @@ type Account struct { Remark *string `gorm:"column:remark;default:''" json:"remark"` ForcePasswordChange *int64 `gorm:"column:force_password_change;default:0" json:"forcePasswordChange"` } + +func (LegacyAccount) TableName() string { + return "account" +} diff --git a/apps/model/entity/admin_user.go b/apps/model/entity/admin_user.go index bef6390..9db6c68 100644 --- a/apps/model/entity/admin_user.go +++ b/apps/model/entity/admin_user.go @@ -14,4 +14,3 @@ type AdminUser struct { func (AdminUser) TableName() string { return "admin_user" } - diff --git a/apps/model/entity/metric_sample.go b/apps/model/entity/metric_sample.go index 2b1a0c7..7e8649c 100644 --- a/apps/model/entity/metric_sample.go +++ b/apps/model/entity/metric_sample.go @@ -2,22 +2,21 @@ package entity type MetricSample struct { BaseEntity - SampledAt *int64 `gorm:"column:sampled_at"` - CpuPercent *float64 `gorm:"column:cpu_percent"` - Load1 *float64 `gorm:"column:load1"` - MemUsedBytes *int64 `gorm:"column:mem_used_bytes"` - MemTotalBytes *int64 `gorm:"column:mem_total_bytes"` - MemPercent *float64 `gorm:"column:mem_percent"` - DiskPath *string `gorm:"column:disk_path"` - DiskUsedBytes *int64 `gorm:"column:disk_used_bytes"` - DiskTotalBytes *int64 `gorm:"column:disk_total_bytes"` - DiskPercent *float64 `gorm:"column:disk_percent"` - HysteriaRunning *int64 `gorm:"column:hysteria_running"` - OnlinePeers *int64 `gorm:"column:online_peers"` - OnlineDevices *int64 `gorm:"column:online_devices"` + SampledAt *int64 `gorm:"column:sampled_at"` + CpuPercent *float64 `gorm:"column:cpu_percent"` + Load1 *float64 `gorm:"column:load1"` + MemUsedBytes *int64 `gorm:"column:mem_used_bytes"` + MemTotalBytes *int64 `gorm:"column:mem_total_bytes"` + MemPercent *float64 `gorm:"column:mem_percent"` + DiskPath *string `gorm:"column:disk_path"` + DiskUsedBytes *int64 `gorm:"column:disk_used_bytes"` + DiskTotalBytes *int64 `gorm:"column:disk_total_bytes"` + DiskPercent *float64 `gorm:"column:disk_percent"` + HysteriaRunning *int64 `gorm:"column:hysteria_running"` + OnlinePeers *int64 `gorm:"column:online_peers"` + OnlineDevices *int64 `gorm:"column:online_devices"` } func (MetricSample) TableName() string { return "metric_sample" } - diff --git a/apps/model/entity/peer.go b/apps/model/entity/peer.go index 6e6c691..99b6de1 100644 --- a/apps/model/entity/peer.go +++ b/apps/model/entity/peer.go @@ -20,4 +20,3 @@ type Peer struct { func (Peer) TableName() string { return "peer" } - diff --git a/apps/model/entity/traffic_aggregate_daily.go b/apps/model/entity/traffic_aggregate_daily.go index 035a76d..55ef02b 100644 --- a/apps/model/entity/traffic_aggregate_daily.go +++ b/apps/model/entity/traffic_aggregate_daily.go @@ -11,4 +11,3 @@ type TrafficAggregateDaily struct { func (TrafficAggregateDaily) TableName() string { return "traffic_aggregate_daily" } - diff --git a/apps/model/entity/traffic_aggregate_hourly.go b/apps/model/entity/traffic_aggregate_hourly.go index cec11cf..762e5aa 100644 --- a/apps/model/entity/traffic_aggregate_hourly.go +++ b/apps/model/entity/traffic_aggregate_hourly.go @@ -11,4 +11,3 @@ type TrafficAggregateHourly struct { func (TrafficAggregateHourly) TableName() string { return "traffic_aggregate_hourly" } - diff --git a/apps/model/entity/traffic_sample.go b/apps/model/entity/traffic_sample.go index 0ccec5c..11dd77f 100644 --- a/apps/model/entity/traffic_sample.go +++ b/apps/model/entity/traffic_sample.go @@ -12,4 +12,3 @@ type TrafficSample struct { func (TrafficSample) TableName() string { return "traffic_sample" } - diff --git a/apps/model/vo/admin_user.go b/apps/model/vo/admin_user.go index 8c0a5cc..cd88837 100644 --- a/apps/model/vo/admin_user.go +++ b/apps/model/vo/admin_user.go @@ -6,4 +6,3 @@ type AdminInfoVo struct { Roles []string `json:"roles"` ForcePasswordChange bool `json:"forcePasswordChange"` } - diff --git a/apps/model/vo/dashboard.go b/apps/model/vo/dashboard.go index 59934bc..1c88f14 100644 --- a/apps/model/vo/dashboard.go +++ b/apps/model/vo/dashboard.go @@ -1,13 +1,13 @@ package vo type DashboardSummaryVo struct { - CollectedAt int64 `json:"collectedAt"` - System DashboardSystemVo `json:"system"` + CollectedAt int64 `json:"collectedAt"` + System DashboardSystemVo `json:"system"` Hysteria DashboardHysteriaVo `json:"hysteria"` - Peers DashboardPeerVo `json:"peers"` - Traffic DashboardTrafficVo `json:"traffic"` - Health DashboardHealthVo `json:"health"` - SecurityRisks []SecurityRiskVo `json:"securityRisks"` + Peers DashboardPeerVo `json:"peers"` + Traffic DashboardTrafficVo `json:"traffic"` + Health DashboardHealthVo `json:"health"` + SecurityRisks []SecurityRiskVo `json:"securityRisks"` } type DashboardHealthVo struct { @@ -22,13 +22,13 @@ type DataHealthVo struct { } type DashboardSystemVo struct { - CpuPercent float64 `json:"cpuPercent"` - MemUsedBytes uint64 `json:"memUsedBytes"` - MemTotalBytes uint64 `json:"memTotalBytes"` - MemPercent float64 `json:"memPercent"` - DiskUsedBytes uint64 `json:"diskUsedBytes"` - DiskTotalBytes uint64 `json:"diskTotalBytes"` - DiskPercent float64 `json:"diskPercent"` + CpuPercent float64 `json:"cpuPercent"` + MemUsedBytes uint64 `json:"memUsedBytes"` + MemTotalBytes uint64 `json:"memTotalBytes"` + MemPercent float64 `json:"memPercent"` + DiskUsedBytes uint64 `json:"diskUsedBytes"` + DiskTotalBytes uint64 `json:"diskTotalBytes"` + DiskPercent float64 `json:"diskPercent"` } type DashboardHysteriaVo struct { @@ -48,11 +48,11 @@ type DashboardPeerVo struct { } type DashboardTrafficVo struct { - DownloadBytes int64 `json:"downloadBytes"` - UploadBytes int64 `json:"uploadBytes"` - TotalBytes int64 `json:"totalBytes"` - TodayDownloadBytes int64 `json:"todayDownloadBytes"` - TodayUploadBytes int64 `json:"todayUploadBytes"` + DownloadBytes int64 `json:"downloadBytes"` + UploadBytes int64 `json:"uploadBytes"` + TotalBytes int64 `json:"totalBytes"` + TodayDownloadBytes int64 `json:"todayDownloadBytes"` + TodayUploadBytes int64 `json:"todayUploadBytes"` SinceResetDownloadBytes int64 `json:"sinceResetDownloadBytes"` SinceResetUploadBytes int64 `json:"sinceResetUploadBytes"` } @@ -65,12 +65,12 @@ type SecurityRiskVo struct { } type DashboardTopPeerVo struct { - PeerId int64 `json:"peerId"` - Name string `json:"name"` - Remark string `json:"remark"` - Download int64 `json:"download"` - Upload int64 `json:"upload"` - Total int64 `json:"total"` + PeerId int64 `json:"peerId"` + Name string `json:"name"` + Remark string `json:"remark"` + Download int64 `json:"download"` + Upload int64 `json:"upload"` + Total int64 `json:"total"` } type DashboardSeriesPointVo struct { @@ -82,9 +82,8 @@ type DashboardSeriesPointVo struct { } type DashboardTimeseriesVo struct { - Range string `json:"range"` - Traffic []DashboardSeriesPointVo `json:"traffic"` - System []DashboardSeriesPointVo `json:"system"` - CollectedAt int64 `json:"collectedAt"` + Range string `json:"range"` + Traffic []DashboardSeriesPointVo `json:"traffic"` + System []DashboardSeriesPointVo `json:"system"` + CollectedAt int64 `json:"collectedAt"` } - diff --git a/apps/model/vo/peer.go b/apps/model/vo/peer.go index 44ed449..1b5b413 100644 --- a/apps/model/vo/peer.go +++ b/apps/model/vo/peer.go @@ -27,4 +27,3 @@ type PeerClientConfigVo struct { Url string `json:"url"` QrCode []byte `json:"qrCode"` } - diff --git a/apps/router/admin_user.go b/apps/router/admin_user.go index 86ae7f7..01a6840 100644 --- a/apps/router/admin_user.go +++ b/apps/router/admin_user.go @@ -13,4 +13,3 @@ func initAdminRouter(adminApi *gin.RouterGroup) { admin.GET("/security", controller.AdminSecurity) } } - diff --git a/apps/router/dashboard.go b/apps/router/dashboard.go index ac37d9c..7f19691 100644 --- a/apps/router/dashboard.go +++ b/apps/router/dashboard.go @@ -14,4 +14,3 @@ func initDashboardRouter(api *gin.RouterGroup) { dashboard.GET("/security", controller.DashboardSecurity) } } - diff --git a/apps/router/router.go b/apps/router/router.go index c27fce1..4fd9d3f 100644 --- a/apps/router/router.go +++ b/apps/router/router.go @@ -20,9 +20,9 @@ func Router(router *gin.Engine, huiWebContext *string) { if !sqliteReady || !configReadable { c.JSON(503, gin.H{ - "ok": false, - "service": "hy2xs-admin", - "sqlite_ready": sqliteReady, + "ok": false, + "service": "hy2xs-admin", + "sqlite_ready": sqliteReady, "config_readable": configReadable, }) return diff --git a/apps/service/admin_user.go b/apps/service/admin_user.go index a3c6188..4189eb7 100644 --- a/apps/service/admin_user.go +++ b/apps/service/admin_user.go @@ -13,6 +13,18 @@ import ( "hy2xs-admin/util" ) +func adminClaimsFromContext(c *gin.Context) (bo.AccountBo, bool) { + if c == nil { + return bo.AccountBo{}, false + } + v, ok := c.Get("adminClaims") + if !ok { + return bo.AccountBo{}, false + } + claims, castOK := v.(bo.AccountBo) + return claims, castOK +} + func Login(username string, plainPassword string) (string, bool, error) { admin, err := dao.GetAdminUser("username = ? and status = 1", username) if err != nil { @@ -45,11 +57,15 @@ func Login(username string, plainPassword string) (string, bool, error) { } func GetAdminInfo(c *gin.Context) (vo.AdminInfoVo, error) { - myClaims, err := ParseToken(GetToken(c)) - if err != nil { - return vo.AdminInfoVo{}, err + claims, ok := adminClaimsFromContext(c) + if !ok { + myClaims, err := ParseToken(GetToken(c)) + if err != nil { + return vo.AdminInfoVo{}, err + } + claims = myClaims.Admin } - admin, err := dao.GetAdminUser("id = ?", myClaims.Admin.Id) + admin, err := dao.GetAdminUser("id = ?", claims.Id) if err != nil { return vo.AdminInfoVo{}, err } @@ -57,7 +73,7 @@ func GetAdminInfo(c *gin.Context) (vo.AdminInfoVo, error) { return vo.AdminInfoVo{}, errors.New("this account has been disabled") } force := admin.ForcePasswordChange != nil && *admin.ForcePasswordChange != 0 - return vo.AdminInfoVo{Id: myClaims.Admin.Id, Username: myClaims.Admin.Username, Roles: myClaims.Admin.Roles, ForcePasswordChange: force}, nil + return vo.AdminInfoVo{Id: claims.Id, Username: claims.Username, Roles: claims.Roles, ForcePasswordChange: force}, nil } func UpdateAdminLastLoginAt(id int64, loginAt int64) error { @@ -100,4 +116,3 @@ func ChangeAdminPassword(c *gin.Context, oldPassword string, newPassword string) func GetAdminForTokenValidation(id int64) (entity.AdminUser, error) { return dao.GetAdminUser("id = ?", id) } - diff --git a/apps/service/dashboard.go b/apps/service/dashboard.go index 1bb45da..9041d44 100644 --- a/apps/service/dashboard.go +++ b/apps/service/dashboard.go @@ -102,4 +102,3 @@ func DashboardSecurityRisks(summary vo.DashboardSummaryVo) []vo.SecurityRiskVo { } return risks } - diff --git a/apps/service/hysteria2_api.go b/apps/service/hysteria2_api.go index a0492c8..e4badb4 100644 --- a/apps/service/hysteria2_api.go +++ b/apps/service/hysteria2_api.go @@ -2,6 +2,7 @@ package service import ( "errors" + "github.com/sirupsen/logrus" "hy2xs-admin/dao" "hy2xs-admin/model/constant" "hy2xs-admin/proxy" @@ -51,7 +52,8 @@ func Hysteria2Auth(conPass string) (int64, string, error) { // Ограничение количества устройств onlineUsers, err := Hysteria2Online() if err != nil { - return 0, "", err + logrus.WithError(err).Warn("hysteria2 online users unavailable; skip device-limit check") + return *peer.Id, *peer.AuthId, nil } device, exist := onlineUsers[*peer.AuthId] if exist && *peer.MaxDevices <= device { diff --git a/apps/service/hysteria2_api_test.go b/apps/service/hysteria2_api_test.go index 7b8e3b3..c88bbc1 100644 --- a/apps/service/hysteria2_api_test.go +++ b/apps/service/hysteria2_api_test.go @@ -71,4 +71,3 @@ func TestBuildHysteria2Url_MinimalConfig(t *testing.T) { t.Fatalf("unexpected optional query params in minimal config: %s", parsed.RawQuery) } } - diff --git a/apps/service/metrics_collector.go b/apps/service/metrics_collector.go index a5b2659..47db022 100644 --- a/apps/service/metrics_collector.go +++ b/apps/service/metrics_collector.go @@ -9,6 +9,8 @@ import ( "time" ) +const hysteriaVersionCacheTTL = 15 * time.Minute + type metricsSnapshot struct { CollectedAt int64 System vo.DashboardSystemVo @@ -23,12 +25,41 @@ var metricsStore = struct { sync.RWMutex snapshot metricsSnapshot lastSuccessAt int64 + version string + versionAt time.Time }{} +func getCachedHysteriaVersion(now time.Time) string { + metricsStore.RLock() + if metricsStore.version != "" && now.Sub(metricsStore.versionAt) < hysteriaVersionCacheTTL { + v := metricsStore.version + metricsStore.RUnlock() + return v + } + metricsStore.RUnlock() + + content, err := util.Exec(util.GetHysteria2BinPath() + " version") + if err != nil { + metricsStore.RLock() + v := metricsStore.version + metricsStore.RUnlock() + if v != "" { + return v + } + return "-" + } + + metricsStore.Lock() + metricsStore.version = content + metricsStore.versionAt = now + metricsStore.Unlock() + return content +} + func CollectMetricsSnapshot() { nowMs := time.Now().UnixMilli() s := metricsSnapshot{ - CollectedAt: nowMs, + CollectedAt: nowMs, CollectorState: vo.DataHealthVo{Status: "ok", LastSuccessAt: nowMs}, HysteriaState: vo.DataHealthVo{Status: "ok", LastSuccessAt: nowMs}, } @@ -52,10 +83,7 @@ func CollectMetricsSnapshot() { } s.Hysteria.Running = Hysteria2IsRunning() - s.Hysteria.Version = "-" - if content, err := util.Exec(util.GetHysteria2BinPath() + " version"); err == nil { - s.Hysteria.Version = content - } + s.Hysteria.Version = getCachedHysteriaVersion(time.Now()) if onlineMap, err := Hysteria2Online(); err == nil { s.Hysteria.ApiReachable = true @@ -80,6 +108,9 @@ func CollectMetricsSnapshot() { running = 1 } diskPath := "/" + if configured := util.GetEnvDiskPath(); configured != "" { + diskPath = configured + } metric := entity.MetricSample{ SampledAt: &nowMs, CpuPercent: &s.System.CpuPercent, @@ -108,4 +139,3 @@ func DashboardSnapshot() metricsSnapshot { defer metricsStore.RUnlock() return metricsStore.snapshot } - diff --git a/apps/service/peer.go b/apps/service/peer.go index 73c858d..2ccec08 100644 --- a/apps/service/peer.go +++ b/apps/service/peer.go @@ -24,7 +24,7 @@ func PagePeer(peerPageDto dto.PeerPageDto) ([]vo.PeerVo, int64, error) { result := make([]vo.PeerVo, 0, len(peers)) for _, p := range peers { item := vo.PeerVo{ - BaseVo: vo.BaseVo{Id: *p.Id, CreateTime: *p.CreateTime}, + BaseVo: vo.BaseVo{Id: *p.Id, CreateTime: *p.CreateTime}, Name: strVal(p.Name), Remark: strVal(p.Remark), AuthId: strVal(p.AuthId), @@ -137,7 +137,7 @@ func GetPeerVo(id int64) (vo.PeerVo, error) { return vo.PeerVo{}, err } return vo.PeerVo{ - BaseVo: vo.BaseVo{Id: *p.Id, CreateTime: *p.CreateTime}, + BaseVo: vo.BaseVo{Id: *p.Id, CreateTime: *p.CreateTime}, Name: strVal(p.Name), Remark: strVal(p.Remark), AuthId: strVal(p.AuthId), @@ -152,8 +152,12 @@ func GetPeerVo(id int64) (vo.PeerVo, error) { }, nil } -func ResetPeerTraffic(id int64) error { return dao.UpdatePeer([]int64{id}, map[string]interface{}{"download_bytes": 0, "upload_bytes": 0}) } -func ReleaseKickPeer(id int64) error { return dao.UpdatePeer([]int64{id}, map[string]interface{}{"banned_until": 0}) } +func ResetPeerTraffic(id int64) error { + return dao.UpdatePeer([]int64{id}, map[string]interface{}{"download_bytes": 0, "upload_bytes": 0}) +} +func ReleaseKickPeer(id int64) error { + return dao.UpdatePeer([]int64{id}, map[string]interface{}{"banned_until": 0}) +} func KickPeer(id int64, bannedUntil int64) error { if err := dao.UpdatePeer([]int64{id}, map[string]interface{}{"banned_until": bannedUntil}); err != nil { @@ -324,6 +328,15 @@ func UpdatePeerLastConnectionAt(id int64, conAt int64) error { return dao.UpdatePeer([]int64{id}, map[string]interface{}{"last_connection_at": conAt}) } -func strVal(v *string) string { if v == nil { return "" }; return *v } -func int64Val(v *int64) int64 { if v == nil { return 0 }; return *v } - +func strVal(v *string) string { + if v == nil { + return "" + } + return *v +} +func int64Val(v *int64) int64 { + if v == nil { + return 0 + } + return *v +} diff --git a/apps/service/peer_secret.go b/apps/service/peer_secret.go index cec5a71..d245d6c 100644 --- a/apps/service/peer_secret.go +++ b/apps/service/peer_secret.go @@ -82,4 +82,3 @@ func DecryptPeerSecret(stored string) (string, error) { } return util.DecryptAESGCM(stored, key) } - diff --git a/apps/util/encrypt.go b/apps/util/encrypt.go index 8679c79..7b20863 100644 --- a/apps/util/encrypt.go +++ b/apps/util/encrypt.go @@ -60,7 +60,6 @@ func HmacSHA256Hex(payload string, secret string) string { return str } - func DecodeBase64Key(raw string, expectedLen int) ([]byte, error) { raw = strings.TrimSpace(raw) if raw == "" { @@ -122,4 +121,3 @@ func DecryptAESGCM(cipherText string, key []byte) (string, error) { } return string(plain), nil } - diff --git a/apps/util/linux.go b/apps/util/linux.go index ddc436c..2277bb4 100644 --- a/apps/util/linux.go +++ b/apps/util/linux.go @@ -11,9 +11,21 @@ import ( "os" "os/exec" "strconv" + "strings" "time" ) +func resolveDiskPath() string { + if configured := strings.TrimSpace(os.Getenv("HY2XS_DISK_PATH")); configured != "" { + return configured + } + return "/" +} + +func GetEnvDiskPath() string { + return strings.TrimSpace(os.Getenv("HY2XS_DISK_PATH")) +} + func Exec(cmd string) (string, error) { command := exec.Command("bash", "-c", cmd) command.Env = os.Environ() @@ -83,22 +95,23 @@ func GetMemInfo() (*mem.VirtualMemoryStat, error) { } func GetDiskPercent() (float64, error) { - var err error - parts, err := disk.Partitions(true) - diskInfo, err := disk.Usage(parts[0].Mountpoint) + diskInfo, err := disk.Usage(resolveDiskPath()) + if err != nil { + return 0, err + } value, err := strconv.ParseFloat(fmt.Sprintf("%.1f", diskInfo.UsedPercent), 64) return value, err } func GetDiskInfo() (*disk.UsageStat, error) { - parts, err := disk.Partitions(true) + usage, err := disk.Usage(resolveDiskPath()) if err != nil { return nil, err } - if len(parts) == 0 { - return nil, errors.New("disk partition not found") + if usage == nil { + return nil, errors.New("disk usage not found") } - return disk.Usage(parts[0].Mountpoint) + return usage, nil } func VerifyPort(port string) error {