mirror of
https://github.com/acepanel/panel.git
synced 2026-02-05 09:17:18 +08:00
514 lines
13 KiB
Vue
514 lines
13 KiB
Vue
<script setup lang="ts">
|
|
import Editor from '@guolao/vue-monaco-editor'
|
|
import { NButton, NDataTable, NDropdown, NFlex, NInput, NSwitch, NTag } from 'naive-ui'
|
|
|
|
import container from '@/api/panel/container'
|
|
import ContainerCreate from '@/views/container/ContainerCreate.vue'
|
|
import type { ContainerList } from '@/views/container/types'
|
|
|
|
const data = ref<ContainerList[]>([] as ContainerList[])
|
|
|
|
const logModal = ref(false)
|
|
const logs = ref('')
|
|
const renameModal = ref(false)
|
|
const renameModel = ref({
|
|
id: '',
|
|
name: ''
|
|
})
|
|
|
|
const containerCreateModal = ref(false)
|
|
const selectedRowKeys = ref<any>([])
|
|
|
|
const onChecked = (rowKeys: any) => {
|
|
selectedRowKeys.value = rowKeys
|
|
}
|
|
|
|
const columns: any = [
|
|
{ type: 'selection', fixed: 'left' },
|
|
{
|
|
title: '容器名',
|
|
key: 'name',
|
|
minWidth: 150,
|
|
resizable: true,
|
|
ellipsis: { tooltip: true }
|
|
},
|
|
{
|
|
title: '状态',
|
|
key: 'state',
|
|
width: 100,
|
|
resizable: true,
|
|
render(row: any) {
|
|
return h(NSwitch, {
|
|
size: 'small',
|
|
rubberBand: false,
|
|
value: row.state === 'running',
|
|
onUpdateValue: (value: boolean) => {
|
|
if (value) {
|
|
handleStart(row.id)
|
|
} else {
|
|
handleStop(row.id)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
},
|
|
{
|
|
title: '镜像',
|
|
key: 'image',
|
|
minWidth: 300,
|
|
resizable: true,
|
|
render(row: any): any {
|
|
return h(NTag, null, {
|
|
default: () => row.image
|
|
})
|
|
}
|
|
},
|
|
{
|
|
title: '端口(主机->容器)',
|
|
key: 'ports',
|
|
minWidth: 200,
|
|
resizable: true,
|
|
render(row: any): any {
|
|
return h(NFlex, null, {
|
|
default: () =>
|
|
row.ports.map((port: any) =>
|
|
h(NTag, null, {
|
|
default: () =>
|
|
`${port.host ? port.host + ':' : ''}${port.container_start}->${port.host_start}/${port.protocol}`
|
|
})
|
|
)
|
|
})
|
|
}
|
|
},
|
|
{
|
|
title: '运行状态',
|
|
key: 'status',
|
|
width: 300,
|
|
resizable: true,
|
|
ellipsis: { tooltip: true }
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'actions',
|
|
width: 250,
|
|
align: 'center',
|
|
hideInExcel: true,
|
|
render(row: any) {
|
|
return [
|
|
h(
|
|
NButton,
|
|
{
|
|
size: 'small',
|
|
type: 'warning',
|
|
secondary: true,
|
|
onClick: () => handleShowLog(row)
|
|
},
|
|
{
|
|
default: () => '日志'
|
|
}
|
|
),
|
|
h(
|
|
NButton,
|
|
{
|
|
size: 'small',
|
|
type: 'success',
|
|
style: 'margin-left: 15px;',
|
|
onClick: () => {
|
|
renameModel.value.id = row.id
|
|
renameModel.value.name = row.name
|
|
renameModal.value = true
|
|
}
|
|
},
|
|
{
|
|
default: () => '重命名'
|
|
}
|
|
),
|
|
h(
|
|
NDropdown,
|
|
{
|
|
options: [
|
|
{
|
|
label: '启动',
|
|
key: 'start',
|
|
disabled: row.state === 'running'
|
|
},
|
|
{
|
|
label: '停止',
|
|
key: 'stop',
|
|
disabled: row.state !== 'running'
|
|
},
|
|
{
|
|
label: '重启',
|
|
key: 'restart',
|
|
disabled: row.state !== 'running'
|
|
},
|
|
{
|
|
label: '强制停止',
|
|
key: 'forceStop',
|
|
disabled: row.state !== 'running'
|
|
},
|
|
{
|
|
label: '暂停',
|
|
key: 'pause',
|
|
disabled: row.state !== 'running'
|
|
},
|
|
{
|
|
label: '恢复',
|
|
key: 'unpause',
|
|
disabled: row.state === 'running'
|
|
},
|
|
{
|
|
label: '删除',
|
|
key: 'delete'
|
|
}
|
|
],
|
|
onSelect: (key: string) => {
|
|
switch (key) {
|
|
case 'start':
|
|
handleStart(row.id)
|
|
break
|
|
case 'stop':
|
|
handleStop(row.id)
|
|
break
|
|
case 'restart':
|
|
handleRestart(row.id)
|
|
break
|
|
case 'forceStop':
|
|
handleForceStop(row.id)
|
|
break
|
|
case 'pause':
|
|
handlePause(row.id)
|
|
break
|
|
case 'unpause':
|
|
handleUnpause(row.id)
|
|
break
|
|
case 'delete':
|
|
handleDelete(row.id)
|
|
break
|
|
}
|
|
}
|
|
},
|
|
{
|
|
default: () => {
|
|
return h(
|
|
NButton,
|
|
{
|
|
size: 'small',
|
|
type: 'primary',
|
|
style: 'margin-left: 15px;'
|
|
},
|
|
{
|
|
default: () => '更多'
|
|
}
|
|
)
|
|
}
|
|
}
|
|
)
|
|
]
|
|
}
|
|
}
|
|
]
|
|
|
|
const pagination = reactive({
|
|
page: 1,
|
|
pageCount: 1,
|
|
pageSize: 20,
|
|
itemCount: 0,
|
|
showQuickJumper: true,
|
|
showSizePicker: true,
|
|
pageSizes: [20, 50, 100, 200]
|
|
})
|
|
|
|
const onPageChange = (page: number) => {
|
|
pagination.page = page
|
|
getContainerList(page, pagination.pageSize).then((res) => {
|
|
data.value = res.items
|
|
pagination.itemCount = res.total
|
|
pagination.pageCount = res.total / pagination.pageSize + 1
|
|
})
|
|
}
|
|
|
|
const onPageSizeChange = (pageSize: number) => {
|
|
pagination.pageSize = pageSize
|
|
onPageChange(1)
|
|
}
|
|
|
|
const getContainerList = async (page: number, pageSize: number) => {
|
|
const { data } = await container.containerList(page, pageSize)
|
|
return data
|
|
}
|
|
|
|
const handleShowLog = async (row: any) => {
|
|
container.containerLogs(row.id).then((res) => {
|
|
logs.value = res.data
|
|
logModal.value = true
|
|
})
|
|
}
|
|
|
|
const handleRename = () => {
|
|
container.containerRename(renameModel.value.id, renameModel.value.name).then(() => {
|
|
window.$message.success('重命名成功')
|
|
renameModal.value = false
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const handleStart = (id: string) => {
|
|
container.containerStart(id).then(() => {
|
|
window.$message.success('启动成功')
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const handleStop = (id: string) => {
|
|
container.containerStop(id).then(() => {
|
|
window.$message.success('停止成功')
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const handleRestart = (id: string) => {
|
|
container.containerRestart(id).then(() => {
|
|
window.$message.success('重启成功')
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const handleForceStop = (id: string) => {
|
|
container.containerKill(id).then(() => {
|
|
window.$message.success('强制停止成功')
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const handlePause = (id: string) => {
|
|
container.containerPause(id).then(() => {
|
|
window.$message.success('暂停成功')
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const handleUnpause = (id: string) => {
|
|
container.containerUnpause(id).then(() => {
|
|
window.$message.success('恢复成功')
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const handleDelete = (id: string) => {
|
|
container.containerRemove(id).then(() => {
|
|
window.$message.success('删除成功')
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const handlePrune = () => {
|
|
container.containerPrune().then(() => {
|
|
window.$message.success('清理成功')
|
|
onPageChange(pagination.page)
|
|
})
|
|
}
|
|
|
|
const bulkStart = async () => {
|
|
if (selectedRowKeys.value.length === 0) {
|
|
window.$message.info('请选择要启动的容器')
|
|
return
|
|
}
|
|
|
|
for (const id of selectedRowKeys.value) {
|
|
await container.containerStart(id).then(() => {
|
|
const container = data.value.find((item) => item.id === id)
|
|
window.$message.success(`${container?.name} 启动成功`)
|
|
})
|
|
}
|
|
|
|
onPageChange(pagination.page)
|
|
}
|
|
|
|
const bulkStop = async () => {
|
|
if (selectedRowKeys.value.length === 0) {
|
|
window.$message.info('请选择要停止的容器')
|
|
return
|
|
}
|
|
|
|
for (const id of selectedRowKeys.value) {
|
|
await container.containerStop(id).then(() => {
|
|
const container = data.value.find((item) => item.id === id)
|
|
window.$message.success(`${container?.name} 停止成功`)
|
|
})
|
|
}
|
|
|
|
onPageChange(pagination.page)
|
|
}
|
|
|
|
const bulkRestart = async () => {
|
|
if (selectedRowKeys.value.length === 0) {
|
|
window.$message.info('请选择要重启的容器')
|
|
return
|
|
}
|
|
|
|
for (const id of selectedRowKeys.value) {
|
|
await container.containerRestart(id).then(() => {
|
|
const container = data.value.find((item) => item.id === id)
|
|
window.$message.success(`${container?.name} 重启成功`)
|
|
})
|
|
}
|
|
|
|
onPageChange(pagination.page)
|
|
}
|
|
|
|
const bulkForceStop = async () => {
|
|
if (selectedRowKeys.value.length === 0) {
|
|
window.$message.info('请选择要强制停止的容器')
|
|
return
|
|
}
|
|
|
|
for (const id of selectedRowKeys.value) {
|
|
await container.containerKill(id).then(() => {
|
|
const container = data.value.find((item) => item.id === id)
|
|
window.$message.success(`${container?.name} 强制停止成功`)
|
|
})
|
|
}
|
|
|
|
onPageChange(pagination.page)
|
|
}
|
|
|
|
const bulkDelete = async () => {
|
|
if (selectedRowKeys.value.length === 0) {
|
|
window.$message.info('请选择要删除的容器')
|
|
return
|
|
}
|
|
|
|
for (const id of selectedRowKeys.value) {
|
|
await container.containerRemove(id).then(() => {
|
|
const container = data.value.find((item) => item.id === id)
|
|
window.$message.success(`${container?.name} 删除成功`)
|
|
})
|
|
}
|
|
|
|
onPageChange(pagination.page)
|
|
}
|
|
|
|
const bulkPause = async () => {
|
|
if (selectedRowKeys.value.length === 0) {
|
|
window.$message.info('请选择要暂停的容器')
|
|
return
|
|
}
|
|
|
|
for (const id of selectedRowKeys.value) {
|
|
await container.containerPause(id).then(() => {
|
|
const container = data.value.find((item) => item.id === id)
|
|
window.$message.success(`${container?.name} 暂停成功`)
|
|
})
|
|
}
|
|
|
|
onPageChange(pagination.page)
|
|
}
|
|
|
|
const bulkUnpause = async () => {
|
|
if (selectedRowKeys.value.length === 0) {
|
|
window.$message.info('请选择要恢复的容器')
|
|
return
|
|
}
|
|
|
|
for (const id of selectedRowKeys.value) {
|
|
await container.containerUnpause(id).then(() => {
|
|
const container = data.value.find((item) => item.id === id)
|
|
window.$message.success(`${container?.name} 恢复成功`)
|
|
})
|
|
}
|
|
|
|
onPageChange(pagination.page)
|
|
}
|
|
|
|
const closeContainerCreateModal = () => {
|
|
containerCreateModal.value = false
|
|
onPageChange(pagination.page)
|
|
}
|
|
|
|
onMounted(() => {
|
|
onPageChange(pagination.page)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<n-space vertical size="large">
|
|
<n-card rounded-10>
|
|
<n-space>
|
|
<n-button type="primary" @click="containerCreateModal = true">创建容器</n-button>
|
|
<n-button type="primary" @click="handlePrune" ghost>清理容器</n-button>
|
|
<n-button-group>
|
|
<n-button @click="bulkStart">启动</n-button>
|
|
<n-button @click="bulkStop">停止</n-button>
|
|
<n-button @click="bulkRestart">重启</n-button>
|
|
<n-button @click="bulkForceStop">强制停止</n-button>
|
|
<n-button @click="bulkPause">暂停</n-button>
|
|
<n-button @click="bulkUnpause">恢复</n-button>
|
|
<n-button @click="bulkDelete">删除</n-button>
|
|
</n-button-group>
|
|
</n-space>
|
|
</n-card>
|
|
<n-card rounded-10>
|
|
<n-data-table
|
|
striped
|
|
remote
|
|
:scroll-x="1000"
|
|
:data="data"
|
|
:columns="columns"
|
|
:row-key="(row: any) => row.id"
|
|
:pagination="pagination"
|
|
:bordered="false"
|
|
:loading="false"
|
|
@update:page="onPageChange"
|
|
@update:page-size="onPageSizeChange"
|
|
@update:checked-row-keys="onChecked"
|
|
/>
|
|
</n-card>
|
|
</n-space>
|
|
<n-modal
|
|
v-model:show="logModal"
|
|
preset="card"
|
|
title="日志"
|
|
style="width: 80vw"
|
|
size="huge"
|
|
:bordered="false"
|
|
:segmented="false"
|
|
>
|
|
<Editor
|
|
v-model:value="logs"
|
|
language="ini"
|
|
theme="vs-dark"
|
|
height="60vh"
|
|
mt-8
|
|
:options="{
|
|
automaticLayout: true,
|
|
formatOnType: true,
|
|
formatOnPaste: true,
|
|
readOnly: true
|
|
}"
|
|
/>
|
|
</n-modal>
|
|
<n-modal
|
|
v-model:show="renameModal"
|
|
preset="card"
|
|
title="重命名"
|
|
style="width: 60vw"
|
|
size="huge"
|
|
:bordered="false"
|
|
:segmented="false"
|
|
>
|
|
<n-form :model="renameModel">
|
|
<n-form-item path="name" label="新名称">
|
|
<n-input
|
|
v-model:value="renameModel.name"
|
|
type="text"
|
|
@keydown.enter.prevent
|
|
placeholder="输入新名称"
|
|
/>
|
|
</n-form-item>
|
|
</n-form>
|
|
<n-button type="info" block @click="handleRename">提交</n-button>
|
|
</n-modal>
|
|
<ContainerCreate :show="containerCreateModal" @close="closeContainerCreateModal" />
|
|
</template>
|