1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-14 01:34:47 +08:00

feat: 网站增加切换数据库功能 (#6592)

This commit is contained in:
zhengkunwang 2024-09-26 21:59:32 +08:00 committed by GitHub
parent f8431c787f
commit 1e1fce4c77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 368 additions and 18 deletions

View File

@ -968,7 +968,6 @@ func (b *BaseApi) SetRealIPConfig(c *gin.Context) {
helper.SuccessWithOutData(c) helper.SuccessWithOutData(c)
} }
// 写一个调用 GetRealIPConfig 的接口
// @Tags Website // @Tags Website
// @Summary Get Real IP Config // @Summary Get Real IP Config
// @Description 获取真实 IP 配置 // @Description 获取真实 IP 配置
@ -990,3 +989,61 @@ func (b *BaseApi) GetRealIPConfig(c *gin.Context) {
} }
helper.SuccessWithData(c, res) helper.SuccessWithData(c, res)
} }
// @Tags Website
// @Summary Get website resource
// @Description 获取网站资源
// @Accept json
// @Param id path int true "id"
// @Success 200 {object} response.WebsiteResource
// @Security ApiKeyAuth
// @Router /websites/resource/{id} [get]
func (b *BaseApi) GetWebsiteResource(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
res, err := websiteService.GetWebsiteResource(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Get databases
// @Description 获取数据库列表
// @Accept json
// @Success 200 {object} response.WebsiteDatabase
// @Security ApiKeyAuth
// @Router /websites/databases [get]
func (b *BaseApi) GetWebsiteDatabase(c *gin.Context) {
res, err := websiteService.ListDatabases()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Change website database
// @Description 切换网站数据库
// @Accept json
// @Param request body request.ChangeDatabase true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/databases [post]
func (b *BaseApi) ChangeWebsiteDatabase(c *gin.Context) {
var req request.ChangeDatabase
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.ChangeDatabase(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@ -275,3 +275,9 @@ type WebsiteRealIP struct {
IPHeader string `json:"ipHeader"` IPHeader string `json:"ipHeader"`
IPOther string `json:"ipOther"` IPOther string `json:"ipOther"`
} }
type ChangeDatabase struct {
WebsiteID uint `json:"websiteID" validate:"required"`
DatabaseID uint `json:"databaseID" validate:"required"`
DatabaseType string `json:"databaseType" validate:"required"`
}

View File

@ -102,3 +102,16 @@ type WebsiteRealIP struct {
IPHeader string `json:"ipHeader"` IPHeader string `json:"ipHeader"`
IPOther string `json:"ipOther"` IPOther string `json:"ipOther"`
} }
type Resource struct {
Name string `json:"name"`
Type string `json:"type"`
ResourceID uint `json:"resourceID"`
Detail interface{} `json:"detail"`
}
type Database struct {
Name string `json:"name"`
Type string `json:"type"`
ID uint `json:"id"`
}

View File

@ -113,6 +113,10 @@ type IWebsiteService interface {
GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error) GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error)
ChangeGroup(group, newGroup uint) error ChangeGroup(group, newGroup uint) error
GetWebsiteResource(websiteID uint) ([]response.Resource, error)
ListDatabases() ([]response.Database, error)
ChangeDatabase(req request.ChangeDatabase) error
} }
func NewIWebsiteService() IWebsiteService { func NewIWebsiteService() IWebsiteService {
@ -3052,3 +3056,116 @@ func (w WebsiteService) GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP
res.IPFrom = strings.Join(ips, "\n") res.IPFrom = strings.Join(ips, "\n")
return res, err return res, err
} }
func (w WebsiteService) GetWebsiteResource(websiteID uint) ([]response.Resource, error) {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
if err != nil {
return nil, err
}
var (
res []response.Resource
databaseID uint
databaseType string
)
if website.Type == constant.Runtime {
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
if err != nil {
return nil, err
}
res = append(res, response.Resource{
Name: runtime.Name,
Type: "runtime",
ResourceID: runtime.ID,
Detail: runtime,
})
}
if website.Type == constant.Deployment {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return nil, err
}
res = append(res, response.Resource{
Name: install.Name,
Type: "app",
ResourceID: install.ID,
Detail: install,
})
installResources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
for _, resource := range installResources {
if resource.Key == constant.AppMysql || resource.Key == constant.AppMariaDB || resource.Key == constant.AppPostgres || resource.Key == constant.AppPostgresql {
databaseType = resource.Key
databaseID = resource.ResourceId
}
}
}
if website.DbID > 0 {
databaseType = website.DbType
databaseID = website.DbID
}
if databaseID > 0 {
switch databaseType {
case constant.AppMysql, constant.AppMariaDB:
db, _ := mysqlRepo.Get(commonRepo.WithByID(databaseID))
if db.ID > 0 {
res = append(res, response.Resource{
Name: db.Name,
Type: "database",
ResourceID: db.ID,
Detail: db,
})
}
case constant.AppPostgresql, constant.AppPostgres:
db, _ := postgresqlRepo.Get(commonRepo.WithByID(databaseID))
if db.ID > 0 {
res = append(res, response.Resource{
Name: db.Name,
Type: "database",
ResourceID: db.ID,
Detail: db,
})
}
}
}
return res, nil
}
func (w WebsiteService) ListDatabases() ([]response.Database, error) {
var res []response.Database
mysqlDBs, _ := mysqlRepo.List()
for _, db := range mysqlDBs {
database, _ := databaseRepo.Get(commonRepo.WithByName(db.MysqlName))
if database.ID > 0 {
res = append(res, response.Database{
ID: db.ID,
Name: db.Name,
Type: database.Type,
})
}
}
pgSqls, _ := postgresqlRepo.List()
for _, db := range pgSqls {
database, _ := databaseRepo.Get(commonRepo.WithByName(db.Name))
if database.ID > 0 {
res = append(res, response.Database{
ID: db.ID,
Name: db.Name,
Type: database.Type,
})
}
}
return res, nil
}
func (w WebsiteService) ChangeDatabase(req request.ChangeDatabase) error {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
if website.DbID == req.DatabaseID {
return nil
}
website.DbID = req.DatabaseID
website.DbType = req.DatabaseType
return websiteRepo.Save(context.Background(), &website)
}

View File

@ -77,5 +77,9 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
websiteRouter.POST("/realip/config", baseApi.SetRealIPConfig) websiteRouter.POST("/realip/config", baseApi.SetRealIPConfig)
websiteRouter.GET("/realip/config/:id", baseApi.GetRealIPConfig) websiteRouter.GET("/realip/config/:id", baseApi.GetRealIPConfig)
websiteRouter.GET("/resource/:id", baseApi.GetWebsiteResource)
websiteRouter.GET("/databases", baseApi.GetWebsiteDatabase)
websiteRouter.POST("/databases", baseApi.ChangeWebsiteDatabase)
} }
} }

View File

@ -15,7 +15,6 @@ import (
type ComposeService struct { type ComposeService struct {
api.Service api.Service
project *types.Project
} }
func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) { func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) {

View File

@ -607,4 +607,23 @@ export namespace Website {
ipHeader: string; ipHeader: string;
ipOther: string; ipOther: string;
} }
export interface WebsiteResource {
name: string;
type: string;
resourceID: number;
detail: any;
}
export interface WebsiteDatabase {
type: string;
databaseID: number;
websiteID: number;
}
export interface ChangeDatabase {
websiteID: number;
databaseID: number;
databaseType: string;
}
} }

View File

@ -319,3 +319,15 @@ export const UpdateRealIPConfig = (req: Website.WebsiteRealIPConfig) => {
export const GetRealIPConfig = (id: number) => { export const GetRealIPConfig = (id: number) => {
return http.get<Website.WebsiteRealIPConfig>(`/websites/realip/config/${id}`); return http.get<Website.WebsiteRealIPConfig>(`/websites/realip/config/${id}`);
}; };
export const GetWebsiteResource = (id: number) => {
return http.get<Website.WebsiteResource[]>(`/websites/resource/${id}`);
};
export const GetWebsiteDatabase = () => {
return http.get<Website.WebsiteDatabase[]>(`/websites/databases`);
};
export const ChangeDatabase = (req: Website.ChangeDatabase) => {
return http.post(`/websites/databases`, req);
};

View File

@ -2238,6 +2238,11 @@ const message = {
'If unsure, you can enter 0.0.0.0/0 (ipv4) ::/0 (ipv6) [Note: Allowing any source IP is not secure]', 'If unsure, you can enter 0.0.0.0/0 (ipv4) ::/0 (ipv6) [Note: Allowing any source IP is not secure]',
http3Helper: http3Helper:
'HTTP/3 is an upgrade to HTTP/2, offering faster connection speeds and better performance, but not all browsers support HTTP/3. Enabling it may cause some browsers to be unable to access the site.', 'HTTP/3 is an upgrade to HTTP/2, offering faster connection speeds and better performance, but not all browsers support HTTP/3. Enabling it may cause some browsers to be unable to access the site.',
database: 'Database',
changeDatabase: 'Change Database',
changeDatabaseHelper1: 'Database association is used for backing up and restoring the website.',
changeDatabaseHelper2: 'Switching to another database will cause previous backups to be unrecoverable.',
}, },
php: { php: {
short_open_tag: 'Short tag support', short_open_tag: 'Short tag support',

View File

@ -2085,6 +2085,11 @@ const message = {
ipFromExample3: '如果不確定可以填 0.0.0.0/0ipv4 ::/0ipv6 [注意允許任意來源 IP 不安全]', ipFromExample3: '如果不確定可以填 0.0.0.0/0ipv4 ::/0ipv6 [注意允許任意來源 IP 不安全]',
http3Helper: http3Helper:
'HTTP/3 HTTP/2 的升級版本提供更快的連線速度和更好的性能但並非所有瀏覽器都支援 HTTP/3啟用後可能會導致部分瀏覽器無法訪問', 'HTTP/3 HTTP/2 的升級版本提供更快的連線速度和更好的性能但並非所有瀏覽器都支援 HTTP/3啟用後可能會導致部分瀏覽器無法訪問',
database: '資料庫',
changeDatabase: '切換資料庫',
changeDatabaseHelper1: '資料庫關聯用於備份恢復網站',
changeDatabaseHelper2: '切換其他資料庫會導致以前的備份無法恢復',
}, },
php: { php: {
short_open_tag: '短標簽支持', short_open_tag: '短標簽支持',

View File

@ -2083,6 +2083,11 @@ const message = {
ipFromExample3: '如果不确定可以填 0.0.0.0/0ipv4 ::/0ipv6 [注意允许任意来源 IP 不安全]', ipFromExample3: '如果不确定可以填 0.0.0.0/0ipv4 ::/0ipv6 [注意允许任意来源 IP 不安全]',
http3Helper: http3Helper:
'HTTP/3 HTTP/2 的升级版本提供更快的连接速度和更好的性能但是不是所有浏览器都支持 HTTP/3开启后可能会导致部分浏览器无法访问', 'HTTP/3 HTTP/2 的升级版本提供更快的连接速度和更好的性能但是不是所有浏览器都支持 HTTP/3开启后可能会导致部分浏览器无法访问',
database: '数据库',
changeDatabase: '切换数据库',
changeDatabaseHelper1: '数据库关联用于备份恢复网站',
changeDatabaseHelper2: '切换其他数据库会导致以前的备份无法恢复',
}, },
php: { php: {
short_open_tag: '短标签支持', short_open_tag: '短标签支持',

View File

@ -1,13 +1,13 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<el-row :gutter="20" v-loading="loading"> <el-row v-loading="loading">
<el-col :xs="24" :sm="18" :md="16" :lg="16" :xl="16"> <el-col :xs="24" :sm="18" :md="16" :lg="16" :xl="16">
<el-form <el-form
:model="form" :model="form"
:rules="rules" :rules="rules"
ref="leechRef" ref="leechRef"
label-position="right" label-position="right"
label-width="180px" label-width="120px"
class="moblie-form" class="moblie-form"
> >
<el-form-item :label="$t('website.enableOrNot')"> <el-form-item :label="$t('website.enableOrNot')">
@ -43,11 +43,18 @@
<el-form-item :label="$t('website.leechReturn')" prop="return"> <el-form-item :label="$t('website.leechReturn')" prop="return">
<el-input v-model="form.return" type="text" :maxlength="35"></el-input> <el-input v-model="form.return" type="text" :maxlength="35"></el-input>
</el-form-item> </el-form-item>
<el-form-item>
<el-button
type="primary"
@click="submit(leechRef, true)"
:disabled="loading"
v-if="form.enable"
>
{{ $t('commons.button.save') }}
</el-button>
</el-form-item>
</div> </div>
</el-form> </el-form>
<el-button type="primary" @click="submit(leechRef, true)" :disabled="loading" v-if="form.enable">
{{ $t('commons.button.save') }}
</el-button>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>

View File

@ -36,15 +36,20 @@
<el-tab-pane :label="$t('website.redirect')"> <el-tab-pane :label="$t('website.redirect')">
<Redirect :id="id" v-if="tabIndex == '11'"></Redirect> <Redirect :id="id" v-if="tabIndex == '11'"></Redirect>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('website.other')">
<Other :id="id" v-if="tabIndex == '12'"></Other>
</el-tab-pane>
<el-tab-pane <el-tab-pane
:label="'PHP'" :label="'PHP'"
name="13"
v-if="(website.type === 'runtime' && website.runtimeType === 'php') || website.type === 'static'" v-if="(website.type === 'runtime' && website.runtimeType === 'php') || website.type === 'static'"
> >
<PHP :website="website" v-if="tabIndex == '13'"></PHP> <PHP :website="website" v-if="tabIndex == '13'"></PHP>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('logs.resource')" name="14">
<Resource :id="id" :websiteType="website.type" v-if="tabIndex == '14'"></Resource>
</el-tab-pane>
<el-tab-pane :label="$t('website.other')" name="12">
<Other :id="id" v-if="tabIndex == '12'"></Other>
</el-tab-pane>
</el-tabs> </el-tabs>
</template> </template>
@ -65,6 +70,7 @@ import Redirect from './redirect/index.vue';
import LoadBalance from './load-balance/index.vue'; import LoadBalance from './load-balance/index.vue';
import PHP from './php/index.vue'; import PHP from './php/index.vue';
import RealIP from './real-ip/index.vue'; import RealIP from './real-ip/index.vue';
import Resource from './resource/index.vue';
const props = defineProps({ const props = defineProps({
website: { website: {

View File

@ -28,11 +28,13 @@
<el-input v-model.number="form.rate" maxlength="15"></el-input> <el-input v-model.number="form.rate" maxlength="15"></el-input>
<span class="input-help">{{ $t('website.rateHelper') }}</span> <span class="input-help">{{ $t('website.rateHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item>
<el-button type="primary" @click="submit(limitForm)" :disabled="loading">
<span v-if="enable">{{ $t('commons.button.save') }}</span>
<span v-else>{{ $t('commons.button.saveAndEnable') }}</span>
</el-button>
</el-form-item>
</el-form> </el-form>
<el-button type="primary" @click="submit(limitForm)" :disabled="loading">
<span v-if="enable">{{ $t('commons.button.save') }}</span>
<span v-else>{{ $t('commons.button.saveAndEnable') }}</span>
</el-button>
</el-col> </el-col>
</el-row> </el-row>
</template> </template>

View File

@ -1,7 +1,7 @@
<template> <template>
<el-row :gutter="20" v-loading="loading"> <el-row :gutter="20" v-loading="loading">
<el-col :xs="24" :sm="18" :md="8" :lg="8" :xl="8"> <el-col :xs="24" :sm="18" :md="8" :lg="8" :xl="8">
<el-form ref="websiteForm" label-position="right" label-width="100px" :model="form" :rules="rules"> <el-form ref="websiteForm" label-position="right" label-width="80px" :model="form" :rules="rules">
<el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain"> <el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain">
<el-input v-model="form.primaryDomain"></el-input> <el-input v-model="form.primaryDomain"></el-input>
</el-form-item> </el-form-item>

View File

@ -1,13 +1,13 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<el-row> <el-row>
<el-col :xs="20" :sm="12" :md="10" :lg="10" :xl="8" :offset="1"> <el-col :xs="20" :sm="12" :md="10" :lg="10" :xl="8">
<el-form> <el-form label-position="right" label-width="80px">
<div v-if="website.type === 'static'"> <div v-if="website.type === 'static'">
<el-text type="info">{{ $t('website.staticChangePHPHelper') }}</el-text> <el-text type="info">{{ $t('website.staticChangePHPHelper') }}</el-text>
</div> </div>
<el-form-item :label="$t('website.changeVersion')"> <el-form-item :label="$t('website.changeVersion')">
<el-select v-model="versionReq.runtimeID" style="width: 100%"> <el-select v-model="versionReq.runtimeID" class="w-full">
<el-option :key="-1" :label="$t('website.static')" :value="0"></el-option> <el-option :key="-1" :label="$t('website.static')" :value="0"></el-option>
<el-option <el-option
v-for="(item, index) in versions" v-for="(item, index) in versions"

View File

@ -0,0 +1,93 @@
<template>
<div class="p-w-400">
<el-descriptions border :column="1">
<div v-for="(resource, index) of data" :key="index">
<el-descriptions-item :label="$t('website.' + resource.type)">{{ resource.name }}</el-descriptions-item>
</div>
</el-descriptions>
<el-form
ref="changeForm"
:model="req"
label-position="left"
label-width="90px"
class="mt-5"
v-if="websiteType === 'static' || websiteType === 'runtime'"
>
<el-form-item :label="$t('website.changeDatabase')" prop="databaseID">
<el-select v-model="req.databaseID" class="w-full" @change="changeDatabase">
<el-option v-for="(item, index) in databases" :key="index" :label="item.name" :value="item.id">
<div class="flex justify-between items-center">
<span>{{ item.name }}</span>
<el-tag>{{ item.type }}</el-tag>
</div>
</el-option>
</el-select>
<el-text type="warning">{{ $t('website.changeDatabaseHelper1') }}</el-text>
<el-text type="warning">{{ $t('website.changeDatabaseHelper2') }}</el-text>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit()">
{{ $t('commons.button.save') }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ChangeDatabase, GetWebsiteDatabase, GetWebsiteResource } from '@/api/modules/website';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
const props = defineProps({
id: {
type: Number,
default: 0,
},
websiteType: {
type: String,
default: '',
},
});
const data = ref([]);
const req = reactive({
websiteID: props.id,
databaseID: 0,
databaseType: '',
});
const databases = ref([]);
const search = async () => {
try {
const res = await GetWebsiteResource(props.id);
data.value = res.data;
} catch (error) {}
};
const listDatabases = async () => {
try {
const res = await GetWebsiteDatabase();
databases.value = res.data;
if (databases.value.length > 0) {
req.databaseID = databases.value[0].id;
}
} catch (error) {}
};
const changeDatabase = () => {
req.databaseType = databases.value.find((item) => item.id === req.databaseID)?.type;
};
const submit = async () => {
try {
await ChangeDatabase(req);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
} catch (error) {}
};
onMounted(() => {
console.log('websiteType', props.websiteType);
search();
listDatabases();
});
</script>