diff --git a/model/project.go b/model/project.go new file mode 100644 index 0000000..0b944ec --- /dev/null +++ b/model/project.go @@ -0,0 +1,59 @@ +// Copyright 2019 syncd Author. All Rights Reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package model + +import( + "time" +) + +type Project struct { + ID int `gorm:"primary_key"` + Name string `gorm:"type:varchar(100);not null;default:''"` + Description string `gorm:"type:varchar(500);not null;default:''"` + NeedAudit int `gorm:"type:int(11);not null;default:0"` + RepoUrl string `gorm:"type:varchar(500);not null;default:''"` + RepoBranch string `gorm:"type:varchar(100);not null;default:''"` + PreReleaseCluster int `gorm:"type:int(11);not null;default:0"` + OnlineCluster string `gorm:"type:varchar(1000);not null;default:''"` + DeployUser string `gorm:"type:varchar(100);not null;default:''"` + DeployPath string `gorm:"type:varchar(500);not null;default:''"` + PreDeployCmd string `gorm:"type:text;not null"` + AfterDeployCmd string `gorm:"type:text;not null"` + DeployTimeout int `gorm:"type:int(11);not null;default:0"` + Ctime int `gorm:"type:int(11);not null;default:0"` +} + +func (m *Project) TableName() string { + return "syd_project" +} + +func (m *Project) Create() bool { + m.Ctime = int(time.Now().Unix()) + return Create(m) +} + +func (m *Project) Update() bool { + return UpdateByPk(m) +} + +func (m *Project) List(query QueryParam) ([]Project, bool) { + var data []Project + ok := GetMulti(&data, query) + return data, ok +} + +func (m *Project) Count(query QueryParam) (int, bool) { + var count int + ok := Count(m, &count, query) + return count, ok +} + +func (m *Project) Delete() bool { + return DeleteByPk(m) +} + +func (m *Project) Get(id int) bool { + return GetByPk(m, id) +} diff --git a/module/project/member.go b/module/project/member.go index 86a003f..1cf7adb 100644 --- a/module/project/member.go +++ b/module/project/member.go @@ -6,7 +6,6 @@ package project import ( "errors" - //"fmt" "github.com/dreamans/syncd/model" ) @@ -22,6 +21,32 @@ type Member struct { Ctime int `json:"ctime"` } +func (m *Member) Delete() error { + member := &model.ProjectMember{ + ID: m.ID, + } + if ok := member.Delete(); !ok { + return errors.New("remove project member failed") + } + return nil +} + +func (m *Member) Total(spaceId int) (int, error) { + member := &model.ProjectMember{} + total, ok := member.Count(model.QueryParam{ + Where: []model.WhereParam{ + model.WhereParam{ + Field: "space_id", + Prepare: spaceId, + }, + }, + }) + if !ok { + return 0, errors.New("get project member count failed") + } + return total, nil +} + func (m *Member) List(spaceId, offset, limit int) ([]Member, error) { member := &model.ProjectMember{} list, ok := member.List(model.QueryParam{ @@ -29,6 +54,12 @@ func (m *Member) List(spaceId, offset, limit int) ([]Member, error) { Offset: offset, Limit: limit, Order: "id DESC", + Where: []model.WhereParam{ + model.WhereParam{ + Field: "space_id", + Prepare: spaceId, + }, + }, }) if !ok { return nil, errors.New("get project member list failed") @@ -36,7 +67,7 @@ func (m *Member) List(spaceId, offset, limit int) ([]Member, error) { var ( memberList []Member - userIdList []int + userIdList, roleIdList []int ) for _, l := range list { userIdList = append(userIdList, l.UserId) @@ -62,25 +93,40 @@ func (m *Member) List(spaceId, offset, limit int) ([]Member, error) { return nil, errors.New("get project user detail list failed") } userMap := make(map[int]model.User) - var roleIdList []int for _, l := range userList { userMap[l.ID] = l roleIdList = append(roleIdList, l.RoleId) } role := &model.UserRole{} roleList, ok := role.List(model.QueryParam{ - Fields: "id, role_id, username, email, status", + Fields: "id, name", Where: []model.WhereParam{ model.WhereParam{ Field: "id", Tag: "IN", - Prepare: userIdList, + Prepare: roleIdList, }, }, }) if !ok { return nil, errors.New("get project user role list failed") } + roleMap := make(map[int]model.UserRole) + for _, l := range roleList { + roleMap[l.ID] = l + } + + for k, m := range memberList { + if u, ok := userMap[m.UserId]; ok { + memberList[k].Username = u.Username + memberList[k].Email = u.Email + memberList[k].Status = u.Status + if r, ok := roleMap[u.RoleId]; ok { + memberList[k].RoleName = r.Name + } + } + + } return memberList, nil } diff --git a/module/project/project.go b/module/project/project.go new file mode 100644 index 0000000..1f1c1a3 --- /dev/null +++ b/module/project/project.go @@ -0,0 +1,58 @@ +// Copyright 2019 syncd Author. All Rights Reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package project + +import ( + "errors" + //"fmt" + + "github.com/dreamans/syncd/model" + "github.com/dreamans/syncd/util/gostring" +) + +type Project struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + NeedAudit int `json:"need_audit"` + RepoUrl string `json:"repo_url"` + RepoBranch string `json:"repo_branch"` + PreReleaseCluster int `json:"pre_release_cluster"` + OnlineCluster []int `json:"online_cluster"` + DeployUser string `json:"deploy_user"` + DeployPath string `json:"deploy_path"` + PreDeployCmd string `json:"pre_deploy_cmd"` + AfterDeployCmd string `json:"after_deploy_cmd"` + DeployTimeout int `json:"deploy_timeout"` + Ctime int `json:"ctime"` +} + +func (p *Project) CreateOrUpdate() error { + project := &model.Project{ + ID: p.ID, + Name: p.Name, + Description: p.Description, + NeedAudit: p.NeedAudit, + RepoUrl: p.RepoUrl, + RepoBranch: p.RepoBranch, + PreReleaseCluster: p.PreReleaseCluster, + OnlineCluster: gostring.JoinIntSlice2String(p.OnlineCluster, ","), + DeployUser: p.DeployUser, + DeployPath: p.DeployPath, + PreDeployCmd: p.PreDeployCmd, + AfterDeployCmd: p.AfterDeployCmd, + DeployTimeout: p.DeployTimeout, + } + if project.ID > 0 { + if ok := project.Update(); !ok { + return errors.New("create project failed") + } + } else { + if ok := project.Create(); !ok { + return errors.New("update project failed") + } + } + return nil +} diff --git a/router/project/member.go b/router/project/member.go index c62f794..d216767 100644 --- a/router/project/member.go +++ b/router/project/member.go @@ -9,7 +9,7 @@ import ( "github.com/dreamans/syncd/module/user" "github.com/dreamans/syncd/module/project" "github.com/dreamans/syncd/render" - //"github.com/dreamans/syncd/util/gostring" + "github.com/dreamans/syncd/util/gostring" ) type MemberAddQueryBind struct { @@ -23,13 +23,44 @@ type MemberListQueryBind struct { Limit int `form:"limit" binding:"required,gte=1,lte=999"` } +func MemberRemove(c *gin.Context) { + id := gostring.Str2Int(c.PostForm("id")) + if id == 0 { + render.ParamError(c, "id cannot be empty") + return + } + member := &project.Member{ + ID: id, + } + if err := member.Delete(); err != nil { + render.AppError(c, err.Error()) + return + } + render.JSON(c, nil) +} + func MemberList(c *gin.Context) { var query MemberListQueryBind if err := c.ShouldBind(&query); err != nil { render.ParamError(c, err.Error()) return } - + m := &project.Member{} + memberList, err := m.List(query.SpaceId, query.Offset, query.Limit) + if err != nil { + render.AppError(c, err.Error()) + return + } + total, err := m.Total(query.SpaceId) + if err != nil { + render.AppError(c, err.Error()) + return + } + + render.JSON(c, gin.H{ + "list": memberList, + "total": total, + }) } func MemberSearch(c *gin.Context) { diff --git a/router/project/project.go b/router/project/project.go new file mode 100644 index 0000000..a52837b --- /dev/null +++ b/router/project/project.go @@ -0,0 +1,75 @@ +// Copyright 2019 syncd Author. All Rights Reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package project + +import ( + "github.com/gin-gonic/gin" + "github.com/dreamans/syncd/render" + "github.com/dreamans/syncd/module/project" + "github.com/dreamans/syncd/util/gostring" + "github.com/dreamans/syncd/util/goslice" +) + +type ProjectFormBind struct { + ID int `form:"id"` + Name string `form:"name" binding:"required"` + Description string `form:"description"` + NeedAudit int `form:"need_audit"` + RepoUrl string `form:"repo_url" binding:"required"` + RepoBranch string `form:"repo_branch"` + PreReleaseCluster int `form:"pre_release_cluster"` + OnlineCluster []int `form:"online_cluster" binding:"required"` + DeployUser string `form:"deploy_user" binding:"required"` + DeployPath string `form:"deploy_path" binding:"required"` + PreDeployCmd string `form:"pre_deploy_cmd"` + AfterDeployCmd string `form:"after_deploy_cmd"` + DeployTimeout int `form:"deploy_timeout" binding:"required"` +} + +func ProjectAdd(c *gin.Context) { + projectCreateOrUpdate(c) +} + +func ProjectUpdate(c *gin.Context) { + id := gostring.Str2Int(c.PostForm("id")) + if id == 0 { + render.ParamError(c, "id cannot be empty") + return + } + projectCreateOrUpdate(c) +} + +func projectCreateOrUpdate(c *gin.Context) { + var projectForm ProjectFormBind + if err := c.ShouldBind(&projectForm); err != nil { + render.ParamError(c, err.Error()) + return + } + onlineCluster := goslice.FilterSliceInt(projectForm.OnlineCluster) + if len(onlineCluster) == 0 { + render.ParamError(c, "online_cluster cannot be empty") + return + } + proj := &project.Project{ + ID: projectForm.ID, + Name: projectForm.Name, + Description: projectForm.Description, + NeedAudit: projectForm.NeedAudit, + RepoUrl: projectForm.RepoUrl, + RepoBranch: projectForm.RepoBranch, + PreReleaseCluster: projectForm.PreReleaseCluster, + OnlineCluster: onlineCluster, + DeployUser: projectForm.DeployUser, + DeployPath: projectForm.DeployPath, + PreDeployCmd: projectForm.PreDeployCmd, + AfterDeployCmd: projectForm.AfterDeployCmd, + DeployTimeout: projectForm.DeployTimeout, + } + if err := proj.CreateOrUpdate(); err != nil { + render.AppError(c, err.Error()) + return + } + render.Success(c) +} \ No newline at end of file diff --git a/router/route/route.go b/router/route/route.go index 6dc5245..0eaa6d9 100644 --- a/router/route/route.go +++ b/router/route/route.go @@ -48,5 +48,8 @@ func RegisterRoute() { api.GET("/project/member/search", project.MemberSearch) api.POST("/project/member/add", project.MemberAdd) api.GET("/project/member/list", project.MemberList) + api.POST("/project/member/remove", project.MemberRemove) + api.POST("/project/add", project.ProjectAdd) + api.POST("/project/update", project.ProjectUpdate) } } diff --git a/web/public/index.html b/web/public/index.html index 268125b..0528426 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -4,7 +4,7 @@ - + web diff --git a/web/src/api/project.js b/web/src/api/project.js index c3ab5f3..4b7a412 100644 --- a/web/src/api/project.js +++ b/web/src/api/project.js @@ -30,4 +30,20 @@ export function addMemberApi(data) { export function listMemberApi(params) { return get('/project/member/list', params) -} \ No newline at end of file +} + +export function removeMemberApi(data) { + return post('/project/member/remove', data) +} + +export function newProjectApi(data) { + return post('/project/add', data) +} + +export function updateProjectApi(data) { + return post('/project/update', data) +} + +export function listProjectApi(params) { + return get('/project/list', params) +} diff --git a/web/src/lang/zh_cn.js b/web/src/lang/zh_cn.js index 0804e3f..e337f61 100644 --- a/web/src/lang/zh_cn.js +++ b/web/src/lang/zh_cn.js @@ -27,7 +27,7 @@ export default { 'cluster_manage': '集群管理', 'server_manage': '服务器管理', 'add_cluster': '新增集群', - //'please_input_content': '请输入内容', + 'please_input_keyword': '关键词搜索', 'please_input_keyword_id_or_name': '关键词搜索,支持ID、名称', 'network_error': '网络错误', 'unknown_error': '未知错误', @@ -98,4 +98,43 @@ export default { 'description': '描述信息', 'add_member_to_space': '添加成员到项目空间', 'add': '添加', + 'project_member_select_space_tips': '成员管理需要指定项目空间,请点击 \'选择项目空间\' 按钮,在弹出窗口中进行选择。', + 'prompt_message': '提示信息', + 'select_project_space': '选择项目空间', + 'switch_project_space': '切换项目空间', + 'add_new_member': '添加新成员', + 'looking_for_users': '正在查找用户...', + 'user_not_found': '未查找到用户', + 'search_for_users_by_username_and_email': '通过用户名、邮箱搜索用户', + 'search_and_select_users_before_adding': '请先搜索并选择用户后再添加', + 'member_added_successfully': '成员添加成功', + 'member_already_exists_do_not_add_repeat': '成员已经存在,请勿重复添加', + 'remove': '移除', + 'project_select_space_tips': '项目管理需要指定项目空间,请点击 \'选择项目空间\' 按钮,在弹出窗口中进行选择。', + 'add_project': '新增项目', + 'project_name': '项目名称', + 'please_input_project_name': '请输入项目名称', + 'please_input_project_description': '请输入项目描述信息', + 'open_audit': '开启审核', + 'if_open_apply_need_audit': '开启后,上线单需要审核通过后才能发起上线', + 'repo_url': '仓库地址', + 'please_input_repo_url': '请输入仓库地址', + 'repo_url_cannot_empty': '仓库地址不能为空', + 'repo_branch': '指定上线分支', + 'online_cluster': '线上集群', + 'pre_release_cluster': '预发布集群', + 'online_cluster_cannot_empty': '线上集群不能为空', + 'user': '用户', + 'user_cannot_empty': '用户不能为空', + 'deploy_user': '目标机用户', + 'path': '目录', + 'deploy_path': '代码/包部署的目录', + 'deploy_path_cannot_empty': '请设置代码部署目录', + 'pre_deploy_cmd': '部署前运行命令', + 'pre_deploy_cmd_tips': '代码部署之前在目标机运行的命令,每行一个命令', + 'after_deploy_cmd': '部署后运行命令', + 'after_deploy_cmd_tips': '代码部署之后在目标机运行的命令,每行一个命令', + 'deploy_timeout': '部署超时时间(秒)', + 'deploy_timeout_tips': '部署命令最大运行时间', + 'project_enable': '项目启用', } \ No newline at end of file diff --git a/web/src/scss/app.scss b/web/src/scss/app.scss index f6da851..0155ce9 100644 --- a/web/src/scss/app.scss +++ b/web/src/scss/app.scss @@ -178,4 +178,22 @@ html, body { width: 100%; margin: 24px 0; background: #e8e8e8; +} +.app-form-subtitle { + margin-bottom: 10px; + font-weight: 500; +} +.app-form-box { + border-radius: 4px; + border: 1px solid #d9d9d9; + .item { + list-style: none; + display: flex; + justify-content: space-between; + margin: 5px 25px; + border-bottom: 1px solid #d9d9d9; + &:last-child { + border-bottom: none; + } + } } \ No newline at end of file diff --git a/web/src/view/project/Member.vue b/web/src/view/project/Member.vue index 00fca6a..29dbe93 100644 --- a/web/src/view/project/Member.vue +++ b/web/src/view/project/Member.vue @@ -3,26 +3,28 @@
- 选择项目空间 + {{ $t('select_project_space') }}
- 切换项目空间 + {{ $t('switch_project_space') }}
-
-

{{ this.spaceDetail.name }}

-

{{ this.spaceDetail.description }}

+
+
+

{{ this.spaceDetail.name }}

+

{{ this.spaceDetail.description }}

+
-
添加新成员
+
{{ $t('add_new_member') }}
+ :placeholder="$t('search_for_users_by_username_and_email')"> @@ -49,19 +51,26 @@ size="medium" v-loading="tableLoading" :data="tableData"> - -
-

{{ scope.row.name }}

-

{{ scope.row.description }}

-
+ + + + + - + @@ -79,9 +88,9 @@ + :title="$t('select_project_space')">
- +
@@ -90,7 +99,7 @@