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

feat: 增加配置网站运行目录功能 (#675)

This commit is contained in:
zhengkunwang223 2023-04-17 16:54:34 +08:00 committed by GitHub
parent 2944ea508e
commit 1086597e3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 404 additions and 48 deletions

View File

@ -604,3 +604,25 @@ func (b *BaseApi) UpdateRewriteConfig(c *gin.Context) {
} }
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags Website
// @Summary Update Site Dir
// @Description 更新网站目录
// @Accept json
// @Param request body request.WebsiteUpdateDir true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/dir/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"websites","output_colume":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录","formatEN":"Update domain [domain] dir"}
func (b *BaseApi) UpdateSiteDir(c *gin.Context) {
var req request.WebsiteUpdateDir
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateSiteDir(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@ -145,3 +145,8 @@ type WebsitePHPFileUpdate struct {
Type string `json:"type" validate:"required"` Type string `json:"type" validate:"required"`
Content string `json:"content" validate:"required"` Content string `json:"content" validate:"required"`
} }
type WebsiteUpdateDir struct {
ID uint `json:"id" validate:"required"`
SiteDir string `json:"siteDir" validate:"required"`
}

View File

@ -4,26 +4,30 @@ import "time"
type Website struct { type Website struct {
BaseModel BaseModel
Protocol string `gorm:"type:varchar(64);not null" json:"protocol"` Protocol string `gorm:"type:varchar;not null" json:"protocol"`
PrimaryDomain string `gorm:"type:varchar(128);not null" json:"primaryDomain"` PrimaryDomain string `gorm:"type:varchar;not null" json:"primaryDomain"`
Type string `gorm:"type:varchar(64);not null" json:"type"` Type string `gorm:"type:varchar;not null" json:"type"`
Alias string `gorm:"type:varchar(128);not null" json:"alias"` Alias string `gorm:"type:varchar;not null" json:"alias"`
Remark string `gorm:"type:longtext;" json:"remark"` Remark string `gorm:"type:longtext;" json:"remark"`
Status string `gorm:"type:varchar(64);not null" json:"status"` Status string `gorm:"type:varchar;not null" json:"status"`
HttpConfig string `gorm:"type:varchar(64);not null" json:"httpConfig"` HttpConfig string `gorm:"type:varchar;not null" json:"httpConfig"`
ExpireDate time.Time `json:"expireDate"` ExpireDate time.Time `json:"expireDate"`
AppInstallID uint `gorm:"type:integer" json:"appInstallId"`
WebsiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"` Proxy string `gorm:"type:varchar;" json:"proxy"`
WebsiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"` ProxyType string `gorm:"type:varchar;" json:"proxyType"`
Proxy string `gorm:"type:varchar(128);not null" json:"proxy"` SiteDir string `gorm:"type:varchar;" json:"siteDir"`
ProxyType string `gorm:"type:varchar;" json:"proxyType"` ErrorLog bool `json:"errorLog"`
ErrorLog bool `json:"errorLog"` AccessLog bool `json:"accessLog"`
AccessLog bool `json:"accessLog"` DefaultServer bool `json:"defaultServer"`
DefaultServer bool `json:"defaultServer"` Rewrite string `gorm:"type:varchar" json:"rewrite"`
Rewrite string `gorm:"type:varchar" json:"rewrite"`
RuntimeID uint `gorm:"type:integer" json:"runtimeID"` WebsiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"`
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"` WebsiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"`
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"` RuntimeID uint `gorm:"type:integer" json:"runtimeID"`
AppInstallID uint `gorm:"type:integer" json:"appInstallId"`
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"`
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"`
} }
func (w Website) TableName() string { func (w Website) TableName() string {

View File

@ -61,6 +61,7 @@ type IWebsiteService interface {
UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) error UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) error
GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error)
UpdateRewriteConfig(req request.NginxRewriteUpdate) error UpdateRewriteConfig(req request.NginxRewriteUpdate) error
UpdateSiteDir(req request.WebsiteUpdateDir) error
} }
func NewIWebsiteService() IWebsiteService { func NewIWebsiteService() IWebsiteService {
@ -147,6 +148,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
WebsiteGroupID: create.WebsiteGroupID, WebsiteGroupID: create.WebsiteGroupID,
Protocol: constant.ProtocolHTTP, Protocol: constant.ProtocolHTTP,
Proxy: create.Proxy, Proxy: create.Proxy,
SiteDir: "/",
AccessLog: true, AccessLog: true,
ErrorLog: true, ErrorLog: true,
} }
@ -1056,3 +1058,20 @@ func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response
Content: string(contentByte), Content: string(contentByte),
}, err }, err
} }
func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
runDir := req.SiteDir
siteDir := path.Join("/www/sites", website.Alias, "index")
if req.SiteDir != "/" {
siteDir = fmt.Sprintf("%s/%s", siteDir, req.SiteDir)
}
if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "root", Params: []string{siteDir}}}, &website); err != nil {
return err
}
website.SiteDir = runDir
return websiteRepo.Save(context.Background(), &website)
}

View File

@ -73,7 +73,7 @@ func createIndexFile(website *model.Website, runtime *model.Runtime) error {
return err return err
} }
} }
if runtime.Resource == constant.ResourceAppstore { if website.Type == constant.Runtime && runtime.Resource == constant.ResourceAppstore {
if err := chownRootDir(indexFolder); err != nil { if err := chownRootDir(indexFolder); err != nil {
return err return err
} }

View File

@ -276,8 +276,14 @@ var UpdateTableHost = &gormigrate.Migration{
} }
var UpdateTableWebsite = &gormigrate.Migration{ var UpdateTableWebsite = &gormigrate.Migration{
ID: "20230414-update-table-website", ID: "20230417-update-table-website",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.Website{}) if err := tx.AutoMigrate(&model.Website{}); err != nil {
return err
}
if err := tx.Model(&model.Website{}).Where("1 = 1").Update("site_dir", "/").Error; err != nil {
return err
}
return nil
}, },
} }

View File

@ -48,5 +48,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
groupRouter.POST("/rewrite", baseApi.GetRewriteConfig) groupRouter.POST("/rewrite", baseApi.GetRewriteConfig)
groupRouter.POST("/rewrite/update", baseApi.UpdateRewriteConfig) groupRouter.POST("/rewrite/update", baseApi.UpdateRewriteConfig)
groupRouter.POST("/dir/update", baseApi.UpdateSiteDir)
} }
} }

View File

@ -8083,6 +8083,57 @@ var doc = `{
} }
} }
}, },
"/websites/dir/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新网站目录",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update Site Dir",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteUpdateDir"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update domain [domain] dir",
"formatZH": "更新网站 [domain] 目录",
"paramKeys": []
}
}
},
"/websites/dns": { "/websites/dns": {
"post": { "post": {
"security": [ "security": [
@ -11918,6 +11969,9 @@ var doc = `{
"runtimeID": { "runtimeID": {
"type": "integer" "type": "integer"
}, },
"siteDir": {
"type": "string"
},
"status": { "status": {
"type": "string" "type": "string"
}, },
@ -13117,6 +13171,21 @@ var doc = `{
} }
} }
}, },
"request.WebsiteUpdateDir": {
"type": "object",
"required": [
"id",
"siteDir"
],
"properties": {
"id": {
"type": "integer"
},
"siteDir": {
"type": "string"
}
}
},
"request.WebsiteWafReq": { "request.WebsiteWafReq": {
"type": "object", "type": "object",
"required": [ "required": [
@ -13533,6 +13602,9 @@ var doc = `{
"runtimeName": { "runtimeName": {
"type": "string" "type": "string"
}, },
"siteDir": {
"type": "string"
},
"sitePath": { "sitePath": {
"type": "string" "type": "string"
}, },

View File

@ -8069,6 +8069,57 @@
} }
} }
}, },
"/websites/dir/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新网站目录",
"consumes": [
"application/json"
],
"tags": [
"Website"
],
"summary": "Update Site Dir",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteUpdateDir"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "websites",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Update domain [domain] dir",
"formatZH": "更新网站 [domain] 目录",
"paramKeys": []
}
}
},
"/websites/dns": { "/websites/dns": {
"post": { "post": {
"security": [ "security": [
@ -11904,6 +11955,9 @@
"runtimeID": { "runtimeID": {
"type": "integer" "type": "integer"
}, },
"siteDir": {
"type": "string"
},
"status": { "status": {
"type": "string" "type": "string"
}, },
@ -13103,6 +13157,21 @@
} }
} }
}, },
"request.WebsiteUpdateDir": {
"type": "object",
"required": [
"id",
"siteDir"
],
"properties": {
"id": {
"type": "integer"
},
"siteDir": {
"type": "string"
}
}
},
"request.WebsiteWafReq": { "request.WebsiteWafReq": {
"type": "object", "type": "object",
"required": [ "required": [
@ -13519,6 +13588,9 @@
"runtimeName": { "runtimeName": {
"type": "string" "type": "string"
}, },
"siteDir": {
"type": "string"
},
"sitePath": { "sitePath": {
"type": "string" "type": "string"
}, },

View File

@ -1746,6 +1746,8 @@ definitions:
type: string type: string
runtimeID: runtimeID:
type: integer type: integer
siteDir:
type: string
status: status:
type: string type: string
type: type:
@ -2548,6 +2550,16 @@ definitions:
- primaryDomain - primaryDomain
- webSiteGroupID - webSiteGroupID
type: object type: object
request.WebsiteUpdateDir:
properties:
id:
type: integer
siteDir:
type: string
required:
- id
- siteDir
type: object
request.WebsiteWafReq: request.WebsiteWafReq:
properties: properties:
key: key:
@ -2824,6 +2836,8 @@ definitions:
type: integer type: integer
runtimeName: runtimeName:
type: string type: string
siteDir:
type: string
sitePath: sitePath:
type: string type: string
status: status:
@ -8009,6 +8023,39 @@ paths:
formatEN: Delete website [domain] formatEN: Delete website [domain]
formatZH: 删除网站 [domain] formatZH: 删除网站 [domain]
paramKeys: [] paramKeys: []
/websites/dir/update:
post:
consumes:
- application/json
description: 更新网站目录
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteUpdateDir'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update Site Dir
tags:
- Website
x-panel-log:
BeforeFuntions:
- db: websites
input_colume: id
input_value: id
isList: false
output_colume: primary_domain
output_value: domain
bodyKeys:
- id
formatEN: Update domain [domain] dir
formatZH: 更新网站 [domain] 目录
paramKeys: []
/websites/dns: /websites/dns:
post: post:
consumes: consumes:

View File

@ -294,4 +294,9 @@ export namespace Website {
name: string; name: string;
content: string; content: string;
} }
export interface DirUpdate {
id: number;
siteDir: string;
}
} }

View File

@ -178,3 +178,7 @@ export const GetRewriteConfig = (req: Website.RewriteReq) => {
export const UpdateRewriteConfig = (req: Website.RewriteUpdate) => { export const UpdateRewriteConfig = (req: Website.RewriteUpdate) => {
return http.post<any>(`/websites/rewrite/update`, req); return http.post<any>(`/websites/rewrite/update`, req);
}; };
export const UpdateWebsiteDir = (req: Website.DirUpdate) => {
return http.post<any>(`/websites/dir/update`, req);
};

View File

@ -1171,6 +1171,9 @@ const message = {
current: 'Current', current: 'Current',
rewriteHelper: rewriteHelper:
'If the website cannot be accessed normally after setting pseudo-static, please try to set it back to default', 'If the website cannot be accessed normally after setting pseudo-static, please try to set it back to default',
runDir: 'Run Directory',
runDirHelper:
'Some programs need to specify a secondary directory as the running directory, such as ThinkPHP5, Laravel',
}, },
php: { php: {
short_open_tag: 'Short tag support', short_open_tag: 'Short tag support',

View File

@ -1163,6 +1163,8 @@ const message = {
rewriteMode: '方案', rewriteMode: '方案',
current: '当前', current: '当前',
rewriteHelper: '若设置伪静态后网站无法正常访问请尝试设置回default', rewriteHelper: '若设置伪静态后网站无法正常访问请尝试设置回default',
runDir: '运行目录',
runDirHelper: '部分程序需要指定二级目录作为运行目录如ThinkPHP5Laravel',
}, },
php: { php: {
short_open_tag: '短标签支持', short_open_tag: '短标签支持',

View File

@ -1,30 +1,62 @@
<template> <template>
<el-row :gutter="20"> <div v-loading="loading">
<el-col :span="14" :offset="1"> <el-row :gutter="20">
<br /> <el-col :span="14" :offset="1">
<el-descriptions :column="1" border v-loading="loading"> <br />
<el-descriptions-item :label="$t('website.siteAlias')">{{ website.alias }}</el-descriptions-item> <el-descriptions :column="1" border>
<el-descriptions-item :label="$t('website.primaryPath')"> <el-descriptions-item :label="$t('website.siteAlias')">{{ website.alias }}</el-descriptions-item>
{{ website.sitePath }} <el-descriptions-item :label="$t('website.primaryPath')">
<el-button type="primary" link @click="toFolder(website.sitePath)"> {{ website.sitePath }}
<el-icon><FolderOpened /></el-icon> <el-button type="primary" link @click="toFolder(website.sitePath)">
</el-button> <el-icon><FolderOpened /></el-icon>
</el-descriptions-item> </el-button>
</el-descriptions> </el-descriptions-item>
<br /> </el-descriptions>
<br />
<el-descriptions :title="$t('website.folderTitle')" :column="1" border> <el-descriptions :title="$t('website.folderTitle')" :column="1" border>
<el-descriptions-item label="waf">{{ $t('website.wafFolder') }}</el-descriptions-item> <el-descriptions-item label="waf">{{ $t('website.wafFolder') }}</el-descriptions-item>
<el-descriptions-item label="ssl">{{ $t('website.sslFolder') }}</el-descriptions-item> <el-descriptions-item label="ssl">{{ $t('website.sslFolder') }}</el-descriptions-item>
<el-descriptions-item label="log">{{ $t('website.logFoler') }}</el-descriptions-item> <el-descriptions-item label="log">{{ $t('website.logFoler') }}</el-descriptions-item>
<el-descriptions-item label="index">{{ $t('website.indexFolder') }}</el-descriptions-item> <el-descriptions-item label="index">{{ $t('website.indexFolder') }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-col> </el-col>
</el-row> <el-col :span="14" :offset="1" v-if="configDir">
<br />
<el-form :inline="true" ref="siteForm" :model="update">
<el-form-item :label="$t('website.runDir')" prop="runDir">
<el-select v-model="update.siteDir">
<el-option :label="'/'" :value="'/'"></el-option>
<el-option
v-for="(item, index) in dirs"
:label="item"
:value="item"
:key="index"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit(siteForm)">{{ $t('nginx.saveAndReload') }}</el-button>
</el-form-item>
</el-form>
<el-form-item>
<el-alert :closable="false">
<template #default>
<span style="white-space: pre-line">{{ $t('website.runDirHelper') }}</span>
</template>
</el-alert>
</el-form-item>
</el-col>
</el-row>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { GetWebsite } from '@/api/modules/website'; import { GetFilesList } from '@/api/modules/files';
import { computed, onMounted, ref } from 'vue'; import { GetWebsite, UpdateWebsiteDir } from '@/api/modules/website';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { computed, onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
const router = useRouter(); const router = useRouter();
@ -37,25 +69,86 @@ const props = defineProps({
const websiteId = computed(() => { const websiteId = computed(() => {
return Number(props.id); return Number(props.id);
}); });
let website = ref<any>({}); const website = ref<any>({});
let loading = ref(false); const loading = ref(false);
const configDir = ref(false);
const update = reactive({
id: 0,
siteDir: '/',
});
const siteForm = ref<FormInstance>();
const dirReq = reactive({
path: '/',
expand: true,
showHidden: false,
page: 1,
pageSize: 100,
search: '',
containSub: false,
dir: true,
});
const dirs = ref([]);
const search = () => { const search = () => {
loading.value = true; loading.value = true;
GetWebsite(websiteId.value) GetWebsite(websiteId.value)
.then((res) => { .then((res) => {
website.value = res.data; website.value = res.data;
update.id = website.value.id;
update.siteDir = website.value.siteDir;
if (website.value.type === 'static' || website.value.runtimeID > 0) {
configDir.value = true;
dirReq.path = website.value.sitePath + '/index';
getDirs();
}
}) })
.finally(() => { .finally(() => {
loading.value = false; loading.value = false;
}); });
}; };
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
UpdateWebsiteDir(update)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
search();
})
.finally(() => {
loading.value = false;
});
});
};
const getDirs = async () => {
loading.value = true;
await GetFilesList(dirReq)
.then((res) => {
const items = res.data.items || [];
for (const item of items) {
dirs.value.push(item.name);
}
})
.finally(() => {
loading.value = false;
});
};
const initData = () => {
dirs.value = [];
};
const toFolder = (folder: string) => { const toFolder = (folder: string) => {
router.push({ path: '/hosts/files', query: { path: folder } }); router.push({ path: '/hosts/files', query: { path: folder } });
}; };
onMounted(() => { onMounted(() => {
initData();
search(); search();
}); });
</script> </script>