diff --git a/internal/configloaders/admin_module.go b/internal/configloaders/admin_module.go new file mode 100644 index 00000000..ced2a48b --- /dev/null +++ b/internal/configloaders/admin_module.go @@ -0,0 +1,72 @@ +package configloaders + +import "github.com/iwind/TeaGo/maps" + +type AdminModuleCode = string + +const ( + AdminModuleCodeServer AdminModuleCode = "server" + AdminModuleCodeNode AdminModuleCode = "node" + AdminModuleCodeDNS AdminModuleCode = "dns" + AdminModuleCodeAdmin AdminModuleCode = "admin" + AdminModuleCodeLog AdminModuleCode = "log" + AdminModuleCodeSetting AdminModuleCode = "setting" +) + +var adminModuleMapping = map[int64]*AdminModuleList{} // adminId => AdminModuleList + +func LoadAdminModuleMapping() (map[int64]*AdminModuleList, error) { + locker.Lock() + defer locker.Unlock() + + if len(adminModuleMapping) > 0 { + return adminModuleMapping, nil + } + + // TODO + + return nil, nil +} + +func NotifyAdminModuleMappingChange() error { + locker.Lock() + adminModuleMapping = map[int64]*AdminModuleList{} + locker.Unlock() // 这里结束是为了避免和LoadAdminModuleMapping()造成死锁 + _, err := LoadAdminModuleMapping() + return err +} + +func IsAllowModule(adminId int64, module string) bool { + // TODO + return false +} + +// 所有权限列表 +func AllModuleMaps() []maps.Map { + return []maps.Map{ + { + "name": "网站服务", + "code": AdminModuleCodeServer, + }, + { + "name": "边缘节点", + "code": AdminModuleCodeNode, + }, + { + "name": "域名解析", + "code": AdminModuleCodeDNS, + }, + { + "name": "系统用户", + "code": AdminModuleCodeAdmin, + }, + { + "name": "日志审计", + "code": AdminModuleCodeLog, + }, + { + "name": "系统设置", + "code": AdminModuleCodeSetting, + }, + } +} diff --git a/internal/configloaders/admin_module_list.go b/internal/configloaders/admin_module_list.go new file mode 100644 index 00000000..fb2447e1 --- /dev/null +++ b/internal/configloaders/admin_module_list.go @@ -0,0 +1,8 @@ +package configloaders + +import "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs" + +type AdminModuleList struct { + IsSuper bool + Modules []*systemconfigs.AdminModule +} diff --git a/internal/web/actions/default/admins/createPopup.go b/internal/web/actions/default/admins/createPopup.go new file mode 100644 index 00000000..eaa3a162 --- /dev/null +++ b/internal/web/actions/default/admins/createPopup.go @@ -0,0 +1,94 @@ +package admins + +import ( + "encoding/json" + "github.com/TeaOSLab/EdgeAdmin/internal/configloaders" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs" + "github.com/iwind/TeaGo/actions" +) + +type CreatePopupAction struct { + actionutils.ParentAction +} + +func (this *CreatePopupAction) Init() { + this.Nav("", "", "") +} + +func (this *CreatePopupAction) RunGet(params struct{}) { + this.Data["modules"] = configloaders.AllModuleMaps() + this.Show() +} + +func (this *CreatePopupAction) RunPost(params struct { + Fullname string + Username string + Pass1 string + Pass2 string + ModuleCodes []string + + Must *actions.Must + CSRF *actionutils.CSRF +}) { + params.Must. + Field("fullname", params.Fullname). + Require("请输入系统用户全名") + + params.Must. + Field("username", params.Username). + Require("请输入登录用户名"). + Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线") + + existsResp, err := this.RPC().AdminRPC().CheckAdminUsername(this.AdminContext(), &pb.CheckAdminUsernameRequest{ + AdminId: 0, + Username: params.Username, + }) + if err != nil { + this.ErrorPage(err) + return + } + if existsResp.Exists { + this.FailField("username", "此用户名已经被别的系统用户使用,请换一个") + } + + params.Must. + Field("pass1", params.Pass1). + Require("请输入登录密码"). + Field("pass2", params.Pass2). + Require("请输入确认登录密码") + if params.Pass1 != params.Pass2 { + this.FailField("pass2", "两次输入的密码不一致") + } + + modules := []*systemconfigs.AdminModule{} + for _, code := range params.ModuleCodes { + modules = append(modules, &systemconfigs.AdminModule{ + Code: code, + AllowAll: true, + Actions: nil, // TODO 后期再开放细粒度控制 + }) + } + modulesJSON, err := json.Marshal(modules) + if err != nil { + this.ErrorPage(err) + return + } + + createResp, err := this.RPC().AdminRPC().CreateAdmin(this.AdminContext(), &pb.CreateAdminRequest{ + Username: params.Username, + Password: params.Pass1, + Fullname: params.Fullname, + ModulesJSON: modulesJSON, + IsSuper: false, // TODO 后期再开放创建超级用户 + }) + if err != nil { + this.ErrorPage(err) + return + } + + defer this.CreateLogInfo("创建系统用户 %d", createResp.AdminId) + + this.Success() +} diff --git a/internal/web/actions/default/admins/delete.go b/internal/web/actions/default/admins/delete.go new file mode 100644 index 00000000..87669adb --- /dev/null +++ b/internal/web/actions/default/admins/delete.go @@ -0,0 +1,24 @@ +package admins + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" +) + +type DeleteAction struct { + actionutils.ParentAction +} + +func (this *DeleteAction) RunPost(params struct { + AdminId int64 +}) { + defer this.CreateLogInfo("删除系统用户 %d", params.AdminId) + + _, err := this.RPC().AdminRPC().DeleteAdmin(this.AdminContext(), &pb.DeleteAdminRequest{AdminId: params.AdminId}) + if err != nil { + this.ErrorPage(err) + return + } + + this.Success() +} diff --git a/internal/web/actions/default/admins/index.go b/internal/web/actions/default/admins/index.go new file mode 100644 index 00000000..947c4e64 --- /dev/null +++ b/internal/web/actions/default/admins/index.go @@ -0,0 +1,49 @@ +package admins + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" + timeutil "github.com/iwind/TeaGo/utils/time" +) + +type IndexAction struct { + actionutils.ParentAction +} + +func (this *IndexAction) Init() { + this.Nav("", "", "") +} + +func (this *IndexAction) RunGet(params struct{}) { + countResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{}) + if err != nil { + this.ErrorPage(err) + return + } + page := this.NewPage(countResp.Count) + this.Data["page"] = page.AsHTML() + + adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{ + Offset: page.Offset, + Size: page.Size, + }) + if err != nil { + this.ErrorPage(err) + return + } + adminMaps := []maps.Map{} + for _, admin := range adminsResp.Admins { + adminMaps = append(adminMaps, maps.Map{ + "id": admin.Id, + "isOn": admin.IsOn, + "isSuper": admin.IsSuper, + "username": admin.Username, + "fullname": admin.Fullname, + "createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt), + }) + } + this.Data["admins"] = adminMaps + + this.Show() +} diff --git a/internal/web/actions/default/admins/init.go b/internal/web/actions/default/admins/init.go new file mode 100644 index 00000000..8d2e71f1 --- /dev/null +++ b/internal/web/actions/default/admins/init.go @@ -0,0 +1,21 @@ +package admins + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers" + "github.com/iwind/TeaGo" +) + +func init() { + TeaGo.BeforeStart(func(server *TeaGo.Server) { + server. + Helper(helpers.NewUserMustAuth()). + Data("teaMenu", "admins"). + Prefix("/admins"). + Get("", new(IndexAction)). + GetPost("/createPopup", new(CreatePopupAction)). + GetPost("/updatePopup", new(UpdatePopupAction)). + Post("/delete", new(DeleteAction)). + Post("/updateOn", new(UpdateOnAction)). + EndAll() + }) +} diff --git a/internal/web/actions/default/admins/updateOn.go b/internal/web/actions/default/admins/updateOn.go new file mode 100644 index 00000000..b2199750 --- /dev/null +++ b/internal/web/actions/default/admins/updateOn.go @@ -0,0 +1,11 @@ +package admins + +import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + +type UpdateOnAction struct { + actionutils.ParentAction +} + +func (this *UpdateOnAction) RunPost(params struct{}) { + this.Success() +} diff --git a/internal/web/actions/default/admins/updatePopup.go b/internal/web/actions/default/admins/updatePopup.go new file mode 100644 index 00000000..ac52658a --- /dev/null +++ b/internal/web/actions/default/admins/updatePopup.go @@ -0,0 +1,129 @@ +package admins + +import ( + "encoding/json" + "github.com/TeaOSLab/EdgeAdmin/internal/configloaders" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs" + "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/maps" +) + +type UpdatePopupAction struct { + actionutils.ParentAction +} + +func (this *UpdatePopupAction) Init() { + this.Nav("", "", "") +} + +func (this *UpdatePopupAction) RunGet(params struct { + AdminId int64 +}) { + + adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId}) + if err != nil { + this.ErrorPage(err) + return + } + admin := adminResp.Admin + + this.Data["admin"] = maps.Map{ + "id": admin.Id, + "fullname": admin.Fullname, + "username": admin.Username, + } + + moduleMaps := configloaders.AllModuleMaps() + for _, m := range moduleMaps { + code := m.GetString("code") + isChecked := false + for _, module := range admin.Modules { + if module.Code == code { + isChecked = true + break + } + } + m["isChecked"] = isChecked + } + this.Data["modules"] = moduleMaps + + this.Show() +} + +func (this *UpdatePopupAction) RunPost(params struct { + AdminId int64 + + Fullname string + Username string + Pass1 string + Pass2 string + ModuleCodes []string + + Must *actions.Must + CSRF *actionutils.CSRF +}) { + defer this.CreateLogInfo("修改系统用户 %d", params.AdminId) + + params.Must. + Field("fullname", params.Fullname). + Require("请输入系统用户全名") + + params.Must. + Field("username", params.Username). + Require("请输入登录用户名"). + Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线") + + existsResp, err := this.RPC().AdminRPC().CheckAdminUsername(this.AdminContext(), &pb.CheckAdminUsernameRequest{ + AdminId: params.AdminId, + Username: params.Username, + }) + if err != nil { + this.ErrorPage(err) + return + } + if existsResp.Exists { + this.FailField("username", "此用户名已经被别的系统用户使用,请换一个") + } + + if len(params.Pass1) > 0 { + params.Must. + Field("pass1", params.Pass1). + Require("请输入登录密码"). + Field("pass2", params.Pass2). + Require("请输入确认登录密码") + if params.Pass1 != params.Pass2 { + this.FailField("pass2", "两次输入的密码不一致") + } + } + + modules := []*systemconfigs.AdminModule{} + for _, code := range params.ModuleCodes { + modules = append(modules, &systemconfigs.AdminModule{ + Code: code, + AllowAll: true, + Actions: nil, // TODO 后期再开放细粒度控制 + }) + } + modulesJSON, err := json.Marshal(modules) + if err != nil { + this.ErrorPage(err) + return + } + + _, err = this.RPC().AdminRPC().UpdateAdmin(this.AdminContext(), &pb.UpdateAdminRequest{ + AdminId: params.AdminId, + Username: params.Username, + Password: params.Pass1, + Fullname: params.Fullname, + ModulesJSON: modulesJSON, + IsSuper: false, // TODO 后期再开放创建超级用户 + }) + if err != nil { + this.ErrorPage(err) + return + } + + this.Success() +} diff --git a/internal/web/actions/default/log/delete.go b/internal/web/actions/default/log/delete.go index accd97bc..a5ddc1da 100644 --- a/internal/web/actions/default/log/delete.go +++ b/internal/web/actions/default/log/delete.go @@ -13,6 +13,9 @@ type DeleteAction struct { func (this *DeleteAction) RunPost(params struct { LogId int64 }) { + // 记录日志 + defer this.CreateLogInfo("删除单个操作日志 %d", params.LogId) + // 读取配置 config, err := configloaders.LoadLogConfig() if err != nil { @@ -23,9 +26,6 @@ func (this *DeleteAction) RunPost(params struct { this.Fail("已设置不能删除") } - // 记录日志 - defer this.CreateLogInfo("删除单个操作日志 %d", params.LogId) - // 执行删除 _, err = this.RPC().LogRPC().DeleteLogPermanently(this.AdminContext(), &pb.DeleteLogPermanentlyRequest{LogId: params.LogId}) if err != nil { diff --git a/internal/web/actions/default/log/settings.go b/internal/web/actions/default/log/settings.go index dd928428..f2f9a428 100644 --- a/internal/web/actions/default/log/settings.go +++ b/internal/web/actions/default/log/settings.go @@ -38,6 +38,8 @@ func (this *SettingsAction) RunPost(params struct { Must *actions.Must CSRF *actionutils.CSRF }) { + defer this.CreateLogInfo("修改日志相关配置") + capacity := &shared.SizeCapacity{} err := json.Unmarshal(params.CapacityJSON, capacity) if err != nil { diff --git a/internal/web/actions/default/settings/profile/index.go b/internal/web/actions/default/settings/profile/index.go index 954f520a..d4c5e0a2 100644 --- a/internal/web/actions/default/settings/profile/index.go +++ b/internal/web/actions/default/settings/profile/index.go @@ -45,7 +45,7 @@ func (this *IndexAction) RunPost(params struct { Field("fullname", params.Fullname). Require("请输入你的姓名") - _, err := this.RPC().AdminRPC().UpdateAdmin(this.AdminContext(), &pb.UpdateAdminRequest{ + _, err := this.RPC().AdminRPC().UpdateAdminInfo(this.AdminContext(), &pb.UpdateAdminInfoRequest{ AdminId: this.AdminId(), Fullname: params.Fullname, }) diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go index a6b4e363..1756142b 100644 --- a/internal/web/helpers/user_must_auth.go +++ b/internal/web/helpers/user_must_auth.go @@ -205,6 +205,11 @@ func (this *UserMustAuth) modules() []maps.Map { }, }, }, + { + "code": "admins", + "name": "系统用户", + "icon": "users", + }, { "code": "log", "name": "日志审计", diff --git a/internal/web/import.go b/internal/web/import.go index b4d7a1b9..76d8e86b 100644 --- a/internal/web/import.go +++ b/internal/web/import.go @@ -3,6 +3,7 @@ package web import ( _ "github.com/TeaOSLab/EdgeAdmin/internal/tasks" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/about" + _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/api" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/api/node" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters" diff --git a/web/views/@default/admins/@menu.html b/web/views/@default/admins/@menu.html new file mode 100644 index 00000000..2a03639e --- /dev/null +++ b/web/views/@default/admins/@menu.html @@ -0,0 +1,3 @@ + + 创建 + \ No newline at end of file diff --git a/web/views/@default/admins/createPopup.css b/web/views/@default/admins/createPopup.css new file mode 100644 index 00000000..e44ed954 --- /dev/null +++ b/web/views/@default/admins/createPopup.css @@ -0,0 +1,7 @@ +.modules-box .module-box { + float: left; + width: 10em; + margin-top: 0.3em; + margin-bottom: 0.3em; +} +/*# sourceMappingURL=createPopup.css.map */ \ No newline at end of file diff --git a/web/views/@default/admins/createPopup.css.map b/web/views/@default/admins/createPopup.css.map new file mode 100644 index 00000000..d2d9422e --- /dev/null +++ b/web/views/@default/admins/createPopup.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["createPopup.less"],"names":[],"mappings":"AAAA,YACC;EACC,WAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA","file":"createPopup.css"} \ No newline at end of file diff --git a/web/views/@default/admins/createPopup.html b/web/views/@default/admins/createPopup.html new file mode 100644 index 00000000..2fcf161a --- /dev/null +++ b/web/views/@default/admins/createPopup.html @@ -0,0 +1,48 @@ +{$layout "layout_popup"} + +

创建系统用户

+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
全名 * + +

可以输入姓名、公司名等容易识别的名称。

+
登录用户名 * + +

用户名只能英文、数字、下划线的组合。

+
登录密码 * + +
确认登录密码 * + +
权限 +
+
+ {{module.name}} +
+
+
+ + +
\ No newline at end of file diff --git a/web/views/@default/admins/createPopup.less b/web/views/@default/admins/createPopup.less new file mode 100644 index 00000000..e3ae75d6 --- /dev/null +++ b/web/views/@default/admins/createPopup.less @@ -0,0 +1,8 @@ +.modules-box { + .module-box { + float: left; + width: 10em; + margin-top: 0.3em; + margin-bottom: 0.3em; + } +} \ No newline at end of file diff --git a/web/views/@default/admins/index.html b/web/views/@default/admins/index.html new file mode 100644 index 00000000..73360f56 --- /dev/null +++ b/web/views/@default/admins/index.html @@ -0,0 +1,31 @@ +{$layout} +{$template "menu"} + + + + + + + + + + + + + + + + + + +
用户名全名创建时间状态操作
{{admin.username}} +
+ 超级管理员 +
+
{{admin.fullname}}{{admin.createdTime}} + + + 修改   删除 +
+ +
\ No newline at end of file diff --git a/web/views/@default/admins/index.js b/web/views/@default/admins/index.js new file mode 100644 index 00000000..888e0215 --- /dev/null +++ b/web/views/@default/admins/index.js @@ -0,0 +1,35 @@ +Tea.context(function () { + this.createAdmin = function () { + teaweb.popup("/admins/createPopup", { + height: "22em", + callback: function () { + teaweb.success("保存成功", function () { + teaweb.reload() + }) + } + }) + } + + this.deleteAdmin = function (adminId) { + let that = this + teaweb.confirm("确定要删除此系统用户吗?", function () { + that.$post(".delete") + .params({ + adminId: adminId + }) + .post() + .refresh() + }) + } + + this.updateAdmin = function (adminId) { + teaweb.popup("/admins/updatePopup?adminId=" + adminId, { + height: "22em", + callback: function () { + teaweb.success("保存成功", function () { + teaweb.reload() + }) + } + }) + } +}) \ No newline at end of file diff --git a/web/views/@default/admins/updatePopup.css b/web/views/@default/admins/updatePopup.css new file mode 100644 index 00000000..99c17be4 --- /dev/null +++ b/web/views/@default/admins/updatePopup.css @@ -0,0 +1,7 @@ +.modules-box .module-box { + float: left; + width: 10em; + margin-top: 0.3em; + margin-bottom: 0.3em; +} +/*# sourceMappingURL=updatePopup.css.map */ \ No newline at end of file diff --git a/web/views/@default/admins/updatePopup.css.map b/web/views/@default/admins/updatePopup.css.map new file mode 100644 index 00000000..e7d9ad0e --- /dev/null +++ b/web/views/@default/admins/updatePopup.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["updatePopup.less"],"names":[],"mappings":"AAAA,YACC;EACC,WAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA","file":"updatePopup.css"} \ No newline at end of file diff --git a/web/views/@default/admins/updatePopup.html b/web/views/@default/admins/updatePopup.html new file mode 100644 index 00000000..eb8f68d4 --- /dev/null +++ b/web/views/@default/admins/updatePopup.html @@ -0,0 +1,51 @@ +{$layout "layout_popup"} + +

修改系统用户

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
全名 * + +

可以输入姓名、公司名等容易识别的名称。

+
登录用户名 * + +

用户名只能英文、数字、下划线的组合。

+
登录密码 + +

留空表示不修改。

+
确认登录密码 + +
权限 +
+
+ {{module.name}} +
+
+
+ + +
\ No newline at end of file diff --git a/web/views/@default/admins/updatePopup.less b/web/views/@default/admins/updatePopup.less new file mode 100644 index 00000000..e3ae75d6 --- /dev/null +++ b/web/views/@default/admins/updatePopup.less @@ -0,0 +1,8 @@ +.modules-box { + .module-box { + float: left; + width: 10em; + margin-top: 0.3em; + margin-bottom: 0.3em; + } +} \ No newline at end of file diff --git a/web/views/@default/log/settings.html b/web/views/@default/log/settings.html index e9ad7060..b47cb33c 100644 --- a/web/views/@default/log/settings.html +++ b/web/views/@default/log/settings.html @@ -35,7 +35,7 @@ 是否允许修改清除配置 -

选中后,不能再次修改删除、清理相关设置,防止出现安全问题。

+

选中后,不能再次修改删除、清理等相关设置,防止攻击者用来擦除日志。