diff --git a/.github/workflows/mockery.yml b/.github/workflows/mockery.yml
index 957cec16..b108edd8 100644
--- a/.github/workflows/mockery.yml
+++ b/.github/workflows/mockery.yml
@@ -3,7 +3,6 @@ on:
push:
branches:
- main
- pull_request:
jobs:
mockery:
runs-on: ubuntu-latest
diff --git a/cmd/web/wire_gen.go b/cmd/web/wire_gen.go
index 0945f54e..fb932fa5 100644
--- a/cmd/web/wire_gen.go
+++ b/cmd/web/wire_gen.go
@@ -73,6 +73,8 @@ func initWeb() (*app.Web, error) {
middlewares := middleware.NewMiddlewares(koanf, logger, manager, appRepo)
userRepo := data.NewUserRepo(locale, db)
userService := service.NewUserService(locale, koanf, manager, userRepo)
+ userTokenRepo := data.NewUserTokenRepo(locale, db)
+ userTokenService := service.NewUserTokenService(locale, userTokenRepo)
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, databaseServerRepo)
databaseRepo := data.NewDatabaseRepo(locale, db, databaseServerRepo, databaseUserRepo)
@@ -142,7 +144,7 @@ func initWeb() (*app.Web, error) {
supervisorApp := supervisor.NewApp(locale)
toolboxApp := toolbox.NewApp(locale)
loader := bootstrap.NewLoader(benchmarkApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
- http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
+ http := route.NewHttp(userService, userTokenService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
wsService := service.NewWsService(locale, koanf, sshRepo)
ws := route.NewWs(wsService)
mux, err := bootstrap.NewRouter(locale, middlewares, http, ws)
diff --git a/go.sum b/go.sum
index bb5a64a4..3669d7aa 100644
--- a/go.sum
+++ b/go.sum
@@ -121,6 +121,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
@@ -403,6 +404,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
+golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -512,6 +515,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
+golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
+golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/biz/ssh.go b/internal/biz/ssh.go
index b88367a2..3f7332d0 100644
--- a/internal/biz/ssh.go
+++ b/internal/biz/ssh.go
@@ -38,7 +38,6 @@ func (r *SSH) BeforeSave(tx *gorm.DB) error {
}
return nil
-
}
func (r *SSH) AfterFind(tx *gorm.DB) error {
diff --git a/internal/biz/user.go b/internal/biz/user.go
index a0054b08..0191b455 100644
--- a/internal/biz/user.go
+++ b/internal/biz/user.go
@@ -21,7 +21,7 @@ type User struct {
type UserRepo interface {
List(page, limit uint) ([]*User, int64, error)
Get(id uint) (*User, error)
- Create(username, password string) (*User, error)
+ Create(username, password, email string) (*User, error)
UpdatePassword(id uint, password string) error
UpdateEmail(id uint, email string) error
Delete(id uint) error
diff --git a/internal/biz/user_token.go b/internal/biz/user_token.go
index 12776146..7b3c7615 100644
--- a/internal/biz/user_token.go
+++ b/internal/biz/user_token.go
@@ -2,20 +2,37 @@ package biz
import (
"time"
+
+ "github.com/go-rat/utils/hash"
+ "gorm.io/gorm"
)
type UserToken struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"index" json:"user_id"`
- Token string `gorm:"not null;default:'';unique" json:"token"`
+ Token string `gorm:"not null;default:'';unique" json:"-"`
IPs []string `gorm:"not null;default:'[]';serializer:json" json:"ips"`
ExpiredAt time.Time `json:"expired_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
-type UserTokenRepo interface {
- Create(userID uint, ips []string) (*UserToken, error)
- Get(id uint) (*UserToken, error)
- Save(user *UserToken) error
+func (r *UserToken) BeforeSave(tx *gorm.DB) error {
+ hasher := hash.NewArgon2id()
+ var err error
+
+ r.Token, err = hasher.Make(r.Token)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type UserTokenRepo interface {
+ List(userID, page, limit uint) ([]*UserToken, int64, error)
+ Create(userID uint, ips []string, expired time.Time) (*UserToken, error)
+ Get(id uint) (*UserToken, error)
+ Delete(id uint) error
+ Update(id uint, ips []string, expired time.Time) (*UserToken, error)
}
diff --git a/internal/data/data.go b/internal/data/data.go
index 4b374816..102dbacd 100644
--- a/internal/data/data.go
+++ b/internal/data/data.go
@@ -25,5 +25,6 @@ var ProviderSet = wire.NewSet(
NewSSHRepo,
NewTaskRepo,
NewUserRepo,
+ NewUserTokenRepo,
NewWebsiteRepo,
)
diff --git a/internal/data/user.go b/internal/data/user.go
index 134faa74..bd51c84b 100644
--- a/internal/data/user.go
+++ b/internal/data/user.go
@@ -44,7 +44,7 @@ func (r *userRepo) Get(id uint) (*biz.User, error) {
return user, nil
}
-func (r *userRepo) Create(username, password string) (*biz.User, error) {
+func (r *userRepo) Create(username, password, email string) (*biz.User, error) {
value, err := r.hasher.Make(password)
if err != nil {
return nil, err
@@ -53,6 +53,7 @@ func (r *userRepo) Create(username, password string) (*biz.User, error) {
user := &biz.User{
Username: username,
Password: value,
+ Email: email,
}
if err = r.db.Create(user).Error; err != nil {
return nil, err
diff --git a/internal/data/user_token.go b/internal/data/user_token.go
new file mode 100644
index 00000000..4fddd369
--- /dev/null
+++ b/internal/data/user_token.go
@@ -0,0 +1,89 @@
+package data
+
+import (
+ "time"
+
+ "github.com/go-rat/utils/hash"
+ "github.com/go-rat/utils/str"
+ "github.com/leonelquinteros/gotext"
+ "gorm.io/gorm"
+
+ "github.com/tnb-labs/panel/internal/biz"
+)
+
+type userTokenRepo struct {
+ t *gotext.Locale
+ db *gorm.DB
+ hasher hash.Hasher
+}
+
+func NewUserTokenRepo(t *gotext.Locale, db *gorm.DB) biz.UserTokenRepo {
+ return &userTokenRepo{
+ t: t,
+ db: db,
+ hasher: hash.NewArgon2id(),
+ }
+}
+
+func (r userTokenRepo) List(userID, page, limit uint) ([]*biz.UserToken, int64, error) {
+ userTokens := make([]*biz.UserToken, 0)
+ var total int64
+ err := r.db.Model(&biz.UserToken{}).Where("user_id = ?", userID).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&userTokens).Error
+ return userTokens, total, err
+}
+
+func (r userTokenRepo) Create(userID uint, ips []string, expired time.Time) (*biz.UserToken, error) {
+ token := str.Random(32)
+ hashedToken, err := r.hasher.Make(token)
+ if err != nil {
+ return nil, err
+ }
+
+ userToken := &biz.UserToken{
+ UserID: userID,
+ Token: hashedToken,
+ IPs: ips,
+ ExpiredAt: expired,
+ }
+ if err = r.db.Create(userToken).Error; err != nil {
+ return nil, err
+ }
+
+ userToken.Token = token
+
+ return userToken, nil
+}
+
+func (r userTokenRepo) Get(id uint) (*biz.UserToken, error) {
+ userToken := new(biz.UserToken)
+ if err := r.db.First(userToken, id).Error; err != nil {
+ return nil, err
+ }
+
+ return userToken, nil
+}
+
+func (r userTokenRepo) Delete(id uint) error {
+ userToken := new(biz.UserToken)
+ if err := r.db.First(userToken, id).Error; err != nil {
+ return err
+ }
+
+ return r.db.Delete(userToken).Error
+}
+
+func (r userTokenRepo) Update(id uint, ips []string, expired time.Time) (*biz.UserToken, error) {
+ userToken := new(biz.UserToken)
+ if err := r.db.First(userToken, id).Error; err != nil {
+ return nil, err
+ }
+
+ userToken.IPs = ips
+ userToken.ExpiredAt = expired
+
+ if err := r.db.Save(userToken).Error; err != nil {
+ return nil, err
+ }
+
+ return userToken, nil
+}
diff --git a/internal/http/request/common.go b/internal/http/request/common.go
index fe85395d..16ece4be 100644
--- a/internal/http/request/common.go
+++ b/internal/http/request/common.go
@@ -1,5 +1,5 @@
package request
type ID struct {
- ID uint `json:"id" form:"id" query:"id" validate:"required|min:1"`
+ ID uint `json:"id" form:"id" query:"id" uri:"id" validate:"required|min:1"`
}
diff --git a/internal/http/request/user.go b/internal/http/request/user.go
index b2d0b317..1b2347b5 100644
--- a/internal/http/request/user.go
+++ b/internal/http/request/user.go
@@ -18,6 +18,7 @@ type UserIsTwoFA struct {
type UserCreate struct {
Username string `json:"username" validate:"required|notExists:users,username"`
Password string `json:"password" validate:"required|password"`
+ Email string `json:"email" validate:"required|email"`
}
type UserUpdatePassword struct {
diff --git a/internal/http/request/user_token.go b/internal/http/request/user_token.go
new file mode 100644
index 00000000..f741ce78
--- /dev/null
+++ b/internal/http/request/user_token.go
@@ -0,0 +1,32 @@
+package request
+
+import "net/http"
+
+type UserTokenList struct {
+ UserID uint `query:"user_id"`
+ Paginate
+}
+
+type UserTokenCreate struct {
+ UserID uint `json:"user_id" validate:"required|exists:users,id"`
+ IPs []string `json:"ips"`
+ ExpiredAt int64 `json:"expired_at" validate:"required"`
+}
+
+func (r *UserTokenCreate) Rules(_ *http.Request) map[string]string {
+ return map[string]string{
+ "IPs.*": "required|ip",
+ }
+}
+
+type UserTokenUpdate struct {
+ ID uint `uri:"id"`
+ IPs []string `json:"ips"`
+ ExpiredAt int64 `json:"expired_at" validate:"required"`
+}
+
+func (r *UserTokenUpdate) Rules(_ *http.Request) map[string]string {
+ return map[string]string{
+ "IPs.*": "required|ip",
+ }
+}
diff --git a/internal/route/http.go b/internal/route/http.go
index 973b60c4..e6d73db7 100644
--- a/internal/route/http.go
+++ b/internal/route/http.go
@@ -16,6 +16,7 @@ import (
type Http struct {
user *service.UserService
+ userToken *service.UserTokenService
dashboard *service.DashboardService
task *service.TaskService
website *service.WebsiteService
@@ -46,6 +47,7 @@ type Http struct {
func NewHttp(
user *service.UserService,
+ userToken *service.UserTokenService,
dashboard *service.DashboardService,
task *service.TaskService,
website *service.WebsiteService,
@@ -75,6 +77,7 @@ func NewHttp(
) *Http {
return &Http{
user: user,
+ userToken: userToken,
dashboard: dashboard,
task: task,
website: website,
@@ -125,6 +128,13 @@ func (route *Http) Register(r *chi.Mux) {
r.Delete("/{id}", route.user.Delete)
})
+ r.Route("/user_tokens", func(r chi.Router) {
+ r.Get("/", route.userToken.List)
+ r.Post("/", route.userToken.Create)
+ r.Put("/{id}", route.userToken.Update)
+ r.Delete("/{id}", route.userToken.Delete)
+ })
+
r.Route("/dashboard", func(r chi.Router) {
r.Get("/panel", route.dashboard.Panel)
r.Get("/home_apps", route.dashboard.HomeApps)
diff --git a/internal/service/cli.go b/internal/service/cli.go
index 4acc0b6c..b12b150c 100644
--- a/internal/service/cli.go
+++ b/internal/service/cli.go
@@ -138,9 +138,6 @@ func (s *CliService) Info(ctx context.Context, cmd *cli.Command) error {
}
user.Username = str.Random(8)
user.Password = hashed
- if user.Email == "" {
- user.Email = str.Random(8) + "@yourdomain.com"
- }
if err = s.db.Save(user).Error; err != nil {
return errors.New(s.t.Get("Failed to save user info: %v", err))
@@ -882,7 +879,7 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
return errors.New(s.t.Get("Initialization failed: %v", err))
}
- _, err = s.userRepo.Create("admin", value)
+ _, err = s.userRepo.Create("admin", value, str.Random(8)+"@yourdomain.com")
if err != nil {
return errors.New(s.t.Get("Initialization failed: %v", err))
}
diff --git a/internal/service/service.go b/internal/service/service.go
index 0c7ccba7..cda167aa 100644
--- a/internal/service/service.go
+++ b/internal/service/service.go
@@ -30,6 +30,7 @@ var ProviderSet = wire.NewSet(
NewSystemctlService,
NewTaskService,
NewUserService,
+ NewUserTokenService,
NewWebsiteService,
NewWsService,
)
diff --git a/internal/service/user.go b/internal/service/user.go
index 410b9ebb..1b8cee0c 100644
--- a/internal/service/user.go
+++ b/internal/service/user.go
@@ -197,7 +197,7 @@ func (s *UserService) Create(w http.ResponseWriter, r *http.Request) {
return
}
- user, err := s.userRepo.Create(req.Username, req.Password)
+ user, err := s.userRepo.Create(req.Username, req.Password, req.Email)
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
diff --git a/internal/service/user_token.go b/internal/service/user_token.go
new file mode 100644
index 00000000..52c27b1e
--- /dev/null
+++ b/internal/service/user_token.go
@@ -0,0 +1,119 @@
+package service
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/go-rat/chix"
+ "github.com/leonelquinteros/gotext"
+
+ "github.com/tnb-labs/panel/internal/biz"
+ "github.com/tnb-labs/panel/internal/http/request"
+)
+
+type UserTokenService struct {
+ t *gotext.Locale
+ userTokenRepo biz.UserTokenRepo
+}
+
+func NewUserTokenService(t *gotext.Locale, userToken biz.UserTokenRepo) *UserTokenService {
+ return &UserTokenService{
+ t: t,
+ userTokenRepo: userToken,
+ }
+}
+
+func (s *UserTokenService) List(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.UserTokenList](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ userTokens, total, err := s.userTokenRepo.List(req.UserID, req.Page, req.Limit)
+ if err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ Success(w, chix.M{
+ "total": total,
+ "items": userTokens,
+ })
+}
+
+func (s *UserTokenService) Create(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.UserTokenCreate](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ expiredAt := time.Unix(0, req.ExpiredAt*int64(time.Millisecond))
+ if expiredAt.Before(time.Now()) {
+ Error(w, http.StatusUnprocessableEntity, s.t.Get("expiration time must be greater than current time"))
+ return
+ }
+ if expiredAt.After(time.Now().AddDate(10, 0, 0)) {
+ Error(w, http.StatusUnprocessableEntity, s.t.Get("expiration time must be less than 10 years"))
+ return
+ }
+
+ userToken, err := s.userTokenRepo.Create(req.UserID, req.IPs, expiredAt)
+ if err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ // 手动组装响应,因为 Token 设置了 json:"-"
+ Success(w, chix.M{
+ "id": userToken.ID,
+ "user_id": userToken.UserID,
+ "token": userToken.Token,
+ "ips": userToken.IPs,
+ "expired_at": userToken.ExpiredAt,
+ "created_at": userToken.CreatedAt,
+ "updated_at": userToken.UpdatedAt,
+ })
+}
+
+func (s *UserTokenService) Update(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.UserTokenUpdate](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ expiredAt := time.Unix(0, req.ExpiredAt*int64(time.Millisecond))
+ if expiredAt.Before(time.Now()) {
+ Error(w, http.StatusUnprocessableEntity, s.t.Get("expiration time must be greater than current time"))
+ return
+ }
+ if expiredAt.After(time.Now().AddDate(10, 0, 0)) {
+ Error(w, http.StatusUnprocessableEntity, s.t.Get("expiration time must be less than 10 years"))
+ return
+ }
+
+ userToken, err := s.userTokenRepo.Update(req.ID, req.IPs, expiredAt)
+ if err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ Success(w, userToken)
+}
+
+func (s *UserTokenService) Delete(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.ID](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ if err = s.userTokenRepo.Delete(req.ID); err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ Success(w, nil)
+}
diff --git a/web/src/api/apps/podman/index.ts b/web/src/api/apps/podman/index.ts
index afa496fa..ecc57ebb 100644
--- a/web/src/api/apps/podman/index.ts
+++ b/web/src/api/apps/podman/index.ts
@@ -4,7 +4,8 @@ export default {
// 获取注册表配置
registryConfig: (): any => http.Get('/apps/podman/registry_config'),
// 保存注册表配置
- saveRegistryConfig: (config: string): any => http.Post('/apps/podman/registry_config', { config }),
+ saveRegistryConfig: (config: string): any =>
+ http.Post('/apps/podman/registry_config', { config }),
// 获取存储配置
storageConfig: (): any => http.Get('/apps/podman/storage_config'),
// 保存存储配置
diff --git a/web/src/api/panel/database/index.ts b/web/src/api/panel/database/index.ts
index ad9cda75..e747a664 100644
--- a/web/src/api/panel/database/index.ts
+++ b/web/src/api/panel/database/index.ts
@@ -27,7 +27,8 @@ export default {
serverRemark: (id: number, remark: string) =>
http.Put(`/database_server/${id}/remark`, { remark }),
// 获取数据库用户列表
- userList: (page: number, limit: number) => http.Get('/database_user', { params: { page, limit } }),
+ userList: (page: number, limit: number) =>
+ http.Get('/database_user', { params: { page, limit } }),
// 创建数据库用户
userCreate: (data: any) => http.Post('/database_user', data),
// 获取数据库用户
diff --git a/web/src/api/panel/user/index.ts b/web/src/api/panel/user/index.ts
index 4038d2d0..9291bd14 100644
--- a/web/src/api/panel/user/index.ts
+++ b/web/src/api/panel/user/index.ts
@@ -35,5 +35,17 @@ export default {
generateTwoFA: (id: number): any => http.Get(`/users/${id}/2fa`),
// 保存2FA密钥
updateTwoFA: (id: number, code: string, secret: string): any =>
- http.Post(`/users/${id}/2fa`, { code, secret })
+ http.Post(`/users/${id}/2fa`, { code, secret }),
+
+ // 获取用户Token列表
+ tokenList: (user_id: number, page: number, limit: number): any =>
+ http.Get(`/user_tokens`, { params: { user_id, page, limit } }),
+ // 创建用户Token
+ tokenCreate: (user_id: number, ips: string[], expired_at: number): any =>
+ http.Post('/user_tokens', { user_id, ips, expired_at }),
+ // 删除用户Token
+ tokenDelete: (id: number): any => http.Delete(`/user_tokens/${id}`),
+ // 更新用户Token
+ tokenUpdate: (id: number, ips: string[], expired_at: number): any =>
+ http.Put(`/user_tokens/${id}`, { ips, expired_at })
}
diff --git a/web/src/views/setting/CreateModal.vue b/web/src/views/setting/CreateModal.vue
new file mode 100644
index 00000000..1aa475fd
--- /dev/null
+++ b/web/src/views/setting/CreateModal.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $gettext('Submit') }}
+
+
+
+
diff --git a/web/src/views/setting/IndexView.vue b/web/src/views/setting/IndexView.vue
index 965be6f9..83e32b1a 100644
--- a/web/src/views/setting/IndexView.vue
+++ b/web/src/views/setting/IndexView.vue
@@ -8,6 +8,7 @@ import { useGettext } from 'vue3-gettext'
import setting from '@/api/panel/setting'
import TheIcon from '@/components/custom/TheIcon.vue'
import { useThemeStore } from '@/store'
+import CreateModal from '@/views/setting/CreateModal.vue'
import SettingBase from '@/views/setting/SettingBase.vue'
import SettingSafe from '@/views/setting/SettingSafe.vue'
import SettingUser from '@/views/setting/SettingUser.vue'
@@ -15,6 +16,7 @@ import SettingUser from '@/views/setting/SettingUser.vue'
const { $gettext } = useGettext()
const themeStore = useThemeStore()
const currentTab = ref('base')
+const createModal = ref(false)
const { data: model } = useRequest(setting.list, {
initialData: {
@@ -50,7 +52,9 @@ const handleSave = () => {
})
}
-const handleCreate = () => {}
+const handleCreate = () => {
+ createModal.value = true
+}
@@ -77,6 +81,7 @@ const handleCreate = () => {}
+
diff --git a/web/src/views/setting/SettingUser.vue b/web/src/views/setting/SettingUser.vue
index 7d346798..e4ffca25 100644
--- a/web/src/views/setting/SettingUser.vue
+++ b/web/src/views/setting/SettingUser.vue
@@ -2,6 +2,7 @@
import user from '@/api/panel/user'
import { formatDateTime, renderIcon } from '@/utils'
import PasswordModal from '@/views/setting/PasswordModal.vue'
+import TokenModal from '@/views/setting/TokenModal.vue'
import TwoFaModal from '@/views/setting/TwoFaModal.vue'
import { NButton, NDataTable, NInput, NPopconfirm, NSwitch } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
@@ -11,6 +12,7 @@ const { $gettext } = useGettext()
const currentID = ref(0)
const passwordModal = ref(false)
const twoFaModal = ref(false)
+const tokenModal = ref(false)
const columns: any = [
{
@@ -73,7 +75,7 @@ const columns: any = [
{
title: $gettext('Actions'),
key: 'actions',
- width: 260,
+ width: 380,
hideInExcel: true,
render(row: any) {
return [
@@ -82,6 +84,22 @@ const columns: any = [
{
size: 'small',
type: 'primary',
+ onClick: () => {
+ currentID.value = row.id
+ tokenModal.value = true
+ }
+ },
+ {
+ default: () => $gettext('Access Token'),
+ icon: renderIcon('material-symbols:edit-outline', { size: 14 })
+ }
+ ),
+ h(
+ NButton,
+ {
+ size: 'small',
+ type: 'primary',
+ style: 'margin-left: 15px;',
onClick: () => {
currentID.value = row.id
passwordModal.value = true
@@ -176,6 +194,7 @@ onMounted(() => {
+
diff --git a/web/src/views/setting/TokenModal.vue b/web/src/views/setting/TokenModal.vue
new file mode 100644
index 00000000..8d2ee9c4
--- /dev/null
+++ b/web/src/views/setting/TokenModal.vue
@@ -0,0 +1,307 @@
+
+
+
+
+
+
+
+ {{ $gettext('Create Access Token') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $gettext('Create') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $gettext('Update') }}
+
+
+
+
+
+
diff --git a/web/src/views/setting/TwoFaModal.vue b/web/src/views/setting/TwoFaModal.vue
index 1c9f612a..891b4888 100644
--- a/web/src/views/setting/TwoFaModal.vue
+++ b/web/src/views/setting/TwoFaModal.vue
@@ -63,7 +63,7 @@ watch(
}}
- {{ model.url }}
+ {{ model.url }}