mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-12-18 11:17:49 +08:00
feat: 完成镜像仓库管理
This commit is contained in:
parent
a79ba71ef4
commit
dcbf92ac12
@ -14,6 +14,7 @@ var (
|
|||||||
backupService = service.ServiceGroupApp.BackupService
|
backupService = service.ServiceGroupApp.BackupService
|
||||||
groupService = service.ServiceGroupApp.GroupService
|
groupService = service.ServiceGroupApp.GroupService
|
||||||
containerService = service.ServiceGroupApp.ContainerService
|
containerService = service.ServiceGroupApp.ContainerService
|
||||||
|
imageRepoService = service.ServiceGroupApp.ImageRepoService
|
||||||
commandService = service.ServiceGroupApp.CommandService
|
commandService = service.ServiceGroupApp.CommandService
|
||||||
operationService = service.ServiceGroupApp.OperationService
|
operationService = service.ServiceGroupApp.OperationService
|
||||||
fileService = service.ServiceGroupApp.FileService
|
fileService = service.ServiceGroupApp.FileService
|
||||||
|
92
backend/app/api/v1/image_repo.go
Normal file
92
backend/app/api/v1/image_repo.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||||
|
"github.com/1Panel-dev/1Panel/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/global"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *BaseApi) GetRepoList(c *gin.Context) {
|
||||||
|
var req dto.PageInfo
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total, list, err := imageRepoService.Page(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, dto.PageResult{
|
||||||
|
Items: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) CreateRepo(c *gin.Context) {
|
||||||
|
var req dto.ImageRepoCreate
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := imageRepoService.Create(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) DeleteRepo(c *gin.Context) {
|
||||||
|
var req dto.BatchDeleteReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := imageRepoService.BatchDelete(req.Ids); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) UpdateRepo(c *gin.Context) {
|
||||||
|
var req dto.ImageRepoUpdate
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, err := helper.GetParamID(c)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
upMap := make(map[string]interface{})
|
||||||
|
upMap["download_url"] = req.DownloadUrl
|
||||||
|
upMap["repo_name"] = req.RepoName
|
||||||
|
upMap["username"] = req.Username
|
||||||
|
upMap["password"] = req.Password
|
||||||
|
upMap["auth"] = req.Auth
|
||||||
|
if err := imageRepoService.Update(id, upMap); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
31
backend/app/dto/image_repo.go
Normal file
31
backend/app/dto/image_repo.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ImageRepoCreate struct {
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
DownloadUrl string `json:"downloadUrl"`
|
||||||
|
RepoName string `json:"repoName"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Auth bool `json:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageRepoUpdate struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
DownloadUrl string `json:"downloadUrl"`
|
||||||
|
RepoName string `json:"repoName"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Auth bool `json:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageRepoInfo struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DownloadUrl string `json:"downloadUrl"`
|
||||||
|
RepoName string `json:"repoName"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Auth bool `json:"auth"`
|
||||||
|
}
|
12
backend/app/model/image_repo.go
Normal file
12
backend/app/model/image_repo.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type ImageRepo struct {
|
||||||
|
BaseModel
|
||||||
|
|
||||||
|
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||||
|
DownloadUrl string `gorm:"type:varchar(256)" json:"downloadUrl"`
|
||||||
|
RepoName string `gorm:"type:varchar(256)" json:"repoName"`
|
||||||
|
Username string `gorm:"type:varchar(256)" json:"username"`
|
||||||
|
Password string `gorm:"type:varchar(256)" json:"password"`
|
||||||
|
Auth bool `gorm:"type:varchar(256)" json:"auth"`
|
||||||
|
}
|
@ -4,6 +4,7 @@ type RepoGroup struct {
|
|||||||
HostRepo
|
HostRepo
|
||||||
BackupRepo
|
BackupRepo
|
||||||
GroupRepo
|
GroupRepo
|
||||||
|
ImageRepoRepo
|
||||||
CommandRepo
|
CommandRepo
|
||||||
OperationRepo
|
OperationRepo
|
||||||
CommonRepo
|
CommonRepo
|
||||||
|
58
backend/app/repo/image_repo.go
Normal file
58
backend/app/repo/image_repo.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/1Panel-dev/1Panel/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImageRepoRepo struct{}
|
||||||
|
|
||||||
|
type IImageRepoRepo interface {
|
||||||
|
Get(opts ...DBOption) (model.ImageRepo, error)
|
||||||
|
Page(limit, offset int, opts ...DBOption) (int64, []model.ImageRepo, error)
|
||||||
|
Create(imageRepo *model.ImageRepo) error
|
||||||
|
Update(id uint, vars map[string]interface{}) error
|
||||||
|
Delete(opts ...DBOption) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIImageRepoRepo() IImageRepoRepo {
|
||||||
|
return &ImageRepoRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoRepo) Get(opts ...DBOption) (model.ImageRepo, error) {
|
||||||
|
var imageRepo model.ImageRepo
|
||||||
|
db := global.DB
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
err := db.First(&imageRepo).Error
|
||||||
|
return imageRepo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoRepo) Page(page, size int, opts ...DBOption) (int64, []model.ImageRepo, error) {
|
||||||
|
var ops []model.ImageRepo
|
||||||
|
db := global.DB.Model(&model.ImageRepo{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
count := int64(0)
|
||||||
|
db = db.Count(&count)
|
||||||
|
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
|
||||||
|
return count, ops, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoRepo) Create(imageRepo *model.ImageRepo) error {
|
||||||
|
return global.DB.Create(imageRepo).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoRepo) Update(id uint, vars map[string]interface{}) error {
|
||||||
|
return global.DB.Model(&model.ImageRepo{}).Where("id = ?", id).Updates(vars).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoRepo) Delete(opts ...DBOption) error {
|
||||||
|
db := global.DB
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
return db.Delete(&model.ImageRepo{}).Error
|
||||||
|
}
|
@ -7,6 +7,7 @@ type ServiceGroup struct {
|
|||||||
HostService
|
HostService
|
||||||
BackupService
|
BackupService
|
||||||
GroupService
|
GroupService
|
||||||
|
ImageRepoService
|
||||||
ContainerService
|
ContainerService
|
||||||
CommandService
|
CommandService
|
||||||
OperationService
|
OperationService
|
||||||
@ -25,6 +26,7 @@ var (
|
|||||||
commandRepo = repo.RepoGroupApp.CommandRepo
|
commandRepo = repo.RepoGroupApp.CommandRepo
|
||||||
operationRepo = repo.RepoGroupApp.OperationRepo
|
operationRepo = repo.RepoGroupApp.OperationRepo
|
||||||
commonRepo = repo.RepoGroupApp.CommonRepo
|
commonRepo = repo.RepoGroupApp.CommonRepo
|
||||||
|
imageRepoRepo = repo.RepoGroupApp.ImageRepoRepo
|
||||||
cronjobRepo = repo.RepoGroupApp.CronjobRepo
|
cronjobRepo = repo.RepoGroupApp.CronjobRepo
|
||||||
settingRepo = repo.RepoGroupApp.SettingRepo
|
settingRepo = repo.RepoGroupApp.SettingRepo
|
||||||
appRepo = repo.RepoGroupApp.AppRepo
|
appRepo = repo.RepoGroupApp.AppRepo
|
||||||
|
56
backend/app/service/image_repo.go
Normal file
56
backend/app/service/image_repo.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/1Panel-dev/1Panel/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImageRepoService struct{}
|
||||||
|
|
||||||
|
type IImageRepoService interface {
|
||||||
|
Page(search dto.PageInfo) (int64, interface{}, error)
|
||||||
|
Create(imageRepoDto dto.ImageRepoCreate) error
|
||||||
|
Update(id uint, upMap map[string]interface{}) error
|
||||||
|
BatchDelete(ids []uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIImageRepoService() IImageRepoService {
|
||||||
|
return &ImageRepoService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoService) Page(search dto.PageInfo) (int64, interface{}, error) {
|
||||||
|
total, ops, err := imageRepoRepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc"))
|
||||||
|
var dtoOps []dto.ImageRepoInfo
|
||||||
|
for _, op := range ops {
|
||||||
|
var item dto.ImageRepoInfo
|
||||||
|
if err := copier.Copy(&item, &op); err != nil {
|
||||||
|
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||||
|
}
|
||||||
|
dtoOps = append(dtoOps, item)
|
||||||
|
}
|
||||||
|
return total, dtoOps, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error {
|
||||||
|
imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(imageRepoDto.RepoName))
|
||||||
|
if imageRepo.ID != 0 {
|
||||||
|
return constant.ErrRecordExist
|
||||||
|
}
|
||||||
|
if err := copier.Copy(&imageRepo, &imageRepoDto); err != nil {
|
||||||
|
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||||
|
}
|
||||||
|
if err := imageRepoRepo.Create(&imageRepo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoService) BatchDelete(ids []uint) error {
|
||||||
|
return imageRepoRepo.Delete(commonRepo.WithIdsIn(ids))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ImageRepoService) Update(id uint, upMap map[string]interface{}) error {
|
||||||
|
return imageRepoRepo.Update(id, upMap)
|
||||||
|
}
|
@ -16,6 +16,7 @@ func Init() {
|
|||||||
migrations.AddTableBackupAccount,
|
migrations.AddTableBackupAccount,
|
||||||
migrations.AddTableCronjob,
|
migrations.AddTableCronjob,
|
||||||
migrations.AddTableApp,
|
migrations.AddTableApp,
|
||||||
|
migrations.AddTableImageRepo,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
@ -153,3 +153,20 @@ var AddTableApp = &gormigrate.Migration{
|
|||||||
return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppInstallResource{}, &model.Database{}, &model.AppInstallBackup{})
|
return tx.AutoMigrate(&model.App{}, &model.AppDetail{}, &model.Tag{}, &model.AppTag{}, &model.AppInstall{}, &model.AppInstallResource{}, &model.Database{}, &model.AppInstallBackup{})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AddTableImageRepo = &gormigrate.Migration{
|
||||||
|
ID: "20201009-add-table-imagerepo",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.AutoMigrate(&model.ImageRepo{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
item := &model.ImageRepo{
|
||||||
|
Name: "Docker Hub",
|
||||||
|
DownloadUrl: "docker.io",
|
||||||
|
}
|
||||||
|
if err := tx.Create(item).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -24,5 +24,10 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
baRouter.GET("/detail/:id", baseApi.ContainerDetail)
|
baRouter.GET("/detail/:id", baseApi.ContainerDetail)
|
||||||
withRecordRouter.POST("operate", baseApi.ContainerOperation)
|
withRecordRouter.POST("operate", baseApi.ContainerOperation)
|
||||||
withRecordRouter.POST("/log", baseApi.ContainerLogs)
|
withRecordRouter.POST("/log", baseApi.ContainerLogs)
|
||||||
|
|
||||||
|
baRouter.POST("/repo/search", baseApi.GetRepoList)
|
||||||
|
baRouter.PUT("/repo/:id", baseApi.UpdateRepo)
|
||||||
|
withRecordRouter.POST("/repo", baseApi.CreateRepo)
|
||||||
|
withRecordRouter.POST("/repo/del", baseApi.DeleteRepo)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,4 +21,30 @@ export namespace Container {
|
|||||||
containerID: string;
|
containerID: string;
|
||||||
mode: string;
|
mode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RepoCreate {
|
||||||
|
name: string;
|
||||||
|
downloadUrl: string;
|
||||||
|
repoName: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
auth: boolean;
|
||||||
|
}
|
||||||
|
export interface RepoUpdate {
|
||||||
|
id: number;
|
||||||
|
downloadUrl: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
auth: boolean;
|
||||||
|
}
|
||||||
|
export interface RepoInfo {
|
||||||
|
id: number;
|
||||||
|
createdAt: Date;
|
||||||
|
name: string;
|
||||||
|
downloadUrl: string;
|
||||||
|
repoName: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
auth: boolean;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ export const addCommand = (params: Command.CommandOperate) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const editCommand = (params: Command.CommandOperate) => {
|
export const editCommand = (params: Command.CommandOperate) => {
|
||||||
console.log(params.id);
|
|
||||||
return http.put(`/commands/${params.id}`, params);
|
return http.put(`/commands/${params.id}`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import http from '@/api';
|
import http from '@/api';
|
||||||
import { ResPage } from '../interface';
|
import { ResPage, ReqPage } from '../interface';
|
||||||
import { Container } from '../interface/container';
|
import { Container } from '../interface/container';
|
||||||
|
|
||||||
export const getContainerPage = (params: Container.ContainerSearch) => {
|
export const getContainerPage = (params: Container.ContainerSearch) => {
|
||||||
@ -17,3 +17,17 @@ export const ContainerOperator = (params: Container.ContainerOperate) => {
|
|||||||
export const getContainerInspect = (containerID: string) => {
|
export const getContainerInspect = (containerID: string) => {
|
||||||
return http.get<string>(`/containers/detail/${containerID}`);
|
return http.get<string>(`/containers/detail/${containerID}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// repo
|
||||||
|
export const getRepoPage = (params: ReqPage) => {
|
||||||
|
return http.post<ResPage<Container.RepoInfo>>(`/containers/repo/search`, params);
|
||||||
|
};
|
||||||
|
export const repoCreate = (params: Container.RepoCreate) => {
|
||||||
|
return http.post(`/containers/repo`, params);
|
||||||
|
};
|
||||||
|
export const repoUpdate = (params: Container.RepoUpdate) => {
|
||||||
|
return http.put(`/containers/repo/${params.id}`, params);
|
||||||
|
};
|
||||||
|
export const deleteRepo = (params: { ids: number[] }) => {
|
||||||
|
return http.post(`/containers/repo/del`, params);
|
||||||
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
commons: {
|
commons: {
|
||||||
|
true: 'true',
|
||||||
|
false: 'false',
|
||||||
button: {
|
button: {
|
||||||
create: 'Create',
|
create: 'Create',
|
||||||
add: 'Add',
|
add: 'Add',
|
||||||
@ -169,6 +171,13 @@ export default {
|
|||||||
last4Hour: 'Last 4 Hours',
|
last4Hour: 'Last 4 Hours',
|
||||||
lastHour: 'Last Hour',
|
lastHour: 'Last Hour',
|
||||||
last10Min: 'Last 10 Minutes',
|
last10Min: 'Last 10 Minutes',
|
||||||
|
|
||||||
|
repo: 'Repo',
|
||||||
|
name: 'Name',
|
||||||
|
downloadUrl: 'Download URL',
|
||||||
|
imageRepo: 'ImageRepo',
|
||||||
|
repoHelper: 'Does it include a mirror repository/organization/project?',
|
||||||
|
auth: 'Auth',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
cronTask: 'Task',
|
cronTask: 'Task',
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
commons: {
|
commons: {
|
||||||
|
true: '是',
|
||||||
|
false: '否',
|
||||||
button: {
|
button: {
|
||||||
create: '新建',
|
create: '新建',
|
||||||
add: '添加',
|
add: '添加',
|
||||||
@ -166,6 +168,13 @@ export default {
|
|||||||
last4Hour: '最近 4 小时',
|
last4Hour: '最近 4 小时',
|
||||||
lastHour: '最近 1 小时',
|
lastHour: '最近 1 小时',
|
||||||
last10Min: '最近 10 分钟',
|
last10Min: '最近 10 分钟',
|
||||||
|
|
||||||
|
repo: '仓库',
|
||||||
|
name: '名称',
|
||||||
|
downloadUrl: '下载地址',
|
||||||
|
imageRepo: '镜像库',
|
||||||
|
repoHelper: '是否包含镜像仓库/组织/项目?',
|
||||||
|
auth: '认证',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
cronTask: '计划任务',
|
cronTask: '计划任务',
|
||||||
|
204
frontend/src/views/container/container/create/index.vue
Normal file
204
frontend/src/views/container/container/create/index.vue
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="createVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="50%">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>容器创建</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" :model="form" label-position="left" :rules="rules" label-width="120px">
|
||||||
|
<el-form-item label="容器名称" prop="name">
|
||||||
|
<el-input clearable v-model="form.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="镜像" prop="image">
|
||||||
|
<el-input clearable v-model="form.image" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="端口" prop="image">
|
||||||
|
<el-radio-group v-model="form.publishAllPorts" class="ml-4">
|
||||||
|
<el-radio :label="false">暴露端口</el-radio>
|
||||||
|
<el-radio :label="true">暴露所有</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px"></div>
|
||||||
|
<table style="width: 100%; margin-top: 5px" class="tab-table">
|
||||||
|
<tr v-for="(row, index) in ports" :key="index">
|
||||||
|
<td width="48%">
|
||||||
|
<el-input v-model="row['key']" />
|
||||||
|
</td>
|
||||||
|
<td width="48%">
|
||||||
|
<el-input v-model="row['value']" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<el-button type="text" style="font-size: 10px" @click="handlePortsDelete(index)">
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left">
|
||||||
|
<el-button @click="handlePortsAdd()">{{ $t('commons.button.add') }}</el-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="启动命令" prop="command">
|
||||||
|
<el-input clearable v-model="form.command" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="autoRemove">
|
||||||
|
<el-checkbox v-model="form.autoRemove">容器停止后自动删除容器</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="限制CPU" prop="cpusetCpus">
|
||||||
|
<el-input v-model="form.cpusetCpus" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="内存" prop="memeryLimit">
|
||||||
|
<el-input v-model="form.memeryLimit" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="挂载卷">
|
||||||
|
<div style="margin-top: 20px"></div>
|
||||||
|
<table style="width: 100%; margin-top: 5px" class="tab-table">
|
||||||
|
<tr v-for="(row, index) in volumes" :key="index">
|
||||||
|
<td width="30%">
|
||||||
|
<el-input v-model="row['name']" />
|
||||||
|
</td>
|
||||||
|
<td width="30%">
|
||||||
|
<el-input v-model="row['bind']" />
|
||||||
|
</td>
|
||||||
|
<td width="30%">
|
||||||
|
<el-input v-model="row['mode']" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<el-button type="text" style="font-size: 10px" @click="handleVolumesDelete(index)">
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left">
|
||||||
|
<el-button @click="handleVolumesAdd()">{{ $t('commons.button.add') }}</el-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="标签" prop="labels">
|
||||||
|
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.labels" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="环境变量(每行一个)" prop="environment">
|
||||||
|
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.environment" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="重启规则" prop="restartPolicy.value">
|
||||||
|
<el-radio-group v-model="form.restartPolicy.value">
|
||||||
|
<el-radio :label="false">关闭后马上重启</el-radio>
|
||||||
|
<el-radio :label="false">错误时重启(默认重启 5 次)</el-radio>
|
||||||
|
<el-radio :label="true">不重启</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="createVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
const createVisiable = ref(false);
|
||||||
|
const form = reactive({
|
||||||
|
name: '',
|
||||||
|
image: '',
|
||||||
|
command: '',
|
||||||
|
publishAllPorts: false,
|
||||||
|
ports: [],
|
||||||
|
cpusetCpus: 1,
|
||||||
|
memeryLimit: 100,
|
||||||
|
volumes: [],
|
||||||
|
autoRemove: false,
|
||||||
|
labels: '',
|
||||||
|
environment: '',
|
||||||
|
restartPolicy: {
|
||||||
|
value: '',
|
||||||
|
name: '',
|
||||||
|
maximumRetryCount: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const ports = ref();
|
||||||
|
const volumes = ref();
|
||||||
|
|
||||||
|
const acceptParams = (): void => {
|
||||||
|
createVisiable.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
name: [Rules.requiredInput, Rules.name],
|
||||||
|
type: [Rules.requiredSelect],
|
||||||
|
specType: [Rules.requiredSelect],
|
||||||
|
week: [Rules.requiredSelect, Rules.number],
|
||||||
|
day: [Rules.number, { max: 31, min: 1 }],
|
||||||
|
hour: [Rules.number, { max: 23, min: 0 }],
|
||||||
|
minute: [Rules.number, { max: 60, min: 1 }],
|
||||||
|
|
||||||
|
script: [Rules.requiredInput],
|
||||||
|
website: [Rules.requiredSelect],
|
||||||
|
database: [Rules.requiredSelect],
|
||||||
|
url: [Rules.requiredInput],
|
||||||
|
sourceDir: [Rules.requiredSelect],
|
||||||
|
targetDirID: [Rules.requiredSelect, Rules.number],
|
||||||
|
retainCopies: [Rules.number],
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const handlePortsAdd = () => {
|
||||||
|
let item = {
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
ports.value.push(item);
|
||||||
|
};
|
||||||
|
const handlePortsDelete = (index: number) => {
|
||||||
|
ports.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVolumesAdd = () => {
|
||||||
|
let item = {
|
||||||
|
from: '',
|
||||||
|
bind: '',
|
||||||
|
mode: '',
|
||||||
|
};
|
||||||
|
volumes.value.push(item);
|
||||||
|
};
|
||||||
|
const handleVolumesDelete = (index: number) => {
|
||||||
|
volumes.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
function restForm() {
|
||||||
|
if (formRef.value) {
|
||||||
|
formRef.value.resetFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
restForm();
|
||||||
|
emit('search');
|
||||||
|
createVisiable.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<el-card style="margin-top: 20px">
|
<el-card style="margin-top: 20px">
|
||||||
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
|
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
@ -30,8 +31,19 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-table-column type="selection" fix />
|
<el-table-column type="selection" fix />
|
||||||
<el-table-column :label="$t('commons.table.name')" show-overflow-tooltip min-width="100" prop="name" fix />
|
<el-table-column
|
||||||
<el-table-column :label="$t('container.image')" show-overflow-tooltip min-width="100" prop="imageName" />
|
:label="$t('commons.table.name')"
|
||||||
|
show-overflow-tooltip
|
||||||
|
min-width="100"
|
||||||
|
prop="name"
|
||||||
|
fix
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('container.image')"
|
||||||
|
show-overflow-tooltip
|
||||||
|
min-width="100"
|
||||||
|
prop="imageName"
|
||||||
|
/>
|
||||||
<el-table-column :label="$t('commons.table.status')" min-width="50" prop="state" fix />
|
<el-table-column :label="$t('commons.table.status')" min-width="50" prop="state" fix />
|
||||||
<el-table-column :label="$t('container.upTime')" min-width="100" prop="runTime" fix />
|
<el-table-column :label="$t('container.upTime')" min-width="100" prop="runTime" fix />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@ -42,10 +54,12 @@
|
|||||||
/>
|
/>
|
||||||
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
||||||
</ComplexTable>
|
</ComplexTable>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
<el-dialog v-model="detailVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="70%">
|
<el-dialog v-model="detailVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="70%">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>{{ $t('commons.button.views') }}</span>
|
<span>{{ $t('commons.button.view') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<codemirror
|
<codemirror
|
||||||
@ -138,11 +152,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</el-card>
|
<CreateDialog ref="dialogCreateRef" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ComplexTable from '@/components/complex-table/index.vue';
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
|
import CreateDialog from '@/views/container/container/create/index.vue';
|
||||||
import { Codemirror } from 'vue-codemirror';
|
import { Codemirror } from 'vue-codemirror';
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { oneDark } from '@codemirror/theme-one-dark';
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
@ -158,7 +174,7 @@ const data = ref();
|
|||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 100,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const containerSearch = reactive({
|
const containerSearch = reactive({
|
||||||
@ -219,7 +235,15 @@ const search = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreate = async () => {};
|
const dialogCreateRef = ref<DialogExpose>();
|
||||||
|
|
||||||
|
interface DialogExpose {
|
||||||
|
acceptParams: () => void;
|
||||||
|
}
|
||||||
|
const onCreate = async () => {
|
||||||
|
dialogCreateRef.value!.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
const onDetail = async (row: Container.ContainerInfo) => {
|
const onDetail = async (row: Container.ContainerInfo) => {
|
||||||
const res = await getContainerInspect(row.containerID);
|
const res = await getContainerInspect(row.containerID);
|
||||||
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
||||||
|
@ -14,13 +14,16 @@
|
|||||||
<el-radio-button class="topButton" size="large" label="storage">
|
<el-radio-button class="topButton" size="large" label="storage">
|
||||||
{{ $t('container.storage') }}
|
{{ $t('container.storage') }}
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
|
<el-radio-button class="topButton" size="large" label="repo">
|
||||||
|
{{ $t('container.repo') }}
|
||||||
|
</el-radio-button>
|
||||||
<el-radio-button class="topButton" size="large" label="schedule">
|
<el-radio-button class="topButton" size="large" label="schedule">
|
||||||
{{ $t('container.schedule') }}
|
{{ $t('container.schedule') }}
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-card>
|
</el-card>
|
||||||
<Container v-if="activeNames === 'container'" />
|
<Container v-if="activeNames === 'container'" />
|
||||||
<Safe v-if="activeNames === 'image'" />
|
<Repo v-if="activeNames === 'repo'" />
|
||||||
<Backup v-if="activeNames === 'network'" />
|
<Backup v-if="activeNames === 'network'" />
|
||||||
<Monitor v-if="activeNames === 'storage'" />
|
<Monitor v-if="activeNames === 'storage'" />
|
||||||
<About v-if="activeNames === 'schedule'" />
|
<About v-if="activeNames === 'schedule'" />
|
||||||
@ -30,7 +33,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import Container from '@/views/container/container/index.vue';
|
import Container from '@/views/container/container/index.vue';
|
||||||
import Safe from '@/views/setting/tabs/safe.vue';
|
import Repo from '@/views/container/repo/index.vue';
|
||||||
import Backup from '@/views/setting/tabs/backup.vue';
|
import Backup from '@/views/setting/tabs/backup.vue';
|
||||||
import Monitor from '@/views/setting/tabs/monitor.vue';
|
import Monitor from '@/views/setting/tabs/monitor.vue';
|
||||||
import About from '@/views/setting/tabs/about.vue';
|
import About from '@/views/setting/tabs/about.vue';
|
||||||
|
127
frontend/src/views/container/repo/index.vue
Normal file
127
frontend/src/views/container/repo/index.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card style="margin-top: 20px">
|
||||||
|
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
|
||||||
|
<template #toolbar>
|
||||||
|
<el-button type="primary" @click="onOpenDialog('create')">
|
||||||
|
{{ $t('commons.button.create') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table-column type="selection" fix></el-table-column>
|
||||||
|
<el-table-column :label="$t('commons.table.name')" prop="name" min-width="60" />
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('container.downloadUrl')"
|
||||||
|
show-overflow-tooltip
|
||||||
|
prop="downloadUrl"
|
||||||
|
min-width="100"
|
||||||
|
fix
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('container.imageRepo')"
|
||||||
|
show-overflow-tooltip
|
||||||
|
prop="repoName"
|
||||||
|
min-width="70"
|
||||||
|
fix
|
||||||
|
/>
|
||||||
|
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ dateFromat(0, 0, row.createdAt) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" />
|
||||||
|
</ComplexTable>
|
||||||
|
</el-card>
|
||||||
|
<OperatorDialog @search="search" ref="dialogRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
|
import OperatorDialog from '@/views/container/repo/operator/index.vue';
|
||||||
|
import { reactive, onMounted, ref } from 'vue';
|
||||||
|
import { dateFromat } from '@/utils/util';
|
||||||
|
import { Container } from '@/api/interface/container';
|
||||||
|
import { deleteRepo, getRepoPage } from '@/api/modules/container';
|
||||||
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
|
||||||
|
const data = ref();
|
||||||
|
const selects = ref<any>([]);
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const repoSearch = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
repoSearch.page = paginationConfig.page;
|
||||||
|
repoSearch.pageSize = paginationConfig.pageSize;
|
||||||
|
await getRepoPage(repoSearch).then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
data.value = res.data.items;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DialogExpose {
|
||||||
|
acceptParams: (params: any) => void;
|
||||||
|
}
|
||||||
|
const dialogRef = ref<DialogExpose>();
|
||||||
|
const onOpenDialog = async (
|
||||||
|
title: string,
|
||||||
|
rowData: Partial<Container.RepoInfo> = {
|
||||||
|
auth: true,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let params = {
|
||||||
|
title,
|
||||||
|
rowData: { ...rowData },
|
||||||
|
};
|
||||||
|
dialogRef.value!.acceptParams(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBatchDelete = async (row: Container.RepoInfo | null) => {
|
||||||
|
let ids: Array<number> = [];
|
||||||
|
if (row) {
|
||||||
|
ids.push(row.id);
|
||||||
|
} else {
|
||||||
|
selects.value.forEach((item: Container.RepoInfo) => {
|
||||||
|
ids.push(item.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await useDeleteData(deleteRepo, { ids: ids }, 'commons.msg.delete', true);
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.edit'),
|
||||||
|
disabled: (row: Container.RepoInfo) => {
|
||||||
|
return row.downloadUrl === 'docker.io';
|
||||||
|
},
|
||||||
|
click: (row: Container.RepoInfo) => {
|
||||||
|
onOpenDialog('edit', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.delete'),
|
||||||
|
disabled: (row: Container.RepoInfo) => {
|
||||||
|
return row.downloadUrl === 'docker.io';
|
||||||
|
},
|
||||||
|
click: (row: Container.RepoInfo) => {
|
||||||
|
onBatchDelete(row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
</script>
|
107
frontend/src/views/container/repo/operator/index.vue
Normal file
107
frontend/src/views/container/repo/operator/index.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="repoVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="50%">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ title }}{{ $t('container.repo') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" :model="dialogData.rowData" label-position="left" :rules="rules" label-width="120px">
|
||||||
|
<el-form-item :label="$t('container.name')" prop="name">
|
||||||
|
<el-input :disabled="dialogData.title === 'edit'" v-model="dialogData.rowData!.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.auth')" prop="auth">
|
||||||
|
<el-radio-group v-model="dialogData.rowData!.auth">
|
||||||
|
<el-radio :label="true">{{ $t('commons.true') }}</el-radio>
|
||||||
|
<el-radio :label="false">{{ $t('commons.false') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="dialogData.rowData!.auth" :label="$t('auth.username')" prop="username">
|
||||||
|
<el-input v-model="dialogData.rowData!.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="dialogData.rowData!.auth" :label="$t('auth.password')" prop="password">
|
||||||
|
<el-input type="password" v-model="dialogData.rowData!.password"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.downloadUrl')" prop="downloadUrl">
|
||||||
|
<el-input v-model="dialogData.rowData!.downloadUrl" :placeholder="'172.16.10.10:8081'"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.imageRepo')" prop="repoName">
|
||||||
|
<el-checkbox v-model="hasRepo">{{ $t('container.repoHelper') }}</el-checkbox>
|
||||||
|
<el-input v-if="hasRepo" v-model="dialogData.rowData!.repoName"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="repoVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm, ElMessage } from 'element-plus';
|
||||||
|
import { Container } from '@/api/interface/container';
|
||||||
|
import { repoCreate, repoUpdate } from '@/api/modules/container';
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Container.RepoInfo;
|
||||||
|
getTableList?: () => Promise<any>;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const repoVisiable = ref(false);
|
||||||
|
const dialogData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
dialogData.value = params;
|
||||||
|
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||||
|
hasRepo.value = params.rowData?.repoName ? params.rowData?.repoName.length !== 0 : false;
|
||||||
|
repoVisiable.value = true;
|
||||||
|
};
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const hasRepo = ref(false);
|
||||||
|
const rules = reactive({
|
||||||
|
name: [Rules.requiredInput, Rules.name],
|
||||||
|
downloadUrl: [Rules.requiredInput],
|
||||||
|
repoName: [Rules.requiredInput],
|
||||||
|
username: [Rules.requiredInput],
|
||||||
|
password: [Rules.requiredInput],
|
||||||
|
auth: [Rules.requiredSelect],
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
function restForm() {
|
||||||
|
if (formRef.value) {
|
||||||
|
formRef.value.resetFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (dialogData.value.title === 'create') {
|
||||||
|
await repoCreate(dialogData.value.rowData!);
|
||||||
|
}
|
||||||
|
if (dialogData.value.title === 'edit') {
|
||||||
|
await repoUpdate(dialogData.value.rowData!);
|
||||||
|
}
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
restForm();
|
||||||
|
emit('search');
|
||||||
|
repoVisiable.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user