mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-08 16:14:06 +08:00
feat: 增加静态网站和 PHP 运行环境网站互相切换功能 (#6452)
Some checks failed
sync2gitee / repo-sync (push) Failing after -8m57s
Some checks failed
sync2gitee / repo-sync (push) Failing after -8m57s
Refs https://github.com/1Panel-dev/1Panel/issues/2532
This commit is contained in:
parent
ad3670c9ad
commit
88961e4ab7
13 changed files with 171 additions and 124 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -472,7 +472,6 @@ export namespace Website {
|
|||
export interface PHPVersionChange {
|
||||
websiteID: number;
|
||||
runtimeID: number;
|
||||
retainConfig: boolean;
|
||||
}
|
||||
|
||||
export interface DirConfig {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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: '短標簽支持',
|
||||
|
|
|
|||
|
|
@ -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: '短标签支持',
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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') }}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue