2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 16:10:59 +08:00

feat: 添加前端翻译

This commit is contained in:
2025-04-13 01:45:40 +08:00
parent 81a320cd93
commit 4dc943438e
24 changed files with 3445 additions and 691 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,7 @@ watch(
<n-modal
v-model:show="show"
preset="card"
:title="$gettext('Preview - %{ path }', { path })"
:title="$gettext('Preview - ') + path"
style="width: 60vw"
size="huge"
:bordered="false"

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import firewall from '@/api/panel/firewall'
import { NButton } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const loading = ref(false)
@@ -36,7 +38,7 @@ const handleCreate = () => {
target_ip: '127.0.0.1',
target_port: 80
}
window.$message.success(`创建成功`)
window.$message.success($gettext('Created successfully'))
})
}
</script>
@@ -45,7 +47,7 @@ const handleCreate = () => {
<n-modal
v-model:show="show"
preset="card"
title="创建转发"
:title="$gettext('Create Forwarding')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -53,15 +55,15 @@ const handleCreate = () => {
@close="show = false"
>
<n-form :model="createModel">
<n-form-item path="protocols" label="传输协议">
<n-form-item path="protocols" :label="$gettext('Transport Protocol')">
<n-select v-model:value="createModel.protocol" :options="protocols" />
</n-form-item>
<n-form-item path="address" label="目标 IP">
<n-form-item path="address" :label="$gettext('Target IP')">
<n-input v-model:value="createModel.target_ip" placeholder="127.0.0.1" />
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="12">
<n-form-item path="address" label="源端口">
<n-form-item path="address" :label="$gettext('Source Port')">
<n-input-number
v-model:value="createModel.port"
:min="1"
@@ -71,7 +73,7 @@ const handleCreate = () => {
</n-form-item>
</n-col>
<n-col :span="12">
<n-form-item path="address" label="目标端口">
<n-form-item path="address" :label="$gettext('Target Port')">
<n-input-number
v-model:value="createModel.target_port"
:min="1"
@@ -83,7 +85,7 @@ const handleCreate = () => {
</n-row>
</n-form>
<n-button type="info" block :loading="loading" :disabled="loading" @click="handleCreate">
提交
{{ $gettext('Submit') }}
</n-button>
</n-modal>
</template>

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import firewall from '@/api/panel/firewall'
import { NButton } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const loading = ref(false)
@@ -33,26 +35,26 @@ const families = [
const strategies = [
{
label: '接受',
label: $gettext('Accept'),
value: 'accept'
},
{
label: '丢弃',
label: $gettext('Drop'),
value: 'drop'
},
{
label: '拒绝',
label: $gettext('Reject'),
value: 'reject'
}
]
const directions = [
{
label: '传入',
label: $gettext('Inbound'),
value: 'in'
},
{
label: '传出',
label: $gettext('Outbound'),
value: 'out'
}
]
@@ -73,7 +75,7 @@ const handleCreate = async () => {
address
})
).onSuccess(() => {
window.$message.success(`${address} 创建成功`)
window.$message.success($gettext('%{ address } created successfully', { address: address }))
show.value = false
})
}
@@ -84,7 +86,7 @@ const handleCreate = async () => {
<n-modal
v-model:show="show"
preset="card"
title="创建规则"
:title="$gettext('Create Rule')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -92,28 +94,32 @@ const handleCreate = async () => {
@close="show = false"
>
<n-form :model="createModel">
<n-form-item path="protocols" label="传输协议">
<n-form-item path="protocols" :label="$gettext('Transport Protocol')">
<n-select v-model:value="createModel.protocol" :options="protocols" />
</n-form-item>
<n-form-item path="family" label="网络协议">
<n-form-item path="family" :label="$gettext('Network Protocol')">
<n-select v-model:value="createModel.family" :options="families" />
</n-form-item>
<n-form-item path="address" label="IP 地址">
<n-form-item path="address" :label="$gettext('IP Address')">
<n-dynamic-input
v-model:value="createModel.address"
show-sort-button
placeholder="可选输入 IP 或 IP 段127.0.0.1 或 172.16.0.0/24多个以英文逗号隔开"
:placeholder="
$gettext(
'Optional IP or IP range: 127.0.0.1 or 172.16.0.0/24 (multiple separated by commas)'
)
"
/>
</n-form-item>
<n-form-item path="strategy" label="策略">
<n-form-item path="strategy" :label="$gettext('Strategy')">
<n-select v-model:value="createModel.strategy" :options="strategies" />
</n-form-item>
<n-form-item path="strategy" label="方向">
<n-form-item path="strategy" :label="$gettext('Direction')">
<n-select v-model:value="createModel.direction" :options="directions" />
</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,7 +1,9 @@
<script setup lang="ts">
import firewall from '@/api/panel/firewall'
import { NButton } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const loading = ref(false)
@@ -33,26 +35,26 @@ const families = [
const strategies = [
{
label: '接受',
label: $gettext('Accept'),
value: 'accept'
},
{
label: '丢弃',
label: $gettext('Drop'),
value: 'drop'
},
{
label: '拒绝',
label: $gettext('Reject'),
value: 'reject'
}
]
const directions = [
{
label: '传入',
label: $gettext('Inbound'),
value: 'in'
},
{
label: '传出',
label: $gettext('Outbound'),
value: 'out'
}
]
@@ -79,7 +81,7 @@ const handleCreate = async () => {
strategy: 'accept',
direction: 'in'
}
window.$message.success('创建成功')
window.$message.success($gettext('Created successfully'))
})
}
</script>
@@ -88,7 +90,7 @@ const handleCreate = async () => {
<n-modal
v-model:show="show"
preset="card"
title="创建规则"
:title="$gettext('Create Rule')"
style="width: 60vw"
size="huge"
:bordered="false"
@@ -96,15 +98,15 @@ const handleCreate = async () => {
@close="show = false"
>
<n-form :model="createModel">
<n-form-item path="protocols" label="传输协议">
<n-form-item path="protocols" :label="$gettext('Transport Protocol')">
<n-select v-model:value="createModel.protocol" :options="protocols" />
</n-form-item>
<n-form-item path="family" label="网络协议">
<n-form-item path="family" :label="$gettext('Network Protocol')">
<n-select v-model:value="createModel.family" :options="families" />
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="12">
<n-form-item path="port_start" label="起始端口">
<n-form-item path="port_start" :label="$gettext('Start Port')">
<n-input-number
v-model:value="createModel.port_start"
:min="1"
@@ -114,7 +116,7 @@ const handleCreate = async () => {
</n-form-item>
</n-col>
<n-col :span="12">
<n-form-item path="port_end" label="结束端口">
<n-form-item path="port_end" :label="$gettext('End Port')">
<n-input-number
v-model:value="createModel.port_end"
:min="1"
@@ -124,21 +126,21 @@ const handleCreate = async () => {
</n-form-item>
</n-col>
</n-row>
<n-form-item path="address" label="目标">
<n-form-item path="address" :label="$gettext('Target')">
<n-input
v-model:value="createModel.address"
placeholder="可选输入 IP IP 段:127.0.0.1 172.16.0.0/24(多个以英文逗号隔开)"
:placeholder="$gettext('Optional IP or IP range: 127.0.0.1 or 172.16.0.0/24 (multiple separated by commas)')"
/>
</n-form-item>
<n-form-item path="strategy" label="策略">
<n-form-item path="strategy" :label="$gettext('Strategy')">
<n-select v-model:value="createModel.strategy" :options="strategies" />
</n-form-item>
<n-form-item path="strategy" label="方向">
<n-form-item path="strategy" :label="$gettext('Direction')">
<n-select v-model:value="createModel.direction" :options="directions" />
</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,16 +1,18 @@
<script setup lang="ts">
import { NButton, NDataTable, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import firewall from '@/api/panel/firewall'
import { renderIcon } from '@/utils'
import CreateForwardModal from '@/views/firewall/CreateForwardModal.vue'
const { $gettext } = useGettext()
const createModalShow = ref(false)
const columns: any = [
{ type: 'selection', fixed: 'left' },
{
title: '传输协议',
title: $gettext('Transport Protocol'),
key: 'protocol',
width: 150,
resizable: true,
@@ -21,13 +23,13 @@ const columns: any = [
if (row.protocol !== '') {
return row.protocol
}
return '无'
return $gettext('None')
}
})
}
},
{
title: '端口',
title: $gettext('Port'),
key: 'port',
width: 150,
render(row: any): any {
@@ -39,7 +41,7 @@ const columns: any = [
}
},
{
title: '目标 IP',
title: $gettext('Target IP'),
key: 'target_ip',
minWidth: 200,
render(row: any): any {
@@ -57,7 +59,7 @@ const columns: any = [
}
},
{
title: '目标端口',
title: $gettext('Target Port'),
key: 'target_port',
width: 150,
render(row: any): any {
@@ -75,7 +77,7 @@ const columns: any = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 200,
align: 'center',
@@ -89,7 +91,7 @@ const columns: any = [
},
{
default: () => {
return '确定要删除吗?'
return $gettext('Are you sure you want to delete?')
},
trigger: () => {
return h(
@@ -100,7 +102,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => '删除',
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
@@ -127,13 +129,13 @@ const selectedRowKeys = ref<any>([])
const handleDelete = (row: any) => {
useRequest(firewall.deleteForward(row)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
})
}
const batchDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要删除的规则')
window.$message.info($gettext('Please select rules to delete'))
return
}
@@ -145,7 +147,7 @@ const batchDelete = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
}
watch(createModalShow, () => {
@@ -162,16 +164,16 @@ onMounted(() => {
<n-flex items-center>
<n-button type="primary" @click="createModalShow = true">
<TheIcon :size="18" icon="material-symbols:add" />
创建转发
{{ $gettext('Create Forwarding') }}
</n-button>
<n-popconfirm @positive-click="batchDelete">
<template #trigger>
<n-button type="warning">
<TheIcon :size="18" icon="material-symbols:delete-outline" />
批量删除
{{ $gettext('Batch Delete') }}
</n-button>
</template>
确定要批量删除吗
{{ $gettext('Are you sure you want to batch delete?') }}
</n-popconfirm>
</n-flex>
<n-data-table

View File

@@ -7,23 +7,25 @@ import ForwardView from '@/views/firewall/ForwardView.vue'
import IpRuleView from '@/views/firewall/IpRuleView.vue'
import RuleView from '@/views/firewall/RuleView.vue'
import SettingView from '@/views/firewall/SettingView.vue'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const currentTab = ref('rule')
</script>
<template>
<common-page show-footer>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="rule" tab="端口规则">
<n-tab-pane name="rule" :tab="$gettext('Port Rules')">
<rule-view />
</n-tab-pane>
<n-tab-pane name="ip-rule" tab="IP 规则">
<n-tab-pane name="ip-rule" :tab="$gettext('IP Rules')">
<ip-rule-view />
</n-tab-pane>
<n-tab-pane name="forward" tab="端口转发">
<n-tab-pane name="forward" :tab="$gettext('Port Forwarding')">
<forward-view />
</n-tab-pane>
<n-tab-pane name="setting" tab="设置">
<n-tab-pane name="setting" :tab="$gettext('Settings')">
<setting-view />
</n-tab-pane>
</n-tabs>

View File

@@ -1,16 +1,18 @@
<script setup lang="ts">
import { NButton, NDataTable, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import firewall from '@/api/panel/firewall'
import { renderIcon } from '@/utils'
import CreateIpModal from '@/views/firewall/CreateIpModal.vue'
const { $gettext } = useGettext()
const createModalShow = ref(false)
const columns: any = [
{ type: 'selection', fixed: 'left' },
{
title: '传输协议',
title: $gettext('Transport Protocol'),
key: 'protocol',
width: 150,
resizable: true,
@@ -21,13 +23,13 @@ const columns: any = [
if (row.protocol !== '') {
return row.protocol
}
return '无'
return $gettext('None')
}
})
}
},
{
title: '网络协议',
title: $gettext('Network Protocol'),
key: 'family',
width: 150,
resizable: true,
@@ -38,13 +40,13 @@ const columns: any = [
if (row.family !== '') {
return row.family
}
return '无'
return $gettext('None')
}
})
}
},
{
title: '策略',
title: $gettext('Strategy'),
key: 'strategy',
width: 150,
render(row: any): any {
@@ -64,15 +66,15 @@ const columns: any = [
default: () => {
switch (row.strategy) {
case 'accept':
return '接受'
return $gettext('Accept')
case 'drop':
return '丢弃'
return $gettext('Drop')
case 'reject':
return '拒绝'
return $gettext('Reject')
case 'mark':
return '标记'
return $gettext('Mark')
default:
return '未知'
return $gettext('Unknown')
}
}
}
@@ -80,7 +82,7 @@ const columns: any = [
}
},
{
title: '方向',
title: $gettext('Direction'),
key: 'direction',
width: 150,
render(row: any): any {
@@ -93,11 +95,11 @@ const columns: any = [
default: () => {
switch (row.direction) {
case 'in':
return '传入'
return $gettext('Inbound')
case 'out':
return '传出'
return $gettext('Outbound')
default:
return '未知'
return $gettext('Unknown')
}
}
}
@@ -105,7 +107,7 @@ const columns: any = [
}
},
{
title: '目标',
title: $gettext('Target'),
key: 'address',
minWidth: 200,
render(row: any): any {
@@ -117,7 +119,7 @@ const columns: any = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 200,
align: 'center',
@@ -131,7 +133,7 @@ const columns: any = [
},
{
default: () => {
return '确定要删除吗?'
return $gettext('Are you sure you want to delete?')
},
trigger: () => {
return h(
@@ -142,7 +144,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => '删除',
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
@@ -169,13 +171,13 @@ const selectedRowKeys = ref<any>([])
const handleDelete = (row: any) => {
useRequest(firewall.deleteIpRule(row)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
})
}
const batchDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要删除的规则')
window.$message.info($gettext('Please select rules to delete'))
return
}
@@ -187,7 +189,7 @@ const batchDelete = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
}
watch(createModalShow, () => {
@@ -204,16 +206,16 @@ onMounted(() => {
<n-flex items-center>
<n-button type="primary" @click="createModalShow = true">
<TheIcon :size="18" icon="material-symbols:add" />
创建规则
{{ $gettext('Create Rule') }}
</n-button>
<n-popconfirm @positive-click="batchDelete">
<template #trigger>
<n-button type="warning">
<TheIcon :size="18" icon="material-symbols:delete-outline" />
批量删除
{{ $gettext('Batch Delete') }}
</n-button>
</template>
确定要批量删除吗
{{ $gettext('Are you sure you want to batch delete?') }}
</n-popconfirm>
</n-flex>
<n-data-table

View File

@@ -1,16 +1,18 @@
<script setup lang="ts">
import { NButton, NDataTable, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import firewall from '@/api/panel/firewall'
import { renderIcon } from '@/utils'
import CreateModal from '@/views/firewall/CreateModal.vue'
const { $gettext } = useGettext()
const createModalShow = ref(false)
const columns: any = [
{ type: 'selection', fixed: 'left' },
{
title: '传输协议',
title: $gettext('Transport Protocol'),
key: 'protocol',
width: 150,
resizable: true,
@@ -21,13 +23,13 @@ const columns: any = [
if (row.protocol !== '') {
return row.protocol
}
return '无'
return $gettext('None')
}
})
}
},
{
title: '网络协议',
title: $gettext('Network Protocol'),
key: 'family',
width: 150,
resizable: true,
@@ -38,13 +40,13 @@ const columns: any = [
if (row.family !== '') {
return row.family
}
return '无'
return $gettext('None')
}
})
}
},
{
title: '端口',
title: $gettext('Port'),
key: 'port',
width: 250,
resizable: true,
@@ -57,7 +59,7 @@ const columns: any = [
}
},
{
title: '状态',
title: $gettext('Status'),
key: 'in_use',
width: 150,
render(row: any): any {
@@ -69,16 +71,16 @@ const columns: any = [
{
default: () => {
if (row.in_use) {
return '使用中'
return $gettext('In Use')
}
return '未使用'
return $gettext('Not Used')
}
}
)
}
},
{
title: '策略',
title: $gettext('Strategy'),
key: 'strategy',
width: 150,
render(row: any): any {
@@ -98,15 +100,15 @@ const columns: any = [
default: () => {
switch (row.strategy) {
case 'accept':
return '接受'
return $gettext('Accept')
case 'drop':
return '丢弃'
return $gettext('Drop')
case 'reject':
return '拒绝'
return $gettext('Reject')
case 'mark':
return '标记'
return $gettext('Mark')
default:
return '未知'
return $gettext('Unknown')
}
}
}
@@ -114,7 +116,7 @@ const columns: any = [
}
},
{
title: '方向',
title: $gettext('Direction'),
key: 'direction',
width: 150,
render(row: any): any {
@@ -127,11 +129,11 @@ const columns: any = [
default: () => {
switch (row.direction) {
case 'in':
return '传入'
return $gettext('Inbound')
case 'out':
return '传出'
return $gettext('Outbound')
default:
return '未知'
return $gettext('Unknown')
}
}
}
@@ -139,14 +141,14 @@ const columns: any = [
}
},
{
title: '目标',
title: $gettext('Target'),
key: 'address',
minWidth: 200,
render(row: any): any {
return h(NTag, null, {
default: () => {
if (row.address === '') {
return '所有'
return $gettext('All')
}
return row.address
}
@@ -154,7 +156,7 @@ const columns: any = [
}
},
{
title: '操作',
title: $gettext('Actions'),
key: 'actions',
width: 200,
align: 'center',
@@ -168,7 +170,7 @@ const columns: any = [
},
{
default: () => {
return '确定要删除吗?'
return $gettext('Are you sure you want to delete?')
},
trigger: () => {
return h(
@@ -179,7 +181,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => '删除',
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
@@ -206,13 +208,13 @@ const selectedRowKeys = ref<any>([])
const handleDelete = async (row: any) => {
useRequest(firewall.deleteRule(row)).onSuccess(() => {
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
})
}
const batchDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要删除的规则')
window.$message.info($gettext('Please select rules to delete'))
return
}
@@ -224,7 +226,7 @@ const batchDelete = async () => {
selectedRowKeys.value = []
refresh()
window.$message.success('删除成功')
window.$message.success($gettext('Deleted successfully'))
}
watch(createModalShow, () => {
@@ -241,16 +243,16 @@ onMounted(() => {
<n-flex items-center>
<n-button type="primary" @click="createModalShow = true">
<TheIcon :size="18" icon="material-symbols:add" />
创建规则
{{ $gettext('Create Rule') }}
</n-button>
<n-popconfirm @positive-click="batchDelete">
<template #trigger>
<n-button type="warning">
<TheIcon :size="18" icon="material-symbols:delete-outline" />
批量删除
{{ $gettext('Batch Delete') }}
</n-button>
</template>
确定要批量删除吗
{{ $gettext('Are you sure you want to batch delete?') }}
</n-popconfirm>
</n-flex>
<n-data-table

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import firewall from '@/api/panel/firewall'
import safe from '@/api/panel/safe'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const model = ref({
firewallStatus: false,
sshStatus: false,
@@ -22,35 +24,35 @@ useRequest(safe.pingStatus).onSuccess(({ data }) => {
const handleFirewallStatus = () => {
useRequest(firewall.updateStatus(model.value.firewallStatus)).onSuccess(() => {
window.$message.success('设置成功')
window.$message.success($gettext('Settings saved successfully'))
})
}
const handleSsh = () => {
useRequest(safe.updateSsh(model.value.sshStatus, model.value.sshPort)).onSuccess(() => {
window.$message.success('设置成功')
window.$message.success($gettext('Settings saved successfully'))
})
}
const handlePingStatus = () => {
useRequest(safe.updatePingStatus(model.value.pingStatus)).onSuccess(() => {
window.$message.success('设置成功')
window.$message.success($gettext('Settings saved successfully'))
})
}
</script>
<template>
<n-form :model="model" label-placement="left" label-width="auto">
<n-form-item path="firewall" label="系统防火墙">
<n-form-item path="firewall" :label="$gettext('System Firewall')">
<n-switch v-model:value="model.firewallStatus" @update:value="handleFirewallStatus" />
</n-form-item>
<n-form-item path="ssh" label="SSH 开关">
<n-form-item path="ssh" :label="$gettext('SSH Switch')">
<n-switch v-model:value="model.sshStatus" @update:value="handleSsh" />
</n-form-item>
<n-form-item path="ping" label="允许 Ping">
<n-form-item path="ping" :label="$gettext('Allow Ping')">
<n-switch v-model:value="model.pingStatus" @update:value="handlePingStatus" />
</n-form-item>
<n-form-item path="sshPort" label="SSH 端口">
<n-form-item path="sshPort" :label="$gettext('SSH Port')">
<n-input-number v-model:value="model.sshPort" @blur="handleSsh" />
</n-form-item>
</n-form>

View File

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

View File

@@ -15,9 +15,12 @@ import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { NButton } from 'naive-ui'
import VChart from 'vue-echarts'
import { useGettext } from 'vue3-gettext'
import monitor from '@/api/panel/monitor'
const { $gettext } = useGettext()
use([
CanvasRenderer,
LineChart,
@@ -54,7 +57,7 @@ const saveDay = ref(30)
const load = ref<any>({
title: {
text: '负载',
text: $gettext('Load'),
textAlign: 'left',
textStyle: {
fontSize: 20
@@ -65,7 +68,7 @@ const load = ref<any>({
},
legend: {
align: 'left',
data: ['1分钟', '5分钟', '15分钟']
data: [$gettext('1 minute'), $gettext('5 minutes'), $gettext('15 minutes')]
},
xAxis: [{ type: 'category', boundaryGap: false, data: data.value.times }],
yAxis: [
@@ -81,22 +84,22 @@ const load = ref<any>({
},
series: [
{
name: '1分钟',
name: $gettext('1 minute'),
type: 'line',
smooth: true,
data: data.value.load.load1,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
},
{
name: '5分钟',
name: $gettext('5 minutes'),
type: 'line',
smooth: true,
emphasis: {
@@ -109,16 +112,16 @@ const load = ref<any>({
data: data.value.load.load5,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
},
{
name: '15分钟',
name: $gettext('15 minutes'),
type: 'line',
smooth: true,
emphasis: {
@@ -131,12 +134,12 @@ const load = ref<any>({
data: data.value.load.load15,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
}
]
@@ -156,7 +159,7 @@ const cpu = ref<any>({
xAxis: [{ type: 'category', boundaryGap: false, data: data.value.times }],
yAxis: [
{
name: '单位 %',
name: $gettext('Unit %'),
min: 0,
max: 100,
type: 'value',
@@ -173,7 +176,7 @@ const cpu = ref<any>({
},
series: [
{
name: '使用率',
name: $gettext('Usage'),
type: 'line',
smooth: true,
emphasis: {
@@ -186,12 +189,12 @@ const cpu = ref<any>({
data: data.value.cpu.percent,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
}
]
@@ -199,7 +202,7 @@ const cpu = ref<any>({
const mem = ref<any>({
title: {
text: '内存',
text: $gettext('Memory'),
textAlign: 'left',
textStyle: {
fontSize: 20
@@ -210,12 +213,12 @@ const mem = ref<any>({
},
legend: {
align: 'left',
data: ['内存', 'Swap']
data: [$gettext('Memory'), 'Swap']
},
xAxis: [{ type: 'category', boundaryGap: false, data: data.value.times }],
yAxis: [
{
name: '单位 MB',
name: $gettext('Unit MB'),
min: 0,
max: data.value.mem.total,
type: 'value',
@@ -232,7 +235,7 @@ const mem = ref<any>({
},
series: [
{
name: '内存',
name: $gettext('Memory'),
type: 'line',
smooth: true,
emphasis: {
@@ -245,12 +248,12 @@ const mem = ref<any>({
data: data.value.mem.used,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
},
{
@@ -267,12 +270,12 @@ const mem = ref<any>({
data: data.value.swap.used,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
}
]
@@ -280,7 +283,7 @@ const mem = ref<any>({
const net = ref<any>({
title: {
text: '网络',
text: $gettext('Network'),
textAlign: 'left',
textStyle: {
fontSize: 20
@@ -291,12 +294,12 @@ const net = ref<any>({
},
legend: {
align: 'left',
data: ['总计出', '总计入', '每秒出', '每秒入']
data: [$gettext('Total Out'), $gettext('Total In'), $gettext('Per Second Out'), $gettext('Per Second In')]
},
xAxis: [{ type: 'category', boundaryGap: false, data: data.value.times }],
yAxis: [
{
name: '单位 MB',
name: $gettext('Unit MB'),
type: 'value',
axisLabel: {
formatter: '{value} MB'
@@ -311,7 +314,7 @@ const net = ref<any>({
},
series: [
{
name: '总计出',
name: $gettext('Total Out'),
type: 'line',
smooth: true,
emphasis: {
@@ -324,16 +327,16 @@ const net = ref<any>({
data: data.value.net.sent,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
},
{
name: '总计入',
name: $gettext('Total In'),
type: 'line',
smooth: true,
emphasis: {
@@ -346,16 +349,16 @@ const net = ref<any>({
data: data.value.net.recv,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
},
{
name: '每秒出',
name: $gettext('Per Second Out'),
type: 'line',
smooth: true,
emphasis: {
@@ -368,16 +371,16 @@ const net = ref<any>({
data: data.value.net.tx,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
},
{
name: '每秒入',
name: $gettext('Per Second In'),
type: 'line',
smooth: true,
emphasis: {
@@ -390,12 +393,12 @@ const net = ref<any>({
data: data.value.net.rx,
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
{ type: 'max', name: $gettext('Maximum') },
{ type: 'min', name: $gettext('Minimum') }
]
},
markLine: {
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: $gettext('Average') }]
}
}
]
@@ -403,13 +406,13 @@ const net = ref<any>({
const handleUpdate = async () => {
useRequest(monitor.updateSetting(monitorSwitch.value, saveDay.value)).onSuccess(() => {
window.$message.success('操作成功')
window.$message.success($gettext('Operation successful'))
})
}
const handleClear = async () => {
useRequest(monitor.clear()).onSuccess(() => {
window.$message.success('操作成功')
window.$message.success($gettext('Operation successful'))
})
}
@@ -440,10 +443,10 @@ watch(data, () => {
<template #trigger>
<n-button type="error">
<TheIcon :size="18" icon="material-symbols:delete-outline" />
清除监控记录
{{ $gettext('Clear Monitoring Records') }}
</n-button>
</template>
确定要清空吗
{{ $gettext('Are you sure you want to clear?') }}
</n-popconfirm>
</template>
<n-card :segmented="true" flex items-center>
@@ -454,18 +457,18 @@ watch(data, () => {
require-mark-placement="right-hanging"
>
<n-flex items-center>
<n-form-item label="开启监控">
<n-form-item :label="$gettext('Enable Monitoring')">
<n-switch v-model:value="monitorSwitch" @update-value="handleUpdate" />
</n-form-item>
<n-form-item label="保存天数">
<n-form-item :label="$gettext('Save Days')">
<n-input-number v-model:value="saveDay">
<template #suffix> </template>
<template #suffix> {{ $gettext('days') }} </template>
</n-input-number>
</n-form-item>
<n-form-item>
<n-button type="primary" @click="handleUpdate">确定</n-button>
<n-button type="primary" @click="handleUpdate">{{ $gettext('Confirm') }}</n-button>
</n-form-item>
<n-form-item label="时间选择">
<n-form-item :label="$gettext('Time Selection')">
<n-date-picker v-model:value="start" type="datetime" />
-
<n-date-picker v-model:value="end" type="datetime" />

View File

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

View File

@@ -25,8 +25,9 @@ const { data: model } = useRequest(setting.list, {
})
const locales = [
{ label: 'English', value: 'en' },
{ label: '简体中文', value: 'zh_CN' },
{ label: 'English', value: 'en' }
{ label: '繁體中文', value: 'zh_TW' }
]
const handleSave = () => {
@@ -48,47 +49,33 @@ const maybeHardReload = () => {
<template>
<n-space vertical>
<n-alert type="info">
{{ $gettext('Modifying panel port/entrance requires corresponding changes in the browser address bar to access the panel!') }}
{{
$gettext(
'Modifying panel port/entrance requires corresponding changes in the browser address bar to access the panel!'
)
}}
</n-alert>
<n-form>
<n-form-item :label="$gettext('Panel Name')">
<n-input
v-model:value="model.name"
:placeholder="$gettext('Panel Name')"
/>
<n-input v-model:value="model.name" :placeholder="$gettext('Panel Name')" />
</n-form-item>
<n-form-item v-show="false" :label="$gettext('Language')">
<n-select v-model:value="model.locale" :options="locales"> </n-select>
</n-form-item>
<n-form-item :label="$gettext('Username')">
<n-input
v-model:value="model.username"
:placeholder="$gettext('admin')"
/>
<n-input v-model:value="model.username" :placeholder="$gettext('admin')" />
</n-form-item>
<n-form-item :label="$gettext('Password')">
<n-input
v-model:value="model.password"
:placeholder="$gettext('admin')"
/>
<n-input v-model:value="model.password" :placeholder="$gettext('admin')" />
</n-form-item>
<n-form-item :label="$gettext('Certificate Default Email')">
<n-input
v-model:value="model.email"
:placeholder="$gettext('admin@example.com')"
/>
<n-input v-model:value="model.email" :placeholder="$gettext('admin@example.com')" />
</n-form-item>
<n-form-item :label="$gettext('Port')">
<n-input-number
v-model:value="model.port"
:placeholder="$gettext('8888')"
/>
<n-input-number v-model:value="model.port" :placeholder="$gettext('8888')" />
</n-form-item>
<n-form-item :label="$gettext('Security Entrance')">
<n-input
v-model:value="model.entrance"
:placeholder="$gettext('admin')"
/>
<n-input v-model:value="model.entrance" :placeholder="$gettext('admin')" />
</n-form-item>
<n-form-item :label="$gettext('Offline Mode')">
<n-switch v-model:value="model.offline_mode" />
@@ -97,16 +84,10 @@ const maybeHardReload = () => {
<n-switch v-model:value="model.auto_update" />
</n-form-item>
<n-form-item :label="$gettext('Default Website Directory')">
<n-input
v-model:value="model.website_path"
:placeholder="$gettext('/www/wwwroot')"
/>
<n-input v-model:value="model.website_path" :placeholder="$gettext('/www/wwwroot')" />
</n-form-item>
<n-form-item :label="$gettext('Default Backup Directory')">
<n-input
v-model:value="model.backup_path"
:placeholder="$gettext('/www/backup')"
/>
<n-input v-model:value="model.backup_path" :placeholder="$gettext('/www/backup')" />
</n-form-item>
</n-form>
</n-space>

View File

@@ -31,7 +31,7 @@ const handleSave = () => {
<template>
<n-space vertical>
<n-alert type="warning"> 错误的证书可能导致面板无法访问请谨慎操作</n-alert>
<n-alert type="warning"> {{ $gettext('Incorrect certificates may cause the panel to be inaccessible. Please proceed with caution!') }}</n-alert>
<n-form>
<n-form-item :label="$gettext('Panel HTTPS')">
<n-switch v-model:value="model.https" />

View File

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

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import ssh from '@/api/panel/ssh'
import { NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const loading = ref(false)
@@ -33,7 +35,7 @@ const handleSubmit = () => {
remark: ''
}
window.$bus.emit('ssh:refresh')
window.$message.success('创建成功')
window.$message.success($gettext('Created successfully'))
})
.onComplete(() => {
loading.value = false
@@ -45,55 +47,55 @@ const handleSubmit = () => {
<n-modal
v-model:show="show"
preset="card"
title="创建主机"
:title="$gettext('Create Host')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form>
<n-form-item label="名称">
<n-form-item :label="$gettext('Name')">
<n-input v-model:value="model.name" placeholder="127.0.0.1" />
</n-form-item>
<n-row :gutter="[0, 24]" pt-20>
<n-col :span="15">
<n-form-item label="主机">
<n-form-item :label="$gettext('Host')">
<n-input v-model:value="model.host" placeholder="127.0.0.1" />
</n-form-item>
</n-col>
<n-col :span="2"> </n-col>
<n-col :span="7">
<n-form-item label="端口">
<n-form-item :label="$gettext('Port')">
<n-input-number v-model:value="model.port" :min="1" :max="65535" />
</n-form-item>
</n-col>
</n-row>
<n-form-item label="认证方式">
<n-form-item :label="$gettext('Authentication Method')">
<n-select
v-model:value="model.auth_method"
:options="[
{ label: '密码', value: 'password' },
{ label: '私钥', value: 'publickey' }
{ label: $gettext('Password'), value: 'password' },
{ label: $gettext('Private Key'), value: 'publickey' }
]"
>
</n-select>
</n-form-item>
<n-form-item v-if="model.auth_method == 'password'" label="用户名">
<n-form-item v-if="model.auth_method == 'password'" :label="$gettext('Username')">
<n-input v-model:value="model.user" placeholder="root" />
</n-form-item>
<n-form-item v-if="model.auth_method == 'password'" label="密码">
<n-form-item v-if="model.auth_method == 'password'" :label="$gettext('Password')">
<n-input v-model:value="model.password" type="password" show-password-on="click" />
</n-form-item>
<n-form-item v-if="model.auth_method == 'publickey'" label="私钥">
<n-form-item v-if="model.auth_method == 'publickey'" :label="$gettext('Private Key')">
<n-input v-model:value="model.key" type="textarea" />
</n-form-item>
<n-form-item label="备注">
<n-form-item :label="$gettext('Remarks')">
<n-input v-model:value="model.remark" type="textarea" />
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]" pt-20>
<n-col :span="24">
<n-button type="info" block :loading="loading" @click="handleSubmit"> 提交 </n-button>
<n-button type="info" block :loading="loading" @click="handleSubmit"> {{ $gettext('Submit') }} </n-button>
</n-col>
</n-row>
</n-modal>

View File

@@ -19,7 +19,9 @@ import { WebglAddon } from '@xterm/addon-webgl'
import { Terminal } from '@xterm/xterm'
import '@xterm/xterm/css/xterm.css'
import { NButton, NFlex, NPopconfirm } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const terminal = ref<HTMLElement | null>(null)
const term = ref()
let sshWs: WebSocket | null = null
@@ -38,7 +40,7 @@ const fetchData = async () => {
list.value = []
const data = await ssh.list(1, 10000)
if (data.items.length === 0) {
window.$message.info('请先创建主机')
window.$message.info($gettext('Please create a host first'))
return
}
data.items.forEach((item: any) => {
@@ -66,7 +68,7 @@ const fetchData = async () => {
},
{
default: () => {
return '编辑'
return $gettext('Edit')
}
}
),
@@ -77,7 +79,7 @@ const fetchData = async () => {
},
{
default: () => {
return '确定删除主机吗?'
return $gettext('Are you sure you want to delete this host?')
},
trigger: () => {
return h(
@@ -88,7 +90,7 @@ const fetchData = async () => {
},
{
default: () => {
return '删除'
return $gettext('Delete')
}
}
)
@@ -157,14 +159,12 @@ const openSession = async (id: number) => {
current.value = id
ws.onclose = () => {
term.value.write('\r\n连接已关闭,请刷新重试。')
term.value.write('\r\nConnection closed. Please refresh.')
term.value.write('\r\n' + $gettext('Connection closed. Please refresh.'))
window.removeEventListener('resize', onResize)
}
ws.onerror = (event) => {
term.value.write('\r\n连接发生错误,请刷新重试。')
term.value.write('\r\nConnection error. Please refresh .')
term.value.write('\r\n' + $gettext('Connection error. Please refresh.'))
console.error(event)
ws.close()
}
@@ -228,7 +228,7 @@ onUnmounted(() => {
<template #action>
<n-button type="primary" @click="create = true">
<TheIcon :size="18" icon="material-symbols:add" />
创建主机
{{ $gettext('Create Host') }}
</n-button>
</template>
<n-layout has-sider sider-placement="right">

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import ssh from '@/api/panel/ssh'
import { NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const id = defineModel<number>('id', { type: Number, required: true })
const loading = ref(false)
@@ -25,7 +27,7 @@ const handleSubmit = () => {
loading.value = false
show.value = false
window.$bus.emit('ssh:refresh')
window.$message.success('更新成功')
window.$message.success($gettext('Updated successfully'))
})
.onComplete(() => {
loading.value = false
@@ -51,55 +53,55 @@ watch(show, async () => {
<n-modal
v-model:show="show"
preset="card"
title="创建主机"
:title="$gettext('Update Host')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form>
<n-form-item label="名称">
<n-form-item :label="$gettext('Name')">
<n-input v-model:value="model.name" placeholder="127.0.0.1" />
</n-form-item>
<n-row :gutter="[0, 24]" pt-20>
<n-col :span="15">
<n-form-item label="主机">
<n-form-item :label="$gettext('Host')">
<n-input v-model:value="model.host" placeholder="127.0.0.1" />
</n-form-item>
</n-col>
<n-col :span="2"> </n-col>
<n-col :span="7">
<n-form-item label="端口">
<n-form-item :label="$gettext('Port')">
<n-input-number v-model:value="model.port" :min="1" :max="65535" />
</n-form-item>
</n-col>
</n-row>
<n-form-item label="认证方式">
<n-form-item :label="$gettext('Authentication Method')">
<n-select
v-model:value="model.auth_method"
:options="[
{ label: '密码', value: 'password' },
{ label: '私钥', value: 'publickey' }
{ label: $gettext('Password'), value: 'password' },
{ label: $gettext('Private Key'), value: 'publickey' }
]"
>
</n-select>
</n-form-item>
<n-form-item v-if="model.auth_method == 'password'" label="用户名">
<n-form-item v-if="model.auth_method == 'password'" :label="$gettext('Username')">
<n-input v-model:value="model.user" placeholder="root" />
</n-form-item>
<n-form-item v-if="model.auth_method == 'password'" label="密码">
<n-form-item v-if="model.auth_method == 'password'" :label="$gettext('Password')">
<n-input v-model:value="model.password" type="password" show-password-on="click" />
</n-form-item>
<n-form-item v-if="model.auth_method == 'publickey'" label="私钥">
<n-form-item v-if="model.auth_method == 'publickey'" :label="$gettext('Private Key')">
<n-input v-model:value="model.key" type="textarea" />
</n-form-item>
<n-form-item label="备注">
<n-form-item :label="$gettext('Remarks')">
<n-input v-model:value="model.remark" type="textarea" />
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]" pt-20>
<n-col :span="24">
<n-button type="info" block :loading="loading" @click="handleSubmit"> 提交 </n-button>
<n-button type="info" block :loading="loading" @click="handleSubmit"> {{ $gettext('Submit') }} </n-button>
</n-col>
</n-row>
</n-modal>

View File

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

View File

@@ -15,7 +15,7 @@ export default {
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'websiteIndex.title',
title: '网站',
icon: 'mdi:web',
role: ['admin'],
requireAuth: true