增加用户系统界面管理、用户可以设置关联集群

This commit is contained in:
GoEdgeLab
2020-12-16 15:49:15 +08:00
parent 041ead3d7f
commit ee549b945d
30 changed files with 411 additions and 312 deletions

View File

@@ -9,17 +9,17 @@ import (
"reflect" "reflect"
) )
var sharedUIConfig *systemconfigs.AdminUIConfig = nil var sharedAdminUIConfig *systemconfigs.AdminUIConfig = nil
const ( const (
UISettingName = "adminUIConfig" AdminUISettingName = "adminUIConfig"
) )
func LoadUIConfig() (*systemconfigs.AdminUIConfig, error) { func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
locker.Lock() locker.Lock()
defer locker.Unlock() defer locker.Unlock()
config, err := loadUIConfig() config, err := loadAdminUIConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -28,7 +28,7 @@ func LoadUIConfig() (*systemconfigs.AdminUIConfig, error) {
return &v, nil return &v, nil
} }
func UpdateUIConfig(uiConfig *systemconfigs.AdminUIConfig) error { func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
locker.Lock() locker.Lock()
defer locker.Unlock() defer locker.Unlock()
@@ -41,48 +41,48 @@ func UpdateUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
return err return err
} }
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{ _, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
Code: UISettingName, Code: AdminUISettingName,
ValueJSON: valueJSON, ValueJSON: valueJSON,
}) })
if err != nil { if err != nil {
return err return err
} }
sharedUIConfig = uiConfig sharedAdminUIConfig = uiConfig
return nil return nil
} }
func loadUIConfig() (*systemconfigs.AdminUIConfig, error) { func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
if sharedUIConfig != nil { if sharedAdminUIConfig != nil {
return sharedUIConfig, nil return sharedAdminUIConfig, nil
} }
var rpcClient, err = rpc.SharedRPC() var rpcClient, err = rpc.SharedRPC()
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{ resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
Code: UISettingName, Code: AdminUISettingName,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(resp.ValueJSON) == 0 { if len(resp.ValueJSON) == 0 {
sharedUIConfig = defaultUIConfig() sharedAdminUIConfig = defaultAdminUIConfig()
return sharedUIConfig, nil return sharedAdminUIConfig, nil
} }
config := &systemconfigs.AdminUIConfig{} config := &systemconfigs.AdminUIConfig{}
err = json.Unmarshal(resp.ValueJSON, config) err = json.Unmarshal(resp.ValueJSON, config)
if err != nil { if err != nil {
logs.Println("[UI_MANAGER]" + err.Error()) logs.Println("[UI_MANAGER]" + err.Error())
sharedUIConfig = defaultUIConfig() sharedAdminUIConfig = defaultAdminUIConfig()
return sharedUIConfig, nil return sharedAdminUIConfig, nil
} }
sharedUIConfig = config sharedAdminUIConfig = config
return sharedUIConfig, nil return sharedAdminUIConfig, nil
} }
func defaultUIConfig() *systemconfigs.AdminUIConfig { func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
return &systemconfigs.AdminUIConfig{ return &systemconfigs.AdminUIConfig{
ProductName: "GoEdge", ProductName: "GoEdge",
AdminSystemName: "GoEdge管理员系统", AdminSystemName: "GoEdge管理员系统",

View File

@@ -0,0 +1,92 @@
package configloaders
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/logs"
"reflect"
)
var sharedUserUIConfig *systemconfigs.UserUIConfig = nil
const (
UserUISettingName = "userUIConfig"
)
func LoadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
locker.Lock()
defer locker.Unlock()
config, err := loadUserUIConfig()
if err != nil {
return nil, err
}
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UserUIConfig)
return &v, nil
}
func UpdateUserUIConfig(uiConfig *systemconfigs.UserUIConfig) error {
locker.Lock()
defer locker.Unlock()
var rpcClient, err = rpc.SharedRPC()
if err != nil {
return err
}
valueJSON, err := json.Marshal(uiConfig)
if err != nil {
return err
}
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
Code: UserUISettingName,
ValueJSON: valueJSON,
})
if err != nil {
return err
}
sharedUserUIConfig = uiConfig
return nil
}
func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
if sharedUserUIConfig != nil {
return sharedUserUIConfig, nil
}
var rpcClient, err = rpc.SharedRPC()
if err != nil {
return nil, err
}
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
Code: UserUISettingName,
})
if err != nil {
return nil, err
}
if len(resp.ValueJSON) == 0 {
sharedUserUIConfig = defaultUserUIConfig()
return sharedUserUIConfig, nil
}
config := &systemconfigs.UserUIConfig{}
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[UI_MANAGER]" + err.Error())
sharedUserUIConfig = defaultUserUIConfig()
return sharedUserUIConfig, nil
}
sharedUserUIConfig = config
return sharedUserUIConfig, nil
}
func defaultUserUIConfig() *systemconfigs.UserUIConfig {
return &systemconfigs.UserUIConfig{
ProductName: "GoEdge",
UserSystemName: "GoEdge用户系统",
ShowOpenSourceInfo: true,
ShowVersion: true,
}
}

View File

@@ -17,6 +17,12 @@ func init() {
GetPost("/create", new(CreateAction)). GetPost("/create", new(CreateAction)).
Post("/sync", new(SyncAction)). Post("/sync", new(SyncAction)).
Post("/checkChange", new(CheckChangeAction)). Post("/checkChange", new(CheckChangeAction)).
// 只要登录即可访问的Action
EndHelpers().
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
Post("/options", new(OptionsAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -0,0 +1,30 @@
package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type OptionsAction struct {
actionutils.ParentAction
}
func (this *OptionsAction) RunPost(params struct{}) {
clustersResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClusters(this.AdminContext(), &pb.FindAllEnabledNodeClustersRequest{})
if err != nil {
this.ErrorPage(err)
return
}
clusterMaps := []maps.Map{}
for _, cluster := range clustersResp.Clusters {
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
})
}
this.Data["clusters"] = clusterMaps
this.Success()
}

View File

@@ -78,7 +78,7 @@ func (this *PricesAction) formatBits(bits int64) string {
} else if bits < 1_000_000_000_000_000 { } else if bits < 1_000_000_000_000_000 {
sizeHuman = fmt.Sprintf("%.2fTBPS", float64(bits)/1000/1000/1000/1000) sizeHuman = fmt.Sprintf("%.2fTBPS", float64(bits)/1000/1000/1000/1000)
} else { } else {
sizeHuman = fmt.Sprintf("%.2fPTBPS", float64(bits)/1000/1000/1000/1000/1000) sizeHuman = fmt.Sprintf("%.2fPBPS", float64(bits)/1000/1000/1000/1000/1000)
} }
return sizeHuman return sizeHuman
} }

View File

@@ -13,6 +13,4 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct{}) { func (this *IndexAction) RunGet(params struct{}) {
// TODO 暂时先跳转到账单页将来做成Dashboard // TODO 暂时先跳转到账单页将来做成Dashboard
this.RedirectURL("/finance/bills") this.RedirectURL("/finance/bills")
this.Show()
} }

View File

@@ -50,7 +50,7 @@ func (this *IndexAction) RunGet(params struct {
this.Data["token"] = stringutil.Md5(TokenSalt+timestamp) + timestamp this.Data["token"] = stringutil.Md5(TokenSalt+timestamp) + timestamp
this.Data["from"] = params.From this.Data["from"] = params.From
config, err := configloaders.LoadUIConfig() config, err := configloaders.LoadAdminUIConfig()
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return

View File

@@ -30,7 +30,8 @@ func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool)
var adminId = session.GetInt64("adminId") var adminId = session.GetInt64("adminId")
if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) { if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) {
tabbar.Add("Web服务", "", "/settings/server", "", this.tab == "server") tabbar.Add("Web服务", "", "/settings/server", "", this.tab == "server")
tabbar.Add("界面设置", "", "/settings/ui", "", this.tab == "ui") tabbar.Add("管理界面设置", "", "/settings/ui", "", this.tab == "ui")
tabbar.Add("用户界面设置", "", "/settings/user-ui", "", this.tab == "userUI")
tabbar.Add("安全设置", "", "/settings/security", "", this.tab == "security") tabbar.Add("安全设置", "", "/settings/security", "", this.tab == "security")
tabbar.Add("IP库", "", "/settings/ip-library", "", this.tab == "ipLibrary") tabbar.Add("IP库", "", "/settings/ip-library", "", this.tab == "ipLibrary")
tabbar.Add("备份", "", "/settings/backup", "", this.tab == "backup") tabbar.Add("备份", "", "/settings/backup", "", this.tab == "backup")

View File

@@ -1,4 +1,4 @@
package server package ui
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
@@ -15,7 +15,7 @@ func (this *IndexAction) Init() {
} }
func (this *IndexAction) RunGet(params struct{}) { func (this *IndexAction) RunGet(params struct{}) {
config, err := configloaders.LoadUIConfig() config, err := configloaders.LoadAdminUIConfig()
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
@@ -41,7 +41,7 @@ func (this *IndexAction) RunPost(params struct {
Field("adminSystemName", params.AdminSystemName). Field("adminSystemName", params.AdminSystemName).
Require("请输入管理员系统名称") Require("请输入管理员系统名称")
config, err := configloaders.LoadUIConfig() config, err := configloaders.LoadAdminUIConfig()
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
@@ -51,7 +51,7 @@ func (this *IndexAction) RunPost(params struct {
config.ShowOpenSourceInfo = params.ShowOpenSourceInfo config.ShowOpenSourceInfo = params.ShowOpenSourceInfo
config.ShowVersion = params.ShowVersion config.ShowVersion = params.ShowVersion
config.Version = params.Version config.Version = params.Version
err = configloaders.UpdateUIConfig(config) err = configloaders.UpdateAdminUIConfig(config)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return

View File

@@ -1,4 +1,4 @@
package server package ui
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"

View File

@@ -0,0 +1,61 @@
package userui
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
config, err := configloaders.LoadUserUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["config"] = config
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ProductName string
UserSystemName string
ShowOpenSourceInfo bool
ShowVersion bool
Version string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("productName", params.ProductName).
Require("请输入产品名称").
Field("userSystemName", params.UserSystemName).
Require("请输入管理员系统名称")
config, err := configloaders.LoadUserUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
config.ProductName = params.ProductName
config.UserSystemName = params.UserSystemName
config.ShowOpenSourceInfo = params.ShowOpenSourceInfo
config.ShowVersion = params.ShowVersion
config.Version = params.Version
err = configloaders.UpdateUserUIConfig(config)
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,19 @@
package userui
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
Helper(settingutils.NewHelper("userUI")).
Prefix("/settings/user-ui").
GetPost("", new(IndexAction)).
EndAll()
})
}

View File

@@ -20,14 +20,15 @@ func (this *CreatePopupAction) RunGet(params struct{}) {
} }
func (this *CreatePopupAction) RunPost(params struct { func (this *CreatePopupAction) RunPost(params struct {
Username string Username string
Pass1 string Pass1 string
Pass2 string Pass2 string
Fullname string Fullname string
Mobile string Mobile string
Tel string Tel string
Email string Email string
Remark string Remark string
ClusterId int64
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
@@ -60,6 +61,10 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("fullname", params.Fullname). Field("fullname", params.Fullname).
Require("请输入全名") Require("请输入全名")
if params.ClusterId <= 0 {
this.Fail("请选择关联集群")
}
if len(params.Mobile) > 0 { if len(params.Mobile) > 0 {
params.Must. params.Must.
Field("mobile", params.Mobile). Field("mobile", params.Mobile).
@@ -72,14 +77,15 @@ func (this *CreatePopupAction) RunPost(params struct {
} }
createResp, err := this.RPC().UserRPC().CreateUser(this.AdminContext(), &pb.CreateUserRequest{ createResp, err := this.RPC().UserRPC().CreateUser(this.AdminContext(), &pb.CreateUserRequest{
Username: params.Username, Username: params.Username,
Password: params.Pass1, Password: params.Pass1,
Fullname: params.Fullname, Fullname: params.Fullname,
Mobile: params.Mobile, Mobile: params.Mobile,
Tel: params.Tel, Tel: params.Tel,
Email: params.Email, Email: params.Email,
Remark: params.Remark, Remark: params.Remark,
Source: "admin:" + numberutils.FormatInt64(this.AdminId()), Source: "admin:" + numberutils.FormatInt64(this.AdminId()),
ClusterId: params.ClusterId,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -34,6 +34,13 @@ func (this *IndexAction) RunGet(params struct {
} }
userMaps := []maps.Map{} userMaps := []maps.Map{}
for _, user := range usersResp.Users { for _, user := range usersResp.Users {
var clusterMap maps.Map = nil
if user.Cluster != nil {
clusterMap = maps.Map{
"id": user.Cluster.Id,
"name": user.Cluster.Name,
}
}
userMaps = append(userMaps, maps.Map{ userMaps = append(userMaps, maps.Map{
"id": user.Id, "id": user.Id,
"username": user.Username, "username": user.Username,
@@ -43,6 +50,7 @@ func (this *IndexAction) RunGet(params struct {
"mobile": user.Mobile, "mobile": user.Mobile,
"tel": user.Tel, "tel": user.Tel,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt), "createdTime": timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt),
"cluster": clusterMap,
}) })
} }
this.Data["users"] = userMaps this.Data["users"] = userMaps

View File

@@ -47,20 +47,26 @@ func (this *UpdateAction) RunGet(params struct {
"isOn": user.IsOn, "isOn": user.IsOn,
} }
this.Data["clusterId"] = 0
if user.Cluster != nil {
this.Data["clusterId"] = user.Cluster.Id
}
this.Show() this.Show()
} }
func (this *UpdateAction) RunPost(params struct { func (this *UpdateAction) RunPost(params struct {
UserId int64 UserId int64
Username string Username string
Pass1 string Pass1 string
Pass2 string Pass2 string
Fullname string Fullname string
Mobile string Mobile string
Tel string Tel string
Email string Email string
Remark string Remark string
IsOn bool IsOn bool
ClusterId int64
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
@@ -109,15 +115,16 @@ func (this *UpdateAction) RunPost(params struct {
} }
_, err = this.RPC().UserRPC().UpdateUser(this.AdminContext(), &pb.UpdateUserRequest{ _, err = this.RPC().UserRPC().UpdateUser(this.AdminContext(), &pb.UpdateUserRequest{
UserId: params.UserId, UserId: params.UserId,
Username: params.Username, Username: params.Username,
Password: params.Pass1, Password: params.Pass1,
Fullname: params.Fullname, Fullname: params.Fullname,
Mobile: params.Mobile, Mobile: params.Mobile,
Tel: params.Tel, Tel: params.Tel,
Email: params.Email, Email: params.Email,
Remark: params.Remark, Remark: params.Remark,
IsOn: params.IsOn, IsOn: params.IsOn,
ClusterId: params.ClusterId,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -35,6 +35,14 @@ func (this *UserAction) RunGet(params struct {
return return
} }
var clusterMap maps.Map = nil
if user.Cluster != nil {
clusterMap = maps.Map{
"id": user.Cluster.Id,
"name": user.Cluster.Name,
}
}
this.Data["user"] = maps.Map{ this.Data["user"] = maps.Map{
"id": user.Id, "id": user.Id,
"username": user.Username, "username": user.Username,
@@ -44,6 +52,7 @@ func (this *UserAction) RunGet(params struct {
"remark": user.Remark, "remark": user.Remark,
"mobile": user.Mobile, "mobile": user.Mobile,
"isOn": user.IsOn, "isOn": user.IsOn,
"cluster": clusterMap,
} }
this.Show() this.Show()

View File

@@ -74,7 +74,7 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
return true return true
} }
config, err := configloaders.LoadUIConfig() config, err := configloaders.LoadAdminUIConfig()
if err != nil { if err != nil {
action.WriteString(err.Error()) action.WriteString(err.Error())
return false return false

View File

@@ -86,6 +86,7 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ui" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ui"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-nodes" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-nodes"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-ui"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ui" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ui"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users"

View File

@@ -0,0 +1,28 @@
Vue.component("cluster-selector", {
mounted: function () {
let that = this
Tea.action("/clusters/options")
.post()
.success(function (resp) {
that.clusters = resp.data.clusters
})
},
props: ["v-cluster-id"],
data: function () {
let clusterId = this.vClusterId
if (clusterId == null) {
clusterId = 0
}
return {
clusters: [],
clusterId: clusterId
}
},
template: `<div>
<select class="ui dropdown auto-width" name="clusterId" v-model="clusterId">
<option value="0">[选择集群]</option>
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
</div>`
})

View File

@@ -213,9 +213,6 @@ body .ui.menu .item .blink {
body.expanded .main-menu { body.expanded .main-menu {
display: none; display: none;
} }
body.expanded .sub-menu {
display: none;
}
body.expanded .main { body.expanded .main {
left: 1em; left: 1em;
} }
@@ -226,6 +223,7 @@ body.expanded .main {
width: 100%; width: 100%;
z-index: 1000; z-index: 1000;
overflow-x: auto; overflow-x: auto;
border: 0 !important;
background: #276ac6 !important; background: #276ac6 !important;
} }
.top-nav::-webkit-scrollbar { .top-nav::-webkit-scrollbar {
@@ -362,6 +360,8 @@ body.expanded .main {
} }
.main-menu .menu { .main-menu .menu {
background: #276ac6 !important; background: #276ac6 !important;
border: 0 !important;
box-shadow: none !important;
} }
.main-menu::-webkit-scrollbar { .main-menu::-webkit-scrollbar {
width: 2px; width: 2px;
@@ -401,113 +401,6 @@ body.expanded .main {
z-index: 999999; z-index: 999999;
background: white; background: white;
} }
/** 子菜单 **/
.main.without-menu .sub-menu {
display: none;
}
.sub-menu {
position: fixed;
left: 8em;
width: 12.5em;
top: 3em;
bottom: 2.8em;
}
.sub-menu .menus-box {
overflow-y: auto;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
padding-right: 0.4em !important;
}
.sub-menu .menus-box::-webkit-scrollbar {
width: 4px;
height: 4px;
}
.sub-menu .menu {
max-width: 12em !important;
border-right: 0 !important;
}
@media screen and (max-width: 512px) {
.sub-menu {
position: relative;
width: 100%;
left: 0;
top: 0;
}
.sub-menu .menus-box {
position: relative !important;
}
.sub-menu .menu {
width: 100% !important;
max-width: 30em !important;
}
.sub-menu .menus-box .menu .item {
width: 100% !important;
max-width: 30em !important;
}
}
.sub-menu .menu .item.active {
font-weight: normal !important;
outline: none !important;
}
.sub-menu .menu .item:not(.header) {
padding-top: 0.7em !important;
padding-bottom: 0.7em !important;
}
.sub-menu .menu .item:not(.header) span {
font-size: 0.8em;
display: block;
margin-top: 0.6em !important;
line-height: 1.5;
}
.sub-menu .menu .item:not(.active):hover {
background: rgba(0, 0, 0, 0.05) !important;
border-top: 1px white solid !important;
border-bottom: 1px white solid !important;
margin-top: -1px !important;
margin-bottom: -1px !important;
}
.sub-menu .menu .item.active {
background: rgba(0, 0, 0, 0.05) !important;
}
.sub-menu .menu .item var {
font-style: normal;
}
.sub-menu .menu .item:not(.active) var.grey {
color: grey;
}
.sub-menu .menu .item span:not(.green) {
color: grey;
}
.sub-menu .menu .item span.red {
color: #db2828 !important;
}
.sub-menu .menus-box .menu .item.header {
padding-right: 0.2em !important;
cursor: pointer;
}
.sub-menu .menus-box .menu .item.header span {
font-weight: normal;
color: grey;
font-size: 0.8em;
}
.sub-menu .menus-box .menu a {
display: block;
word-break: break-all;
line-height: 1.6 !important;
}
.sub-menu .menus-box .menu .item .menu {
margin-top: 0 !important;
}
.sub-menu .fourth-menu {
margin-left: 1.2em;
}
.sub-menu .fourth-menu .icon,
.sub-menu .third-menu .icon {
float: left !important;
}
/** 右侧文本子菜单 **/ /** 右侧文本子菜单 **/
.text.menu { .text.menu {
overflow-x: auto; overflow-x: auto;

File diff suppressed because one or more lines are too long

View File

@@ -171,10 +171,6 @@ body.expanded .main-menu {
display: none; display: none;
} }
body.expanded .sub-menu {
display: none;
}
body.expanded .main { body.expanded .main {
left: 1em; left: 1em;
} }
@@ -186,6 +182,7 @@ body.expanded .main {
width: 100%; width: 100%;
z-index: 1000; z-index: 1000;
overflow-x: auto; overflow-x: auto;
border: 0 !important;
background: #276ac6 !important; background: #276ac6 !important;
} }
@@ -349,10 +346,12 @@ body.expanded .main {
overflow-y: auto; overflow-y: auto;
background: #276ac6 !important; background: #276ac6 !important;
z-index: 10; z-index: 10;
}
.main-menu .menu { .menu {
background: #276ac6 !important; background: #276ac6 !important;
border: 0 !important;
box-shadow: none !important;
}
} }
.main-menu::-webkit-scrollbar { .main-menu::-webkit-scrollbar {
@@ -405,136 +404,6 @@ body.expanded .main {
background: white; background: white;
} }
/** 子菜单 **/
.main.without-menu .sub-menu {
display: none;
}
.sub-menu {
position: fixed;
left: 8em;
width: 12.5em;
top: 3em;
bottom: 2.8em;
}
.sub-menu .menus-box {
overflow-y: auto;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
padding-right: 0.4em !important;
}
.sub-menu .menus-box::-webkit-scrollbar {
width: 4px;
height: 4px;
}
.sub-menu .menu {
max-width: 12em !important;
border-right: 0 !important;
}
@media screen and (max-width: 512px) {
.sub-menu {
position: relative;
width: 100%;
left: 0;
top: 0;
}
.sub-menu .menus-box {
position: relative !important;
}
.sub-menu .menu {
width: 100% !important;
max-width: 30em !important;
}
.sub-menu .menus-box .menu .item {
width: 100% !important;
max-width: 30em !important;
}
}
.sub-menu .menu .item.active {
font-weight: normal !important;
outline: none !important;
}
.sub-menu .menu .item:not(.header) {
padding-top: 0.7em !important;
padding-bottom: 0.7em !important;
}
.sub-menu .menu .item:not(.header) span {
font-size: 0.8em;
display: block;
margin-top: 0.6em !important;
line-height: 1.5;
}
.sub-menu .menu .item:not(.active):hover {
background: rgba(0, 0, 0, 0.05) !important;
border-top: 1px white solid !important;
border-bottom: 1px white solid !important;
margin-top: -1px !important;
margin-bottom: -1px !important;
}
.sub-menu .menu .item.active {
background: rgba(0, 0, 0, 0.05) !important;
}
.sub-menu .menu .item var {
font-style: normal;
}
.sub-menu .menu .item:not(.active) var.grey {
color: grey;
}
.sub-menu .menu .item span:not(.green) {
color: grey;
}
.sub-menu .menu .item span.red {
color: #db2828 !important;
}
.sub-menu .menus-box .menu .item.header {
padding-right: 0.2em !important;
cursor: pointer;
}
.sub-menu .menus-box .menu .item.header span {
font-weight: normal;
color: grey;
font-size: 0.8em;
}
.sub-menu .menus-box .menu a {
display: block;
word-break: break-all;
line-height: 1.6 !important;
}
.sub-menu .menus-box .menu .item .menu {
margin-top: 0 !important;
}
.sub-menu .fourth-menu {
margin-left: 1.2em;
}
.sub-menu .fourth-menu .icon, .sub-menu .third-menu .icon {
float: left !important;
}
/** 右侧文本子菜单 **/ /** 右侧文本子菜单 **/
.text.menu { .text.menu {
overflow-x: auto; overflow-x: auto;

View File

@@ -0,0 +1,41 @@
{$layout}
<form class="ui form" method="post" 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="productName" v-model="config.productName" maxlength="100"/>
</td>
</tr>
<tr>
<td>用户系统名称 *</td>
<td>
<input type="text" name="userSystemName" v-model="config.userSystemName" maxlength="100"/>
</td>
</tr>
<tr>
<td>是否显示底部开源信息</td>
<td>
<checkbox name="showOpenSourceInfo" v-model="config.showOpenSourceInfo"></checkbox>
</td>
</tr>
<tr>
<td>是否显示版本号</td>
<td>
<checkbox name="showVersion" v-model="config.showVersion"></checkbox>
</td>
</tr>
<tr v-show="config.showVersion">
<td>定制版本号</td>
<td>
<input type="text" name="version" v-model="config.version" maxlength="100"/>
<p class="comment">定制自己的版本号,留空表示使用系统自带的版本号。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -31,6 +31,13 @@
<p class="comment">用户姓名或者公司名称等等。</p> <p class="comment">用户姓名或者公司名称等等。</p>
</td> </td>
</tr> </tr>
<tr>
<td>关联集群 *</td>
<td>
<cluster-selector></cluster-selector>
<p class="comment">用户发布的网站服务会自动部署到此集群。</p>
</td>
</tr>
<tr> <tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td> <td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr> </tr>

View File

@@ -10,6 +10,7 @@
<tr> <tr>
<th>用户名</th> <th>用户名</th>
<th>全名</th> <th>全名</th>
<th>关联集群</th>
<th>手机号</th> <th>手机号</th>
<th>注册时间</th> <th>注册时间</th>
<th class="center width10">状态</th> <th class="center width10">状态</th>
@@ -19,6 +20,10 @@
<tr v-for="user in users"> <tr v-for="user in users">
<td :class="{disabled:!user.isOn}">{{user.username}}</td> <td :class="{disabled:!user.isOn}">{{user.username}}</td>
<td :class="{disabled:!user.isOn}">{{user.fullname}}</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>
<span v-else class="disabled">-</span>
</td>
<td :class="{disabled:!user.isOn}"> <td :class="{disabled:!user.isOn}">
<span v-if="user.mobile.length > 0">{{user.mobile}}</span> <span v-if="user.mobile.length > 0">{{user.mobile}}</span>
<span v-else class="disabled">-</span> <span v-else class="disabled">-</span>

View File

@@ -1,6 +1,7 @@
Tea.context(function () { Tea.context(function () {
this.createUser = function () { this.createUser = function () {
teaweb.popup(Tea.url(".createPopup"), { teaweb.popup(Tea.url(".createPopup"), {
height: "30em",
callback: function () { callback: function () {
teaweb.success("保存成功", function () { teaweb.success("保存成功", function () {
teaweb.reload() teaweb.reload()

View File

@@ -38,6 +38,13 @@
<p class="comment">用户姓名或者公司名称等等。</p> <p class="comment">用户姓名或者公司名称等等。</p>
</td> </td>
</tr> </tr>
<tr>
<td>关联集群 *</td>
<td>
<cluster-selector :v-cluster-id="clusterId"></cluster-selector>
<p class="comment">用户发布的网站服务会自动部署到此集群。</p>
</td>
</tr>
<tr> <tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td> <td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr> </tr>

View File

@@ -20,6 +20,13 @@
{{user.fullname}} {{user.fullname}}
</td> </td>
</tr> </tr>
<tr>
<td>关联集群</td>
<td>
<span v-if="user.cluster != null">{{user.cluster.name}} <link-icon :href="'/clusters/cluster?clusterId=' + user.cluster.id"></link-icon></span>
<span v-else class="disabled">-</span>
</td>
</tr>
<tr> <tr>
<td>手机号</td> <td>手机号</td>
<td> <td>