mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 03:07:20 +08:00
feat: 数据库管理提交1
This commit is contained in:
@@ -69,4 +69,5 @@ type DatabaseServerRepo interface {
|
||||
Create(req *request.DatabaseServerCreate) error
|
||||
Update(req *request.DatabaseServerUpdate) error
|
||||
Delete(id uint) error
|
||||
Sync(id uint) error
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ func (r *backupRepo) createMySQL(to string, name string) error {
|
||||
|
||||
// createPostgres 创建 PostgreSQL 备份
|
||||
func (r *backupRepo) createPostgres(to string, name string) error {
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432, fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root))
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -436,7 +436,7 @@ func (r *backupRepo) restorePostgres(backup, target string) error {
|
||||
return errors.New("备份文件不存在")
|
||||
}
|
||||
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432, fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root))
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/db"
|
||||
)
|
||||
|
||||
type databaseRepo struct{}
|
||||
|
||||
func NewDatabaseRepo() biz.DatabaseRepo {
|
||||
return &databaseRepo{}
|
||||
type databaseRepo struct {
|
||||
databaseServer biz.DatabaseServerRepo
|
||||
}
|
||||
|
||||
func (d databaseRepo) Count() (int64, error) {
|
||||
func NewDatabaseRepo() biz.DatabaseRepo {
|
||||
return &databaseRepo{
|
||||
databaseServer: NewDatabaseServerRepo(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r databaseRepo) Count() (int64, error) {
|
||||
var count int64
|
||||
if err := app.Orm.Model(&biz.Database{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
@@ -21,14 +28,14 @@ func (d databaseRepo) Count() (int64, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (d databaseRepo) List(page, limit uint) ([]*biz.Database, int64, error) {
|
||||
func (r databaseRepo) List(page, limit uint) ([]*biz.Database, int64, error) {
|
||||
var database []*biz.Database
|
||||
var total int64
|
||||
err := app.Orm.Model(&biz.Database{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&database).Error
|
||||
return database, total, err
|
||||
}
|
||||
|
||||
func (d databaseRepo) Get(id uint) (*biz.Database, error) {
|
||||
func (r databaseRepo) Get(id uint) (*biz.Database, error) {
|
||||
database := new(biz.Database)
|
||||
if err := app.Orm.Where("id = ?", id).First(database).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -37,7 +44,43 @@ func (d databaseRepo) Get(id uint) (*biz.Database, error) {
|
||||
return database, nil
|
||||
}
|
||||
|
||||
func (d databaseRepo) Create(req *request.DatabaseCreate) error {
|
||||
func (r databaseRepo) Create(req *request.DatabaseCreate) error {
|
||||
server, err := r.databaseServer.Get(req.ServerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mysql.UserCreate(req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mysql.DatabaseCreate(req.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mysql.PrivilegesGrant(req.Username, req.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = postgres.UserCreate(req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = postgres.DatabaseCreate(req.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = postgres.PrivilegesGrant(req.Username, req.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
database := &biz.Database{
|
||||
Name: req.Name,
|
||||
Username: req.Username,
|
||||
@@ -50,7 +93,7 @@ func (d databaseRepo) Create(req *request.DatabaseCreate) error {
|
||||
return app.Orm.Create(database).Error
|
||||
}
|
||||
|
||||
func (d databaseRepo) Update(req *request.DatabaseUpdate) error {
|
||||
func (r databaseRepo) Update(req *request.DatabaseUpdate) error {
|
||||
database := &biz.Database{
|
||||
Name: req.Name,
|
||||
Username: req.Username,
|
||||
@@ -61,6 +104,6 @@ func (d databaseRepo) Update(req *request.DatabaseUpdate) error {
|
||||
return app.Orm.Model(database).Where("id = ?", req.ID).Omit("ServerID").Updates(database).Error
|
||||
}
|
||||
|
||||
func (d databaseRepo) Delete(id uint) error {
|
||||
func (r databaseRepo) Delete(id uint) error {
|
||||
return app.Orm.Delete(&biz.Database{}, id).Error
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewDatabaseServerRepo() biz.DatabaseServerRepo {
|
||||
return &databaseServerRepo{}
|
||||
}
|
||||
|
||||
func (d databaseServerRepo) Count() (int64, error) {
|
||||
func (r databaseServerRepo) Count() (int64, error) {
|
||||
var count int64
|
||||
if err := app.Orm.Model(&biz.DatabaseServer{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
@@ -27,14 +27,14 @@ func (d databaseServerRepo) Count() (int64, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (d databaseServerRepo) List(page, limit uint) ([]*biz.DatabaseServer, int64, error) {
|
||||
func (r databaseServerRepo) List(page, limit uint) ([]*biz.DatabaseServer, int64, error) {
|
||||
var databaseServer []*biz.DatabaseServer
|
||||
var total int64
|
||||
err := app.Orm.Model(&biz.DatabaseServer{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&databaseServer).Error
|
||||
return databaseServer, total, err
|
||||
}
|
||||
|
||||
func (d databaseServerRepo) Get(id uint) (*biz.DatabaseServer, error) {
|
||||
func (r databaseServerRepo) Get(id uint) (*biz.DatabaseServer, error) {
|
||||
databaseServer := new(biz.DatabaseServer)
|
||||
if err := app.Orm.Where("id = ?", id).First(databaseServer).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -43,14 +43,14 @@ func (d databaseServerRepo) Get(id uint) (*biz.DatabaseServer, error) {
|
||||
return databaseServer, nil
|
||||
}
|
||||
|
||||
func (d databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
|
||||
func (r databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
|
||||
switch biz.DatabaseType(req.Type) {
|
||||
case biz.DatabaseTypeMysql:
|
||||
if _, err := db.NewMySQL(req.Username, req.Password, fmt.Sprintf("%s:%d", req.Host, req.Port)); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
if _, err := db.NewPostgres(req.Username, req.Password, req.Host, req.Port, ""); err != nil {
|
||||
if _, err := db.NewPostgres(req.Username, req.Password, req.Host, req.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypeRedis:
|
||||
@@ -73,18 +73,18 @@ func (d databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
|
||||
return app.Orm.Create(databaseServer).Error
|
||||
}
|
||||
|
||||
func (d databaseServerRepo) Update(req *request.DatabaseServerUpdate) error {
|
||||
func (r databaseServerRepo) Update(req *request.DatabaseServerUpdate) error {
|
||||
switch biz.DatabaseType(req.Type) {
|
||||
case biz.DatabaseTypeMysql:
|
||||
if _, err := db.NewMySQL(req.Username, req.Password, fmt.Sprintf("%s:%d", req.Host, req.Port)); err != nil {
|
||||
if _, err := db.NewMySQL(req.Username, req.Password, fmt.Sprintf("%s:%r", req.Host, req.Port)); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypePostgresql:
|
||||
if _, err := db.NewPostgres(req.Username, req.Password, req.Host, req.Port, ""); err != nil {
|
||||
if _, err := db.NewPostgres(req.Username, req.Password, req.Host, req.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
case biz.DatabaseTypeRedis:
|
||||
if _, err := db.NewRedis(req.Username, req.Password, fmt.Sprintf("%s:%d", req.Host, req.Port)); err != nil {
|
||||
if _, err := db.NewRedis(req.Username, req.Password, fmt.Sprintf("%s:%r", req.Host, req.Port)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ func (d databaseServerRepo) Update(req *request.DatabaseServerUpdate) error {
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (d databaseServerRepo) Delete(id uint) error {
|
||||
func (r databaseServerRepo) Delete(id uint) error {
|
||||
ds := new(biz.DatabaseServer)
|
||||
if err := app.Orm.Where("id = ?", id).First(ds).Error; err != nil {
|
||||
return err
|
||||
@@ -113,3 +113,46 @@ func (d databaseServerRepo) Delete(id uint) error {
|
||||
|
||||
return app.Orm.Delete(&biz.DatabaseServer{}, id).Error
|
||||
}
|
||||
|
||||
func (r databaseServerRepo) Sync(id uint) error {
|
||||
/*server, err := r.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.Type {
|
||||
case biz.DatabaseTypeMysql:
|
||||
mysql, err := db.NewMySQL(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
databases, err := mysql.Databases()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for database := range slices.Values(databases) {
|
||||
db := &biz.Database{
|
||||
Name: database.Name,
|
||||
Username: server.Username,
|
||||
Password: server.Password,
|
||||
ServerID: server.ID,
|
||||
Status: biz.DatabaseStatusInvalid,
|
||||
}
|
||||
if err := app.Orm.Where("name = ? AND server_id = ?", database.Name, server.ID).First(db).Error; err != nil {
|
||||
app.Orm.Create(db)
|
||||
}
|
||||
}
|
||||
|
||||
case biz.DatabaseTypePostgresql:
|
||||
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
databases, err := postgres.Databases()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}*/
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -307,10 +307,10 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = mysql.DatabaseCreate(req.DBName); err != nil {
|
||||
if err = mysql.UserCreate(req.DBUser, req.DBPassword); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = mysql.UserCreate(req.DBUser, req.DBPassword); err != nil {
|
||||
if err = mysql.DatabaseCreate(req.DBName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = mysql.PrivilegesGrant(req.DBUser, req.DBName); err != nil {
|
||||
@@ -318,20 +318,17 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
}
|
||||
}
|
||||
if req.DB && req.DBType == "postgresql" {
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432, fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root))
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = postgres.DatabaseCreate(req.DBName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = postgres.UserCreate(req.DBUser, req.DBPassword); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = postgres.PrivilegesGrant(req.DBUser, req.DBName); err != nil {
|
||||
if err = postgres.DatabaseCreate(req.DBName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = postgres.HostAdd(req.DBName, req.DBUser, "127.0.0.1/32"); err != nil {
|
||||
if err = postgres.PrivilegesGrant(req.DBUser, req.DBName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -526,13 +523,14 @@ func (r *websiteRepo) Delete(req *request.WebsiteDelete) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix")
|
||||
if err == nil {
|
||||
_ = mysql.DatabaseDrop(website.Name)
|
||||
if mysql, err := db.NewMySQL("root", rootPassword, "/tmp/mysql.sock", "unix"); err == nil {
|
||||
_ = mysql.UserDrop(website.Name)
|
||||
_ = mysql.DatabaseDrop(website.Name)
|
||||
}
|
||||
if postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432); err == nil {
|
||||
_ = postgres.UserDrop(website.Name)
|
||||
_ = postgres.DatabaseDrop(website.Name)
|
||||
}
|
||||
_, _ = shell.Execf(`echo "DROP DATABASE IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
|
||||
_, _ = shell.Execf(`echo "DROP USER IF EXISTS '%s';" | su - postgres -c "psql"`, website.Name)
|
||||
}
|
||||
|
||||
if err := app.Orm.Delete(website).Error; err != nil {
|
||||
|
||||
@@ -71,7 +71,7 @@ func Http(r chi.Router) {
|
||||
})
|
||||
|
||||
r.Route("/databaseServer", func(r chi.Router) {
|
||||
database := service.NewDatabaseService()
|
||||
database := service.NewDatabaseServerService()
|
||||
r.Get("/", database.List)
|
||||
r.Post("/", database.Create)
|
||||
r.Put("/{id}", database.Update)
|
||||
|
||||
@@ -140,7 +140,7 @@ func (s *DashboardService) CountInfo(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if postgresqlInstalled {
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432, fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root))
|
||||
postgres, err := db.NewPostgres("postgres", "", "127.0.0.1", 5432)
|
||||
if err == nil {
|
||||
defer postgres.Close()
|
||||
databases, err := postgres.Databases()
|
||||
|
||||
@@ -83,3 +83,18 @@ func (s *DatabaseServer) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *DatabaseServer) Sync(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.ID](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.databaseServerRepo.Sync(req.ID); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
@@ -17,13 +15,15 @@ type Postgres struct {
|
||||
username string
|
||||
password string
|
||||
address string
|
||||
hbaFile string
|
||||
port uint
|
||||
}
|
||||
|
||||
func NewPostgres(username, password, address string, port uint, hbaFile string) (*Postgres, error) {
|
||||
func NewPostgres(username, password, address string, port uint) (*Postgres, error) {
|
||||
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=postgres sslmode=disable", address, port, username, password)
|
||||
if password == "" {
|
||||
if username == "" {
|
||||
username = "postgres"
|
||||
}
|
||||
dsn = fmt.Sprintf("host=%s port=%d user=%s dbname=postgres sslmode=disable", address, port, username)
|
||||
}
|
||||
db, err := sql.Open("postgres", dsn)
|
||||
@@ -38,7 +38,6 @@ func NewPostgres(username, password, address string, port uint, hbaFile string)
|
||||
username: username,
|
||||
password: password,
|
||||
address: address,
|
||||
hbaFile: hbaFile,
|
||||
port: port,
|
||||
}, nil
|
||||
}
|
||||
@@ -109,7 +108,6 @@ func (m *Postgres) UserDrop(user string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = shell.Execf(`sed -i '/%s/d' %s`, user, m.hbaFile)
|
||||
return systemctl.Reload("postgresql")
|
||||
}
|
||||
|
||||
@@ -134,24 +132,6 @@ func (m *Postgres) PrivilegesRevoke(user, database string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Postgres) HostAdd(database, user, host string) error {
|
||||
config := fmt.Sprintf("host %s %s %s scram-sha-256", database, user, host)
|
||||
if err := io.WriteAppend(m.hbaFile, config, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return systemctl.Reload("postgresql")
|
||||
}
|
||||
|
||||
func (m *Postgres) HostRemove(database, user, host string) error {
|
||||
regex := fmt.Sprintf(`host\s+%s\s+%s\s+%s`, database, user, host)
|
||||
if _, err := shell.Execf(`sed -i '/%s/d' %s`, regex, m.hbaFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return systemctl.Reload("postgresql")
|
||||
}
|
||||
|
||||
func (m *Postgres) Users() ([]types.PostgresUser, error) {
|
||||
query := `
|
||||
SELECT rolname,
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
85
web/src/views/database/CreateDatabaseModal.vue
Normal file
85
web/src/views/database/CreateDatabaseModal.vue
Normal 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>
|
||||
110
web/src/views/database/CreateServerModal.vue
Normal file
110
web/src/views/database/CreateServerModal.vue
Normal 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>
|
||||
172
web/src/views/database/DatabaseListView.vue
Normal file
172
web/src/views/database/DatabaseListView.vue
Normal 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>
|
||||
53
web/src/views/database/IndexView.vue
Normal file
53
web/src/views/database/IndexView.vue
Normal 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>
|
||||
172
web/src/views/database/ServerListView.vue
Normal file
172
web/src/views/database/ServerListView.vue
Normal 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>
|
||||
25
web/src/views/database/route.ts
Normal file
25
web/src/views/database/route.ts
Normal 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
|
||||
Reference in New Issue
Block a user