feat: compose 详情界面增加启动、停止操作

pull/69/head
ssongliu 2022-12-06 17:24:01 +08:00 committed by ssongliu
parent cecc108784
commit 41be181c7d
12 changed files with 222 additions and 204 deletions

View File

@ -89,8 +89,6 @@ func (b *BaseApi) UpdateComposeTemplate(c *gin.Context) {
}
upMap := make(map[string]interface{})
upMap["from"] = req.From
upMap["path"] = req.Path
upMap["content"] = req.Content
upMap["description"] = req.Description
if err := composeTemplateService.Update(id, upMap); err != nil {

View File

@ -4,16 +4,12 @@ import "time"
type ComposeTemplateCreate struct {
Name string `json:"name" validate:"required"`
From string `json:"from" validate:"required,oneof=edit path"`
Description string `json:"description"`
Path string `json:"path"`
Content string `json:"content"`
}
type ComposeTemplateUpdate struct {
From string `json:"from" validate:"required,oneof=edit path"`
Description string `json:"description"`
Path string `json:"path"`
Content string `json:"content"`
}
@ -21,8 +17,6 @@ type ComposeTemplateInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name"`
From string `json:"from"`
Description string `json:"description"`
Path string `json:"path"`
Content string `json:"content"`
}

View File

@ -135,7 +135,7 @@ type ComposeCreate struct {
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=up stop down"`
Operation string `json:"operation" validate:"required,oneof=start stop down"`
}
type ComposeUpdate struct {
Path string `json:"path" validate:"required"`

View File

@ -4,9 +4,7 @@ type ComposeTemplate struct {
BaseModel
Name string `gorm:"type:varchar(64);not null;unique" json:"name"`
From string `gorm:"type:varchar(64);not null" json:"from"`
Description string `gorm:"type:varchar(256)" json:"description"`
Path string `gorm:"type:varchar(64)" json:"path"`
Content string `gorm:"type:longtext" json:"content"`
}

View File

@ -114,12 +114,8 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
if err != nil {
return err
}
req.From = template.From
if req.From == "edit" {
req.File = template.Content
} else {
req.Path = template.Path
}
req.From = "edit"
req.File = template.Content
}
if req.From == "edit" {
dir := fmt.Sprintf("%s/%s", constant.TmpComposeBuildDir, req.Name)
@ -159,6 +155,7 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
global.LOG.Debugf("docker-compose %s %s successful", req.Operation, req.Name)
if req.Operation == "down" {
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
_ = os.RemoveAll(strings.ReplaceAll(req.Path, req.Name+"/docker-compose.yml", ""))
}
return nil

View File

@ -193,6 +193,7 @@ export namespace Container {
template: number;
}
export interface ComposeOpration {
name: string;
operation: string;
path: string;
}

View File

@ -446,7 +446,9 @@ export default {
containerNumber: 'Container number',
down: 'Down',
up: 'Up',
operatorComposeHelper: '{0} will be performed on the selected compose. Do you want to continue?',
composeDetailHelper:
'The compose is created external to 1Panel. The start and stop operations are not supported.',
composeOperatorHelper: '{1} operation will be performed on {0}. Do you want to continue?',
},
cronjob: {
cronTask: 'Task',

View File

@ -355,7 +355,7 @@ export default {
pause: '',
unpause: '',
rename: '',
remove: '',
remove: '',
container: '',
upTime: '',
all: '',
@ -455,7 +455,8 @@ export default {
containerNumber: '',
down: '',
up: '',
operatorComposeHelper: ' Compose {0} ',
composeDetailHelper: ' compose 1Panel ',
composeOperatorHelper: ' {0} {1} ',
setting: '',
mirrors: '',

View File

@ -1,100 +1,102 @@
<template>
<div v-loading="loading">
<Submenu activeName="compose" />
<el-card style="margin-top: 20px">
<LayoutContent :header="'Compose-' + composeName" back-name="Compose" :reload="true">
<div>
<el-button icon="VideoPause">停止</el-button>
<el-button icon="Delete" plain type="danger">删除</el-button>
</div>
<el-card style="margin-top: 20px">
<template #header>
<div class="card-header">
<span>Containers</span>
</div>
<LayoutContent :header="'Compose-' + composeName" back-name="Compose" :reload="true">
<div v-if="createdBy === 'local'">
<el-button icon="VideoPlay" @click="onComposeOperate('start')">{{ $t('container.start') }}</el-button>
<el-button icon="VideoPause" @click="onComposeOperate('stop')">{{ $t('container.stop') }}</el-button>
<el-button icon="Delete" @click="onComposeOperate('down')" plain type="danger">
{{ $t('container.remove') }}
</el-button>
</div>
<div v-else>
<el-alert :closable="false" show-icon :title="$t('container.composeDetailHelper')" type="info" />
</div>
<el-card style="margin-top: 20px">
<template #header>
<div class="card-header">
<span>Containers</span>
</div>
</template>
<ComplexTable
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
@search="search"
>
<template #toolbar>
<el-button-group>
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
{{ $t('container.start') }}
</el-button>
<el-button :disabled="checkStatus('stop')" @click="onOperate('stop')">
{{ $t('container.stop') }}
</el-button>
<el-button :disabled="checkStatus('restart')" @click="onOperate('restart')">
{{ $t('container.restart') }}
</el-button>
<el-button :disabled="checkStatus('kill')" @click="onOperate('kill')">
{{ $t('container.kill') }}
</el-button>
<el-button :disabled="checkStatus('pause')" @click="onOperate('pause')">
{{ $t('container.pause') }}
</el-button>
<el-button :disabled="checkStatus('unpause')" @click="onOperate('unpause')">
{{ $t('container.unpause') }}
</el-button>
<el-button :disabled="checkStatus('remove')" @click="onOperate('remove')">
{{ $t('container.remove') }}
</el-button>
</el-button-group>
</template>
<ComplexTable
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
@search="search"
<el-table-column type="selection" fix />
<el-table-column
:label="$t('commons.table.name')"
show-overflow-tooltip
min-width="100"
prop="name"
fix
>
<template #toolbar>
<el-button-group>
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
{{ $t('container.start') }}
</el-button>
<el-button :disabled="checkStatus('stop')" @click="onOperate('stop')">
{{ $t('container.stop') }}
</el-button>
<el-button :disabled="checkStatus('restart')" @click="onOperate('restart')">
{{ $t('container.restart') }}
</el-button>
<el-button :disabled="checkStatus('kill')" @click="onOperate('kill')">
{{ $t('container.kill') }}
</el-button>
<el-button :disabled="checkStatus('pause')" @click="onOperate('pause')">
{{ $t('container.pause') }}
</el-button>
<el-button :disabled="checkStatus('unpause')" @click="onOperate('unpause')">
{{ $t('container.unpause') }}
</el-button>
<el-button :disabled="checkStatus('remove')" @click="onOperate('remove')">
{{ $t('container.remove') }}
</el-button>
</el-button-group>
<template #default="{ row }">
<el-link @click="onInspect(row.containerID)" type="primary">{{ row.name }}</el-link>
</template>
<el-table-column type="selection" fix />
<el-table-column
:label="$t('commons.table.name')"
show-overflow-tooltip
min-width="100"
prop="name"
fix
>
<template #default="{ row }">
<el-link @click="onInspect(row.containerID)" type="primary">{{ row.name }}</el-link>
</template>
</el-table-column>
<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('container.upTime')" min-width="100" prop="runTime" fix />
<el-table-column
prop="createTime"
:label="$t('commons.table.date')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations
width="200px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</el-table-column>
<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('container.upTime')" min-width="100" prop="runTime" fix />
<el-table-column
prop="createTime"
:label="$t('commons.table.date')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations
width="200px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
<CodemirrorDialog ref="mydetail" />
<CodemirrorDialog ref="mydetail" />
<ReNameDialog @search="search" ref="dialogReNameRef" />
<ContainerLogDialog ref="dialogContainerLogRef" />
<CreateDialog @search="search" ref="dialogCreateRef" />
<MonitorDialog ref="dialogMonitorRef" />
<TerminalDialog ref="dialogTerminalRef" />
</el-card>
</LayoutContent>
</el-card>
<ReNameDialog @search="search" ref="dialogReNameRef" />
<ContainerLogDialog ref="dialogContainerLogRef" />
<CreateDialog @search="search" ref="dialogCreateRef" />
<MonitorDialog ref="dialogMonitorRef" />
<TerminalDialog ref="dialogTerminalRef" />
</el-card>
</LayoutContent>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import Submenu from '@/views/container/index.vue';
import { reactive, ref } from 'vue';
import LayoutContent from '@/layout/layout-content.vue';
import ReNameDialog from '@/views/container/container/rename/index.vue';
import CreateDialog from '@/views/container/container/create/index.vue';
@ -104,19 +106,30 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { dateFromat } from '@/utils/util';
import { ContainerOperator, inspect, searchContainer } from '@/api/modules/container';
import { composeOperator, ContainerOperator, inspect, searchContainer } from '@/api/modules/container';
import { ElMessage, ElMessageBox } from 'element-plus';
import i18n from '@/lang';
import { Container } from '@/api/interface/container';
interface Filters {
filters?: string;
}
const props = withDefaults(defineProps<Filters>(), {
filters: '',
});
const composeName = ref();
const composePath = ref();
const filters = ref();
const createdBy = ref();
const emit = defineEmits<{ (e: 'back'): void }>();
interface DialogProps {
createdBy: string;
name: string;
path: string;
filters: string;
}
const acceptParams = (props: DialogProps): void => {
composePath.value = props.path;
composeName.value = props.name;
filters.value = props.filters;
createdBy.value = props.createdBy;
search();
};
const data = ref();
const selects = ref<any>([]);
@ -129,8 +142,7 @@ const paginationConfig = reactive({
const loading = ref(false);
const search = async () => {
let filterItem = props.filters ? props.filters : '';
composeName.value = props.filters.replaceAll('com.docker.compose.project=', '');
let filterItem = filters.value;
let params = {
page: paginationConfig.page,
pageSize: paginationConfig.pageSize,
@ -173,20 +185,6 @@ const checkStatus = (operation: string) => {
}
}
return false;
case 'pause':
for (const item of selects.value) {
if (item.state === 'paused' || item.state === 'exited') {
return true;
}
}
return false;
case 'unpause':
for (const item of selects.value) {
if (item.state !== 'paused') {
return true;
}
}
return false;
}
};
@ -220,6 +218,38 @@ const onOperate = async (operation: string) => {
});
};
const onComposeOperate = async (operation: string) => {
ElMessageBox.confirm(
i18n.global.t('container.composeOperatorHelper', [composeName.value, i18n.global.t('container.' + operation)]),
i18n.global.t('container.' + operation),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
).then(async () => {
let params = {
name: composeName.value,
path: composePath.value,
operation: operation,
};
loading.value = true;
await composeOperator(params)
.then(() => {
loading.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
if (operation === 'down') {
emit('back');
} else {
search();
}
})
.catch(() => {
loading.value = false;
});
});
};
const dialogMonitorRef = ref();
const onMonitor = (containerID: string) => {
dialogMonitorRef.value!.acceptParams({ containerID: containerID });
@ -265,7 +295,8 @@ const buttons = [
},
},
];
onMounted(() => {
search();
defineExpose({
acceptParams,
});
</script>

View File

@ -2,34 +2,49 @@
<div v-loading="loading">
<Submenu activeName="compose" />
<el-card style="margin-top: 20px">
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
<template #toolbar>
<el-button icon="Plus" type="primary" @click="onOpenDialog()">
{{ $t('commons.button.create') }}
</el-button>
</template>
<el-table-column
:label="$t('commons.table.name')"
show-overflow-tooltip
min-width="100"
prop="name"
fix
<div v-if="!isOnDetail">
<ComplexTable
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
@search="search"
>
<template #default="{ row }">
<el-link @click="goContainer(row.name)" type="primary">{{ row.name }}</el-link>
<template #toolbar>
<el-button icon="Plus" type="primary" @click="onOpenDialog()">
{{ $t('commons.button.create') }}
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('container.from')" prop="createdBy" min-width="80" fix />
<el-table-column :label="$t('container.containerNumber')" prop="containerNumber" min-width="80" fix />
<el-table-column :label="$t('commons.table.createdAt')" prop="createdAt" min-width="80" fix />
<fu-table-operations
width="200px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
<el-table-column
:label="$t('commons.table.name')"
show-overflow-tooltip
min-width="100"
prop="name"
fix
>
<template #default="{ row }">
<el-link @click="loadDetail(row)" type="primary">{{ row.name }}</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('container.from')" prop="createdBy" min-width="80" fix />
<el-table-column
:label="$t('container.containerNumber')"
prop="containerNumber"
min-width="80"
fix
/>
<el-table-column :label="$t('commons.table.createdAt')" prop="createdAt" min-width="80" fix />
<fu-table-operations
width="200px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</div>
<div v-show="isOnDetail">
<ComposeDetial @back="backList" ref="composeDetailRef" />
</div>
</el-card>
<EditDialog ref="dialogEditRef" />
@ -40,13 +55,13 @@
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, onMounted, ref } from 'vue';
import CreateDialog from '@/views/container/compose/create/index.vue';
import EditDialog from '@/views/container/compose/edit/index.vue';
import CreateDialog from '@/views/container/compose/create/index.vue';
import ComposeDetial from '@/views/container/compose/detail/index.vue';
import Submenu from '@/views/container/index.vue';
import { composeOperator, searchCompose } from '@/api/modules/container';
import i18n from '@/lang';
import { ElMessage } from 'element-plus';
import router from '@/routers';
import { Container } from '@/api/interface/container';
import { useDeleteData } from '@/hooks/use-delete-data';
import { LoadFile } from '@/api/modules/files';
@ -55,6 +70,8 @@ const data = ref();
const selects = ref<any>([]);
const loading = ref(false);
const isOnDetail = ref(false);
const paginationConfig = reactive({
page: 1,
pageSize: 10,
@ -78,8 +95,20 @@ const search = async () => {
});
};
const goContainer = async (name: string) => {
router.push({ name: 'ComposeDetail', params: { filters: 'com.docker.compose.project=' + name } });
const composeDetailRef = ref();
const loadDetail = async (row: Container.ComposeInfo) => {
let params = {
createdBy: row.createdBy,
name: row.name,
path: row.path,
filters: 'com.docker.compose.project=' + row.name,
};
isOnDetail.value = true;
composeDetailRef.value!.acceptParams(params);
};
const backList = async () => {
isOnDetail.value = false;
search();
};
const dialogRef = ref();
@ -114,12 +143,18 @@ const buttons = [
click: (row: Container.ComposeInfo) => {
onEdit(row);
},
disabled: (row: any) => {
return row.createdBy !== 'local';
},
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: Container.ComposeInfo) => {
onDelete(row);
},
disabled: (row: any) => {
return row.createdBy === 'apps';
},
},
];
onMounted(() => {

View File

@ -77,7 +77,6 @@ import OperatorDialog from '@/views/container/template/operator/index.vue';
import { deleteComposeTemplate, searchComposeTemplate } from '@/api/modules/container';
import { useDeleteData } from '@/hooks/use-delete-data';
import i18n from '@/lang';
import { LoadFile } from '@/api/modules/files';
const data = ref();
const selects = ref<any>([]);
@ -104,14 +103,8 @@ const search = async () => {
};
const onOpenDetail = async (row: Container.TemplateInfo) => {
if (row.from === 'edit') {
detailInfo.value = row.content;
detailVisiable.value = true;
} else {
const res = await LoadFile({ path: row.path });
detailInfo.value = res.data;
detailVisiable.value = true;
}
detailInfo.value = row.content;
detailVisiable.value = true;
};
const dialogRef = ref();
@ -119,10 +112,8 @@ const onOpenDialog = async (
title: string,
rowData: Partial<Container.TemplateInfo> = {
name: '',
from: 'edit',
description: '',
path: '',
content: '',
},
) => {
let params = {

View File

@ -12,24 +12,7 @@
<el-form-item :label="$t('container.description')">
<el-input v-model="dialogData.rowData!.description"></el-input>
</el-form-item>
<el-form-item :label="$t('container.from')">
<el-radio-group v-model="dialogData.rowData!.from">
<el-radio label="edit">{{ $t('container.edit') }}</el-radio>
<el-radio label="path">{{ $t('container.pathSelect') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.from === 'path'" prop="path">
<el-input
clearable
:placeholder="$t('commons.example') + '/tmp/docker-compose.yml'"
v-model="dialogData.rowData!.path"
>
<template #append>
<FileList @choose="loadDir" :dir="false"></FileList>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.from === 'edit'">
<el-form-item>
<codemirror
:autofocus="true"
placeholder="None data"
@ -59,7 +42,6 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import FileList from '@/components/file-list/index.vue';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
@ -87,16 +69,8 @@ const acceptParams = (params: DialogProps): void => {
};
const emit = defineEmits<{ (e: 'search'): void }>();
const varifyPath = (rule: any, value: any, callback: any) => {
console.log(value, value.indexOf('docker-compose.yml'));
if (value.indexOf('docker-compose.yml') === -1) {
callback(new Error(i18n.global.t('commons.rule.selectHelper', ['docker-compose.yml'])));
}
callback();
};
const rules = reactive({
name: [Rules.requiredInput, Rules.name],
path: [Rules.requiredInput, { validator: varifyPath, trigger: 'change', required: true }],
content: [Rules.requiredInput],
});
@ -125,10 +99,6 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
});
};
const loadDir = async (path: string) => {
dialogData.value.rowData!.path = path;
};
defineExpose({
acceptParams,
});