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

feat: 重构选项卡样式

This commit is contained in:
2025-08-23 01:07:42 +08:00
parent 48829ba9ce
commit aca5db5405
19 changed files with 572 additions and 554 deletions

View File

@@ -12,7 +12,7 @@ withDefaults(defineProps<Props>(), {
<transition appear mode="out-in" name="fade-slide">
<section class="cus-scroll-y wh-full flex-col bg-[#f5f6fb] p-15" dark:bg-hex-121212>
<slot />
<app-footer v-if="showFooter" mt-15 />
<app-footer v-if="showFooter" mt-auto pt-20 />
</section>
</transition>
</template>

View File

@@ -1,6 +1,4 @@
<script lang="ts" setup>
import { translateTitle } from '@/locales/menu'
interface Props {
showFooter?: boolean
showHeader?: boolean
@@ -9,32 +7,23 @@ interface Props {
withDefaults(defineProps<Props>(), {
showFooter: false,
showHeader: true,
showHeader: false,
title: undefined
})
const route = useRoute()
</script>
<template>
<app-page :show-footer="showFooter">
<header v-if="showHeader" mb-15 min-h-45 flex items-center justify-between px-15>
<slot v-if="$slots.header" name="header" />
<template v-else>
<div flex items-center>
<slot v-if="$slots['title-prefix']" name="title-prefix" />
<div mr-12 h-16 w-4 rounded-l-2 bg-primary></div>
<h2 font-normal>
{{ title ? title : route.meta.title ? translateTitle(route.meta.title) : '' }}
</h2>
<slot name="title-suffix" />
</div>
<slot name="action" />
</template>
</header>
<n-card flex-1>
<slot />
</n-card>
<div class="flex flex-col gap-10">
<header v-if="showHeader">
<slot v-if="$slots.header" name="header" />
<n-card v-else size="small">
<slot name="tabbar" />
</n-card>
</header>
<n-card>
<slot />
</n-card>
</div>
</app-page>
</template>

View File

@@ -3,11 +3,16 @@ import logoImg from '@/assets/images/logo.png'
import { useThemeStore } from '@/store'
const themeStore = useThemeStore()
const router = useRouter()
const logo = computed(() => themeStore.logo || logoImg)
const toDashboard = () => {
router.push({ name: 'dashboard' })
}
</script>
<template>
<router-link class="h-60 f-c-c" to="/">
<div class="h-60 f-c-c cursor-pointer" @click="toDashboard">
<n-image :src="logo" height="32" preview-disabled />
<h2
v-show="!themeStore.sider.collapsed"
@@ -15,5 +20,5 @@ const logo = computed(() => themeStore.logo || logoImg)
>
{{ themeStore.name }}
</h2>
</router-link>
</div>
</template>

View File

@@ -65,44 +65,44 @@ const menus = computed<TreeSelectOption[]>(() => {
</template>
{{ $gettext('Menu Settings') }}
</n-tooltip>
<n-modal
v-model:show="settingModal"
preset="card"
:title="$gettext('Menu Settings')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="settingModal = false"
@mask-click="settingModal = false"
>
<n-form>
<n-flex vertical>
<n-alert type="info">
{{
$gettext(
'Settings are saved in the browser and will be reset after clearing the browser cache'
)
}}
</n-alert>
<n-form-item :label="$gettext('Custom Logo')">
<n-input
v-model:value="themeStore.logo"
:placeholder="$gettext('Please enter the complete URL')"
/>
</n-form-item>
<n-form-item :label="$gettext('Hide Menu')">
<n-tree-select
cascade
checkable
clearable
multiple
:options="menus"
v-model:value="permissionStore.hiddenRoutes"
></n-tree-select>
</n-form-item>
</n-flex>
</n-form>
</n-modal>
</div>
<n-modal
v-model:show="settingModal"
preset="card"
:title="$gettext('Menu Settings')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="settingModal = false"
@mask-click="settingModal = false"
>
<n-form>
<n-flex vertical>
<n-alert type="info">
{{
$gettext(
'Settings are saved in the browser and will be reset after clearing the browser cache'
)
}}
</n-alert>
<n-form-item :label="$gettext('Custom Logo')">
<n-input
v-model:value="themeStore.logo"
:placeholder="$gettext('Please enter the complete URL')"
/>
</n-form-item>
<n-form-item :label="$gettext('Hide Menu')">
<n-tree-select
cascade
checkable
clearable
multiple
:options="menus"
v-model:value="permissionStore.hiddenRoutes"
/>
</n-form-item>
</n-flex>
</n-form>
</n-modal>
</template>

View File

@@ -31,19 +31,14 @@ const postgreSQLInstalled = computed(() => {
</script>
<template>
<common-page show-footer>
<n-flex vertical>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="website" :tab="$gettext('Website')">
<list-view v-model:type="currentTab" />
</n-tab-pane>
<n-tab-pane v-if="mySQLInstalled" name="mysql" tab="MySQL">
<list-view v-model:type="currentTab" />
</n-tab-pane>
<n-tab-pane v-if="postgreSQLInstalled" name="postgres" tab="PostgreSQL">
<list-view v-model:type="currentTab" />
</n-tab-pane>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="currentTab" animated>
<n-tab name="website" :tab="$gettext('Website')" />
<n-tab v-if="mySQLInstalled" name="mysql" tab="MySQL" />
<n-tab v-if="postgreSQLInstalled" name="postgres" tab="PostgreSQL" />
</n-tabs>
</n-flex>
</template>
<list-view v-model:type="currentTab" />
</common-page>
</template>

View File

@@ -149,6 +149,21 @@ const handleDelete = async (file: string) => {
})
}
watch(
type,
(newType) => {
if (newType === 'website') {
createModel.value.target = websites.value[0]?.value || ''
restoreModel.value.target = websites.value[0]?.value || ''
} else {
createModel.value.target = ''
restoreModel.value.target = ''
}
refresh()
},
{ immediate: true }
)
onMounted(() => {
useRequest(app.isInstalled('nginx')).onSuccess(({ data }) => {
if (data.installed) {

View File

@@ -90,8 +90,15 @@ onUnmounted(() => {
</script>
<template>
<common-page show-footer>
<template #action>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="currentTab" animated>
<n-tab name="cert" :tab="$gettext('Certificate List')" />
<n-tab name="account" :tab="$gettext('Account List')" />
<n-tab name="dns" :tab="$gettext('DNS List')" />
</n-tabs>
</template>
<n-flex vertical>
<n-flex>
<n-button v-if="currentTab == 'cert'" type="success" @click="uploadCert = true">
<the-icon :size="18" icon="material-symbols:upload" />
@@ -101,7 +108,7 @@ onUnmounted(() => {
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Certificate') }}
</n-button>
<n-button v-if="currentTab == 'user'" type="primary" @click="createAccount = true">
<n-button v-if="currentTab == 'account'" type="primary" @click="createAccount = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Account') }}
</n-button>
@@ -110,18 +117,20 @@ onUnmounted(() => {
{{ $gettext('Create DNS') }}
</n-button>
</n-flex>
</template>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="cert" :tab="$gettext('Certificate List')">
<cert-view :accounts="accounts" :algorithms="algorithms" :websites="websites" :dns="dns" />
</n-tab-pane>
<n-tab-pane name="user" :tab="$gettext('Account List')">
<account-view :ca-providers="caProviders" :algorithms="algorithms" />
</n-tab-pane>
<n-tab-pane name="dns" :tab="$gettext('DNS List')">
<dns-view :dns-providers="dnsProviders" />
</n-tab-pane>
</n-tabs>
<cert-view
v-if="currentTab == 'cert'"
:accounts="accounts"
:algorithms="algorithms"
:websites="websites"
:dns="dns"
/>
<account-view
v-if="currentTab == 'account'"
:ca-providers="caProviders"
:algorithms="algorithms"
/>
<dns-view v-if="currentTab == 'dns'" :dns-providers="dnsProviders" />
</n-flex>
</common-page>
<upload-cert-modal v-model:show="uploadCert" />
<create-cert-modal

View File

@@ -16,23 +16,20 @@ 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="$gettext('Containers')">
<container-view />
</n-tab-pane>
<n-tab-pane name="compose" :tab="$gettext('Compose')">
<compose-view />
</n-tab-pane>
<n-tab-pane name="image" :tab="$gettext('Images')">
<image-view />
</n-tab-pane>
<n-tab-pane name="network" :tab="$gettext('Networks')">
<network-view />
</n-tab-pane>
<n-tab-pane name="volume" :tab="$gettext('Volumes')">
<volume-view />
</n-tab-pane>
</n-tabs>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="current" animated>
<n-tab name="container" :tab="$gettext('Containers')" />
<n-tab name="compose" :tab="$gettext('Compose')" />
<n-tab name="image" :tab="$gettext('Images')" />
<n-tab name="network" :tab="$gettext('Networks')" />
<n-tab name="volume" :tab="$gettext('Volumes')" />
</n-tabs>
</template>
<container-view v-if="current === 'container'" />
<compose-view v-else-if="current === 'compose'" />
<image-view v-else-if="current === 'image'" />
<network-view v-else-if="current === 'network'" />
<volume-view v-else-if="current === 'volume'" />
</common-page>
</template>

View File

@@ -21,37 +21,40 @@ const createServerModalShow = ref(false)
</script>
<template>
<common-page show-footer>
<template #action>
<n-button
v-if="currentTab === 'database'"
type="primary"
@click="createDatabaseModalShow = true"
>
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Database') }}
</n-button>
<n-button v-if="currentTab === 'user'" type="primary" @click="createUserModalShow = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create User') }}
</n-button>
<n-button v-if="currentTab === 'server'" type="primary" @click="createServerModalShow = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Add Server') }}
</n-button>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="currentTab" animated>
<n-tab name="database" :tab="$gettext('Database')" />
<n-tab name="user" :tab="$gettext('User')" />
<n-tab name="server" :tab="$gettext('Server')" />
</n-tabs>
</template>
<n-flex vertical>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="database" :tab="$gettext('Database')">
<database-list />
</n-tab-pane>
<n-tab-pane name="user" :tab="$gettext('User')">
<user-list />
</n-tab-pane>
<n-tab-pane name="server" :tab="$gettext('Server')">
<server-list />
</n-tab-pane>
</n-tabs>
<n-flex>
<n-button
v-if="currentTab === 'database'"
type="primary"
@click="createDatabaseModalShow = true"
>
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Database') }}
</n-button>
<n-button v-if="currentTab === 'user'" type="primary" @click="createUserModalShow = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create User') }}
</n-button>
<n-button
v-if="currentTab === 'server'"
type="primary"
@click="createServerModalShow = true"
>
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Add Server') }}
</n-button>
</n-flex>
<database-list v-if="currentTab === 'database'" />
<user-list v-if="currentTab === 'user'" />
<server-list v-if="currentTab === 'server'" />
</n-flex>
</common-page>
<create-database-modal v-model:show="createDatabaseModalShow" />

View File

@@ -14,21 +14,19 @@ 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="$gettext('Port Rules')">
<rule-view />
</n-tab-pane>
<n-tab-pane name="ip-rule" :tab="$gettext('IP Rules')">
<ip-rule-view />
</n-tab-pane>
<n-tab-pane name="forward" :tab="$gettext('Port Forwarding')">
<forward-view />
</n-tab-pane>
<n-tab-pane name="setting" :tab="$gettext('Settings')">
<setting-view />
</n-tab-pane>
</n-tabs>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="currentTab" animated>
<n-tab name="rule" :tab="$gettext('Port Rules')" />
<n-tab name="ip-rule" :tab="$gettext('IP Rules')" />
<n-tab name="forward" :tab="$gettext('Port Forwarding')" />
<n-tab name="setting" :tab="$gettext('Settings')" />
</n-tabs>
</template>
<rule-view v-if="currentTab === 'rule'" />
<ip-rule-view v-if="currentTab === 'ip-rule'" />
<forward-view v-if="currentTab === 'forward'" />
<setting-view v-if="currentTab === 'setting'" />
</common-page>
</template>

View File

@@ -442,45 +442,44 @@ watch(data, () => {
</script>
<template>
<common-page show-footer>
<template #action>
<n-popconfirm @positive-click="handleClear">
<template #trigger>
<n-button type="error">
<the-icon :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>
<n-form
inline
label-placement="left"
label-width="auto"
require-mark-placement="right-hanging"
>
<n-flex items-center>
<n-form-item :label="$gettext('Enable Monitoring')">
<common-page show-header show-footer>
<template #tabbar>
<div class="flex items-center justify-between gap-8 py-4">
<div class="flex items-center gap-6">
<div class="flex items-center gap-10">
{{ $gettext('Enable Monitoring') }}
<n-switch v-model:value="monitorSwitch" @update-value="handleUpdate" />
</n-form-item>
<n-form-item :label="$gettext('Save Days')">
</div>
<div class="flex items-center gap-10 pl-20">
{{ $gettext('Save Days') }}
<n-input-number v-model:value="saveDay">
<template #suffix> {{ $gettext('days') }} </template>
</n-input-number>
</n-form-item>
<n-form-item>
</div>
<div>
<n-button type="primary" @click="handleUpdate">{{ $gettext('Confirm') }}</n-button>
</n-form-item>
<n-form-item :label="$gettext('Time Selection')">
</div>
</div>
<div class="flex items-center gap-10">
<span>{{ $gettext('Time Selection') }}</span>
<div class="flex items-center gap-2">
<n-date-picker v-model:value="start" type="datetime" />
-
<span class="mx-1">-</span>
<n-date-picker v-model:value="end" type="datetime" />
</n-form-item>
</n-flex>
</n-form>
</n-card>
</div>
<n-popconfirm @positive-click="handleClear">
<template #trigger>
<n-button type="error">
<the-icon :size="16" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Monitoring Records') }}
</n-button>
</template>
{{ $gettext('Are you sure you want to clear?') }}
</n-popconfirm>
</div>
</div>
</template>
<n-grid
v-if="!loading"
cols="1 s:1 m:1 l:2 xl:2 2xl:2"

View File

@@ -58,28 +58,31 @@ const handleCreate = () => {
</script>
<template>
<common-page show-footer>
<template #action>
<n-button v-if="currentTab != 'user'" type="primary" @click="handleSave">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button v-if="currentTab == 'user'" type="primary" @click="handleCreate">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create User') }}
</n-button>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="currentTab" animated>
<n-tab name="base" :tab="$gettext('Basic')" />
<n-tab name="safe" :tab="$gettext('Safe')" />
<n-tab name="user" :tab="$gettext('User')" />
</n-tabs>
</template>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="base" :tab="$gettext('Basic')">
<setting-base v-model:model="model" />
</n-tab-pane>
<n-tab-pane name="safe" :tab="$gettext('Safe')">
<setting-safe v-model:model="model" />
</n-tab-pane>
<n-tab-pane name="user" :tab="$gettext('User')">
<setting-user />
</n-tab-pane>
</n-tabs>
<n-flex vertical>
<n-flex>
<n-button v-if="currentTab == 'user'" type="primary" @click="handleCreate">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create User') }}
</n-button>
</n-flex>
<setting-base v-if="currentTab === 'base'" v-model:model="model" />
<setting-safe v-if="currentTab === 'safe'" v-model:model="model" />
<setting-user v-if="currentTab === 'user'" />
<n-flex>
<n-button v-if="currentTab != 'user'" type="primary" @click="handleSave">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</n-flex>
</n-flex>
</common-page>
<create-modal v-model:show="createModal" />
</template>

View File

@@ -225,12 +225,6 @@ onUnmounted(() => {
<template>
<common-page show-footer>
<template #action>
<n-button type="primary" @click="create = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Host') }}
</n-button>
</template>
<n-layout has-sider sider-placement="right">
<n-layout content-style="overflow: visible" bg-hex-111>
<div ref="terminal" @wheel="onTermWheel" h-75vh></div>
@@ -245,7 +239,14 @@ onUnmounted(() => {
@expand="collapsed = false"
@after-enter="onResize"
@after-leave="onResize"
pl-10
>
<div class="text-center">
<n-button type="primary" @click="create = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Host') }}
</n-button>
</div>
<n-menu
v-model:value="current"
:collapsed="collapsed"

View File

@@ -18,24 +18,25 @@ const create = ref(false)
</script>
<template>
<common-page show-footer>
<template #action>
<n-button v-if="current == 'cron'" type="primary" @click="create = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Task') }}
</n-button>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="current" animated>
<n-tab name="cron" :tab="$gettext('Scheduled Tasks')" />
<n-tab name="system" :tab="$gettext('System Processes')" />
<n-tab name="task" :tab="$gettext('Panel Tasks')" />
</n-tabs>
</template>
<n-tabs v-model:value="current" type="line" animated>
<n-tab-pane name="cron" :tab="$gettext('Scheduled Tasks')">
<cron-view />
</n-tab-pane>
<n-tab-pane name="system" :tab="$gettext('System Processes')">
<system-view />
</n-tab-pane>
<n-tab-pane name="task" :tab="$gettext('Panel Tasks')">
<task-view />
</n-tab-pane>
</n-tabs>
<n-flex vertical>
<n-flex>
<n-button v-if="current == 'cron'" type="primary" @click="create = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Task') }}
</n-button>
</n-flex>
<cron-view v-if="current === 'cron'" />
<system-view v-if="current === 'system'" />
<task-view v-if="current === 'task'" />
</n-flex>
</common-page>
<create-modal v-model:show="create" />
</template>

View File

@@ -83,195 +83,193 @@ const handleTest = async () => {
</script>
<template>
<common-page show-footer>
<n-flex vertical>
<n-alert type="warning">
{{
$gettext(
'Benchmark results are for reference only and may differ from actual performance due to system resource scheduling, caching, and other factors!'
)
}}
</n-alert>
<n-alert
v-if="inTest"
:title="$gettext('Benchmarking in progress, it may take some time...')"
type="info"
>
{{ $gettext('Current project: %{ current }', { current: current }) }}
</n-alert>
<n-progress v-if="inTest" :percentage="progress" color="var(--primary-color)" processing />
</n-flex>
<n-flex vertical items-center pt-40>
<div w-800>
<n-grid :cols="3">
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="cpuTotal !== 0">
<n-number-animation :from="0" :to="cpuTotal" show-separator />
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<the-icon :size="50" icon="bi:cpu" color="var(--primary-color)" />
</n-progress>
{{ $gettext('CPU') }}
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>{{ $gettext('Image Processing') }}</th>
<td>
{{ cpu.image }}
</td>
</tr>
<tr>
<th>{{ $gettext('Machine Learning') }}</th>
<td>
{{ cpu.machine }}
</td>
</tr>
<tr>
<th>{{ $gettext('Program Compilation') }}</th>
<td>
{{ cpu.compile }}
</td>
</tr>
<tr>
<th>{{ $gettext('AES Encryption') }}</th>
<td>
{{ cpu.encryption }}
</td>
</tr>
<tr>
<th>{{ $gettext('Compression/Decompression') }}</th>
<td>
{{ cpu.compression }}
</td>
</tr>
<tr>
<th>{{ $gettext('Physics Simulation') }}</th>
<td>
{{ cpu.physics }}
</td>
</tr>
<tr>
<th>{{ $gettext('JSON Parsing') }}</th>
<td>
{{ cpu.json }}
</td>
</tr>
</n-table>
</n-popover>
</n-gi>
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="memory.score !== 0">
<n-number-animation :from="0" :to="memory.score" show-separator />
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<the-icon :size="50" icon="bi:memory" color="var(--primary-color)" />
</n-progress>
{{ $gettext('Memory') }}
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>{{ $gettext('Memory Bandwidth') }}</th>
<td>{{ memory.bandwidth }}</td>
</tr>
<tr>
<th>{{ $gettext('Memory Latency') }}</th>
<td>{{ memory.latency }}</td>
</tr>
</n-table>
</n-popover>
</n-gi>
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="disk.score !== 0">
<n-number-animation :from="0" :to="disk.score" show-separator />
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<the-icon :size="50" icon="bi:hdd-stack" color="var(--primary-color)" />
</n-progress>
{{ $gettext('Disk') }}
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>{{ $gettext('4KB Read') }}</th>
<td>
{{ disk['4'].read_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('4KB Write') }}</th>
<td>
{{ disk['4'].write_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('64KB Read') }}</th>
<td>
{{ disk['64'].read_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('64KB Write') }}</th>
<td>
{{ disk['64'].write_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('1MB Read') }}</th>
<td>
{{ disk['1024'].read_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('1MB Write') }}</th>
<td>
{{ disk['1024'].write_speed }}
</td>
</tr>
</n-table>
</n-popover>
</n-gi>
</n-grid>
</div>
<n-button
type="primary"
size="large"
:disabled="inTest"
:loading="inTest"
@click="handleTest"
mt-40
w-200
>
{{ inTest ? $gettext('Benchmarking...') : $gettext('Start Benchmark') }}
</n-button>
</n-flex>
</common-page>
<n-flex vertical>
<n-alert type="warning">
{{
$gettext(
'Benchmark results are for reference only and may differ from actual performance due to system resource scheduling, caching, and other factors!'
)
}}
</n-alert>
<n-alert
v-if="inTest"
:title="$gettext('Benchmarking in progress, it may take some time...')"
type="info"
>
{{ $gettext('Current project: %{ current }', { current: current }) }}
</n-alert>
<n-progress v-if="inTest" :percentage="progress" color="var(--primary-color)" processing />
</n-flex>
<n-flex vertical items-center pt-40>
<div w-800>
<n-grid :cols="3">
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="cpuTotal !== 0">
<n-number-animation :from="0" :to="cpuTotal" show-separator />
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<the-icon :size="50" icon="bi:cpu" color="var(--primary-color)" />
</n-progress>
{{ $gettext('CPU') }}
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>{{ $gettext('Image Processing') }}</th>
<td>
{{ cpu.image }}
</td>
</tr>
<tr>
<th>{{ $gettext('Machine Learning') }}</th>
<td>
{{ cpu.machine }}
</td>
</tr>
<tr>
<th>{{ $gettext('Program Compilation') }}</th>
<td>
{{ cpu.compile }}
</td>
</tr>
<tr>
<th>{{ $gettext('AES Encryption') }}</th>
<td>
{{ cpu.encryption }}
</td>
</tr>
<tr>
<th>{{ $gettext('Compression/Decompression') }}</th>
<td>
{{ cpu.compression }}
</td>
</tr>
<tr>
<th>{{ $gettext('Physics Simulation') }}</th>
<td>
{{ cpu.physics }}
</td>
</tr>
<tr>
<th>{{ $gettext('JSON Parsing') }}</th>
<td>
{{ cpu.json }}
</td>
</tr>
</n-table>
</n-popover>
</n-gi>
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="memory.score !== 0">
<n-number-animation :from="0" :to="memory.score" show-separator />
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<the-icon :size="50" icon="bi:memory" color="var(--primary-color)" />
</n-progress>
{{ $gettext('Memory') }}
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>{{ $gettext('Memory Bandwidth') }}</th>
<td>{{ memory.bandwidth }}</td>
</tr>
<tr>
<th>{{ $gettext('Memory Latency') }}</th>
<td>{{ memory.latency }}</td>
</tr>
</n-table>
</n-popover>
</n-gi>
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="disk.score !== 0">
<n-number-animation :from="0" :to="disk.score" show-separator />
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<the-icon :size="50" icon="bi:hdd-stack" color="var(--primary-color)" />
</n-progress>
{{ $gettext('Disk') }}
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>{{ $gettext('4KB Read') }}</th>
<td>
{{ disk['4'].read_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('4KB Write') }}</th>
<td>
{{ disk['4'].write_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('64KB Read') }}</th>
<td>
{{ disk['64'].read_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('64KB Write') }}</th>
<td>
{{ disk['64'].write_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('1MB Read') }}</th>
<td>
{{ disk['1024'].read_speed }}
</td>
</tr>
<tr>
<th>{{ $gettext('1MB Write') }}</th>
<td>
{{ disk['1024'].write_speed }}
</td>
</tr>
</n-table>
</n-popover>
</n-gi>
</n-grid>
</div>
<n-button
type="primary"
size="large"
:disabled="inTest"
:loading="inTest"
@click="handleTest"
mt-40
w-200
>
{{ inTest ? $gettext('Benchmarking...') : $gettext('Start Benchmark') }}
</n-button>
</n-flex>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
defineOptions({
name: 'toolbox-index'
})
import BenchmarkView from '@/views/toolbox/BenchmarkView.vue'
import SystemView from '@/views/toolbox/SystemView.vue'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const current = ref('system')
</script>
<template>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="current" animated>
<n-tab name="system" :tab="$gettext('System')" />
<n-tab name="benchmark" :tab="$gettext('Benchmark')" />
</n-tabs>
</template>
<n-flex vertical>
<system-view v-if="current === 'system'" />
<benchmark-view v-if="current === 'benchmark'" />
</n-flex>
</common-page>
</template>
<style scoped lang="scss"></style>

View File

@@ -5,7 +5,6 @@ defineOptions({
import Editor from '@guolao/vue-monaco-editor'
import { DateTime } from 'luxon'
import { NButton } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import system from '@/api/panel/toolbox-system'
@@ -90,76 +89,57 @@ const handleSyncTime = () => {
</script>
<template>
<common-page show-footer>
<template #action>
<n-button v-if="currentTab == 'dns'" class="ml-16" type="primary" @click="handleUpdateDNS">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button v-if="currentTab == 'swap'" class="ml-16" type="primary" @click="handleUpdateSwap">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button v-if="currentTab == 'host'" class="ml-16" type="primary" @click="handleUpdateHost">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button v-if="currentTab == 'time'" class="ml-16" type="primary" @click="handleUpdateTime">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
v-if="currentTab == 'root-password'"
class="ml-16"
type="primary"
@click="handleUpdateRootPassword"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Modify') }}
</n-button>
</template>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="dns" tab="DNS">
<n-flex vertical>
<n-alert type="warning">
{{ $gettext('DNS modifications will revert to default after system restart.') }}
</n-alert>
<n-form>
<n-form-item label="DNS1">
<n-input v-model:value="dns1" />
</n-form-item>
<n-form-item label="DNS2">
<n-input v-model:value="dns2" />
</n-form-item>
</n-form>
<n-tabs v-model:value="currentTab" type="line" placement="left" animated>
<n-tab-pane name="dns" tab="DNS">
<n-flex vertical>
<n-alert type="warning">
{{ $gettext('DNS modifications will revert to default after system restart.') }}
</n-alert>
<n-form>
<n-form-item label="DNS1">
<n-input v-model:value="dns1" />
</n-form-item>
<n-form-item label="DNS2">
<n-input v-model:value="dns2" />
</n-form-item>
</n-form>
<n-flex>
<n-button type="primary" @click="handleUpdateDNS">
{{ $gettext('Save') }}
</n-button>
</n-flex>
</n-tab-pane>
<n-tab-pane name="swap" tab="SWAP">
<n-flex vertical>
<n-alert type="info">
{{
$gettext('Total %{ total }, used %{ used }, free %{ free }', {
total: swapTotal,
used: swapUsed,
free: swapFree
})
}}
</n-alert>
<n-form>
<n-form-item :label="$gettext('SWAP Size')">
<n-input-number v-model:value="swap" />
MB
</n-form-item>
</n-form>
</n-flex>
</n-tab-pane>
<n-tab-pane name="swap" tab="SWAP">
<n-flex vertical>
<n-alert type="info">
{{
$gettext('Total %{ total }, used %{ used }, free %{ free }', {
total: swapTotal,
used: swapUsed,
free: swapFree
})
}}
</n-alert>
<n-form>
<n-form-item :label="$gettext('SWAP Size')">
<n-input-number v-model:value="swap" />
MB
</n-form-item>
</n-form>
<n-flex>
<n-button type="primary" @click="handleUpdateSwap">
{{ $gettext('Save') }}
</n-button>
</n-flex>
</n-tab-pane>
<n-tab-pane name="host" :tab="$gettext('Host')">
<n-flex vertical>
<n-form>
<n-form-item :label="$gettext('Hostname')">
<n-input v-model:value="hostname" />
</n-form-item>
</n-form>
</n-flex>
</n-tab-pane>
<n-tab-pane name="host" :tab="$gettext('Host')">
<n-form>
<n-form-item :label="$gettext('Hostname')">
<n-input v-model:value="hostname" />
</n-form-item>
<n-form-item :label="$gettext('Hosts')">
<Editor
v-model:value="hosts"
language="ini"
@@ -172,43 +152,52 @@ const handleSyncTime = () => {
formatOnPaste: true
}"
/>
</n-flex>
</n-tab-pane>
<n-tab-pane name="time" :tab="$gettext('Time')">
<n-flex vertical>
<n-alert type="info">
{{
$gettext(
'After manually changing the time, it may still be overwritten by system automatic time synchronization.'
)
}}
</n-alert>
<n-form>
<n-form-item :label="$gettext('Select Timezone')">
<n-select
v-model:value="timezone"
:placeholder="$gettext('Please select a timezone')"
:options="timezones"
/>
</n-form-item>
<n-form-item :label="$gettext('Modify Time')">
<n-date-picker v-model:value="time" type="datetime" clearable />
</n-form-item>
<n-form-item :label="$gettext('NTP Time Synchronization')">
<n-button type="info" @click="handleSyncTime">{{
$gettext('Synchronize Time')
}}</n-button>
</n-form-item>
</n-form>
</n-flex>
</n-tab-pane>
<n-tab-pane name="root-password" :tab="$gettext('Root Password')">
</n-form-item>
</n-form>
<n-button type="primary" @click="handleUpdateHost">
{{ $gettext('Save') }}
</n-button>
</n-tab-pane>
<n-tab-pane name="time" :tab="$gettext('Time')">
<n-flex vertical>
<n-alert type="info">
{{
$gettext(
'After manually changing the time, it may still be overwritten by system automatic time synchronization.'
)
}}
</n-alert>
<n-form>
<n-form-item :label="$gettext('Root Password')">
<n-input v-model:value="rootPassword" type="password" show-password-on="click" />
<n-form-item :label="$gettext('Select Timezone')">
<n-select
v-model:value="timezone"
:placeholder="$gettext('Please select a timezone')"
:options="timezones"
/>
</n-form-item>
<n-form-item :label="$gettext('Modify Time')">
<n-date-picker v-model:value="time" type="datetime" clearable />
</n-form-item>
</n-form>
</n-tab-pane>
</n-tabs>
</common-page>
<n-flex>
<n-button type="primary" @click="handleUpdateTime">
{{ $gettext('Save') }}
</n-button>
<n-button type="info" @click="handleSyncTime">
{{ $gettext('Synchronize Time') }}
</n-button>
</n-flex>
</n-flex>
</n-tab-pane>
<n-tab-pane name="root-password" :tab="$gettext('Root Password')">
<n-form>
<n-form-item :label="$gettext('Root Password')">
<n-input v-model:value="rootPassword" type="password" show-password-on="click" />
</n-form-item>
</n-form>
<n-button type="primary" @click="handleUpdateRootPassword">
{{ $gettext('Save') }}
</n-button>
</n-tab-pane>
</n-tabs>
</template>

View File

@@ -7,27 +7,16 @@ export default {
path: '/toolbox',
component: Layout,
meta: {
title: 'Toolbox',
icon: 'mdi:tools',
order: 90
},
children: [
{
name: 'toolbox-system',
path: 'system',
component: () => import('./SystemView.vue'),
name: 'toolbox-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: 'System',
role: ['admin'],
requireAuth: true
}
},
{
name: 'toolbox-benchmark',
path: 'benchmark',
component: () => import('./BenchmarkView.vue'),
meta: {
title: 'Benchmark',
title: 'Toolbox',
icon: 'mdi:tools',
role: ['admin'],
requireAuth: true
}

View File

@@ -376,8 +376,16 @@ onMounted(() => {
<template>
<common-page show-footer>
<template #action>
<n-flex vertical>
<n-flex>
<n-button type="primary" @click="createModal = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Website') }}
</n-button>
<n-button type="primary" @click="bulkCreateModal = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Bulk Create Website') }}
</n-button>
<n-button type="warning" @click="editDefaultPageModal = true">
<the-icon :size="18" icon="material-symbols:edit-document-outline" />
{{ $gettext('Modify Default Page') }}
@@ -395,17 +403,7 @@ onMounted(() => {
)
}}
</n-popconfirm>
<n-button type="primary" @click="bulkCreateModal = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Bulk Create Website') }}
</n-button>
<n-button type="primary" @click="createModal = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Website') }}
</n-button>
</n-flex>
</template>
<n-flex vertical :size="20">
<n-data-table
striped
remote