mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 05:00:25 +08:00 
			
		
		
		
	实现用户注册/审核功能
This commit is contained in:
		@@ -378,6 +378,10 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
 | 
			
		||||
				"isActive": secondMenuItem == "traffic",
 | 
			
		||||
				"isOn":     serverConfig.TrafficLimit != nil && serverConfig.TrafficLimit.IsOn,
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			if serverConfig.Web != nil && serverConfig.Web.RequestScripts != nil {
 | 
			
		||||
				_ = serverConfig.Web.RequestScripts.Init()
 | 
			
		||||
			}
 | 
			
		||||
			menuItems = append(menuItems, maps.Map{
 | 
			
		||||
				"name":     "边缘脚本",
 | 
			
		||||
				"url":      "/servers/server/settings/requestScripts?serverId=" + serverIdString,
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ func (this *IndexAction) RunGet(params struct {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	userMaps := []maps.Map{}
 | 
			
		||||
	var userMaps = []maps.Map{}
 | 
			
		||||
	for _, user := range usersResp.Users {
 | 
			
		||||
		var clusterMap maps.Map = nil
 | 
			
		||||
		if user.NodeCluster != nil {
 | 
			
		||||
@@ -45,16 +45,20 @@ func (this *IndexAction) RunGet(params struct {
 | 
			
		||||
				"name": user.NodeCluster.Name,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		userMaps = append(userMaps, maps.Map{
 | 
			
		||||
			"id":          user.Id,
 | 
			
		||||
			"username":    user.Username,
 | 
			
		||||
			"isOn":        user.IsOn,
 | 
			
		||||
			"fullname":    user.Fullname,
 | 
			
		||||
			"email":       user.Email,
 | 
			
		||||
			"mobile":      user.Mobile,
 | 
			
		||||
			"tel":         user.Tel,
 | 
			
		||||
			"createdTime": timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt),
 | 
			
		||||
			"cluster":     clusterMap,
 | 
			
		||||
			"id":           user.Id,
 | 
			
		||||
			"username":     user.Username,
 | 
			
		||||
			"isOn":         user.IsOn,
 | 
			
		||||
			"fullname":     user.Fullname,
 | 
			
		||||
			"email":        user.Email,
 | 
			
		||||
			"mobile":       user.Mobile,
 | 
			
		||||
			"tel":          user.Tel,
 | 
			
		||||
			"createdTime":  timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt),
 | 
			
		||||
			"cluster":      clusterMap,
 | 
			
		||||
			"registeredIP": user.RegisteredIP,
 | 
			
		||||
			"isVerified":   user.IsVerified,
 | 
			
		||||
			"isRejected":   user.IsRejected,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["users"] = userMaps
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ func init() {
 | 
			
		||||
			GetPost("/update", new(UpdateAction)).
 | 
			
		||||
			Post("/delete", new(DeleteAction)).
 | 
			
		||||
			GetPost("/features", new(FeaturesAction)).
 | 
			
		||||
			GetPost("/verifyPopup", new(VerifyPopupAction)).
 | 
			
		||||
 | 
			
		||||
			// AccessKeys
 | 
			
		||||
			Prefix("/users/accessKeys").
 | 
			
		||||
@@ -26,7 +27,6 @@ func init() {
 | 
			
		||||
			GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
 | 
			
		||||
			Post("/delete", new(accesskeys.DeleteAction)).
 | 
			
		||||
			Post("/updateIsOn", new(accesskeys.UpdateIsOnAction)).
 | 
			
		||||
 | 
			
		||||
			EndAll()
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,11 @@ func (this *UserAction) RunGet(params struct {
 | 
			
		||||
}) {
 | 
			
		||||
	err := userutils.InitUser(this.Parent(), params.UserId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == userutils.ErrUserNotFound {
 | 
			
		||||
			this.RedirectURL("/users")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -51,17 +56,35 @@ func (this *UserAction) RunGet(params struct {
 | 
			
		||||
	}
 | 
			
		||||
	countAccessKeys := countAccessKeyResp.Count
 | 
			
		||||
 | 
			
		||||
	// IP地址
 | 
			
		||||
	var registeredRegion = ""
 | 
			
		||||
	if len(user.RegisteredIP) > 0 {
 | 
			
		||||
		regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: user.RegisteredIP})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if regionResp.IpRegion != nil {
 | 
			
		||||
			registeredRegion = regionResp.IpRegion.Summary
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["user"] = maps.Map{
 | 
			
		||||
		"id":              user.Id,
 | 
			
		||||
		"username":        user.Username,
 | 
			
		||||
		"fullname":        user.Fullname,
 | 
			
		||||
		"email":           user.Email,
 | 
			
		||||
		"tel":             user.Tel,
 | 
			
		||||
		"remark":          user.Remark,
 | 
			
		||||
		"mobile":          user.Mobile,
 | 
			
		||||
		"isOn":            user.IsOn,
 | 
			
		||||
		"cluster":         clusterMap,
 | 
			
		||||
		"countAccessKeys": countAccessKeys,
 | 
			
		||||
		"id":               user.Id,
 | 
			
		||||
		"username":         user.Username,
 | 
			
		||||
		"fullname":         user.Fullname,
 | 
			
		||||
		"email":            user.Email,
 | 
			
		||||
		"tel":              user.Tel,
 | 
			
		||||
		"remark":           user.Remark,
 | 
			
		||||
		"mobile":           user.Mobile,
 | 
			
		||||
		"isOn":             user.IsOn,
 | 
			
		||||
		"cluster":          clusterMap,
 | 
			
		||||
		"countAccessKeys":  countAccessKeys,
 | 
			
		||||
		"isRejected":       user.IsRejected,
 | 
			
		||||
		"rejectReason":     user.RejectReason,
 | 
			
		||||
		"isVerified":       user.IsVerified,
 | 
			
		||||
		"registeredIP":     user.RegisteredIP,
 | 
			
		||||
		"registeredRegion": registeredRegion,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ import (
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var ErrUserNotFound = errors.New("not found user")
 | 
			
		||||
 | 
			
		||||
// InitUser 查找用户基本信息
 | 
			
		||||
func InitUser(p *actionutils.ParentAction, userId int64) error {
 | 
			
		||||
	resp, err := p.RPC().UserRPC().FindEnabledUser(p.AdminContext(), &pb.FindEnabledUserRequest{UserId: userId})
 | 
			
		||||
@@ -14,7 +16,7 @@ func InitUser(p *actionutils.ParentAction, userId int64) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if resp.User == nil {
 | 
			
		||||
		return errors.New("not found user")
 | 
			
		||||
		return ErrUserNotFound
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// AccessKey数量
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										56
									
								
								internal/web/actions/default/users/verifyPopup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								internal/web/actions/default/users/verifyPopup.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
 | 
			
		||||
 | 
			
		||||
package users
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/iwind/TeaGo/actions"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type VerifyPopupAction struct {
 | 
			
		||||
	actionutils.ParentAction
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *VerifyPopupAction) RunGet(params struct {
 | 
			
		||||
	UserId int64
 | 
			
		||||
}) {
 | 
			
		||||
	this.Data["userId"] = params.UserId
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *VerifyPopupAction) RunPost(params struct {
 | 
			
		||||
	UserId       int64
 | 
			
		||||
	Result       string
 | 
			
		||||
	RejectReason string
 | 
			
		||||
 | 
			
		||||
	Must *actions.Must
 | 
			
		||||
	CSRF *actionutils.CSRF
 | 
			
		||||
}) {
 | 
			
		||||
	defer this.CreateLogInfo("审核用户:%d 结果:%s", params.UserId, params.Result)
 | 
			
		||||
 | 
			
		||||
	if params.Result == "pass" {
 | 
			
		||||
		params.RejectReason = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := this.RPC().UserRPC().VerifyUser(this.AdminContext(), &pb.VerifyUserRequest{
 | 
			
		||||
		UserId:       params.UserId,
 | 
			
		||||
		IsRejected:   params.Result == "reject" || params.Result == "delete",
 | 
			
		||||
		RejectReason: params.RejectReason,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if params.Result == "delete" {
 | 
			
		||||
		_, err = this.RPC().UserRPC().DeleteUser(this.AdminContext(), &pb.DeleteUserRequest{UserId: params.UserId})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Success()
 | 
			
		||||
}
 | 
			
		||||
@@ -31,5 +31,15 @@ Vue.component("micro-basic-label", {
 | 
			
		||||
 | 
			
		||||
// 灰色的Label
 | 
			
		||||
Vue.component("grey-label", {
 | 
			
		||||
	template: `<span class="ui label basic grey tiny" style="margin-top: 0.4em; font-size: 0.7em; border: 1px solid #ddd!important; font-weight: normal;"><slot></slot></span>`
 | 
			
		||||
	props: ["color"],
 | 
			
		||||
	data: function () {
 | 
			
		||||
		let color = "grey"
 | 
			
		||||
		if (this.color != null && this.color.length > 0) {
 | 
			
		||||
			color = "red"
 | 
			
		||||
		}
 | 
			
		||||
		return {
 | 
			
		||||
			labelColor: color
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	template: `<span class="ui label basic tiny" :class="labelColor" style="margin-top: 0.4em; font-size: 0.7em; border: 1px solid #ddd!important; font-weight: normal;"><slot></slot></span>`
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -31,13 +31,13 @@
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="ui field">
 | 
			
		||||
					<div class="ui left icon input">
 | 
			
		||||
						<i class="ui user icon"></i>
 | 
			
		||||
						<i class="ui user icon small"></i>
 | 
			
		||||
						<input type="text" name="username" v-model="username" placeholder="请输入用户名" maxlength="200" ref="usernameRef" @input="changeUsername"/>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="ui field">
 | 
			
		||||
					<div class="ui left icon input">
 | 
			
		||||
						<i class="ui lock icon"></i>
 | 
			
		||||
						<i class="ui lock icon small"></i>
 | 
			
		||||
						<input type="password" v-model="password" placeholder="请输入密码" maxlength="200" @input="changePassword()" ref="passwordRef"/>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -71,8 +71,14 @@
 | 
			
		||||
                <p class="comment">显示在系统界面上的图标,请使用PNG格式。</p>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>注册设置</td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <a href="/users/setting">修改用户注册相关设置 »</a>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    </table>
 | 
			
		||||
    <p class="comment">修改后,可能需要等待数分钟才会生效。</p>
 | 
			
		||||
    <p class="comment">修改后,在3分钟内生效。</p>
 | 
			
		||||
 | 
			
		||||
    <submit-btn></submit-btn>
 | 
			
		||||
</form>
 | 
			
		||||
@@ -18,7 +18,15 @@
 | 
			
		||||
		</tr>
 | 
			
		||||
	</thead>
 | 
			
		||||
	<tr v-for="user in users">
 | 
			
		||||
        <td :class="{disabled:!user.isOn}"><a :href="'/users/user?userId=' + user.id">{{user.username}}</a></td>
 | 
			
		||||
        <td :class="{disabled:!user.isOn}">
 | 
			
		||||
            <a :href="'/users/user?userId=' + user.id">{{user.username}}</a>
 | 
			
		||||
            <div v-if="!user.isVerified">
 | 
			
		||||
                <grey-label color="red">未审核</grey-label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-if="user.isRejected">
 | 
			
		||||
                <grey-label color="red">已拒绝</grey-label>
 | 
			
		||||
            </div>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td :class="{disabled:!user.isOn}">{{user.fullname}}</td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <span v-if="user.cluster != null">{{user.cluster.name}} <link-icon :href="'/clusters/cluster?clusterId=' + user.cluster.id"></link-icon></span>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								web/views/@default/users/setting/index_plus.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/views/@default/users/setting/index_plus.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
.feature-boxes .feature-box {
 | 
			
		||||
  margin-bottom: 1em;
 | 
			
		||||
  width: 24em;
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
.feature-boxes .feature-box:hover label {
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
/*# sourceMappingURL=index_plus.css.map */
 | 
			
		||||
							
								
								
									
										1
									
								
								web/views/@default/users/setting/index_plus.css.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/views/@default/users/setting/index_plus.css.map
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"version":3,"sources":["index_plus.less"],"names":[],"mappings":"AAAA,cACC;EACC,kBAAA;EACA,WAAA;EACA,WAAA;;AAJF,cAOC,aAAY,MACX;EACC,iBAAA","file":"index_plus.css"}
 | 
			
		||||
							
								
								
									
										13
									
								
								web/views/@default/users/setting/index_plus.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/views/@default/users/setting/index_plus.less
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
.feature-boxes {
 | 
			
		||||
	.feature-box {
 | 
			
		||||
		margin-bottom: 1em;
 | 
			
		||||
		width: 24em;
 | 
			
		||||
		float: left;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.feature-box:hover {
 | 
			
		||||
		label {
 | 
			
		||||
			font-weight: bold;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,16 @@
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>状态</td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <label-on :v-is-on="user.isOn"></label-on>
 | 
			
		||||
            <span v-if="!user.isVerified" class="red">
 | 
			
		||||
                未审核   <a href="" @click.prevent="verify">[审核]</a>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span v-else-if="user.isRejected" class="red">已拒绝
 | 
			
		||||
                   <a href="" @click.prevent="verify">[重新审核]</a>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span v-else>
 | 
			
		||||
                <label-on :v-is-on="user.isOn"></label-on>
 | 
			
		||||
            </span>
 | 
			
		||||
            <p class="comment" v-if="user.isVerified && user.isRejected && user.rejectReason.length > 0">拒绝原因:{{user.rejectReason}}</p>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
@@ -55,4 +64,12 @@
 | 
			
		||||
            <span v-else class="disabled">-</span>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>注册IP</td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <span v-if="user.registeredIP.length == 0" class="disabled">-</span>
 | 
			
		||||
            <span v-else-if="user.registeredRegion.length == 0">{{user.registeredIP}}</span>
 | 
			
		||||
            <span v-else>{{user.registeredIP}}<span class="grey small">({{user.registeredRegion}})</span></span>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
</table>
 | 
			
		||||
							
								
								
									
										11
									
								
								web/views/@default/users/user.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								web/views/@default/users/user.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
Tea.context(function () {
 | 
			
		||||
	this.verify = function () {
 | 
			
		||||
		teaweb.popup(".verifyPopup?userId=" + this.user.id, {
 | 
			
		||||
			callback: function () {
 | 
			
		||||
				teaweb.success("保存成功", function () {
 | 
			
		||||
					teaweb.reload()
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										31
									
								
								web/views/@default/users/verifyPopup.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/views/@default/users/verifyPopup.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
{$layout "layout_popup"}
 | 
			
		||||
 | 
			
		||||
<h3>审核</h3>
 | 
			
		||||
<form class="ui form" data-tea-success="success" data-tea-action="$">
 | 
			
		||||
    <csrf-token></csrf-token>
 | 
			
		||||
    <input type="hidden" name="userId" :value="userId"/>
 | 
			
		||||
 | 
			
		||||
    <table class="ui table definition selectable">
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td class="title">审核结果</td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select class="ui dropdown auto-width" name="result" v-model="result">
 | 
			
		||||
                    <option value="pass">通过</option>
 | 
			
		||||
                    <option value="reject">拒绝</option>
 | 
			
		||||
                    <option value="delete">拒绝并删除</option>
 | 
			
		||||
                </select>
 | 
			
		||||
                <p class="comment" v-if="result == 'pass'">通过后,用户可正常创建服务。</p>
 | 
			
		||||
                <p class="comment" v-if="result == 'reject'">拒绝后,用户不可创建服务。</p>
 | 
			
		||||
                <p class="comment" v-if="result == 'delete'">将删除当前用户信息。</p>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr v-if="result == 'reject' || result == 'delete'">
 | 
			
		||||
            <td>拒绝原因</td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <textarea rows="2" name="rejectReason"></textarea>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
    <submit-btn></submit-btn>
 | 
			
		||||
</form>
 | 
			
		||||
							
								
								
									
										3
									
								
								web/views/@default/users/verifyPopup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/views/@default/users/verifyPopup.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
Tea.context(function () {
 | 
			
		||||
	this.result = "pass"
 | 
			
		||||
})
 | 
			
		||||
		Reference in New Issue
	
	Block a user