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

fix: remove database app manage page

This commit is contained in:
耗子
2024-10-11 03:29:45 +08:00
parent 07bb0961da
commit 6738f3e83d
18 changed files with 63 additions and 1415 deletions

View File

@@ -40,7 +40,7 @@ func (r *cronRepo) Count() (int64, error) {
func (r *cronRepo) List(page, limit uint) ([]*biz.Cron, int64, error) {
var cron []*biz.Cron
var total int64
err := app.Orm.Model(&biz.Cert{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&cron).Error
err := app.Orm.Model(&biz.Cron{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&cron).Error
return cron, total, err
}

View File

@@ -2,13 +2,13 @@ package service
import (
"fmt"
"github.com/spf13/cast"
"net/http"
"regexp"
"strings"
"github.com/go-rat/chix"
"github.com/hashicorp/go-version"
"github.com/spf13/cast"
"github.com/TheTNB/panel/internal/app"
"github.com/TheTNB/panel/internal/biz"

View File

@@ -22,43 +22,5 @@ export default {
rootPassword: (): Promise<AxiosResponse<any>> => request.get('/apps/mysql/rootPassword'),
// 修改 root 密码
setRootPassword: (password: string): Promise<AxiosResponse<any>> =>
request.post('/apps/mysql/rootPassword', { password }),
// 数据库列表
databases: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/apps/mysql/databases', { params: { page, limit } }),
// 创建数据库
addDatabase: (database: any): Promise<AxiosResponse<any>> =>
request.post('/apps/mysql/databases', database),
// 删除数据库
deleteDatabase: (database: string): Promise<AxiosResponse<any>> =>
request.delete('/apps/mysql/databases', { params: { database } }),
// 备份列表
backups: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/apps/mysql/backups', { params: { page, limit } }),
// 创建备份
createBackup: (database: string): Promise<AxiosResponse<any>> =>
request.post('/apps/mysql/backups', { database }),
// 上传备份
uploadBackup: (backup: any): Promise<AxiosResponse<any>> =>
request.put('/apps/mysql/backups', backup),
// 删除备份
deleteBackup: (name: string): Promise<AxiosResponse<any>> =>
request.delete('/apps/mysql/backups', { params: { name } }),
// 还原备份
restoreBackup: (backup: string, database: string): Promise<AxiosResponse<any>> =>
request.post('/apps/mysql/backups/restore', { backup, database }),
// 用户列表
users: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/apps/mysql/users', { params: { page, limit } }),
// 创建用户
addUser: (user: any): Promise<AxiosResponse<any>> => request.post('/apps/mysql/users', user),
// 删除用户
deleteUser: (user: string): Promise<AxiosResponse<any>> =>
request.delete('/apps/mysql/users', { params: { user } }),
// 设置用户密码
setUserPassword: (user: string, password: string): Promise<AxiosResponse<any>> =>
request.post('/apps/mysql/users/password', { user, password }),
// 设置用户权限
setUserPrivileges: (user: string, database: string): Promise<AxiosResponse<any>> =>
request.post('/apps/mysql/users/privileges', { user, database })
request.post('/apps/mysql/rootPassword', { password })
}

View File

@@ -18,40 +18,5 @@ export default {
// 获取日志
log: (): Promise<AxiosResponse<any>> => request.get('/apps/postgresql/log'),
// 清空错误日志
clearLog: (): Promise<AxiosResponse<any>> => request.post('/apps/postgresql/clearLog'),
// 数据库列表
databases: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/apps/postgresql/databases', { params: { page, limit } }),
// 创建数据库
addDatabase: (database: any): Promise<AxiosResponse<any>> =>
request.post('/apps/postgresql/databases', database),
// 删除数据库
deleteDatabase: (database: string): Promise<AxiosResponse<any>> =>
request.delete('/apps/postgresql/databases', { params: { database } }),
// 备份列表
backups: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/apps/postgresql/backups', { params: { page, limit } }),
// 创建备份
createBackup: (database: string): Promise<AxiosResponse<any>> =>
request.post('/apps/postgresql/backups', { database }),
// 上传备份
uploadBackup: (backup: any): Promise<AxiosResponse<any>> =>
request.put('/apps/postgresql/backups', backup),
// 删除备份
deleteBackup: (name: string): Promise<AxiosResponse<any>> =>
request.delete('/apps/postgresql/backups', { params: { name } }),
// 还原备份
restoreBackup: (backup: string, database: string): Promise<AxiosResponse<any>> =>
request.post('/apps/postgresql/backups/restore', { backup, database }),
// 角色列表
roles: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/apps/postgresql/roles', { params: { page, limit } }),
// 创建角色
addRole: (user: any): Promise<AxiosResponse<any>> => request.post('/apps/postgresql/roles', user),
// 删除角色
deleteRole: (user: string): Promise<AxiosResponse<any>> =>
request.delete('/apps/postgresql/roles', { params: { user } }),
// 设置角色密码
setRolePassword: (user: string, password: string): Promise<AxiosResponse<any>> =>
request.post('/apps/postgresql/roles/password', { user, password })
clearLog: (): Promise<AxiosResponse<any>> => request.post('/apps/postgresql/clearLog')
}

View File

@@ -5,19 +5,19 @@ import { request } from '@/utils'
export default {
// 获取任务列表
list: (page: number, limit: number): Promise<AxiosResponse<any>> =>
request.get('/cron/list', { params: { page, limit } }),
request.get('/cron', { params: { page, limit } }),
// 获取任务脚本
script: (id: number): Promise<AxiosResponse<any>> => request.get('/cron/' + id),
// 添加任务
add: (task: any): Promise<AxiosResponse<any>> => request.post('/cron/add', task),
get: (id: number): Promise<AxiosResponse<any>> => request.get('/cron/' + id),
// 创建任务
create: (task: any): Promise<AxiosResponse<any>> => request.post('/cron', task),
// 修改任务
update: (id: number, name: string, time: string, script: string): Promise<AxiosResponse<any>> =>
request.put('/cron/' + id, { name, time, script }),
// 删除任务
delete: (id: number): Promise<AxiosResponse<any>> => request.delete('/cron/' + id),
// 获取任务日志
log: (id: number): Promise<AxiosResponse<any>> => request.get('/cron/log/' + id),
log: (id: number): Promise<AxiosResponse<any>> => request.get('/cron/' + id + '/log'),
// 修改任务状态
status: (id: number, status: boolean): Promise<AxiosResponse<any>> =>
request.post('/cron/status', { id, status })
request.post('/cron/' + id + '/status', { status })
}

View File

@@ -348,8 +348,8 @@
"actions": "操作"
},
"create": {
"trigger": "建网站",
"title": "建网站",
"trigger": "建网站",
"title": "建网站",
"fields": {
"name": {
"label": "网站名",

View File

@@ -1,17 +1,11 @@
<script setup lang="ts">
import Editor from '@guolao/vue-monaco-editor'
import type { MessageReactive, UploadFileInfo } from 'naive-ui'
import { NButton, NDataTable, NInput, NPopconfirm } from 'naive-ui'
import mysql from '@/api/apps/mysql'
import systemctl from '@/api/panel/systemctl'
import { generateRandomString, renderIcon } from '@/utils'
import type { Backup, Database, User } from '@/views/apps/mysql/types'
let messageReactive: MessageReactive | null = null
const currentTab = ref('status')
const currentDatabase = ref('')
const status = ref(false)
const isEnabled = ref(false)
const config = ref('')
@@ -26,339 +20,18 @@ const statusStr = computed(() => {
return status.value ? '正常运行中' : '已停止运行'
})
const addDatabaseModel = ref({
database: '',
user: '',
password: generateRandomString(16)
})
const addUserModel = ref({
database: '',
user: '',
password: generateRandomString(16)
})
const changePasswordModel = ref({
user: '',
password: generateRandomString(16)
})
const changePrivilegesModel = ref({
user: '',
database: ''
})
const addDatabaseModal = ref(false)
const addUserModal = ref(false)
const changePasswordModal = ref(false)
const changePrivilegesModal = ref(false)
const backupModal = ref(false)
const databaseColumns: any = [
{ title: '库名', key: 'name', fixed: 'left', resizable: true, ellipsis: { tooltip: true } },
{
title: '操作',
key: 'actions',
width: 240,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'warning',
secondary: true,
onClick: () => {
currentDatabase.value = row.name
backupModal.value = true
}
},
{
default: () => '备份',
icon: renderIcon('material-symbols:save-outline', { size: 14 })
}
),
h(
NPopconfirm,
{
onPositiveClick: () => handleDeleteDatabase(row.name)
},
{
default: () => {
return '确定删除数据库吗?'
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const userColumns: any = [
{ title: '用户名', key: 'user', fixed: 'left', resizable: true, ellipsis: { tooltip: true } },
{ title: '主机', key: 'host', resizable: true, ellipsis: { tooltip: true } },
{ title: '权限', key: 'grants', width: 350, resizable: true, ellipsis: { tooltip: true } },
{
title: '操作',
key: 'actions',
width: 300,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'warning',
secondary: true,
onClick: () => showChangePasswordModal(row.user)
},
{
default: () => '改密',
icon: renderIcon('majesticons:key-line', { size: 14 })
}
),
h(
NButton,
{
size: 'small',
type: 'warning',
secondary: true,
style: 'margin-left: 15px;',
onClick: () => showChangePrivilegesModal(row.user)
},
{
default: () => '权限',
icon: renderIcon('majesticons:lock-line', { size: 14 })
}
),
h(
NPopconfirm,
{
onPositiveClick: () => handleDeleteUser(row.user)
},
{
default: () => {
return '确定删除用户吗?'
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const loadColumns: any = [
{ title: '属性', key: 'name', fixed: 'left', resizable: true, ellipsis: { tooltip: true } },
{ title: '当前值', key: 'value', width: 200, ellipsis: { tooltip: true } }
]
const backupColumns: any = [
{ title: '文件名', key: 'name', fixed: 'left', resizable: true, ellipsis: { tooltip: true } },
{ title: '大小', key: 'size', width: 200, ellipsis: { tooltip: true } },
{
title: '操作',
key: 'actions',
width: 200,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'warning',
secondary: true,
onClick: () => handleRestoreBackup(row)
},
{
default: () => '恢复',
icon: renderIcon('material-symbols:settings-backup-restore-rounded', { size: 14 })
}
),
h(
NPopconfirm,
{
onPositiveClick: () => handleDeleteBackup(row.name)
},
{
default: () => {
return '确定删除备份吗?'
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const databases = ref<Database[]>([] as Database[])
const users = ref<User[]>([] as User[])
const backup = ref<Backup[]>([])
const load = ref<any[]>([])
const databasePagination = reactive({
page: 1,
pageCount: 1,
pageSize: 10,
itemCount: 0,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [10, 20, 50, 100]
})
const userPagination = reactive({
page: 1,
pageCount: 1,
pageSize: 10,
itemCount: 0,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [10, 20, 50, 100]
})
const backupPagination = reactive({
page: 1,
pageCount: 1,
pageSize: 10,
itemCount: 0,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [10, 20, 50, 100]
})
const getLoad = async () => {
const { data } = await mysql.load()
return data
}
const getDatabaseList = async (page: number, limit: number) => {
const { data } = await mysql.databases(page, limit)
return data
}
const getUserList = async (page: number, limit: number) => {
const { data } = await mysql.users(page, limit)
return data
}
const getBackupList = async (page: number, limit: number) => {
const { data } = await mysql.backups(page, limit)
return data
}
const onDatabasePageChange = (page: number) => {
databasePagination.page = page
getDatabaseList(page, databasePagination.pageSize).then((res) => {
databases.value = res.items
databasePagination.itemCount = res.total
databasePagination.pageCount = res.total / databasePagination.pageSize + 1
})
}
const onUserPageChange = (page: number) => {
userPagination.page = page
getUserList(page, userPagination.pageSize).then((res) => {
users.value = res.items
userPagination.itemCount = res.total
userPagination.pageCount = res.total / userPagination.pageSize + 1
})
}
const onBackupPageChange = (page: number) => {
backupPagination.page = page
getBackupList(page, backupPagination.pageSize).then((res) => {
backup.value = res.items
backupPagination.itemCount = res.total
backupPagination.pageCount = res.total / backupPagination.pageSize + 1
})
}
const onDatabasePageSizeChange = (pageSize: number) => {
databasePagination.pageSize = pageSize
onDatabasePageChange(1)
}
const onUserPageSizeChange = (pageSize: number) => {
userPagination.pageSize = pageSize
onUserPageChange(1)
}
const onBackupPageSizeChange = (pageSize: number) => {
backupPagination.pageSize = pageSize
onBackupPageChange(1)
}
const handleDeleteDatabase = async (name: string) => {
mysql.deleteDatabase(name).then(() => {
window.$message.success('删除成功')
onDatabasePageChange(databasePagination.page)
})
}
const handleDeleteUser = async (user: string) => {
mysql.deleteUser(user).then(() => {
window.$message.success('删除成功')
onUserPageChange(userPagination.page)
})
}
const showChangePasswordModal = (user: string) => {
changePasswordModel.value.user = user
changePasswordModal.value = true
}
const showChangePrivilegesModal = (user: string) => {
changePrivilegesModel.value.user = user
changePrivilegesModal.value = true
}
const getIsEnabled = async () => {
await systemctl.isEnabled('mysqld').then((res: any) => {
isEnabled.value = res.data
@@ -453,113 +126,10 @@ const handleSetRootPassword = async () => {
window.$message.success('修改成功')
}
const handleAddDatabase = async () => {
mysql.addDatabase(addDatabaseModel.value).then(() => {
window.$message.success('添加成功')
addDatabaseModal.value = false
addDatabaseModel.value = {
database: '',
user: '',
password: generateRandomString(16)
}
onDatabasePageChange(databasePagination.page)
onUserPageChange(userPagination.page)
})
}
const handleAddUser = async () => {
mysql.addUser(addUserModel.value).then(() => {
window.$message.success('添加成功')
addUserModal.value = false
addDatabaseModel.value = {
user: '',
password: generateRandomString(16),
database: ''
}
onUserPageChange(userPagination.page)
})
}
const handleChangePassword = async () => {
mysql
.setUserPassword(changePasswordModel.value.user, changePasswordModel.value.password)
.then(() => {
window.$message.success('修改成功')
changePasswordModal.value = false
changePasswordModel.value = {
user: '',
password: generateRandomString(16)
}
onUserPageChange(userPagination.page)
})
}
const handleChangePrivileges = async () => {
mysql
.setUserPrivileges(changePrivilegesModel.value.user, changePrivilegesModel.value.database)
.then(() => {
window.$message.success('修改成功')
changePrivilegesModal.value = false
changePrivilegesModel.value = {
user: '',
database: ''
}
onUserPageChange(userPagination.page)
})
}
const handleUploadBackup = async (files: UploadFileInfo[]) => {
messageReactive = window.$message.loading('上传中...', {
duration: 0
})
for (let i = 0; i < files.length; i++) {
const file = files[i]
const formData = new FormData()
formData.append('file', file.file as Blob, file.name)
await mysql.uploadBackup(formData).then(() => {
messageReactive?.destroy()
window.$message.success('上传成功')
onBackupPageChange(backupPagination.page)
})
}
}
const handleCreateBackup = async () => {
messageReactive = window.$message.loading('创建中...', {
duration: 0
})
await mysql.createBackup(currentDatabase.value).then(() => {
messageReactive?.destroy()
window.$message.success('创建成功')
onBackupPageChange(backupPagination.page)
})
}
const handleRestoreBackup = async (row: any) => {
messageReactive = window.$message.loading('恢复中...', {
duration: 0
})
await mysql.restoreBackup(row.name, currentDatabase.value).then(() => {
messageReactive?.destroy()
window.$message.success('恢复成功')
onBackupPageChange(backupPagination.page)
})
}
const handleDeleteBackup = async (name: string) => {
await mysql.deleteBackup(name).then(() => {
window.$message.success('删除成功')
onBackupPageChange(backupPagination.page)
})
}
onMounted(() => {
getStatus()
getIsEnabled()
getRootPassword()
onDatabasePageChange(databasePagination.page)
onUserPageChange(userPagination.page)
onBackupPageChange(backupPagination.page)
getLoad().then((res) => {
load.value = res
})
@@ -578,16 +148,6 @@ onMounted(() => {
<template>
<common-page show-footer>
<template #action>
<n-space v-if="currentTab == 'manage'">
<n-button class="ml-16" type="info" @click="addUserModal = true">
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
新建用户
</n-button>
<n-button class="ml-16" type="primary" @click="addDatabaseModal = true">
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
新建数据库
</n-button>
</n-space>
<n-button
v-if="currentTab == 'config'"
class="ml-16"
@@ -671,34 +231,6 @@ onMounted(() => {
</n-card>
</n-space>
</n-tab-pane>
<n-tab-pane name="manage" tab="管理">
<n-space vertical>
<n-card title="数据库" :segmented="true" rounded-10>
<n-data-table
striped
remote
:loading="false"
:columns="databaseColumns"
:data="databases"
:row-key="(row: any) => row.name"
@update:page="onDatabasePageChange"
@update:page-size="onDatabasePageSizeChange"
/>
</n-card>
<n-card title="用户" :segmented="true" rounded-10>
<n-data-table
striped
remote
:loading="false"
:columns="userColumns"
:data="users"
:row-key="(row: any) => row.user"
@update:page="onUserPageChange"
@update:page-size="onUserPageSizeChange"
/>
</n-card>
</n-space>
</n-tab-pane>
<n-tab-pane name="config" tab="修改配置">
<n-space vertical>
<n-alert type="warning">
@@ -753,159 +285,4 @@ onMounted(() => {
</n-tab-pane>
</n-tabs>
</common-page>
<n-modal v-model:show="addDatabaseModal" title="新建数据库">
<n-card
closable
@close="() => (addDatabaseModal = false)"
title="新建数据库"
style="width: 60vw"
>
<n-form :model="addDatabaseModel">
<n-form-item path="database" label="数据库名">
<n-input
v-model:value="addDatabaseModel.database"
type="text"
@keydown.enter.prevent
placeholder="输入数据库名"
/>
</n-form-item>
<n-form-item path="user" label="用户名">
<n-input
v-model:value="addDatabaseModel.user"
type="text"
@keydown.enter.prevent
placeholder="输入用户名"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-input
v-model:value="addDatabaseModel.password"
type="text"
@keydown.enter.prevent
placeholder="建议使用生成器生成随机密码"
/>
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]">
<n-col :span="24">
<n-button type="info" block @click="handleAddDatabase">提交</n-button>
</n-col>
</n-row>
</n-card>
</n-modal>
<n-modal v-model:show="addUserModal" title="新建用户">
<n-card closable @close="() => (addUserModal = false)" title="新建用户" style="width: 60vw">
<n-form :model="addUserModel">
<n-form-item path="user" label="用户名">
<n-input
v-model:value="addUserModel.user"
type="text"
@keydown.enter.prevent
placeholder="输入用户名"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-input
v-model:value="addUserModel.password"
type="text"
@keydown.enter.prevent
placeholder="建议使用生成器生成随机密码"
/>
</n-form-item>
<n-form-item path="database" label="数据库名">
<n-input
v-model:value="addUserModel.database"
type="text"
@keydown.enter.prevent
placeholder="输入授权给该用户的数据库名"
/>
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]">
<n-col :span="24">
<n-button type="info" block @click="handleAddUser">提交</n-button>
</n-col>
</n-row>
</n-card>
</n-modal>
<n-modal v-model:show="backupModal">
<n-card
closable
@close="() => (backupModal = false)"
:title="'备份管理 - ' + currentDatabase"
style="width: 60vw"
>
<n-space vertical>
<n-space>
<n-button type="primary" @click="handleCreateBackup">创建备份</n-button>
<n-upload
accept=".sql,.zip,tar.gz,.tar,.rar,.bz2"
:default-upload="false"
:show-file-list="false"
@update:file-list="handleUploadBackup"
>
<n-button>上传备份</n-button>
</n-upload>
</n-space>
<n-data-table
striped
remote
:loading="false"
:columns="backupColumns"
:data="backup"
:row-key="(row: any) => row.name"
@update:page="onBackupPageChange"
@update:page-size="onBackupPageSizeChange"
/>
</n-space>
</n-card>
</n-modal>
<n-modal v-model:show="changePasswordModal">
<n-card
closable
@close="() => (changePasswordModal = false)"
title="修改密码"
style="width: 60vw"
>
<n-form :model="changePasswordModel">
<n-form-item path="password" label="密码">
<n-input
v-model:value="changePasswordModel.password"
type="text"
@keydown.enter.prevent
placeholder="建议使用生成器生成随机密码"
/>
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]">
<n-col :span="24">
<n-button type="info" block @click="handleChangePassword">提交</n-button>
</n-col>
</n-row>
</n-card>
</n-modal>
<n-modal v-model:show="changePrivilegesModal">
<n-card
closable
@close="() => (changePrivilegesModal = false)"
title="修改权限"
style="width: 60vw"
>
<n-form :model="changePrivilegesModel">
<n-form-item path="database" label="数据库名">
<n-input
v-model:value="changePrivilegesModel.database"
type="text"
@keydown.enter.prevent
placeholder="输入授权给该用户的数据库名"
/>
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]">
<n-col :span="24">
<n-button type="info" block @click="handleChangePrivileges">提交</n-button>
</n-col>
</n-row>
</n-card>
</n-modal>
</template>

View File

@@ -3,17 +3,17 @@ import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'mysql57',
path: '/apps/mysql57',
name: 'mysql',
path: '/apps/mysql',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-mysql57-index',
name: 'apps-mysql-index',
path: '',
component: () => import('../mysql/IndexView.vue'),
component: () => import('./IndexView.vue'),
meta: {
title: 'MySQL 5.7',
title: 'PerconaMySQL',
icon: 'mdi:database',
role: ['admin'],
requireAuth: true

View File

@@ -1,14 +0,0 @@
export interface Database {
name: string
}
export interface User {
user: string
host: string
grants: string
}
export interface Backup {
name: string
size: string
}

View File

@@ -1,23 +0,0 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'mysql84',
path: '/apps/mysql84',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-mysql84-index',
path: '',
component: () => import('../mysql/IndexView.vue'),
meta: {
title: 'MySQL 8.4',
icon: 'mdi:database',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -1,17 +1,11 @@
<script setup lang="ts">
import Editor from '@guolao/vue-monaco-editor'
import type { MessageReactive, UploadFileInfo } from 'naive-ui'
import { NButton, NDataTable, NFlex, NInput, NPopconfirm, NTag } from 'naive-ui'
import { NButton, NDataTable, NPopconfirm } from 'naive-ui'
import postgresql from '@/api/apps/postgresql'
import systemctl from '@/api/panel/systemctl'
import { generateRandomString, renderIcon } from '@/utils'
import type { Backup, Database, Role } from '@/views/apps/postgresql/types'
let messageReactive: MessageReactive | null = null
const currentTab = ref('status')
const currentDatabase = ref('')
const status = ref(false)
const isEnabled = ref(false)
const config = ref('')
@@ -25,332 +19,18 @@ const statusStr = computed(() => {
return status.value ? '正常运行中' : '已停止运行'
})
const addDatabaseModel = ref({
database: '',
user: '',
password: generateRandomString(16)
})
const addRoleModel = ref({
database: '',
user: '',
password: generateRandomString(16)
})
const changePasswordModel = ref({
user: '',
password: generateRandomString(16)
})
const addDatabaseModal = ref(false)
const addRoleModal = ref(false)
const changePasswordModal = ref(false)
const backupModal = ref(false)
const databaseColumns: any = [
{ title: '库名', key: 'name', fixed: 'left', resizable: true, ellipsis: { tooltip: true } },
{ title: '拥有者', key: 'owner', resizable: true, ellipsis: { tooltip: true } },
{ title: '编码', key: 'encoding', resizable: true, ellipsis: { tooltip: true } },
{
title: '操作',
key: 'actions',
width: 240,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'warning',
secondary: true,
onClick: () => {
currentDatabase.value = row.name
backupModal.value = true
}
},
{
default: () => '备份',
icon: renderIcon('material-symbols:save-outline', { size: 14 })
}
),
h(
NPopconfirm,
{
onPositiveClick: () => handleDeleteDatabase(row.name)
},
{
default: () => {
return '确定删除数据库吗?'
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const roleColumns: any = [
{ title: '角色名', key: 'role', fixed: 'left', resizable: true, ellipsis: { tooltip: true } },
{
title: '权限',
key: 'attributes',
resizable: true,
ellipsis: { tooltip: true },
render(row: any) {
return h(NFlex, null, {
default: () =>
row.attributes.map((perm: any) =>
h(NTag, null, {
default: () => perm
})
)
})
}
},
{
title: '操作',
key: 'actions',
width: 300,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'warning',
secondary: true,
onClick: () => showChangePasswordModal(row.user)
},
{
default: () => '改密',
icon: renderIcon('majesticons:key-line', { size: 14 })
}
),
h(
NPopconfirm,
{
onPositiveClick: () => handleDeleteRole(row.user)
},
{
default: () => {
return '确定删除角色吗?'
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const loadColumns: any = [
{ title: '属性', key: 'name', fixed: 'left', resizable: true, ellipsis: { tooltip: true } },
{ title: '当前值', key: 'value', width: 200, ellipsis: { tooltip: true } }
]
const backupColumns: any = [
{ title: '文件名', key: 'name', fixed: 'left', resizable: true, ellipsis: { tooltip: true } },
{ title: '大小', key: 'size', width: 200, ellipsis: { tooltip: true } },
{
title: '操作',
key: 'actions',
width: 200,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'warning',
secondary: true,
onClick: () => handleRestoreBackup(row)
},
{
default: () => '恢复',
icon: renderIcon('material-symbols:settings-backup-restore-rounded', { size: 14 })
}
),
h(
NPopconfirm,
{
onPositiveClick: () => handleDeleteBackup(row.name)
},
{
default: () => {
return '确定删除备份吗?'
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const databases = ref<Database[]>([] as Database[])
const roles = ref<Role[]>([] as Role[])
const backup = ref<Backup[]>([])
const load = ref<any[]>([])
const databasePagination = reactive({
page: 1,
pageCount: 1,
pageSize: 10,
itemCount: 0,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [10, 20, 50, 100]
})
const rolePagination = reactive({
page: 1,
pageCount: 1,
pageSize: 10,
itemCount: 0,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [10, 20, 50, 100]
})
const backupPagination = reactive({
page: 1,
pageCount: 1,
pageSize: 10,
itemCount: 0,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [10, 20, 50, 100]
})
const getLoad = async () => {
const { data } = await postgresql.load()
return data
}
const getDatabaseList = async (page: number, limit: number) => {
const { data } = await postgresql.databases(page, limit)
return data
}
const getRoleList = async (page: number, limit: number) => {
const { data } = await postgresql.roles(page, limit)
return data
}
const getBackupList = async (page: number, limit: number) => {
const { data } = await postgresql.backups(page, limit)
return data
}
const onDatabasePageChange = (page: number) => {
databasePagination.page = page
getDatabaseList(page, databasePagination.pageSize).then((res) => {
databases.value = res.items
databasePagination.itemCount = res.total
databasePagination.pageCount = res.total / databasePagination.pageSize + 1
})
}
const onRolePageChange = (page: number) => {
rolePagination.page = page
getRoleList(page, rolePagination.pageSize).then((res) => {
roles.value = res.items
rolePagination.itemCount = res.total
rolePagination.pageCount = res.total / rolePagination.pageSize + 1
})
}
const onBackupPageChange = (page: number) => {
backupPagination.page = page
getBackupList(page, backupPagination.pageSize).then((res) => {
backup.value = res.items
backupPagination.itemCount = res.total
backupPagination.pageCount = res.total / backupPagination.pageSize + 1
})
}
const onDatabasePageSizeChange = (pageSize: number) => {
databasePagination.pageSize = pageSize
onDatabasePageChange(1)
}
const onRolePageSizeChange = (pageSize: number) => {
rolePagination.pageSize = pageSize
onRolePageChange(1)
}
const onBackupPageSizeChange = (pageSize: number) => {
backupPagination.pageSize = pageSize
onBackupPageChange(1)
}
const handleDeleteDatabase = async (name: string) => {
postgresql.deleteDatabase(name).then(() => {
window.$message.success('删除成功')
onDatabasePageChange(databasePagination.page)
getUserConfig()
})
}
const handleDeleteRole = async (user: string) => {
postgresql.deleteRole(user).then(() => {
window.$message.success('删除成功')
onRolePageChange(rolePagination.page)
})
}
const showChangePasswordModal = (user: string) => {
changePasswordModel.value.user = user
changePasswordModal.value = true
}
const getIsEnabled = async () => {
await systemctl.isEnabled('postgresql').then((res: any) => {
isEnabled.value = res.data
@@ -433,99 +113,9 @@ const handleReload = async () => {
await getStatus()
}
const handleAddDatabase = async () => {
postgresql.addDatabase(addDatabaseModel.value).then(() => {
window.$message.success('添加成功')
addDatabaseModal.value = false
addDatabaseModel.value = {
database: '',
user: '',
password: generateRandomString(16)
}
onDatabasePageChange(databasePagination.page)
onRolePageChange(rolePagination.page)
getUserConfig()
})
}
const handleAddRole = async () => {
postgresql.addRole(addRoleModel.value).then(() => {
window.$message.success('添加成功')
addRoleModal.value = false
addDatabaseModel.value = {
user: '',
password: generateRandomString(16),
database: ''
}
onRolePageChange(rolePagination.page)
})
}
const handleChangePassword = async () => {
postgresql
.setRolePassword(changePasswordModel.value.user, changePasswordModel.value.password)
.then(() => {
window.$message.success('修改成功')
changePasswordModal.value = false
changePasswordModel.value = {
user: '',
password: generateRandomString(16)
}
onRolePageChange(rolePagination.page)
})
}
const handleUploadBackup = async (files: UploadFileInfo[]) => {
messageReactive = window.$message.loading('上传中...', {
duration: 0
})
for (let i = 0; i < files.length; i++) {
const file = files[i]
const formData = new FormData()
formData.append('file', file.file as Blob, file.name)
await postgresql.uploadBackup(formData).then(() => {
messageReactive?.destroy()
window.$message.success('上传成功')
onBackupPageChange(backupPagination.page)
})
}
}
const handleCreateBackup = async () => {
messageReactive = window.$message.loading('创建中...', {
duration: 0
})
await postgresql.createBackup(currentDatabase.value).then(() => {
messageReactive?.destroy()
window.$message.success('创建成功')
onBackupPageChange(backupPagination.page)
})
}
const handleRestoreBackup = async (row: any) => {
messageReactive = window.$message.loading('恢复中...', {
duration: 0
})
await postgresql.restoreBackup(row.name, currentDatabase.value).then(() => {
messageReactive?.destroy()
window.$message.success('恢复成功')
onBackupPageChange(backupPagination.page)
})
}
const handleDeleteBackup = async (name: string) => {
await postgresql.deleteBackup(name).then(() => {
window.$message.success('删除成功')
onBackupPageChange(backupPagination.page)
})
}
onMounted(() => {
getStatus()
getIsEnabled()
onDatabasePageChange(databasePagination.page)
onRolePageChange(rolePagination.page)
onBackupPageChange(backupPagination.page)
getLoad().then((res) => {
load.value = res
})
@@ -540,16 +130,6 @@ onMounted(() => {
<template>
<common-page show-footer>
<template #action>
<n-space v-if="currentTab == 'manage'">
<n-button class="ml-16" type="info" @click="addRoleModal = true">
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
新建角色
</n-button>
<n-button class="ml-16" type="primary" @click="addDatabaseModal = true">
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
新建数据库
</n-button>
</n-space>
<n-button
v-if="currentTab == 'config'"
class="ml-16"
@@ -622,34 +202,6 @@ onMounted(() => {
</n-card>
</n-space>
</n-tab-pane>
<n-tab-pane name="manage" tab="管理">
<n-space vertical>
<n-card title="数据库" :segmented="true" rounded-10>
<n-data-table
striped
remote
:loading="false"
:columns="databaseColumns"
:data="databases"
:row-key="(row: any) => row.name"
@update:page="onDatabasePageChange"
@update:page-size="onDatabasePageSizeChange"
/>
</n-card>
<n-card title="角色" :segmented="true" rounded-10>
<n-data-table
striped
remote
:loading="false"
:columns="roleColumns"
:data="roles"
:row-key="(row: any) => row.user"
@update:page="onRolePageChange"
@update:page-size="onRolePageSizeChange"
/>
</n-card>
</n-space>
</n-tab-pane>
<n-tab-pane name="config" tab="主配置">
<n-space vertical>
<n-alert type="warning">
@@ -708,135 +260,4 @@ onMounted(() => {
</n-tab-pane>
</n-tabs>
</common-page>
<n-modal v-model:show="addDatabaseModal" title="新建数据库">
<n-card
closable
@close="() => (addDatabaseModal = false)"
title="新建数据库"
style="width: 60vw"
>
<n-form :model="addDatabaseModel">
<n-form-item path="database" label="数据库名">
<n-input
v-model:value="addDatabaseModel.database"
type="text"
@keydown.enter.prevent
placeholder="输入数据库名"
/>
</n-form-item>
<n-form-item path="user" label="角色名">
<n-input
v-model:value="addDatabaseModel.user"
type="text"
@keydown.enter.prevent
placeholder="输入角色名"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-input
v-model:value="addDatabaseModel.password"
type="text"
@keydown.enter.prevent
placeholder="建议使用生成器生成随机密码"
/>
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]">
<n-col :span="24">
<n-button type="info" block @click="handleAddDatabase">提交</n-button>
</n-col>
</n-row>
</n-card>
</n-modal>
<n-modal v-model:show="addRoleModal" title="新建角色">
<n-card closable @close="() => (addRoleModal = false)" title="新建角色" style="width: 60vw">
<n-form :model="addRoleModel">
<n-form-item path="user" label="角色名">
<n-input
v-model:value="addRoleModel.user"
type="text"
@keydown.enter.prevent
placeholder="输入角色名"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-input
v-model:value="addRoleModel.password"
type="text"
@keydown.enter.prevent
placeholder="建议使用生成器生成随机密码"
/>
</n-form-item>
<n-form-item path="database" label="数据库名">
<n-input
v-model:value="addRoleModel.database"
type="text"
@keydown.enter.prevent
placeholder="输入授权给该角色的数据库名"
/>
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]">
<n-col :span="24">
<n-button type="info" block @click="handleAddRole">提交</n-button>
</n-col>
</n-row>
</n-card>
</n-modal>
<n-modal v-model:show="backupModal">
<n-card
closable
@close="() => (backupModal = false)"
:title="'备份管理 - ' + currentDatabase"
style="width: 60vw"
>
<n-space vertical>
<n-space>
<n-button type="primary" @click="handleCreateBackup">创建备份</n-button>
<n-upload
accept=".sql,.zip,tar.gz,.tar,.rar,.bz2"
:default-upload="false"
:show-file-list="false"
@update:file-list="handleUploadBackup"
>
<n-button>上传备份</n-button>
</n-upload>
</n-space>
<n-data-table
striped
remote
:loading="false"
:columns="backupColumns"
:data="backup"
:row-key="(row: any) => row.name"
@update:page="onBackupPageChange"
@update:page-size="onBackupPageSizeChange"
/>
</n-space>
</n-card>
</n-modal>
<n-modal v-model:show="changePasswordModal">
<n-card
closable
@close="() => (changePasswordModal = false)"
title="修改密码"
style="width: 60vw"
>
<n-form :model="changePasswordModel">
<n-form-item path="password" label="密码">
<n-input
v-model:value="changePasswordModel.password"
type="text"
@keydown.enter.prevent
placeholder="建议使用生成器生成随机密码"
/>
</n-form-item>
</n-form>
<n-row :gutter="[0, 24]">
<n-col :span="24">
<n-button type="info" block @click="handleChangePassword">提交</n-button>
</n-col>
</n-row>
</n-card>
</n-modal>
</template>

View File

@@ -3,17 +3,17 @@ import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'mysql80',
path: '/apps/mysql80',
name: 'postgresql',
path: '/apps/postgresql',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-mysql80-index',
name: 'apps-postgresql-index',
path: '',
component: () => import('../mysql/IndexView.vue'),
component: () => import('./IndexView.vue'),
meta: {
title: 'MySQL 8.0',
title: 'PostgreSQL',
icon: 'mdi:database',
role: ['admin'],
requireAuth: true

View File

@@ -1,13 +0,0 @@
export interface Database {
name: string
}
export interface Role {
role: string
attributes: string[]
}
export interface Backup {
name: string
size: string
}

View File

@@ -1,23 +0,0 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'postgresql15',
path: '/apps/postgresql15',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-postgresql15-index',
path: '',
component: () => import('../postgresql/IndexView.vue'),
meta: {
title: 'PostgreSQL 15',
icon: 'mdi:database',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -1,23 +0,0 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'postgresql16',
path: '/apps/postgresql16',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-postgresql16-index',
path: '',
component: () => import('../postgresql/IndexView.vue'),
meta: {
title: 'PostgreSQL 16',
icon: 'mdi:database',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -3,9 +3,10 @@ import Editor from '@guolao/vue-monaco-editor'
import { NButton, NDataTable, NInput, NPopconfirm, NSwitch } from 'naive-ui'
import cron from '@/api/panel/cron'
import file from '@/api/panel/file'
import info from '@/api/panel/info'
import website from '@/api/panel/website'
import { renderIcon } from '@/utils'
import { formatDateTime, renderIcon } from '@/utils'
import type { CronTask } from '@/views/cron/types'
const addModel = ref({
@@ -82,9 +83,19 @@ const columns: any = [
key: 'created_at',
width: 200,
resizable: true,
ellipsis: { tooltip: true }
ellipsis: { tooltip: true },
render(row: any): string {
return formatDateTime(row.created_at)
}
},
{
title: '最后更新时间',
key: 'updated_at',
ellipsis: { tooltip: true },
render(row: any): string {
return formatDateTime(row.updated_at)
}
},
{ title: '最后更新时间', key: 'updated_at', ellipsis: { tooltip: true } },
{
title: '操作',
key: 'actions',
@@ -219,20 +230,22 @@ const handleShowLog = async (row: any) => {
})
}
const handleAdd = async () => {
await cron.add(addModel.value).then(() => {
window.$message.success('添加成功')
const handleCreate = async () => {
await cron.create(addModel.value).then(() => {
window.$message.success('创建成功')
})
onPageChange(pagination.page)
}
const handleEdit = async (row: any) => {
cron.script(row.id).then((res) => {
editTask.value.id = row.id
editTask.value.name = row.name
editTask.value.time = row.time
editTask.value.script = res.data
editTaskModal.value = true
await cron.get(row.id).then(async (res) => {
await file.content(res.data.shell).then((res) => {
editTask.value.id = row.id
editTask.value.name = row.name
editTask.value.time = row.time
editTask.value.script = res.data
editTaskModal.value = true
})
})
}
@@ -270,7 +283,7 @@ onMounted(() => {
<template>
<common-page show-footer>
<n-space vertical>
<n-card flex-1 rounded-10 title="添加计划任务">
<n-card flex-1 rounded-10 title="创建计划任务">
<n-space vertical>
<n-alert type="info">
面板的计划任务均基于脚本运行若任务类型满足不了需求可自行修改对应的脚本
@@ -347,7 +360,7 @@ onMounted(() => {
<n-input-number v-model:value="addModel.save" />
</n-form-item>
</n-form>
<n-button type="primary" @click="handleAdd">添加</n-button>
<n-button type="primary" @click="handleCreate">创建</n-button>
</n-space>
</n-card>
<n-card flex-1 rounded-10 title="计划任务列表">

View File

@@ -114,7 +114,7 @@ const bulkDelete = () => {
]"
@update:value="showNew"
>
<n-button type="primary"> </n-button>
<n-button type="primary"> </n-button>
</n-popselect>
<n-button @click="upload = true"> 上传 </n-button>
<n-button style="display: none"> 远程下载 </n-button>
@@ -141,7 +141,7 @@ const bulkDelete = () => {
<n-modal
v-model:show="newModal"
preset="card"
title="建"
title="建"
style="width: 60vw"
size="huge"
:bordered="false"

View File

@@ -4,7 +4,7 @@ import { NButton, NDataTable, NPopconfirm } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import task from '@/api/panel/task'
import { renderIcon } from '@/utils'
import { formatDateTime, renderIcon } from '@/utils'
import type { Task } from '@/views/task/types'
const { t } = useI18n()
@@ -40,14 +40,20 @@ const columns: any = [
{
title: t('taskIndex.columns.createdAt'),
key: 'created_at',
width: 160,
ellipsis: { tooltip: true }
width: 200,
ellipsis: { tooltip: true },
render(row: any): string {
return formatDateTime(row.created_at)
}
},
{
title: t('taskIndex.columns.updatedAt'),
key: 'updated_at',
width: 160,
ellipsis: { tooltip: true }
width: 200,
ellipsis: { tooltip: true },
render(row: any): string {
return formatDateTime(row.updated_at)
}
},
{
title: t('taskIndex.columns.actions'),