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

feat: 数据库管理提交1

This commit is contained in:
耗子
2024-11-22 02:58:31 +08:00
parent 39d2eee676
commit a96bbf3dfb
17 changed files with 764 additions and 65 deletions

View File

@@ -11,11 +11,11 @@ export default {
delete: (id: number) => http.Delete(`/database/${id}`),
// 获取数据库服务器列表
serverList: (page: number, limit: number) =>
http.Get('/database/serverList', { params: { page, limit } }),
http.Get('/databaseServer', { params: { page, limit } }),
// 创建数据库服务器
createServer: (data: any) => http.Post('/database/server', data),
createServer: (data: any) => http.Post('/databaseServer', data),
// 更新数据库服务器
updateServer: (id: number, data: any) => http.Put(`/database/server/${id}`, data),
updateServer: (id: number, data: any) => http.Put(`/databaseServer/${id}`, data),
// 删除数据库服务器
deleteServer: (id: number) => http.Delete(`/database/server/${id}`)
deleteServer: (id: number) => http.Delete(`/databaseServer/${id}`)
}

View File

@@ -24,6 +24,8 @@ export const request = createAxios({
})
export const http = createAlova({
id: 'panel',
cacheFor: null,
statesHook: VueHook,
requestAdapter: adapterFetch(),
baseURL: import.meta.env.VITE_BASE_API,

View File

@@ -0,0 +1,85 @@
<script setup lang="ts">
import database from '@/api/panel/database'
import { NButton, NInput } from 'naive-ui'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const createModel = ref({
database: '',
username: '',
password: '',
host: 'localhost'
})
const hostType = [
{ label: '本地localhost', value: 'localhost' },
{ label: '所有(%', value: '%' },
{ label: '指定', value: '' }
]
const handleCreate = () => {
useRequest(() => database.create(createModel.value)).onSuccess(() => {
show.value = false
window.$message.success('创建成功')
window.$bus.emit('database:refresh')
})
}
</script>
<template>
<n-modal
v-model:show="show"
preset="card"
title="创建数据库"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="show = false"
>
<n-form :model="createModel">
<n-form-item path="database" label="数据库名">
<n-input
v-model:value="createModel.database"
type="text"
@keydown.enter.prevent
placeholder="输入数据库名称"
/>
</n-form-item>
<n-form-item path="username" label="用户名">
<n-input
v-model:value="createModel.username"
type="text"
@keydown.enter.prevent
placeholder="输入用户名"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-input
v-model:value="createModel.password"
type="password"
@keydown.enter.prevent
placeholder="输入密码"
/>
</n-form-item>
<n-form-item path="host-select" label="主机">
<n-select
v-model:value="createModel.host"
@keydown.enter.prevent
placeholder="选择主机"
:options="hostType"
/>
</n-form-item>
<n-form-item v-if="createModel.host === ''" path="host" label="指定主机">
<n-input
v-model:value="createModel.host"
type="text"
@keydown.enter.prevent
placeholder="输入受支持的主机地址"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">提交</n-button>
</n-modal>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,110 @@
<script setup lang="ts">
import database from '@/api/panel/database'
import { NButton, NInput } from 'naive-ui'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const createModel = ref({
name: '',
type: 'mysql',
host: '127.0.0.1',
port: 3306,
username: '',
password: '',
remark: ''
})
const databaseType = [
{ label: 'MySQL', value: 'mysql' },
{ label: 'PostgreSQL', value: 'postgresql' }
]
const handleCreate = () => {
useRequest(() => database.createServer(createModel.value)).onSuccess(() => {
show.value = false
window.$message.success('添加成功')
window.$bus.emit('database:refresh')
})
}
</script>
<template>
<n-modal
v-model:show="show"
preset="card"
title="添加数据库服务器"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="show = false"
>
<n-form :model="createModel">
<n-form-item path="name" label="名称">
<n-input
v-model:value="createModel.name"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器名称"
/>
</n-form-item>
<n-form-item path="type" label="类型">
<n-select
v-model:value="createModel.type"
@keydown.enter.prevent
placeholder="选择数据库类型"
:options="databaseType"
/>
</n-form-item>
<n-row :gutter="[0, 24]">
<n-col :span="15">
<n-form-item path="host" label="主机">
<n-input
v-model:value="createModel.host"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器主机"
/>
</n-form-item>
</n-col>
<n-col :span="2"></n-col>
<n-col :span="7">
<n-form-item path="port" label="端口">
<n-input-number
w-full
v-model:value="createModel.port"
@keydown.enter.prevent
placeholder="输入数据库服务器端口"
/>
</n-form-item>
</n-col>
</n-row>
<n-form-item path="username" label="用户名">
<n-input
v-model:value="createModel.username"
type="text"
@keydown.enter.prevent
placeholder="输入数据库服务器用户名"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-input
v-model:value="createModel.password"
type="password"
@keydown.enter.prevent
placeholder="输入数据库服务器密码"
/>
</n-form-item>
<n-form-item path="remark" label="备注">
<n-input
v-model:value="createModel.remark"
type="textarea"
@keydown.enter.prevent
placeholder="输入数据库服务器备注"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">提交</n-button>
</n-modal>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,172 @@
<script setup lang="ts">
import { renderIcon } from '@/utils'
import { NButton, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
import database from '@/api/panel/database'
import { formatDateTime } from '@/utils'
const columns: any = [
{
title: '名称',
key: 'name',
minWidth: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '类型',
key: 'type',
width: 150,
render(row: any) {
return h(
NTag,
{ type: 'info' },
{
default: () => {
switch (row.type) {
case 'mysql':
return 'MySQL'
case 'postgresql':
return 'PostgreSQL'
default:
return row.type
}
}
}
)
}
},
{
title: '用户名',
key: 'username',
width: 150,
ellipsis: { tooltip: true },
render(row: any) {
return row.username || '无'
}
},
{
title: '密码',
key: 'password',
width: 200,
render(row: any) {
return h(NInputGroup, null, {
default: () => [
h(NInput, {
value: row.password,
type: 'password',
showPasswordOn: 'click',
readonly: true
})
]
})
}
},
{
title: '主机',
key: 'host',
width: 200,
render(row: any) {
return h(NTag, null, {
default: () => `${row.host}:${row.port}`
})
}
},
{
title: '更新日期',
key: 'updated_at',
width: 200,
ellipsis: { tooltip: true },
render(row: any) {
return formatDateTime(row.updated_at)
}
},
{
title: '操作',
key: 'actions',
width: 200,
align: 'center',
hideInExcel: true,
render(row: any) {
return [
h(
NPopconfirm,
{
onPositiveClick: () => handleDelete(row.id)
},
{
default: () => {
return '确定删除服务器吗?'
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => database.serverList(page, pageSize),
{
initialData: { total: 0, list: [] },
total: (res: any) => res.total,
data: (res: any) => res.items
}
)
const handleDelete = async (id: number) => {
await database.deleteServer(id).then(() => {
window.$message.success('删除成功')
refresh()
})
}
onMounted(() => {
window.$bus.on('database:refresh', () => {
refresh()
})
})
onUnmounted(() => {
window.$bus.off('database:refresh')
})
</script>
<template>
<n-data-table
striped
remote
:scroll-x="1200"
:loading="loading"
:columns="columns"
:data="data"
:row-key="(row: any) => row.name"
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]
}"
/>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,53 @@
<script setup lang="ts">
defineOptions({
name: 'database-index'
})
import CreateDatabaseModal from '@/views/database/CreateDatabaseModal.vue'
import CreateDatabaseServerModal from '@/views/database/CreateServerModal.vue'
import DatabaseListView from '@/views/database/DatabaseListView.vue'
import ServerListView from '@/views/database/ServerListView.vue'
import { NButton } from 'naive-ui'
const currentTab = ref('database')
const createDatabaseModalShow = ref(false)
const createDatabaseServerModalShow = ref(false)
</script>
<template>
<common-page show-footer>
<template #action>
<n-button
v-if="currentTab === 'database'"
type="primary"
@click="createDatabaseModalShow = true"
>
<TheIcon :size="18" icon="material-symbols:add" />
创建数据库
</n-button>
<n-flex v-if="currentTab === 'server'">
<n-button type="success">
<TheIcon :size="18" icon="material-symbols:sync" />
同步数据库
</n-button>
<n-button type="primary" @click="createDatabaseServerModalShow = true">
<TheIcon :size="18" icon="material-symbols:add" />
添加服务器
</n-button>
</n-flex>
</template>
<n-flex vertical>
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="database" tab="数据库">
<database-list-view v-model:type="currentTab" />
</n-tab-pane>
<n-tab-pane name="server" tab="服务器">
<server-list-view v-model:type="currentTab" />
</n-tab-pane>
</n-tabs>
</n-flex>
</common-page>
<create-database-modal v-model:show="createDatabaseModalShow" />
<create-database-server-modal v-model:show="createDatabaseServerModalShow" />
</template>

View File

@@ -0,0 +1,172 @@
<script setup lang="ts">
import { renderIcon } from '@/utils'
import { NButton, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
import database from '@/api/panel/database'
import { formatDateTime } from '@/utils'
const columns: any = [
{
title: '名称',
key: 'name',
minWidth: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: '类型',
key: 'type',
width: 150,
render(row: any) {
return h(
NTag,
{ type: 'info' },
{
default: () => {
switch (row.type) {
case 'mysql':
return 'MySQL'
case 'postgresql':
return 'PostgreSQL'
default:
return row.type
}
}
}
)
}
},
{
title: '用户名',
key: 'username',
width: 150,
ellipsis: { tooltip: true },
render(row: any) {
return row.username || '无'
}
},
{
title: '密码',
key: 'password',
width: 200,
render(row: any) {
return h(NInputGroup, null, {
default: () => [
h(NInput, {
value: row.password,
type: 'password',
showPasswordOn: 'click',
readonly: true
})
]
})
}
},
{
title: '主机',
key: 'host',
width: 200,
render(row: any) {
return h(NTag, null, {
default: () => `${row.host}:${row.port}`
})
}
},
{
title: '更新日期',
key: 'updated_at',
width: 200,
ellipsis: { tooltip: true },
render(row: any) {
return formatDateTime(row.updated_at)
}
},
{
title: '操作',
key: 'actions',
width: 200,
align: 'center',
hideInExcel: true,
render(row: any) {
return [
h(
NPopconfirm,
{
onPositiveClick: () => handleDelete(row.id)
},
{
default: () => {
return '确定删除服务器吗?'
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => '删除',
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => database.serverList(page, pageSize),
{
initialData: { total: 0, list: [] },
total: (res: any) => res.total,
data: (res: any) => res.items
}
)
const handleDelete = async (id: number) => {
await database.deleteServer(id).then(() => {
window.$message.success('删除成功')
refresh()
})
}
onMounted(() => {
window.$bus.on('database:refresh', () => {
refresh()
})
})
onUnmounted(() => {
window.$bus.off('database:refresh')
})
</script>
<template>
<n-data-table
striped
remote
:scroll-x="1200"
:loading="loading"
:columns="columns"
:data="data"
:row-key="(row: any) => row.name"
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]
}"
/>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,25 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'database',
path: '/database',
component: Layout,
meta: {
order: 2
},
children: [
{
name: 'database-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: '数据库',
icon: 'mdi:database',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType