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

feat: 去掉没用的图标

This commit is contained in:
2025-08-23 03:18:04 +08:00
parent 6bc2c6fc90
commit 1e9399ae37
43 changed files with 718 additions and 774 deletions

View File

@@ -148,20 +148,17 @@ onMounted(() => {
</n-alert>
<n-flex>
<n-button type="success" v-model:disabled="fetchingStatus" @click="handleStart">
<the-icon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
{{ $gettext('Start') }}
</n-button>
<n-popconfirm @positive-click="handleStop">
<template #trigger>
<n-button type="error" v-model:disabled="fetchingStatus">
<the-icon :size="24" icon="material-symbols:stop-outline-rounded" />
{{ $gettext('Stop') }}
</n-button>
</template>
{{ $gettext('Are you sure you want to stop %{ service }?', { service: props.service }) }}
</n-popconfirm>
<n-button type="warning" v-model:disabled="fetchingStatus" @click="handleRestart">
<the-icon :size="18" icon="material-symbols:replay-rounded" />
{{ $gettext('Restart') }}
</n-button>
<n-button
@@ -170,7 +167,6 @@ onMounted(() => {
v-model:disabled="fetchingStatus"
@click="handleReload"
>
<the-icon :size="20" icon="material-symbols:refresh-rounded" />
{{ $gettext('Reload') }}
</n-button>
</n-flex>

View File

@@ -11,7 +11,6 @@ import { useGettext } from 'vue3-gettext'
import app from '@/api/panel/app'
import TheIcon from '@/components/custom/TheIcon.vue'
import { router } from '@/router'
import { renderIcon } from '@/utils'
const { $gettext } = useGettext()
@@ -93,10 +92,7 @@ const columns: any = [
type: 'warning'
},
{
default: () => $gettext('Update'),
icon: renderIcon('material-symbols:arrow-circle-up-outline-rounded', {
size: 14
})
default: () => $gettext('Update')
}
)
}
@@ -112,8 +108,7 @@ const columns: any = [
onClick: () => handleManage(row.slug)
},
{
default: () => $gettext('Manage'),
icon: renderIcon('material-symbols:settings-outline', { size: 14 })
default: () => $gettext('Manage')
}
)
: null,
@@ -135,8 +130,7 @@ const columns: any = [
type: 'error'
},
{
default: () => $gettext('Uninstall'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Uninstall')
}
)
}
@@ -156,8 +150,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Install'),
icon: renderIcon('material-symbols:download-rounded', { size: 14 })
default: () => $gettext('Install')
}
)
: null
@@ -220,7 +213,6 @@ onMounted(() => {
<common-page show-footer>
<template #action>
<n-button type="primary" @click="handleUpdateCache">
<the-icon :size="18" icon="material-symbols:refresh" />
{{ $gettext('Update Cache') }}
</n-button>
</template>

View File

@@ -35,7 +35,6 @@ const handleSaveConfig = () => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -35,7 +35,6 @@ const handleSaveConfig = () => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -10,7 +10,6 @@ import fail2ban from '@/api/apps/fail2ban'
import app from '@/api/panel/app'
import website from '@/api/panel/website'
import ServiceStatus from '@/components/common/ServiceStatus.vue'
import { renderIcon } from '@/utils'
const { $gettext } = useGettext()
const currentTab = ref('status')
@@ -75,8 +74,7 @@ const jailsColumns: any = [
}
},
{
default: () => $gettext('View'),
icon: renderIcon('material-symbols:visibility', { size: 14 })
default: () => $gettext('View')
}
),
h(
@@ -97,8 +95,7 @@ const jailsColumns: any = [
style: 'margin-left: 15px'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}
@@ -141,8 +138,7 @@ const banedIPColumns: any = [
type: 'error'
},
{
default: () => $gettext('Unban'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Unban')
}
)
}
@@ -235,7 +231,6 @@ onMounted(() => {
type="primary"
@click="handleSaveWhiteList"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save Whitelist') }}
</n-button>
<n-button
@@ -244,7 +239,6 @@ onMounted(() => {
type="primary"
@click="addJailModal = true"
>
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Add Rule') }}
</n-button>
</template>

View File

@@ -44,7 +44,6 @@ onMounted(() => {
<n-card :title="$gettext('Modify Configuration')">
<template #header-extra>
<n-button type="primary" @click="handleSaveConfig('frps')">
<the-icon :size="18" icon="material-symbols:save-outline-rounded" />
{{ $gettext('Save') }}
</n-button>
</template>
@@ -69,7 +68,6 @@ onMounted(() => {
<n-card :title="$gettext('Modify Configuration')">
<template #header-extra>
<n-button type="primary" @click="handleSaveConfig('frpc')">
<the-icon :size="18" icon="material-symbols:save-outline-rounded" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -33,7 +33,6 @@ const handleSaveConfig = () => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -55,7 +55,6 @@ const handleSaveConfig = () => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -28,7 +28,6 @@ const handleSaveEnv = () => {
<common-page show-footer>
<template #action>
<n-button v-if="currentTab == 'env'" class="ml-16" type="primary" @click="handleSaveEnv">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -75,7 +75,6 @@ const handleSetRootPassword = async () => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -84,7 +83,6 @@ const handleSetRootPassword = async () => {
type="primary"
@click="handleClearErrorLog"
>
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Log') }}
</n-button>
<n-button
@@ -93,7 +91,6 @@ const handleSetRootPassword = async () => {
type="primary"
@click="handleClearSlowLog"
>
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Slow Log') }}
</n-button>
</template>

View File

@@ -61,7 +61,6 @@ const handleClearErrorLog = () => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -70,7 +69,6 @@ const handleClearErrorLog = () => {
type="primary"
@click="handleClearErrorLog"
>
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Log') }}
</n-button>
</template>

View File

@@ -5,7 +5,6 @@ import { useGettext } from 'vue3-gettext'
import php from '@/api/apps/php'
import ServiceStatus from '@/components/common/ServiceStatus.vue'
import { renderIcon } from '@/utils'
const { $gettext } = useGettext()
const props = defineProps({
@@ -78,8 +77,7 @@ const extensionColumns: any = [
type: 'info'
},
{
default: () => $gettext('Install'),
icon: renderIcon('material-symbols:download-rounded', { size: 14 })
default: () => $gettext('Install')
}
)
}
@@ -106,8 +104,7 @@ const extensionColumns: any = [
type: 'error'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}
@@ -190,7 +187,6 @@ const handleUninstallExtension = async (name: string) => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -199,7 +195,6 @@ const handleUninstallExtension = async (name: string) => {
type="primary"
@click="handleSaveFPMConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -208,7 +203,6 @@ const handleUninstallExtension = async (name: string) => {
type="primary"
@click="handleClearErrorLog"
>
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Error Log') }}
</n-button>
<n-button
@@ -217,7 +211,6 @@ const handleUninstallExtension = async (name: string) => {
type="primary"
@click="handleClearSlowLog"
>
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Slow Log') }}
</n-button>
</template>

View File

@@ -54,7 +54,6 @@ onMounted(() => {
<common-page show-footer>
<template #action>
<n-button v-if="currentTab == 'status'" class="ml-16" type="primary" @click="handleSave">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -63,7 +62,6 @@ onMounted(() => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -43,7 +43,6 @@ const handleSaveStorageConfig = () => {
type="primary"
@click="handleSaveRegistryConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -52,7 +51,6 @@ const handleSaveStorageConfig = () => {
type="primary"
@click="handleSaveStorageConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -67,7 +67,6 @@ const handleClearLog = async () => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -76,11 +75,9 @@ const handleClearLog = async () => {
type="primary"
@click="handleSaveUserConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button v-if="currentTab == 'log'" class="ml-16" type="primary" @click="handleClearLog">
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Log') }}
</n-button>
</template>

View File

@@ -8,7 +8,7 @@ import { useGettext } from 'vue3-gettext'
import pureftpd from '@/api/apps/pureftpd'
import ServiceStatus from '@/components/common/ServiceStatus.vue'
import { generateRandomString, renderIcon } from '@/utils'
import { generateRandomString } from '@/utils'
const { $gettext } = useGettext()
const currentTab = ref('status')
@@ -62,8 +62,7 @@ const userColumns: any = [
}
},
{
default: () => $gettext('Change Password'),
icon: renderIcon('material-symbols:key-outline', { size: 14 })
default: () => $gettext('Change Password')
}
),
h(
@@ -86,8 +85,7 @@ const userColumns: any = [
style: 'margin-left: 15px'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}
@@ -158,7 +156,6 @@ onMounted(() => {
<common-page show-footer>
<template #action>
<n-button v-if="currentTab == 'status'" class="ml-16" type="primary" @click="handleSavePort">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -167,7 +164,6 @@ onMounted(() => {
type="primary"
@click="addUserModal = true"
>
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Add User') }}
</n-button>
</template>

View File

@@ -52,7 +52,6 @@ const handleSaveConfig = () => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
</template>

View File

@@ -9,7 +9,7 @@ import { useGettext } from 'vue3-gettext'
import rsync from '@/api/apps/rsync'
import ServiceStatus from '@/components/common/ServiceStatus.vue'
import { generateRandomString, renderIcon } from '@/utils'
import { generateRandomString } from '@/utils'
const { $gettext } = useGettext()
const currentTab = ref('status')
@@ -80,8 +80,7 @@ const processColumns: any = [
onClick: () => handleModelEdit(row)
},
{
default: () => $gettext('Configure'),
icon: renderIcon('material-symbols:settings-outline', { size: 14 })
default: () => $gettext('Configure')
}
),
h(
@@ -104,8 +103,7 @@ const processColumns: any = [
style: 'margin-left: 15px'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}
@@ -197,7 +195,6 @@ onMounted(() => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -206,7 +203,6 @@ onMounted(() => {
type="primary"
@click="addModuleModal = true"
>
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Add Module') }}
</n-button>
</template>

View File

@@ -7,7 +7,6 @@ import { NButton, NDataTable, NInput, NPopconfirm } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import s3fs from '@/api/apps/s3fs'
import { renderIcon } from '@/utils'
const { $gettext } = useGettext()
const addMountModal = ref(false)
@@ -55,8 +54,7 @@ const columns: any = [
type: 'error'
},
{
default: () => $gettext('Unmount'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Unmount')
}
)
}
@@ -101,7 +99,6 @@ onMounted(() => {
<common-page show-footer>
<template #action>
<n-button class="ml-16" type="primary" @click="addMountModal = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Add Mount') }}
</n-button>
</template>

View File

@@ -9,7 +9,6 @@ import { useGettext } from 'vue3-gettext'
import supervisor from '@/api/apps/supervisor'
import ServiceStatus from '@/components/common/ServiceStatus.vue'
import { renderIcon } from '@/utils'
const { $gettext } = useGettext()
const currentTab = ref('status')
@@ -88,8 +87,7 @@ const processColumns: any = [
onClick: () => handleShowProcessLog(row)
},
{
default: () => $gettext('Logs'),
icon: renderIcon('material-symbols:visibility', { size: 14 })
default: () => $gettext('Logs')
}
),
h(
@@ -101,8 +99,7 @@ const processColumns: any = [
onClick: () => handleEditProcess(row.name)
},
{
default: () => $gettext('Configure'),
icon: renderIcon('material-symbols:settings-outline', { size: 14 })
default: () => $gettext('Configure')
}
),
row.status != 'RUNNING'
@@ -116,8 +113,7 @@ const processColumns: any = [
onClick: () => handleProcessStart(row.name)
},
{
default: () => $gettext('Start'),
icon: renderIcon('material-symbols:play-arrow-outline', { size: 18 })
default: () => $gettext('Start')
}
)
: null,
@@ -142,8 +138,7 @@ const processColumns: any = [
style: 'margin-left: 15px'
},
{
default: () => $gettext('Stop'),
icon: renderIcon('material-symbols:stop-outline', { size: 18 })
default: () => $gettext('Stop')
}
)
}
@@ -171,8 +166,7 @@ const processColumns: any = [
style: 'margin-left: 15px'
},
{
default: () => $gettext('Restart'),
icon: renderIcon('material-symbols:replay', { size: 18 })
default: () => $gettext('Restart')
}
)
}
@@ -199,8 +193,7 @@ const processColumns: any = [
style: 'margin-left: 15px'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}
@@ -309,7 +302,6 @@ onUnmounted(() => {
type="primary"
@click="handleSaveConfig"
>
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-button
@@ -318,11 +310,9 @@ onUnmounted(() => {
type="primary"
@click="createProcessModal = true"
>
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Add Process') }}
</n-button>
<n-button v-if="currentTab == 'log'" class="ml-16" type="primary" @click="handleClearLog">
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Log') }}
</n-button>
</template>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import backup from '@/api/panel/backup'
import { renderIcon } from '@/utils'
import type { MessageReactive } from 'naive-ui'
import { NButton, NDataTable, NFlex, NInput, NPopconfirm } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
@@ -73,8 +72,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Restore'),
icon: renderIcon('material-symbols:settings-backup-restore-rounded', { size: 14 })
default: () => $gettext('Restore')
}
),
h(
@@ -95,8 +93,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}

View File

@@ -101,19 +101,15 @@ onUnmounted(() => {
<n-flex vertical>
<n-flex>
<n-button v-if="currentTab == 'cert'" type="success" @click="uploadCert = true">
<the-icon :size="18" icon="material-symbols:upload" />
{{ $gettext('Upload Certificate') }}
</n-button>
<n-button v-if="currentTab == 'cert'" type="primary" @click="createCert = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Certificate') }}
</n-button>
<n-button v-if="currentTab == 'account'" type="primary" @click="createAccount = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Account') }}
</n-button>
<n-button v-if="currentTab == 'dns'" type="primary" @click="createDNS = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create DNS') }}
</n-button>
</n-flex>

View File

@@ -55,7 +55,6 @@ const handleUpdate = () => {
<template #action>
<div>
<n-button v-if="versions" class="ml-16" type="primary" @click="handleUpdate">
<the-icon :size="18" icon="material-symbols:arrow-circle-up-outline-rounded" />
{{ $gettext('Update Now') }}
</n-button>
</div>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { renderIcon } from '@/utils'
import { NButton, NInput, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
@@ -95,8 +94,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}

View File

@@ -36,11 +36,9 @@ const createServerModalShow = ref(false)
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
@@ -48,7 +46,6 @@ const createServerModalShow = ref(false)
type="primary"
@click="createServerModalShow = true"
>
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Add Server') }}
</n-button>
</n-flex>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { renderIcon } from '@/utils'
import copy2clipboard from '@vavt/copy2clipboard'
import { NButton, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
@@ -162,8 +161,7 @@ const columns: any = [
type: 'success'
},
{
default: () => $gettext('Sync'),
icon: renderIcon('material-symbols:sync', { size: 14 })
default: () => $gettext('Sync')
}
)
}
@@ -181,8 +179,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Modify'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
default: () => $gettext('Modify')
}
),
h(
@@ -214,8 +211,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { renderIcon } from '@/utils'
import copy2clipboard from '@vavt/copy2clipboard'
import { NButton, NFlex, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
@@ -166,8 +165,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Modify'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
default: () => $gettext('Modify')
}
),
h(
@@ -188,8 +186,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}

View File

@@ -3,7 +3,6 @@ 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()
@@ -101,8 +100,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}
@@ -162,13 +160,11 @@ onMounted(() => {
<n-flex vertical :size="20">
<n-flex items-center>
<n-button type="primary" @click="createModalShow = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Forwarding') }}
</n-button>
<n-popconfirm @positive-click="batchDelete">
<template #trigger>
<n-button type="warning">
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Batch Delete') }}
</n-button>
</template>

View File

@@ -3,7 +3,6 @@ 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()
@@ -143,8 +142,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}
@@ -204,13 +202,11 @@ onMounted(() => {
<n-flex vertical :size="20">
<n-flex items-center>
<n-button type="primary" @click="createModalShow = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Rule') }}
</n-button>
<n-popconfirm @positive-click="batchDelete">
<template #trigger>
<n-button type="warning">
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Batch Delete') }}
</n-button>
</template>

View File

@@ -3,7 +3,6 @@ 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()
@@ -180,8 +179,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}
@@ -241,13 +239,11 @@ onMounted(() => {
<n-flex vertical :size="20">
<n-flex items-center>
<n-button type="primary" @click="createModalShow = true">
<the-icon :size="18" icon="material-symbols:add" />
{{ $gettext('Create Rule') }}
</n-button>
<n-popconfirm @positive-click="batchDelete">
<template #trigger>
<n-button type="warning">
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Batch Delete') }}
</n-button>
</template>

View File

@@ -471,7 +471,6 @@ watch(data, () => {
<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>

View File

@@ -6,7 +6,6 @@ import { NButton } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import setting from '@/api/panel/setting'
import TheIcon from '@/components/custom/TheIcon.vue'
import { useThemeStore } from '@/store'
import CreateModal from '@/views/setting/CreateModal.vue'
import SettingBase from '@/views/setting/SettingBase.vue'
@@ -69,7 +68,6 @@ const handleCreate = () => {
<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>
@@ -78,7 +76,6 @@ const handleCreate = () => {
<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>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import user from '@/api/panel/user'
import { formatDateTime, renderIcon } from '@/utils'
import { formatDateTime } from '@/utils'
import PasswordModal from '@/views/setting/PasswordModal.vue'
import TokenModal from '@/views/setting/TokenModal.vue'
import TwoFaModal from '@/views/setting/TwoFaModal.vue'
@@ -100,8 +100,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Access Tokens'),
icon: renderIcon('material-symbols:vpn-key-outline', { size: 14 })
default: () => $gettext('Access Tokens')
}
),
h(
@@ -116,8 +115,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Change Password'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
default: () => $gettext('Change Password')
}
),
h(
@@ -139,8 +137,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import user from '@/api/panel/user'
import { formatDateTime, renderIcon } from '@/utils'
import { formatDateTime } from '@/utils'
import copy2clipboard from '@vavt/copy2clipboard'
import { NAlert, NButton, NDataTable, NFlex, NInput, NPopconfirm } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
@@ -66,8 +66,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Modify'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
default: () => $gettext('Modify')
}
),
h(
@@ -89,8 +88,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}

View File

@@ -5,7 +5,6 @@ defineOptions({
import ssh from '@/api/panel/ssh'
import ws from '@/api/ws'
import TheIcon from '@/components/custom/TheIcon.vue'
import CreateModal from '@/views/ssh/CreateModal.vue'
import UpdateModal from '@/views/ssh/UpdateModal.vue'
import '@fontsource-variable/jetbrains-mono/wght-italic.css'
@@ -243,7 +242,6 @@ onUnmounted(() => {
>
<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>

View File

@@ -8,7 +8,7 @@ import { useGettext } from 'vue3-gettext'
import cron from '@/api/panel/cron'
import file from '@/api/panel/file'
import { decodeBase64, formatDateTime, renderIcon } from '@/utils'
import { decodeBase64, formatDateTime } from '@/utils'
import { CronNaive } from '@vue-js-cron/naive-ui'
const { $gettext } = useGettext()
@@ -117,8 +117,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Logs'),
icon: renderIcon('majesticons:eye-line', { size: 14 })
default: () => $gettext('Logs')
}
),
h(
@@ -130,8 +129,7 @@ const columns: any = [
onClick: () => handleEdit(row)
},
{
default: () => $gettext('Edit'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
default: () => $gettext('Edit')
}
),
h(
@@ -152,8 +150,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}

View File

@@ -3,7 +3,6 @@ defineOptions({
name: 'task-index'
})
import TheIcon from '@/components/custom/TheIcon.vue'
import CreateModal from '@/views/task/CreateModal.vue'
import CronView from '@/views/task/CronView.vue'
import SystemView from '@/views/task/SystemView.vue'
@@ -29,7 +28,6 @@ const create = ref(false)
<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>

View File

@@ -3,7 +3,7 @@ import { NButton, NDataTable, NPopconfirm, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import process from '@/api/panel/process'
import { formatBytes, formatDateTime, formatPercent, renderIcon } from '@/utils'
import { formatBytes, formatDateTime, formatPercent } from '@/utils'
const { $gettext } = useGettext()
@@ -124,8 +124,7 @@ const columns: any = [
type: 'error'
},
{
default: () => $gettext('Terminate'),
icon: renderIcon('material-symbols:stop-circle-outline-rounded', { size: 14 })
default: () => $gettext('Terminate')
}
)
}

View File

@@ -4,7 +4,7 @@ import { useGettext } from 'vue3-gettext'
import task from '@/api/panel/task'
import RealtimeLogModal from '@/components/common/RealtimeLogModal.vue'
import { formatDateTime, renderIcon } from '@/utils'
import { formatDateTime } from '@/utils'
const { $gettext } = useGettext()
const logModal = ref(false)
@@ -71,8 +71,7 @@ const columns: any = [
}
},
{
default: () => $gettext('Logs'),
icon: renderIcon('material-symbols:visibility', { size: 14 })
default: () => $gettext('Logs')
}
)
: null,
@@ -95,8 +94,7 @@ const columns: any = [
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
default: () => $gettext('Delete')
}
)
}

View File

@@ -184,7 +184,6 @@ const onCreateListen = () => {
<n-popconfirm v-if="current === 'config'" @positive-click="handleReset">
<template #trigger>
<n-button type="success">
<the-icon :size="18" icon="material-symbols:refresh" />
{{ $gettext('Reset Configuration') }}
</n-button>
</template>
@@ -196,7 +195,6 @@ const onCreateListen = () => {
type="success"
@click="proxyBuilderModal = true"
>
<the-icon :size="18" icon="material-symbols:build-outline-rounded" />
{{ $gettext('Generate Reverse Proxy Configuration') }}
</n-button>
<n-button
@@ -207,17 +205,14 @@ const onCreateListen = () => {
type="success"
@click="handleObtainCert"
>
<the-icon :size="18" icon="material-symbols:done-rounded" />
{{ $gettext('One-click Certificate Issuance') }}
</n-button>
<n-button v-if="current !== 'log'" class="ml-16" type="primary" @click="handleSave">
<the-icon :size="18" icon="material-symbols:save-outline" />
{{ $gettext('Save') }}
</n-button>
<n-popconfirm v-if="current === 'log'" @positive-click="clearLog">
<template #trigger>
<n-button type="primary">
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Clear Logs') }}
</n-button>
</template>

View File

@@ -3,612 +3,23 @@ defineOptions({
name: 'website-index'
})
import Editor from '@guolao/vue-monaco-editor'
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NPopconfirm, NSwitch, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import PhpView from '@/views/website/PhpView.vue'
import SettingView from '@/views/website/SettingView.vue'
import dashboard from '@/api/panel/dashboard'
import website from '@/api/panel/website'
import { useFileStore } from '@/store'
import { generateRandomString, isNullOrUndef, renderIcon } from '@/utils'
import BulkCreate from '@/views/website/BulkCreate.vue'
const fileStore = useFileStore()
const { $gettext } = useGettext()
const router = useRouter()
const selectedRowKeys = ref<any>([])
const columns: any = [
{ type: 'selection', fixed: 'left' },
{
title: $gettext('Website Name'),
key: 'name',
width: 200,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: $gettext('Running'),
key: 'status',
width: 150,
render(row: any) {
return h(NSwitch, {
size: 'small',
rubberBand: false,
value: row.status,
onUpdateValue: () => handleStatusChange(row)
})
}
},
{
title: $gettext('Directory'),
key: 'path',
minWidth: 200,
resizable: true,
render(row: any) {
return h(
NTag,
{
class: 'cursor-pointer hover:opacity-60',
type: 'info',
onClick: () => {
fileStore.path = row.path
router.push({ name: 'file-index' })
}
},
{ default: () => row.path }
)
}
},
{
title: 'HTTPS',
key: 'https',
width: 150,
render(row: any) {
return h(NSwitch, {
size: 'small',
rubberBand: false,
value: row.https,
onClick: () => handleEdit(row)
})
}
},
{
title: $gettext('Certificate expiration'),
key: 'cert_expire',
width: 200,
render(row: any) {
return h(
NTag,
{
type: row.cert_expire == 0 ? 'default' : row.cert_expire > 0 ? 'success' : 'error',
class: 'cursor-pointer hover:opacity-60',
onClick: () => handleEdit(row)
},
{
default: () => {
if (row.cert_expire == 0) {
return $gettext('Not configured')
}
if (row.cert_expire < 0) {
return $gettext('Expired %{ days } days ago', {
days: Math.abs(row.cert_expire)
})
}
if (row.cert_expire > 0) {
return $gettext('Expires in %{ days } days', {
days: row.cert_expire
})
}
}
}
)
}
},
{
title: $gettext('Remark'),
key: 'remark',
minWidth: 200,
resizable: true,
ellipsis: { tooltip: true },
render(row: any) {
return h(NInput, {
size: 'small',
value: row.remark,
onBlur: () => handleRemark(row),
onUpdateValue(v) {
row.remark = v
}
})
}
},
{
title: $gettext('Actions'),
key: 'actions',
width: 220,
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'primary',
style: 'margin-left: 15px;',
onClick: () => handleEdit(row)
},
{
default: () => $gettext('Edit'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
}
),
h(
NPopconfirm,
{
showIcon: false,
onPositiveClick: () => handleDelete(row.id)
},
{
default: () => {
return h(
NFlex,
{
vertical: true
},
{
default: () => [
h(
'strong',
{},
{
default: () =>
$gettext('Are you sure you want to delete website %{ name }?', {
name: row.name
})
}
),
h(
NCheckbox,
{
checked: deleteModel.value.path,
onUpdateChecked: (v) => (deleteModel.value.path = v)
},
{ default: () => $gettext('Delete website directory') }
),
h(
NCheckbox,
{
checked: deleteModel.value.db,
onUpdateChecked: (v) => (deleteModel.value.db = v)
},
{ default: () => $gettext('Delete local database with the same name') }
)
]
}
)
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const createModal = ref(false)
const bulkCreateModal = ref(false)
const editDefaultPageModal = ref(false)
const createModel = ref({
name: '',
listens: [] as Array<string>,
domains: [] as Array<string>,
path: '',
php: 0,
db: false,
db_type: '0',
db_name: '',
db_user: '',
db_password: '',
remark: ''
})
const deleteModel = ref({
path: true,
db: false
})
const editDefaultPageModel = ref({
index: '',
stop: ''
})
const { data: installedDbAndPhp } = useRequest(dashboard.installedDbAndPhp, {
initialData: {
php: [
{
label: $gettext('Not used'),
value: 0
}
],
db: [
{
label: '',
value: ''
}
]
}
})
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => website.list(page, pageSize),
{
initialData: { total: 0, list: [] },
initialPageSize: 20,
total: (res: any) => res.total,
data: (res: any) => res.items
}
)
// 修改运行状态
const handleStatusChange = (row: any) => {
if (isNullOrUndef(row.id)) return
useRequest(website.status(row.id, !row.status)).onSuccess(() => {
row.status = !row.status
if (row.status) {
window.$message.success($gettext('Started successfully'))
} else {
window.$message.success($gettext('Stopped successfully'))
}
})
}
const getDefaultPage = async () => {
editDefaultPageModel.value = await website.defaultConfig()
}
const handleRemark = (row: any) => {
useRequest(website.updateRemark(row.id, row.remark)).onSuccess(() => {
window.$message.success($gettext('Modified successfully'))
})
}
const handleEdit = (row: any) => {
router.push({
name: 'website-edit',
params: {
id: row.id
}
})
}
const handleDelete = (id: number) => {
useRequest(website.delete(id, deleteModel.value.path, deleteModel.value.db)).onSuccess(() => {
refresh()
deleteModel.value.path = true
window.$message.success($gettext('Deleted successfully'))
})
}
const handleSaveDefaultPage = () => {
useRequest(
website.saveDefaultConfig(editDefaultPageModel.value.index, editDefaultPageModel.value.stop)
).onSuccess(() => {
editDefaultPageModal.value = false
window.$message.success($gettext('Modified successfully'))
})
}
const handleCreate = async () => {
// 去除空的域名和端口
createModel.value.domains = createModel.value.domains.filter((item) => item !== '')
createModel.value.listens = createModel.value.listens.filter((item) => item !== '')
// 端口为空自动添加 80 端口
if (createModel.value.listens.length === 0) {
createModel.value.listens.push('80')
}
// 端口中去掉 443 端口nginx 不允许在未配置证书下监听 443 端口
createModel.value.listens = createModel.value.listens.filter((item) => item !== '443')
useRequest(website.create(createModel.value)).onSuccess(() => {
refresh()
window.$message.success(
$gettext('Website %{ name } created successfully', { name: createModel.value.name })
)
createModal.value = false
createModel.value = {
name: '',
domains: [] as Array<string>,
listens: [] as Array<string>,
php: 0,
db: false,
db_type: '0',
db_name: '',
db_user: '',
db_password: '',
path: '',
remark: ''
}
})
}
const bulkDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info($gettext('Please select the websites to delete'))
return
}
const promises = selectedRowKeys.value.map((id: any) => website.delete(id, true, false))
await Promise.all(promises)
selectedRowKeys.value = []
refresh()
window.$message.success($gettext('Deleted successfully'))
}
const formatDbValue = (value: string) => {
value = value.replace(/\./g, '_')
value = value.replace(/-/g, '_')
if (value.length > 16) {
value = value.substring(0, 16)
}
return value
}
onMounted(() => {
refresh()
getDefaultPage()
window.$bus.on('website:refresh', refresh)
})
const currentTab = ref('proxy')
</script>
<template>
<common-page show-footer>
<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') }}
</n-button>
<n-popconfirm @positive-click="bulkDelete">
<template #trigger>
<n-button type="error">
<the-icon :size="18" icon="material-symbols:delete-outline" />
{{ $gettext('Batch Delete') }}
</n-button>
</template>
{{
$gettext(
'This will delete the website directory but not the database with the same name. Are you sure you want to delete the selected websites?'
)
}}
</n-popconfirm>
</n-flex>
<n-data-table
striped
remote
:loading="loading"
:scroll-x="1400"
:columns="columns"
:data="data"
:row-key="(row: any) => row.id"
v-model:checked-row-keys="selectedRowKeys"
v-model:page="page"
v-model:pageSize="pageSize"
:pagination="{
page: page,
pageCount: pageCount,
pageSize: pageSize,
itemCount: total,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [20, 50, 100, 200]
}"
/>
</n-flex>
<common-page show-header show-footer>
<template #tabbar>
<n-tabs v-model:value="currentTab" animated>
<n-tab name="proxy" :tab="$gettext('Reverse Proxy')" />
<n-tab name="php" :tab="$gettext('Classic PHP')" />
<n-tab name="static" :tab="$gettext('Pure Static')" />
<n-tab name="setting" :tab="$gettext('Settings')" />
</n-tabs>
</template>
<php-view v-if="currentTab === 'php'" />
<setting-view v-if="currentTab === 'setting'" />
</common-page>
<n-modal
v-model:show="createModal"
:title="$gettext('Create Website')"
preset="card"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="createModal = false"
>
<n-form :model="createModel">
<n-form-item path="name" :label="$gettext('Website Name')">
<n-input
v-model:value="createModel.name"
type="text"
@keydown.enter.prevent
:placeholder="
$gettext(
'Recommended to use English for the website name, it cannot be modified after setting'
)
"
/>
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="11">
<n-form-item :label="$gettext('Domain')">
<n-dynamic-input
v-model:value="createModel.domains"
placeholder="example.com"
:min="1"
show-sort-button
/>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="11">
<n-form-item :label="$gettext('Port')">
<n-dynamic-input
v-model:value="createModel.listens"
placeholder="80"
:min="1"
show-sort-button
/>
</n-form-item>
</n-col>
</n-row>
<n-row :gutter="[0, 24]">
<n-col :span="11">
<n-form-item path="php" :label="$gettext('PHP Version')">
<n-select
v-model:value="createModel.php"
:options="installedDbAndPhp.php"
:placeholder="$gettext('Select PHP Version')"
@keydown.enter.prevent
>
</n-select>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="11">
<n-form-item path="db" :label="$gettext('Database')">
<n-select
v-model:value="createModel.db_type"
:options="installedDbAndPhp.db"
:placeholder="$gettext('Select Database')"
@keydown.enter.prevent
@update:value="
() => {
createModel.db = createModel.db_type != '0'
createModel.db_name = formatDbValue(createModel.name)
createModel.db_user = formatDbValue(createModel.name)
createModel.db_password = generateRandomString(16)
}
"
>
</n-select>
</n-form-item>
</n-col>
</n-row>
<n-row :gutter="[0, 24]">
<n-col :span="7">
<n-form-item v-if="createModel.db" path="db_name" :label="$gettext('Database Name')">
<n-input
v-model:value="createModel.db_name"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database Name')"
/>
</n-form-item>
</n-col>
<n-col :span="1"></n-col>
<n-col :span="7">
<n-form-item v-if="createModel.db" path="db_user" :label="$gettext('Database User')">
<n-input
v-model:value="createModel.db_user"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database User')"
/>
</n-form-item>
</n-col>
<n-col :span="1"></n-col>
<n-col :span="8">
<n-form-item
v-if="createModel.db"
path="db_password"
:label="$gettext('Database Password')"
>
<n-input
v-model:value="createModel.db_password"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database Password')"
/>
</n-form-item>
</n-col>
</n-row>
<n-form-item path="path" :label="$gettext('Directory')">
<n-input
v-model:value="createModel.path"
type="text"
@keydown.enter.prevent
:placeholder="
$gettext(
'Website root directory (if left empty, defaults to website directory/website name)'
)
"
/>
</n-form-item>
<n-form-item path="remark" :label="$gettext('Remark')">
<n-input
v-model:value="createModel.remark"
type="textarea"
@keydown.enter.prevent
:placeholder="$gettext('Remark')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">
{{ $gettext('Create') }}
</n-button>
</n-modal>
<n-modal
v-model:show="editDefaultPageModal"
preset="card"
:title="$gettext('Modify Default Page')"
style="width: 80vw"
size="huge"
:bordered="false"
:segmented="false"
@close="handleSaveDefaultPage"
>
<n-tabs type="line" animated>
<n-tab-pane :name="$gettext('Default Page')" :tab="$gettext('Default Page')">
<Editor
v-model:value="editDefaultPageModel.index"
language="html"
theme="vs-dark"
height="60vh"
mt-8
:options="{
automaticLayout: true,
formatOnType: true,
formatOnPaste: true
}"
/>
</n-tab-pane>
<n-tab-pane :name="$gettext('Stop Page')" :tab="$gettext('Stop Page')">
<Editor
v-model:value="editDefaultPageModel.stop"
language="html"
theme="vs-dark"
height="60vh"
mt-8
:options="{
automaticLayout: true,
formatOnType: true,
formatOnPaste: true
}"
/>
</n-tab-pane>
</n-tabs>
</n-modal>
<bulk-create v-model:show="bulkCreateModal" />
</template>

View File

@@ -0,0 +1,538 @@
<script lang="ts" setup>
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NPopconfirm, NSwitch, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import dashboard from '@/api/panel/dashboard'
import website from '@/api/panel/website'
import { useFileStore } from '@/store'
import { generateRandomString, isNullOrUndef } from '@/utils'
import BulkCreate from '@/views/website/BulkCreate.vue'
const fileStore = useFileStore()
const { $gettext } = useGettext()
const router = useRouter()
const selectedRowKeys = ref<any>([])
const columns: any = [
{ type: 'selection', fixed: 'left' },
{
title: $gettext('Website Name'),
key: 'name',
width: 200,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: $gettext('Running'),
key: 'status',
width: 150,
render(row: any) {
return h(NSwitch, {
size: 'small',
rubberBand: false,
value: row.status,
onUpdateValue: () => handleStatusChange(row)
})
}
},
{
title: $gettext('Directory'),
key: 'path',
minWidth: 200,
resizable: true,
render(row: any) {
return h(
NTag,
{
class: 'cursor-pointer hover:opacity-60',
type: 'info',
onClick: () => {
fileStore.path = row.path
router.push({ name: 'file-index' })
}
},
{ default: () => row.path }
)
}
},
{
title: 'HTTPS',
key: 'https',
width: 150,
render(row: any) {
return h(NSwitch, {
size: 'small',
rubberBand: false,
value: row.https,
onClick: () => handleEdit(row)
})
}
},
{
title: $gettext('Certificate expiration'),
key: 'cert_expire',
width: 200,
render(row: any) {
return h(
NTag,
{
type: row.cert_expire == 0 ? 'default' : row.cert_expire > 0 ? 'success' : 'error',
class: 'cursor-pointer hover:opacity-60',
onClick: () => handleEdit(row)
},
{
default: () => {
if (row.cert_expire == 0) {
return $gettext('Not configured')
}
if (row.cert_expire < 0) {
return $gettext('Expired %{ days } days ago', {
days: Math.abs(row.cert_expire)
})
}
if (row.cert_expire > 0) {
return $gettext('Expires in %{ days } days', {
days: row.cert_expire
})
}
}
}
)
}
},
{
title: $gettext('Remark'),
key: 'remark',
minWidth: 200,
resizable: true,
ellipsis: { tooltip: true },
render(row: any) {
return h(NInput, {
size: 'small',
value: row.remark,
onBlur: () => handleRemark(row),
onUpdateValue(v) {
row.remark = v
}
})
}
},
{
title: $gettext('Actions'),
key: 'actions',
width: 220,
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'primary',
style: 'margin-left: 15px;',
onClick: () => handleEdit(row)
},
{
default: () => $gettext('Edit')
}
),
h(
NPopconfirm,
{
showIcon: false,
onPositiveClick: () => handleDelete(row.id)
},
{
default: () => {
return h(
NFlex,
{
vertical: true
},
{
default: () => [
h(
'strong',
{},
{
default: () =>
$gettext('Are you sure you want to delete website %{ name }?', {
name: row.name
})
}
),
h(
NCheckbox,
{
checked: deleteModel.value.path,
onUpdateChecked: (v) => (deleteModel.value.path = v)
},
{ default: () => $gettext('Delete website directory') }
),
h(
NCheckbox,
{
checked: deleteModel.value.db,
onUpdateChecked: (v) => (deleteModel.value.db = v)
},
{ default: () => $gettext('Delete local database with the same name') }
)
]
}
)
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete')
}
)
}
}
)
]
}
}
]
const createModal = ref(false)
const bulkCreateModal = ref(false)
const createModel = ref({
name: '',
listens: [] as Array<string>,
domains: [] as Array<string>,
path: '',
php: 0,
db: false,
db_type: '0',
db_name: '',
db_user: '',
db_password: '',
remark: ''
})
const deleteModel = ref({
path: true,
db: false
})
const { data: installedDbAndPhp } = useRequest(dashboard.installedDbAndPhp, {
initialData: {
php: [
{
label: $gettext('Not used'),
value: 0
}
],
db: [
{
label: '',
value: ''
}
]
}
})
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => website.list(page, pageSize),
{
initialData: { total: 0, list: [] },
initialPageSize: 20,
total: (res: any) => res.total,
data: (res: any) => res.items
}
)
// 修改运行状态
const handleStatusChange = (row: any) => {
if (isNullOrUndef(row.id)) return
useRequest(website.status(row.id, !row.status)).onSuccess(() => {
row.status = !row.status
if (row.status) {
window.$message.success($gettext('Started successfully'))
} else {
window.$message.success($gettext('Stopped successfully'))
}
})
}
const handleRemark = (row: any) => {
useRequest(website.updateRemark(row.id, row.remark)).onSuccess(() => {
window.$message.success($gettext('Modified successfully'))
})
}
const handleEdit = (row: any) => {
router.push({
name: 'website-edit',
params: {
id: row.id
}
})
}
const handleDelete = (id: number) => {
useRequest(website.delete(id, deleteModel.value.path, deleteModel.value.db)).onSuccess(() => {
refresh()
deleteModel.value.path = true
window.$message.success($gettext('Deleted successfully'))
})
}
const handleCreate = async () => {
// 去除空的域名和端口
createModel.value.domains = createModel.value.domains.filter((item) => item !== '')
createModel.value.listens = createModel.value.listens.filter((item) => item !== '')
// 端口为空自动添加 80 端口
if (createModel.value.listens.length === 0) {
createModel.value.listens.push('80')
}
// 端口中去掉 443 端口nginx 不允许在未配置证书下监听 443 端口
createModel.value.listens = createModel.value.listens.filter((item) => item !== '443')
useRequest(website.create(createModel.value)).onSuccess(() => {
refresh()
window.$message.success(
$gettext('Website %{ name } created successfully', { name: createModel.value.name })
)
createModal.value = false
createModel.value = {
name: '',
domains: [] as Array<string>,
listens: [] as Array<string>,
php: 0,
db: false,
db_type: '0',
db_name: '',
db_user: '',
db_password: '',
path: '',
remark: ''
}
})
}
const bulkDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info($gettext('Please select the websites to delete'))
return
}
const promises = selectedRowKeys.value.map((id: any) => website.delete(id, true, false))
await Promise.all(promises)
selectedRowKeys.value = []
refresh()
window.$message.success($gettext('Deleted successfully'))
}
const formatDbValue = (value: string) => {
value = value.replace(/\./g, '_')
value = value.replace(/-/g, '_')
if (value.length > 16) {
value = value.substring(0, 16)
}
return value
}
onMounted(() => {
refresh()
window.$bus.on('website:refresh', refresh)
})
</script>
<template>
<n-flex vertical>
<n-flex>
<n-button type="primary" @click="createModal = true">
{{ $gettext('Create Website') }}
</n-button>
<n-button type="primary" @click="bulkCreateModal = true">
{{ $gettext('Bulk Create Website') }}
</n-button>
<n-popconfirm @positive-click="bulkDelete">
<template #trigger>
<n-button type="error">
{{ $gettext('Batch Delete') }}
</n-button>
</template>
{{
$gettext(
'This will delete the website directory but not the database with the same name. Are you sure you want to delete the selected websites?'
)
}}
</n-popconfirm>
</n-flex>
<n-data-table
striped
remote
:loading="loading"
:scroll-x="1400"
:columns="columns"
:data="data"
:row-key="(row: any) => row.id"
v-model:checked-row-keys="selectedRowKeys"
v-model:page="page"
v-model:pageSize="pageSize"
:pagination="{
page: page,
pageCount: pageCount,
pageSize: pageSize,
itemCount: total,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [20, 50, 100, 200]
}"
/>
</n-flex>
<n-modal
v-model:show="createModal"
:title="$gettext('Create Website')"
preset="card"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="createModal = false"
>
<n-form :model="createModel">
<n-form-item path="name" :label="$gettext('Website Name')">
<n-input
v-model:value="createModel.name"
type="text"
@keydown.enter.prevent
:placeholder="
$gettext(
'Recommended to use English for the website name, it cannot be modified after setting'
)
"
/>
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="11">
<n-form-item :label="$gettext('Domain')">
<n-dynamic-input
v-model:value="createModel.domains"
placeholder="example.com"
:min="1"
show-sort-button
/>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="11">
<n-form-item :label="$gettext('Port')">
<n-dynamic-input
v-model:value="createModel.listens"
placeholder="80"
:min="1"
show-sort-button
/>
</n-form-item>
</n-col>
</n-row>
<n-row :gutter="[0, 24]">
<n-col :span="11">
<n-form-item path="php" :label="$gettext('PHP Version')">
<n-select
v-model:value="createModel.php"
:options="installedDbAndPhp.php"
:placeholder="$gettext('Select PHP Version')"
@keydown.enter.prevent
>
</n-select>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="11">
<n-form-item path="db" :label="$gettext('Database')">
<n-select
v-model:value="createModel.db_type"
:options="installedDbAndPhp.db"
:placeholder="$gettext('Select Database')"
@keydown.enter.prevent
@update:value="
() => {
createModel.db = createModel.db_type != '0'
createModel.db_name = formatDbValue(createModel.name)
createModel.db_user = formatDbValue(createModel.name)
createModel.db_password = generateRandomString(16)
}
"
>
</n-select>
</n-form-item>
</n-col>
</n-row>
<n-row :gutter="[0, 24]">
<n-col :span="7">
<n-form-item v-if="createModel.db" path="db_name" :label="$gettext('Database Name')">
<n-input
v-model:value="createModel.db_name"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database Name')"
/>
</n-form-item>
</n-col>
<n-col :span="1"></n-col>
<n-col :span="7">
<n-form-item v-if="createModel.db" path="db_user" :label="$gettext('Database User')">
<n-input
v-model:value="createModel.db_user"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database User')"
/>
</n-form-item>
</n-col>
<n-col :span="1"></n-col>
<n-col :span="8">
<n-form-item
v-if="createModel.db"
path="db_password"
:label="$gettext('Database Password')"
>
<n-input
v-model:value="createModel.db_password"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Database Password')"
/>
</n-form-item>
</n-col>
</n-row>
<n-form-item path="path" :label="$gettext('Directory')">
<n-input
v-model:value="createModel.path"
type="text"
@keydown.enter.prevent
:placeholder="
$gettext(
'Website root directory (if left empty, defaults to website directory/website name)'
)
"
/>
</n-form-item>
<n-form-item path="remark" :label="$gettext('Remark')">
<n-input
v-model:value="createModel.remark"
type="textarea"
@keydown.enter.prevent
:placeholder="$gettext('Remark')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">
{{ $gettext('Create') }}
</n-button>
</n-modal>
<bulk-create v-model:show="bulkCreateModal" />
</template>

View File

@@ -0,0 +1,117 @@
<script setup lang="ts">
import website from '@/api/panel/website'
import Editor from '@guolao/vue-monaco-editor'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const currentTab = ref('default-page')
const defaultPageModel = ref({
index: '',
not_found: '',
stop: ''
})
const defaultSettingModel = ref({
tls_version: ['TLSv1.2', 'TLSv1.3'],
cipher_suites: ''
})
const getDefaultPage = async () => {
defaultPageModel.value = await website.defaultConfig()
}
const handleSaveDefaultPage = () => {
useRequest(
website.saveDefaultConfig(defaultPageModel.value.index, defaultPageModel.value.stop)
).onSuccess(() => {
window.$message.success($gettext('Modified successfully'))
})
}
onMounted(() => {
getDefaultPage()
})
</script>
<template>
<n-tabs v-model:value="currentTab" type="line" placement="left" animated>
<n-tab-pane name="default-page" :tab="$gettext('Default Page')">
<Editor
v-model:value="defaultPageModel.index"
language="html"
theme="vs-dark"
height="60vh"
mt-8
:options="{
automaticLayout: true,
formatOnType: true,
formatOnPaste: true
}"
/>
</n-tab-pane>
<n-tab-pane name="404-page" :tab="$gettext('404 Page')">
<Editor
v-model:value="defaultPageModel.not_found"
language="html"
theme="vs-dark"
height="60vh"
mt-8
:options="{
automaticLayout: true,
formatOnType: true,
formatOnPaste: true
}"
/>
</n-tab-pane>
<n-tab-pane name="stop-page" :tab="$gettext('Stop Page')">
<Editor
v-model:value="defaultPageModel.stop"
language="html"
theme="vs-dark"
height="60vh"
mt-8
:options="{
automaticLayout: true,
formatOnType: true,
formatOnPaste: true
}"
/>
</n-tab-pane>
<n-tab-pane name="default-site" :tab="$gettext('Default Site')">
<n-alert type="info">待开发</n-alert>
</n-tab-pane>
<n-tab-pane name="default-setting" :tab="$gettext('Default Settings')">
<n-form>
<n-form-item :label="$gettext('Default TLS Version')">
<n-select
v-model:value="defaultSettingModel.tls_version"
:options="[
{ label: 'TLS 1.0', value: 'TLSv1.0' },
{ label: 'TLS 1.1', value: 'TLSv1.1' },
{ label: 'TLS 1.2', value: 'TLSv1.2' },
{ label: 'TLS 1.3', value: 'TLSv1.3' }
]"
multiple
/>
</n-form-item>
<n-form-item :label="$gettext('Default Cipher Suites')">
<n-input
type="textarea"
v-model:value="defaultSettingModel.cipher_suites"
:placeholder="
$gettext('Enter the default cipher suite, leave blank to reset to default')
"
rows="4"
/>
</n-form-item>
<n-button type="primary">
{{ $gettext('Save Changes') }}
</n-button>
</n-form>
</n-tab-pane>
</n-tabs>
</template>
<style scoped lang="scss"></style>