mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-10 23:47:39 +08:00
fix: Fix issue where website group can still be deleted even if it contains other websites (#8049)
This commit is contained in:
parent
451b69072d
commit
adbf44a176
27 changed files with 619 additions and 13 deletions
|
@ -67,4 +67,5 @@ var (
|
|||
|
||||
websiteCAService = service.NewIWebsiteCAService()
|
||||
taskService = service.NewITaskService()
|
||||
groupService = service.NewIGroupService()
|
||||
)
|
||||
|
|
96
agent/app/api/v2/group.go
Normal file
96
agent/app/api/v2/group.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags System Group
|
||||
// @Summary Create group
|
||||
// @Accept json
|
||||
// @Param request body dto.GroupCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /agent/groups [post]
|
||||
// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"}
|
||||
func (b *BaseApi) CreateGroup(c *gin.Context) {
|
||||
var req dto.GroupCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := groupService.Create(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags System Group
|
||||
// @Summary Delete group
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /agent/groups/del [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"name","output_value":"name"},{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"}
|
||||
func (b *BaseApi) DeleteGroup(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := groupService.Delete(req.ID); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags System Group
|
||||
// @Summary Update group
|
||||
// @Accept json
|
||||
// @Param request body dto.GroupUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /agent/groups/update [post]
|
||||
// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"}
|
||||
func (b *BaseApi) UpdateGroup(c *gin.Context) {
|
||||
var req dto.GroupUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := groupService.Update(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags System Group
|
||||
// @Summary List groups
|
||||
// @Accept json
|
||||
// @Param request body dto.GroupSearch true "request"
|
||||
// @Success 200 {array} dto.OperateByType
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /agent/groups/search [post]
|
||||
func (b *BaseApi) ListGroup(c *gin.Context) {
|
||||
var req dto.OperateByType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list, err := groupService.List(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
29
agent/app/dto/group.go
Normal file
29
agent/app/dto/group.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package dto
|
||||
|
||||
type GroupCreate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type GroupSearch struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
}
|
||||
|
||||
type GroupUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
}
|
||||
|
||||
type GroupInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
}
|
||||
|
||||
type OperateByType struct {
|
||||
Type string `json:"type"`
|
||||
}
|
8
agent/app/model/group.go
Normal file
8
agent/app/model/group.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package model
|
||||
|
||||
type Group struct {
|
||||
BaseModel
|
||||
IsDefault bool `json:"isDefault"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
56
agent/app/repo/group.go
Normal file
56
agent/app/repo/group.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
type GroupRepo struct{}
|
||||
|
||||
type IGroupRepo interface {
|
||||
Get(opts ...DBOption) (model.Group, error)
|
||||
GetList(opts ...DBOption) ([]model.Group, error)
|
||||
Create(group *model.Group) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewIGroupRepo() IGroupRepo {
|
||||
return &GroupRepo{}
|
||||
}
|
||||
|
||||
func (g *GroupRepo) Get(opts ...DBOption) (model.Group, error) {
|
||||
var group model.Group
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&group).Error
|
||||
return group, err
|
||||
}
|
||||
|
||||
func (g *GroupRepo) GetList(opts ...DBOption) ([]model.Group, error) {
|
||||
var groups []model.Group
|
||||
db := global.DB.Model(&model.Group{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&groups).Error
|
||||
return groups, err
|
||||
}
|
||||
|
||||
func (g *GroupRepo) Create(group *model.Group) error {
|
||||
return global.DB.Create(group).Error
|
||||
}
|
||||
|
||||
func (g *GroupRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.Group{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (g *GroupRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.Group{}).Error
|
||||
}
|
|
@ -45,4 +45,6 @@ var (
|
|||
favoriteRepo = repo.NewIFavoriteRepo()
|
||||
|
||||
taskRepo = repo.NewITaskRepo()
|
||||
|
||||
groupRepo = repo.NewIGroupRepo()
|
||||
)
|
||||
|
|
87
agent/app/service/group.go
Normal file
87
agent/app/service/group.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
type GroupService struct{}
|
||||
|
||||
type IGroupService interface {
|
||||
List(req dto.OperateByType) ([]dto.GroupInfo, error)
|
||||
Create(req dto.GroupCreate) error
|
||||
Update(req dto.GroupUpdate) error
|
||||
Delete(id uint) error
|
||||
}
|
||||
|
||||
func NewIGroupService() IGroupService {
|
||||
return &GroupService{}
|
||||
}
|
||||
|
||||
func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) {
|
||||
options := []repo.DBOption{
|
||||
repo.WithOrderBy("is_default desc"),
|
||||
repo.WithOrderBy("created_at desc"),
|
||||
}
|
||||
if len(req.Type) != 0 {
|
||||
options = append(options, repo.WithByType(req.Type))
|
||||
}
|
||||
var (
|
||||
groups []model.Group
|
||||
err error
|
||||
)
|
||||
groups, err = groupRepo.GetList(options...)
|
||||
if err != nil {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
var dtoUsers []dto.GroupInfo
|
||||
for _, group := range groups {
|
||||
var item dto.GroupInfo
|
||||
if err := copier.Copy(&item, &group); err != nil {
|
||||
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
dtoUsers = append(dtoUsers, item)
|
||||
}
|
||||
return dtoUsers, err
|
||||
}
|
||||
|
||||
func (u *GroupService) Create(req dto.GroupCreate) error {
|
||||
group, _ := groupRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type))
|
||||
if group.ID != 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
if err := copier.Copy(&group, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
if err := groupRepo.Create(&group); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *GroupService) Delete(id uint) error {
|
||||
group, _ := groupRepo.Get(repo.WithByID(id))
|
||||
if group.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if group.IsDefault {
|
||||
return buserr.New("ErrGroupIsDefault")
|
||||
}
|
||||
if group.Type == "website" {
|
||||
websites, _ := websiteRepo.List(websiteRepo.WithGroupID(group.ID))
|
||||
if len(websites) > 0 {
|
||||
return buserr.New("ErrGroupIsInWebsiteUse")
|
||||
}
|
||||
}
|
||||
return groupRepo.Delete(repo.WithByID(id))
|
||||
}
|
||||
|
||||
func (u *GroupService) Update(req dto.GroupUpdate) error {
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["name"] = req.Name
|
||||
upMap["is_default"] = req.IsDefault
|
||||
return groupRepo.Update(req.ID, upMap)
|
||||
}
|
|
@ -19,6 +19,8 @@ ErrTypePort: "Port {{ .name }} format error"
|
|||
Success: "Success"
|
||||
Failed: "Failed"
|
||||
SystemRestart: "System restart causes task interruption"
|
||||
ErrGroupIsDefault: "Default group, cannot be deleted"
|
||||
ErrGroupIsInWebsiteUse: "Group is being used by other websites, cannot be deleted"
|
||||
|
||||
#backup
|
||||
ErrBackupLocalDelete: "Deleting local server backup accounts is not currently supported."
|
||||
|
|
|
@ -28,6 +28,8 @@ ErrTypePortRange: 'ポートレンジは1-65535の間である必要がありま
|
|||
Success: "成功"
|
||||
Failed: "失敗した"
|
||||
SystemRestart: "システムの再起動により、タスクが中断されます"
|
||||
ErrGroupIsDefault: "デフォルトグループ、削除できません"
|
||||
ErrGroupIsInWebsiteUse: "グループは他のウェブサイトで使用されています、削除できません"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{.Detail}}ポートはすでに使用されています"
|
||||
|
|
|
@ -29,6 +29,8 @@ ErrTypePortRange: '포트 범위는 1-65535 사이여야 합니다'
|
|||
Success: "성공"
|
||||
Failed: "실패"
|
||||
SystemRestart: "시스템 재시작으로 인해 작업이 중단되었습니다"
|
||||
ErrGroupIsDefault: "기본 그룹, 삭제할 수 없습니다"
|
||||
ErrGroupIsInWebsiteUse: "그룹이 다른 웹사이트에서 사용 중입니다, 삭제할 수 없습니다"
|
||||
|
||||
# 애플리케이션
|
||||
ErrPortInUsed: "{{ .detail }} 포트가 이미 사용 중입니다"
|
||||
|
|
|
@ -29,6 +29,8 @@ ErrTypePortRange: 'Julat port perlu berada di antara 1-65535'
|
|||
Success: "Berjaya"
|
||||
Failed: "Gagal"
|
||||
SystemRestart: "Mulakan semula sistem menyebabkan gangguan tugas"
|
||||
ErrGroupIsDefault: "Kumpulan lalai, tidak boleh dipadam"
|
||||
ErrGroupIsInWebsiteUse: "Kumpulan sedang digunakan oleh laman web lain, tidak boleh dipadam"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "Port {{ .detail }} sudah digunakan"
|
||||
|
|
|
@ -28,6 +28,8 @@ ErrTypePortRange: 'O intervalo da porta deve estar entre 1-65535'
|
|||
Success: "Sucesso"
|
||||
Failed: "Falhou"
|
||||
SystemRestart: "A reinicialização do sistema causa interrupção da tarefa"
|
||||
ErrGroupIsDefault: "Grupo padrão, não pode ser excluído"
|
||||
ErrGroupIsInWebsiteUse: "O grupo está sendo usado por outros sites, não pode ser excluído"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "A porta {{ .detail }} já está em uso"
|
||||
|
|
|
@ -29,6 +29,8 @@ ErrTypePortRange: "Диапазон портов должен быть межд
|
|||
Success: "Успех"
|
||||
Failed: "Неудача"
|
||||
SystemRestart: "Перезагрузка системы приводит к прерыванию задачи"
|
||||
ErrGroupIsDefault: "Группа по умолчанию, невозможно удалить"
|
||||
ErrGroupIsInWebsiteUse: "Группа используется другими сайтами, невозможно удалить"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "Порт {{ .detail }} уже используется"
|
||||
|
|
|
@ -24,6 +24,8 @@ Success: "成功"
|
|||
Failed: "失敗"
|
||||
SystemRestart: "系統重啟導致任務中斷"
|
||||
ErrInvalidChar: "禁止使用非法字元"
|
||||
ErrGroupIsDefault: "預設分組,無法刪除"
|
||||
ErrGroupIsInWebsiteUse: "分組正在被其他網站使用,無法刪除"
|
||||
|
||||
#backup
|
||||
ErrBackupLocalDelete: "暫不支持刪除本地伺服器備份帳號"
|
||||
|
|
|
@ -30,6 +30,8 @@ ErrTypePortRange: '端口范围需要在 1-65535 之间'
|
|||
Success: "成功"
|
||||
Failed: "失败"
|
||||
SystemRestart: "系统重启导致任务中断"
|
||||
ErrGroupIsDefault: "默认分组,无法删除"
|
||||
ErrGroupIsInWebsiteUse: "分组正在被其他网站使用,无法删除"
|
||||
|
||||
#backup
|
||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||
|
|
|
@ -26,6 +26,7 @@ func InitAgentDB() {
|
|||
migrations.UpdateApp,
|
||||
migrations.AddOllamaModel,
|
||||
migrations.UpdateSettingStatus,
|
||||
migrations.InitDefault,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
@ -57,6 +57,7 @@ var AddTable = &gormigrate.Migration{
|
|||
&model.WebsiteDnsAccount{},
|
||||
&model.WebsiteDomain{},
|
||||
&model.WebsiteSSL{},
|
||||
&model.Group{},
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -285,3 +286,13 @@ var UpdateSettingStatus = &gormigrate.Migration{
|
|||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var InitDefault = &gormigrate.Migration{
|
||||
ID: "20250301-init-default",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Group{Name: "Default", Type: "website", IsDefault: true}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
|
@ -22,5 +22,6 @@ func commonGroups() []CommonRouter {
|
|||
&ProcessRouter{},
|
||||
&WebsiteCARouter{},
|
||||
&AIToolsRouter{},
|
||||
&GroupRouter{},
|
||||
}
|
||||
}
|
||||
|
|
20
agent/router/ro_group.go
Normal file
20
agent/router/ro_group.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type GroupRouter struct {
|
||||
}
|
||||
|
||||
func (a *GroupRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
groupRouter := Router.Group("groups")
|
||||
baseApi := v2.ApiGroupApp.BaseApi
|
||||
{
|
||||
groupRouter.POST("", baseApi.CreateGroup)
|
||||
groupRouter.POST("/del", baseApi.DeleteGroup)
|
||||
groupRouter.POST("/update", baseApi.UpdateGroup)
|
||||
groupRouter.POST("/search", baseApi.ListGroup)
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ type IGroupRepo interface {
|
|||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...global.DBOption) error
|
||||
|
||||
WithByDefault(isDefalut bool) global.DBOption
|
||||
WithByDefault(isDefault bool) global.DBOption
|
||||
CancelDefault(groupType string) error
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,9 @@ func NewIGroupRepo() IGroupRepo {
|
|||
return &GroupRepo{}
|
||||
}
|
||||
|
||||
func (c *GroupRepo) WithByDefault(isDefalut bool) global.DBOption {
|
||||
func (c *GroupRepo) WithByDefault(isDefault bool) global.DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("is_default = ?", isDefalut)
|
||||
return g.Where("is_default = ?", isDefault)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -166,20 +166,20 @@ var InitSetting = &gormigrate.Migration{
|
|||
var InitHost = &gormigrate.Migration{
|
||||
ID: "20240816-init-host",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
hostGroup := &model.Group{Name: "default", Type: "host", IsDefault: true}
|
||||
hostGroup := &model.Group{Name: "Default", Type: "host", IsDefault: true}
|
||||
if err := tx.Create(hostGroup).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Group{Name: "default", Type: "node", IsDefault: true}).Error; err != nil {
|
||||
if err := tx.Create(&model.Group{Name: "Default", Type: "node", IsDefault: true}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Group{Name: "default", Type: "command", IsDefault: true}).Error; err != nil {
|
||||
if err := tx.Create(&model.Group{Name: "Default", Type: "command", IsDefault: true}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Group{Name: "default", Type: "website", IsDefault: true}).Error; err != nil {
|
||||
if err := tx.Create(&model.Group{Name: "Default", Type: "website", IsDefault: true}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Group{Name: "default", Type: "redis", IsDefault: true}).Error; err != nil {
|
||||
if err := tx.Create(&model.Group{Name: "Default", Type: "redis", IsDefault: true}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
host := model.Host{
|
||||
|
|
|
@ -13,3 +13,16 @@ export const updateGroup = (params: Group.GroupUpdate) => {
|
|||
export const deleteGroup = (id: number) => {
|
||||
return http.post(`/core/groups/del`, { id: id });
|
||||
};
|
||||
|
||||
export const getAgentGroupList = (type: string) => {
|
||||
return http.post<Array<Group.GroupInfo>>(`/groups/search`, { type: type });
|
||||
};
|
||||
export const createAgentGroup = (params: Group.GroupCreate) => {
|
||||
return http.post<Group.GroupCreate>(`/groups`, params);
|
||||
};
|
||||
export const updateAgentGroup = (params: Group.GroupUpdate) => {
|
||||
return http.post(`/groups/update`, params);
|
||||
};
|
||||
export const deleteAgentGroup = (id: number) => {
|
||||
return http.post(`/groups/del`, { id: id });
|
||||
};
|
||||
|
|
83
frontend/src/components/agent-group/change.vue
Normal file
83
frontend/src/components/agent-group/change.vue
Normal file
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<DrawerPro v-model="drawerVisible" :header="$t('terminal.groupChange')" :back="handleClose" size="small">
|
||||
<el-form @submit.prevent ref="hostInfoRef" label-position="top" :model="dialogData" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.group')" prop="group">
|
||||
<el-select filterable v-model="dialogData.groupID" clearable style="width: 100%">
|
||||
<div v-for="item in groupList" :key="item.id">
|
||||
<el-option :label="item.name" :value="item.id" />
|
||||
</div>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit(hostInfoRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import type { ElForm } from 'element-plus';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { getGroupList } from '@/api/modules/group';
|
||||
|
||||
const loading = ref();
|
||||
interface DialogProps {
|
||||
group: string;
|
||||
groupType: string;
|
||||
}
|
||||
const drawerVisible = ref(false);
|
||||
const dialogData = ref({
|
||||
groupID: 0,
|
||||
groupType: '',
|
||||
});
|
||||
|
||||
const groupList = ref();
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value.groupType = params.groupType;
|
||||
loadGroups(params.group);
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits(['search', 'change']);
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const hostInfoRef = ref<FormInstance>();
|
||||
const rules = reactive({
|
||||
groupID: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const loadGroups = async (groupName: string) => {
|
||||
const res = await getGroupList(dialogData.value.groupType);
|
||||
groupList.value = res.data;
|
||||
for (const group of groupList.value) {
|
||||
if (group.name === groupName) {
|
||||
dialogData.value.groupID = group.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
emit('change', Number(dialogData.value.groupID));
|
||||
loading.value = false;
|
||||
drawerVisible.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
179
frontend/src/components/agent-group/index.vue
Normal file
179
frontend/src/components/agent-group/index.vue
Normal file
|
@ -0,0 +1,179 @@
|
|||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('commons.table.group')" @close="handleClose" size="large" :back="handleClose">
|
||||
<template #content>
|
||||
<ComplexTable :data="data" @search="search()">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="openCreate">{{ $t('website.createGroup') }}</el-button>
|
||||
</template>
|
||||
<el-table-column :label="$t('commons.table.name')" prop="name">
|
||||
<template #default="{ row }">
|
||||
<div v-if="!row.edit">
|
||||
<span v-if="row.name === 'default'">
|
||||
{{ $t('commons.table.default') }}
|
||||
</span>
|
||||
<span v-if="row.name !== 'default'">{{ row.name }}</span>
|
||||
<el-tag v-if="row.isDefault" type="success" class="ml-2" size="small">
|
||||
({{ $t('commons.table.default') }})
|
||||
</el-tag>
|
||||
|
||||
<el-tag type="warning" size="small" class="ml-4" v-if="row.isDelete">
|
||||
{{ $t('app.takeDown') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<el-form @submit.prevent ref="groupForm" v-if="row.edit" :model="row">
|
||||
<el-form-item prop="name" v-if="row.edit" :rules="Rules.name">
|
||||
<div style="margin-top: 20px; width: 100%"><el-input v-model="row.name" /></div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="$t('commons.table.operate')">
|
||||
<template #default="{ row, $index }">
|
||||
<div>
|
||||
<el-button link v-if="row.edit" type="primary" @click="saveGroup(groupForm, row)">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
<el-button link v-if="!row.edit" type="primary" @click="editGroup($index)">
|
||||
{{ $t('commons.button.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
v-if="!row.edit"
|
||||
:disabled="row.isDefault"
|
||||
type="primary"
|
||||
@click="removeGroup($index)"
|
||||
>
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
<el-button link v-if="row.edit" type="primary" @click="search()">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
v-if="!row.edit && !row.isDefault && !row.isDelete"
|
||||
type="primary"
|
||||
@click="setDefault(row)"
|
||||
>
|
||||
{{ $t('website.setDefault') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { createAgentGroup, deleteAgentGroup, getAgentGroupList, updateAgentGroup } from '@/api/modules/group';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Group } from '@/api/interface/group';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { FormInstance } from 'element-plus';
|
||||
|
||||
const open = ref(false);
|
||||
const type = ref();
|
||||
const data = ref();
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
data.value = [];
|
||||
emit('search');
|
||||
};
|
||||
interface DialogProps {
|
||||
type: string;
|
||||
}
|
||||
|
||||
const groupForm = ref<FormInstance>();
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
type.value = params.type;
|
||||
open.value = true;
|
||||
search();
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const search = () => {
|
||||
getAgentGroupList(type.value).then((res) => {
|
||||
data.value = res.data || [];
|
||||
});
|
||||
};
|
||||
|
||||
const saveGroup = async (formEl: FormInstance, group: Group.GroupInfo) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
group.type = type.value;
|
||||
if (group.id == 0) {
|
||||
createAgentGroup(group).then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
search();
|
||||
});
|
||||
} else {
|
||||
updateAgentGroup(group).then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
search();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const setDefault = (group: Group.GroupInfo) => {
|
||||
group.isDefault = true;
|
||||
group.type = type.value;
|
||||
updateAgentGroup(group).then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
search();
|
||||
});
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
for (const d of data.value) {
|
||||
if (d.name == '') {
|
||||
return;
|
||||
}
|
||||
if (d.edit) {
|
||||
d.edit = false;
|
||||
}
|
||||
}
|
||||
const g = {
|
||||
id: 0,
|
||||
name: '',
|
||||
isDefault: false,
|
||||
edit: true,
|
||||
status: 'Enable',
|
||||
};
|
||||
data.value.unshift(g);
|
||||
};
|
||||
|
||||
const removeGroup = (index: number) => {
|
||||
const group = data.value[index];
|
||||
|
||||
if (group.id > 0) {
|
||||
deleteAgentGroup(group.id).then(() => {
|
||||
data.value.splice(index, 1);
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
});
|
||||
} else {
|
||||
data.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const editGroup = (index: number) => {
|
||||
for (const i in data.value) {
|
||||
const d = data.value[i];
|
||||
if (d.name == '') {
|
||||
data.value.splice(Number(i), 1);
|
||||
}
|
||||
if (d.edit) {
|
||||
d.edit = false;
|
||||
}
|
||||
}
|
||||
data.value[index].edit = true;
|
||||
};
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
</script>
|
|
@ -42,6 +42,9 @@ router.beforeEach((to, from, next) => {
|
|||
if (to.path === '/apps/all' && to.query.install != undefined) {
|
||||
return next();
|
||||
}
|
||||
// if (to.query.uncached != undefined) {
|
||||
// return next();
|
||||
// }
|
||||
|
||||
const activeMenuKey = 'cachedRoute' + (to.meta.activeMenu || '');
|
||||
const cachedRoute = localStorage.getItem(activeMenuKey);
|
||||
|
|
|
@ -41,7 +41,7 @@ import { computed, onMounted, reactive, ref } from 'vue';
|
|||
import { FormInstance } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { getGroupList } from '@/api/modules/group';
|
||||
import { getAgentGroupList } from '@/api/modules/group';
|
||||
import { Group } from '@/api/interface/group';
|
||||
|
||||
const websiteForm = ref<FormInstance>();
|
||||
|
@ -87,7 +87,7 @@ const submit = async (formEl: FormInstance | undefined) => {
|
|||
});
|
||||
};
|
||||
const search = async () => {
|
||||
const res = await getGroupList('website');
|
||||
const res = await getAgentGroupList('website');
|
||||
groups.value = res.data;
|
||||
|
||||
getWebsite(websiteId.value).then((res) => {
|
||||
|
|
|
@ -245,7 +245,7 @@ import DefaultHtml from '@/views/website/website/html/index.vue';
|
|||
import CreateWebSite from '@/views/website/website/create/index.vue';
|
||||
import DeleteWebsite from '@/views/website/website/delete/index.vue';
|
||||
import NginxConfig from '@/views/website/website/nginx/index.vue';
|
||||
import GroupDialog from '@/components/group/index.vue';
|
||||
import GroupDialog from '@/components/agent-group/index.vue';
|
||||
import AppStatus from '@/components/app-status/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
|
@ -257,7 +257,7 @@ import { ElMessageBox } from 'element-plus';
|
|||
import { dateFormatSimple } from '@/utils/util';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getGroupList } from '@/api/modules/group';
|
||||
import { getAgentGroupList } from '@/api/modules/group';
|
||||
import { Group } from '@/api/interface/group';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
@ -359,7 +359,7 @@ const search = async () => {
|
|||
};
|
||||
|
||||
const listGroup = async () => {
|
||||
const res = await getGroupList('website');
|
||||
const res = await getAgentGroupList('website');
|
||||
groups.value = res.data;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue