feat: 增加静态网站和 PHP 运行环境网站互相切换功能 (#6452)
Some checks failed
sync2gitee / repo-sync (push) Failing after -8m57s

Refs https://github.com/1Panel-dev/1Panel/issues/2532
This commit is contained in:
zhengkunwang 2024-09-11 16:20:15 +08:00 committed by GitHub
parent ad3670c9ad
commit 88961e4ab7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 171 additions and 124 deletions

View file

@ -182,9 +182,8 @@ type WebsiteDefaultUpdate struct {
}
type WebsitePHPVersionReq struct {
WebsiteID uint `json:"websiteID" validate:"required"`
RuntimeID uint `json:"runtimeID" validate:"required"`
RetainConfig bool `json:"retainConfig" `
WebsiteID uint `json:"websiteID" validate:"required"`
RuntimeID uint `json:"runtimeID"`
}
type WebsiteUpdateDir struct {

View file

@ -13,6 +13,7 @@ type WebsiteDTO struct {
SitePath string `json:"sitePath"`
AppName string `json:"appName"`
RuntimeName string `json:"runtimeName"`
RuntimeType string `json:"runtimeType"`
SiteDir string `json:"siteDir"`
}

View file

@ -27,9 +27,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/spf13/afero"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/env"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
@ -532,13 +529,18 @@ func (w WebsiteService) GetWebsite(id uint) (response.WebsiteDTO, error) {
return res, err
}
res.Website = website
sitePath := GetSitePath(website, SiteDir)
res.ErrorLogPath = GetSitePath(website, SiteErrorLog)
res.AccessLogPath = GetSitePath(website, SiteAccessLog)
res.SitePath = sitePath
res.SitePath = GetSitePath(website, SiteDir)
res.SiteDir = website.SiteDir
if website.Type == constant.Runtime {
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
if err != nil {
return res, err
}
res.RuntimeType = runtime.Type
res.RuntimeName = runtime.Name
}
return res, nil
}
@ -1319,108 +1321,61 @@ func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error
if err != nil {
return err
}
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.RuntimeID))
if website.Type == constant.Runtime {
oldRuntime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
if err != nil {
return err
}
if oldRuntime.Resource == constant.ResourceLocal {
return buserr.New("ErrPHPResource")
}
}
configPath := GetSitePath(website, SiteConf)
nginxContent, err := files.NewFileOp().GetContent(configPath)
if err != nil {
return err
}
oldRuntime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
config, err := parser.NewStringParser(string(nginxContent)).Parse()
if err != nil {
return err
}
if runtime.Resource == constant.ResourceLocal || oldRuntime.Resource == constant.ResourceLocal {
return buserr.New("ErrPHPResource")
servers := config.FindServers()
if len(servers) == 0 {
return errors.New("nginx config is not valid")
}
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
server := servers[0]
if req.RuntimeID > 0 {
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.RuntimeID))
if err != nil {
return err
}
if runtime.Resource == constant.ResourceLocal {
return buserr.New("ErrPHPResource")
}
website.RuntimeID = req.RuntimeID
phpProxy := fmt.Sprintf("127.0.0.1:%d", runtime.Port)
website.Proxy = phpProxy
server.UpdatePHPProxy([]string{website.Proxy}, "")
website.Type = constant.Runtime
} else {
website.RuntimeID = 0
website.Type = constant.Static
website.Proxy = ""
server.RemoveDirective("location", []string{"~", "[^/]\\.php(/|$)"})
}
config.FilePath = configPath
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID))
if err != nil {
if err = nginxCheckAndReload(string(nginxContent), configPath, nginxInstall.ContainerName); err != nil {
return err
}
envs := make(map[string]interface{})
if err = json.Unmarshal([]byte(appInstall.Env), &envs); err != nil {
return err
}
if out, err := compose.Down(appInstall.GetComposePath()); err != nil {
if out != "" {
return errors.New(out)
}
return err
}
var (
busErr error
fileOp = files.NewFileOp()
envPath = appInstall.GetEnvPath()
composePath = appInstall.GetComposePath()
confDir = path.Join(appInstall.GetPath(), "conf")
backupConfDir = path.Join(appInstall.GetPath(), "conf_bak")
fpmConfDir = path.Join(confDir, "php-fpm.conf")
phpDir = path.Join(constant.RuntimeDir, runtime.Type, runtime.Name, "php")
oldFmContent, _ = fileOp.GetContent(fpmConfDir)
newComposeByte []byte
)
envParams := make(map[string]string, len(envs))
handleMap(envs, envParams)
envParams["IMAGE_NAME"] = runtime.Image
defer func() {
if busErr != nil {
envParams["IMAGE_NAME"] = oldRuntime.Image
_ = env.Write(envParams, envPath)
_ = fileOp.WriteFile(composePath, strings.NewReader(appInstall.DockerCompose), 0775)
if fileOp.Stat(backupConfDir) {
_ = fileOp.DeleteDir(confDir)
_ = fileOp.Rename(backupConfDir, confDir)
}
}
}()
if busErr = env.Write(envParams, envPath); busErr != nil {
return busErr
}
newComposeByte, busErr = changeServiceName(appDetail.DockerCompose, appInstall.ServiceName)
if busErr != nil {
return err
}
if busErr = fileOp.WriteFile(composePath, bytes.NewReader(newComposeByte), 0775); busErr != nil {
return busErr
}
if !req.RetainConfig {
if busErr = fileOp.Rename(confDir, backupConfDir); busErr != nil {
return busErr
}
_ = fileOp.CreateDir(confDir, 0755)
if busErr = fileOp.CopyFile(path.Join(phpDir, "php-fpm.conf"), confDir); busErr != nil {
return busErr
}
if busErr = fileOp.CopyFile(path.Join(phpDir, "php.ini"), confDir); busErr != nil {
_ = fileOp.WriteFile(fpmConfDir, bytes.NewReader(oldFmContent), 0775)
return busErr
}
}
if out, err := compose.Up(appInstall.GetComposePath()); err != nil {
if out != "" {
busErr = errors.New(out)
return busErr
}
busErr = err
return busErr
}
_ = fileOp.DeleteDir(backupConfDir)
appInstall.AppDetailId = runtime.AppDetailID
appInstall.AppId = appDetail.AppId
appInstall.Version = appDetail.Version
appInstall.DockerCompose = string(newComposeByte)
_ = appInstallRepo.Save(context.Background(), &appInstall)
website.RuntimeID = req.RuntimeID
return websiteRepo.Save(context.Background(), &website)
}

View file

@ -70,5 +70,7 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
websiteRouter.POST("/lbs/del", baseApi.DeleteLoadBalance)
websiteRouter.POST("/lbs/update", baseApi.UpdateLoadBalance)
websiteRouter.POST("/lbs/file", baseApi.UpdateLoadBalanceFile)
websiteRouter.POST("/php/version", baseApi.ChangePHPVersion)
}
}

View file

@ -472,7 +472,6 @@ export namespace Website {
export interface PHPVersionChange {
websiteID: number;
runtimeID: number;
retainConfig: boolean;
}
export interface DirConfig {

View file

@ -2129,8 +2129,7 @@ const message = {
notKeep: 'Do not keep',
redirectRoot: 'Redirect to the homepage',
redirectHelper: '301 permanent redirection, 302 temporary redirection',
changePHPVersionWarn:
'Switching the PHP version will delete the original PHP container (the website code that has been mounted will not be lost), continue? ',
changePHPVersionWarn: 'This operation cannot be rolled back, do you want to continue?',
changeVersion: 'Switch version',
retainConfig: 'Whether to keep php-fpm.conf and php.ini files',
runDirHelper2: 'Please ensure that the secondary running directory is under the index directory',
@ -2187,6 +2186,7 @@ const message = {
strategy: 'Strategy',
strategyDown: 'Down',
strategyBackup: 'Backup',
staticChangePHPHelper: 'Currently a static website, you can switch to a PHP website',
},
php: {
short_open_tag: 'Short tag support',

View file

@ -1983,7 +1983,7 @@ const message = {
notKeep: '不保留',
redirectRoot: '重定向到首頁',
redirectHelper: '301永久重定向302臨時重定向',
changePHPVersionWarn: '切換 PHP 版本會刪除原有的 PHP 容器不會丟失已經掛載的網站代碼是否繼續 ',
changePHPVersionWarn: '此動作不可回滾是否繼續',
changeVersion: '切換版本',
retainConfig: '是否保留 php-fpm.conf php.ini 文件',
runDirHelper2: '請確保二級運行目錄位於 index 目錄下',
@ -2034,6 +2034,7 @@ const message = {
strategy: '策略',
strategyDown: '停用',
strategyBackup: '備用',
staticChangePHPHelper: '目前為靜態網站可切換為 PHP 網站',
},
php: {
short_open_tag: '短標簽支持',

View file

@ -1984,7 +1984,7 @@ const message = {
notKeep: '不保留',
redirectRoot: '重定向到首页',
redirectHelper: '301永久重定向302临时重定向',
changePHPVersionWarn: '切换 PHP 版本会删除原有的 PHP 容器不会丢失已经挂载的网站代码是否继续',
changePHPVersionWarn: '此操作不可回滚是否继续',
changeVersion: '切换版本',
retainConfig: '是否保留 php-fpm.conf php.ini 文件',
runDirHelper2: '请确保二级运行目录位于 index 目录下',
@ -2035,6 +2035,7 @@ const message = {
strategy: '策略',
strategyDown: '停用',
strategyBackup: '备用',
staticChangePHPHelper: '当前为静态网站可以切换为 PHP 网站',
},
php: {
short_open_tag: '短标签支持',

View file

@ -1,7 +1,7 @@
<template>
<el-tabs tab-position="left" v-model="tabIndex">
<el-tabs tab-position="left" v-model="tabIndex" v-if="id > 0">
<el-tab-pane :label="$t('website.domainConfig')">
<Domain :key="id" :id="id" v-if="tabIndex == '0' && id > 0"></Domain>
<Domain :key="id" :id="id" v-if="tabIndex == '0'"></Domain>
</el-tab-pane>
<el-tab-pane :label="$t('website.sitePath')">
<SitePath :id="id" v-if="tabIndex == '1'"></SitePath>
@ -36,6 +36,12 @@
<el-tab-pane :label="$t('website.other')">
<Other :id="id" v-if="tabIndex == '11'"></Other>
</el-tab-pane>
<el-tab-pane
:label="'PHP'"
v-if="(website.type === 'runtime' && website.runtimeType === 'php') || website.type === 'static'"
>
<PHP :website="website" v-if="tabIndex == '12'"></PHP>
</el-tab-pane>
</el-tabs>
</template>
@ -54,15 +60,15 @@ import AuthBasic from './auth-basic/index.vue';
import AntiLeech from './anti-Leech/index.vue';
import Redirect from './redirect/index.vue';
import LoadBalance from './load-balance/index.vue';
import PHP from './php/index.vue';
const props = defineProps({
id: {
type: Number,
default: -1,
website: {
type: Object,
},
});
const id = computed(() => {
return props.id;
return props.website.id;
});
const tabIndex = ref('0');

View file

@ -24,7 +24,6 @@
<el-form-item prop="IPV6">
<el-checkbox v-model="form.IPV6" :label="$t('website.ipv6')" size="large" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit(websiteForm)" :disabled="loading">
{{ $t('commons.button.save') }}

View file

@ -0,0 +1,91 @@
<template>
<div v-loading="loading">
<el-row>
<el-col :xs="20" :sm="12" :md="10" :lg="10" :xl="8" :offset="1">
<el-form>
<div v-if="website.type === 'static'">
<el-text type="info">{{ $t('website.staticChangePHPHelper') }}</el-text>
</div>
<el-form-item :label="$t('website.changeVersion')">
<el-select v-model="versionReq.runtimeID" style="width: 100%">
<el-option :key="-1" :label="$t('website.static')" :value="0"></el-option>
<el-option
v-for="(item, index) in versions"
:key="index"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit()" :disabled="versionReq.runtimeID === oldRuntimeID">
{{ $t('commons.button.save') }}
</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { SearchRuntimes } from '@/api/modules/runtime';
import { onMounted, reactive, ref } from 'vue';
import { Runtime } from '@/api/interface/runtime';
import { Website } from '@/api/interface/website';
import { ChangePHPVersion } from '@/api/modules/website';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
const props = defineProps({
website: {
type: Object,
},
});
const runtimeReq = reactive<Runtime.RuntimeReq>({ page: 1, pageSize: 200, type: 'php' });
const versionReq = reactive<Website.PHPVersionChange>({
websiteID: undefined,
runtimeID: undefined,
});
const versions = ref([]);
const loading = ref(false);
const oldRuntimeID = ref(0);
const getRuntimes = async () => {
try {
loading.value = true;
const res = await SearchRuntimes(runtimeReq);
const items = res.data.items || [];
for (const item of items) {
versions.value.push({
value: item.id,
label: item.name + ' [' + i18n.global.t('runtime.version') + ':' + item.params['PHP_VERSION'] + ']',
});
}
} catch (error) {}
loading.value = false;
};
const submit = async () => {
try {
ElMessageBox.confirm(i18n.global.t('website.changePHPVersionWarn'), i18n.global.t('website.changeVersion'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
}).then(async () => {
loading.value = true;
try {
await ChangePHPVersion(versionReq);
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
} catch (error) {}
loading.value = false;
});
} catch (error) {}
};
onMounted(() => {
versionReq.runtimeID = props.website.runtimeID;
versionReq.websiteID = props.website.id;
oldRuntimeID.value = props.website.runtimeID;
getRuntimes();
});
</script>

View file

@ -27,16 +27,12 @@
<el-button type="primary" :plain="index !== 'resource'" @click="changeTab('resource')">
{{ $t('website.source') }}
</el-button>
<el-button type="primary" v-if="configPHP" :plain="index !== 'php'" @click="changeTab('php')">
PHP
</el-button>
</template>
<template #main>
<MainDiv :heightDiff="320">
<Basic :id="id" v-if="index === 'basic'"></Basic>
<Basic :website="website" v-if="index === 'basic'"></Basic>
<Log :id="id" v-if="index === 'log'"></Log>
<Resource :id="id" v-if="index === 'resource'"></Resource>
<PHP :id="id" v-if="index === 'php'"></PHP>
</MainDiv>
</template>
</LayoutContent>
@ -48,7 +44,6 @@ import { onMounted, ref, watch } from 'vue';
import Basic from './basic/index.vue';
import Resource from './resource/index.vue';
import Log from './log/index.vue';
import PHP from './php/index.vue';
import router from '@/routers';
import WebsiteStatus from '@/views/website/website/status/index.vue';
import { GetWebsite } from '@/api/modules/website';
@ -65,10 +60,10 @@ const props = defineProps({
},
});
let id = ref(0);
let index = ref('basic');
let website = ref<any>({});
let loading = ref(false);
const id = ref(0);
const index = ref('basic');
const website = ref<any>({});
const loading = ref(false);
const configPHP = ref(false);
watch(index, (curr, old) => {

View file

@ -3,7 +3,6 @@
<el-tab-pane :label="'OpenResty'" name="0">
<Nginx :id="id" v-if="index == '0'"></Nginx>
</el-tab-pane>
</el-tabs>
</template>
@ -13,7 +12,6 @@ import { GetWebsite } from '@/api/modules/website';
import { computed, onMounted, ref } from 'vue';
import Nginx from './nginx/index.vue';
const props = defineProps({
id: {
type: Number,