Browse Source

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

pull/676/head
zhengkunwang223 2 years ago committed by GitHub
parent
commit
1086597e3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      backend/app/api/v1/website.go
  2. 5
      backend/app/dto/request/website.go
  3. 44
      backend/app/model/website.go
  4. 19
      backend/app/service/website.go
  5. 2
      backend/app/service/website_utils.go
  6. 10
      backend/init/migration/migrations/init.go
  7. 2
      backend/router/ro_website.go
  8. 72
      cmd/server/docs/docs.go
  9. 72
      cmd/server/docs/swagger.json
  10. 47
      cmd/server/docs/swagger.yaml
  11. 5
      frontend/src/api/interface/website.ts
  12. 4
      frontend/src/api/modules/website.ts
  13. 3
      frontend/src/lang/modules/en.ts
  14. 2
      frontend/src/lang/modules/zh.ts
  15. 145
      frontend/src/views/website/website/config/basic/site-folder/index.vue

22
backend/app/api/v1/website.go

@ -604,3 +604,25 @@ func (b *BaseApi) UpdateRewriteConfig(c *gin.Context) {
}
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)
}

5
backend/app/dto/request/website.go

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

44
backend/app/model/website.go

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

19
backend/app/service/website.go

@ -61,6 +61,7 @@ type IWebsiteService interface {
UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) error
GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error)
UpdateRewriteConfig(req request.NginxRewriteUpdate) error
UpdateSiteDir(req request.WebsiteUpdateDir) error
}
func NewIWebsiteService() IWebsiteService {
@ -147,6 +148,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
WebsiteGroupID: create.WebsiteGroupID,
Protocol: constant.ProtocolHTTP,
Proxy: create.Proxy,
SiteDir: "/",
AccessLog: true,
ErrorLog: true,
}
@ -1056,3 +1058,20 @@ func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response
Content: string(contentByte),
}, 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)
}

2
backend/app/service/website_utils.go

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

10
backend/init/migration/migrations/init.go

@ -276,8 +276,14 @@ var UpdateTableHost = &gormigrate.Migration{
}
var UpdateTableWebsite = &gormigrate.Migration{
ID: "20230414-update-table-website",
ID: "20230417-update-table-website",
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
},
}

2
backend/router/ro_website.go

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

72
cmd/server/docs/docs.go

@ -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": {
"post": {
"security": [
@ -11918,6 +11969,9 @@ var doc = `{
"runtimeID": {
"type": "integer"
},
"siteDir": {
"type": "string"
},
"status": {
"type": "string"
},
@ -13117,6 +13171,21 @@ var doc = `{
}
}
},
"request.WebsiteUpdateDir": {
"type": "object",
"required": [
"id",
"siteDir"
],
"properties": {
"id": {
"type": "integer"
},
"siteDir": {
"type": "string"
}
}
},
"request.WebsiteWafReq": {
"type": "object",
"required": [
@ -13533,6 +13602,9 @@ var doc = `{
"runtimeName": {
"type": "string"
},
"siteDir": {
"type": "string"
},
"sitePath": {
"type": "string"
},

72
cmd/server/docs/swagger.json

@ -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": {
"post": {
"security": [
@ -11904,6 +11955,9 @@
"runtimeID": {
"type": "integer"
},
"siteDir": {
"type": "string"
},
"status": {
"type": "string"
},
@ -13103,6 +13157,21 @@
}
}
},
"request.WebsiteUpdateDir": {
"type": "object",
"required": [
"id",
"siteDir"
],
"properties": {
"id": {
"type": "integer"
},
"siteDir": {
"type": "string"
}
}
},
"request.WebsiteWafReq": {
"type": "object",
"required": [
@ -13519,6 +13588,9 @@
"runtimeName": {
"type": "string"
},
"siteDir": {
"type": "string"
},
"sitePath": {
"type": "string"
},

47
cmd/server/docs/swagger.yaml

@ -1746,6 +1746,8 @@ definitions:
type: string
runtimeID:
type: integer
siteDir:
type: string
status:
type: string
type:
@ -2548,6 +2550,16 @@ definitions:
- primaryDomain
- webSiteGroupID
type: object
request.WebsiteUpdateDir:
properties:
id:
type: integer
siteDir:
type: string
required:
- id
- siteDir
type: object
request.WebsiteWafReq:
properties:
key:
@ -2824,6 +2836,8 @@ definitions:
type: integer
runtimeName:
type: string
siteDir:
type: string
sitePath:
type: string
status:
@ -8009,6 +8023,39 @@ paths:
formatEN: Delete website [domain]
formatZH: 删除网站 [domain]
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:
post:
consumes:

5
frontend/src/api/interface/website.ts

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

4
frontend/src/api/modules/website.ts

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

3
frontend/src/lang/modules/en.ts

@ -1171,6 +1171,9 @@ const message = {
current: 'Current',
rewriteHelper:
'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: {
short_open_tag: 'Short tag support',

2
frontend/src/lang/modules/zh.ts

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

145
frontend/src/views/website/website/config/basic/site-folder/index.vue

@ -1,30 +1,62 @@
<template>
<el-row :gutter="20">
<el-col :span="14" :offset="1">
<br />
<el-descriptions :column="1" border v-loading="loading">
<el-descriptions-item :label="$t('website.siteAlias')">{{ website.alias }}</el-descriptions-item>
<el-descriptions-item :label="$t('website.primaryPath')">
{{ website.sitePath }}
<el-button type="primary" link @click="toFolder(website.sitePath)">
<el-icon><FolderOpened /></el-icon>
</el-button>
</el-descriptions-item>
</el-descriptions>
<br />
<el-descriptions :title="$t('website.folderTitle')" :column="1" border>
<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="log">{{ $t('website.logFoler') }}</el-descriptions-item>
<el-descriptions-item label="index">{{ $t('website.indexFolder') }}</el-descriptions-item>
</el-descriptions>
</el-col>
</el-row>
<div v-loading="loading">
<el-row :gutter="20">
<el-col :span="14" :offset="1">
<br />
<el-descriptions :column="1" border>
<el-descriptions-item :label="$t('website.siteAlias')">{{ website.alias }}</el-descriptions-item>
<el-descriptions-item :label="$t('website.primaryPath')">
{{ website.sitePath }}
<el-button type="primary" link @click="toFolder(website.sitePath)">
<el-icon><FolderOpened /></el-icon>
</el-button>
</el-descriptions-item>
</el-descriptions>
<br />
<el-descriptions :title="$t('website.folderTitle')" :column="1" border>
<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="log">{{ $t('website.logFoler') }}</el-descriptions-item>
<el-descriptions-item label="index">{{ $t('website.indexFolder') }}</el-descriptions-item>
</el-descriptions>
</el-col>
<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>
<script lang="ts" setup>
import { GetWebsite } from '@/api/modules/website';
import { computed, onMounted, ref } from 'vue';
import { GetFilesList } from '@/api/modules/files';
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';
const router = useRouter();
@ -37,25 +69,86 @@ const props = defineProps({
const websiteId = computed(() => {
return Number(props.id);
});
let website = ref<any>({});
let loading = ref(false);
const website = ref<any>({});
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 = () => {
loading.value = true;
GetWebsite(websiteId.value)
.then((res) => {
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(() => {
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) => {
router.push({ path: '/hosts/files', query: { path: folder } });
};
onMounted(() => {
initData();
search();
});
</script>

Loading…
Cancel
Save