commit c882f0452942dfe83a5fcd5595f6234636f64b42
Author: likun <906102152@qq.com>
Date: Fri Apr 18 17:17:23 2025 +0800
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..485dee6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
diff --git a/admin/apps/game/boot.go b/admin/apps/game/boot.go
new file mode 100644
index 0000000..1a92228
--- /dev/null
+++ b/admin/apps/game/boot.go
@@ -0,0 +1,26 @@
+package game
+
+import (
+ "admin/apps/game/server"
+ "admin/apps/game/service"
+ "admin/internal/global"
+ "admin/lib/node"
+)
+
+func initFun(app *node.Application) error {
+ svc := service.NewCmdServerSvc(global.GLOB_DB) // 初始化应用服务
+ srv := server.New(svc) // 初始化http服务
+ srv.Route(global.GLOB_API_ENGINE) // 初始化http服务路由
+ return nil
+}
+
+func New() *node.ApplicationDescInfo {
+ app := node.NewApplicationDescInfo("user", initFun)
+ return app
+}
+
+func must(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/admin/apps/game/domain/entity/project.go b/admin/apps/game/domain/entity/project.go
new file mode 100644
index 0000000..cef1ad7
--- /dev/null
+++ b/admin/apps/game/domain/entity/project.go
@@ -0,0 +1,94 @@
+package entity
+
+import (
+ "admin/apps/game/model"
+ "admin/apps/game/model/dto"
+ "reflect"
+)
+
+var ProjectDtoFieldsDescInfo = DefaultProject().GetDtoFieldsDescInfo()
+
+type Project struct {
+ Id int
+ po *model.Project
+}
+
+func DefaultProject() *Project {
+ return &Project{
+ po: &model.Project{},
+ }
+}
+
+func FromProjectPo(po *model.Project) *Project {
+ return &Project{
+ Id: po.ID,
+ po: po,
+ }
+}
+
+func FromProjectDto(dto *dto.CommonDtoValues) *Project {
+ et := DefaultProject()
+ po := et.po
+
+ //to := reflect.TypeOf(po)
+ vo := reflect.ValueOf(po)
+
+ for _, f := range dto.Values {
+ fo := vo.FieldByName(f.FieldName)
+ fo.Set(reflect.ValueOf(f.Value))
+ }
+
+ return et
+}
+
+func (et *Project) ToPo() *model.Project {
+ return et.po
+}
+
+func (et *Project) ToCommonDto() *dto.CommonDtoValues {
+ obj := &dto.CommonDtoValues{}
+
+ to := reflect.TypeOf(et.po).Elem()
+ vo := reflect.ValueOf(et.po).Elem()
+ for i := 0; i < vo.NumField(); i++ {
+ ft := to.Field(i)
+ fo := vo.Field(i)
+
+ f1 := &dto.CommonDtoValue{
+ FieldName: ft.Name,
+ Value: fo.Interface(),
+ }
+
+ obj.Values = append(obj.Values, f1)
+ }
+
+ return obj
+}
+
+func (et *Project) GetDtoFieldsDescInfo() []*dto.CommonDtoFieldDesc {
+
+ to := reflect.TypeOf(et.po).Elem()
+ vo := reflect.ValueOf(et.po).Elem()
+
+ obj := make([]*dto.CommonDtoFieldDesc, 0, to.NumField())
+
+ for i := 0; i < vo.NumField(); i++ {
+ ft := to.Field(i)
+ //fo := vo.Field(i)
+
+ f1 := &dto.CommonDtoFieldDesc{
+ Name: ft.Name,
+ Key: ft.Name,
+ Type: ft.Type.Name(),
+ HelpText: ft.Tag.Get("desc"),
+ Editable: true,
+ Require: true,
+ Choices: make([]*dto.CommonDtoFieldChoice, 0),
+ MultiChoice: false,
+ }
+
+ obj = append(obj, f1)
+ }
+
+ return obj
+}
diff --git a/admin/apps/game/domain/entity/server.go b/admin/apps/game/domain/entity/server.go
new file mode 100644
index 0000000..9a817a1
--- /dev/null
+++ b/admin/apps/game/domain/entity/server.go
@@ -0,0 +1,94 @@
+package entity
+
+import (
+ "admin/apps/game/model"
+ "admin/apps/game/model/dto"
+ "reflect"
+)
+
+var ServerDtoFieldsDescInfo = DefaultServer().GetDtoFieldsDescInfo()
+
+type Server struct {
+ Id int
+ po *model.Server
+}
+
+func DefaultServer() *Server {
+ return &Server{
+ po: &model.Server{},
+ }
+}
+
+func FromServerPo(po *model.Server) *Server {
+ return &Server{
+ Id: po.ID,
+ po: po,
+ }
+}
+
+func FromServerDto(dto *dto.CommonDtoValues) *Server {
+ et := DefaultServer()
+ po := et.po
+
+ //to := reflect.TypeOf(po)
+ vo := reflect.ValueOf(po)
+
+ for _, f := range dto.Values {
+ fo := vo.FieldByName(f.FieldName)
+ fo.Set(reflect.ValueOf(f.Value))
+ }
+
+ return et
+}
+
+func (et *Server) ToPo() *model.Server {
+ return et.po
+}
+
+func (et *Server) ToCommonDto() *dto.CommonDtoValues {
+ obj := &dto.CommonDtoValues{}
+
+ to := reflect.TypeOf(et.po).Elem()
+ vo := reflect.ValueOf(et.po).Elem()
+ for i := 0; i < vo.NumField(); i++ {
+ ft := to.Field(i)
+ fo := vo.Field(i)
+
+ f1 := &dto.CommonDtoValue{
+ FieldName: ft.Name,
+ Value: fo.Interface(),
+ }
+
+ obj.Values = append(obj.Values, f1)
+ }
+
+ return obj
+}
+
+func (et *Server) GetDtoFieldsDescInfo() []*dto.CommonDtoFieldDesc {
+
+ to := reflect.TypeOf(et.po).Elem()
+ vo := reflect.ValueOf(et.po).Elem()
+
+ obj := make([]*dto.CommonDtoFieldDesc, 0, to.NumField())
+
+ for i := 0; i < vo.NumField(); i++ {
+ ft := to.Field(i)
+ //fo := vo.Field(i)
+
+ f1 := &dto.CommonDtoFieldDesc{
+ Name: ft.Name,
+ Key: ft.Name,
+ Type: ft.Type.Name(),
+ HelpText: ft.Tag.Get("desc"),
+ Editable: true,
+ Require: true,
+ Choices: make([]*dto.CommonDtoFieldChoice, 0),
+ MultiChoice: false,
+ }
+
+ obj = append(obj, f1)
+ }
+
+ return obj
+}
diff --git a/admin/apps/game/domain/irestfull.go b/admin/apps/game/domain/irestfull.go
new file mode 100644
index 0000000..c35e957
--- /dev/null
+++ b/admin/apps/game/domain/irestfull.go
@@ -0,0 +1,31 @@
+package domain
+
+import (
+ "admin/apps/game/model/dto"
+ "admin/internal/errcode"
+)
+
+type IRestfulEntity interface {
+ ToCommonDto() *dto.CommonDtoValues
+}
+
+type IRestfulResourceSvc interface {
+ List(pageNo, pageLen int) ([]*dto.CommonDtoFieldDesc, []IRestfulEntity, error)
+ Post(obj *dto.CommonDtoValues) (IRestfulEntity, error)
+ Put(obj *dto.CommonDtoValues) (IRestfulEntity, error)
+ Delete(id int) error
+}
+
+var restfulResourceSvcMgr = make(map[string]IRestfulResourceSvc)
+
+func registerRestfulSvc(name string, svc IRestfulResourceSvc) {
+ restfulResourceSvcMgr[name] = svc
+}
+
+func FindRestfulResourceSvc(name string) (IRestfulResourceSvc, error) {
+ svc, find := restfulResourceSvcMgr[name]
+ if !find {
+ return nil, errcode.New(errcode.ServerError, "not found %v restful svc", name)
+ }
+ return svc, nil
+}
diff --git a/admin/apps/game/domain/project.go b/admin/apps/game/domain/project.go
new file mode 100644
index 0000000..0cc64bf
--- /dev/null
+++ b/admin/apps/game/domain/project.go
@@ -0,0 +1,48 @@
+package domain
+
+import (
+ "admin/apps/game/domain/entity"
+ "admin/apps/game/domain/repo"
+ "admin/apps/game/model/dto"
+ "gorm.io/gorm"
+)
+
+type ProjectSvc struct {
+ proRepo repo.IProjectRepo
+}
+
+func NewProjectSvc(db *gorm.DB) *ProjectSvc {
+ svc := &ProjectSvc{
+ proRepo: repo.NewProjectRepo(db),
+ }
+ registerRestfulSvc("project", svc)
+ return svc
+}
+
+func (svc *ProjectSvc) List(pageNo, pageLen int) ([]*dto.CommonDtoFieldDesc, []IRestfulEntity, error) {
+ entityList, err := svc.proRepo.List(pageNo, pageLen)
+ if err != nil {
+ return nil, nil, err
+ }
+ iList := make([]IRestfulEntity, 0, len(entityList))
+ for _, v := range entityList {
+ iList = append(iList, v)
+ }
+ return entity.ProjectDtoFieldsDescInfo, iList, nil
+}
+
+func (svc *ProjectSvc) Post(obj *dto.CommonDtoValues) (IRestfulEntity, error) {
+ et := entity.FromProjectDto(obj)
+ err := svc.proRepo.Create(et)
+ return et, err
+}
+
+func (svc *ProjectSvc) Put(obj *dto.CommonDtoValues) (IRestfulEntity, error) {
+ et := entity.FromProjectDto(obj)
+ err := svc.proRepo.Edit(et)
+ return et, err
+}
+
+func (svc *ProjectSvc) Delete(id int) error {
+ return svc.proRepo.Delete(id)
+}
diff --git a/admin/apps/game/domain/repo/project.go b/admin/apps/game/domain/repo/project.go
new file mode 100644
index 0000000..361a20c
--- /dev/null
+++ b/admin/apps/game/domain/repo/project.go
@@ -0,0 +1,77 @@
+package repo
+
+import (
+ "admin/apps/game/domain/entity"
+ "admin/apps/game/model"
+ "admin/internal/errcode"
+ "gorm.io/gorm"
+)
+
+type IProjectRepo interface {
+ List(pageNo, pageLen int) ([]*entity.Project, error)
+ Create(et *entity.Project) error
+ Edit(et *entity.Project) error
+ Delete(id int) error
+}
+
+func NewProjectRepo(db *gorm.DB) IProjectRepo {
+ return newProjectRepoImpl(db)
+}
+
+type projectRepoImpl struct {
+ db *gorm.DB
+}
+
+func newProjectRepoImpl(db *gorm.DB) *projectRepoImpl {
+ return &projectRepoImpl{db: db}
+}
+
+func (repo *projectRepoImpl) List(pageNo, pageLen int) ([]*entity.Project, error) {
+ list := make([]*model.Project, 0)
+ err := repo.db.Find(&list).Error
+ if err != nil {
+ return nil, errcode.New(errcode.DBError, "find project error:%v", err)
+ }
+
+ // debug
+ list = append(list, &model.Project{
+ ID: 123,
+ Name: "神魔大陆",
+ Desc: "神魔大陆服务器",
+ ApiAddr: "http://192.168.1.1:8081",
+ })
+
+ entityList := make([]*entity.Project, 0, len(list))
+ for _, Project := range list {
+ entityList = append(entityList, entity.FromProjectPo(Project))
+ }
+
+ return entityList, nil
+}
+
+func (repo *projectRepoImpl) Create(et *entity.Project) error {
+ po := et.ToPo()
+ err := repo.db.Create(po).Error
+ if err != nil {
+ return errcode.New(errcode.DBError, "create obj:%+v error:%v", et, err)
+ }
+ et.Id = po.ID
+ return nil
+}
+
+func (repo *projectRepoImpl) Edit(et *entity.Project) error {
+ po := et.ToPo()
+ err := repo.db.Where("id=?", et.Id).Updates(po).Error
+ if err != nil {
+ return errcode.New(errcode.DBError, "edit obj:%+v error:%v", et, err)
+ }
+ return nil
+}
+
+func (repo *projectRepoImpl) Delete(id int) error {
+ err := repo.db.Where("id=?", id).Unscoped().Delete(&model.Project{}).Error
+ if err != nil {
+ return errcode.New(errcode.DBError, "delete obj:%+v error:%v", id, err)
+ }
+ return nil
+}
diff --git a/admin/apps/game/domain/repo/server.go b/admin/apps/game/domain/repo/server.go
new file mode 100644
index 0000000..38327e7
--- /dev/null
+++ b/admin/apps/game/domain/repo/server.go
@@ -0,0 +1,69 @@
+package repo
+
+import (
+ "admin/apps/game/domain/entity"
+ "admin/apps/game/model"
+ "admin/internal/errcode"
+ "gorm.io/gorm"
+)
+
+type IServerRepo interface {
+ List(pageNo, pageLen int) ([]*entity.Server, error)
+ Create(et *entity.Server) error
+ Edit(et *entity.Server) error
+ Delete(id int) error
+}
+
+func NewServerRepo(db *gorm.DB) IServerRepo {
+ return newServerRepoImpl(db)
+}
+
+type ServerRepoImpl struct {
+ db *gorm.DB
+}
+
+func newServerRepoImpl(db *gorm.DB) *ServerRepoImpl {
+ return &ServerRepoImpl{db: db}
+}
+
+func (repo *ServerRepoImpl) List(pageNo, pageLen int) ([]*entity.Server, error) {
+ list := make([]*model.Server, 0)
+ err := repo.db.Find(&list).Error
+ if err != nil {
+ return nil, errcode.New(errcode.DBError, "find Server error:%v", err)
+ }
+
+ entityList := make([]*entity.Server, 0, len(list))
+ for _, Server := range list {
+ entityList = append(entityList, entity.FromServerPo(Server))
+ }
+
+ return entityList, nil
+}
+
+func (repo *ServerRepoImpl) Create(et *entity.Server) error {
+ po := et.ToPo()
+ err := repo.db.Create(po).Error
+ if err != nil {
+ return errcode.New(errcode.DBError, "create obj:%+v error:%v", et, err)
+ }
+ et.Id = po.ID
+ return nil
+}
+
+func (repo *ServerRepoImpl) Edit(et *entity.Server) error {
+ po := et.ToPo()
+ err := repo.db.Where("id=?", et.Id).Updates(po).Error
+ if err != nil {
+ return errcode.New(errcode.DBError, "edit obj:%+v error:%v", et, err)
+ }
+ return nil
+}
+
+func (repo *ServerRepoImpl) Delete(id int) error {
+ err := repo.db.Where("id=?", id).Unscoped().Delete(&model.Server{}).Error
+ if err != nil {
+ return errcode.New(errcode.DBError, "delete obj:%+v error:%v", id, err)
+ }
+ return nil
+}
diff --git a/admin/apps/game/domain/server.go b/admin/apps/game/domain/server.go
new file mode 100644
index 0000000..398c24b
--- /dev/null
+++ b/admin/apps/game/domain/server.go
@@ -0,0 +1,48 @@
+package domain
+
+import (
+ "admin/apps/game/domain/entity"
+ "admin/apps/game/domain/repo"
+ "admin/apps/game/model/dto"
+ "gorm.io/gorm"
+)
+
+type ServerSvc struct {
+ serverRepo repo.IServerRepo
+}
+
+func NewServerSvc(db *gorm.DB) *ServerSvc {
+ svc := &ServerSvc{
+ serverRepo: repo.NewServerRepo(db),
+ }
+ registerRestfulSvc("server", svc)
+ return svc
+}
+
+func (svc *ServerSvc) List(pageNo, pageLen int) ([]*dto.CommonDtoFieldDesc, []IRestfulEntity, error) {
+ entityList, err := svc.serverRepo.List(pageNo, pageLen)
+ if err != nil {
+ return nil, nil, err
+ }
+ iList := make([]IRestfulEntity, 0, len(entityList))
+ for _, v := range entityList {
+ iList = append(iList, v)
+ }
+ return entity.ServerDtoFieldsDescInfo, iList, nil
+}
+
+func (svc *ServerSvc) Post(obj *dto.CommonDtoValues) (IRestfulEntity, error) {
+ et := entity.FromServerDto(obj)
+ err := svc.serverRepo.Create(et)
+ return et, err
+}
+
+func (svc *ServerSvc) Put(obj *dto.CommonDtoValues) (IRestfulEntity, error) {
+ et := entity.FromServerDto(obj)
+ err := svc.serverRepo.Edit(et)
+ return et, err
+}
+
+func (svc *ServerSvc) Delete(id int) error {
+ return svc.serverRepo.Delete(id)
+}
diff --git a/admin/apps/game/model/dto/common.go b/admin/apps/game/model/dto/common.go
new file mode 100644
index 0000000..5acad08
--- /dev/null
+++ b/admin/apps/game/model/dto/common.go
@@ -0,0 +1,35 @@
+package dto
+
+type CommonDtoFieldChoice struct {
+ Desc string `json:"desc"`
+ Value any `json:"value"`
+ // 描述选项的类型,例如添加物品时,可以添加道具、翅膀、宠物等,他们可能不一定都设计为道具
+ Type int `json:"type"`
+}
+
+type CommonDtoFieldDesc struct {
+ Name string `json:"name"`
+ Key string `json:"key"`
+ // 字段类型,基础类型支持int float string bool []<基础类行>,
+ // 支持自定义类型和自定义类型的数组
+ Type string `json:"type"`
+ HelpText string `json:"help_text"`
+ Editable bool `json:"editable"` // 是否可编辑,例如id就不可编辑,新增时也不需要填写
+ Require bool `json:"require"` // 是否必填,不能为空
+ Choices []*CommonDtoFieldChoice `json:"choices"` // 可选项,用于字段做下拉框
+ MultiChoice bool `json:"multi_choice"` // 是否多选
+}
+
+type CommonDtoValue struct {
+ FieldName string `json:"field_name"`
+ Value any `json:"value"`
+}
+
+type CommonDtoValues struct {
+ Values []*CommonDtoValue `json:"values"`
+}
+
+type CommonDtoList struct {
+ FieldsDesc []*CommonDtoFieldDesc `json:"fields_desc"` // 数据字段描述信息
+ Rows []*CommonDtoValues `json:"rows"` // 数据行
+}
diff --git a/admin/apps/game/model/dto/request.go b/admin/apps/game/model/dto/request.go
new file mode 100644
index 0000000..9bf54ab
--- /dev/null
+++ b/admin/apps/game/model/dto/request.go
@@ -0,0 +1,18 @@
+package dto
+
+type CommonListReq struct {
+ PageNo int `json:"page_no"`
+ PageLen int `json:"page_len"`
+}
+
+type CommonPostReq struct {
+ Dto *CommonDtoValues `json:"dto"`
+}
+
+type CommonPutReq struct {
+ Dto *CommonDtoValues `json:"dto"`
+}
+
+type CommonDeleteReq struct {
+ Id int `json:"id"`
+}
diff --git a/admin/apps/game/model/dto/response.go b/admin/apps/game/model/dto/response.go
new file mode 100644
index 0000000..95538ee
--- /dev/null
+++ b/admin/apps/game/model/dto/response.go
@@ -0,0 +1,3 @@
+package dto
+
+type CommonListRsp = CommonDtoList
diff --git a/admin/apps/game/model/project.go b/admin/apps/game/model/project.go
new file mode 100644
index 0000000..a9644e5
--- /dev/null
+++ b/admin/apps/game/model/project.go
@@ -0,0 +1,28 @@
+package model
+
+import (
+ "admin/internal/db"
+ "gorm.io/gorm"
+ "time"
+)
+
+func init() {
+ db.RegisterTableModels(Project{})
+}
+
+// Project 游戏项目,例如神谕、神魔大陆
+type Project struct {
+ ID int `gorm:"primarykey"`
+ Name string `gorm:"primarykey"`
+ Desc string
+ // command_list接口服务器地址,为空代表由由项目下各个逻辑服提供command_list.
+ // 取决于每个项目改造难度:
+ // 不为空就代表项目要实现一个自己统一对外暴露的gm调用服务对内聚合、分发指令执行,本后台执行指令只调用一次;
+ // 为空就代表command_list实现在各个逻辑服,由本后台系统在执行指令时聚合、分发指令
+ // 调用各个逻辑服执行,本后台执行指令需要根据逻辑服数量调用;
+ ApiAddr string //
+
+ CreatedAt time.Time
+ UpdatedAt time.Time
+ DeletedAt gorm.DeletedAt `gorm:"index"`
+}
diff --git a/admin/apps/game/model/server.go b/admin/apps/game/model/server.go
new file mode 100644
index 0000000..fe876c7
--- /dev/null
+++ b/admin/apps/game/model/server.go
@@ -0,0 +1,28 @@
+package model
+
+import (
+ "admin/internal/db"
+ "gorm.io/gorm"
+ "time"
+)
+
+func init() {
+ db.RegisterTableModels(Server{})
+}
+
+// Server 逻辑服
+type Server struct {
+ ID int `gorm:"primarykey"`
+ ServerConfID string `gorm:"primarykey"`
+ Desc string
+ // command_list接口服务器地址,为空代表由由项目统一提供command_list.
+ // 取决于每个项目改造难度:
+ // 为空就代表项目要实现一个自己统一对外暴露的gm调用服务对内聚合、分发指令执行,本后台执行指令只调用一次;
+ // 不为空就代表command_list实现在各个逻辑服,由本后台系统在执行指令时聚合、分发指令
+ // 调用各个逻辑服执行,本后台执行指令需要根据逻辑服数量调用;
+ ApiAddr string
+
+ CreatedAt time.Time
+ UpdatedAt time.Time
+ DeletedAt gorm.DeletedAt `gorm:"index"`
+}
diff --git a/admin/apps/game/server/ctl.go b/admin/apps/game/server/ctl.go
new file mode 100644
index 0000000..9c799bd
--- /dev/null
+++ b/admin/apps/game/server/ctl.go
@@ -0,0 +1,11 @@
+package server
+
+import "admin/apps/game/service"
+
+type controller struct {
+ svc *service.Service
+}
+
+func newController(svc *service.Service) *controller {
+ return &controller{svc: svc}
+}
diff --git a/admin/apps/game/server/ctl_common_rest.go b/admin/apps/game/server/ctl_common_rest.go
new file mode 100644
index 0000000..24b255e
--- /dev/null
+++ b/admin/apps/game/server/ctl_common_rest.go
@@ -0,0 +1,42 @@
+package server
+
+import (
+ "admin/apps/game/model/dto"
+ "admin/internal/context"
+)
+
+func (ctl *controller) CommonList(ctx *context.WebContext, restfulResourceName string, params *dto.CommonListReq) {
+ list, err := ctl.svc.CommonList(ctx, restfulResourceName, params)
+ if err != nil {
+ ctx.Fail(err)
+ return
+ }
+ ctx.Ok(list)
+}
+
+func (ctl *controller) CommonPost(ctx *context.WebContext, restfulResourceName string, params *dto.CommonDtoValues) {
+ newObj, err := ctl.svc.CommonPost(ctx, restfulResourceName, params)
+ if err != nil {
+ ctx.Fail(err)
+ return
+ }
+ ctx.Ok(newObj)
+}
+
+func (ctl *controller) CommonPut(ctx *context.WebContext, restfulResourceName string, params *dto.CommonDtoValues) {
+ newObj, err := ctl.svc.CommonPut(ctx, restfulResourceName, params)
+ if err != nil {
+ ctx.Fail(err)
+ return
+ }
+ ctx.Ok(newObj)
+}
+
+func (ctl *controller) CommonDelete(ctx *context.WebContext, restfulResourceName string, id int) {
+ err := ctl.svc.CommonDelete(ctx, restfulResourceName, id)
+ if err != nil {
+ ctx.Fail(err)
+ return
+ }
+ ctx.Ok(nil)
+}
diff --git a/admin/apps/game/server/route.go b/admin/apps/game/server/route.go
new file mode 100644
index 0000000..46f92ee
--- /dev/null
+++ b/admin/apps/game/server/route.go
@@ -0,0 +1,57 @@
+package server
+
+import (
+ "admin/apps/game/model/dto"
+ "admin/internal/context"
+ "admin/lib/web"
+)
+
+func (srv *Server) Route(engine *web.Engine) {
+ apiGroup := engine.Group("/api")
+ srv.proRoute(apiGroup)
+ srv.serverRoute(apiGroup)
+}
+
+func (srv *Server) proRoute(engine *web.Group) {
+ resourceName := "project"
+ proGroup := engine.Group("/" + resourceName)
+
+ proGroup.Get("", "获取项目列表", web.AccessMode_Read, dto.CommonListReq{}, commonHandlerList(srv.ctl, resourceName))
+ proGroup.Post("", "新增项目", web.AccessMode_Read, dto.CommonPostReq{}, commonHandlerList(srv.ctl, resourceName))
+ proGroup.Put("", "修改项目", web.AccessMode_Read, dto.CommonPutReq{}, commonHandlerList(srv.ctl, resourceName))
+ proGroup.Delete("", "删除项目", web.AccessMode_Read, dto.CommonDeleteReq{}, commonHandlerList(srv.ctl, resourceName))
+}
+
+func (srv *Server) serverRoute(engine *web.Group) {
+ resourceName := "server"
+ proGroup := engine.Group("/" + resourceName)
+
+ proGroup.Get("", "获取服务器列表", web.AccessMode_Read, dto.CommonListReq{}, commonHandlerList(srv.ctl, resourceName))
+ proGroup.Post("", "新增服务器", web.AccessMode_Read, dto.CommonPostReq{}, commonHandlerList(srv.ctl, resourceName))
+ proGroup.Put("", "修改服务器", web.AccessMode_Read, dto.CommonPutReq{}, commonHandlerList(srv.ctl, resourceName))
+ proGroup.Delete("", "删除服务器", web.AccessMode_Read, dto.CommonDeleteReq{}, commonHandlerList(srv.ctl, resourceName))
+}
+
+func commonHandlerList(ctl *controller, resourceName string) func(ctx *context.WebContext, params *dto.CommonListReq) {
+ return func(ctx *context.WebContext, params *dto.CommonListReq) {
+ ctl.CommonList(ctx, resourceName, params)
+ }
+}
+
+func commonHandlerPost(ctl *controller, resourceName string) func(ctx *context.WebContext, params *dto.CommonPostReq) {
+ return func(ctx *context.WebContext, params *dto.CommonPostReq) {
+ ctl.CommonPost(ctx, resourceName, params.Dto)
+ }
+}
+
+func commonHandlerPut(ctl *controller, resourceName string) func(ctx *context.WebContext, params *dto.CommonPutReq) {
+ return func(ctx *context.WebContext, params *dto.CommonPutReq) {
+ ctl.CommonPut(ctx, resourceName, params.Dto)
+ }
+}
+
+func commonHandlerDelete(ctl *controller, resourceName string) func(ctx *context.WebContext, params *dto.CommonDeleteReq) {
+ return func(ctx *context.WebContext, params *dto.CommonDeleteReq) {
+ ctl.CommonDelete(ctx, resourceName, params.Id)
+ }
+}
diff --git a/admin/apps/game/server/server.go b/admin/apps/game/server/server.go
new file mode 100644
index 0000000..cb99acc
--- /dev/null
+++ b/admin/apps/game/server/server.go
@@ -0,0 +1,15 @@
+package server
+
+import "admin/apps/game/service"
+
+type Server struct {
+ svc *service.Service
+ ctl *controller
+}
+
+func New(svc *service.Service) *Server {
+ return &Server{
+ svc: svc,
+ ctl: newController(svc),
+ }
+}
diff --git a/admin/apps/game/service/service.go b/admin/apps/game/service/service.go
new file mode 100644
index 0000000..acc1cc0
--- /dev/null
+++ b/admin/apps/game/service/service.go
@@ -0,0 +1,68 @@
+package service
+
+import (
+ "admin/apps/game/domain"
+ "admin/apps/game/model/dto"
+ "context"
+ "gorm.io/gorm"
+)
+
+type Service struct {
+ db *gorm.DB
+ projectSvc *domain.ProjectSvc
+ serverSvc *domain.ServerSvc
+}
+
+func NewCmdServerSvc(db *gorm.DB) *Service {
+ return &Service{
+ db: db,
+ projectSvc: domain.NewProjectSvc(db),
+ serverSvc: domain.NewServerSvc(db),
+ }
+}
+
+func (svc *Service) CommonList(ctx context.Context, resourceName string, params *dto.CommonListReq) (*dto.CommonDtoList, error) {
+ restfulDomainSvc, err := domain.FindRestfulResourceSvc(resourceName)
+ if err != nil {
+ return nil, err
+ }
+ dtoFieldsDescInfo, list, err := restfulDomainSvc.List(params.PageNo, params.PageLen)
+ if err != nil {
+ return nil, err
+ }
+ retList := make([]*dto.CommonDtoValues, 0, len(list))
+ for _, v := range list {
+ retList = append(retList, v.ToCommonDto())
+ }
+ return &dto.CommonDtoList{FieldsDesc: dtoFieldsDescInfo, Rows: retList}, nil
+}
+
+func (svc *Service) CommonPost(ctx context.Context, resourceName string, params *dto.CommonDtoValues) (*dto.CommonDtoValues, error) {
+ restfulDomainSvc, err := domain.FindRestfulResourceSvc(resourceName)
+ if err != nil {
+ return nil, err
+ }
+ et, err := restfulDomainSvc.Post(params)
+ if err != nil {
+ return nil, err
+ }
+ return et.ToCommonDto(), nil
+}
+func (svc *Service) CommonPut(ctx context.Context, resourceName string, params *dto.CommonDtoValues) (*dto.CommonDtoValues, error) {
+ restfulDomainSvc, err := domain.FindRestfulResourceSvc(resourceName)
+ if err != nil {
+ return nil, err
+ }
+ et, err := restfulDomainSvc.Put(params)
+ if err != nil {
+ return nil, err
+ }
+ return et.ToCommonDto(), nil
+}
+func (svc *Service) CommonDelete(ctx context.Context, resourceName string, id int) error {
+ restfulDomainSvc, err := domain.FindRestfulResourceSvc(resourceName)
+ if err != nil {
+ return err
+ }
+ return restfulDomainSvc.Delete(id)
+}
diff --git a/admin/apps/user/boot.go b/admin/apps/user/boot.go
new file mode 100644
index 0000000..53e3e8d
--- /dev/null
+++ b/admin/apps/user/boot.go
@@ -0,0 +1,18 @@
+package user
+
+import "admin/lib/node"
+
+func initFun(app *node.Application) error {
+ return nil
+}
+
+func New() *node.ApplicationDescInfo {
+ app := node.NewApplicationDescInfo("user", initFun)
+ return app
+}
+
+func must(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/admin/cmd/all_in_one/main.go b/admin/cmd/all_in_one/main.go
new file mode 100644
index 0000000..c073490
--- /dev/null
+++ b/admin/cmd/all_in_one/main.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "admin/apps/game"
+ "admin/internal/mynode"
+ "admin/lib/node"
+)
+
+var appList []*node.ApplicationDescInfo
+
+func init() {
+ appList = []*node.ApplicationDescInfo{
+ game.New(),
+ }
+}
+
+func main() {
+ nd := mynode.New()
+ for _, app := range appList {
+ nd.WithApp(app)
+ }
+ err := nd.Run()
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/admin/go.mod b/admin/go.mod
new file mode 100644
index 0000000..b7c9beb
--- /dev/null
+++ b/admin/go.mod
@@ -0,0 +1,53 @@
+module admin
+
+go 1.24.2
+
+require (
+ github.com/gin-contrib/pprof v1.5.3
+ github.com/gin-gonic/gin v1.10.0
+ github.com/go-sql-driver/mysql v1.7.0
+ github.com/prometheus/client_golang v1.22.0
+ github.com/rs/zerolog v1.34.0
+ gopkg.in/yaml.v3 v3.0.1
+ gorm.io/driver/mysql v1.5.7
+ gorm.io/driver/sqlite v1.5.7
+ gorm.io/gorm v1.25.12
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/bytedance/sonic v1.13.2 // indirect
+ github.com/bytedance/sonic/loader v0.2.4 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/cloudwego/base64x v0.1.5 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.8 // indirect
+ github.com/gin-contrib/sse v1.0.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.26.0 // indirect
+ github.com/goccy/go-json v0.10.5 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.10 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-sqlite3 v1.14.28 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.3 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
+ github.com/prometheus/common v0.62.0 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.12 // indirect
+ golang.org/x/arch v0.16.0 // indirect
+ golang.org/x/crypto v0.37.0 // indirect
+ golang.org/x/net v0.38.0 // indirect
+ golang.org/x/sys v0.32.0 // indirect
+ golang.org/x/text v0.24.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+)
diff --git a/admin/go.sum b/admin/go.sum
new file mode 100644
index 0000000..ee894ae
--- /dev/null
+++ b/admin/go.sum
@@ -0,0 +1,139 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
+github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
+github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
+github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
+github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
+github.com/gin-contrib/pprof v1.5.3 h1:Bj5SxJ3kQDVez/s/+f9+meedJIqLS+xlkIVDe/lcvgM=
+github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+YEYpfQQ6xY=
+github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
+github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
+github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
+github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
+github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
+github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
+github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
+github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
+github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
+golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
+golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
+golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
+gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
+gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
+gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
+gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
+gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
diff --git a/admin/internal/config/flags.go b/admin/internal/config/flags.go
new file mode 100644
index 0000000..a8b5796
--- /dev/null
+++ b/admin/internal/config/flags.go
@@ -0,0 +1,10 @@
+package config
+
+type CommonBootFlags struct {
+ ApiPort string `env:"api_port" default:"8080" desc:"api端口,客户端请求的地址端口"`
+ DBType string `env:"db_type" default:"sqlite3" desc:"数据库类型,默认sqlite,可选:sqlite|mysql|pg"`
+ DBAddr string `env:"db_addr" default:"localhost" desc:"数据库地址"`
+ DBName string `env:"db_name" default:"uniugm" desc:"数据库名字"`
+ DBUser string `env:"db_user" default:"root" desc:"数据库用户名"`
+ DBPass string `env:"db_pass" default:"" desc:"数据库密码"`
+}
diff --git a/admin/internal/config/yaml.go b/admin/internal/config/yaml.go
new file mode 100644
index 0000000..d912156
--- /dev/null
+++ b/admin/internal/config/yaml.go
@@ -0,0 +1 @@
+package config
diff --git a/admin/internal/context/ctx_web.go b/admin/internal/context/ctx_web.go
new file mode 100644
index 0000000..8ae820b
--- /dev/null
+++ b/admin/internal/context/ctx_web.go
@@ -0,0 +1,43 @@
+package context
+
+import (
+ "admin/internal/errcode"
+ "admin/lib/web"
+ "context"
+)
+
+type WebContext struct {
+ context.Context
+ rawCtx web.RawContext
+}
+
+func NewWebContext() web.Context {
+ return &WebContext{}
+}
+
+func (ctx *WebContext) Ok(data any) {
+ ctx.rawCtx.Json(200, map[string]any{
+ "code": 200,
+ "msg": "",
+ "data": data,
+ })
+}
+
+func (ctx *WebContext) Fail(err error) {
+ code, stack, msg := errcode.ParseError(err)
+ ctx.rawCtx.Json(200, map[string]any{
+ "code": code,
+ "stack": stack,
+ "msg": msg,
+ "data": "",
+ })
+}
+
+func (ctx *WebContext) SetRawContext(rawCtx web.RawContext) {
+ ctx.Context = context.Background()
+ ctx.rawCtx = rawCtx
+}
+
+func (ctx *WebContext) GetRawContext() web.RawContext {
+ return ctx.rawCtx
+}
diff --git a/admin/internal/db/db.go b/admin/internal/db/db.go
new file mode 100644
index 0000000..4426b9b
--- /dev/null
+++ b/admin/internal/db/db.go
@@ -0,0 +1,142 @@
+package db
+
+import (
+ "admin/internal/errcode"
+ "admin/internal/global"
+ "admin/lib/xlog"
+ "fmt"
+ mysqlDriver "github.com/go-sql-driver/mysql"
+ "gorm.io/driver/mysql"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+ "sync"
+ "time"
+)
+
+var (
+ globalTables []any
+ locker sync.Mutex
+)
+
+func RegisterTableModels(models ...any) {
+ locker.Lock()
+ defer locker.Unlock()
+ globalTables = append(globalTables, models...)
+}
+
+func NewDB(dbType, dbAddr, dbName, dbUser, dbPass string) (db *gorm.DB, err error) {
+ switch dbType {
+ case "sqlite":
+ db, err = gorm.Open(sqlite.Open(dbName+".db"), &gorm.Config{})
+ if err != nil {
+ return nil, err
+ }
+ case "mysql":
+ dsn := fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPass, dbAddr, dbName)
+ dsnWithoutDB := fmt.Sprintf("%v:%v@tcp(%v)/?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPass, dbAddr)
+ db, err = createDBAndGuaranteeMigrate(dsnWithoutDB, dsn, globalTables)
+ }
+ global.GLOB_DB = db
+ return db, nil
+}
+
+func createDBAndGuaranteeMigrate(dsnWithoutDb, dsn string, tables []any) (*gorm.DB, error) {
+ mysqlDriverConf, err := mysqlDriver.ParseDSN(dsn)
+ if err != nil {
+ return nil, fmt.Errorf("parse dsn:%v error:%v", dsn, err)
+ }
+ dbName := mysqlDriverConf.DBName
+ _, err = tryCreateDB(dbName, dsnWithoutDb)
+ if err != nil {
+ xlog.Fatalf(err)
+ return nil, err
+ }
+
+ driverConf := mysql.Config{
+ DSN: dsn,
+ DontSupportRenameColumn: true,
+ //SkipInitializeWithVersion: false, // 根据数据库版本自动配置
+ }
+ dialector := mysql.New(driverConf)
+
+ //slowLogger := logger.New(
+ // syslog.New(xlog.GetGlobalWriter(), "\n", syslog.LstdFlags),
+ // logger.Config{
+ // // 设定慢查询时间阈值为 默认值:200 * time.Millisecond
+ // SlowThreshold: 200 * time.Millisecond,
+ // // 设置日志级别
+ // LogLevel: logger.Warn,
+ // Colorful: true,
+ // },
+ //)
+
+ db, err := gorm.Open(dialector, &gorm.Config{
+ Logger: &gormLogger{},
+ PrepareStmt: false, // 关闭缓存sql语句功能,因为后续use db会报错,这个缓存会无限存储可能导致内存泄露
+ //SkipDefaultTransaction: true, // 跳过默认事务
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to connect to mysql:%v", err)
+ }
+
+ sqlDB, _ := db.DB()
+ sqlDB.SetMaxIdleConns(10)
+ sqlDB.SetMaxOpenConns(50)
+ sqlDB.SetConnMaxIdleTime(time.Minute * 5)
+ sqlDB.SetConnMaxLifetime(time.Minute * 10)
+
+ if len(tables) > 0 {
+ err = autoMigrate(db, tables...)
+ if err != nil {
+ xlog.Fatalf(err)
+ return nil, fmt.Errorf("automigrate error:%v", err)
+ }
+ }
+
+ //addMetricsCollection(db, strconv.Itoa(int(serverId)), dbName)
+ return db, nil
+}
+
+func tryCreateDB(dbName, dsn string) (string, error) {
+ driverConf := mysql.Config{
+ DSN: dsn,
+ DontSupportRenameColumn: true,
+ //SkipInitializeWithVersion: false, // 根据数据库版本自动配置
+ }
+ dialector := mysql.New(driverConf)
+
+ db, err := gorm.Open(dialector, &gorm.Config{
+ PrepareStmt: false, // 关闭缓存sql语句功能,因为后续use db会报错,这个缓存会无限存储可能导致内存泄露
+ //SkipDefaultTransaction: true, // 跳过默认事务
+ })
+ if err != nil {
+ return "", fmt.Errorf("failed to connect to mysql:%v", err)
+ }
+
+ // 检查数据库是否存在
+ var count int
+ db.Raw("SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = ?", dbName).Scan(&count)
+ if count == 0 {
+ // 数据库不存在,创建它
+ sql := fmt.Sprintf(`create database if not exists %s default charset utf8mb4 collate utf8mb4_unicode_ci`,
+ dbName)
+ if e := db.Exec(sql).Error; e != nil {
+ return "", fmt.Errorf("failed to create database:%v", e)
+ }
+ }
+
+ sqlDb, _ := db.DB()
+ sqlDb.Close()
+
+ return dbName, nil
+}
+
+func autoMigrate(db *gorm.DB, tables ...interface{}) error {
+ // 这个函数是在InitConn之后调用的
+
+ // 初始化表
+ if err := db.AutoMigrate(tables...); err != nil {
+ return errcode.New(errcode.DBError, "failed to init tables", err)
+ }
+ return nil
+}
diff --git a/admin/internal/db/logger.go b/admin/internal/db/logger.go
new file mode 100644
index 0000000..5cc77c8
--- /dev/null
+++ b/admin/internal/db/logger.go
@@ -0,0 +1,99 @@
+package db
+
+import (
+ "admin/lib/xlog"
+ "context"
+ "fmt"
+ "gorm.io/gorm/logger"
+ "strings"
+ "time"
+)
+
+type gormLogger struct {
+}
+
+func (l *gormLogger) LogMode(logger.LogLevel) logger.Interface {
+ return l
+}
+func (l *gormLogger) Info(ctx context.Context, format string, args ...interface{}) {
+ xlog.Infof(fmt.Sprintf("[GORM LOGGER] "+format, args...))
+}
+func (l *gormLogger) Warn(ctx context.Context, format string, args ...interface{}) {
+ xlog.Warnf(fmt.Sprintf("[GORM LOGGER] "+format, args...))
+}
+func (l *gormLogger) Error(ctx context.Context, format string, args ...interface{}) {
+ xlog.Errorf(fmt.Sprintf("[GORM LOGGER] "+format, args...))
+}
+func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
+ sqlStr, aff := fc()
+ if strings.Contains(sqlStr, "SHOW STATUS") {
+ return
+ }
+ if err != nil {
+ xlog.Errorf("[GORM LOGGER] "+"sql:%v affected:%v error:%v", sqlStr, aff, err)
+ cmd, table := getSqlCmdAndTable(sqlStr)
+ if cmd == "" || table == "" {
+ return
+ }
+ //metricsMysqlErrorCount.LabelValues(cmd, table).Add(1)
+ } else {
+ dura := time.Now().Sub(begin)
+ if dura.Milliseconds() > 2000 {
+ xlog.Warnf("[GORM LOGGER] [SLOW] "+"sql:%v affected:%v use %vms", sqlStr, aff, dura.Milliseconds())
+ } else {
+ xlog.Tracef("[GORM LOGGER] "+"sql:%v affected:%v", sqlStr, aff)
+ }
+ }
+}
+
+func getSqlCmdAndTable(sql string) (string, string) {
+ cmd := ""
+ table := ""
+ idx := strings.IndexAny(sql, " ")
+ if idx <= 0 {
+ cmd = sql
+ } else {
+ cmd = sql[:idx]
+ }
+ switch cmd {
+ case "SELECT":
+ if idx := strings.Index(sql, "FROM"); idx >= 0 {
+ for i := idx + 5; i < len(sql); i++ {
+ if sql[i] == ' ' {
+ break
+ }
+ table += string(sql[i])
+ }
+ }
+ case "UPDATE":
+ if idx := strings.Index(sql, "UPDATE"); idx >= 0 {
+ for i := idx + 7; i < len(sql); i++ {
+ if sql[i] == ' ' {
+ break
+ }
+ table += string(sql[i])
+ }
+ }
+ case "INSERT":
+ if idx := strings.Index(sql, "INTO"); idx >= 0 {
+ for i := idx + 5; i < len(sql); i++ {
+ if sql[i] == ' ' || sql[i] == '(' {
+ break
+ }
+ table += string(sql[i])
+ }
+ }
+ case "DELETE":
+ if idx := strings.Index(sql, "FROM"); idx >= 0 {
+ for i := idx + 5; i < len(sql); i++ {
+ if sql[i] == ' ' {
+ break
+ }
+ table += string(sql[i])
+ }
+ }
+ default:
+ }
+
+ return cmd, table
+}
diff --git a/admin/internal/errcode/code.go b/admin/internal/errcode/code.go
new file mode 100644
index 0000000..2b8afd2
--- /dev/null
+++ b/admin/internal/errcode/code.go
@@ -0,0 +1,7 @@
+package errcode
+
+const (
+ Ok = 0
+ ServerError = 1 // 服务器错误
+ DBError = 2 // 数据库错误
+)
diff --git a/admin/internal/errcode/error.go b/admin/internal/errcode/error.go
new file mode 100644
index 0000000..7d5a3b0
--- /dev/null
+++ b/admin/internal/errcode/error.go
@@ -0,0 +1,53 @@
+package errcode
+
+import (
+ "fmt"
+ "runtime"
+ "strconv"
+ "strings"
+)
+
+type errorWithCode struct {
+ code int
+ stack string
+ msg string
+}
+
+func newErrorWithCode(code int, stack, msg string) *errorWithCode {
+ return &errorWithCode{
+ code: code,
+ stack: stack,
+ msg: msg,
+ }
+}
+
+func (e *errorWithCode) Error() string {
+ return fmt.Sprintf("[stack ==> %v] code:%v msg:%v", e.stack, e.code, e.msg)
+}
+
+func New(code int, format string, args ...interface{}) error {
+ return newError(code, 2, format, args...)
+}
+
+func ParseError(err error) (int, string, string) {
+ if specErr, ok := err.(*errorWithCode); ok {
+ return specErr.code, specErr.stack, specErr.msg
+ }
+ return ParseError(newError(ServerError, 3, err.Error()))
+}
+
+func newError(code int, callDeep int, format string, args ...interface{}) error {
+ _, caller, line, ok := runtime.Caller(callDeep)
+ if !ok {
+ panic(ok)
+ }
+ tokens := strings.Split(caller, "/")
+ if len(tokens) > 5 {
+ tokens = tokens[len(tokens)-5:]
+ }
+ fileInfo := strings.Join(tokens, "/")
+
+ err := newErrorWithCode(code, fileInfo+":"+strconv.Itoa(int(line)), fmt.Sprintf(format, args...))
+
+ return err
+}
diff --git a/admin/internal/global/var.go b/admin/internal/global/var.go
new file mode 100644
index 0000000..98f4790
--- /dev/null
+++ b/admin/internal/global/var.go
@@ -0,0 +1,13 @@
+package global
+
+import (
+ "admin/internal/config"
+ "admin/lib/web"
+ "gorm.io/gorm"
+)
+
+var (
+ GLOB_BOOT_FLAGS = &config.CommonBootFlags{} // 启动命令行参数
+ GLOB_DB *gorm.DB // 数据库
+ GLOB_API_ENGINE *web.Engine // 全局api服务器
+)
diff --git a/admin/internal/mynode/node.go b/admin/internal/mynode/node.go
new file mode 100644
index 0000000..f239c3e
--- /dev/null
+++ b/admin/internal/mynode/node.go
@@ -0,0 +1,38 @@
+package mynode
+
+import (
+ "admin/internal/context"
+ "admin/internal/db"
+ "admin/internal/global"
+ "admin/lib/node"
+ "admin/lib/web"
+ "admin/lib/xlog"
+)
+
+func New() *node.Node {
+ nd := node.NewNode()
+ nd.ApplyOptions(node.WithNodeExBootFlags(global.GLOB_BOOT_FLAGS))
+
+ nd.AddInitTask("初始化全局api监听服务", func() error {
+ global.GLOB_API_ENGINE = web.NewEngine("gin", context.NewWebContext)
+ return nil
+ })
+
+ nd.AddInitTask("初始化数据库", func() error {
+ flags := global.GLOB_BOOT_FLAGS
+ _, err := db.NewDB(flags.DBType, flags.DBAddr, flags.DBName, flags.DBUser, flags.DBPass)
+ return err
+ })
+
+ nd.AddPostTask("启动全局api监听服务", func() error {
+ go func() {
+ err := global.GLOB_API_ENGINE.Run(":" + global.GLOB_BOOT_FLAGS.ApiPort)
+ if err != nil {
+ xlog.Errorf("start api server on %v error: %v", global.GLOB_BOOT_FLAGS, err)
+ }
+ }()
+ return nil
+ })
+
+ return nd
+}
diff --git a/admin/lib/flags/flags.go b/admin/lib/flags/flags.go
new file mode 100644
index 0000000..be110c9
--- /dev/null
+++ b/admin/lib/flags/flags.go
@@ -0,0 +1,76 @@
+package flags
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "reflect"
+ "strconv"
+ "unsafe"
+)
+
+// ParseWithStructPointers 启动参数解析,如果启动参数没有指定,会去env里查找同名参数
+// flagStructPointers为结构体指针数组
+func ParseWithStructPointers(flagStructPointers ...interface{}) {
+
+ for _, st := range flagStructPointers {
+ flagParseStruct2Flags(st)
+ }
+
+ flag.Parse()
+}
+
+func flagParseStruct2Flags(st interface{}) {
+ if st == nil {
+ return
+ }
+
+ var stTo = reflect.TypeOf(st)
+ var stVo = reflect.ValueOf(st)
+ switch stTo.Kind() {
+ case reflect.Ptr:
+ stTo = stTo.Elem()
+ stVo = stVo.Elem()
+ // case reflect.Struct:
+ // break
+ default:
+ panic(fmt.Errorf("invalid flags parse struct(%+v), must be pointer or struct", st))
+ }
+
+ for i := 0; i < stTo.NumField(); i++ {
+ field := stTo.Field(i)
+
+ key, find := field.Tag.Lookup("env")
+ if !find {
+ continue
+ }
+
+ desc := field.Tag.Get("desc")
+
+ defaultValue, find := os.LookupEnv(key)
+ if !find {
+ defaultValue, find = field.Tag.Lookup("default")
+ if !find {
+ defaultValue = ""
+ }
+ }
+
+ var fieldValuePointer = unsafe.Pointer(stVo.Field(i).Addr().Pointer())
+ switch field.Type.Kind() {
+ case reflect.String:
+ flag.StringVar((*string)(fieldValuePointer), key, defaultValue, desc)
+ case reflect.Int:
+ defaultValue1, _ := strconv.Atoi(defaultValue)
+ flag.IntVar((*int)(fieldValuePointer), key, defaultValue1, desc)
+ case reflect.Int64:
+ defaultValue1, _ := strconv.ParseInt(defaultValue, 10, 64)
+ flag.Int64Var((*int64)(fieldValuePointer), key, defaultValue1, desc)
+ case reflect.Bool:
+ flag.BoolVar((*bool)(fieldValuePointer), key, defaultValue == "true", desc)
+ default:
+ panic(fmt.Errorf("parse flag kind invalid,must be string/int/int64/bool, not %+v", field.Type.Kind()))
+ }
+ }
+
+ return
+}
diff --git a/admin/lib/node/app.go b/admin/lib/node/app.go
new file mode 100644
index 0000000..33d0f54
--- /dev/null
+++ b/admin/lib/node/app.go
@@ -0,0 +1,216 @@
+package node
+
+import (
+ "admin/lib/xlog"
+ "fmt"
+ "github.com/gin-gonic/gin"
+)
+
+// Task 不会永久执行的任务,串行用于启动前初始化或者启动后初始化工作,返回error就停止application
+type Task func() error
+
+// Worker 永久执行的工作协程,一旦停止就停止application
+type Worker func() error
+
+// Job 不会永久执行的任务,且不关心执行结果,不关心执行顺序,例如内存预热等
+type Job func()
+
+type pair struct {
+ key any
+ value any
+}
+
+// Application 受scheduler调度的最小逻辑单元,有独立的启动参数、各种串行、并行任务
+type Application struct {
+ Name string
+ bootFlag interface{}
+ initializeTasks []pair // 启动服务前串行执行初始化任务的job
+ services []pair // rpc服务
+ servers []pair // web服务
+ postRunTasks []pair // 启动后串行执行的job
+ postRunWorkers []pair // 启动后后台永久执行的工作协程,一旦推出就停止application
+ parallelJobs []pair // 启动services、servers后并行执行的任务,不关心结果,例如内存数据的预热等
+ stopTasks []pair
+}
+
+// newApp
+func newApp(name string, options ...AppOption) *Application {
+ app := new(Application)
+ app.Name = name
+ app.applyOptions(options...)
+ return app
+}
+
+// WithInitializeTask app完成init之后run之前执行的任务,可以用来初始化某些业务或者检查配置等
+func (app *Application) WithInitializeTask(desc string, task Task) *Application {
+ if task == nil {
+ return app
+ }
+ app.initializeTasks = append(app.initializeTasks, pair{desc, task})
+ return app
+}
+
+// WithServer 添加web服务器
+func (app *Application) WithServer(desc string, addr string) *gin.Engine {
+ server := gin.Default()
+ app.servers = append(app.servers, pair{desc, pair{addr, server}})
+ return server
+}
+
+// WithService 添加rpc服务
+//func (app *Application) WithService(desc string, service *joyservice.ServicesManager) *Application {
+// if service == nil {
+// return app
+// }
+// app.services = append(app.services, pair{desc, service})
+// return app
+//}
+
+// WithPostTask app run之后执行的任务,一般做临时检查任务,可以用来服务启动后加载数据检查等
+func (app *Application) WithPostTask(desc string, task Task) *Application {
+ if task == nil {
+ return nil
+ }
+ app.postRunTasks = append(app.postRunTasks, pair{desc, task})
+ return app
+}
+
+// WithPostWorker 完成post task之后执行的后台任务,报错退出等app也会退出,一般做永久的关键后台逻辑
+func (app *Application) WithPostWorker(desc string, worker Worker) *Application {
+ if worker == nil {
+ return app
+ }
+ app.postRunWorkers = append(app.postRunWorkers, pair{desc, worker})
+ return app
+}
+
+// WithParallelJob 完成post task之后执行的并行后台任务,一般做永久的不关键后台逻辑,例如内存预热等
+func (app *Application) WithParallelJob(desc string, job Job) *Application {
+ if job == nil {
+ return app
+ }
+ app.parallelJobs = append(app.parallelJobs, pair{desc, job})
+ return app
+}
+
+// WithStopTask 注册停服逻辑(只能处理正常停服,异常例如panic、oom会监听不到)
+func (app *Application) WithStopTask(desc string, task Task) *Application {
+ if task == nil {
+ return app
+ }
+ app.stopTasks = append(app.stopTasks, pair{desc, task})
+ return app
+}
+
+func (app *Application) GetBootConfig() any {
+ return app.bootFlag
+}
+
+func (app *Application) applyOptions(options ...AppOption) *Application {
+ for _, option := range options {
+ option.Apply(app)
+ }
+ return app
+}
+
+func (app *Application) run() (err error) {
+ waitChan := make(chan error, 1)
+
+ // 启动前的初始化任务
+ for _, j := range app.initializeTasks {
+ curErr := j.value.(Task)()
+ if curErr != nil {
+ err = fmt.Errorf("run initialize task(%s) return error:%v", j.key, curErr)
+ return
+ }
+ }
+
+ // 启动rpc服务
+ //for _, pair := range app.services {
+ // go func(desc string, s *joyservice.ServicesManager) {
+ // jlog.Noticef("app %v service %v will listen on %v", app.Name, desc, s.Addr)
+ // curErr := s.Run()
+ // if curErr != nil {
+ // waitChan <- fmt.Errorf("service %s run on %v error:%v", desc, s.Addr, curErr)
+ // } else {
+ //
+ // }
+ // }(pair.desc, pair.item.(*joyservice.ServicesManager))
+ //}
+
+ //defer app.stopServices()
+
+ // 启动web服务
+ for _, server := range app.servers {
+ go func(desc string, info pair) {
+ addr := info.key.(string)
+ engine := info.value.(*gin.Engine)
+ xlog.Noticef("app %v server %v will listen on %v", app.Name, desc, addr)
+ err := engine.Run(addr)
+ if err != nil {
+ waitChan <- fmt.Errorf("server %s error:%v", desc, err)
+ } else {
+
+ }
+ }(server.key.(string), server.value.(pair))
+ }
+
+ //defer app.stopServers()
+
+ // 启动后串行执行的job
+ for _, j := range app.postRunTasks {
+ curErr := j.value.(Task)()
+ if curErr != nil {
+ err = fmt.Errorf("run post task %s return error:%v", j.key, curErr)
+ return
+ }
+ }
+
+ // 启动后串行执行的工作协程
+ for _, worker := range app.postRunWorkers {
+ go func(desc string, g Worker) {
+ curErr := g()
+ if curErr != nil {
+ waitChan <- fmt.Errorf("run post worker %s return error:%v", desc, curErr)
+ }
+ }(worker.key.(string), worker.value.(Worker))
+ }
+
+ // 启动后的并行job
+ for _, j := range app.parallelJobs {
+ go j.value.(Job)()
+ }
+
+ xlog.Noticef("application[%v] run ok.", app.Name)
+
+ select {
+ case anyErr := <-waitChan:
+ xlog.Critif("scheduler stop with execute error:%v", anyErr)
+ return anyErr
+ }
+}
+
+func (app *Application) stop() {
+ for _, task := range app.stopTasks {
+ err := task.value.(Task)()
+ if err != nil {
+ xlog.Errorf("app stop, execute %v error:%v", task.key, err)
+ } else {
+ xlog.Infof("app %v stop, execute task:%v", app.Name, task.key)
+ }
+ }
+ //app.stopServices()
+ //app.stopServers()
+}
+
+//func (app *Application) stopServers() {
+// for _, s := range app.servers {
+// s.item.(*web.Engine).Stop()
+// }
+//}
+
+//func (app *Application) stopServices() {
+// for _, s := range app.services {
+// s.item.(*joyservice.ServicesManager).Stop()
+// }
+//}
diff --git a/admin/lib/node/app_desc.go b/admin/lib/node/app_desc.go
new file mode 100644
index 0000000..d22534f
--- /dev/null
+++ b/admin/lib/node/app_desc.go
@@ -0,0 +1,22 @@
+package node
+
+type AppInitFunc func(app *Application) error
+
+// ApplicationDescInfo 调度器创建app时注入的app描述信息
+type ApplicationDescInfo struct {
+ name string
+ initFunc AppInitFunc
+ options []AppOption
+}
+
+func NewApplicationDescInfo(name string, initFunc AppInitFunc) *ApplicationDescInfo {
+ adi := new(ApplicationDescInfo)
+ adi.name = name
+ adi.initFunc = initFunc
+ return adi
+}
+
+func (adi *ApplicationDescInfo) WithOptions(options ...AppOption) *ApplicationDescInfo {
+ adi.options = append(adi.options, options...)
+ return adi
+}
diff --git a/admin/lib/node/app_options.go b/admin/lib/node/app_options.go
new file mode 100644
index 0000000..b67c900
--- /dev/null
+++ b/admin/lib/node/app_options.go
@@ -0,0 +1,31 @@
+package node
+
+// WithAppBootFlag 设置app的起服参数,flags必须为结构体指针!
+// 只支持string/int/int64/bool四种字段类型,例如:
+//
+// type Flags struct {
+// F1 string `env:"id" desc:"boot id" default:"default value"`
+// F2 int `env:"num" desc:"number" default:"3"`
+// }
+// WithAppBootFlag(&Flags{})
+func WithAppBootFlag(flag interface{}) AppOption {
+ return appOptionFunction(func(app *Application) {
+ app.bootFlag = flag
+ })
+}
+
+func WithAppStopTask(desc string, task Task) AppOption {
+ return appOptionFunction(func(app *Application) {
+ app.stopTasks = append(app.stopTasks, pair{desc, task})
+ })
+}
+
+type AppOption interface {
+ Apply(scd *Application)
+}
+
+type appOptionFunction func(app *Application)
+
+func (of appOptionFunction) Apply(app *Application) {
+ of(app)
+}
diff --git a/admin/lib/node/node.go b/admin/lib/node/node.go
new file mode 100644
index 0000000..b0eb245
--- /dev/null
+++ b/admin/lib/node/node.go
@@ -0,0 +1,209 @@
+package node
+
+import (
+ "admin/lib/xlog"
+ "admin/lib/xos"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "sync"
+ "time"
+)
+
+type IBootConfigContent interface {
+ GetLogConfig() *LogBootConfig
+ OnReload(first bool, oldContent string)
+}
+
+// Node 调度器,调度多个app
+type Node struct {
+ // app全局启动参数
+ bootFlags struct {
+ globalBootFlag *CommonBootFlags
+ exBootFlag interface{}
+ }
+ // app全局配置文件
+ bootConfigFile struct {
+ globalBootConfigFileContent IBootConfigContent // app全局配置文件内容结构体指针,为空没有配置文件解析
+ globalBootConfigParser func(in []byte, out interface{}) error // 配置文件解析函数,默认yaml
+ }
+
+ preInitFuncs []pair // 节点node全局的调用方自定义初始化方法,早于节点初始化(初始化起服配置、日志后)
+ initFuncs []pair // 节点node全局的调用方自定义初始化方法,晚于节点初始化(初始化起服配置、日志后)、早于各个app初始化
+ postFuncs []pair // 节点node全局的调用方自定义启动所有app之后执行的方法
+ adis []*ApplicationDescInfo // app的描述信息列表,用来生成apps
+ apps []*Application // 可绑定多个app
+ tracer *gin.Engine // app全局监控服务,为prometheus、pprof共用
+}
+
+// NewNode
+func NewNode(appOptions ...NodeOption) *Node {
+ node := new(Node)
+ node.bootFlags.globalBootFlag = GlobalBootFlags
+ node.applyOptions(appOptions...)
+ return node
+}
+
+// AddPreInitTask Node自身初始化任务,在启动APP逻辑之前执行
+func (node *Node) AddPreInitTask(desc string, task Task) *Node {
+ node.preInitFuncs = append(node.preInitFuncs, pair{desc, task})
+ return node
+}
+
+// AddInitTask Node自身初始化任务,在启动APP逻辑之前执行
+func (node *Node) AddInitTask(desc string, task Task) *Node {
+ node.initFuncs = append(node.initFuncs, pair{desc, task})
+ return node
+}
+
+// AddInitTask Node自身初始化任务,在启动APP逻辑之前执行
+func (node *Node) AddPostTask(desc string, task Task) *Node {
+ node.postFuncs = append(node.postFuncs, pair{desc, task})
+ return node
+}
+
+// GetBootFlags 获取节点全局的启动参数信息,即./node -node_id 123 -service_name game的值
+func (node *Node) GetBootFlags() *CommonBootFlags {
+ return node.bootFlags.globalBootFlag
+}
+
+// GetExBootFlags 获取调用方自定义的全局启动参数
+func (node *Node) GetExBootFlags() interface{} {
+ return node.bootFlags.exBootFlag
+}
+
+// GetBootConfigContent 获取注册启动配置文件app.yaml内容
+func (node *Node) GetBootConfigContent() IBootConfigContent {
+ return node.bootConfigFile.globalBootConfigFileContent
+}
+
+func (node *Node) applyOptions(options ...NodeOption) *Node {
+ for _, option := range options {
+ option.Apply(node)
+ }
+ return node
+}
+
+// ApplyOptions 应用一些节点配置项
+func (node *Node) ApplyOptions(options ...NodeOption) *Node {
+ node.applyOptions(options...)
+ return node
+}
+
+// WithApp 节点注入app
+func (node *Node) WithApp(appDescInfoList ...*ApplicationDescInfo) *Node {
+ for _, adi := range appDescInfoList {
+ node.withAppDescInfo(adi)
+ }
+ return node
+}
+
+func (node *Node) Run() error {
+ // 初始化node
+ node.initialize()
+
+ defer xlog.CatchWithInfo("run node panic")
+
+ // 启动调度器
+ type waitInfo struct {
+ desc string
+ app *Application
+ err error
+ }
+ waitChan := make(chan waitInfo, 1)
+
+ // 运行trace server
+ go func() {
+ tracerPort := node.bootFlags.globalBootFlag.TracePort
+ xlog.Noticef("trace server listen on %v", tracerPort)
+ err := node.tracer.Run(":" + tracerPort)
+ if err != nil {
+ waitChan <- waitInfo{"trace server", nil, err}
+ }
+ }()
+
+ // 初始化各个app
+ wg := &sync.WaitGroup{}
+ wg.Add(len(node.apps))
+ for i, app := range node.apps {
+ go func(app *Application, initFunc AppInitFunc) {
+ if initFunc != nil {
+ err := initFunc(app)
+ if err != nil {
+ errInfo := fmt.Errorf("application[%v] init return error[%v]", app.Name, err)
+ waitChan <- waitInfo{app.Name, app, errInfo}
+ } else {
+ xlog.Noticef("application[%v] initialize ok", app.Name)
+ }
+ }
+ wg.Done()
+ err := app.run()
+ if err != nil {
+ // 返回调度器的报错
+ waitChan <- waitInfo{app.Name, app, err}
+ }
+ }(app, node.adis[i].initFunc)
+ }
+
+ // for _, app := range node.apps {
+ // go func(app *Application) {
+ // err := app.run()
+ // if err != nil {
+ // // 返回调度器的报错
+ // waitChan <- waitInfo{app.Name, app, err}
+ // }
+ // }(app)
+ // }
+ wg.Wait()
+
+ // 初始化逻辑
+ for _, j := range node.postFuncs {
+ curErr := j.value.(Task)()
+ if curErr != nil {
+ err := fmt.Errorf("node run post task(%s) return error:%v", j.key, curErr)
+ panic(err)
+ }
+ }
+
+ watchSignChan := xos.WatchSignal1()
+
+ defer node.Stop()
+
+ select {
+ case signal := <-watchSignChan:
+ // 优雅停服,监听某些信号
+ xlog.Noticef("Application receive signal(%v), will graceful stop", signal)
+ return nil
+ case errInfo := <-waitChan:
+ err := fmt.Errorf("Application receive Node(%v) stop with error:%v", errInfo.desc, errInfo.err)
+ xlog.Errorf(err.Error())
+ return err
+ }
+}
+
+func (node *Node) Stop() {
+ for _, app := range node.apps {
+ app.stop()
+ }
+ time.Sleep(3 * time.Second)
+}
+
+// WithNode 添加调度器
+func (node *Node) withAppDescInfo(adi *ApplicationDescInfo) *Node {
+ node.adis = append(node.adis, adi)
+ return node
+}
+
+func (node *Node) initApps() error {
+ for i, app := range node.apps {
+ if node.adis[i].initFunc != nil {
+ err := node.adis[i].initFunc(app)
+ if err != nil {
+ return fmt.Errorf("application[%v] init return error[%v]", app.Name, err)
+ } else {
+ xlog.Noticef("application[%v] initialize ok", app.Name)
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/admin/lib/node/node_flags.go b/admin/lib/node/node_flags.go
new file mode 100644
index 0000000..bdfc60f
--- /dev/null
+++ b/admin/lib/node/node_flags.go
@@ -0,0 +1,10 @@
+package node
+
+var GlobalBootFlags = &CommonBootFlags{}
+
+type CommonBootFlags struct {
+ NodeID string `env:"node_id" desc:"【必填】节点id(进程id),分布式环境下唯一标识,没指定就是随机、k8s环境下用pod名字作为id"`
+ ServiceName string `env:"service_name" desc:"【可选】节点服务名,标识节点服务种类,例如game、login,用于rpc服务名调用、日志服务种类输出等"`
+ TracePort string `env:"trace_port" default:"7788" desc:"监控服务端口,暴露prometheus指标、pprof采集等"`
+ BootConfig string `env:"boot_config" default:"config/boot.yaml" desc:"启动配置文件路径,默认\"config/boot.yaml\""`
+}
diff --git a/admin/lib/node/node_init.go b/admin/lib/node/node_init.go
new file mode 100644
index 0000000..f3bcf38
--- /dev/null
+++ b/admin/lib/node/node_init.go
@@ -0,0 +1,201 @@
+/**
+ * @Author: likun
+ * @Title: todo
+ * @Description: todo
+ * @File: node_init
+ * @Date: 2024-11-08 14:02:42
+ */
+package node
+
+import (
+ "admin/lib/flags"
+ "admin/lib/prom"
+ "admin/lib/xlog"
+ "admin/lib/xlog/handler"
+ "encoding/json"
+ "fmt"
+ "github.com/rs/zerolog"
+ "gopkg.in/yaml.v3"
+ "io"
+ "os"
+ "time"
+)
+
+// initialize 初始化app
+func (node *Node) initialize() {
+
+ // 初始化节点启动参数,例如 ./app -a a -b b -c 123
+ must(node.initBootFlags())
+
+ // 初始化节点启动配置文件,例如config/app.yaml
+ must(node.initBootConfig())
+
+ // 初始化逻辑
+ for _, j := range node.preInitFuncs {
+ curErr := j.value.(Task)()
+ if curErr != nil {
+ err := fmt.Errorf("node run pre_initialize task(%s) return error:%v", j.key, curErr)
+ panic(err)
+ }
+ }
+
+ // 初始化程序日志系统
+ must(node.initLog())
+
+ // 初始化prometheus metrics、go pprof
+ must(node.initTracer())
+
+ // 初始化逻辑
+ for _, j := range node.initFuncs {
+ curErr := j.value.(Task)()
+ if curErr != nil {
+ err := fmt.Errorf("node run initialize task(%s) return error:%v", j.key, curErr)
+ panic(err)
+ }
+ }
+ return
+}
+
+func (node *Node) initBootFlags() error {
+ var appBootFlags []interface{}
+ for _, v := range node.adis {
+ curApp := newApp(v.name, v.options...)
+ node.apps = append(node.apps, curApp)
+ if curApp.bootFlag == nil {
+ continue
+ }
+ appBootFlags = append(appBootFlags, curApp.bootFlag)
+ }
+
+ // 解析启动参数
+ flags.ParseWithStructPointers(append([]interface{}{node.bootFlags.globalBootFlag, node.bootFlags.exBootFlag}, appBootFlags...)...)
+
+ flagBin, _ := json.Marshal(node.bootFlags.globalBootFlag)
+ xlog.Noticef("os args:%+v, parsed flags:%v", os.Args, string(flagBin))
+
+ // check
+ if node.bootFlags.globalBootFlag.ServiceName == "" {
+ if len(node.adis) > 1 {
+ node.bootFlags.globalBootFlag.ServiceName = "all_in_one"
+ } else {
+ node.bootFlags.globalBootFlag.ServiceName = node.adis[0].name
+ }
+ }
+
+ return nil
+}
+
+func (node *Node) initBootConfig() error {
+ // 解析配置文件
+ if node.bootConfigFile.globalBootConfigFileContent != nil && node.bootFlags.globalBootFlag.BootConfig != "" {
+ err := node.parseWithConfigFileContent(true)
+ if err != nil {
+ newErr := fmt.Errorf("parse With Config File Content is error:%v", err)
+ return newErr
+ }
+
+ // 定时解析配置文件
+ ticker := time.NewTicker(time.Second * 3)
+ go func() {
+ for range ticker.C {
+ // 解析配置文件
+ err = node.parseWithConfigFileContent(false)
+ if err != nil {
+ xlog.Errorf("parse With Config File Content is error:%v", err)
+ continue
+ }
+ }
+ }()
+ }
+
+ return nil
+}
+
+func (node *Node) initLog() error {
+ logConfig := &LogBootConfig{}
+ if node.bootConfigFile.globalBootConfigFileContent != nil {
+ logConfig = node.bootConfigFile.globalBootConfigFileContent.GetLogConfig().Check()
+ }
+
+ var (
+ enableLogFile = logConfig.EnableFile
+ enableLogStdout = logConfig.EnableStdout
+ logDir = logConfig.LogDir
+ logLevel = logConfig.LogLevel
+ nodeId = node.bootFlags.globalBootFlag.NodeID
+ serviceName = node.bootFlags.globalBootFlag.ServiceName
+ maxLogFileBytesSize = logConfig.LogFileSize * 1024 * 1024 * 1024
+ maxLogRotateBackupCount = logConfig.LogFileRotateCount
+ )
+
+ // 初始化日志系统
+ var logHandlers []io.Writer
+ if !enableLogFile {
+ // 没有指定日志输出目录,默认输出到控制台
+ logHandlers = append(logHandlers, os.Stdout)
+ } else {
+ // 指定日志输出目录
+ logHandler, err := handler.NewRotatingDayMaxFileHandler(logDir, serviceName, maxLogFileBytesSize, maxLogRotateBackupCount)
+ if err != nil {
+ newErr := fmt.Errorf("new xlog file handler with path [%v] name[%v] error:%v", logDir, serviceName, err)
+ return newErr
+ }
+ logHandlers = append(logHandlers, logHandler)
+
+ // 也指定输出到控制台
+ if enableLogStdout {
+ logHandlers = append(logHandlers, os.Stdout)
+ }
+ }
+
+ // 创建logger
+ logLevelEnum := xlog.ParseLogLevelString(logLevel)
+ xlog.NewGlobalLogger(logHandlers, logLevelEnum, func(l zerolog.Logger) zerolog.Logger {
+ return l.With().Str("service", serviceName).Str("node_id", nodeId).Logger()
+ })
+
+ return nil
+}
+
+func (node *Node) initTracer() error {
+ node.tracer = prom.NewEngine(true)
+ return nil
+}
+
+// 解析配置文件
+func (node *Node) parseWithConfigFileContent(first bool) error {
+
+ content, err := os.ReadFile(node.bootFlags.globalBootFlag.BootConfig)
+ if err != nil {
+ newErr := fmt.Errorf("load boot config file %v error:%v", node.bootFlags.globalBootFlag.BootConfig, err)
+ return newErr
+ }
+
+ if node.bootConfigFile.globalBootConfigParser == nil {
+ node.bootConfigFile.globalBootConfigParser = yaml.Unmarshal
+ }
+
+ oldContent, _ := json.Marshal(node.bootConfigFile.globalBootConfigFileContent)
+
+ err = node.bootConfigFile.globalBootConfigParser(content, node.bootConfigFile.globalBootConfigFileContent)
+ if err != nil {
+ newErr := fmt.Errorf("load boot config file %v content %v ok, but parse content error:%v",
+ node.bootFlags.globalBootFlag.BootConfig, string(content), err)
+ return newErr
+ }
+
+ // 读取配置后设置一次日志等级
+ if !first {
+ xlog.SetLogLevel(xlog.ParseLogLevelString(node.bootConfigFile.globalBootConfigFileContent.GetLogConfig().LogLevel))
+ }
+
+ node.bootConfigFile.globalBootConfigFileContent.OnReload(first, string(oldContent))
+
+ return nil
+}
+
+func must(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/admin/lib/node/node_log.go b/admin/lib/node/node_log.go
new file mode 100644
index 0000000..0acbc4d
--- /dev/null
+++ b/admin/lib/node/node_log.go
@@ -0,0 +1,37 @@
+/**
+ * @Author: likun
+ * @Title: todo
+ * @Description: todo
+ * @File: node_log
+ * @Date: 2024-11-08 14:39:33
+ */
+package node
+
+type LogBootConfig struct {
+ EnableStdout bool `yaml:"enable_stdout"`
+ EnableFile bool `yaml:"enable_file"`
+ LogDir string `yaml:"log_dir"`
+ LogFileSize int `yaml:"log_file_size"` // 单个日志文件最大容量(单位:G)
+ LogFileRotateCount int `yaml:"log_file_rotate_count"` // 日志文件最大滚动次数
+ LogLevel string `yaml:"log_level"` // 日志等级: trace/debug/info/notice/warn/error/fatal
+}
+
+func (lc *LogBootConfig) Check() *LogBootConfig {
+ if lc.LogDir == "" {
+ lc.LogDir = "./logs"
+ }
+
+ if lc.LogFileSize == 0 {
+ lc.LogFileSize = 1024 * 1024 * 1024 * 5
+ }
+
+ if lc.LogFileRotateCount == 0 {
+ lc.LogFileRotateCount = 10
+ }
+
+ if lc.LogLevel == "" {
+ lc.LogLevel = "debug"
+ }
+
+ return lc
+}
diff --git a/admin/lib/node/node_options.go b/admin/lib/node/node_options.go
new file mode 100644
index 0000000..ba36466
--- /dev/null
+++ b/admin/lib/node/node_options.go
@@ -0,0 +1,46 @@
+package node
+
+// WithNodeBootFlags 设置启动参数解析结构
+//func WithNodeBootFlags(f *CommonBootFlags) NodeOption {
+// return nodeOptionFun(func(node *Node) {
+// node.globalBootFlag = f
+// })
+//}
+
+// WithNodeBootConfigFileContent 设置启动配置文件的解析结构,不设置默认无起服配置,默认以yaml解析
+func WithNodeBootConfigFileContent(content IBootConfigContent) NodeOption {
+ return nodeOptionFun(func(node *Node) {
+ node.bootConfigFile.globalBootConfigFileContent = content
+ })
+}
+
+// WithNodeExBootFlags 设置额外的启动参数解析结构
+func WithNodeExBootFlags(content any) NodeOption {
+ return nodeOptionFun(func(node *Node) {
+ node.bootFlags.exBootFlag = content
+ })
+}
+
+// WithNodeBootConfigFileParser 设置起服文件解析函数,默认yaml格式
+func WithNodeBootConfigFileParser(f func(content []byte, out interface{}) error) NodeOption {
+ return nodeOptionFun(func(node *Node) {
+ node.bootConfigFile.globalBootConfigParser = f
+ })
+}
+
+// WithNodeLogFileTimestampFormat 设置日志文件默认时间戳格式,默认"20060102"
+//func WithNodeLogFileTimestampFormat(format string) NodeOption {
+// return appOptionFun(func(node *Node) {
+// node.xlog.logFileTsFormat = format
+// })
+//}
+
+type NodeOption interface {
+ Apply(app *Node)
+}
+
+type nodeOptionFun func(node *Node)
+
+func (of nodeOptionFun) Apply(node *Node) {
+ of(node)
+}
diff --git a/admin/lib/prom/example_test.go b/admin/lib/prom/example_test.go
new file mode 100644
index 0000000..f071e93
--- /dev/null
+++ b/admin/lib/prom/example_test.go
@@ -0,0 +1,50 @@
+package prom
+
+import (
+ "math/rand"
+ "testing"
+ "time"
+)
+
+func TestProm(t *testing.T) {
+ go func() {
+ NewEngine(true).Run(":9008")
+ }()
+
+ // 模拟gate进程1
+ ccuOnlineCounter := NewGauge("ccu").InitDefaultLabels(map[string]string{
+ "app_id": "1",
+ "app": "gate",
+ }, []string{"nation"})
+
+ // 模拟gate进程2
+ ccuOnlineCounter1 := NewGauge("ccu1").InitDefaultLabels(map[string]string{
+ "app_id": "2",
+ "app": "gate",
+ }, []string{"nation"})
+
+ // 模拟比赛进程1
+ ccuBattleCounter := NewGauge("ccu_battle").InitDefaultLabels(map[string]string{
+ "app_id": "1",
+ "app": "battle",
+ }, []string{"nation"})
+
+ go func() {
+ for {
+ // 模拟统计gate1间隔收集在线人数
+ num := float64(rand.Int31n(100))
+ ccuOnlineCounter.LabelValues("cn").Add(num)
+
+ // 模拟统计gate2间隔收集在线人数
+ ccuOnlineCounter1.LabelValues("cn").Add(num * 2)
+
+ // 模拟统计比赛1间隔收集比赛人数
+ num = float64(rand.Int31n(100))
+ ccuBattleCounter.LabelValues("uk").Add(num)
+
+ time.Sleep(time.Second * 5)
+ }
+ }()
+
+ select {}
+}
diff --git a/admin/lib/prom/pprof/prof.go b/admin/lib/prom/pprof/prof.go
new file mode 100644
index 0000000..aadb83f
--- /dev/null
+++ b/admin/lib/prom/pprof/prof.go
@@ -0,0 +1,65 @@
+package pprof
+
+import (
+ "expvar"
+ _ "expvar"
+ "net/http"
+ _ "net/http/pprof"
+ "sync"
+)
+
+var _expvars_ints = make(map[string]*expvar.Int)
+var _expvars_floats = make(map[string]*expvar.Float)
+var _expvars_strings = make(map[string]*expvar.String)
+
+var _expvars_ints_lock = &sync.RWMutex{}
+var _expvars_floats_lock = &sync.RWMutex{}
+var _expvars_strings_lock = &sync.RWMutex{}
+
+// StartCommonProfileMonitor 启动公共性能分析http服务器
+// 接口1:http://ip:port/debug/vars返回内存监控的json数据
+// 接口2:http://ip:port/debug/pprof/xxx
+func StartCommonProfileMonitor(accessHttpAddr string) {
+ go func() {
+ http.ListenAndServe(accessHttpAddr, nil)
+ }()
+}
+
+// AddCommonProfileExpVarInt 添加/debug/vars返回的json变量,保证全局名字唯一
+func AddCommonProfileExpVarInt(name string, delta int64) {
+ _expvars_ints_lock.Lock()
+ defer _expvars_ints_lock.Unlock()
+ if data, find := _expvars_ints[name]; find {
+ data.Add(delta)
+ } else {
+ v := expvar.NewInt(name)
+ v.Add(delta)
+ _expvars_ints[name] = v
+ }
+}
+
+// AddCommonProfileExpVarFloat 添加/debug/vars返回的json变量,保证全局名字唯一
+func AddCommonProfileExpVarFloat(name string, delta float64) {
+ _expvars_floats_lock.Lock()
+ defer _expvars_floats_lock.Unlock()
+ if data, find := _expvars_floats[name]; find {
+ data.Add(delta)
+ } else {
+ v := expvar.NewFloat(name)
+ v.Add(delta)
+ _expvars_floats[name] = v
+ }
+}
+
+// AddCommonProfileExpVarInt 添加/debug/vars返回的json变量,保证全局名字唯一
+func AddCommonProfileExpVarString(name string, cur string) {
+ _expvars_strings_lock.Lock()
+ defer _expvars_strings_lock.Unlock()
+ if data, find := _expvars_strings[name]; find {
+ data.Set(cur)
+ } else {
+ v := expvar.NewString(name)
+ v.Set(cur)
+ _expvars_strings[name] = v
+ }
+}
diff --git a/admin/lib/prom/prom_test.go b/admin/lib/prom/prom_test.go
new file mode 100644
index 0000000..e6a6f6c
--- /dev/null
+++ b/admin/lib/prom/prom_test.go
@@ -0,0 +1,37 @@
+package prom
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "math/rand"
+ "testing"
+ "time"
+)
+
+func TestRawProm(t *testing.T) {
+ vec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "test",
+ Help: "test help",
+ Buckets: []float64{1, 50, 100},
+ },
+ []string{"app"},
+ )
+ prometheus.MustRegister(vec)
+
+ go func() {
+ for i := 0; i < 1000; i++ {
+ vec.WithLabelValues("battle").Observe(float64(rand.Intn(100)))
+ time.Sleep(time.Second)
+ }
+ }()
+
+ gin.SetMode(gin.ReleaseMode)
+ engine := gin.Default()
+ engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
+ err := engine.Run(":12345")
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/admin/lib/prom/promethus.go b/admin/lib/prom/promethus.go
new file mode 100644
index 0000000..38ff74d
--- /dev/null
+++ b/admin/lib/prom/promethus.go
@@ -0,0 +1,211 @@
+package prom
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ ginPprof "github.com/gin-contrib/pprof"
+ "github.com/gin-gonic/gin"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+func NewCounter(name string) *PromeCounterStatMgr {
+ mgr := new(PromeCounterStatMgr)
+ mgr.promeStatMgr = newPromeStatMgr(name, func(defaultLabels, labels []string) prometheus.Collector {
+ return newCounterVec(name, defaultLabels, labels)
+ })
+ return mgr
+}
+
+func NewGauge(name string) *PromeGaugeStatMgr {
+ mgr := new(PromeGaugeStatMgr)
+ mgr.promeStatMgr = newPromeStatMgr(name, func(defaultLabels, labels []string) prometheus.Collector {
+ return newGagueVec(name, defaultLabels, labels)
+ })
+ return mgr
+}
+
+func NewHistogram(name string, buckets []float64) *PromeHistogramStatMgr {
+ mgr := new(PromeHistogramStatMgr)
+ mgr.promeStatMgr = newPromeStatMgr(name, func(defaultLabels, labels []string) prometheus.Collector {
+ return newHistogramVec(name, defaultLabels, labels, buckets)
+ })
+ return mgr
+}
+
+type PromeCounterStatMgr struct {
+ *promeStatMgr
+}
+
+// InitLabels 初始化指标的标签说明,以后每次收集指标都给上标签值
+func (c *PromeCounterStatMgr) InitLabels(labels []string) *PromeCounterStatMgr {
+ return c.InitDefaultLabels(nil, labels)
+}
+
+// InitDefaultLabels 带上默认标签,例如某些服务启动就能知道标签值,比如app_name,app_id等,
+// 又不想每次收集指标都传递标签值,可以用此方法一开始就设置好
+func (c *PromeCounterStatMgr) InitDefaultLabels(defaultLabels map[string]string, labels []string) *PromeCounterStatMgr {
+ c.promeStatMgr.withDefaultLabels(defaultLabels, labels)
+ return c
+}
+
+// LabelValues 准备收集指标,调用这个传递标签值,后续再给上指标值
+// 即:c.LabelValues("xxx").Add(234)
+func (c *PromeCounterStatMgr) LabelValues(labels ...string) prometheus.Counter {
+ return c.promeStatMgr.getCounterWithLabels(labels...)
+}
+
+type PromeGaugeStatMgr struct {
+ *promeStatMgr
+}
+
+// InitLabels 初始化指标的标签说明,以后每次收集指标都给上标签值
+func (c *PromeGaugeStatMgr) InitLabels(labels []string) *PromeGaugeStatMgr {
+ return c.InitDefaultLabels(nil, labels)
+}
+
+// InitDefaultLabels 带上默认标签,例如某些服务启动就能知道标签值,比如app_name,app_id等,
+// 又不想每次收集指标都传递标签值,可以用此方法一开始就设置好
+func (c *PromeGaugeStatMgr) InitDefaultLabels(defaultLabels map[string]string, labels []string) *PromeGaugeStatMgr {
+ c.promeStatMgr.withDefaultLabels(defaultLabels, labels)
+ return c
+}
+
+// LabelValues 准备收集指标,调用这个传递标签值,后续再给上指标值
+// 即:c.LabelValues("xxx").Add(234)
+func (c *PromeGaugeStatMgr) LabelValues(labels ...string) prometheus.Gauge {
+ return c.promeStatMgr.getGaugeWithLabels(labels...)
+}
+
+type PromeHistogramStatMgr struct {
+ *promeStatMgr
+}
+
+// InitLabels 初始化指标的标签说明,以后每次收集指标都给上标签值
+func (c *PromeHistogramStatMgr) InitLabels(labels []string) *PromeHistogramStatMgr {
+ return c.InitDefaultLabels(nil, labels)
+}
+
+// InitDefaultLabels 带上默认标签,例如某些服务启动就能知道标签值,比如app_name,app_id等,
+// 又不想每次收集指标都传递标签值,可以用此方法一开始就设置好
+func (c *PromeHistogramStatMgr) InitDefaultLabels(defaultLabels map[string]string, labels []string) *PromeHistogramStatMgr {
+ c.promeStatMgr.withDefaultLabels(defaultLabels, labels)
+ return c
+}
+
+// LabelValues 准备收集指标,调用这个传递标签值,后续再给上指标值
+// 即:c.LabelValues("xxx").Add(234)
+func (c *PromeHistogramStatMgr) LabelValues(labels ...string) prometheus.Observer {
+ return c.promeStatMgr.getHistogramWithLabels(labels...)
+}
+
+type promeStatMgr struct {
+ collector prometheus.Collector
+ name string
+ newCollectorFun func(defaultLabels, labels []string) prometheus.Collector
+ defaultLabelsValue []string
+ initCounter int32
+}
+
+func (mgr *promeStatMgr) withDefaultLabels(defaultLabels map[string]string, labels []string) {
+ if atomic.AddInt32(&mgr.initCounter, 1) > 1 {
+ panic(fmt.Errorf("promethus vec labels must init once"))
+ }
+
+ defaultLabelKeys := make([]string, 0)
+ defaultLabelValues := make([]string, 0)
+ for k, v := range defaultLabels {
+ defaultLabelKeys = append(defaultLabelKeys, k)
+ defaultLabelValues = append(defaultLabelValues, v)
+ }
+
+ mgr.defaultLabelsValue = defaultLabelValues
+ mgr.collector = mgr.newCollectorFun(defaultLabelKeys, labels)
+ return
+}
+
+func (mgr *promeStatMgr) joinLabels(labels ...string) []string {
+ newLabels := make([]string, 0, len(mgr.defaultLabelsValue)+len(labels))
+ newLabels = append(newLabels, mgr.defaultLabelsValue...)
+ newLabels = append(newLabels, labels...)
+ return newLabels
+}
+
+func (mgr *promeStatMgr) getCounterWithLabels(labels ...string) prometheus.Counter {
+ newLabels := mgr.joinLabels(labels...)
+ return mgr.collector.(*prometheus.CounterVec).WithLabelValues(newLabels...)
+}
+
+func (mgr *promeStatMgr) getGaugeWithLabels(labels ...string) prometheus.Gauge {
+ newLabels := mgr.joinLabels(labels...)
+ return mgr.collector.(*prometheus.GaugeVec).WithLabelValues(newLabels...)
+}
+
+func (mgr *promeStatMgr) getHistogramWithLabels(labels ...string) prometheus.Observer {
+ newLabels := mgr.joinLabels(labels...)
+ return mgr.collector.(*prometheus.HistogramVec).WithLabelValues(newLabels...)
+}
+
+func newPromeStatMgr(name string,
+ newCollectorFun func(defaultLabelsKey []string, labels []string) prometheus.Collector) *promeStatMgr {
+ mgr := new(promeStatMgr)
+ mgr.name = name
+ mgr.newCollectorFun = newCollectorFun
+ return mgr
+}
+
+func newCounterVec(name string, defaultLabels []string, dynamicLabels []string) prometheus.Collector {
+ vec := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: name,
+ Help: name,
+ },
+ append(defaultLabels, dynamicLabels...),
+ )
+ prometheus.MustRegister(vec)
+ return vec
+}
+
+func newGagueVec(name string, defaultLabels []string, dynamicLabels []string) prometheus.Collector {
+ vec := prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Name: name,
+ Help: name,
+ },
+ append(defaultLabels, dynamicLabels...),
+ )
+ prometheus.MustRegister(vec)
+ return vec
+}
+
+func newHistogramVec(name string, defaultLabels []string, dynamicLabels []string, buckets []float64) prometheus.Collector {
+ vec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: name,
+ Help: name,
+ Buckets: buckets,
+ },
+ append(defaultLabels, dynamicLabels...),
+ )
+ prometheus.MustRegister(vec)
+ return vec
+}
+
+func NewEngine(enablePprof bool) *gin.Engine {
+ //gin.SetMode(gin.ReleaseMode)
+ engine := gin.New()
+ engine.Use(gin.Recovery())
+ RouteEngine(engine, enablePprof)
+ return engine
+}
+
+func RouteEngine(engine *gin.Engine, enablePprof bool) {
+ if enablePprof {
+ ginPprof.Register(engine)
+ }
+ engine.Use(gin.LoggerWithConfig(gin.LoggerConfig{
+ SkipPaths: []string{"/metrics"},
+ }))
+ engine.GET("/metrics", gin.WrapH(promhttp.Handler()))
+}
diff --git a/admin/lib/web/example/example.go b/admin/lib/web/example/example.go
new file mode 100644
index 0000000..4d27ae7
--- /dev/null
+++ b/admin/lib/web/example/example.go
@@ -0,0 +1,212 @@
+package main
+
+import (
+ "admin/lib/web"
+ "fmt"
+ "html/template"
+ "sort"
+)
+
+type MyContext struct {
+ rawCtx web.RawContext
+}
+
+func (ctx *MyContext) SetRawContext(rawCtx web.RawContext) {
+ ctx.rawCtx = rawCtx
+}
+func (ctx *MyContext) GetRawContext() web.RawContext {
+ return ctx.rawCtx
+}
+
+func main() {
+ coreRouter := "iris" // gin|iris
+ engine := web.NewEngine(":8888", coreRouter, func() web.Context {
+ return new(MyContext)
+ })
+ groupV1 := engine.Group("/v1")
+ {
+ type Test1 struct {
+ RoleId string `json:"role_id" desc:"角色id字段" required:"true"`
+ F1 int `json:"f1" desc:"这是字段1" default:"324"`
+ F2 string `json:"f2" desc:"这是字段2" default:"abcd"`
+ F3 bool `json:"f3" desc:"这是字段3" default:"false"`
+ }
+ groupV1.Get("/test1", "设置玩家背包数据", web.AccessMode_Write, Test1{}, func(ctx *MyContext, params *Test1) {
+ fmt.Printf("receive test1 path params:%+v\n", params)
+ ctx.GetRawContext().Json(200, map[string]any{
+ "msg": "ok",
+ "code": 200,
+ })
+ })
+ groupV1.Post("/test1", "获取玩家数据", web.AccessMode_Read, Test1{}, func(ctx *MyContext, params *Test1) {
+ fmt.Printf("receive test1 path params:%+v\n", params)
+ ctx.GetRawContext().Json(200, map[string]any{
+ "msg": "ok",
+ "code": 200,
+ })
+ })
+ // URI --> :8888/v1/test1?f1=123&f2=sdfsd&f3=true
+ // BODY --> :8888/v1/test1 {"f1":123,"f2":"sdfds","f3":"true"}
+ }
+ engine.Get("/test2", "获取玩家比赛", web.AccessMode_Read, nil, func(ctx *MyContext) {
+ fmt.Printf("receive test2 request\n")
+ ctx.GetRawContext().Json(200, map[string]any{
+ "msg": "ok",
+ "code": 200,
+ })
+ })
+ engine.Post("/test2", "测试gm指令名字", web.AccessMode_Read, nil, func(ctx *MyContext) {
+ fmt.Printf("receive test2 request\n")
+ ctx.GetRawContext().Json(200, map[string]any{
+ "msg": "ok",
+ "code": 200,
+ })
+ })
+ groupV2 := engine.Group("v2")
+ {
+ type Test2 struct {
+ F1 int `json:"f1" desc:"这是字段1"`
+ F2 string `json:"f2" desc:"这是字段2"`
+ F3 bool `json:"f3" desc:"这是字段3"`
+ }
+ groupV2.Post("test3", "测试gm指令名字123", web.AccessMode_Write, Test2{}, func(ctx *MyContext, params *Test2) {
+ fmt.Printf("receive test3\n")
+ ctx.GetRawContext().Json(200, map[string]any{
+ "msg": "ok",
+ "code": 200,
+ })
+ })
+ }
+
+ engine.Get("path_tree", "测试gm指令名字3424", web.AccessMode_Write, nil, func(ctx *MyContext) {
+ tree := engine.TravelPathTree()
+ ctx.GetRawContext().Json(200, tree)
+ })
+
+ engine.Get("/grid", "获取指令描述表", web.AccessMode_Read, nil, func(c *MyContext) {
+ tree := &Tree{tree: engine.TravelPathTree()}
+
+ tmpl, err := template.New("html_test").Funcs(template.FuncMap(map[string]any{
+ "incr": incr,
+ })).Parse(tplText)
+ if err != nil {
+ panic(err)
+ }
+ err = tmpl.Execute(c.GetRawContext().Writer(), tree)
+ if err != nil {
+ panic(err)
+ }
+ })
+
+ err := engine.Run()
+ if err != nil {
+ panic(err)
+ }
+}
+
+type Path struct {
+ Path string
+ Route *web.RouteDescInfo
+}
+
+type Tree struct {
+ tree map[string]*web.RouteDescInfo
+}
+
+func (t *Tree) Paths() [][]*Path {
+ splitCol := 4
+ list := make([]*Path, 0)
+ for k, v := range t.tree {
+ list = append(list, &Path{Path: k, Route: v})
+ }
+ sort.SliceStable(list, func(i, j int) bool {
+ return list[i].Path < list[j].Path
+ })
+
+ if len(list) <= splitCol {
+ return [][]*Path{list}
+ }
+
+ section := len(list) / splitCol
+ paths := make([][]*Path, splitCol)
+
+ for i := 0; i < splitCol; i++ {
+ paths[i] = make([]*Path, 0)
+ start := i * section
+ for j := 0; j < section; j++ {
+ start += j
+ paths[i] = append(paths[i], list[start])
+ }
+ }
+
+ if len(list)%splitCol > 0 {
+ idx := 0
+ for i := len(list) - len(list)%splitCol; i < len(list); i++ {
+ paths[idx] = append(paths[idx], list[i])
+ idx++
+ }
+ }
+
+ return paths
+}
+
+func incr(src int) int {
+ return src + 1
+}
+
+var tplText = `
+
+
+
+
+ GM MASTER
+
+ {{ $pathsList := .Paths }}
+ {{ range $pidex, $paths := $pathsList }}
+
+
指令列表{{ incr $pidex }}
+
+
+ {{ range $idx, $path := $paths }}
+
+ {{ $path.Route.Desc }} |
+
+
+ 请求路径 |
+ {{ $path.Path }} |
+
+
+ 方法 |
+ {{ $path.Route.MethodDesc "或" }} |
+
+ {{ if $path.Route.HasRequest }}
+ {{ range $idx1, $field := $path.Route.Fields }}
+
+ {{ printf "参数%d" $idx1 }} |
+ {{printf "%s" $field.Name }} |
+ {{printf "%s" $field.Type }} |
+ {{printf "%s" $field.Desc }} |
+ {{ if eq $field.Default "" }}
+ {{printf "默认:无" }} |
+ {{ else }}
+ {{printf "默认:%s" $field.Default }} |
+ {{ end }}
+
+ {{ end }}
+ {{ else }}
+ 无需参数 |
+ {{ end }}
+
+ {{ end }}
+
---|
+
+
+ {{ end }}
+
+
+
+
+
+
+
+`
diff --git a/admin/lib/web/group.go b/admin/lib/web/group.go
new file mode 100644
index 0000000..62b41b7
--- /dev/null
+++ b/admin/lib/web/group.go
@@ -0,0 +1,183 @@
+package web
+
+type routeGroupInterface interface {
+ Use(middlewares ...HandlerFunc)
+ Group(path string, handlers ...HandlerFunc) routeGroupInterface
+ Get(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface
+ Post(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface
+ Put(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface
+ Delete(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface
+}
+
+type Group struct {
+ coreRouter routeGroupInterface
+ DirectRoutes map[string]*RouteDescInfo
+ GroupRoutes map[string]*Group
+}
+
+func newGroup(coreRouter routeGroupInterface) *Group {
+ g := &Group{
+ coreRouter: coreRouter,
+ DirectRoutes: make(map[string]*RouteDescInfo),
+ GroupRoutes: make(map[string]*Group),
+ }
+ return g
+}
+
+func (e *Group) Use(middlewares ...HandlerFunc) {
+ e.coreRouter.Use(middlewares...)
+}
+
+func (e *Group) Group(path string, handlers ...HandlerFunc) *Group {
+ path = pathBeTheSame(path)
+ routeGroup := e.coreRouter.Group(path, handlers...)
+ group := newGroup(routeGroup)
+ e.GroupRoutes[path] = group
+ return group
+}
+
+// Get 注册get方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数
+//
+// path:路径
+// desc:路由的一个简短描述
+// request:请求结构体
+// 格式:
+// type struct {
+// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"`
+// }
+// tag描述:
+// json:字段名
+// desc:字段简短描述,没有可以不写
+// default:默认值,没有可以不写
+// required:是否必填字段,没有要求可以不写
+// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型,
+// 例如:
+// type Field struct {
+// A int
+// B bool
+// }
+// type Request struct {
+// F1 *Field
+// }
+// F1字段就是非法的,无法解析,会报错
+// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request)
+func (e *Group) Get(path string, desc string, access AccessMode, request any, handler HandlerFunc) {
+ path = pathBeTheSame(path)
+ old, find := e.DirectRoutes[path]
+ if !find {
+ e.DirectRoutes[path] = newRouteDescInfo(path, desc, "GET", access, request)
+ } else {
+ old.Method = append(old.Method, "GET")
+ e.DirectRoutes[path] = old
+ }
+ e.coreRouter.Get(path, desc, request, handler)
+}
+
+// Post 注册post方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数
+//
+// path:路径
+// desc:路由的一个简短描述
+// request:请求结构体
+// 格式:
+// type struct {
+// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"`
+// }
+// tag描述:
+// json:字段名
+// desc:字段简短描述,没有可以不写
+// default:默认值,没有可以不写
+// required:是否必填字段,没有要求可以不写
+// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型,
+// 例如:
+// type Field struct {
+// A int
+// B bool
+// }
+// type Request struct {
+// F1 *Field
+// }
+// F1字段就是非法的,无法解析,会报错
+// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request)
+func (e *Group) Post(path, desc string, access AccessMode, request any, handler HandlerFunc) {
+ path = pathBeTheSame(path)
+ old, find := e.DirectRoutes[path]
+ if !find {
+ e.DirectRoutes[path] = newRouteDescInfo(path, desc, "POST", access, request)
+ } else {
+ old.Method = append(old.Method, "POST")
+ e.DirectRoutes[path] = old
+ }
+ e.coreRouter.Post(path, desc, request, handler)
+}
+
+func (e *Group) Put(path, desc string, access AccessMode, request any, handler HandlerFunc) {
+ path = pathBeTheSame(path)
+ old, find := e.DirectRoutes[path]
+ if !find {
+ e.DirectRoutes[path] = newRouteDescInfo(path, desc, "PUT", access, request)
+ } else {
+ old.Method = append(old.Method, "PUT")
+ e.DirectRoutes[path] = old
+ }
+ e.coreRouter.Put(path, desc, request, handler)
+}
+
+func (e *Group) Delete(path, desc string, access AccessMode, request any, handler HandlerFunc) {
+ path = pathBeTheSame(path)
+ old, find := e.DirectRoutes[path]
+ if !find {
+ e.DirectRoutes[path] = newRouteDescInfo(path, desc, "DELETE", access, request)
+ } else {
+ old.Method = append(old.Method, "DELETE")
+ e.DirectRoutes[path] = old
+ }
+ e.coreRouter.Delete(path, desc, request, handler)
+}
+
+// GetAndPost 注册get&post方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数
+//
+// path:路径
+// desc:路由的一个简短描述
+// request:请求结构体
+// 格式:
+// type struct {
+// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"`
+// }
+// tag描述:
+// json:字段名
+// desc:字段简短描述,没有可以不写
+// default:默认值,没有可以不写
+// required:是否必填字段,没有要求可以不写
+// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型,
+// 例如:
+// type Field struct {
+// A int
+// B bool
+// }
+// type Request struct {
+// F1 *Field
+// }
+// F1字段就是非法的,无法解析,会报错
+// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request)
+func (e *Group) GetAndPost(path, desc string, access AccessMode, request any, handler HandlerFunc) {
+ path = pathBeTheSame(path)
+ record := newRouteDescInfo(path, desc, "GET", access, request)
+ record.Method = append(record.Method, "POST")
+ e.DirectRoutes[path] = record
+ e.coreRouter.Get(path, desc, request, handler)
+ e.coreRouter.Post(path, desc, request, handler)
+}
+
+func (e *Group) TravelPathTree() map[string]*RouteDescInfo {
+ m := make(map[string]*RouteDescInfo)
+ for k, route := range e.DirectRoutes {
+ m[k] = route
+ }
+ for k, subG := range e.GroupRoutes {
+ gm := subG.TravelPathTree()
+ for k1, v1 := range gm {
+ m[k+k1] = v1
+ }
+ }
+ return m
+}
diff --git a/admin/lib/web/model.go b/admin/lib/web/model.go
new file mode 100644
index 0000000..59f9837
--- /dev/null
+++ b/admin/lib/web/model.go
@@ -0,0 +1,24 @@
+package web
+
+import (
+ "fmt"
+ "io"
+)
+
+type Context interface {
+ SetRawContext(ctx RawContext)
+ GetRawContext() RawContext
+}
+
+type HandlerFunc any
+
+type RawContext interface {
+ Json(code int, v any)
+ Writer() io.Writer
+}
+
+var ParseParamsErrorHandler = func(ctx Context, path string, err error) {
+ ctx.GetRawContext().Json(501, map[string]interface{}{
+ "MSG": fmt.Sprintf("parse params error:%v", err),
+ })
+}
diff --git a/admin/lib/web/readme.txt b/admin/lib/web/readme.txt
new file mode 100644
index 0000000..3d01ed6
--- /dev/null
+++ b/admin/lib/web/readme.txt
@@ -0,0 +1,12 @@
+
+
+注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型,
+例如:
+type Field struct {
+ A int
+ B bool
+}
+type Request struct {
+ F1 *Field
+}
+F1字段就是非法的,无法解析,会报错
\ No newline at end of file
diff --git a/admin/lib/web/router.go b/admin/lib/web/router.go
new file mode 100644
index 0000000..16180a5
--- /dev/null
+++ b/admin/lib/web/router.go
@@ -0,0 +1,173 @@
+package web
+
+import "fmt"
+
+type routerInterface interface {
+ routeGroupInterface
+ Run(addr string) error
+}
+
+type Engine struct {
+ Addr string
+ newContextFun func() Context
+ DirectRoutes map[string]*RouteDescInfo
+ GroupRoutes map[string]*Group
+ coreRouter routerInterface
+}
+
+// NewEngine 使用gin或者iris创建web引擎,newContextFun为调用方需要实现的context初始化函数
+// coreRouter指定不同web引擎,目前只支持gin
+func NewEngine(coreRouter string, newContextFun func() Context) *Engine {
+ e := &Engine{
+ newContextFun: newContextFun,
+ DirectRoutes: make(map[string]*RouteDescInfo),
+ GroupRoutes: make(map[string]*Group),
+ }
+ switch coreRouter {
+ //case "iris":
+ // e.coreRouter = newRouterIris(newContextFun)
+ case "gin":
+ e.coreRouter = newRouterGin(newContextFun)
+ default:
+ panic(fmt.Errorf("NewEngine only support irir or gin, invalid type:%v", coreRouter))
+ }
+ return e
+}
+
+func (e *Engine) Use(middlewares ...HandlerFunc) {
+ e.coreRouter.Use(middlewares...)
+}
+
+func (e *Engine) Group(path string, handlers ...HandlerFunc) *Group {
+ path = pathBeTheSame(path)
+ routeGroup := e.coreRouter.Group(path, handlers...)
+ group := newGroup(routeGroup)
+ e.GroupRoutes[path] = group
+ return group
+}
+
+// Get 注册get方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数
+//
+// path:路径
+// desc:路由的一个简短描述
+// request:请求结构体
+// 格式:
+// type struct {
+// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"`
+// }
+// tag描述:
+// json:字段名
+// desc:字段简短描述,没有可以不写
+// default:默认值,没有可以不写
+// required:是否必填字段,没有要求可以不写
+// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型,
+// 例如:
+// type Field struct {
+// A int
+// B bool
+// }
+// type Request struct {
+// F1 *Field
+// }
+// F1字段就是非法的,无法解析,会报错
+// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request)
+func (e *Engine) Get(path string, desc string, access AccessMode, request any, handler HandlerFunc) {
+ path = pathBeTheSame(path)
+ old, find := e.DirectRoutes[path]
+ if !find {
+ e.DirectRoutes[path] = newRouteDescInfo(path, desc, "GET", access, request)
+ } else {
+ old.Method = append(old.Method, "GET")
+ e.DirectRoutes[path] = old
+ }
+ e.coreRouter.Get(path, desc, request, handler)
+}
+
+// Post 注册post方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数
+//
+// path:路径
+// desc:路由的一个简短描述
+// request:请求结构体
+// 格式:
+// type struct {
+// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"`
+// }
+// tag描述:
+// json:字段名
+// desc:字段简短描述,没有可以不写
+// default:默认值,没有可以不写
+// required:是否必填字段,没有要求可以不写
+// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型,
+// 例如:
+// type Field struct {
+// A int
+// B bool
+// }
+// type Request struct {
+// F1 *Field
+// }
+// F1字段就是非法的,无法解析,会报错
+// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request)
+func (e *Engine) Post(path, desc string, access AccessMode, request any, handler HandlerFunc) {
+ path = pathBeTheSame(path)
+ old, find := e.DirectRoutes[path]
+ if !find {
+ e.DirectRoutes[path] = newRouteDescInfo(path, desc, "POST", access, request)
+ } else {
+ old.Method = append(old.Method, "POST")
+ e.DirectRoutes[path] = old
+ }
+ e.coreRouter.Post(path, desc, request, handler)
+}
+
+func (e *Engine) Run(addr string) error {
+ e.Addr = addr
+ return e.coreRouter.Run(addr)
+}
+
+// TravelPathTree 获取所有路径的描述表
+func (e *Engine) TravelPathTree() map[string]*RouteDescInfo {
+ m := make(map[string]*RouteDescInfo)
+ for k, route := range e.DirectRoutes {
+ m[k] = route
+ }
+ for k, subG := range e.GroupRoutes {
+ gm := subG.TravelPathTree()
+ for k1, v1 := range gm {
+ m[k+k1] = v1
+ }
+ }
+ return m
+}
+
+func pathBeTheSame(path string) string {
+ if path == "" {
+ return ""
+ }
+ if path == "/" {
+ return path
+ }
+ if path[0] != '/' {
+ path = "/" + path
+ }
+ if path[len(path)-1] == '/' {
+ path = path[:len(path)-1]
+ }
+ return path
+}
+
+func pathBeTheSame1(path string) string {
+ if path == "" {
+ return ""
+ }
+ if path == "/" {
+ return path
+ }
+ if path[0] != '/' {
+ path = "/" + path
+ }
+ if path[len(path)-1] == '/' {
+ path = path[:len(path)-1]
+ }
+ return path
+}
diff --git a/admin/lib/web/router_desc.go b/admin/lib/web/router_desc.go
new file mode 100644
index 0000000..da509c9
--- /dev/null
+++ b/admin/lib/web/router_desc.go
@@ -0,0 +1,85 @@
+package web
+
+import (
+ "reflect"
+ "strings"
+)
+
+type AccessMode int
+
+const (
+ AccessMode_Read = 1
+ AccessMode_Write = 2
+)
+
+type RouteDescInfo struct {
+ Path string
+ Desc string
+ Method []string
+ AccessMode
+ rawRequest any
+ Fields []*FieldDescInfo
+}
+
+func newRouteDescInfo(path, desc, method string, access AccessMode, request any) *RouteDescInfo {
+ info := &RouteDescInfo{
+ Path: path,
+ Desc: desc,
+ Method: []string{method},
+ AccessMode: access,
+ rawRequest: request,
+ }
+ info.RequestFieldsDesc()
+ return info
+}
+
+func (info *RouteDescInfo) MethodDesc(sep string) string {
+ return strings.Join(info.Method, sep)
+}
+
+func (info *RouteDescInfo) RequestFieldsDesc() []*FieldDescInfo {
+ if !info.HasRequest() {
+ return []*FieldDescInfo{}
+ }
+
+ list := make([]*FieldDescInfo, 0)
+ to := reflect.TypeOf(info.rawRequest)
+ if to.Kind() == reflect.Ptr {
+ to = to.Elem()
+ }
+ for i := 0; i < to.NumField(); i++ {
+ field := to.Field(i)
+ list = append(list, newFieldDescInfo(field))
+ }
+ info.Fields = list
+ return list
+}
+
+func (info *RouteDescInfo) HasRequest() bool {
+ return info.rawRequest != nil
+}
+
+type FieldDescInfo struct {
+ Name string
+ FieldName string
+ Type string
+ Desc string
+ Required bool
+ Default string
+ rawTypeOfField reflect.StructField
+}
+
+func newFieldDescInfo(field reflect.StructField) *FieldDescInfo {
+ desc := &FieldDescInfo{
+ Name: field.Tag.Get("json"),
+ FieldName: field.Name,
+ Type: field.Type.String(),
+ Desc: field.Tag.Get("desc"),
+ rawTypeOfField: field,
+ }
+ if field.Tag.Get("required") == "true" {
+ desc.Required = true
+ }
+ desc.Default = desc.rawTypeOfField.Tag.Get("default")
+ return desc
+}
diff --git a/admin/lib/web/router_gin.go b/admin/lib/web/router_gin.go
new file mode 100644
index 0000000..f0bfe1b
--- /dev/null
+++ b/admin/lib/web/router_gin.go
@@ -0,0 +1,303 @@
+package web
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "io"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+type routerGroupGin struct {
+ group *gin.RouterGroup
+ newContextFun func() Context
+}
+
+func (group *routerGroupGin) Use(middlewares ...HandlerFunc) {
+ group.group.Use(getGinHandlerFun(group.newContextFun, middlewares...)...)
+}
+func (group *routerGroupGin) Group(path string, handlers ...HandlerFunc) routeGroupInterface {
+ ginGroup := group.group.Group(path, getGinHandlerFun(group.newContextFun, handlers...)...)
+ group1 := &routerGroupGin{group: ginGroup, newContextFun: group.newContextFun}
+ return group1
+}
+func (group *routerGroupGin) Get(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface {
+ if req == nil {
+ group.group.GET(path, getGinHandlerFun(group.newContextFun, handlers...)...)
+ } else {
+ group.group.GET(path, getGinHandlerFunWithRequest(group.newContextFun, req, handlers...)...)
+ }
+ return group
+}
+func (group *routerGroupGin) Post(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface {
+ if req == nil {
+ group.group.POST(path, getGinHandlerFun(group.newContextFun, handlers...)...)
+ } else {
+ group.group.POST(path, getGinHandlerFunWithRequest(group.newContextFun, req, handlers...)...)
+ }
+ return group
+}
+
+func (group *routerGroupGin) Put(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface {
+ if req == nil {
+ group.group.PUT(path, getGinHandlerFun(group.newContextFun, handlers...)...)
+ } else {
+ group.group.PUT(path, getGinHandlerFunWithRequest(group.newContextFun, req, handlers...)...)
+ }
+ return group
+}
+
+func (group *routerGroupGin) Delete(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface {
+ if req == nil {
+ group.group.DELETE(path, getGinHandlerFun(group.newContextFun, handlers...)...)
+ } else {
+ group.group.DELETE(path, getGinHandlerFunWithRequest(group.newContextFun, req, handlers...)...)
+ }
+ return group
+}
+
+type routerGin struct {
+ engine *gin.Engine
+ newContextFun func() Context
+}
+
+func newRouterGin(newContextFun func() Context) routerInterface {
+ engine := gin.Default()
+ router := &routerGin{
+ engine: engine,
+ newContextFun: newContextFun,
+ }
+ return router
+}
+
+func (router *routerGin) Use(middlewares ...HandlerFunc) {
+ router.engine.Use(getGinHandlerFun(router.newContextFun, middlewares...)...)
+}
+func (router *routerGin) Group(path string, handlers ...HandlerFunc) routeGroupInterface {
+ ginGroup := router.engine.Group(path, getGinHandlerFun(router.newContextFun, handlers...)...)
+ group := &routerGroupGin{
+ group: ginGroup,
+ newContextFun: router.newContextFun,
+ }
+ return group
+}
+func (router *routerGin) Get(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface {
+ if req == nil {
+ router.engine.GET(path, getGinHandlerFun(router.newContextFun, handlers...)...)
+ } else {
+ router.engine.GET(path, getGinHandlerFunWithRequest(router.newContextFun, req, handlers...)...)
+ }
+ return router
+}
+func (router *routerGin) Post(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface {
+ if req == nil {
+ router.engine.POST(path, getGinHandlerFun(router.newContextFun, handlers...)...)
+ } else {
+ router.engine.POST(path, getGinHandlerFunWithRequest(router.newContextFun, req, handlers...)...)
+ }
+ return router
+}
+
+func (router *routerGin) Put(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface {
+ if req == nil {
+ router.engine.PUT(path, getGinHandlerFun(router.newContextFun, handlers...)...)
+ } else {
+ router.engine.PUT(path, getGinHandlerFunWithRequest(router.newContextFun, req, handlers...)...)
+ }
+ return router
+}
+
+func (router *routerGin) Delete(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface {
+ if req == nil {
+ router.engine.DELETE(path, getGinHandlerFun(router.newContextFun, handlers...)...)
+ } else {
+ router.engine.DELETE(path, getGinHandlerFunWithRequest(router.newContextFun, req, handlers...)...)
+ }
+ return router
+}
+
+func getGinHandlerFun(newContextFun func() Context, handlers ...HandlerFunc) []gin.HandlerFunc {
+ list := make([]gin.HandlerFunc, 0, len(handlers))
+ for _, handler := range handlers {
+ list = append(list, func(rawCtx *gin.Context) {
+ rawCtx1 := &ginCtx{ctx: rawCtx}
+ ctx := newContextFun()
+ ctx.SetRawContext(rawCtx1)
+ reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(ctx)})
+ if rawCtx.IsAborted() {
+ return
+ }
+ })
+ }
+ return list
+}
+
+func getGinHandlerFunWithRequest(newContextFun func() Context, requestTemplate any, handlers ...HandlerFunc) []gin.HandlerFunc {
+ list := make([]gin.HandlerFunc, 0, len(handlers))
+ for _, handler := range handlers {
+ list = append(list, func(rawCtx *gin.Context) {
+ rawCtx1 := &ginCtx{ctx: rawCtx}
+ ctx := newContextFun()
+ ctx.SetRawContext(rawCtx1)
+ request, err := rawCtx1.parseRequest(requestTemplate)
+ if err != nil {
+ ParseParamsErrorHandler(ctx, rawCtx.Request.RequestURI, err)
+ return
+ }
+ reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(request)})
+ })
+ }
+ return list
+}
+
+func (router *routerGin) Run(addr string) error {
+ return router.engine.Run(addr)
+}
+
+type ginCtx struct {
+ ctx *gin.Context
+}
+
+func (ctx *ginCtx) Json(code int, v any) {
+ ctx.ctx.JSON(code, v)
+}
+
+func (ctx *ginCtx) Writer() io.Writer {
+ return ctx.ctx.Writer
+}
+
+func (ctx *ginCtx) body() ([]byte, error) {
+ buf, err := io.ReadAll(ctx.ctx.Request.Body)
+ if err != nil {
+ return nil, fmt.Errorf("read body error:%v", err)
+ }
+
+ return buf, nil
+}
+
+func (ctx *ginCtx) parseRequest(request any) (any, error) {
+ if request == nil {
+ return nil, nil
+ }
+
+ requestType := reflect.TypeOf(request)
+ newRequest := reflect.New(requestType).Interface()
+
+ body, err := ctx.body()
+ if err != nil {
+ return nil, err
+ }
+
+ if len(body) == 0 {
+ newRequestValue := reflect.ValueOf(newRequest).Elem()
+ newRequestValueType := newRequestValue.Type()
+ for i := 0; i < newRequestValue.NumField(); i++ {
+ f := newRequestValueType.Field(i)
+ fieldTagName := f.Tag.Get("json")
+ if fieldTagName == "" {
+ fieldTagName = f.Name
+ }
+
+ field := newRequestValue.Field(i)
+ if !field.CanSet() {
+ continue
+ }
+ fieldStr := ctx.ctx.Query(fieldTagName)
+ err := setValue(field, fieldStr, f.Tag.Get("default"), f.Tag.Get("required"))
+ if err != nil {
+ return nil, fmt.Errorf("parse uri params field(%v) set value(%v) error:%v", f.Name, fieldStr, err)
+ }
+ }
+ } else {
+ err = json.Unmarshal(body, newRequest)
+ if err != nil {
+ return nil, fmt.Errorf("json unmarshal body error:%v", err)
+ }
+ }
+ return newRequest, nil
+}
+
+// setValue 设置结构体一个字段的值
+func setValue(field reflect.Value, value string, defaultValue string, required string) error {
+ if value == "" {
+ value = defaultValue
+ }
+ if value == "" && required == "true" {
+ return fmt.Errorf("field is required, please give a valid value")
+ }
+
+ if field.Kind() == reflect.Ptr {
+ if value == "" {
+ return nil
+ }
+
+ if field.IsNil() {
+ field.Set(reflect.New(field.Type().Elem()))
+ }
+ field = field.Elem()
+ }
+ switch field.Kind() {
+ case reflect.String:
+ field.SetString(value)
+ case reflect.Bool:
+ if value == "" {
+ field.SetBool(false)
+ } else {
+ b, err := strconv.ParseBool(value)
+ if err != nil {
+ return err
+ }
+ field.SetBool(b)
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if value == "" {
+ field.SetInt(0)
+ } else {
+ i, err := strconv.ParseInt(value, 0, field.Type().Bits())
+ if err != nil {
+ return err
+ }
+ field.SetInt(i)
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ if value == "" {
+ field.SetUint(0)
+ break
+ }
+ ui, err := strconv.ParseUint(value, 0, field.Type().Bits())
+ if err != nil {
+ return err
+ }
+ field.SetUint(ui)
+ case reflect.Float32, reflect.Float64:
+ if value == "" {
+ field.SetFloat(0)
+ break
+ }
+ f, err := strconv.ParseFloat(value, field.Type().Bits())
+ if err != nil {
+ return err
+ }
+ field.SetFloat(f)
+ case reflect.Struct:
+ return fmt.Errorf("unsupport struct field:%v", field.Type())
+ case reflect.Slice:
+ values := strings.Split(value, ",")
+ if len(values) == 1 && values[0] == "" {
+ values = []string{}
+ }
+ field.Set(reflect.MakeSlice(field.Type(), len(values), len(values)))
+ for i := 0; i < len(values); i++ {
+ err := setValue(field.Index(i), values[i], "", "")
+ if err != nil {
+ return err
+ }
+ }
+
+ default:
+ return fmt.Errorf("no support type %s", field.Type())
+ }
+ return nil
+}
diff --git a/admin/lib/xlog/catch.go b/admin/lib/xlog/catch.go
new file mode 100644
index 0000000..7d736e2
--- /dev/null
+++ b/admin/lib/xlog/catch.go
@@ -0,0 +1,56 @@
+package xlog
+
+import (
+ "runtime"
+ "time"
+)
+
+func Catch() {
+ if v := recover(); v != nil {
+ backtrace(v)
+ }
+}
+
+func CatchWithInfo(info string) {
+ if v := recover(); v != nil {
+ Critif("panic info: [%v]", info)
+ backtrace(v)
+ }
+}
+
+func CatchWithInfoFun(info string, f func()) {
+ if v := recover(); v != nil {
+ Critif("panic info: [%v]", info)
+ backtrace(v)
+ f()
+ }
+}
+
+func PrintCallerStack(info string) {
+ for i := 1; ; i++ {
+ pc, file, line, ok := runtime.Caller(i + 1)
+ if !ok {
+ break
+ }
+ //fmt.Fprintf(os.Stderr, "% 3d. %s() %s:%d\n", i, runtime.FuncForPC(pc).Name(), file, line)
+ Debugf("[%v] % 3d. %s() %s:%d\n", info, i, runtime.FuncForPC(pc).Name(), file, line)
+ }
+}
+
+func backtrace(message interface{}) {
+ //fmt.Fprintf(os.Stderr, "Traceback[%s] (most recent call last):\n", time.Now())
+ Critif("Traceback[%s] (most recent call last):\n", time.Now())
+ for i := 0; ; i++ {
+ pc, file, line, ok := runtime.Caller(i + 1)
+ if !ok {
+ break
+ }
+ //fmt.Fprintf(os.Stderr, "% 3d. %s() %s:%d\n", i, runtime.FuncForPC(pc).Name(), file, line)
+ Critif("% 3d. %s() %s:%d\n", i, runtime.FuncForPC(pc).Name(), file, line)
+ }
+ //fmt.Fprintf(os.Stderr, "%v\n", message)
+ Critif("%v\n", message)
+
+ // 等待写日志
+ //time.Sleep(time.Second * 5)
+}
diff --git a/admin/lib/xlog/crosssyslog/syslog_darwin.go b/admin/lib/xlog/crosssyslog/syslog_darwin.go
new file mode 100644
index 0000000..e0aecb2
--- /dev/null
+++ b/admin/lib/xlog/crosssyslog/syslog_darwin.go
@@ -0,0 +1,31 @@
+//go:build darwin
+// +build darwin
+
+package crosssyslog
+
+import "log/syslog"
+
+type Syslog struct {
+ *syslog.Writer
+}
+
+type Priority = syslog.Priority
+
+var (
+ LOG_INFO = syslog.LOG_INFO
+ LOG_LOCAL0 = syslog.LOG_LOCAL0
+)
+
+func New(priority Priority, tag string) (*Syslog, error) {
+ SyslogLocal, syslogErr := syslog.New(syslog.LOG_NOTICE, tag)
+ if syslogErr != nil {
+ return nil, syslogErr
+ }
+ return &Syslog{
+ Writer: SyslogLocal,
+ }, nil
+}
+
+func (s *Syslog) Write(data []byte) (int, error) {
+ return s.Writer.Write(data)
+}
diff --git a/admin/lib/xlog/crosssyslog/syslog_linux.go b/admin/lib/xlog/crosssyslog/syslog_linux.go
new file mode 100644
index 0000000..014deb1
--- /dev/null
+++ b/admin/lib/xlog/crosssyslog/syslog_linux.go
@@ -0,0 +1,31 @@
+//go:build linux
+// +build linux
+
+package crosssyslog
+
+import "log/syslog"
+
+type Syslog struct {
+ *syslog.Writer
+}
+
+type Priority = syslog.Priority
+
+var (
+ LOG_INFO = syslog.LOG_INFO
+ LOG_LOCAL0 = syslog.LOG_LOCAL0
+)
+
+func New(priority Priority, tag string) (*Syslog, error) {
+ SyslogLocal, syslogErr := syslog.New(priority, tag)
+ if syslogErr != nil {
+ return nil, syslogErr
+ }
+ return &Syslog{
+ Writer: SyslogLocal,
+ }, nil
+}
+
+func (s *Syslog) Write(data []byte) (int, error) {
+ return s.Writer.Write(data)
+}
diff --git a/admin/lib/xlog/crosssyslog/syslog_windows.go b/admin/lib/xlog/crosssyslog/syslog_windows.go
new file mode 100644
index 0000000..0553a37
--- /dev/null
+++ b/admin/lib/xlog/crosssyslog/syslog_windows.go
@@ -0,0 +1,21 @@
+// +build windows
+
+package crosssyslog
+
+type Syslog struct {
+}
+
+type Priority int
+
+var (
+ LOG_INFO Priority = 1
+ LOG_LOCAL0 Priority = 2
+)
+
+func New(priority Priority, tag string) (*Syslog, error) {
+ return &Syslog{}, nil
+}
+
+func (s *Syslog) Write(data []byte) (int, error) {
+ return len(data), nil
+}
diff --git a/admin/lib/xlog/handler.go b/admin/lib/xlog/handler.go
new file mode 100644
index 0000000..4417f36
--- /dev/null
+++ b/admin/lib/xlog/handler.go
@@ -0,0 +1,8 @@
+package xlog
+
+// Handler writes logs to somewhere
+type Handler interface {
+ //Rotate()
+ Write(p []byte) (n int, err error)
+ Close() error
+}
diff --git a/admin/lib/xlog/handler/handler_file.go b/admin/lib/xlog/handler/handler_file.go
new file mode 100644
index 0000000..0efa43b
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_file.go
@@ -0,0 +1,58 @@
+package handler
+
+import (
+ "os"
+ "path"
+ "sync"
+)
+
+// FileHandler writes xlog to a file.
+type FileHandler struct {
+ fileName string
+ flag int
+ fd *os.File
+ fdLock *sync.RWMutex
+}
+
+func NewFileHandler(fileName string, flag int) (*FileHandler, error) {
+ dir := path.Dir(fileName)
+ os.Mkdir(dir, 0777)
+
+ f, err := os.OpenFile(fileName, flag, 0666)
+ if err != nil {
+ return nil, err
+ }
+
+ h := new(FileHandler)
+
+ h.fileName = fileName
+ h.flag = flag
+ h.fd = f
+ h.fdLock = &sync.RWMutex{}
+
+ return h, nil
+}
+
+//func (h *FileHandler) Rotate() {
+// fd, err := os.OpenFile(h.fileName, h.flag, 0666)
+// if err != nil {
+// h.fd.Write([]byte(fmt.Sprintf("interval check xlog file %v open error:%v", h.fileName, err)))
+// return
+// }
+//
+// h.fdLock.Lock()
+// oldFd := h.fd
+// h.fd = fd
+// oldFd.Close()
+// h.fdLock.Unlock()
+//}
+
+func (h *FileHandler) Write(b []byte) (n int, err error) {
+ h.fdLock.RLock()
+ defer h.fdLock.RUnlock()
+ return h.fd.Write(b)
+}
+
+func (h *FileHandler) Close() error {
+ return h.fd.Close()
+}
diff --git a/admin/lib/xlog/handler/handler_file_rotate.go b/admin/lib/xlog/handler/handler_file_rotate.go
new file mode 100644
index 0000000..415f833
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_file_rotate.go
@@ -0,0 +1,83 @@
+package handler
+
+import (
+ "fmt"
+ "os"
+ "path"
+)
+
+// RotatingFileHandler writes xlog a file, if file size exceeds maxBytes,
+// it will backup current file and open a new one.
+//
+// max backup file number is set by backupCount, it will delete oldest if backups too many.
+type RotatingFileHandler struct {
+ fd *os.File
+
+ fileName string
+ maxBytes int
+ backupCount int
+}
+
+func NewRotatingFileHandler(fileName string, maxBytes int, backupCount int) (*RotatingFileHandler, error) {
+ dir := path.Dir(fileName)
+ os.Mkdir(dir, 0777)
+
+ h := new(RotatingFileHandler)
+
+ if maxBytes <= 0 {
+ return nil, fmt.Errorf("invalid max bytes")
+ }
+
+ h.fileName = fileName
+ h.maxBytes = maxBytes
+ h.backupCount = backupCount
+
+ var err error
+ h.fd, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ if err != nil {
+ return nil, err
+ }
+
+ return h, nil
+}
+
+func (h *RotatingFileHandler) Write(p []byte) (n int, err error) {
+ h.doRollover()
+ return h.fd.Write(p)
+}
+
+func (h *RotatingFileHandler) Close() error {
+ if h.fd != nil {
+ return h.fd.Close()
+ }
+ return nil
+}
+
+func (h *RotatingFileHandler) doRollover() {
+ f, err := h.fd.Stat()
+ if err != nil {
+ return
+ }
+
+ if h.maxBytes <= 0 {
+ return
+ } else if f.Size() < int64(h.maxBytes) {
+ return
+ }
+
+ if h.backupCount > 0 {
+ h.fd.Close()
+
+ for i := h.backupCount - 1; i > 0; i-- {
+ sfn := fmt.Sprintf("%s.%d", h.fileName, i)
+ dfn := fmt.Sprintf("%s.%d", h.fileName, i+1)
+
+ os.Rename(sfn, dfn)
+ }
+
+ dfn := fmt.Sprintf("%s.1", h.fileName)
+ os.Rename(h.fileName, dfn)
+
+ h.fd, _ = os.OpenFile(h.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ }
+}
diff --git a/admin/lib/xlog/handler/handler_file_rotate_atday.go b/admin/lib/xlog/handler/handler_file_rotate_atday.go
new file mode 100644
index 0000000..d756750
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_file_rotate_atday.go
@@ -0,0 +1,207 @@
+package handler
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "time"
+)
+
+const (
+ kRollPerSeconds = 60 * 60 * 24 // one day
+)
+
+var (
+ pid = os.Getpid()
+ host = "unknowhost"
+)
+
+func init() {
+ h, err := os.Hostname()
+ if err == nil {
+ host = shortHostname(h)
+ }
+}
+
+func shortHostname(hostname string) string {
+ if i := strings.Index(hostname, "."); i >= 0 {
+ return hostname[:i]
+ }
+ return hostname
+}
+
+func logFileName(basename string) string {
+ now := time.Now()
+ name := fmt.Sprintf("%s.%04d%02d%02d-%02d%02d%02d.%s.%d.xlog",
+ basename,
+ now.Year(),
+ now.Month(),
+ now.Day(),
+ now.Hour(),
+ now.Minute(),
+ now.Second(),
+ host,
+ pid,
+ )
+
+ return name
+}
+
+type RotatingFileAtDayHandler struct {
+ baseName string
+ rollSize int
+ flushInterval int64
+ checkEveryN int
+ syncFlush bool
+ count int
+ startofPeriod int64
+ lastRoll int64
+ lastFlush int64
+ file *appendFile
+}
+
+// baseName 日志文件的基本名包含全路径 如 "/tmp/test"
+// rollSize 每写入rollSize字节日志滚动文件
+// flushInterval 刷新文件写入缓存的间隔
+// checkEveryN 每写入checkEveryN次 检查文件回滚和缓存刷新
+// syncFlush == true flushInterval和checkEveryN 失效
+func NewRotatingFileAtDayHandler(baseName string, rollSize int,
+ flushInterval int64, checkEveryN int, syncFlush bool) *RotatingFileAtDayHandler {
+ hander := &RotatingFileAtDayHandler{
+ baseName: baseName,
+ rollSize: rollSize,
+ flushInterval: flushInterval,
+ checkEveryN: checkEveryN,
+ syncFlush: syncFlush,
+ count: 0,
+ startofPeriod: 0,
+ lastRoll: 0,
+ lastFlush: 0,
+ }
+ hander.rollFile()
+ return hander
+}
+
+func NewDefaultRotatingFileAtDayHandler(baseName string,
+ rollSize int) *RotatingFileAtDayHandler {
+ //checkEveryN 开发期间默认设置为1 上线后调高已提高处理性能
+ return NewRotatingFileAtDayHandler(baseName, rollSize, 3, 1, true)
+}
+
+func (self *RotatingFileAtDayHandler) Write(b []byte) (int, error) {
+ n, err := self.file.append(b)
+ if err != nil {
+ return n, err
+ }
+ if self.file.writtenBytes() > self.rollSize {
+ self.rollFile()
+ } else {
+ self.count++
+ if self.count >= self.checkEveryN || self.syncFlush {
+ self.count = 0
+ now := time.Now().Unix()
+ thisPeriod := now / kRollPerSeconds * kRollPerSeconds
+ if thisPeriod != self.startofPeriod {
+ self.rollFile()
+ } else if now-self.lastFlush > self.flushInterval || self.syncFlush {
+ self.lastFlush = now
+ err = self.file.flush()
+ }
+ }
+ }
+ return n, err
+}
+
+func (self *RotatingFileAtDayHandler) Rotate() {
+
+}
+
+func (self *RotatingFileAtDayHandler) flush() error {
+ return self.file.flush()
+}
+
+func (self *RotatingFileAtDayHandler) rollFile() bool {
+ fileName := logFileName(self.baseName)
+ t := time.Now().Unix()
+ start := t / kRollPerSeconds * kRollPerSeconds
+ //滚动时间间隔最小为1秒
+ if t > self.lastRoll {
+ self.lastRoll = t
+ self.lastFlush = t
+ self.startofPeriod = start
+ if self.file != nil {
+ self.file.close()
+ }
+ tmpFile, _ := newAppendFile(fileName)
+ self.file = tmpFile
+ return true
+ }
+ return false
+}
+
+func (self *RotatingFileAtDayHandler) Close() error {
+ return self.file.close()
+}
+
+type appendFile struct {
+ file *os.File
+ writer *bufio.Writer
+ writtenBytes_ int
+}
+
+func newAppendFile(filename string) (*appendFile, error) {
+ file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
+ if err != nil {
+ return nil, err
+ }
+ return &appendFile{
+ file: file,
+ writer: bufio.NewWriterSize(file, 64*1024),
+ writtenBytes_: 0,
+ }, nil
+}
+
+func (self *appendFile) append(b []byte) (int, error) {
+ length := len(b)
+ remain := length
+ offset := 0
+ var err error
+ for remain > 0 {
+ x, err := self.writer.Write(b[offset:])
+ if err != nil {
+ if err != io.ErrShortWrite {
+ break
+ }
+ }
+
+ offset = offset + x
+ remain = length - offset
+ }
+ self.writtenBytes_ = self.writtenBytes_ + length
+ return offset, err
+}
+
+func (self *appendFile) writtenBytes() int {
+ return self.writtenBytes_
+}
+func (self *appendFile) flush() error {
+ return self.writer.Flush()
+}
+
+func (self *appendFile) close() error {
+ err := self.writer.Flush()
+ for err != nil {
+ if err == io.ErrShortWrite {
+ err = self.writer.Flush()
+ } else {
+ break
+ }
+ }
+ if err != nil {
+ return err
+ }
+
+ return self.file.Close()
+}
diff --git a/admin/lib/xlog/handler/handler_file_rotate_daymax.go b/admin/lib/xlog/handler/handler_file_rotate_daymax.go
new file mode 100644
index 0000000..7249daa
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_file_rotate_daymax.go
@@ -0,0 +1,202 @@
+package handler
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "admin/lib/xlog/handler/logsyscall"
+)
+
+type RotatingDayMaxFileHandler struct {
+ baseName string
+ outPath string
+ fd *os.File
+
+ rotateInfo struct {
+ maxBytes int // 单个日志文件最大长度
+ maxBackupCount int // 最大归档文件数量
+ day time.Time // 记录跨天
+ }
+}
+
+// NewRotatingDayMaxFileHandler 每天00点滚动,当超过大小也滚动
+func NewRotatingDayMaxFileHandler(outPath, baseName string, maxBytes int, backupCount int) (*RotatingDayMaxFileHandler, error) {
+ if outPath == "" {
+ outPath = "log/"
+ }
+
+ h := new(RotatingDayMaxFileHandler)
+ h.baseName = baseName
+ h.outPath = outPath
+ h.rotateInfo.day = time.Now()
+ h.rotateInfo.maxBytes = maxBytes
+ h.rotateInfo.maxBackupCount = backupCount
+ fd, err := openFile(h.outPath, h.baseName, false)
+ if err != nil {
+ panic(err)
+ } else {
+ h.fd = fd
+ }
+ go h.rotateHandler()
+ return h, nil
+}
+
+func (h *RotatingDayMaxFileHandler) Write(p []byte) (n int, err error) {
+ return h.fd.Write(p)
+}
+
+func (h *RotatingDayMaxFileHandler) Close() error {
+ if h.fd != nil {
+ return h.fd.Close()
+ }
+ return nil
+}
+
+func (h *RotatingDayMaxFileHandler) rotateHandler() {
+ for {
+ time.Sleep(time.Second)
+ h.tryRotate()
+ }
+}
+
+func (h *RotatingDayMaxFileHandler) tryRotate() {
+ // 校验时间是否触发归档
+ now := time.Now()
+ if now.Day() != h.rotateInfo.day.Day() {
+ h.rotateDay()
+ h.rotateInfo.day = now
+ return
+ }
+
+ // 校验文件大小是否触发归档
+ size, err := calcFileSize(h.fd)
+ if err != nil {
+ outErrorLog("stat xlog file(%v) error:%v/home/likun/work/idlerpg-server/internal/utils/xlog", h.baseName, err)
+ return
+ }
+
+ if h.rotateInfo.maxBytes > 0 && h.rotateInfo.maxBytes <= size {
+ h.rotateSize()
+ return
+ }
+}
+
+// rotateAt 到点触发归档
+func (h *RotatingDayMaxFileHandler) rotateDay() {
+ // 创建新的一天的日志文件
+ newFd, err := openFile(h.outPath, h.baseName, false)
+ if err != nil {
+ outErrorLog("new day rotate file, but open new file(%v) error:%v", h.baseName, err)
+ return
+ }
+
+ // 用新的一天的日志文件描述符接管当前使用的
+ oldFd := h.fd
+ h.fd = newFd
+ oldFd.Close()
+}
+
+// rotateSize 文件过大触发归档
+func (h *RotatingDayMaxFileHandler) rotateSize() {
+ // 锁定文件,使触发归档的别的进程也锁住
+ lockFile(h.fd)
+ // 重新打开文件判断大小,防止文件被别的归档进程改名
+ curSize := calcFileNameSize(h.fd.Name())
+ if curSize < h.rotateInfo.maxBytes {
+ // 别的进程归档过了
+ unlockFile(h.fd)
+ return
+ }
+
+ // 滚动copy归档的文件,那么归档的1号文件空出来了
+ baseFileName := h.fd.Name()
+ for i := h.rotateInfo.maxBackupCount; i > 0; i-- {
+ sfn := fmt.Sprintf("%s.%d", baseFileName, i)
+ dfn := fmt.Sprintf("%s.%d", baseFileName, i+1)
+ os.Rename(sfn, dfn)
+ }
+
+ // 将当前文件内容拷贝到归档1号文件
+ dfn := fmt.Sprintf("%s.1", baseFileName)
+ os.Rename(baseFileName, dfn)
+
+ // 重新创建当前日志文件得到新的描述符
+ newFd, err := os.OpenFile(baseFileName, os.O_CREATE|os.O_APPEND|os.O_RDWR|os.O_TRUNC, 0777)
+ if err != nil {
+ outErrorLog("rotate xlog file size, open file(%v) error:%v", baseFileName, err)
+ unlockFile(h.fd)
+ } else {
+ oldFd := h.fd
+ h.fd = newFd
+ unlockFile(h.fd)
+ oldFd.Close()
+ }
+}
+
+func calcFileSize(fd *os.File) (int, error) {
+ st, err := fd.Stat()
+ return int(st.Size()), err
+}
+
+func calcFileNameSize(fileName string) int {
+ fd, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0777)
+ if err != nil {
+ return 0
+ }
+ defer fd.Close()
+ st, err := fd.Stat()
+ if err != nil {
+ return 0
+ }
+ return int(st.Size())
+}
+
+var fileTimeStampFormat = "20060102"
+
+func openFile(path, baseName string, isTrunc bool) (*os.File, error) {
+ os.MkdirAll(path, 0777)
+ timeNow := time.Now()
+ t := timeNow.Format(fileTimeStampFormat)
+ fullFileName := baseName + "-" + t + ".log"
+
+ if path != "" {
+ if path[len(path)-1] != '/' {
+ path += "/"
+ }
+ }
+
+ fullPathFileName := path + fullFileName
+
+ var fd *os.File
+ var err error
+
+ flag := os.O_CREATE | os.O_APPEND | os.O_RDWR
+ if isTrunc {
+ flag |= os.O_TRUNC
+ }
+
+ fd, err = os.OpenFile(fullPathFileName, flag, 0777)
+ if err != nil {
+ return fd, fmt.Errorf("open xlog file(%v) error:%v", fullPathFileName, err)
+ }
+ return fd, nil
+}
+
+func lockFile(fd *os.File) {
+ err := logsyscall.Flock(int(fd.Fd()), logsyscall.LOCK_EX)
+ if err != nil {
+ outErrorLog("lock file(%v) error:%v", fd.Name(), err)
+ }
+}
+
+func unlockFile(fd *os.File) {
+ err := logsyscall.Flock(int(fd.Fd()), logsyscall.LOCK_UN)
+ if err != nil {
+ outErrorLog("unlock file(%v) error:%v", fd.Name(), err)
+ }
+}
+
+func outErrorLog(format string, values ...interface{}) {
+ fmt.Fprintf(os.Stderr, format+"\n", values...)
+}
diff --git a/admin/lib/xlog/handler/handler_file_rotate_timing.go b/admin/lib/xlog/handler/handler_file_rotate_timing.go
new file mode 100644
index 0000000..9f13eff
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_file_rotate_timing.go
@@ -0,0 +1,96 @@
+package handler
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "time"
+)
+
+// TimeRotatingFileHandler writes xlog to a file,
+// it will backup current and open a new one, with a period time you sepecified.
+//
+// refer: http://docs.python.org/2/library/logging.handlers.html.
+// same like python TimedRotatingFileHandler.
+type TimeRotatingFileHandler struct {
+ fd *os.File
+
+ baseName string
+ interval int64
+ suffix string
+ rolloverAt int64
+}
+
+const (
+ WhenSecond = iota
+ WhenMinute
+ WhenHour
+ WhenDay
+)
+
+func NewTimeRotatingFileHandler(baseName string, when int8, interval int) (*TimeRotatingFileHandler, error) {
+ dir := path.Dir(baseName)
+ os.Mkdir(dir, 0777)
+
+ h := new(TimeRotatingFileHandler)
+
+ h.baseName = baseName
+
+ switch when {
+ case WhenSecond:
+ h.interval = 1
+ h.suffix = "2006-01-02_15-04-05"
+ case WhenMinute:
+ h.interval = 60
+ h.suffix = "2006-01-02_15-04"
+ case WhenHour:
+ h.interval = 3600
+ h.suffix = "2006-01-02_15"
+ case WhenDay:
+ h.interval = 3600 * 24
+ h.suffix = "2006-01-02"
+ default:
+ return nil, fmt.Errorf("invalid when_rotate: %d", when)
+ }
+
+ h.interval = h.interval * int64(interval)
+
+ var err error
+ h.fd, err = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+ if err != nil {
+ return nil, err
+ }
+
+ fInfo, _ := h.fd.Stat()
+ h.rolloverAt = fInfo.ModTime().Unix() + h.interval
+
+ return h, nil
+}
+
+func (h *TimeRotatingFileHandler) doRollover() {
+ //refer http://hg.python.org/cpython/file/2.7/Lib/logging/handlers.py
+ now := time.Now()
+ t := now.Unix()
+
+ if h.rolloverAt <= t {
+ fName := h.baseName + now.Format(h.suffix)
+ h.fd.Close()
+ e := os.Rename(h.baseName, fName)
+ if e != nil {
+ panic(e)
+ }
+
+ h.fd, _ = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
+
+ h.rolloverAt = t + h.interval
+ }
+}
+
+func (h *TimeRotatingFileHandler) Write(b []byte) (n int, err error) {
+ h.doRollover()
+ return h.fd.Write(b)
+}
+
+func (h *TimeRotatingFileHandler) Close() error {
+ return h.fd.Close()
+}
diff --git a/admin/lib/xlog/handler/handler_null.go b/admin/lib/xlog/handler/handler_null.go
new file mode 100644
index 0000000..c6c60ea
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_null.go
@@ -0,0 +1,17 @@
+package handler
+
+// NullHandler does nothing, it discards anything.
+type NullHandler struct {
+}
+
+func NewNullHandler() (*NullHandler, error) {
+ return new(NullHandler), nil
+}
+
+func (h *NullHandler) Write(b []byte) (n int, err error) {
+ return len(b), nil
+}
+
+func (h *NullHandler) Close() {
+
+}
diff --git a/admin/lib/xlog/handler/handler_socket.go b/admin/lib/xlog/handler/handler_socket.go
new file mode 100644
index 0000000..6dcd7d3
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_socket.go
@@ -0,0 +1,65 @@
+package handler
+
+import (
+ "encoding/binary"
+ "net"
+ "time"
+)
+
+// SocketHandler writes xlog to a connectionl.
+// Network protocol is simple: xlog length + xlog | xlog length + xlog. xlog length is uint32, bigendian.
+// you must implement your own xlog server, maybe you can use logd instead simply.
+type SocketHandler struct {
+ c net.Conn
+ protocol string
+ addr string
+}
+
+func NewSocketHandler(protocol string, addr string) (*SocketHandler, error) {
+ s := new(SocketHandler)
+
+ s.protocol = protocol
+ s.addr = addr
+
+ return s, nil
+}
+
+func (h *SocketHandler) Write(p []byte) (n int, err error) {
+ if err = h.connect(); err != nil {
+ return
+ }
+
+ buf := make([]byte, len(p)+4)
+
+ binary.BigEndian.PutUint32(buf, uint32(len(p)))
+
+ copy(buf[4:], p)
+
+ n, err = h.c.Write(buf)
+ if err != nil {
+ h.c.Close()
+ h.c = nil
+ }
+ return
+}
+
+func (h *SocketHandler) Close() error {
+ if h.c != nil {
+ h.c.Close()
+ }
+ return nil
+}
+
+func (h *SocketHandler) connect() error {
+ if h.c != nil {
+ return nil
+ }
+
+ var err error
+ h.c, err = net.DialTimeout(h.protocol, h.addr, 20*time.Second)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/admin/lib/xlog/handler/handler_stream.go b/admin/lib/xlog/handler/handler_stream.go
new file mode 100644
index 0000000..a44251a
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_stream.go
@@ -0,0 +1,28 @@
+package handler
+
+import "io"
+
+// StreamHandler writes logs to a specified io Writer, maybe stdout, stderr, etc...
+type StreamHandler struct {
+ w io.Writer
+}
+
+func NewStreamHandler(w io.Writer) (*StreamHandler, error) {
+ h := new(StreamHandler)
+
+ h.w = w
+
+ return h, nil
+}
+
+func (h *StreamHandler) Rotate() {
+
+}
+
+func (h *StreamHandler) Write(b []byte) (n int, err error) {
+ return h.w.Write(b)
+}
+
+func (h *StreamHandler) Close() error {
+ return nil
+}
diff --git a/admin/lib/xlog/handler/handler_test.go b/admin/lib/xlog/handler/handler_test.go
new file mode 100644
index 0000000..5ee6c7c
--- /dev/null
+++ b/admin/lib/xlog/handler/handler_test.go
@@ -0,0 +1,62 @@
+package handler
+
+import (
+ "strconv"
+ "sync"
+ "testing"
+ "time"
+)
+
+func testHandler(goroutineNo int) {
+ h, err := NewRotatingDayMaxFileHandler("test_log", "shop", 40960, 1000)
+ if err != nil {
+ panic(err)
+ }
+
+ wg := new(sync.WaitGroup)
+ wg.Add(3)
+ go func(no int) {
+ for i := 0; i < 10000; i++ {
+ h.Write([]byte("test content:" + strconv.Itoa(goroutineNo) + ":" + strconv.Itoa(no) + ":" + strconv.Itoa(i) + "\n"))
+ time.Sleep(time.Millisecond * 2)
+ }
+ wg.Done()
+ }(1)
+
+ go func(no int) {
+ for i := 0; i < 10000; i++ {
+ h.Write([]byte("test content:" + strconv.Itoa(goroutineNo) + ":" + strconv.Itoa(no) + ":" + strconv.Itoa(i) + "\n"))
+ time.Sleep(time.Millisecond * 2)
+ }
+ wg.Done()
+ }(2)
+
+ go func(no int) {
+ for i := 0; i < 10000; i++ {
+ h.Write([]byte("test content:" + strconv.Itoa(goroutineNo) + ":" + strconv.Itoa(no) + ":" + strconv.Itoa(i) + "\n"))
+ time.Sleep(time.Millisecond * 2)
+ }
+ wg.Done()
+ }(3)
+
+ wg.Wait()
+ h.Write([]byte("tail\n"))
+ h.Close()
+}
+
+func TestHandler(t *testing.T) {
+ wg := new(sync.WaitGroup)
+ num := 5
+ wg.Add(5)
+
+ // 并发创建几个协程,模拟多进程并发写归档
+ // grep "test content" test_log/*|sed 's/^.*test content:\(.*\):\(.*\):\(.*\)$/\3/g'|sort|uniq -c|awk -F' ' '{print $1}'|uniq -ct pull
+ for i := 0; i < num; i++ {
+ go func(no int) {
+ testHandler(no)
+ wg.Done()
+ }(i)
+ }
+
+ wg.Wait()
+}
diff --git a/admin/lib/xlog/handler/logsyscall/jlog_syscall_darwin.go b/admin/lib/xlog/handler/logsyscall/jlog_syscall_darwin.go
new file mode 100644
index 0000000..2dacb56
--- /dev/null
+++ b/admin/lib/xlog/handler/logsyscall/jlog_syscall_darwin.go
@@ -0,0 +1,14 @@
+//go:build darwin
+// +build darwin
+
+package logsyscall
+
+const (
+ LOCK_EX int = 2
+ LOCK_UN int = 8
+)
+
+// Flock
+func Flock(fd int, how int) (err error) {
+ return nil
+}
diff --git a/admin/lib/xlog/handler/logsyscall/jlog_syscall_linux.go b/admin/lib/xlog/handler/logsyscall/jlog_syscall_linux.go
new file mode 100644
index 0000000..478f7fa
--- /dev/null
+++ b/admin/lib/xlog/handler/logsyscall/jlog_syscall_linux.go
@@ -0,0 +1,16 @@
+//go:build linux
+// +build linux
+
+package logsyscall
+
+import "syscall"
+
+const (
+ LOCK_EX int = 2
+ LOCK_UN int = 8
+)
+
+// Flock
+func Flock(fd int, how int) (err error) {
+ return syscall.Flock(fd, how)
+}
diff --git a/admin/lib/xlog/handler/logsyscall/jlog_syscall_windows.go b/admin/lib/xlog/handler/logsyscall/jlog_syscall_windows.go
new file mode 100644
index 0000000..5e7cf78
--- /dev/null
+++ b/admin/lib/xlog/handler/logsyscall/jlog_syscall_windows.go
@@ -0,0 +1,14 @@
+//go:build windows
+// +build windows
+
+package logsyscall
+
+const (
+ LOCK_EX int = 2
+ LOCK_UN int = 8
+)
+
+// Flock
+func Flock(fd int, how int) (err error) {
+ return nil
+}
diff --git a/admin/lib/xlog/log.go b/admin/lib/xlog/log.go
new file mode 100644
index 0000000..3458a2e
--- /dev/null
+++ b/admin/lib/xlog/log.go
@@ -0,0 +1,255 @@
+package xlog
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "strconv"
+
+ "github.com/rs/zerolog"
+ "github.com/rs/zerolog/log"
+)
+
+type LogLevel = zerolog.Level
+type LogEvent = zerolog.Event
+
+var (
+ LogLevelTrace = zerolog.TraceLevel // 追踪日志,调试完毕需要删除
+ LogLevelDebug = zerolog.DebugLevel // 调试日志,可以放在代码中,线上出问题调成这个级别
+ LogLevelInfo = zerolog.InfoLevel // 正常关键逻辑记录信息,线上日常设置为这个级别
+ LogLevelNotice = zerolog.Level(99) // 系统关键节点时输出的留意日志
+ LogLevelWarn = zerolog.WarnLevel // 警告,某些逻辑出现意向不到的情况,输出告警,例如配置表错误、rpc错误
+ LogLevelError = zerolog.ErrorLevel // 错误,服务器重要组件出现意向不到的情况,输出错误,例如数据库、redis错误
+ LogLevelCriti = zerolog.Level(100) // 危急,用于需要开发注意的信息,例如崩溃但不影响服务器运行的栈日志,一般接上sms、im告警
+ LogLevelFatal = zerolog.FatalLevel // 致命,核心组建出问题,无法运行,输出告警,并以1的错误码退出
+ LogLevelPanic = zerolog.PanicLevel // 崩溃,核心组建出问题,无法运行,崩溃退出
+)
+
+var LogLevelStr2Enum = map[string]LogLevel{
+ "trace": LogLevelTrace,
+ "debug": LogLevelDebug,
+ "info": LogLevelInfo,
+ "notice": LogLevelNotice,
+ "warn": LogLevelWarn,
+ "error": LogLevelError,
+ "criti": LogLevelCriti,
+ "fatal": LogLevelFatal,
+ "panic": LogLevelPanic,
+}
+
+func init() {
+ // 设置时间格式
+ zerolog.TimeFieldFormat = "06/01/02 15:04:05.000"
+ // 修改level字段key,防止跟调用方的key一样
+ zerolog.LevelFieldName = "log_level"
+ // 修改时间key
+ zerolog.TimestampFieldName = "log_time"
+ // 设置调用文件名路径深度
+ zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
+ depth := 1
+ for i := len(file) - 1; i > 0; i-- {
+ if file[i] == '/' {
+ if depth == FileReversedDepth {
+ file = file[i+1:]
+ break
+ }
+ depth++
+ }
+ }
+ return file + ":" + strconv.Itoa(line)
+ }
+ // 设置全局日志等级
+ zerolog.SetGlobalLevel(LogLevelTrace)
+}
+
+// 文件路径保留深度
+var FileReversedDepth = 3
+
+func NewGlobalLogger(writers []io.Writer, level LogLevel, initFun func(logger zerolog.Logger) zerolog.Logger) {
+ // 设置全局日志等级
+ zerolog.SetGlobalLevel(level)
+ var parentLogger zerolog.Logger
+
+ if len(writers) == 0 {
+ fmt.Fprintf(os.Stderr, "NewGlobalLogger but write is nil, default give os.Stdout\n")
+ writers = append(writers, os.Stdout)
+ }
+
+ multi := zerolog.MultiLevelWriter(writers...)
+
+ // 创建全局日志
+ parentLogger = zerolog.New(multi).With().Logger()
+
+ if initFun != nil {
+ log.Logger = initFun(parentLogger)
+ } else {
+ log.Logger = parentLogger
+ }
+
+ log.Logger = log.Hook(new(PrefixHook))
+ globalWriter = multi
+}
+
+func NewCustomLogger(writer Handler, initFun func(logger *zerolog.Logger) *zerolog.Logger) *zerolog.Logger {
+ if writer == nil {
+ fmt.Fprintf(os.Stderr, "NewGlobalLogger but write is nil, default give os.Stdout\n")
+ writer = os.Stdout
+ }
+ parentLogger := zerolog.New(writer).With().Logger()
+ initFun(&parentLogger)
+ return &parentLogger
+}
+
+var globalWriter io.Writer
+
+func GetGlobalWriter() io.Writer {
+ if globalWriter == nil {
+ return os.Stdout
+ }
+ return globalWriter
+}
+
+// GetSubLogger 获取全局logger的子logger,可以设置子logger的输出格式
+func GetSubLogger() zerolog.Context {
+ return log.Logger.With()
+}
+
+func GetLogLevel() LogLevel {
+ return zerolog.GlobalLevel()
+}
+
+func SetLogLevel(level LogLevel) {
+ zerolog.SetGlobalLevel(level)
+}
+
+func ParseLogLevelString(level string) LogLevel {
+ lvEnum, find := LogLevelStr2Enum[level]
+ if !find {
+ return LogLevelDebug
+ }
+ return lvEnum
+}
+
+// transFormat
+func transFormat(v ...interface{}) (string, []interface{}) {
+ if len(v) == 0 {
+ return "empty content", v
+ } else {
+ formatStr, ok := v[0].(string)
+ if ok {
+ return formatStr, v[1:]
+ }
+ formatStr = fmt.Sprint(v...)
+ return formatStr, []interface{}{}
+ }
+}
+
+func Tracef(v ...interface{}) {
+ format, v := transFormat(v...)
+ output(LogLevelTrace, format, v...)
+}
+
+func Debugf(v ...interface{}) {
+ format, v := transFormat(v...)
+ output(LogLevelDebug, format, v...)
+}
+
+func Infof(v ...interface{}) {
+ format, v := transFormat(v...)
+ output(LogLevelInfo, format, v...)
+}
+
+func Noticef(v ...interface{}) {
+ format, v := transFormat(v...)
+ output(LogLevelNotice, format, v...)
+}
+
+func Warnf(v ...interface{}) {
+ format, v := transFormat(v...)
+ output(LogLevelWarn, format, v...)
+}
+
+func Errorf(v ...interface{}) {
+ format, v := transFormat(v...)
+ output(LogLevelError, format, v...)
+}
+
+func Critif(v ...interface{}) {
+ format, v := transFormat(v...)
+ output(LogLevelCriti, format, v...)
+}
+
+func Fatalf(v ...interface{}) {
+ format, v := transFormat(v...)
+ output(LogLevelFatal, format, v...)
+}
+
+func WithLevelEvent(level LogLevel) *LogEvent {
+ e := log.WithLevel(level)
+ return e
+}
+
+func WithEventMsgF(e *LogEvent, v ...interface{}) {
+ format, v := transFormat(v...)
+ e.Timestamp().Caller(2).Msgf(format, v...)
+}
+
+func output(level LogLevel, format string, v ...interface{}) {
+ var e *zerolog.Event
+ switch level {
+ case LogLevelTrace:
+ e = log.Trace()
+ case LogLevelDebug:
+ e = log.Debug()
+ case LogLevelInfo:
+ e = log.Info()
+ case LogLevelNotice:
+ e = log.WithLevel(zerolog.NoLevel)
+ e.Str("log_level", "notice")
+ case LogLevelWarn:
+ e = log.Warn()
+ case LogLevelError:
+ e = log.Error()
+ case LogLevelCriti:
+ e = log.WithLevel(zerolog.NoLevel)
+ e.Str("log_level", "criti")
+ case LogLevelFatal:
+ e = log.Fatal()
+ case LogLevelPanic:
+ e = log.Panic()
+ default:
+ return
+ }
+
+ e.Timestamp().Caller(2).Msgf(format, v...)
+}
+
+func Output(level LogLevel) *zerolog.Event {
+ var e *zerolog.Event
+ switch level {
+ case LogLevelTrace:
+ e = log.Trace()
+ case LogLevelDebug:
+ e = log.Debug()
+ case LogLevelInfo:
+ e = log.Info()
+ case LogLevelNotice:
+ e = log.WithLevel(zerolog.NoLevel)
+ e.Str("log_level", "notice")
+ case LogLevelWarn:
+ e = log.Warn()
+ case LogLevelError:
+ e = log.Error()
+ case LogLevelCriti:
+ e = log.WithLevel(zerolog.NoLevel)
+ e.Str("log_level", "criti")
+ case LogLevelFatal:
+ e = log.Fatal()
+ case LogLevelPanic:
+ e = log.Panic()
+ default:
+ return nil
+ }
+
+ return e
+}
diff --git a/admin/lib/xlog/log_hook.go b/admin/lib/xlog/log_hook.go
new file mode 100644
index 0000000..8ce4501
--- /dev/null
+++ b/admin/lib/xlog/log_hook.go
@@ -0,0 +1,15 @@
+package xlog
+
+import (
+ "github.com/rs/zerolog"
+)
+
+type PrefixHook struct{}
+
+func (h *PrefixHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
+ //e.Str("log_level", level.String())
+ return
+ if level != zerolog.NoLevel {
+ e.Str("severity", level.String())
+ }
+}
diff --git a/admin/lib/xos/signal.go b/admin/lib/xos/signal.go
new file mode 100644
index 0000000..0fb05d9
--- /dev/null
+++ b/admin/lib/xos/signal.go
@@ -0,0 +1,30 @@
+package xos
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+)
+
+func WatchSignal(notify func(signal2 os.Signal)) {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
+ for {
+ s := <-c
+ switch s {
+ case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL:
+ notify(s)
+ return
+ case syscall.SIGHUP:
+ // TODO reload
+ default:
+ return
+ }
+ }
+}
+
+func WatchSignal1() chan os.Signal {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
+ return c
+}
diff --git a/ui/.env.development b/ui/.env.development
new file mode 100644
index 0000000..ff54252
--- /dev/null
+++ b/ui/.env.development
@@ -0,0 +1,2 @@
+VITE_APP_BASE_API = '/api'
+VITE_APP_BASE_URL = 'http://192.168.78.128:8080/api'
\ No newline at end of file
diff --git a/ui/.gitignore b/ui/.gitignore
new file mode 100644
index 0000000..8ee54e8
--- /dev/null
+++ b/ui/.gitignore
@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo
diff --git a/ui/.vscode/extensions.json b/ui/.vscode/extensions.json
new file mode 100644
index 0000000..a7cea0b
--- /dev/null
+++ b/ui/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["Vue.volar"]
+}
diff --git a/ui/README.md b/ui/README.md
new file mode 100644
index 0000000..82d7ccd
--- /dev/null
+++ b/ui/README.md
@@ -0,0 +1,29 @@
+# ui
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vite.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Compile and Minify for Production
+
+```sh
+npm run build
+```
diff --git a/ui/index.html b/ui/index.html
new file mode 100644
index 0000000..b19040a
--- /dev/null
+++ b/ui/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite App
+
+
+
+
+
+
diff --git a/ui/jsconfig.json b/ui/jsconfig.json
new file mode 100644
index 0000000..5a1f2d2
--- /dev/null
+++ b/ui/jsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/ui/package-lock.json b/ui/package-lock.json
new file mode 100644
index 0000000..2ade7bd
--- /dev/null
+++ b/ui/package-lock.json
@@ -0,0 +1,3862 @@
+{
+ "name": "ui",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ui",
+ "version": "0.0.0",
+ "dependencies": {
+ "axios": "^1.8.4",
+ "element-plus": "^2.9.7",
+ "pinia": "^3.0.1",
+ "vue": "^3.5.13",
+ "vue-router": "^4.5.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.3",
+ "unplugin-auto-import": "^19.1.2",
+ "unplugin-vue-components": "^28.5.0",
+ "vite": "^6.2.4",
+ "vite-plugin-vue-devtools": "^7.7.2"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@antfu/utils": {
+ "version": "0.7.10",
+ "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz",
+ "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.26.8",
+ "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.26.8.tgz",
+ "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.26.10",
+ "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.26.10.tgz",
+ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.26.10",
+ "@babel/helper-compilation-targets": "^7.26.5",
+ "@babel/helper-module-transforms": "^7.26.0",
+ "@babel/helpers": "^7.26.10",
+ "@babel/parser": "^7.26.10",
+ "@babel/template": "^7.26.9",
+ "@babel/traverse": "^7.26.10",
+ "@babel/types": "^7.26.10",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.27.0.tgz",
+ "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz",
+ "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz",
+ "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.26.8",
+ "@babel/helper-validator-option": "^7.25.9",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz",
+ "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-member-expression-to-functions": "^7.25.9",
+ "@babel/helper-optimise-call-expression": "^7.25.9",
+ "@babel/helper-replace-supers": "^7.26.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
+ "@babel/traverse": "^7.27.0",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz",
+ "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
+ "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz",
+ "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.26.5",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
+ "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.26.5",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz",
+ "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.25.9",
+ "@babel/helper-optimise-call-expression": "^7.25.9",
+ "@babel/traverse": "^7.26.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz",
+ "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
+ "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.0.tgz",
+ "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.0.tgz",
+ "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-decorators": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz",
+ "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.25.9",
+ "@babel/plugin-syntax-decorators": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-decorators": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz",
+ "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz",
+ "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz",
+ "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz",
+ "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typescript": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz",
+ "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.25.9",
+ "@babel/helper-create-class-features-plugin": "^7.27.0",
+ "@babel/helper-plugin-utils": "^7.26.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
+ "@babel/plugin-syntax-typescript": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.0.tgz",
+ "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.27.0.tgz",
+ "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.27.0",
+ "@babel/parser": "^7.27.0",
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.0.tgz",
+ "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@ctrl/tinycolor": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+ "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@element-plus/icons-vue": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
+ "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
+ "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
+ "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
+ "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
+ "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
+ "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
+ "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
+ "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
+ "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
+ "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
+ "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
+ "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
+ "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
+ "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
+ "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
+ "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
+ "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
+ "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
+ "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
+ "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
+ "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
+ "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
+ "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
+ "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
+ "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
+ "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.6.9",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.9.tgz",
+ "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.6.13",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.13.tgz",
+ "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.6.0",
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.9.tgz",
+ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@popperjs/core": {
+ "name": "@sxzz/popperjs-es",
+ "version": "2.11.7",
+ "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+ "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
+ "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz",
+ "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz",
+ "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz",
+ "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz",
+ "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz",
+ "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz",
+ "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz",
+ "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz",
+ "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz",
+ "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz",
+ "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz",
+ "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz",
+ "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz",
+ "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz",
+ "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz",
+ "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz",
+ "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz",
+ "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz",
+ "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz",
+ "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz",
+ "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sec-ant/readable-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmmirror.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.17.16",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.16.tgz",
+ "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash-es": {
+ "version": "4.17.12",
+ "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+ "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.16",
+ "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+ "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz",
+ "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/babel-helper-vue-transform-on": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz",
+ "integrity": "sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vue/babel-plugin-jsx": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.4.0.tgz",
+ "integrity": "sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.26.5",
+ "@babel/plugin-syntax-jsx": "^7.25.9",
+ "@babel/template": "^7.26.9",
+ "@babel/traverse": "^7.26.9",
+ "@babel/types": "^7.26.9",
+ "@vue/babel-helper-vue-transform-on": "1.4.0",
+ "@vue/babel-plugin-resolve-type": "1.4.0",
+ "@vue/shared": "^3.5.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/babel-plugin-resolve-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.4.0.tgz",
+ "integrity": "sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-plugin-utils": "^7.26.5",
+ "@babel/parser": "^7.26.9",
+ "@vue/compiler-sfc": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+ "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/shared": "3.5.13",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+ "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
+ "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/compiler-core": "3.5.13",
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.11",
+ "postcss": "^8.4.48",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
+ "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "7.7.5",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.5.tgz",
+ "integrity": "sha512-HYV3tJGARROq5nlVMJh5KKHk7GU8Au3IrrmNNqr978m0edxgpHgYPDoNUGrvEgIbObz09SQezFR3A1EVmB5WZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.5"
+ }
+ },
+ "node_modules/@vue/devtools-core": {
+ "version": "7.7.5",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-core/-/devtools-core-7.7.5.tgz",
+ "integrity": "sha512-ElKr0NDor57gVaT+gMQ8kcVP4uFGqHcxuuQndW/rPwh6aHWvEcUL3sxL8cEk+e1Rdt28kS88erpsiIMO6hEENQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.5",
+ "@vue/devtools-shared": "^7.7.5",
+ "mitt": "^3.0.1",
+ "nanoid": "^5.1.0",
+ "pathe": "^2.0.3",
+ "vite-hot-client": "^2.0.4"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
+ "node_modules/@vue/devtools-core/node_modules/nanoid": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.5.tgz",
+ "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.5",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.5.tgz",
+ "integrity": "sha512-S9VAVJYVAe4RPx2JZb9ZTEi0lqTySz2CBeF0wHT5D3dkTLnT9yMMGegKNl4b2EIELwLSkcI9bl2qp0/jW+upqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.5",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.5",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.5.tgz",
+ "integrity": "sha512-QBjG72RfpM0DKtpns2RZOxBltO226kOAls9e4Lri6YxS2gWTgL0H+wj1R2K76lxxIeOrqo4+2Ty6RQnzv+WSTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz",
+ "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
+ "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
+ "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.13",
+ "@vue/runtime-core": "3.5.13",
+ "@vue/shared": "3.5.13",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
+ "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13"
+ },
+ "peerDependencies": {
+ "vue": "3.5.13"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
+ "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
+ "license": "MIT"
+ },
+ "node_modules/@vueuse/core": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+ "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.16",
+ "@vueuse/metadata": "9.13.0",
+ "@vueuse/shared": "9.13.0",
+ "vue-demi": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/core/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+ "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+ "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+ "license": "MIT",
+ "dependencies": {
+ "vue-demi": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/async-validator": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+ "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.8.4",
+ "resolved": "https://registry.npmmirror.com/axios/-/axios-1.8.4.tgz",
+ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/birpc": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.3.0.tgz",
+ "integrity": "sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bundle-name": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz",
+ "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "run-applescript": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001714",
+ "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001714.tgz",
+ "integrity": "sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/confbox": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz",
+ "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/copy-anything": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz",
+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/default-browser": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.2.1.tgz",
+ "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bundle-name": "^4.1.0",
+ "default-browser-id": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser-id": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.0.tgz",
+ "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.137",
+ "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.137.tgz",
+ "integrity": "sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/element-plus": {
+ "version": "2.9.7",
+ "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.7.tgz",
+ "integrity": "sha512-6vjZh5SXBncLhUwJGTVKS5oDljfgGMh6J4zVTeAZK3YdMUN76FgpvHkwwFXocpJpMbii6rDYU3sgie64FyPerQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ctrl/tinycolor": "^3.4.1",
+ "@element-plus/icons-vue": "^2.3.1",
+ "@floating-ui/dom": "^1.0.1",
+ "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+ "@types/lodash": "^4.14.182",
+ "@types/lodash-es": "^4.17.6",
+ "@vueuse/core": "^9.1.0",
+ "async-validator": "^4.2.5",
+ "dayjs": "^1.11.13",
+ "escape-html": "^1.0.3",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "lodash-unified": "^1.0.2",
+ "memoize-one": "^6.0.0",
+ "normalize-wheel-es": "^1.2.0"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/error-stack-parser-es": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmmirror.com/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz",
+ "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.2",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.2.tgz",
+ "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.2",
+ "@esbuild/android-arm": "0.25.2",
+ "@esbuild/android-arm64": "0.25.2",
+ "@esbuild/android-x64": "0.25.2",
+ "@esbuild/darwin-arm64": "0.25.2",
+ "@esbuild/darwin-x64": "0.25.2",
+ "@esbuild/freebsd-arm64": "0.25.2",
+ "@esbuild/freebsd-x64": "0.25.2",
+ "@esbuild/linux-arm": "0.25.2",
+ "@esbuild/linux-arm64": "0.25.2",
+ "@esbuild/linux-ia32": "0.25.2",
+ "@esbuild/linux-loong64": "0.25.2",
+ "@esbuild/linux-mips64el": "0.25.2",
+ "@esbuild/linux-ppc64": "0.25.2",
+ "@esbuild/linux-riscv64": "0.25.2",
+ "@esbuild/linux-s390x": "0.25.2",
+ "@esbuild/linux-x64": "0.25.2",
+ "@esbuild/netbsd-arm64": "0.25.2",
+ "@esbuild/netbsd-x64": "0.25.2",
+ "@esbuild/openbsd-arm64": "0.25.2",
+ "@esbuild/openbsd-x64": "0.25.2",
+ "@esbuild/sunos-x64": "0.25.2",
+ "@esbuild/win32-arm64": "0.25.2",
+ "@esbuild/win32-ia32": "0.25.2",
+ "@esbuild/win32-x64": "0.25.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/execa": {
+ "version": "9.5.2",
+ "resolved": "https://registry.npmmirror.com/execa/-/execa-9.5.2.tgz",
+ "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^4.0.0",
+ "cross-spawn": "^7.0.3",
+ "figures": "^6.1.0",
+ "get-stream": "^9.0.0",
+ "human-signals": "^8.0.0",
+ "is-plain-obj": "^4.1.0",
+ "is-stream": "^4.0.1",
+ "npm-run-path": "^6.0.0",
+ "pretty-ms": "^9.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^4.0.0",
+ "yoctocolors": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.4.tgz",
+ "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.4.3",
+ "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.3.tgz",
+ "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/figures": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmmirror.com/figures/-/figures-6.1.0.tgz",
+ "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-unicode-supported": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.0",
+ "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.0.tgz",
+ "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-9.0.1.tgz",
+ "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sec-ant/readable-stream": "^0.4.1",
+ "is-stream": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "license": "MIT"
+ },
+ "node_modules/human-signals": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-8.0.1.tgz",
+ "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-4.0.1.tgz",
+ "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.0.tgz",
+ "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-inside-container": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/kolorist": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz",
+ "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/local-pkg": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz",
+ "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.4",
+ "pkg-types": "^2.0.1",
+ "quansync": "^0.2.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-unified": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+ "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/lodash-es": "*",
+ "lodash": "*",
+ "lodash-es": "*"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+ "license": "MIT"
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
+ "node_modules/mlly": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.7.4.tgz",
+ "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "pathe": "^2.0.1",
+ "pkg-types": "^1.3.0",
+ "ufo": "^1.5.4"
+ }
+ },
+ "node_modules/mlly/node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mlly/node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-wheel-es": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+ "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/npm-run-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-6.0.0.tgz",
+ "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0",
+ "unicorn-magic": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/open": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmmirror.com/open/-/open-10.1.1.tgz",
+ "integrity": "sha512-zy1wx4+P3PfhXSEPJNtZmJXfhkkIaxU1VauWIrDZw1O7uJRDRJtKr9n3Ic4NgbA16KyOxOXO2ng9gYwCdXuSXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "default-browser": "^5.2.1",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "is-wsl": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-ms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz",
+ "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.2.tgz",
+ "integrity": "sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^7.7.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.1.0.tgz",
+ "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.1",
+ "exsolve": "^1.0.1",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/pretty-ms": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmmirror.com/pretty-ms/-/pretty-ms-9.2.0.tgz",
+ "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parse-ms": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/quansync": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz",
+ "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/antfu"
+ },
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/readdirp/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "license": "MIT"
+ },
+ "node_modules/rollup": {
+ "version": "4.40.0",
+ "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.40.0.tgz",
+ "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.7"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.40.0",
+ "@rollup/rollup-android-arm64": "4.40.0",
+ "@rollup/rollup-darwin-arm64": "4.40.0",
+ "@rollup/rollup-darwin-x64": "4.40.0",
+ "@rollup/rollup-freebsd-arm64": "4.40.0",
+ "@rollup/rollup-freebsd-x64": "4.40.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.40.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.40.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.40.0",
+ "@rollup/rollup-linux-arm64-musl": "4.40.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.40.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.40.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.40.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.40.0",
+ "@rollup/rollup-linux-x64-gnu": "4.40.0",
+ "@rollup/rollup-linux-x64-musl": "4.40.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.40.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.40.0",
+ "@rollup/rollup-win32-x64-msvc": "4.40.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-applescript": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.0.0.tgz",
+ "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/scule": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
+ "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sirv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/sirv/-/sirv-3.0.1.tgz",
+ "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/speakingurl": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz",
+ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
+ "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.0.0.tgz",
+ "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/superjson": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz",
+ "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "copy-anything": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.12.tgz",
+ "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.3",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz",
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unicorn-magic": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmmirror.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/unimport": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/unimport/-/unimport-4.2.0.tgz",
+ "integrity": "sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.1",
+ "escape-string-regexp": "^5.0.0",
+ "estree-walker": "^3.0.3",
+ "local-pkg": "^1.1.1",
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "pkg-types": "^2.1.0",
+ "scule": "^1.3.0",
+ "strip-literal": "^3.0.0",
+ "tinyglobby": "^0.2.12",
+ "unplugin": "^2.2.2",
+ "unplugin-utils": "^0.2.4"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/unimport/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unplugin": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.2.tgz",
+ "integrity": "sha512-3n7YA46rROb3zSj8fFxtxC/PqoyvYQ0llwz9wtUPUutr9ig09C8gGo5CWCwHrUzlqC1LLR43kxp5vEIyH1ac1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.1",
+ "picomatch": "^4.0.2",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/unplugin-auto-import": {
+ "version": "19.1.2",
+ "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-19.1.2.tgz",
+ "integrity": "sha512-EkxNIJm4ZPYtV7rRaPBKnsscgTaifIZNrJF5DkMffTxkUOJOlJuKVypA6YBSBOjzPJDTFPjfVmCQPoBuOO+YYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "local-pkg": "^1.1.1",
+ "magic-string": "^0.30.17",
+ "picomatch": "^4.0.2",
+ "unimport": "^4.1.2",
+ "unplugin": "^2.2.2",
+ "unplugin-utils": "^0.2.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": "^3.2.2",
+ "@vueuse/core": "*"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "@vueuse/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unplugin-utils": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.2.4.tgz",
+ "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pathe": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ },
+ "node_modules/unplugin-vue-components": {
+ "version": "28.5.0",
+ "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-28.5.0.tgz",
+ "integrity": "sha512-o7fMKU/uI8NiP+E0W62zoduuguWqB0obTfHFtbr1AP2uo2lhUPnPttWUE92yesdiYfo9/0hxIrj38FMc1eaySg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.6.0",
+ "debug": "^4.4.0",
+ "local-pkg": "^1.1.1",
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "tinyglobby": "^0.2.12",
+ "unplugin": "^2.3.2",
+ "unplugin-utils": "^0.2.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@babel/parser": "^7.15.8",
+ "@nuxt/kit": "^3.2.2",
+ "vue": "2 || 3"
+ },
+ "peerDependenciesMeta": {
+ "@babel/parser": {
+ "optional": true
+ },
+ "@nuxt/kit": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.0.tgz",
+ "integrity": "sha512-9aC0n4pr6hIbvi1YOpFjwQ+QOTGssvbJKoeYkuHHGWwlXfdxQlI8L2qNMo9awEEcCPSiS+5mJZk5jH1PAqoDeQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.3",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.12"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-hot-client": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmmirror.com/vite-hot-client/-/vite-hot-client-2.0.4.tgz",
+ "integrity": "sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0"
+ }
+ },
+ "node_modules/vite-plugin-inspect": {
+ "version": "0.8.9",
+ "resolved": "https://registry.npmmirror.com/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz",
+ "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/utils": "^0.7.10",
+ "@rollup/pluginutils": "^5.1.3",
+ "debug": "^4.3.7",
+ "error-stack-parser-es": "^0.1.5",
+ "fs-extra": "^11.2.0",
+ "open": "^10.1.0",
+ "perfect-debounce": "^1.0.0",
+ "picocolors": "^1.1.1",
+ "sirv": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-vue-devtools": {
+ "version": "7.7.5",
+ "resolved": "https://registry.npmmirror.com/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.5.tgz",
+ "integrity": "sha512-cSlQYI1E+8d0qubBg70suTBbXMFbTHLn7vLPYUPK9GjNNJ0nw+Yks0ZLOAp7/+PjmqSpN5fK1taor6HeAjKb1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-core": "^7.7.5",
+ "@vue/devtools-kit": "^7.7.5",
+ "@vue/devtools-shared": "^7.7.5",
+ "execa": "^9.5.2",
+ "sirv": "^3.0.1",
+ "vite-plugin-inspect": "0.8.9",
+ "vite-plugin-vue-inspector": "^5.3.1"
+ },
+ "engines": {
+ "node": ">=v14.21.3"
+ },
+ "peerDependencies": {
+ "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0"
+ }
+ },
+ "node_modules/vite-plugin-vue-inspector": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmmirror.com/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz",
+ "integrity": "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.23.0",
+ "@babel/plugin-proposal-decorators": "^7.23.0",
+ "@babel/plugin-syntax-import-attributes": "^7.22.5",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-transform-typescript": "^7.22.15",
+ "@vue/babel-plugin-jsx": "^1.1.5",
+ "@vue/compiler-dom": "^3.3.4",
+ "kolorist": "^1.8.0",
+ "magic-string": "^0.30.4"
+ },
+ "peerDependencies": {
+ "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0"
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
+ "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-sfc": "3.5.13",
+ "@vue/runtime-dom": "3.5.13",
+ "@vue/server-renderer": "3.5.13",
+ "@vue/shared": "3.5.13"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.0.tgz",
+ "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/vue-router/node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+ "license": "MIT"
+ },
+ "node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yoctocolors": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/yoctocolors/-/yoctocolors-2.1.1.tgz",
+ "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/ui/package.json b/ui/package.json
new file mode 100644
index 0000000..6e86fd3
--- /dev/null
+++ b/ui/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "ui",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --host 0.0.0.0",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "axios": "^1.8.4",
+ "element-plus": "^2.9.7",
+ "pinia": "^3.0.1",
+ "vue": "^3.5.13",
+ "vue-router": "^4.5.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.3",
+ "unplugin-auto-import": "^19.1.2",
+ "unplugin-vue-components": "^28.5.0",
+ "vite": "^6.2.4",
+ "vite-plugin-vue-devtools": "^7.7.2"
+ }
+}
diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico
new file mode 100644
index 0000000..df36fcf
Binary files /dev/null and b/ui/public/favicon.ico differ
diff --git a/ui/src/App.vue b/ui/src/App.vue
new file mode 100644
index 0000000..194c9c9
--- /dev/null
+++ b/ui/src/App.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/ui/src/api/project/project.js b/ui/src/api/project/project.js
new file mode 100644
index 0000000..09061f1
--- /dev/null
+++ b/ui/src/api/project/project.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function getProjectList(params) {
+ return request({
+ url: '/project',
+ method: 'get',
+ params: params
+ })
+}
\ No newline at end of file
diff --git a/ui/src/assets/base.css b/ui/src/assets/base.css
new file mode 100644
index 0000000..8816868
--- /dev/null
+++ b/ui/src/assets/base.css
@@ -0,0 +1,86 @@
+/* color palette from */
+:root {
+ --vt-c-white: #ffffff;
+ --vt-c-white-soft: #f8f8f8;
+ --vt-c-white-mute: #f2f2f2;
+
+ --vt-c-black: #181818;
+ --vt-c-black-soft: #222222;
+ --vt-c-black-mute: #282828;
+
+ --vt-c-indigo: #2c3e50;
+
+ --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
+ --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
+ --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
+ --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
+
+ --vt-c-text-light-1: var(--vt-c-indigo);
+ --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
+ --vt-c-text-dark-1: var(--vt-c-white);
+ --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
+}
+
+/* semantic color variables for this project */
+:root {
+ --color-background: var(--vt-c-white);
+ --color-background-soft: var(--vt-c-white-soft);
+ --color-background-mute: var(--vt-c-white-mute);
+
+ --color-border: var(--vt-c-divider-light-2);
+ --color-border-hover: var(--vt-c-divider-light-1);
+
+ --color-heading: var(--vt-c-text-light-1);
+ --color-text: var(--vt-c-text-light-1);
+
+ --section-gap: 160px;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-background: var(--vt-c-black);
+ --color-background-soft: var(--vt-c-black-soft);
+ --color-background-mute: var(--vt-c-black-mute);
+
+ --color-border: var(--vt-c-divider-dark-2);
+ --color-border-hover: var(--vt-c-divider-dark-1);
+
+ --color-heading: var(--vt-c-text-dark-1);
+ --color-text: var(--vt-c-text-dark-2);
+ }
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ margin: 0;
+ font-weight: normal;
+}
+
+body {
+ min-height: 100vh;
+ color: var(--color-text);
+ background: var(--color-background);
+ transition:
+ color 0.5s,
+ background-color 0.5s;
+ line-height: 1.6;
+ font-family:
+ Inter,
+ -apple-system,
+ BlinkMacSystemFont,
+ 'Segoe UI',
+ Roboto,
+ Oxygen,
+ Ubuntu,
+ Cantarell,
+ 'Fira Sans',
+ 'Droid Sans',
+ 'Helvetica Neue',
+ sans-serif;
+ font-size: 15px;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
diff --git a/ui/src/assets/icon/header.png b/ui/src/assets/icon/header.png
new file mode 100644
index 0000000..7d9e717
Binary files /dev/null and b/ui/src/assets/icon/header.png differ
diff --git a/ui/src/assets/logo.svg b/ui/src/assets/logo.svg
new file mode 100644
index 0000000..7565660
--- /dev/null
+++ b/ui/src/assets/logo.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/assets/main.css b/ui/src/assets/main.css
new file mode 100644
index 0000000..36fb845
--- /dev/null
+++ b/ui/src/assets/main.css
@@ -0,0 +1,35 @@
+@import './base.css';
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ font-weight: normal;
+}
+
+a,
+.green {
+ text-decoration: none;
+ color: hsla(160, 100%, 37%, 1);
+ transition: 0.4s;
+ padding: 3px;
+}
+
+@media (hover: hover) {
+ a:hover {
+ background-color: hsla(160, 100%, 37%, 0.2);
+ }
+}
+
+@media (min-width: 1024px) {
+ body {
+ display: flex;
+ place-items: center;
+ }
+
+ #app {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ padding: 0 2rem;
+ }
+}
diff --git a/ui/src/components/restful/table.vue b/ui/src/components/restful/table.vue
new file mode 100644
index 0000000..9d662bd
--- /dev/null
+++ b/ui/src/components/restful/table.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ 添加
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/src/main.js b/ui/src/main.js
new file mode 100644
index 0000000..f38afea
--- /dev/null
+++ b/ui/src/main.js
@@ -0,0 +1,16 @@
+import './assets/main.css'
+
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import App from './App.vue'
+import router from './router'
+
+const app = createApp(App)
+
+app.use(ElementPlus)
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')
diff --git a/ui/src/router/index.js b/ui/src/router/index.js
new file mode 100644
index 0000000..6eac7d3
--- /dev/null
+++ b/ui/src/router/index.js
@@ -0,0 +1,27 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import Home from '../views/Home.vue'
+
+const router = createRouter({
+ history: createWebHistory(import.meta.env.BASE_URL),
+ routes: [
+ {
+ path: '/',
+ name: 'home',
+ component: Home,
+ children: [
+ {
+ path: '/user',
+ name: 'user',
+ component: import('@/views/user/user.vue')
+ },
+ {
+ path: '/project',
+ name: 'project',
+ component: import('@/views/project/project.vue')
+ }
+ ]
+ },
+ ],
+})
+
+export default router
diff --git a/ui/src/stores/counter.js b/ui/src/stores/counter.js
new file mode 100644
index 0000000..b6757ba
--- /dev/null
+++ b/ui/src/stores/counter.js
@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+ const count = ref(0)
+ const doubleCount = computed(() => count.value * 2)
+ function increment() {
+ count.value++
+ }
+
+ return { count, doubleCount, increment }
+})
diff --git a/ui/src/utils/request.js b/ui/src/utils/request.js
new file mode 100644
index 0000000..fc500bb
--- /dev/null
+++ b/ui/src/utils/request.js
@@ -0,0 +1,68 @@
+import axios from 'axios'
+import {ElMessageBox} from "element-plus";
+// import LocalCache from "@/stores/localCache";
+import {useRouter} from "vue-router";
+// import ExpireCache from "@/stores/expireCache";
+
+// 创建axios实例
+const service=axios.create({
+ baseURL: import.meta.env.VITE_APP_BASE_API,
+ timeout: 10000,
+ headers: {
+ "Content-type": "application/json;charset=utf-8",
+ "Cache-Control": 'no-cache',
+ }
+})
+
+// axios.post('192.168.1.60:8888/login', {username: "likun", password: "likun1"}).then((res)=>{
+// console.log(res)
+// }).catch((err)=>{
+// console.log('login error')
+// console.log(err)
+// })
+
+const reqInterceptor = (config)=>{
+ config.headers=config.headers || {}
+ // const token = ExpireCache.getCache('token')
+ // if(token){
+ // config.headers["x-token"]=token || ""
+ // } else {
+ // console.log("not found local storage token")
+ // }
+ return config
+}
+
+const resInterceptor = (res)=>{
+ console.log("res:", res.data)
+ const code=res.data.code
+ if(code!=200) {
+ console.log("interceptor err code", res)
+ ElMessageBox.alert("请求服务器成功,但是逻辑错误:" + res.data.message, "服务器错误码" + code, {
+ type: "warning",
+ confirmButtonText: '知道了',
+ })
+ return Promise.reject(res.data)
+ }
+
+ return res.data
+}
+
+const resErrorInterceptor = (err)=>{
+ console.log(err)
+ const code = err.response && err.response.status || -1
+ const message = err.response && err.response.data.message || err
+ ElMessageBox.alert(message, "请求服务器返回http错误码-" + code, {
+ type: "error",
+ confirmButtonText: '知道了',
+ })
+ return Promise.reject(err)
+}
+
+// 请求拦截
+service.interceptors.request.use(reqInterceptor)
+// 响应拦截
+service.interceptors.response.use(resInterceptor, resErrorInterceptor)
+
+
+export default service
+
diff --git a/ui/src/views/Home.vue b/ui/src/views/Home.vue
new file mode 100644
index 0000000..3adecc4
--- /dev/null
+++ b/ui/src/views/Home.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+ 用户管理
+
+
+ 项目管理
+
+
+
+
+ Header
+
+
+
+
+
+
diff --git a/ui/src/views/project/project.vue b/ui/src/views/project/project.vue
new file mode 100644
index 0000000..b3317f8
--- /dev/null
+++ b/ui/src/views/project/project.vue
@@ -0,0 +1,30 @@
+
+
+
+ 项目
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/src/views/user/user.vue b/ui/src/views/user/user.vue
new file mode 100644
index 0000000..9415308
--- /dev/null
+++ b/ui/src/views/user/user.vue
@@ -0,0 +1,11 @@
+
+
+
+用户管理
+
+
+
\ No newline at end of file
diff --git a/ui/vite.config.js b/ui/vite.config.js
new file mode 100644
index 0000000..db5dca4
--- /dev/null
+++ b/ui/vite.config.js
@@ -0,0 +1,58 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import vueDevTools from 'vite-plugin-vue-devtools'
+
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+import { loadEnv } from 'vite'
+
+// https://vite.dev/config/
+export default defineConfig(({ mode, command }) => {
+ const env = loadEnv(mode, process.cwd())
+ const { VITE_APP_ENV, VITE_APP_BASE_URL,VITE_APP_BASE_URL_WS} = env
+ return {
+ plugins: [
+ vue(),
+ vueDevTools(),
+ AutoImport({
+ resolvers: [ElementPlusResolver()],
+ }),
+ Components({
+ resolvers: [ElementPlusResolver()],
+ }),
+ ],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url))
+ },
+ },
+
+ // vite 相关配置
+ server: {
+ port: 5173,
+ host: true,
+ open: false,
+
+
+ proxy: {
+ // https://cn.vitejs.dev/config/#server-proxy
+ '/api': {
+ target: VITE_APP_BASE_URL,
+ changeOrigin: true,
+ rewrite: (p) => p.replace(/^\/api/, ''),
+ },
+
+ // '/dev-ws': {
+ // target: VITE_APP_BASE_URL_WS,
+ // changeOrigin: true,
+ // rewrite: (p) => p.replace(/^\/dev-ws/, ''),
+ // ws: true
+ // }
+
+ }
+ },
+ }
+})