[系统用户]实现系统用户的增删改

This commit is contained in:
GoEdgeLab
2020-12-02 23:11:43 +08:00
parent 99283d9b55
commit 02f6638963
25 changed files with 621 additions and 5 deletions

View File

@@ -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,
},
}
}

View File

@@ -0,0 +1,8 @@
package configloaders
import "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
type AdminModuleList struct {
IsSuper bool
Modules []*systemconfigs.AdminModule
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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()
})
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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,
})

View File

@@ -205,6 +205,11 @@ func (this *UserMustAuth) modules() []maps.Map {
},
},
},
{
"code": "admins",
"name": "系统用户",
"icon": "users",
},
{
"code": "log",
"name": "日志审计",

View File

@@ -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"

View File

@@ -0,0 +1,3 @@
<first-menu>
<menu-item @click.prevent="createAdmin">创建</menu-item>
</first-menu>

View File

@@ -0,0 +1,7 @@
.modules-box .module-box {
float: left;
width: 10em;
margin-top: 0.3em;
margin-bottom: 0.3em;
}
/*# sourceMappingURL=createPopup.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["createPopup.less"],"names":[],"mappings":"AAAA,YACC;EACC,WAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA","file":"createPopup.css"}

View File

@@ -0,0 +1,48 @@
{$layout "layout_popup"}
<h3>创建系统用户</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">全名 *</td>
<td>
<input type="text" name="fullname" maxlength="100" ref="focus"/>
<p class="comment">可以输入姓名、公司名等容易识别的名称。</p>
</td>
</tr>
<tr>
<td>登录用户名 *</td>
<td>
<input type="text" name="username" maxlength="100"/>
<p class="comment">用户名只能英文、数字、下划线的组合。</p>
</td>
</tr>
<tr>
<td>登录密码 *</td>
<td>
<input type="password" name="pass1" maxlength="100"/>
</td>
</tr>
<tr>
<td>确认登录密码 *</td>
<td>
<input type="password" name="pass2" maxlength="100"/>
</td>
</tr>
<tr>
<td>权限</td>
<td>
<div class="modules-box">
<div class="module-box" v-for="module in modules">
<checkbox name="moduleCodes" :v-value="module.code">{{module.name}}</checkbox>
</div>
</div>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,8 @@
.modules-box {
.module-box {
float: left;
width: 10em;
margin-top: 0.3em;
margin-bottom: 0.3em;
}
}

View File

@@ -0,0 +1,31 @@
{$layout}
{$template "menu"}
<table class="ui table selectable">
<thead>
<tr>
<th>用户名</th>
<th>全名</th>
<th>创建时间</th>
<th class="center width10">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="admin in admins">
<td>{{admin.username}}
<div v-if="admin.isSuper" style="margin-top: 0.5em">
<tiny-basic-label class="olive">超级管理员</tiny-basic-label>
</div>
</td>
<td>{{admin.fullname}}</td>
<td>{{admin.createdTime}}</td>
<td class="center">
<label-on :v-is-on="admin.isOn"></label-on>
</td>
<td>
<a href="" @click.prevent="updateAdmin(admin.id)">修改</a> &nbsp; <a href="" v-if="!admin.isSuper" @click.prevent="deleteAdmin(admin.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -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()
})
}
})
}
})

View File

@@ -0,0 +1,7 @@
.modules-box .module-box {
float: left;
width: 10em;
margin-top: 0.3em;
margin-bottom: 0.3em;
}
/*# sourceMappingURL=updatePopup.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["updatePopup.less"],"names":[],"mappings":"AAAA,YACC;EACC,WAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA","file":"updatePopup.css"}

View File

@@ -0,0 +1,51 @@
{$layout "layout_popup"}
<h3>修改系统用户</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="adminId" :value="admin.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">全名 *</td>
<td>
<input type="text" name="fullname" maxlength="100" ref="focus" v-model="admin.fullname"/>
<p class="comment">可以输入姓名、公司名等容易识别的名称。</p>
</td>
</tr>
<tr>
<td>登录用户名 *</td>
<td>
<input type="text" name="username" maxlength="100" v-model="admin.username"/>
<p class="comment">用户名只能英文、数字、下划线的组合。</p>
</td>
</tr>
<tr>
<td>登录密码</td>
<td>
<input type="password" name="pass1" maxlength="100"/>
<p class="comment">留空表示不修改。</p>
</td>
</tr>
<tr>
<td>确认登录密码</td>
<td>
<input type="password" name="pass2" maxlength="100"/>
</td>
</tr>
<tr>
<td>权限</td>
<td>
<div class="modules-box">
<div class="module-box" v-for="module in modules">
<checkbox name="moduleCodes" :v-value="module.code" v-model="module.isChecked">{{module.name}}</checkbox>
</div>
</div>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,8 @@
.modules-box {
.module-box {
float: left;
width: 10em;
margin-top: 0.3em;
margin-bottom: 0.3em;
}
}

View File

@@ -35,7 +35,7 @@
<td>是否允许修改清除配置</td>
<td>
<checkbox name="canChange" v-model="logConfig.canChange"></checkbox>
<p class="comment">选中后,不能再次修改删除、清理相关设置,防止出现安全问题</p>
<p class="comment">选中后,不能再次修改删除、清理相关设置,防止攻击者用来擦除日志</p>
</td>
</tr>
</table>