Compare commits
8 Commits
release-1.
...
dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3e6cd1cab1 | ||
![]() |
98df3806f5 | ||
![]() |
18e8af6234 | ||
![]() |
5bbda8f842 | ||
![]() |
85f8c1e634 | ||
![]() |
85c935ee46 | ||
![]() |
b4033471e7 | ||
![]() |
8dca519068 |
@@ -34,6 +34,28 @@ func (b *BaseApi) CreateRemoteDB(c *gin.Context) {
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Check remote database
|
||||
// @Description 检测远程数据库连接性
|
||||
// @Accept json
|
||||
// @Param request body dto.RemoteDBCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /databases/remote/check [post]
|
||||
// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"检测远程数据库 [name][type] 连接性","formatEN":"check if remote database [name][type] is connectable"}
|
||||
func (b *BaseApi) CheckeRemoteDB(c *gin.Context) {
|
||||
var req dto.RemoteDBCreate
|
||||
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
|
||||
}
|
||||
helper.SuccessWithData(c, remoteDBService.CheckeRemoteDB(req))
|
||||
}
|
||||
|
||||
// @Tags Database
|
||||
// @Summary Page remote databases
|
||||
// @Description 获取远程数据库列表分页
|
||||
|
@@ -341,6 +341,24 @@ func upgradeInstall(installId uint, detailId uint, backup bool) error {
|
||||
install.Version = detail.Version
|
||||
install.AppDetailId = detailId
|
||||
|
||||
images, err := getImages(install)
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
dockerCli, err := composeV2.NewClient()
|
||||
if err != nil {
|
||||
upErr = err
|
||||
return
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
if err = dockerCli.PullImage(image, true); err != nil {
|
||||
upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := compose.Down(install.GetComposePath()); err != nil {
|
||||
if out != "" {
|
||||
upErr = errors.New(out)
|
||||
@@ -398,6 +416,26 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
|
||||
return containerNames, nil
|
||||
}
|
||||
|
||||
func getImages(install model.AppInstall) ([]string, error) {
|
||||
envStr, err := coverEnvJsonToStr(install.Env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imagesMap := make(map[string]struct{})
|
||||
for _, service := range project.AllServices() {
|
||||
imagesMap[service.Image] = struct{}{}
|
||||
}
|
||||
var images []string
|
||||
for k := range imagesMap {
|
||||
images = append(images, k)
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func coverEnvJsonToStr(envJson string) (string, error) {
|
||||
envMap := make(map[string]interface{})
|
||||
_ = json.Unmarshal([]byte(envJson), &envMap)
|
||||
|
@@ -16,6 +16,7 @@ type RemoteDBService struct{}
|
||||
type IRemoteDBService interface {
|
||||
Get(name string) (dto.RemoteDBInfo, error)
|
||||
SearchWithPage(search dto.RemoteDBSearch) (int64, interface{}, error)
|
||||
CheckeRemoteDB(req dto.RemoteDBCreate) bool
|
||||
Create(req dto.RemoteDBCreate) error
|
||||
Update(req dto.RemoteDBUpdate) error
|
||||
Delete(id uint) error
|
||||
@@ -68,6 +69,20 @@ func (u *RemoteDBService) List(dbType string) ([]dto.RemoteDBOption, error) {
|
||||
return datas, err
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) CheckeRemoteDB(req dto.RemoteDBCreate) bool {
|
||||
if _, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: 6,
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *RemoteDBService) Create(req dto.RemoteDBCreate) error {
|
||||
db, _ := remoteDBRepo.Get(commonRepo.WithByName(req.Name))
|
||||
if db.ID != 0 {
|
||||
@@ -79,7 +94,7 @@ func (u *RemoteDBService) Create(req dto.RemoteDBCreate) error {
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: 300,
|
||||
Timeout: 6,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -61,3 +61,18 @@ func WithMap(Key string, maps map[string]interface{}, err error) BusinessError {
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func WithNameAndErr(Key string, name string, err error) BusinessError {
|
||||
paramMap := map[string]interface{}{}
|
||||
if name != "" {
|
||||
paramMap["name"] = name
|
||||
}
|
||||
if err != nil {
|
||||
paramMap["err"] = err.Error()
|
||||
}
|
||||
return BusinessError{
|
||||
Msg: Key,
|
||||
Map: paramMap,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
@@ -45,6 +45,7 @@ ErrImagePullTimeOut: 'Image pull timeout'
|
||||
ErrContainerNotFound: '{{ .name }} container does not exist'
|
||||
ErrContainerMsg: '{{ .name }} container is abnormal, please check the log on the container page for details'
|
||||
ErrAppBackup: '{{ .name }} application backup failed err {{.err}}'
|
||||
ErrImagePull: '{{ .name }} image pull failed err {{.err}}'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "File can not read"
|
||||
|
@@ -45,6 +45,7 @@ ErrImagePullTimeOut: "鏡像拉取超時"
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器異常,具體請在容器頁面查看日誌'
|
||||
ErrAppBackup: '{{ .name }} 應用備份失敗 err {{.err}}'
|
||||
ErrImagePull: '{{ .name }} 鏡像拉取失敗 err {{.err}}'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持預覽"
|
||||
|
@@ -45,6 +45,7 @@ ErrImagePullTimeOut: '镜像拉取超时'
|
||||
ErrContainerNotFound: '{{ .name }} 容器不存在'
|
||||
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
|
||||
ErrAppBackup: '{{ .name }} 应用备份失败 err {{.err}}'
|
||||
ErrImagePull: '镜像拉取失败 {{.err}}'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
|
@@ -43,6 +43,7 @@ func (s *DatabaseRouter) InitDatabaseRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/redis/conffile/update", baseApi.UpdateRedisConfByFile)
|
||||
cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf)
|
||||
|
||||
cmdRouter.POST("/remote/check", baseApi.CheckeRemoteDB)
|
||||
cmdRouter.POST("/remote", baseApi.CreateRemoteDB)
|
||||
cmdRouter.GET("/remote/:name", baseApi.GetRemoteDB)
|
||||
cmdRouter.GET("/remote/list/:type", baseApi.ListRemoteDB)
|
||||
|
@@ -4,11 +4,7 @@ import (
|
||||
"context"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/joho/godotenv"
|
||||
"path"
|
||||
"regexp"
|
||||
@@ -21,24 +17,6 @@ type ComposeService struct {
|
||||
project *types.Project
|
||||
}
|
||||
|
||||
func NewComposeService(ops ...command.DockerCliOption) (*ComposeService, error) {
|
||||
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = append(ops, command.WithAPIClient(apiClient), command.WithDefaultContextStoreConfig())
|
||||
cli, err := command.NewDockerCli(ops...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cliOp := flags.NewClientOptions()
|
||||
if err := cli.Initialize(cliOp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service := compose.NewComposeService(cli)
|
||||
return &ComposeService{service, nil}, nil
|
||||
}
|
||||
|
||||
func (s *ComposeService) SetProject(project *types.Project) {
|
||||
s.project = project
|
||||
for i, s := range project.Services {
|
||||
|
@@ -72,6 +72,22 @@ func (c Client) DeleteImage(imageID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) PullImage(imageName string, force bool) error {
|
||||
if !force {
|
||||
exist, err := c.CheckImageExist(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if _, err := c.cli.ImagePull(context.Background(), imageName, types.ImagePullOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) GetImageIDByName(imageName string) (string, error) {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("reference", imageName)
|
||||
@@ -87,6 +103,18 @@ func (c Client) GetImageIDByName(imageName string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (c Client) CheckImageExist(imageName string) (bool, error) {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("reference", imageName)
|
||||
list, err := c.cli.ImageList(context.Background(), types.ImageListOptions{
|
||||
Filters: filter,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(list) > 0, nil
|
||||
}
|
||||
|
||||
func (c Client) NetworkExist(name string) bool {
|
||||
var options types.NetworkListOptions
|
||||
options.Filters = filters.NewArgs(filters.Arg("name", name))
|
||||
|
@@ -1,8 +1,10 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -38,9 +40,16 @@ func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := db.Ping(); err != nil {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, buserr.New(constant.ErrExecTimeOut)
|
||||
}
|
||||
|
||||
return client.NewRemote(client.Remote{
|
||||
Client: db,
|
||||
From: conn.From,
|
||||
|
@@ -4410,6 +4410,49 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/remote/check": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "检测远程数据库连接性",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Database"
|
||||
],
|
||||
"summary": "Check remote database",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RemoteDBCreate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFuntions": [],
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"formatEN": "check if remote database [name][type] is connectable",
|
||||
"formatZH": "检测远程数据库 [name][type] 连接性",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/remote/del": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -11621,7 +11664,6 @@ const docTemplate = `{
|
||||
"dto.ChangeDBInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
@@ -12923,13 +12965,15 @@ const docTemplate = `{
|
||||
"type": "integer"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"protocol": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -13776,7 +13820,8 @@ const docTemplate = `{
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
@@ -13817,7 +13862,8 @@ const docTemplate = `{
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
|
@@ -4403,6 +4403,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/remote/check": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "检测远程数据库连接性",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Database"
|
||||
],
|
||||
"summary": "Check remote database",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.RemoteDBCreate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFuntions": [],
|
||||
"bodyKeys": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"formatEN": "check if remote database [name][type] is connectable",
|
||||
"formatZH": "检测远程数据库 [name][type] 连接性",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/databases/remote/del": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -11614,7 +11657,6 @@
|
||||
"dto.ChangeDBInfo": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
@@ -12916,13 +12958,15 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"protocol": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -13769,7 +13813,8 @@
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
@@ -13810,7 +13855,8 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
|
@@ -127,7 +127,6 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- value
|
||||
type: object
|
||||
dto.ChangeHostGroup:
|
||||
@@ -1000,10 +999,12 @@ definitions:
|
||||
id:
|
||||
type: integer
|
||||
password:
|
||||
maxLength: 256
|
||||
type: string
|
||||
protocol:
|
||||
type: string
|
||||
username:
|
||||
maxLength: 256
|
||||
type: string
|
||||
type: object
|
||||
dto.ImageSave:
|
||||
@@ -1565,6 +1566,7 @@ definitions:
|
||||
- remote
|
||||
type: string
|
||||
name:
|
||||
maxLength: 256
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
@@ -1599,6 +1601,7 @@ definitions:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
maxLength: 256
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
@@ -6648,6 +6651,34 @@ paths:
|
||||
summary: Get remote databases
|
||||
tags:
|
||||
- Database
|
||||
/databases/remote/check:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 检测远程数据库连接性
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.RemoteDBCreate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Check remote database
|
||||
tags:
|
||||
- Database
|
||||
x-panel-log:
|
||||
BeforeFuntions: []
|
||||
bodyKeys:
|
||||
- name
|
||||
- type
|
||||
formatEN: check if remote database [name][type] is connectable
|
||||
formatZH: 检测远程数据库 [name][type] 连接性
|
||||
paramKeys: []
|
||||
/databases/remote/del:
|
||||
post:
|
||||
consumes:
|
||||
|
@@ -100,11 +100,14 @@ export const searchRemoteDBs = (params: Database.SearchRemoteDBPage) => {
|
||||
export const listRemoteDBs = (type: string) => {
|
||||
return http.get<Array<Database.RemoteDBOption>>(`/databases/remote/list/${type}`);
|
||||
};
|
||||
export const checkRemoteDB = (params: Database.RemoteDBCreate) => {
|
||||
return http.post<boolean>(`/databases/remote/check`, params, 40000);
|
||||
};
|
||||
export const addRemoteDB = (params: Database.RemoteDBCreate) => {
|
||||
return http.post(`/databases/remote`, params);
|
||||
return http.post(`/databases/remote`, params, 40000);
|
||||
};
|
||||
export const editRemoteDB = (params: Database.RemoteDBUpdate) => {
|
||||
return http.post(`/databases/remote/update`, params);
|
||||
return http.post(`/databases/remote/update`, params, 40000);
|
||||
};
|
||||
export const deleteRemoteDB = (id: number) => {
|
||||
return http.post(`/databases/remote/del`, { id: id });
|
||||
|
@@ -3,23 +3,6 @@
|
||||
<div class="complex-table__header" v-if="$slots.header || header">
|
||||
<slot name="header">{{ header }}</slot>
|
||||
</div>
|
||||
<div v-if="$slots.toolbar && !searchConfig" style="margin-bottom: 10px">
|
||||
<slot name="toolbar"></slot>
|
||||
</div>
|
||||
|
||||
<template v-if="searchConfig">
|
||||
<fu-filter-bar v-bind="searchConfig" @exec="search">
|
||||
<template #tl>
|
||||
<slot name="toolbar"></slot>
|
||||
</template>
|
||||
<template #default>
|
||||
<slot name="complex"></slot>
|
||||
</template>
|
||||
<template #buttons>
|
||||
<slot name="buttons"></slot>
|
||||
</template>
|
||||
</fu-filter-bar>
|
||||
</template>
|
||||
|
||||
<div class="complex-table__body">
|
||||
<fu-table v-bind="$attrs" ref="tableRef" @selection-change="handleSelectionChange">
|
||||
@@ -30,13 +13,14 @@
|
||||
</fu-table>
|
||||
</div>
|
||||
|
||||
<div class="complex-table__pagination" v-if="$slots.pagination || paginationConfig">
|
||||
<div class="complex-table__pagination" v-if="props.paginationConfig">
|
||||
<slot name="pagination">
|
||||
<fu-table-pagination
|
||||
v-model:current-page="paginationConfig.currentPage"
|
||||
v-model:page-size="paginationConfig.pageSize"
|
||||
v-bind="paginationConfig"
|
||||
@change="search"
|
||||
:total="paginationConfig.total"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
:small="mobile"
|
||||
:layout="mobile ? 'total, prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
|
||||
/>
|
||||
@@ -49,15 +33,15 @@ import { ref, computed } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
defineOptions({ name: 'ComplexTable' });
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
header: String,
|
||||
searchConfig: Object,
|
||||
paginationConfig: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['search', 'update:selects']);
|
||||
const emit = defineEmits(['search', 'update:selects', 'update:paginationConfig']);
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
@@ -65,13 +49,15 @@ const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
||||
const condition = ref({});
|
||||
const tableRef = ref();
|
||||
function search(conditions: any, e: any) {
|
||||
if (conditions) {
|
||||
condition.value = conditions;
|
||||
}
|
||||
emit('search', condition.value, e);
|
||||
|
||||
function currentChange() {
|
||||
emit('search');
|
||||
}
|
||||
|
||||
function sizeChange() {
|
||||
props.paginationConfig.currentPage = 1;
|
||||
emit('search');
|
||||
}
|
||||
|
||||
function handleSelectionChange(row: any) {
|
||||
|
@@ -13,20 +13,7 @@ const terminalElement = ref<HTMLDivElement | null>(null);
|
||||
const fitAddon = new FitAddon();
|
||||
const termReady = ref(false);
|
||||
const webSocketReady = ref(false);
|
||||
const term = ref(
|
||||
new Terminal({
|
||||
lineHeight: 1.2,
|
||||
fontSize: 12,
|
||||
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
||||
theme: {
|
||||
background: '#000000',
|
||||
},
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'underline',
|
||||
scrollback: 100,
|
||||
tabStopWidth: 4,
|
||||
}),
|
||||
);
|
||||
const term = ref();
|
||||
const terminalSocket = ref<WebSocket>();
|
||||
const heartbeatTimer = ref<number>();
|
||||
const latency = ref(0);
|
||||
@@ -56,6 +43,21 @@ const acceptParams = (props: WsProps) => {
|
||||
});
|
||||
};
|
||||
|
||||
const newTerm = () => {
|
||||
term.value = new Terminal({
|
||||
lineHeight: 1.2,
|
||||
fontSize: 12,
|
||||
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
||||
theme: {
|
||||
background: '#000000',
|
||||
},
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'underline',
|
||||
scrollback: 100,
|
||||
tabStopWidth: 4,
|
||||
});
|
||||
};
|
||||
|
||||
const init = (endpoint: string, args: string) => {
|
||||
if (initTerminal(true)) {
|
||||
initWebSocket(endpoint, args);
|
||||
@@ -78,11 +80,13 @@ function onClose(isKeepShow: boolean = false) {
|
||||
term.value.dispose();
|
||||
} catch {}
|
||||
}
|
||||
terminalElement.value.innerHTML = '';
|
||||
}
|
||||
|
||||
// terminal 相关代码 start
|
||||
|
||||
const initTerminal = (online: boolean = false): boolean => {
|
||||
newTerm();
|
||||
if (terminalElement.value) {
|
||||
term.value.open(terminalElement.value);
|
||||
term.value.loadAddon(fitAddon);
|
||||
|
@@ -479,7 +479,10 @@ const message = {
|
||||
rename: 'Rename',
|
||||
remove: 'Remove',
|
||||
containerPrune: 'Container prune',
|
||||
containerPruneHelper: 'Remove all stopped containers. Do you want to continue?',
|
||||
containerPruneHelper1: 'Cleaning containers will delete all containers that are in a stopped state.',
|
||||
containerPruneHelper2:
|
||||
'If the containers are from the app store, after performing the cleanup, you need to go to the [Installed] list in the [App Store] and click the [Rebuild] button to reinstall them.',
|
||||
containerPruneHelper3: 'This operation cannot be rolled back. Do you want to continue?',
|
||||
imagePrune: 'Image prune',
|
||||
imagePruneSome: 'Clean unlabeled',
|
||||
imagePruneSomeHelper: 'Remove all unused and unlabeled container images。',
|
||||
@@ -533,7 +536,7 @@ const message = {
|
||||
exposePort: 'Expose port',
|
||||
exposeAll: 'Expose all',
|
||||
cmd: 'Command',
|
||||
cmdHelper: 'Example: echo "hello"',
|
||||
cmdHelper: "Separate multiple commands with ' ' as delimiter, such as 'nginx' '-g' 'daemon off;'",
|
||||
autoRemove: 'Auto remove',
|
||||
cpuQuota: 'NacosCPU',
|
||||
memoryLimit: 'Memory',
|
||||
@@ -1684,6 +1687,14 @@ const message = {
|
||||
restartHelper:
|
||||
'Initialization will restart the service, causing all the original daemon processes to close',
|
||||
msg: 'Message',
|
||||
RUNNING: 'Running',
|
||||
STOPPED: 'Stopped',
|
||||
STOPPING: 'Stopping',
|
||||
STARTING: 'Starting',
|
||||
FATAL: 'Failed to start',
|
||||
BACKOFF: 'Start exception',
|
||||
statusCode: 'Status code',
|
||||
manage: 'Management',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -469,7 +469,10 @@ const message = {
|
||||
rename: '重命名',
|
||||
remove: '刪除',
|
||||
containerPrune: '清理容器',
|
||||
containerPruneHelper: '清理容器 將刪除所有處於停止狀態的容器,該操作無法回滾,是否繼續?',
|
||||
containerPruneHelper1: '清理容器 將刪除所有處於停止狀態的容器。',
|
||||
containerPruneHelper2:
|
||||
'若容器來自於應用商店,在執行清理操作後,您需要前往 [應用商店] 的 [已安裝] 列表,點擊 [重建] 按鈕進行重新安裝。',
|
||||
containerPruneHelper3: '該操作無法回滾,是否繼續?',
|
||||
imagePrune: '清理鏡像',
|
||||
imagePruneSome: '未標簽鏡像',
|
||||
imagePruneSomeHelper: '清理標簽為 none 且未被任何容器使用的鏡像。',
|
||||
@@ -519,7 +522,7 @@ const message = {
|
||||
exposePort: '暴露端口',
|
||||
exposeAll: '暴露所有',
|
||||
cmd: '啟動命令',
|
||||
cmdHelper: '例:echo "hello"',
|
||||
cmdHelper: "多個命令間請用 ' ' 分隔開,如 'nginx' '-g' 'daemon off;'",
|
||||
autoRemove: '容器退出後自動刪除容器',
|
||||
cpuQuota: 'CPU 限製',
|
||||
memoryLimit: '內存限製',
|
||||
@@ -1596,6 +1599,14 @@ const message = {
|
||||
serviceNameHelper: 'systemctl 管理的 Supervisor 服務名稱,一般為 supervisor 或 supervisord',
|
||||
restartHelper: '初始化會重啟服務,導致原有的守護進程全部關閉',
|
||||
msg: '信息',
|
||||
RUNNING: '運行中',
|
||||
STOPPED: '已停止',
|
||||
STOPPING: '停止中',
|
||||
STARTING: '啟動中',
|
||||
FATAL: '啟動失敗',
|
||||
BACKOFF: '啟動異常',
|
||||
statusCode: '狀態碼',
|
||||
manage: '管理',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -469,7 +469,10 @@ const message = {
|
||||
rename: '重命名',
|
||||
remove: '删除',
|
||||
containerPrune: '清理容器',
|
||||
containerPruneHelper: '清理容器 将删除所有处于停止状态的容器,该操作无法回滚,是否继续?',
|
||||
containerPruneHelper1: '清理容器 将删除所有处于停止状态的容器。',
|
||||
containerPruneHelper2:
|
||||
'若容器来自于应用商店,在执行清理操作后,您需要前往 [应用商店] 的 [已安装] 列表,点击 [重建] 按钮进行重新安装。',
|
||||
containerPruneHelper3: '该操作无法回滚,是否继续?',
|
||||
imagePrune: '清理镜像',
|
||||
imagePruneSome: '未标签镜像',
|
||||
imagePruneSomeHelper: '清理标签为 none 且未被任何容器使用的镜像。',
|
||||
@@ -519,7 +522,7 @@ const message = {
|
||||
exposePort: '暴露端口',
|
||||
exposeAll: '暴露所有',
|
||||
cmd: '启动命令',
|
||||
cmdHelper: '例:echo "hello"',
|
||||
cmdHelper: "多个命令间请用 ' ' 分隔开,如 'nginx' '-g' 'daemon off;'",
|
||||
autoRemove: '容器退出后自动删除容器',
|
||||
cpuQuota: 'CPU 限制',
|
||||
memoryLimit: '内存限制',
|
||||
@@ -1598,6 +1601,14 @@ const message = {
|
||||
serviceNameHelper: 'systemctl 管理的 Supervisor 服务名称,一般为 supervisor、supervisord',
|
||||
restartHelper: '初始化会重启服务,导致原有的守护进程全部关闭',
|
||||
msg: '信息',
|
||||
RUNNING: '运行中',
|
||||
STOPPED: '已停止',
|
||||
STOPPING: '停止中',
|
||||
STARTING: '启动中',
|
||||
FATAL: '启动失败',
|
||||
BACKOFF: '启动异常',
|
||||
statusCode: '状态码',
|
||||
manage: '管理',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -157,6 +157,7 @@
|
||||
</LayoutContent>
|
||||
|
||||
<CodemirrorDialog ref="mydetail" />
|
||||
<PruneDialog @search="search" ref="dialogPruneRef" />
|
||||
|
||||
<ReNameDialog @search="search" ref="dialogReNameRef" />
|
||||
<ContainerLogDialog ref="dialogContainerLogRef" />
|
||||
@@ -172,6 +173,7 @@
|
||||
<script lang="ts" setup>
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import PruneDialog from '@/views/container/container/prune/index.vue';
|
||||
import ReNameDialog from '@/views/container/container/rename/index.vue';
|
||||
import OperateDialog from '@/views/container/container/operate/index.vue';
|
||||
import UpgraeDialog from '@/views/container/container/upgrade/index.vue';
|
||||
@@ -185,7 +187,6 @@ import { reactive, onMounted, ref, computed } from 'vue';
|
||||
import {
|
||||
containerListStats,
|
||||
containerOperator,
|
||||
containerPrune,
|
||||
inspect,
|
||||
loadContainerInfo,
|
||||
loadDockerStatus,
|
||||
@@ -196,7 +197,6 @@ import { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import { computeSize } from '@/utils/util';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
@@ -264,6 +264,7 @@ const mydetail = ref();
|
||||
|
||||
const dialogContainerLogRef = ref();
|
||||
const dialogReNameRef = ref();
|
||||
const dialogPruneRef = ref();
|
||||
|
||||
const search = async (column?: any) => {
|
||||
let filterItem = props.filters ? props.filters : '';
|
||||
@@ -360,31 +361,7 @@ const onInspect = async (id: string) => {
|
||||
};
|
||||
|
||||
const onClean = () => {
|
||||
ElMessageBox.confirm(i18n.global.t('container.containerPruneHelper'), i18n.global.t('container.containerPrune'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
pruneType: 'container',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
MsgSuccess(
|
||||
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||
res.data.deletedNumber,
|
||||
computeSize(res.data.spaceReclaimed),
|
||||
]),
|
||||
);
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
dialogPruneRef.value!.acceptParams();
|
||||
};
|
||||
|
||||
const checkStatus = (operation: string, row: Container.ContainerInfo | null) => {
|
||||
|
@@ -110,7 +110,8 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.cmd')" prop="cmdStr">
|
||||
<el-input :placeholder="$t('container.cmdHelper')" v-model="dialogData.rowData!.cmdStr" />
|
||||
<el-input v-model="dialogData.rowData!.cmdStr" />
|
||||
<span class="input-help">{{ $t('container.cmdHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item prop="autoRemove">
|
||||
<el-checkbox v-model="dialogData.rowData!.autoRemove">
|
||||
|
71
frontend/src/views/container/container/prune/index.vue
Normal file
71
frontend/src/views/container/container/prune/index.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisiable" :title="$t('container.containerPrune')" :destroy-on-close="true" width="30%">
|
||||
<div>
|
||||
<ul class="help-ul">
|
||||
<li lineClass style="color: red">{{ $t('container.containerPruneHelper1') }}</li>
|
||||
<li class="lineClass">{{ $t('container.containerPruneHelper2') }}</li>
|
||||
<li class="lineClass">{{ $t('container.containerPruneHelper3') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="dialogVisiable = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onClean()">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { containerPrune } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ref } from 'vue';
|
||||
import { computeSize } from '@/utils/util';
|
||||
|
||||
const loading = ref(false);
|
||||
const dialogVisiable = ref<boolean>(false);
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const onClean = async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
pruneType: 'container',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
MsgSuccess(
|
||||
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||
res.data.deletedNumber,
|
||||
computeSize(res.data.spaceReclaimed),
|
||||
]),
|
||||
);
|
||||
dialogVisiable.value = false;
|
||||
emit('search');
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const acceptParams = (): void => {
|
||||
dialogVisiable.value = true;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.lineClass {
|
||||
line-height: 30px;
|
||||
}
|
||||
</style>
|
@@ -43,7 +43,7 @@
|
||||
<el-button v-if="!terminalOpen" @click="initTerm(formRef)">
|
||||
{{ $t('commons.button.conn') }}
|
||||
</el-button>
|
||||
<el-button v-else @click="handleClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||
<el-button v-else @click="onClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||
<Terminal style="height: calc(100vh - 302px)" ref="terminalRef"></Terminal>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
@@ -99,10 +99,14 @@ const initTerm = (formEl: FormInstance | undefined) => {
|
||||
});
|
||||
};
|
||||
|
||||
function handleClose() {
|
||||
const onClose = () => {
|
||||
terminalRef.value?.onClose();
|
||||
terminalVisiable.value = false;
|
||||
terminalOpen.value = false;
|
||||
};
|
||||
|
||||
function handleClose() {
|
||||
onClose();
|
||||
terminalVisiable.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
@@ -8,7 +8,7 @@
|
||||
:back="handleClose"
|
||||
/>
|
||||
</template>
|
||||
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||
<el-form ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
@@ -20,7 +20,7 @@
|
||||
<el-tag v-else>{{ dialogData.rowData!.name }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.version')" prop="version">
|
||||
<el-select v-model="dialogData.rowData!.version">
|
||||
<el-select @change="isOK = false" v-model="dialogData.rowData!.version">
|
||||
<el-option value="5.6" label="5.6" />
|
||||
<el-option value="5.7" label="5.7" />
|
||||
<el-option value="8.0" label="8.0" />
|
||||
@@ -28,17 +28,23 @@
|
||||
<span class="input-help">{{ $t('database.versionHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('database.address')" prop="address">
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.address" />
|
||||
<el-input @change="isOK = false" clearable v-model.trim="dialogData.rowData!.address" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.port')" prop="port">
|
||||
<el-input clearable v-model.number="dialogData.rowData!.port" />
|
||||
<el-input @change="isOK = false" clearable v-model.number="dialogData.rowData!.port" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.username')" prop="username">
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.username" />
|
||||
<el-input @change="isOK = false" clearable v-model.trim="dialogData.rowData!.username" />
|
||||
<span class="input-help">{{ $t('database.userHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.login.password')" prop="password">
|
||||
<el-input type="password" clearable show-password v-model.trim="dialogData.rowData!.password" />
|
||||
<el-input
|
||||
@change="isOK = false"
|
||||
type="password"
|
||||
clearable
|
||||
show-password
|
||||
v-model.trim="dialogData.rowData!.password"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.description" />
|
||||
@@ -49,7 +55,10 @@
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">
|
||||
<el-button @click="onSubmit(formRef, 'check')">
|
||||
{{ $t('terminal.testConn') }}
|
||||
</el-button>
|
||||
<el-button type="primary" :disabled="!isOK" @click="onSubmit(formRef, dialogData.title)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -63,9 +72,9 @@ import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { addRemoteDB, editRemoteDB } from '@/api/modules/database';
|
||||
import { addRemoteDB, checkRemoteDB, editRemoteDB } from '@/api/modules/database';
|
||||
|
||||
interface DialogProps {
|
||||
title: string;
|
||||
@@ -77,6 +86,9 @@ const drawerVisiable = ref(false);
|
||||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
});
|
||||
const isOK = ref(false);
|
||||
const loading = ref();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
title.value = i18n.global.t('database.' + dialogData.value.title + 'RemoteDB');
|
||||
@@ -91,7 +103,7 @@ const handleClose = () => {
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput],
|
||||
version: [Rules.requiredSelect],
|
||||
address: [Rules.ip],
|
||||
address: [Rules.host],
|
||||
port: [Rules.port],
|
||||
username: [Rules.requiredInput],
|
||||
password: [Rules.requiredInput],
|
||||
@@ -100,40 +112,65 @@ const rules = reactive({
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
const onSubmit = async (formEl: FormInstance | undefined, operation: string) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (dialogData.value.title === 'create') {
|
||||
let param = {
|
||||
name: dialogData.value.rowData.name,
|
||||
type: 'mysql',
|
||||
version: dialogData.value.rowData.version,
|
||||
from: 'remote',
|
||||
address: dialogData.value.rowData.address,
|
||||
port: dialogData.value.rowData.port,
|
||||
username: dialogData.value.rowData.username,
|
||||
password: dialogData.value.rowData.password,
|
||||
description: dialogData.value.rowData.description,
|
||||
};
|
||||
await addRemoteDB(param);
|
||||
}
|
||||
if (dialogData.value.title === 'edit') {
|
||||
let param = {
|
||||
id: dialogData.value.rowData.id,
|
||||
version: dialogData.value.rowData.version,
|
||||
address: dialogData.value.rowData.address,
|
||||
port: dialogData.value.rowData.port,
|
||||
username: dialogData.value.rowData.username,
|
||||
password: dialogData.value.rowData.password,
|
||||
description: dialogData.value.rowData.description,
|
||||
};
|
||||
await editRemoteDB(param);
|
||||
let param = {
|
||||
id: dialogData.value.rowData.id,
|
||||
name: dialogData.value.rowData.name,
|
||||
type: 'mysql',
|
||||
version: dialogData.value.rowData.version,
|
||||
from: 'remote',
|
||||
address: dialogData.value.rowData.address,
|
||||
port: dialogData.value.rowData.port,
|
||||
username: dialogData.value.rowData.username,
|
||||
password: dialogData.value.rowData.password,
|
||||
description: dialogData.value.rowData.description,
|
||||
};
|
||||
loading.value = true;
|
||||
|
||||
if (operation === 'check') {
|
||||
await checkRemoteDB(param)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.data) {
|
||||
isOK.value = true;
|
||||
MsgSuccess(i18n.global.t('terminal.connTestOk'));
|
||||
} else {
|
||||
MsgError(i18n.global.t('terminal.connTestFailed'));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
MsgError(i18n.global.t('terminal.connTestFailed'));
|
||||
});
|
||||
}
|
||||
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisiable.value = false;
|
||||
if (operation === 'create') {
|
||||
await addRemoteDB(param)
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisiable.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
if (operation === 'edit') {
|
||||
await editRemoteDB(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisiable.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@@ -39,6 +39,39 @@
|
||||
prop="numprocs"
|
||||
width="100px"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('tool.supervisor.manage')" width="100px">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.status && row.status.length > 0">
|
||||
<el-button
|
||||
v-if="checkStatus(row.status) === 'RUNNING'"
|
||||
link
|
||||
type="success"
|
||||
:icon="VideoPlay"
|
||||
@click="operate('stop', row.name)"
|
||||
>
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="checkStatus(row.status) === 'WARNING'"
|
||||
link
|
||||
type="warning"
|
||||
:icon="RefreshRight"
|
||||
@click="operate('restart', row.name)"
|
||||
>
|
||||
{{ $t('commons.status.unhealthy') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
link
|
||||
type="danger"
|
||||
:icon="VideoPause"
|
||||
@click="operate('start', row.name)"
|
||||
>
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" width="100px">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.status">
|
||||
@@ -48,7 +81,7 @@
|
||||
{{ $t('website.check') }}
|
||||
</el-button>
|
||||
<el-button type="primary" link v-else>
|
||||
<span>{{ row.status[0].status }}</span>
|
||||
<span>{{ $t('tool.supervisor.' + row.status[0].status) }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<el-table :data="row.status">
|
||||
@@ -60,7 +93,7 @@
|
||||
/>
|
||||
<el-table-column
|
||||
property="status"
|
||||
:label="$t('commons.table.status')"
|
||||
:label="$t('tool.supervisor.statusCode')"
|
||||
width="100px"
|
||||
/>
|
||||
<el-table-column property="PID" label="PID" width="100px" />
|
||||
@@ -85,7 +118,7 @@
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
:fixed="mobile ? false : 'right'"
|
||||
width="350px"
|
||||
width="250px"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
@@ -110,6 +143,7 @@ import { GlobalStore } from '@/store';
|
||||
import i18n from '@/lang';
|
||||
import { HostTool } from '@/api/interface/host-tool';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { VideoPlay, VideoPause, RefreshRight } from '@element-plus/icons-vue';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -170,6 +204,20 @@ const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
||||
const checkStatus = (status: HostTool.ProcessStatus[]): string => {
|
||||
if (!status || status.length === 0) return 'STOPPED';
|
||||
|
||||
const statusCounts = status.reduce((acc, curr) => {
|
||||
acc[curr.status] = (acc[curr.status] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
if (statusCounts['STARTING']) return 'STARTING';
|
||||
if (statusCounts['RUNNING'] === status.length) return 'RUNNING';
|
||||
if (statusCounts['RUNNING'] > 0) return 'WARNING';
|
||||
return 'STOPPED';
|
||||
};
|
||||
|
||||
const operate = async (operation: string, name: string) => {
|
||||
try {
|
||||
ElMessageBox.confirm(
|
||||
@@ -224,42 +272,11 @@ const buttons = [
|
||||
getFile(row.name, 'out.log');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('app.start'),
|
||||
click: function (row: HostTool.SupersivorProcess) {
|
||||
operate('start', row.name);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
if (row.status == undefined) {
|
||||
return true;
|
||||
} else {
|
||||
return row.status && row.status[0].status == 'RUNNING';
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('app.stop'),
|
||||
click: function (row: HostTool.SupersivorProcess) {
|
||||
operate('stop', row.name);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
if (row.status == undefined) {
|
||||
return true;
|
||||
}
|
||||
return row.status && row.status[0].status != 'RUNNING';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.restart'),
|
||||
click: function (row: HostTool.SupersivorProcess) {
|
||||
operate('restart', row.name);
|
||||
},
|
||||
disabled: (row: any): boolean => {
|
||||
if (row.status == undefined) {
|
||||
return true;
|
||||
}
|
||||
return row.status && row.status[0].status != 'RUNNING';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
|
Reference in New Issue
Block a user