mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-28 05:15:04 +08:00
feat: 增加编排详情界面
This commit is contained in:
parent
415200492a
commit
cecc108784
@ -9,3 +9,9 @@ type ComposeTemplate struct {
|
||||
Path string `gorm:"type:varchar(64)" json:"path"`
|
||||
Content string `gorm:"type:longtext" json:"content"`
|
||||
}
|
||||
|
||||
type Compose struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"type:varchar(256)" json:"name"`
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ type IComposeTemplateRepo interface {
|
||||
Create(compose *model.ComposeTemplate) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
|
||||
CreateRecord(compose *model.Compose) error
|
||||
DeleteRecord(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewIComposeTemplateRepo() IComposeTemplateRepo {
|
||||
@ -67,3 +70,23 @@ func (u *ComposeTemplateRepo) Delete(opts ...DBOption) error {
|
||||
}
|
||||
return db.Delete(&model.ComposeTemplate{}).Error
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateRepo) ListRecord() ([]model.Compose, error) {
|
||||
var composes []model.Compose
|
||||
if err := global.DB.Find(&composes).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return composes, nil
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateRepo) CreateRecord(compose *model.Compose) error {
|
||||
return global.DB.Create(compose).Error
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateRepo) DeleteRecord(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.Compose{}).Error
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ var AddTableApp = &gormigrate.Migration{
|
||||
var AddTableImageRepo = &gormigrate.Migration{
|
||||
ID: "20201009-add-table-imagerepo",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.ImageRepo{}, &model.ComposeTemplate{}); err != nil {
|
||||
if err := tx.AutoMigrate(&model.ImageRepo{}, &model.ComposeTemplate{}, &model.Compose{}); err != nil {
|
||||
return err
|
||||
}
|
||||
item := &model.ImageRepo{
|
||||
|
@ -11,7 +11,7 @@ const containerRouter = {
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: ':filters?',
|
||||
path: '',
|
||||
name: 'Container',
|
||||
component: () => import('@/views/container/container/index.vue'),
|
||||
props: true,
|
||||
@ -20,6 +20,16 @@ const containerRouter = {
|
||||
activeMenu: '/containers',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'composeDetail/:filters?',
|
||||
name: 'ComposeDetail',
|
||||
component: () => import('@/views/container/compose/detail/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/containers',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'image',
|
||||
name: 'Image',
|
||||
|
271
frontend/src/views/container/compose/detail/index.vue
Normal file
271
frontend/src/views/container/compose/detail/index.vue
Normal file
@ -0,0 +1,271 @@
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import Submenu from '@/views/container/index.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';
|
||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||
import ContainerLogDialog from '@/views/container/container/log/index.vue';
|
||||
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 { 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 data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const paginationConfig = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const search = async () => {
|
||||
let filterItem = props.filters ? props.filters : '';
|
||||
composeName.value = props.filters.replaceAll('com.docker.compose.project=', '');
|
||||
let params = {
|
||||
page: paginationConfig.page,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
filters: filterItem,
|
||||
};
|
||||
await searchContainer(params).then((res) => {
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
});
|
||||
};
|
||||
|
||||
const detailInfo = ref();
|
||||
const mydetail = ref();
|
||||
const onInspect = async (id: string) => {
|
||||
const res = await inspect({ id: id, type: 'container' });
|
||||
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
||||
let param = {
|
||||
header: i18n.global.t('commons.button.view'),
|
||||
detailInfo: detailInfo.value,
|
||||
};
|
||||
mydetail.value!.acceptParams(param);
|
||||
};
|
||||
|
||||
const checkStatus = (operation: string) => {
|
||||
if (selects.value.length < 1) {
|
||||
return true;
|
||||
}
|
||||
switch (operation) {
|
||||
case 'start':
|
||||
for (const item of selects.value) {
|
||||
if (item.state === 'running') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case 'stop':
|
||||
for (const item of selects.value) {
|
||||
if (item.state === 'stopped' || item.state === 'exited') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const onOperate = async (operation: string) => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('container.operatorHelper', [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(() => {
|
||||
let ps = [];
|
||||
for (const item of selects.value) {
|
||||
const param = {
|
||||
containerID: item.containerID,
|
||||
operation: operation,
|
||||
newName: '',
|
||||
};
|
||||
ps.push(ContainerOperator(param));
|
||||
}
|
||||
Promise.all(ps)
|
||||
.then(() => {
|
||||
search();
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
search();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const dialogMonitorRef = ref();
|
||||
const onMonitor = (containerID: string) => {
|
||||
dialogMonitorRef.value!.acceptParams({ containerID: containerID });
|
||||
};
|
||||
|
||||
const dialogTerminalRef = ref();
|
||||
const onTerminal = (containerID: string) => {
|
||||
dialogTerminalRef.value!.acceptParams({ containerID: containerID });
|
||||
};
|
||||
|
||||
const dialogContainerLogRef = ref();
|
||||
const dialogReNameRef = ref();
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('file.terminal'),
|
||||
disabled: (row: Container.ContainerInfo) => {
|
||||
return row.state !== 'running';
|
||||
},
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onTerminal(row.containerID);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.monitor'),
|
||||
disabled: (row: Container.ContainerInfo) => {
|
||||
return row.state !== 'running';
|
||||
},
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onMonitor(row.containerID);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.rename'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
dialogReNameRef.value!.acceptParams({ containerID: row.containerID, container: row.name });
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.log'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
dialogContainerLogRef.value!.acceptParams({ containerID: row.containerID });
|
||||
},
|
||||
},
|
||||
];
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
@ -7,28 +7,7 @@
|
||||
<el-button icon="Plus" type="primary" @click="onOpenDialog()">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
<el-button-group style="margin-left: 10px">
|
||||
<el-button :disabled="selects.length === 0" @click="onOperate('up')">
|
||||
{{ $t('container.start') }}
|
||||
</el-button>
|
||||
<el-button :disabled="selects.length === 0" @click="onOperate('stop')">
|
||||
{{ $t('container.stop') }}
|
||||
</el-button>
|
||||
<el-button :disabled="selects.length === 0" @click="onOperate('pause')">
|
||||
{{ $t('container.pause') }}
|
||||
</el-button>
|
||||
<el-button :disabled="selects.length === 0" @click="onOperate('unpause')">
|
||||
{{ $t('container.unpause') }}
|
||||
</el-button>
|
||||
<el-button :disabled="selects.length === 0" @click="onOperate('restart')">
|
||||
{{ $t('container.restart') }}
|
||||
</el-button>
|
||||
<el-button :disabled="selects.length === 0" @click="onOperate('down')">
|
||||
{{ $t('container.down') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</template>
|
||||
<el-table-column type="selection" fix></el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
show-overflow-tooltip
|
||||
@ -42,38 +21,35 @@
|
||||
</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('container.container')" prop="contaienrs" min-width="80" fix>
|
||||
<template #default="{ row }">
|
||||
<div v-for="(item, index) in row.containers" :key="index">
|
||||
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||
<el-tag>{{ item.name }} [{{ item.state }}]</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!row.expand && row.containers.length > 3">
|
||||
<el-button type="primary" link @click="row.expand = true">
|
||||
{{ $t('commons.button.expand') }}...
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<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-card>
|
||||
|
||||
<OperatorDialog @search="search" ref="dialogRef" />
|
||||
<EditDialog ref="dialogEditRef" />
|
||||
<CreateDialog @search="search" ref="dialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import OperatorDialog from '@/views/container/compose/operator/index.vue';
|
||||
import CreateDialog from '@/views/container/compose/create/index.vue';
|
||||
import EditDialog from '@/views/container/compose/edit/index.vue';
|
||||
import Submenu from '@/views/container/index.vue';
|
||||
import { ComposeOperator, searchCompose } from '@/api/modules/container';
|
||||
import { composeOperator, searchCompose } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
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';
|
||||
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
@ -103,7 +79,7 @@ const search = async () => {
|
||||
};
|
||||
|
||||
const goContainer = async (name: string) => {
|
||||
router.push({ name: 'Container', params: { filters: 'com.docker.compose.project=' + name } });
|
||||
router.push({ name: 'ComposeDetail', params: { filters: 'com.docker.compose.project=' + name } });
|
||||
};
|
||||
|
||||
const dialogRef = ref();
|
||||
@ -111,35 +87,41 @@ const onOpenDialog = async () => {
|
||||
dialogRef.value!.acceptParams();
|
||||
};
|
||||
|
||||
const onOperate = async (operation: string) => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('container.operatorComposeHelper', [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(() => {
|
||||
let ps = [];
|
||||
for (const item of selects.value) {
|
||||
const param = {
|
||||
path: item.path,
|
||||
operation: operation,
|
||||
};
|
||||
ps.push(ComposeOperator(param));
|
||||
}
|
||||
Promise.all(ps)
|
||||
.then(() => {
|
||||
search();
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
search();
|
||||
});
|
||||
});
|
||||
const onDelete = async (row: Container.ComposeInfo) => {
|
||||
const param = {
|
||||
name: row.name,
|
||||
path: row.path,
|
||||
operation: 'down',
|
||||
};
|
||||
await useDeleteData(composeOperator, param, 'commons.msg.delete');
|
||||
search();
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
};
|
||||
|
||||
const dialogEditRef = ref();
|
||||
const onEdit = async (row: Container.ComposeInfo) => {
|
||||
const res = await LoadFile({ path: row.path });
|
||||
let params = {
|
||||
path: row.path,
|
||||
content: res.data,
|
||||
};
|
||||
dialogEditRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: (row: Container.ComposeInfo) => {
|
||||
onEdit(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: Container.ComposeInfo) => {
|
||||
onDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
@ -69,75 +69,8 @@
|
||||
|
||||
<CodemirrorDialog ref="mydetail" />
|
||||
|
||||
<el-dialog
|
||||
@close="onCloseLog"
|
||||
v-model="logVisiable"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
width="70%"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('commons.button.log') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-select @change="searchLogs" style="width: 10%; float: left" v-model="logSearch.mode">
|
||||
<el-option v-for="item in timeOptions" :key="item.label" :value="item.value" :label="item.label" />
|
||||
</el-select>
|
||||
<div style="margin-left: 20px; float: left">
|
||||
<el-checkbox border v-model="logSearch.isWatch">{{ $t('commons.button.watch') }}</el-checkbox>
|
||||
</div>
|
||||
<el-button style="margin-left: 20px" @click="onDownload" icon="Download">
|
||||
{{ $t('file.download') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
placeholder="None data"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="margin-top: 10px; max-height: 500px"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="logInfo"
|
||||
:readOnly="true"
|
||||
/>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="logVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
@close="search()"
|
||||
v-model="newNameVisiable"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
width="30%"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('container.rename') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="newNameRef" :model="renameForm">
|
||||
<el-form-item :label="$t('container.newName')" :rules="Rules.requiredInput" prop="newName">
|
||||
<el-input v-model="renameForm.newName"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="newNameVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button @click="onSubmitName(newNameRef)">{{ $t('commons.button.confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<ReNameDialog @search="search" ref="dialogReNameRef" />
|
||||
<ContainerLogDialog ref="dialogContainerLogRef" />
|
||||
<CreateDialog @search="search" ref="dialogCreateRef" />
|
||||
<MonitorDialog ref="dialogMonitorRef" />
|
||||
<TerminalDialog ref="dialogTerminalRef" />
|
||||
@ -146,20 +79,18 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import ReNameDialog from '@/views/container/container/rename/index.vue';
|
||||
import CreateDialog from '@/views/container/container/create/index.vue';
|
||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||
import ContainerLogDialog from '@/views/container/container/log/index.vue';
|
||||
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
||||
import Submenu from '@/views/container/index.vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { dateFromat, dateFromatForName } from '@/utils/util';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { ContainerOperator, inspect, logContainer, searchContainer } from '@/api/modules/container';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { ContainerOperator, inspect, searchContainer } from '@/api/modules/container';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import { ElForm, ElMessage, ElMessageBox, FormInstance } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const data = ref();
|
||||
@ -180,45 +111,8 @@ const props = withDefaults(defineProps<Filters>(), {
|
||||
const detailInfo = ref();
|
||||
const mydetail = ref();
|
||||
|
||||
const extensions = [javascript(), oneDark];
|
||||
const logVisiable = ref<boolean>(false);
|
||||
const logInfo = ref();
|
||||
const logSearch = reactive({
|
||||
isWatch: false,
|
||||
container: '',
|
||||
containerID: '',
|
||||
mode: 'all',
|
||||
});
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const newNameVisiable = ref<boolean>(false);
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const newNameRef = ref<FormInstance>();
|
||||
const renameForm = reactive({
|
||||
containerID: '',
|
||||
operation: 'rename',
|
||||
newName: '',
|
||||
});
|
||||
|
||||
const timeOptions = ref([
|
||||
{ label: i18n.global.t('container.all'), value: 'all' },
|
||||
{
|
||||
label: i18n.global.t('container.lastDay'),
|
||||
value: new Date(new Date().getTime() - 3600 * 1000 * 24 * 1).getTime() / 1000 + '',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.last4Hour'),
|
||||
value: new Date(new Date().getTime() - 3600 * 1000 * 4).getTime() / 1000 + '',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.lastHour'),
|
||||
value: new Date(new Date().getTime() - 3600 * 1000).getTime() / 1000 + '',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.last10Min'),
|
||||
value: new Date(new Date().getTime() - 600 * 1000).getTime() / 1000 + '',
|
||||
},
|
||||
]);
|
||||
const dialogContainerLogRef = ref();
|
||||
const dialogReNameRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
let filterItem = props.filters ? props.filters : '';
|
||||
@ -228,9 +122,8 @@ const search = async () => {
|
||||
filters: filterItem,
|
||||
};
|
||||
await searchContainer(params).then((res) => {
|
||||
if (res.data) {
|
||||
data.value = res.data.items;
|
||||
}
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
});
|
||||
};
|
||||
|
||||
@ -259,50 +152,6 @@ const onInspect = async (id: string) => {
|
||||
mydetail.value!.acceptParams(param);
|
||||
};
|
||||
|
||||
const onLog = async (row: Container.ContainerInfo) => {
|
||||
logSearch.container = row.name;
|
||||
logSearch.containerID = row.containerID;
|
||||
searchLogs();
|
||||
logVisiable.value = true;
|
||||
timer = setInterval(() => {
|
||||
if (logVisiable.value && logSearch.isWatch) {
|
||||
searchLogs();
|
||||
}
|
||||
}, 1000 * 5);
|
||||
};
|
||||
const onCloseLog = async () => {
|
||||
clearInterval(Number(timer));
|
||||
};
|
||||
const searchLogs = async () => {
|
||||
const res = await logContainer(logSearch);
|
||||
logInfo.value = res.data;
|
||||
};
|
||||
const onDownload = async () => {
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([logInfo.value]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
a.download = logSearch.container + '-' + dateFromatForName(new Date()) + '.log';
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
};
|
||||
|
||||
const onRename = async (row: Container.ContainerInfo) => {
|
||||
renameForm.containerID = row.containerID;
|
||||
renameForm.newName = '';
|
||||
newNameVisiable.value = true;
|
||||
};
|
||||
const onSubmitName = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
await ContainerOperator(renameForm);
|
||||
search();
|
||||
newNameVisiable.value = false;
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
});
|
||||
};
|
||||
|
||||
const checkStatus = (operation: string) => {
|
||||
if (selects.value.length < 1) {
|
||||
return true;
|
||||
@ -390,13 +239,13 @@ const buttons = [
|
||||
{
|
||||
label: i18n.global.t('container.rename'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onRename(row);
|
||||
dialogReNameRef.value!.acceptParams({ containerID: row.containerID, container: row.name });
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.log'),
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onLog(row);
|
||||
dialogContainerLogRef.value!.acceptParams({ containerID: row.containerID });
|
||||
},
|
||||
},
|
||||
];
|
||||
|
131
frontend/src/views/container/container/log/index.vue
Normal file
131
frontend/src/views/container/container/log/index.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="logVisiable"
|
||||
@close="onCloseLog"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
width="70%"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('commons.button.log') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-select @change="searchLogs" style="width: 10%; float: left" v-model="logSearch.mode">
|
||||
<el-option v-for="item in timeOptions" :key="item.label" :value="item.value" :label="item.label" />
|
||||
</el-select>
|
||||
<div style="margin-left: 20px; float: left">
|
||||
<el-checkbox border v-model="logSearch.isWatch">{{ $t('commons.button.watch') }}</el-checkbox>
|
||||
</div>
|
||||
<el-button style="margin-left: 20px" @click="onDownload" icon="Download">
|
||||
{{ $t('file.download') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
placeholder="None data"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="margin-top: 10px; max-height: 500px"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="logInfo"
|
||||
:readOnly="true"
|
||||
/>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="logVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { logContainer } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { dateFromatForName } from '@/utils/util';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
|
||||
const extensions = [javascript(), oneDark];
|
||||
|
||||
const logVisiable = ref(false);
|
||||
|
||||
const logInfo = ref();
|
||||
const logSearch = reactive({
|
||||
isWatch: false,
|
||||
container: '',
|
||||
containerID: '',
|
||||
mode: 'all',
|
||||
});
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const timeOptions = ref([
|
||||
{ label: i18n.global.t('container.all'), value: 'all' },
|
||||
{
|
||||
label: i18n.global.t('container.lastDay'),
|
||||
value: new Date(new Date().getTime() - 3600 * 1000 * 24 * 1).getTime() / 1000 + '',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.last4Hour'),
|
||||
value: new Date(new Date().getTime() - 3600 * 1000 * 4).getTime() / 1000 + '',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.lastHour'),
|
||||
value: new Date(new Date().getTime() - 3600 * 1000).getTime() / 1000 + '',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.last10Min'),
|
||||
value: new Date(new Date().getTime() - 600 * 1000).getTime() / 1000 + '',
|
||||
},
|
||||
]);
|
||||
|
||||
const onCloseLog = async () => {
|
||||
clearInterval(Number(timer));
|
||||
};
|
||||
|
||||
const searchLogs = async () => {
|
||||
const res = await logContainer(logSearch);
|
||||
logInfo.value = res.data;
|
||||
};
|
||||
|
||||
const onDownload = async () => {
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([logInfo.value]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
a.download = logSearch.container + '-' + dateFromatForName(new Date()) + '.log';
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
};
|
||||
|
||||
interface DialogProps {
|
||||
container: string;
|
||||
containerID: string;
|
||||
}
|
||||
|
||||
const acceptParams = (props: DialogProps): void => {
|
||||
logVisiable.value = true;
|
||||
logSearch.containerID = props.containerID;
|
||||
logSearch.mode = 'all';
|
||||
logSearch.isWatch = false;
|
||||
logSearch.container = props.container;
|
||||
searchLogs();
|
||||
timer = setInterval(() => {
|
||||
if (logSearch.isWatch) {
|
||||
searchLogs();
|
||||
}
|
||||
}, 1000 * 5);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
78
frontend/src/views/container/container/reName/index.vue
Normal file
78
frontend/src/views/container/container/reName/index.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
@close="onClose()"
|
||||
v-model="newNameVisiable"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
width="30%"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('container.rename') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="newNameRef" :model="renameForm">
|
||||
<el-form-item :label="$t('container.newName')" :rules="Rules.requiredInput" prop="newName">
|
||||
<el-input v-model="renameForm.newName"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="newNameVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmitName(newNameRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ContainerOperator } from '@/api/modules/container';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm, ElMessage } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
const renameForm = reactive({
|
||||
containerID: '',
|
||||
operation: 'rename',
|
||||
newName: '',
|
||||
});
|
||||
|
||||
const newNameRef = ref<FormInstance>();
|
||||
|
||||
const newNameVisiable = ref<boolean>(false);
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const onSubmitName = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
await ContainerOperator(renameForm);
|
||||
emit('search');
|
||||
newNameVisiable.value = false;
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
});
|
||||
};
|
||||
|
||||
interface DialogProps {
|
||||
containerID: string;
|
||||
}
|
||||
|
||||
const acceptParams = (props: DialogProps): void => {
|
||||
renameForm.containerID = props.containerID;
|
||||
renameForm.newName = '';
|
||||
newNameVisiable.value = true;
|
||||
};
|
||||
|
||||
const onClose = async () => {
|
||||
emit('search');
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user