2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-07 05:47:21 +08:00

feat: 提交部分前端翻译

This commit is contained in:
2025-04-13 01:14:11 +08:00
parent db0679cd92
commit df32b3b5de
25 changed files with 5589 additions and 1294 deletions

View File

@@ -1,10 +1,12 @@
<script setup lang="ts">
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import container from '@/api/panel/container'
import { useFileStore } from '@/store'
import { formatDateTime } from '@/utils'
const { $gettext } = useGettext()
const fileStore = useFileStore()
const router = useRouter()
@@ -26,14 +28,14 @@ const updateModal = ref(false)
const columns: any = [
{
title: '名称',
title: $gettext('Name'),
key: 'name',
minWidth: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '目录',
title: $gettext('Directory'),
key: 'path',
minWidth: 150,
resizable: true,
@@ -53,14 +55,14 @@ const columns: any = [
}
},
{
title: '状态',
title: $gettext('Status'),
key: 'status',
width: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '创建时间',
title: $gettext('Creation Time'),
key: 'created_at',
width: 200,
resizable: true,
@@ -69,7 +71,7 @@ const columns: any = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 280,
align: 'center',
@@ -92,7 +94,7 @@ const columns: any = [
}
},
{
default: () => '编辑'
default: () => $gettext('Edit')
}
),
h(
@@ -100,14 +102,14 @@ const columns: any = [
{
showIcon: false,
onPositiveClick: () => {
const messageReactive = window.$message.loading('启动中...', {
const messageReactive = window.$message.loading($gettext('Starting...'), {
duration: 0
})
useRequest(container.composeUp(row.name, forcePush.value))
.onSuccess(() => {
refresh()
forcePush.value = false
window.$message.success('启动成功')
window.$message.success($gettext('Start successful'))
})
.onComplete(() => {
messageReactive?.destroy()
@@ -123,14 +125,14 @@ const columns: any = [
},
{
default: () => [
h('strong', {}, { default: () => `确定启动编排 ${row.name} 吗?` }),
h('strong', {}, { default: () => $gettext(`Are you sure you want to start compose %{ name }?`, { name: row.name }) }),
h(
NCheckbox,
{
checked: forcePush.value,
onUpdateChecked: (v) => (forcePush.value = v)
},
{ default: () => '强制拉取镜像' }
{ default: () => $gettext('Force pull images') }
)
]
}
@@ -145,7 +147,7 @@ const columns: any = [
type: 'success'
},
{
default: () => '启动'
default: () => $gettext('Start')
}
)
}
@@ -157,13 +159,13 @@ const columns: any = [
onPositiveClick: () => {
useRequest(container.composeDown(row.name)).onSuccess(() => {
refresh()
window.$message.success('停止成功')
window.$message.success($gettext('Stop successful'))
})
}
},
{
default: () => {
return `确定停止编排 ${row.name} 吗?`
return $gettext(`Are you sure you want to stop compose %{ name }?`, { name: row.name })
},
trigger: () => {
return h(
@@ -174,7 +176,7 @@ const columns: any = [
type: 'warning'
},
{
default: () => '停止'
default: () => $gettext('Stop')
}
)
}
@@ -186,13 +188,13 @@ const columns: any = [
onPositiveClick: () => {
useRequest(container.composeRemove(row.name)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Delete successful'))
})
}
},
{
default: () => {
return `确定删除编排 ${row.name} 吗?`
return $gettext(`Are you sure you want to delete compose %{ name }?`, { name: row.name })
},
trigger: () => {
return h(
@@ -203,7 +205,7 @@ const columns: any = [
type: 'error'
},
{
default: () => '删除'
default: () => $gettext('Delete')
}
)
}
@@ -229,7 +231,7 @@ const handleCreate = () => {
useRequest(container.composeCreate(createModel.value))
.onSuccess(() => {
refresh()
window.$message.success('创建成功')
window.$message.success($gettext('Created successfully'))
})
.onComplete(() => {
loading.value = false
@@ -247,7 +249,7 @@ const handleUpdate = () => {
useRequest(container.composeUpdate(updateModel.value.name, updateModel.value))
.onSuccess(() => {
refresh()
window.$message.success('更新成功')
window.$message.success($gettext('Update successful'))
})
.onComplete(() => {
loading.value = false
@@ -268,7 +270,7 @@ onMounted(() => {
<template>
<n-flex vertical :size="20">
<n-flex>
<n-button type="primary" @click="createModal = true">创建编排</n-button>
<n-button type="primary" @click="createModal = true">{{ $gettext('Create Compose') }}</n-button>
</n-flex>
<n-data-table
striped
@@ -294,64 +296,64 @@ onMounted(() => {
<n-modal
v-model:show="createModal"
preset="card"
title="创建编排"
:title="$gettext('Create Compose')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="createModel">
<n-form-item path="name" label="编排名">
<n-form-item path="name" :label="$gettext('Compose Name')">
<n-input v-model:value="createModel.name" type="text" />
</n-form-item>
<n-form-item path="compose" label="编排">
<n-form-item path="compose" :label="$gettext('Compose')">
<n-input
v-model:value="createModel.compose"
type="textarea"
:autosize="{ minRows: 10, maxRows: 20 }"
/>
</n-form-item>
<n-form-item path="envs" label="环境变量">
<n-form-item path="envs" :label="$gettext('Environment Variables')">
<n-dynamic-input
v-model:value="createModel.envs"
preset="pair"
key-placeholder="变量名"
value-placeholder="变量值"
:key-placeholder="$gettext('Variable Name')"
:value-placeholder="$gettext('Variable Value')"
/>
</n-form-item>
</n-form>
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleCreate">
提交
{{ $gettext('Submit') }}
</n-button>
</n-modal>
<n-modal
v-model:show="updateModal"
preset="card"
title="编辑编排"
:title="$gettext('Edit Compose')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="updateModel">
<n-form-item path="compose" label="编排">
<n-form-item path="compose" :label="$gettext('Compose')">
<n-input
v-model:value="updateModel.compose"
type="textarea"
:autosize="{ minRows: 10, maxRows: 20 }"
/>
</n-form-item>
<n-form-item path="envs" label="环境变量">
<n-form-item path="envs" :label="$gettext('Environment Variables')">
<n-dynamic-input
v-model:value="updateModel.envs"
preset="pair"
key-placeholder="变量名"
value-placeholder="变量值"
:key-placeholder="$gettext('Variable Name')"
:value-placeholder="$gettext('Variable Value')"
/>
</n-form-item>
</n-form>
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleUpdate">
提交
{{ $gettext('Submit') }}
</n-button>
</n-modal>
</template>

View File

@@ -1,5 +1,8 @@
<script setup lang="ts">
import container from '@/api/panel/container'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const props = defineProps({
show: {
@@ -49,10 +52,10 @@ const createModel = reactive({
const networks = ref<any>({})
const restartPolicyOptions = [
{ label: '无', value: 'no' },
{ label: '始终', value: 'always' },
{ label: '失败时(默认重启 5 次)', value: 'on-failure' },
{ label: '未手动停止则重启', value: 'unless-stopped' }
{ label: $gettext('None'), value: 'no' },
{ label: $gettext('Always'), value: 'always' },
{ label: $gettext('On failure (default 5 retries)'), value: 'on-failure' },
{ label: $gettext('Unless stopped'), value: 'unless-stopped' }
]
const addPortRow = () => {
@@ -100,7 +103,7 @@ const handleSubmit = () => {
doSubmit.value = true
useRequest(container.containerCreate(createModel))
.onSuccess(() => {
window.$message.success('创建成功')
window.$message.success($gettext('Created successfully'))
handleClose()
})
.onComplete(() => {
@@ -121,7 +124,7 @@ onMounted(() => {
<template>
<n-modal
title="创建容器"
:title="$gettext('Create Container')"
preset="card"
style="width: 60vw"
size="huge"
@@ -131,40 +134,40 @@ onMounted(() => {
@close="handleClose"
>
<n-form :model="createModel">
<n-form-item path="name" label="容器名">
<n-form-item path="name" :label="$gettext('Container Name')">
<n-input v-model:value="createModel.name" type="text" @keydown.enter.prevent />
</n-form-item>
<n-form-item path="name" label="镜像">
<n-form-item path="name" :label="$gettext('Image')">
<n-input v-model:value="createModel.image" type="text" @keydown.enter.prevent />
</n-form-item>
<n-form-item path="exposedAll" label="端口">
<n-form-item path="exposedAll" :label="$gettext('Ports')">
<n-radio
:checked="!createModel.publish_all_ports"
:value="false"
@change="createModel.publish_all_ports = !$event.target.value"
>
映射端口
{{ $gettext('Map Ports') }}
</n-radio>
<n-radio
:checked="createModel.publish_all_ports"
:value="true"
@change="createModel.publish_all_ports = !!$event.target.value"
>
暴露所有
{{ $gettext('Expose All') }}
</n-radio>
</n-form-item>
<n-form-item path="ports" label="端口映射" v-if="!createModel.publish_all_ports">
<n-form-item path="ports" :label="$gettext('Port Mapping')" v-if="!createModel.publish_all_ports">
<n-space vertical>
<n-table striped>
<thead>
<tr>
<th>IP</th>
<th>主机起始</th>
<th>主机结束</th>
<th>容器起始</th>
<th>容器结束</th>
<th>协议</th>
<th>操作</th>
<th>{{ $gettext('Host (Start)') }}</th>
<th>{{ $gettext('Host (End)') }}</th>
<th>{{ $gettext('Container (Start)') }}</th>
<th>{{ $gettext('Container (End)') }}</th>
<th>{{ $gettext('Protocol') }}</th>
<th>{{ $gettext('Actions') }}</th>
</tr>
</thead>
<tbody>
@@ -174,7 +177,7 @@ onMounted(() => {
v-model:value="item.host"
type="text"
@keydown.enter.prevent
placeholder="可留空"
:placeholder="$gettext('Optional')"
/>
</td>
<td>
@@ -223,25 +226,25 @@ onMounted(() => {
UDP
</n-radio>
</td>
<td><n-button @click="removePortRow(index)" size="small">删除</n-button></td>
<td><n-button @click="removePortRow(index)" size="small">{{ $gettext('Delete') }}</n-button></td>
</tr>
</tbody>
</n-table>
<n-button @click="addPortRow">添加</n-button>
<n-button @click="addPortRow">{{ $gettext('Add') }}</n-button>
</n-space>
</n-form-item>
<n-form-item path="network" label="网络">
<n-form-item path="network" :label="$gettext('Network')">
<n-select v-model:value="createModel.network" :options="networks" />
</n-form-item>
<n-form-item path="mount" label="挂载">
<n-form-item path="mount" :label="$gettext('Mount')">
<n-space vertical>
<n-table striped>
<thead>
<tr>
<th>主机目录</th>
<th>容器目录</th>
<th>权限</th>
<th>操作</th>
<th>{{ $gettext('Host Directory') }}</th>
<th>{{ $gettext('Container Directory') }}</th>
<th>{{ $gettext('Permission') }}</th>
<th>{{ $gettext('Actions') }}</th>
</tr>
</thead>
<tbody>
@@ -259,7 +262,7 @@ onMounted(() => {
name="mode"
@change="item.mode = $event.target.value"
>
读写
{{ $gettext('Read-Write') }}
</n-radio>
<n-radio
:checked="item.mode === 'ro'"
@@ -267,25 +270,25 @@ onMounted(() => {
name="mode"
@change="item.mode = $event.target.value"
>
只读
{{ $gettext('Read-Only') }}
</n-radio>
</td>
<td><n-button @click="removeVolumeRow(index)" size="small">删除</n-button></td>
<td><n-button @click="removeVolumeRow(index)" size="small">{{ $gettext('Delete') }}</n-button></td>
</tr>
</tbody>
</n-table>
<n-button @click="addVolumeRow">添加</n-button>
<n-button @click="addVolumeRow">{{ $gettext('Add') }}</n-button>
</n-space>
</n-form-item>
<n-form-item path="command" label="启动命令">
<n-dynamic-input v-model:value="createModel.command" placeholder="命令" />
<n-form-item path="command" :label="$gettext('Command')">
<n-dynamic-input v-model:value="createModel.command" :placeholder="$gettext('Command')" />
</n-form-item>
<n-form-item path="entrypoint" label="入口点">
<n-dynamic-input v-model:value="createModel.entrypoint" placeholder="入口点" />
<n-form-item path="entrypoint" :label="$gettext('Entrypoint')">
<n-dynamic-input v-model:value="createModel.entrypoint" :placeholder="$gettext('Entrypoint')" />
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="8">
<n-form-item path="memory" label="内存">
<n-form-item path="memory" :label="$gettext('Memory')">
<n-input-number v-model:value="createModel.memory" />
</n-form-item>
</n-col>
@@ -295,61 +298,61 @@ onMounted(() => {
</n-form-item>
</n-col>
<n-col :span="8">
<n-form-item path="cpu_shares" label="CPU 权重">
<n-form-item path="cpu_shares" :label="$gettext('CPU Shares')">
<n-input-number v-model:value="createModel.cpu_shares" />
</n-form-item>
</n-col>
</n-row>
<n-row :gutter="[0, 24]">
<n-col :span="6">
<n-form-item path="tty" label="伪终端(-t">
<n-form-item path="tty" :label="$gettext('TTY (-t)')">
<n-switch v-model:value="createModel.tty" />
</n-form-item>
</n-col>
<n-col :span="6">
<n-form-item path="open_stdin" label="标准输入(-i">
<n-form-item path="open_stdin" :label="$gettext('STDIN (-i)')">
<n-switch v-model:value="createModel.open_stdin" />
</n-form-item>
</n-col>
<n-col :span="6">
<n-form-item path="auto_remove" label="退出后自动删除">
<n-form-item path="auto_remove" :label="$gettext('Auto Remove')">
<n-switch v-model:value="createModel.auto_remove" />
</n-form-item>
</n-col>
<n-col :span="6">
<n-form-item path="privileged" label="特权模式">
<n-form-item path="privileged" :label="$gettext('Privileged Mode')">
<n-switch v-model:value="createModel.privileged" />
</n-form-item>
</n-col>
</n-row>
<n-form-item path="restart_policy" label="重启策略">
<n-form-item path="restart_policy" :label="$gettext('Restart Policy')">
<n-select
v-model:value="createModel.restart_policy"
placeholder="选择重启策略"
:placeholder="$gettext('Select restart policy')"
:options="restartPolicyOptions"
>
{{ createModel.restart_policy || '选择重启策略' }}
{{ createModel.restart_policy || $gettext('Select restart policy') }}
</n-select>
</n-form-item>
<n-form-item path="env" label="环境变量">
<n-form-item path="env" :label="$gettext('Environment Variables')">
<n-dynamic-input
v-model:value="createModel.env"
preset="pair"
key-placeholder="变量名"
value-placeholder="变量值"
:key-placeholder="$gettext('Variable Name')"
:value-placeholder="$gettext('Variable Value')"
/>
</n-form-item>
<n-form-item path="labels" label="标签">
<n-form-item path="labels" :label="$gettext('Labels')">
<n-dynamic-input
v-model:value="createModel.labels"
preset="pair"
key-placeholder="标签名"
value-placeholder="标签值"
:key-placeholder="$gettext('Label Name')"
:value-placeholder="$gettext('Label Value')"
/>
</n-form-item>
</n-form>
<n-button type="info" block :loading="doSubmit" :disabled="doSubmit" @click="handleSubmit">
提交
{{ $gettext('Submit') }}
</n-button>
</n-modal>
</template>

View File

@@ -1,10 +1,13 @@
<script setup lang="ts">
import Editor from '@guolao/vue-monaco-editor'
import { NButton, NDataTable, NDropdown, NFlex, NInput, NSwitch, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import container from '@/api/panel/container'
import ContainerCreate from '@/views/container/ContainerCreate.vue'
const { $gettext } = useGettext()
const logModal = ref(false)
const logs = ref('')
const renameModal = ref(false)
@@ -19,14 +22,14 @@ const selectedRowKeys = ref<any>([])
const columns: any = [
{ type: 'selection', fixed: 'left' },
{
title: '容器名',
title: $gettext('Container Name'),
key: 'name',
minWidth: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '状态',
title: $gettext('Status'),
key: 'state',
width: 100,
resizable: true,
@@ -46,7 +49,7 @@ const columns: any = [
}
},
{
title: '镜像',
title: $gettext('Image'),
key: 'image',
minWidth: 300,
resizable: true,
@@ -57,7 +60,7 @@ const columns: any = [
}
},
{
title: '端口(主机->容器)',
title: $gettext('Ports (Host->Container)'),
key: 'ports',
minWidth: 200,
resizable: true,
@@ -74,14 +77,14 @@ const columns: any = [
}
},
{
title: '运行状态',
title: $gettext('Running Status'),
key: 'status',
width: 300,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 250,
align: 'center',
@@ -97,7 +100,7 @@ const columns: any = [
onClick: () => handleShowLog(row)
},
{
default: () => '日志'
default: () => $gettext('Logs')
}
),
h(
@@ -113,7 +116,7 @@ const columns: any = [
}
},
{
default: () => '重命名'
default: () => $gettext('Rename')
}
),
h(
@@ -121,37 +124,37 @@ const columns: any = [
{
options: [
{
label: '启动',
label: $gettext('Start'),
key: 'start',
disabled: row.state === 'running'
},
{
label: '停止',
label: $gettext('Stop'),
key: 'stop',
disabled: row.state !== 'running'
},
{
label: '重启',
label: $gettext('Restart'),
key: 'restart',
disabled: row.state !== 'running'
},
{
label: '强制停止',
label: $gettext('Force Stop'),
key: 'forceStop',
disabled: row.state !== 'running'
},
{
label: '暂停',
label: $gettext('Pause'),
key: 'pause',
disabled: row.state !== 'running'
},
{
label: '恢复',
label: $gettext('Resume'),
key: 'unpause',
disabled: row.state === 'running'
},
{
label: '删除',
label: $gettext('Delete'),
key: 'delete'
}
],
@@ -191,7 +194,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => '更多'
default: () => $gettext('More')
}
)
}
@@ -224,7 +227,7 @@ const handleRename = () => {
() => {
refresh()
renameModal.value = false
window.$message.success('重命名成功')
window.$message.success($gettext('Rename successful'))
}
)
}
@@ -232,62 +235,62 @@ const handleRename = () => {
const handleStart = (id: string) => {
useRequest(container.containerStart(id)).onSuccess(() => {
refresh()
window.$message.success('启动成功')
window.$message.success($gettext('Start successful'))
})
}
const handleStop = (id: string) => {
useRequest(container.containerStop(id)).onSuccess(() => {
refresh()
window.$message.success('停止成功')
window.$message.success($gettext('Stop successful'))
})
}
const handleRestart = (id: string) => {
useRequest(container.containerRestart(id)).onSuccess(() => {
refresh()
window.$message.success('重启成功')
window.$message.success($gettext('Restart successful'))
})
}
const handleForceStop = (id: string) => {
useRequest(container.containerKill(id)).onSuccess(() => {
refresh()
window.$message.success('强制停止成功')
window.$message.success($gettext('Force stop successful'))
})
}
const handlePause = (id: string) => {
useRequest(container.containerPause(id)).onSuccess(() => {
refresh()
window.$message.success('暂停成功')
window.$message.success($gettext('Pause successful'))
})
}
const handleUnpause = (id: string) => {
useRequest(container.containerUnpause(id)).onSuccess(() => {
refresh()
window.$message.success('恢复成功')
window.$message.success($gettext('Resume successful'))
})
}
const handleDelete = (id: string) => {
useRequest(container.containerRemove(id)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Delete successful'))
})
}
const handlePrune = () => {
useRequest(container.containerPrune()).onSuccess(() => {
refresh()
window.$message.success('清理成功')
window.$message.success($gettext('Cleanup successful'))
})
}
const bulkStart = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要启动的容器')
window.$message.info($gettext('Please select containers to start'))
return
}
@@ -296,12 +299,12 @@ const bulkStart = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('启动成功')
window.$message.success($gettext('Start successful'))
}
const bulkStop = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要停止的容器')
window.$message.info($gettext('Please select containers to stop'))
return
}
@@ -310,12 +313,12 @@ const bulkStop = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('停止成功')
window.$message.success($gettext('Stop successful'))
}
const bulkRestart = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要重启的容器')
window.$message.info($gettext('Please select containers to restart'))
return
}
@@ -324,12 +327,12 @@ const bulkRestart = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('重启成功')
window.$message.success($gettext('Restart successful'))
}
const bulkForceStop = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要强制停止的容器')
window.$message.info($gettext('Please select containers to force stop'))
return
}
@@ -338,12 +341,12 @@ const bulkForceStop = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('强制停止成功')
window.$message.success($gettext('Force stop successful'))
}
const bulkDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要删除的容器')
window.$message.info($gettext('Please select containers to delete'))
return
}
@@ -352,12 +355,12 @@ const bulkDelete = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Delete successful'))
}
const bulkPause = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要暂停的容器')
window.$message.info($gettext('Please select containers to pause'))
return
}
@@ -366,12 +369,12 @@ const bulkPause = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('暂停成功')
window.$message.success($gettext('Pause successful'))
}
const bulkUnpause = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要恢复的容器')
window.$message.info($gettext('Please select containers to resume'))
return
}
@@ -380,7 +383,7 @@ const bulkUnpause = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('恢复成功')
window.$message.success($gettext('Resume successful'))
}
const closeContainerCreateModal = () => {
@@ -396,16 +399,16 @@ onMounted(() => {
<template>
<n-flex vertical :size="20">
<n-flex>
<n-button type="primary" @click="containerCreateModal = true">创建容器</n-button>
<n-button type="primary" @click="handlePrune" ghost>清理容器</n-button>
<n-button type="primary" @click="containerCreateModal = true">{{ $gettext('Create Container') }}</n-button>
<n-button type="primary" @click="handlePrune" ghost>{{ $gettext('Cleanup Containers') }}</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 @click="bulkStart">{{ $gettext('Start') }}</n-button>
<n-button @click="bulkStop">{{ $gettext('Stop') }}</n-button>
<n-button @click="bulkRestart">{{ $gettext('Restart') }}</n-button>
<n-button @click="bulkForceStop">{{ $gettext('Force Stop') }}</n-button>
<n-button @click="bulkPause">{{ $gettext('Pause') }}</n-button>
<n-button @click="bulkUnpause">{{ $gettext('Resume') }}</n-button>
<n-button @click="bulkDelete">{{ $gettext('Delete') }}</n-button>
</n-button-group>
</n-flex>
<n-data-table
@@ -433,7 +436,7 @@ onMounted(() => {
<n-modal
v-model:show="logModal"
preset="card"
title="日志"
:title="$gettext('Logs')"
style="width: 80vw"
size="huge"
:bordered="false"
@@ -456,23 +459,23 @@ onMounted(() => {
<n-modal
v-model:show="renameModal"
preset="card"
title="重命名"
:title="$gettext('Rename')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="renameModel">
<n-form-item path="name" label="新名称">
<n-form-item path="name" :label="$gettext('New Name')">
<n-input
v-model:value="renameModel.name"
type="text"
@keydown.enter.prevent
placeholder="输入新名称"
:placeholder="$gettext('Enter new name')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleRename">提交</n-button>
<n-button type="info" block @click="handleRename">{{ $gettext('Submit') }}</n-button>
</n-modal>
<ContainerCreate :show="containerCreateModal" @close="closeContainerCreateModal" />
</template>

View File

@@ -1,9 +1,12 @@
<script setup lang="ts">
import { NButton, NDataTable, NFlex, NInput, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import container from '@/api/panel/container'
import { formatDateTime } from '@/utils'
const { $gettext } = useGettext()
const pullModel = ref({
name: '',
auth: false,
@@ -23,14 +26,14 @@ const columns: any = [
ellipsis: { tooltip: true }
},
{
title: '容器数',
title: $gettext('Container Count'),
key: 'containers',
width: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '镜像',
title: $gettext('Image'),
key: 'repo_tags',
minWidth: 200,
resizable: true,
@@ -47,14 +50,14 @@ const columns: any = [
}
},
{
title: '大小',
title: $gettext('Size'),
key: 'size',
width: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '创建时间',
title: $gettext('Creation Time'),
key: 'created_at',
width: 200,
resizable: true,
@@ -63,7 +66,7 @@ const columns: any = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 120,
align: 'center',
@@ -79,7 +82,7 @@ const columns: any = [
},
{
default: () => {
return '确定删除吗?'
return $gettext('Are you sure you want to delete?')
},
trigger: () => {
return h(
@@ -89,7 +92,7 @@ const columns: any = [
type: 'error'
},
{
default: () => '删除'
default: () => $gettext('Delete')
}
)
}
@@ -113,14 +116,14 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
const handleDelete = async (row: any) => {
useRequest(container.imageRemove(row.id)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Delete successful'))
})
}
const handlePrune = () => {
useRequest(container.imagePrune()).onSuccess(() => {
refresh()
window.$message.success('清理成功')
window.$message.success($gettext('Cleanup successful'))
})
}
@@ -129,7 +132,7 @@ const handlePull = () => {
useRequest(container.imagePull(pullModel.value))
.onSuccess(() => {
refresh()
window.$message.success('拉取成功')
window.$message.success($gettext('Pull successful'))
})
.onComplete(() => {
loading.value = false
@@ -145,8 +148,8 @@ onMounted(() => {
<template>
<n-flex vertical :size="20">
<n-flex>
<n-button type="primary" @click="pullModal = true">拉取镜像</n-button>
<n-button type="primary" @click="handlePrune" ghost>清理镜像</n-button>
<n-button type="primary" @click="pullModal = true">{{ $gettext('Pull Image') }}</n-button>
<n-button type="primary" @click="handlePrune" ghost>{{ $gettext('Cleanup Images') }}</n-button>
</n-flex>
<n-data-table
striped
@@ -173,44 +176,44 @@ onMounted(() => {
<n-modal
v-model:show="pullModal"
preset="card"
title="拉取镜像"
:title="$gettext('Pull Image')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="pullModel">
<n-form-item path="name" label="镜像名">
<n-form-item path="name" :label="$gettext('Image Name')">
<n-input
v-model:value="pullModel.name"
type="text"
@keydown.enter.prevent
placeholder="docker.io/php:8.3-fpm"
:placeholder="$gettext('docker.io/php:8.3-fpm')"
/>
</n-form-item>
<n-form-item path="auth" label="验证">
<n-form-item path="auth" :label="$gettext('Authentication')">
<n-switch v-model:value="pullModel.auth" />
</n-form-item>
<n-form-item v-if="pullModel.auth" path="username" label="用户名">
<n-form-item v-if="pullModel.auth" path="username" :label="$gettext('Username')">
<n-input
v-model:value="pullModel.username"
type="text"
@keydown.enter.prevent
placeholder="输入用户名"
:placeholder="$gettext('Enter username')"
/>
</n-form-item>
<n-form-item v-if="pullModel.auth" path="password" label="密码">
<n-form-item v-if="pullModel.auth" path="password" :label="$gettext('Password')">
<n-input
v-model:value="pullModel.password"
type="password"
show-password-on="click"
@keydown.enter.prevent
placeholder="输入密码"
:placeholder="$gettext('Enter password')"
/>
</n-form-item>
</n-form>
<n-button type="info" block :loading="loading" :disabled="loading" @click="handlePull">
提交
{{ $gettext('Submit') }}
</n-button>
</n-modal>
</template>

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import ComposeView from '@/views/container/ComposeView.vue'
import { useGettext } from 'vue3-gettext'
defineOptions({
name: 'container-index'
@@ -10,25 +11,26 @@ import ImageView from '@/views/container/ImageView.vue'
import NetworkView from '@/views/container/NetworkView.vue'
import VolumeView from '@/views/container/VolumeView.vue'
const { $gettext } = useGettext()
const current = ref('container')
</script>
<template>
<common-page show-footer>
<n-tabs v-model:value="current" type="line" animated>
<n-tab-pane name="container" tab="容器">
<n-tab-pane name="container" :tab="$gettext('Containers')">
<container-view />
</n-tab-pane>
<n-tab-pane name="compose" tab="编排">
<n-tab-pane name="compose" :tab="$gettext('Compose')">
<compose-view />
</n-tab-pane>
<n-tab-pane name="image" tab="镜像">
<n-tab-pane name="image" :tab="$gettext('Images')">
<image-view />
</n-tab-pane>
<n-tab-pane name="network" tab="网络">
<n-tab-pane name="network" :tab="$gettext('Networks')">
<network-view />
</n-tab-pane>
<n-tab-pane name="volume" tab="">
<n-tab-pane name="volume" :tab="$gettext('Volumes')">
<volume-view />
</n-tab-pane>
</n-tabs>

View File

@@ -1,9 +1,12 @@
<script setup lang="ts">
import { NButton, NDataTable, NFlex, NInput, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import container from '@/api/panel/container'
import { formatDateTime } from '@/utils'
const { $gettext } = useGettext()
const createModel = ref({
name: '',
driver: 'bridge',
@@ -39,28 +42,28 @@ const selectedRowKeys = ref<any>([])
const columns: any = [
{ type: 'selection', fixed: 'left' },
{
title: '名称',
title: $gettext('Name'),
key: 'name',
minWidth: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '驱动',
title: $gettext('Driver'),
key: 'driver',
width: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '范围',
title: $gettext('Scope'),
key: 'scope',
width: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '子网',
title: $gettext('Subnet'),
key: 'subnet',
minWidth: 150,
resizable: true,
@@ -77,7 +80,7 @@ const columns: any = [
}
},
{
title: '网关',
title: $gettext('Gateway'),
key: 'gateway',
width: 150,
resizable: true,
@@ -94,7 +97,7 @@ const columns: any = [
}
},
{
title: '创建时间',
title: $gettext('Creation Time'),
key: 'created_at',
width: 200,
resizable: true,
@@ -103,7 +106,7 @@ const columns: any = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 120,
align: 'center',
@@ -119,7 +122,7 @@ const columns: any = [
},
{
default: () => {
return '确定删除吗?'
return $gettext('Are you sure you want to delete?')
},
trigger: () => {
return h(
@@ -129,7 +132,7 @@ const columns: any = [
type: 'error'
},
{
default: () => '删除'
default: () => $gettext('Delete')
}
)
}
@@ -153,14 +156,14 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
const handleDelete = (row: any) => {
useRequest(container.networkRemove(row.id)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Delete successful'))
})
}
const handlePrune = () => {
useRequest(container.networkPrune()).onSuccess(() => {
refresh()
window.$message.success('清理成功')
window.$message.success($gettext('Cleanup successful'))
})
}
@@ -169,7 +172,7 @@ const handleCreate = () => {
useRequest(container.networkCreate(createModel.value))
.onSuccess(() => {
refresh()
window.$message.success('创建成功')
window.$message.success($gettext('Created successfully'))
})
.onComplete(() => {
loading.value = false
@@ -185,8 +188,8 @@ onMounted(() => {
<template>
<n-flex vertical :size="20">
<n-flex>
<n-button type="primary" @click="createModal = true">创建网络</n-button>
<n-button type="primary" @click="handlePrune" ghost>清理网络</n-button>
<n-button type="primary" @click="createModal = true">{{ $gettext('Create Network') }}</n-button>
<n-button type="primary" @click="handlePrune" ghost>{{ $gettext('Cleanup Networks') }}</n-button>
</n-flex>
<n-data-table
striped
@@ -213,17 +216,17 @@ onMounted(() => {
<n-modal
v-model:show="createModal"
preset="card"
title="创建网络"
:title="$gettext('Create Network')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="createModel">
<n-form-item path="name" label="网络名">
<n-form-item path="name" :label="$gettext('Network Name')">
<n-input v-model:value="createModel.name" type="text" @keydown.enter.prevent />
</n-form-item>
<n-form-item path="driver" label="驱动">
<n-form-item path="driver" :label="$gettext('Driver')">
<n-select
:options="options"
v-model:value="createModel.driver"
@@ -235,76 +238,76 @@ onMounted(() => {
<n-form-item path="ipv4" label="IPV4">
<n-switch v-model:value="createModel.ipv4.enabled" />
</n-form-item>
<n-form-item v-if="createModel.ipv4.enabled" path="subnet" label="子网">
<n-form-item v-if="createModel.ipv4.enabled" path="subnet" :label="$gettext('Subnet')">
<n-input
v-model:value="createModel.ipv4.subnet"
type="text"
@keydown.enter.prevent
placeholder="172.16.10.0/24"
:placeholder="$gettext('172.16.10.0/24')"
/>
</n-form-item>
<n-form-item v-if="createModel.ipv4.enabled" path="gateway" label="网关">
<n-form-item v-if="createModel.ipv4.enabled" path="gateway" :label="$gettext('Gateway')">
<n-input
v-model:value="createModel.ipv4.gateway"
type="text"
@keydown.enter.prevent
placeholder="172.16.10.254"
:placeholder="$gettext('172.16.10.254')"
/>
</n-form-item>
<n-form-item v-if="createModel.ipv4.enabled" path="ip_range" label="IP范围">
<n-form-item v-if="createModel.ipv4.enabled" path="ip_range" :label="$gettext('IP Range')">
<n-input
v-model:value="createModel.ipv4.ip_range"
type="text"
@keydown.enter.prevent
placeholder="172.16.10.0/24"
:placeholder="$gettext('172.16.10.0/24')"
/>
</n-form-item>
<n-form-item path="ipv6" label="IPV6">
<n-switch v-model:value="createModel.ipv6.enabled" />
</n-form-item>
<n-form-item v-if="createModel.ipv6.enabled" path="subnet" label="子网">
<n-form-item v-if="createModel.ipv6.enabled" path="subnet" :label="$gettext('Subnet')">
<n-input
v-model:value="createModel.ipv6.subnet"
type="text"
@keydown.enter.prevent
placeholder="2408:400e::/48"
:placeholder="$gettext('2408:400e::/48')"
/>
</n-form-item>
<n-form-item v-if="createModel.ipv6.enabled" path="gateway" label="网关">
<n-form-item v-if="createModel.ipv6.enabled" path="gateway" :label="$gettext('Gateway')">
<n-input
v-model:value="createModel.ipv6.gateway"
type="text"
@keydown.enter.prevent
placeholder="2408:400e::1"
:placeholder="$gettext('2408:400e::1')"
/>
</n-form-item>
<n-form-item v-if="createModel.ipv6.enabled" path="ip_range" label="IP范围">
<n-form-item v-if="createModel.ipv6.enabled" path="ip_range" :label="$gettext('IP Range')">
<n-input
v-model:value="createModel.ipv6.ip_range"
type="text"
@keydown.enter.prevent
placeholder="2408:400e::/64"
:placeholder="$gettext('2408:400e::/64')"
/>
</n-form-item>
<n-form-item path="env" label="标签">
<n-form-item path="env" :label="$gettext('Labels')">
<n-dynamic-input
v-model:value="createModel.labels"
preset="pair"
key-placeholder="标签名"
value-placeholder="标签值"
:key-placeholder="$gettext('Label Name')"
:value-placeholder="$gettext('Label Value')"
/>
</n-form-item>
<n-form-item path="env" label="选项">
<n-form-item path="env" :label="$gettext('Options')">
<n-dynamic-input
v-model:value="createModel.options"
preset="pair"
key-placeholder="选项名"
value-placeholder="选项值"
:key-placeholder="$gettext('Option Name')"
:value-placeholder="$gettext('Option Value')"
/>
</n-form-item>
</n-form>
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleCreate">
提交
{{ $gettext('Submit') }}
</n-button>
</n-modal>
</template>

View File

@@ -1,9 +1,12 @@
<script setup lang="ts">
import { NButton, NDataTable, NInput, NPopconfirm } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import container from '@/api/panel/container'
import { formatDateTime } from '@/utils'
const { $gettext } = useGettext()
const createModel = ref({
name: '',
driver: 'local',
@@ -20,35 +23,35 @@ const selectedRowKeys = ref<any>([])
const columns: any = [
{ type: 'selection', fixed: 'left' },
{
title: '名称',
title: $gettext('Name'),
key: 'name',
minWidth: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '驱动',
title: $gettext('Driver'),
key: 'driver',
width: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '范围',
title: $gettext('Scope'),
key: 'scope',
width: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '挂载点',
title: $gettext('Mount Point'),
key: 'mount_point',
resizable: true,
minWidth: 150,
ellipsis: { tooltip: true }
},
{
title: '创建时间',
title: $gettext('Creation Time'),
key: 'created_at',
width: 200,
resizable: true,
@@ -57,7 +60,7 @@ const columns: any = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 120,
align: 'center',
@@ -73,7 +76,7 @@ const columns: any = [
},
{
default: () => {
return '确定删除吗?'
return $gettext('Are you sure you want to delete?')
},
trigger: () => {
return h(
@@ -83,7 +86,7 @@ const columns: any = [
type: 'error'
},
{
default: () => '删除'
default: () => $gettext('Delete')
}
)
}
@@ -107,14 +110,14 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
const handleDelete = async (row: any) => {
useRequest(container.volumeRemove(row.id)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Delete successful'))
})
}
const handlePrune = () => {
useRequest(container.volumePrune()).onSuccess(() => {
refresh()
window.$message.success('清理成功')
window.$message.success($gettext('Cleanup successful'))
})
}
@@ -123,7 +126,7 @@ const handleCreate = () => {
useRequest(container.volumeCreate(createModel.value))
.onSuccess(() => {
refresh()
window.$message.success('创建成功')
window.$message.success($gettext('Created successfully'))
})
.onComplete(() => {
loading.value = false
@@ -139,8 +142,8 @@ onMounted(() => {
<template>
<n-flex vertical :size="20">
<n-flex>
<n-button type="primary" @click="createModal = true">创建卷</n-button>
<n-button type="primary" @click="handlePrune" ghost>清理卷</n-button>
<n-button type="primary" @click="createModal = true">{{ $gettext('Create Volume') }}</n-button>
<n-button type="primary" @click="handlePrune" ghost>{{ $gettext('Cleanup Volumes') }}</n-button>
</n-flex>
<n-data-table
striped
@@ -167,17 +170,17 @@ onMounted(() => {
<n-modal
v-model:show="createModal"
preset="card"
title="创建卷"
:title="$gettext('Create Volume')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="createModel">
<n-form-item path="name" label="卷名">
<n-form-item path="name" :label="$gettext('Volume Name')">
<n-input v-model:value="createModel.name" type="text" @keydown.enter.prevent />
</n-form-item>
<n-form-item path="driver" label="驱动">
<n-form-item path="driver" :label="$gettext('Driver')">
<n-select
:options="options"
v-model:value="createModel.driver"
@@ -186,25 +189,25 @@ onMounted(() => {
>
</n-select>
</n-form-item>
<n-form-item path="env" label="标签">
<n-form-item path="env" :label="$gettext('Labels')">
<n-dynamic-input
v-model:value="createModel.labels"
preset="pair"
key-placeholder="标签名"
value-placeholder="标签值"
:key-placeholder="$gettext('Label Name')"
:value-placeholder="$gettext('Label Value')"
/>
</n-form-item>
<n-form-item path="env" label="选项">
<n-form-item path="env" :label="$gettext('Options')">
<n-dynamic-input
v-model:value="createModel.options"
preset="pair"
key-placeholder="选项名"
value-placeholder="选项值"
:key-placeholder="$gettext('Option Name')"
:value-placeholder="$gettext('Option Value')"
/>
</n-form-item>
</n-form>
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleCreate">
提交
{{ $gettext('Submit') }}
</n-button>
</n-modal>
</template>

View File

@@ -1,3 +1,4 @@
import { $gettext } from '@/utils/gettext'
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
@@ -15,7 +16,7 @@ export default {
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'containerIndex.title',
title: $gettext('Container'),
icon: 'mdi:layers-outline',
role: ['admin'],
requireAuth: true