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

feat: PHP 运行环境增加扩展模版 (#3502)

Refs https://github.com/1Panel-dev/1Panel/issues/1636
This commit is contained in:
zhengkunwang 2024-01-02 21:54:28 +08:00 committed by GitHub
parent a8e10d0d71
commit b8a89d86a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1231 additions and 14 deletions

View File

@ -21,10 +21,10 @@ var (
imageService = service.NewIImageService() imageService = service.NewIImageService()
dockerService = service.NewIDockerService() dockerService = service.NewIDockerService()
mysqlService = service.NewIMysqlService() mysqlService = service.NewIMysqlService()
postgresqlService = service.NewIPostgresqlService() postgresqlService = service.NewIPostgresqlService()
databaseService = service.NewIDatabaseService() databaseService = service.NewIDatabaseService()
redisService = service.NewIRedisService() redisService = service.NewIRedisService()
cronjobService = service.NewICronjobService() cronjobService = service.NewICronjobService()
@ -53,8 +53,9 @@ var (
snapshotService = service.NewISnapshotService() snapshotService = service.NewISnapshotService()
upgradeService = service.NewIUpgradeService() upgradeService = service.NewIUpgradeService()
runtimeService = service.NewRuntimeService() runtimeService = service.NewRuntimeService()
processService = service.NewIProcessService() processService = service.NewIProcessService()
phpExtensionsService = service.NewIPHPExtensionsService()
hostToolService = service.NewIHostToolService() hostToolService = service.NewIHostToolService()

View File

@ -0,0 +1,103 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
// @Tags PHP Extensions
// @Summary Page Extensions
// @Description Page Extensions
// @Accept json
// @Param request body request.PHPExtensionsSearch true "request"
// @Success 200 {array} response.PHPExtensionsDTO
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions/search [post]
func (b *BaseApi) PagePHPExtensions(c *gin.Context) {
var req request.PHPExtensionsSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if req.All {
list, err := phpExtensionsService.List()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
} else {
total, list, err := phpExtensionsService.Page(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: list,
})
}
}
// @Tags PHP Extensions
// @Summary Create Extensions
// @Description Create Extensions
// @Accept json
// @Param request body request.PHPExtensionsCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions [post]
func (b *BaseApi) CreatePHPExtensions(c *gin.Context) {
var req request.PHPExtensionsCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := phpExtensionsService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags PHP Extensions
// @Summary Update Extensions
// @Description Update Extensions
// @Accept json
// @Param request body request.PHPExtensionsUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions/update [post]
func (b *BaseApi) UpdatePHPExtensions(c *gin.Context) {
var req request.PHPExtensionsUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := phpExtensionsService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags PHP Extensions
// @Summary Delete Extensions
// @Description Delete Extensions
// @Accept json
// @Param request body request.PHPExtensionsDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions/del [post]
func (b *BaseApi) DeletePHPExtensions(c *gin.Context) {
var req request.PHPExtensionsDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := phpExtensionsService.Delete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@ -0,0 +1,22 @@
package request
import "github.com/1Panel-dev/1Panel/backend/app/dto"
type PHPExtensionsSearch struct {
dto.PageInfo
All bool `json:"all"`
}
type PHPExtensionsCreate struct {
Name string `json:"name" validate:"required"`
Extensions string `json:"extensions" validate:"required"`
}
type PHPExtensionsUpdate struct {
ID uint `json:"id" validate:"required"`
Extensions string `json:"extensions" validate:"required"`
}
type PHPExtensionsDelete struct {
ID uint `json:"id" validate:"required"`
}

View File

@ -0,0 +1,7 @@
package response
import "github.com/1Panel-dev/1Panel/backend/app/model"
type PHPExtensionsDTO struct {
model.PHPExtensions
}

View File

@ -0,0 +1,7 @@
package model
type PHPExtensions struct {
BaseModel
Name string ` json:"name" gorm:"not null"`
Extensions string `json:"extensions" gorm:"not null"`
}

View File

@ -0,0 +1,59 @@
package repo
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type PHPExtensionsRepo struct {
}
type IPHPExtensionsRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.PHPExtensions, error)
Save(extension *model.PHPExtensions) error
Create(extension *model.PHPExtensions) error
GetFirst(opts ...DBOption) (model.PHPExtensions, error)
DeleteBy(opts ...DBOption) error
List() ([]model.PHPExtensions, error)
}
func NewIPHPExtensionsRepo() IPHPExtensionsRepo {
return &PHPExtensionsRepo{}
}
func (p *PHPExtensionsRepo) Page(page, size int, opts ...DBOption) (int64, []model.PHPExtensions, error) {
var (
phpExtensions []model.PHPExtensions
)
db := getDb(opts...).Model(&model.PHPExtensions{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&phpExtensions).Error
return count, phpExtensions, err
}
func (p *PHPExtensionsRepo) List() ([]model.PHPExtensions, error) {
var (
phpExtensions []model.PHPExtensions
)
err := getDb().Model(&model.PHPExtensions{}).Find(&phpExtensions).Error
return phpExtensions, err
}
func (p *PHPExtensionsRepo) Save(extension *model.PHPExtensions) error {
return getDb().Save(&extension).Error
}
func (p *PHPExtensionsRepo) Create(extension *model.PHPExtensions) error {
return getDb().Create(&extension).Error
}
func (p *PHPExtensionsRepo) GetFirst(opts ...DBOption) (model.PHPExtensions, error) {
var extension model.PHPExtensions
db := getDb(opts...).Model(&model.PHPExtensions{})
err := db.First(&extension).Error
return extension, err
}
func (p *PHPExtensionsRepo) DeleteBy(opts ...DBOption) error {
return getDb(opts...).Delete(&model.PHPExtensions{}).Error
}

View File

@ -12,9 +12,9 @@ var (
appInstallRepo = repo.NewIAppInstallRepo() appInstallRepo = repo.NewIAppInstallRepo()
appInstallResourceRepo = repo.NewIAppInstallResourceRpo() appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
mysqlRepo = repo.NewIMysqlRepo() mysqlRepo = repo.NewIMysqlRepo()
postgresqlRepo = repo.NewIPostgresqlRepo() postgresqlRepo = repo.NewIPostgresqlRepo()
databaseRepo = repo.NewIDatabaseRepo() databaseRepo = repo.NewIDatabaseRepo()
imageRepoRepo = repo.NewIImageRepoRepo() imageRepoRepo = repo.NewIImageRepoRepo()
composeRepo = repo.NewIComposeTemplateRepo() composeRepo = repo.NewIComposeTemplateRepo()
@ -38,7 +38,8 @@ var (
logRepo = repo.NewILogRepo() logRepo = repo.NewILogRepo()
snapshotRepo = repo.NewISnapshotRepo() snapshotRepo = repo.NewISnapshotRepo()
runtimeRepo = repo.NewIRunTimeRepo() runtimeRepo = repo.NewIRunTimeRepo()
phpExtensionsRepo = repo.NewIPHPExtensionsRepo()
favoriteRepo = repo.NewIFavoriteRepo() favoriteRepo = repo.NewIFavoriteRepo()
) )

View File

@ -0,0 +1,86 @@
package service
import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
)
type PHPExtensionsService struct {
}
type IPHPExtensionsService interface {
Page(req request.PHPExtensionsSearch) (int64, []response.PHPExtensionsDTO, error)
List() ([]response.PHPExtensionsDTO, error)
Create(req request.PHPExtensionsCreate) error
Update(req request.PHPExtensionsUpdate) error
Delete(req request.PHPExtensionsDelete) error
}
func NewIPHPExtensionsService() IPHPExtensionsService {
return &PHPExtensionsService{}
}
func (p PHPExtensionsService) Page(req request.PHPExtensionsSearch) (int64, []response.PHPExtensionsDTO, error) {
var (
total int64
extensions []model.PHPExtensions
err error
result []response.PHPExtensionsDTO
)
total, extensions, err = phpExtensionsRepo.Page(req.Page, req.PageSize)
if err != nil {
return 0, nil, err
}
for _, extension := range extensions {
result = append(result, response.PHPExtensionsDTO{
PHPExtensions: extension,
})
}
return total, result, nil
}
func (p PHPExtensionsService) List() ([]response.PHPExtensionsDTO, error) {
var (
extensions []model.PHPExtensions
err error
result []response.PHPExtensionsDTO
)
extensions, err = phpExtensionsRepo.List()
if err != nil {
return nil, err
}
for _, extension := range extensions {
result = append(result, response.PHPExtensionsDTO{
PHPExtensions: extension,
})
}
return result, nil
}
func (p PHPExtensionsService) Create(req request.PHPExtensionsCreate) error {
exist, _ := phpExtensionsRepo.GetFirst(commonRepo.WithByName(req.Name))
if exist.ID == 0 {
return buserr.New(constant.ErrNameIsExist)
}
extension := model.PHPExtensions{
Name: req.Name,
Extensions: req.Extensions,
}
return phpExtensionsRepo.Create(&extension)
}
func (p PHPExtensionsService) Update(req request.PHPExtensionsUpdate) error {
exist, err := phpExtensionsRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
exist.Extensions = req.Extensions
return phpExtensionsRepo.Save(&exist)
}
func (p PHPExtensionsService) Delete(req request.PHPExtensionsDelete) error {
return phpExtensionsRepo.DeleteBy(commonRepo.WithByID(req.ID))
}

View File

@ -62,6 +62,8 @@ func Init() {
migrations.AddDefaultCA, migrations.AddDefaultCA,
migrations.AddSettingRecycleBin, migrations.AddSettingRecycleBin,
migrations.UpdateWebsiteBackupRecord, migrations.UpdateWebsiteBackupRecord,
migrations.AddTablePHPExtensions,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -103,3 +103,16 @@ var UpdateWebsiteBackupRecord = &gormigrate.Migration{
return nil return nil
}, },
} }
var AddTablePHPExtensions = &gormigrate.Migration{
ID: "20240102-add-php-extensions",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.PHPExtensions{}); err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "默认", Extensions: "bcmath,gd,gettext,intl,pcntl,shmop,soap,sockets,sysvsem,xmlrpc,zip"}).Error; err != nil {
return err
}
return nil
},
}

View File

@ -20,10 +20,16 @@ func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) {
groupRouter.POST("/del", baseApi.DeleteRuntime) groupRouter.POST("/del", baseApi.DeleteRuntime)
groupRouter.POST("/update", baseApi.UpdateRuntime) groupRouter.POST("/update", baseApi.UpdateRuntime)
groupRouter.GET("/:id", baseApi.GetRuntime) groupRouter.GET("/:id", baseApi.GetRuntime)
groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript) groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript)
groupRouter.POST("/operate", baseApi.OperateRuntime) groupRouter.POST("/operate", baseApi.OperateRuntime)
groupRouter.POST("/node/modules", baseApi.GetNodeModules) groupRouter.POST("/node/modules", baseApi.GetNodeModules)
groupRouter.POST("/node/modules/operate", baseApi.OperateNodeModules) groupRouter.POST("/node/modules/operate", baseApi.OperateNodeModules)
groupRouter.POST("/php/extensions/search", baseApi.PagePHPExtensions)
groupRouter.POST("/php/extensions", baseApi.CreatePHPExtensions)
groupRouter.POST("/php/extensions/update", baseApi.UpdatePHPExtensions)
groupRouter.POST("/php/extensions/del", baseApi.DeletePHPExtensions)
} }
} }

View File

@ -8995,6 +8995,144 @@ const docTemplate = `{
} }
} }
}, },
"/runtimes/php/extensions": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Create Extensions",
"consumes": [
"application/json"
],
"tags": [
"PHP Extensions"
],
"summary": "Create Extensions",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.PHPExtensionsCreate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/runtimes/php/extensions/del": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Delete Extensions",
"consumes": [
"application/json"
],
"tags": [
"PHP Extensions"
],
"summary": "Delete Extensions",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.PHPExtensionsDelete"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/runtimes/php/extensions/search": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Page Extensions",
"consumes": [
"application/json"
],
"tags": [
"PHP Extensions"
],
"summary": "Page Extensions",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.PHPExtensionsSearch"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/response.PHPExtensionsDTO"
}
}
}
}
}
},
"/runtimes/php/extensions/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Update Extensions",
"consumes": [
"application/json"
],
"tags": [
"PHP Extensions"
],
"summary": "Update Extensions",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.PHPExtensionsUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/runtimes/search": { "/runtimes/search": {
"post": { "post": {
"security": [ "security": [
@ -19390,6 +19528,65 @@ const docTemplate = `{
} }
} }
}, },
"request.PHPExtensionsCreate": {
"type": "object",
"required": [
"extensions",
"name"
],
"properties": {
"extensions": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"request.PHPExtensionsDelete": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "integer"
}
}
},
"request.PHPExtensionsSearch": {
"type": "object",
"required": [
"page",
"pageSize"
],
"properties": {
"all": {
"type": "boolean"
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"request.PHPExtensionsUpdate": {
"type": "object",
"required": [
"extensions",
"id"
],
"properties": {
"extensions": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"request.PortUpdate": { "request.PortUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -20915,6 +21112,26 @@ const docTemplate = `{
} }
} }
}, },
"response.PHPExtensionsDTO": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"extensions": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"response.WebsiteAcmeAccountDTO": { "response.WebsiteAcmeAccountDTO": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -8988,6 +8988,144 @@
} }
} }
}, },
"/runtimes/php/extensions": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Create Extensions",
"consumes": [
"application/json"
],
"tags": [
"PHP Extensions"
],
"summary": "Create Extensions",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.PHPExtensionsCreate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/runtimes/php/extensions/del": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Delete Extensions",
"consumes": [
"application/json"
],
"tags": [
"PHP Extensions"
],
"summary": "Delete Extensions",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.PHPExtensionsDelete"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/runtimes/php/extensions/search": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Page Extensions",
"consumes": [
"application/json"
],
"tags": [
"PHP Extensions"
],
"summary": "Page Extensions",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.PHPExtensionsSearch"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/response.PHPExtensionsDTO"
}
}
}
}
}
},
"/runtimes/php/extensions/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Update Extensions",
"consumes": [
"application/json"
],
"tags": [
"PHP Extensions"
],
"summary": "Update Extensions",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.PHPExtensionsUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/runtimes/search": { "/runtimes/search": {
"post": { "post": {
"security": [ "security": [
@ -19383,6 +19521,65 @@
} }
} }
}, },
"request.PHPExtensionsCreate": {
"type": "object",
"required": [
"extensions",
"name"
],
"properties": {
"extensions": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"request.PHPExtensionsDelete": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "integer"
}
}
},
"request.PHPExtensionsSearch": {
"type": "object",
"required": [
"page",
"pageSize"
],
"properties": {
"all": {
"type": "boolean"
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"request.PHPExtensionsUpdate": {
"type": "object",
"required": [
"extensions",
"id"
],
"properties": {
"extensions": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"request.PortUpdate": { "request.PortUpdate": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -20908,6 +21105,26 @@
} }
} }
}, },
"response.PHPExtensionsDTO": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"extensions": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"response.WebsiteAcmeAccountDTO": { "response.WebsiteAcmeAccountDTO": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -3706,6 +3706,45 @@ definitions:
codeDir: codeDir:
type: string type: string
type: object type: object
request.PHPExtensionsCreate:
properties:
extensions:
type: string
name:
type: string
required:
- extensions
- name
type: object
request.PHPExtensionsDelete:
properties:
id:
type: integer
required:
- id
type: object
request.PHPExtensionsSearch:
properties:
all:
type: boolean
page:
type: integer
pageSize:
type: integer
required:
- page
- pageSize
type: object
request.PHPExtensionsUpdate:
properties:
extensions:
type: string
id:
type: integer
required:
- extensions
- id
type: object
request.PortUpdate: request.PortUpdate:
properties: properties:
key: key:
@ -4732,6 +4771,19 @@ definitions:
uploadMaxSize: uploadMaxSize:
type: string type: string
type: object type: object
response.PHPExtensionsDTO:
properties:
createdAt:
type: string
extensions:
type: string
id:
type: integer
name:
type: string
updatedAt:
type: string
type: object
response.WebsiteAcmeAccountDTO: response.WebsiteAcmeAccountDTO:
properties: properties:
createdAt: createdAt:
@ -10612,6 +10664,90 @@ paths:
formatEN: Operate runtime [name] formatEN: Operate runtime [name]
formatZH: 操作运行环境 [name] formatZH: 操作运行环境 [name]
paramKeys: [] paramKeys: []
/runtimes/php/extensions:
post:
consumes:
- application/json
description: Create Extensions
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.PHPExtensionsCreate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Create Extensions
tags:
- PHP Extensions
/runtimes/php/extensions/del:
post:
consumes:
- application/json
description: Delete Extensions
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.PHPExtensionsDelete'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Delete Extensions
tags:
- PHP Extensions
/runtimes/php/extensions/search:
post:
consumes:
- application/json
description: Page Extensions
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.PHPExtensionsSearch'
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/response.PHPExtensionsDTO'
type: array
security:
- ApiKeyAuth: []
summary: Page Extensions
tags:
- PHP Extensions
/runtimes/php/extensions/update:
post:
consumes:
- application/json
description: Update Extensions
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.PHPExtensionsUpdate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update Extensions
tags:
- PHP Extensions
/runtimes/search: /runtimes/search:
post: post:
consumes: consumes:

View File

@ -97,4 +97,29 @@ export namespace Runtime {
Module?: string; Module?: string;
PkgManager?: string; PkgManager?: string;
} }
export interface PHPExtensions extends CommonModel {
id: number;
name: string;
extensions: string;
}
export interface PHPExtensionsList extends ReqPage {
all: boolean;
}
export interface PHPExtensionsCreate {
name: string;
extensions: string;
}
export interface PHPExtensionsUpdate {
id: number;
name: string;
extensions: string;
}
export interface PHPExtensionsDelete {
id: number;
}
} }

View File

@ -1,5 +1,5 @@
import http from '@/api'; import http from '@/api';
import { ResPage } from '../interface'; import { ResPage, ReqPage } from '../interface';
import { Runtime } from '../interface/runtime'; import { Runtime } from '../interface/runtime';
import { TimeoutEnum } from '@/enums/http-enum'; import { TimeoutEnum } from '@/enums/http-enum';
@ -38,3 +38,23 @@ export const GetNodeModules = (req: Runtime.NodeModuleReq) => {
export const OperateNodeModule = (req: Runtime.NodeModuleReq) => { export const OperateNodeModule = (req: Runtime.NodeModuleReq) => {
return http.post<any>(`/runtimes/node/modules/operate`, req, TimeoutEnum.T_10M); return http.post<any>(`/runtimes/node/modules/operate`, req, TimeoutEnum.T_10M);
}; };
export const SearchPHPExtensions = (req: ReqPage) => {
return http.post<ResPage<Runtime.PHPExtensions>>(`/runtimes/php/extensions/search`, req);
};
export const ListPHPExtensions = (req: Runtime.PHPExtensionsList) => {
return http.post<Runtime.PHPExtensions[]>(`/runtimes/php/extensions/search`, req);
};
export const CreatePHPExtensions = (req: Runtime.PHPExtensionsCreate) => {
return http.post<any>(`/runtimes/php/extensions`, req);
};
export const UpdatePHPExtensions = (req: Runtime.PHPExtensionsUpdate) => {
return http.post<any>(`/runtimes/php/extensions/update`, req);
};
export const DeletePHPExtensions = (req: Runtime.PHPExtensionsDelete) => {
return http.post<any>(`/runtimes/php/extensions/del`, req);
};

View File

@ -474,6 +474,19 @@ const checkFilePermission = (rule, value, callback) => {
} }
}; };
const checkPHPExtensions = (rule, value, callback) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.phpExtension')));
} else {
const reg = /^[a-z0-9,_]{3,300}$/;
if (!reg.test(value)) {
callback(new Error(i18n.global.t('commons.rule.phpExtension')));
} else {
callback();
}
}
};
interface CommonRule { interface CommonRule {
requiredInput: FormItemRule; requiredInput: FormItemRule;
requiredSelect: FormItemRule; requiredSelect: FormItemRule;
@ -507,6 +520,7 @@ interface CommonRule {
leechExts: FormItemRule; leechExts: FormItemRule;
domainWithPort: FormItemRule; domainWithPort: FormItemRule;
filePermission: FormItemRule; filePermission: FormItemRule;
phpExtensions: FormItemRule;
paramCommon: FormItemRule; paramCommon: FormItemRule;
paramComplexity: FormItemRule; paramComplexity: FormItemRule;
@ -711,4 +725,9 @@ export const Rules: CommonRule = {
validator: checkFilePermission, validator: checkFilePermission,
trigger: 'blur', trigger: 'blur',
}, },
phpExtensions: {
required: true,
validator: checkPHPExtensions,
trigger: 'blur',
},
}; };

View File

@ -191,6 +191,7 @@ const message = {
paramSimple: 'Support lowercase letters and numbers, length 1-128', paramSimple: 'Support lowercase letters and numbers, length 1-128',
filePermission: 'File Permission Error', filePermission: 'File Permission Error',
formatErr: 'Format error, please check and retry', formatErr: 'Format error, please check and retry',
phpExtension: 'Only supports , _ lowercase English and numbers',
}, },
res: { res: {
paramError: 'The request failed, please try again later!', paramError: 'The request failed, please try again later!',
@ -1839,6 +1840,10 @@ const message = {
uploadMaxSize: 'Upload limit', uploadMaxSize: 'Upload limit',
indexHelper: indexHelper:
'In order to ensure the normal operation of the PHP website, please place the code in the index directory and avoid renaming', 'In order to ensure the normal operation of the PHP website, please place the code in the index directory and avoid renaming',
extensions: 'Extension template',
extension: 'Extension',
extensionHelper: 'Please use multiple extensions, split',
toExtensionsList: 'View extension list',
}, },
nginx: { nginx: {
serverNamesHashBucketSizeHelper: 'The hash table size of the server name', serverNamesHashBucketSizeHelper: 'The hash table size of the server name',

View File

@ -190,6 +190,7 @@ const message = {
paramSimple: '支持小寫字母和數字,長度 1-128', paramSimple: '支持小寫字母和數字,長度 1-128',
filePermission: '權限錯誤', filePermission: '權限錯誤',
formatErr: '格式錯誤檢查後重試', formatErr: '格式錯誤檢查後重試',
phpExtension: '僅支持 , _ 小寫英文和數字',
}, },
res: { res: {
paramError: '請求失敗,請稍後重試!', paramError: '請求失敗,請稍後重試!',
@ -1725,6 +1726,10 @@ const message = {
disableFunctionHelper: '輸入要禁用的函數例如exec多個請用,分割', disableFunctionHelper: '輸入要禁用的函數例如exec多個請用,分割',
uploadMaxSize: '上傳限製', uploadMaxSize: '上傳限製',
indexHelper: '為保障PHP網站正常運行請將代碼放置於 index 目錄並避免重命名', indexHelper: '為保障PHP網站正常運行請將代碼放置於 index 目錄並避免重命名',
extensions: '擴充範本',
extension: '擴充',
extensionHelper: '多個擴充功能,分割',
toExtensionsList: '檢視擴充清單',
}, },
nginx: { nginx: {
serverNamesHashBucketSizeHelper: '服務器名字的hash表大小', serverNamesHashBucketSizeHelper: '服務器名字的hash表大小',

View File

@ -190,6 +190,7 @@ const message = {
paramSimple: '支持小写字母和数字,长度1-128', paramSimple: '支持小写字母和数字,长度1-128',
filePermission: '权限错误', filePermission: '权限错误',
formatErr: '格式错误检查后重试', formatErr: '格式错误检查后重试',
phpExtension: '仅支持 , _ 小写英文和数字',
}, },
res: { res: {
paramError: '请求失败,请稍后重试!', paramError: '请求失败,请稍后重试!',
@ -1725,6 +1726,10 @@ const message = {
disableFunctionHelper: '输入要禁用的函数例如exec多个请用,分割', disableFunctionHelper: '输入要禁用的函数例如exec多个请用,分割',
uploadMaxSize: '上传限制', uploadMaxSize: '上传限制',
indexHelper: '为保障 PHP 网站正常运行请将代码放置于主目录下的 index 目录并避免重命名', indexHelper: '为保障 PHP 网站正常运行请将代码放置于主目录下的 index 目录并避免重命名',
extensions: '扩展模版',
extension: '扩展',
extensionsHelper: '多个扩展请用,分割',
toExtensionsList: '查看扩展列表',
}, },
nginx: { nginx: {
serverNamesHashBucketSizeHelper: '服务器名字的hash表大小', serverNamesHashBucketSizeHelper: '服务器名字的hash表大小',

View File

@ -81,7 +81,16 @@
{{ $t('runtime.phpsourceHelper') }} {{ $t('runtime.phpsourceHelper') }}
</span> </span>
</el-form-item> </el-form-item>
<el-form-item :label="$t('php.extensions')">
<el-select v-model="extensions" @change="changePHPExtension()">
<el-option
v-for="(extension, index) in phpExtensions"
:key="index"
:label="extension.name"
:value="extension.extensions"
></el-option>
</el-select>
</el-form-item>
<Params <Params
v-if="mode === 'create'" v-if="mode === 'create'"
v-model:form="runtime.params" v-model:form="runtime.params"
@ -146,7 +155,7 @@
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
import { Runtime } from '@/api/interface/runtime'; import { Runtime } from '@/api/interface/runtime';
import { GetApp, GetAppDetail, SearchApp } from '@/api/modules/app'; import { GetApp, GetAppDetail, SearchApp } from '@/api/modules/app';
import { CreateRuntime, GetRuntime, UpdateRuntime } from '@/api/modules/runtime'; import { CreateRuntime, GetRuntime, ListPHPExtensions, UpdateRuntime } from '@/api/modules/runtime';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
@ -172,6 +181,7 @@ const mode = ref('create');
const appParams = ref<App.AppParams>(); const appParams = ref<App.AppParams>();
const editParams = ref<App.InstallParams[]>(); const editParams = ref<App.InstallParams[]>();
const appVersions = ref<string[]>([]); const appVersions = ref<string[]>([]);
const phpExtensions = ref([]);
const appReq = reactive({ const appReq = reactive({
type: 'php', type: 'php',
page: 1, page: 1,
@ -187,6 +197,7 @@ const initData = (type: string) => ({
rebuild: false, rebuild: false,
source: 'mirrors.ustc.edu.cn', source: 'mirrors.ustc.edu.cn',
}); });
const extensions = ref();
let runtime = reactive<Runtime.RuntimeCreate>(initData('php')); let runtime = reactive<Runtime.RuntimeCreate>(initData('php'));
@ -364,6 +375,21 @@ const getRuntime = async (id: number) => {
} catch (error) {} } catch (error) {}
}; };
const listPHPExtensions = async () => {
try {
const res = await ListPHPExtensions({
all: true,
page: 1,
pageSize: 100,
});
phpExtensions.value = res.data;
} catch (error) {}
};
const changePHPExtension = () => {
runtime.params['PHP_EXTENSIONS'] = extensions.value.split(',');
};
const acceptParams = async (props: OperateRrops) => { const acceptParams = async (props: OperateRrops) => {
mode.value = props.mode; mode.value = props.mode;
initParam.value = false; initParam.value = false;
@ -374,6 +400,8 @@ const acceptParams = async (props: OperateRrops) => {
searchApp(props.appID); searchApp(props.appID);
getRuntime(props.id); getRuntime(props.id);
} }
extensions.value = '';
listPHPExtensions();
open.value = true; open.value = true;
}; };

View File

@ -0,0 +1,101 @@
<template>
<el-drawer :close-on-click-modal="false" v-model="open" size="50%" :before-close="handleClose">
<template #header>
<DrawerHeader :header="$t('php.extensions')" :back="handleClose" />
</template>
<ComplexTable :data="data" @search="search()" :pagination-config="paginationConfig">
<template #toolbar>
<el-button type="primary" @click="openCreate">{{ $t('commons.button.create') }}</el-button>
</template>
<el-table-column :label="$t('commons.table.name')" width="150px" prop="name"></el-table-column>
<el-table-column :label="$t('php.extension')" fix prop="extensions"></el-table-column>
<fu-table-operations
:ellipsis="10"
width="120px"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
<Create ref="createRef" @close="search()" />
<OpDialog ref="opRef" @search="search" />
</el-drawer>
</template>
<script lang="ts" setup>
import { DeletePHPExtensions, SearchPHPExtensions } from '@/api/modules/runtime';
import { reactive, ref } from 'vue';
import Create from './operate/index.vue';
import { Runtime } from '@/api/interface/runtime';
import i18n from '@/lang';
const open = ref(false);
const data = ref();
const createRef = ref();
const opRef = ref();
const paginationConfig = reactive({
cacheSizeKey: 'website-page-size',
currentPage: 1,
pageSize: Number(localStorage.getItem('website-page-size')) || 10,
total: 0,
});
const buttons = [
{
label: i18n.global.t('commons.button.edit'),
click: function (row: Runtime.PHPExtensions) {
openUpdate(row);
},
},
{
label: i18n.global.t('commons.button.delete'),
click: function (row: Runtime.PHPExtensions) {
openDelete(row);
},
},
];
const handleClose = () => {
open.value = false;
};
const acceptParams = (): void => {
open.value = true;
search();
};
const openCreate = () => {
createRef.value.acceptParams('create');
};
const search = async () => {
try {
const res = await SearchPHPExtensions({
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
});
data.value = res.data.items;
paginationConfig.total = res.data.total;
} catch (error) {}
};
const openDelete = async (row: Runtime.PHPExtensions) => {
opRef.value.acceptParams({
title: i18n.global.t('commons.msg.deleteTitle'),
names: [row.name],
msg: i18n.global.t('commons.msg.operatorHelper', [
i18n.global.t('php.extensions'),
i18n.global.t('commons.button.delete'),
]),
api: DeletePHPExtensions,
params: { id: row.id },
});
};
const openUpdate = async (row: Runtime.PHPExtensions) => {
createRef.value.acceptParams('edit', row);
};
defineExpose({ acceptParams });
</script>

View File

@ -0,0 +1,121 @@
<template>
<el-dialog
v-model="open"
:title="$t('commons.button.' + operate) + $t('php.extensions')"
:close-on-click-modal="false"
width="30%"
:before-close="handleClose"
>
<el-row v-loading="loading">
<el-col :span="22" :offset="1">
<el-form @submit.prevent ref="extensionsForm" label-position="top" :model="extensions" :rules="rules">
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model.trim="extensions.name" :disabled="operate == 'edit'"></el-input>
</el-form-item>
<el-form-item :label="$t('php.extension')" prop="extensions">
<el-input
type="textarea"
:placeholder="$t('php.extensionsHelper')"
:autosize="{ minRows: 3, maxRows: 10 }"
v-model="extensions.extensions"
/>
</el-form-item>
<a target="“_blank”" href="https://1panel.cn/docs/user_manual/websites/php/#php_1">
{{ $t('php.toExtensionsList') }}
</a>
</el-form>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="submit(extensionsForm)" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import { FormInstance } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import { CreatePHPExtensions, UpdatePHPExtensions } from '@/api/modules/runtime';
import i18n from '@/lang';
import { Runtime } from '@/api/interface/runtime';
const open = ref(false);
const operate = ref('create');
const loading = ref(false);
const updateID = ref(0);
const extensionsForm = ref<FormInstance>();
const rules = ref({
name: [Rules.requiredInput],
extensions: [Rules.requiredInput, Rules.phpExtensions],
});
const em = defineEmits(['close']);
const initData = () => ({
name: '',
extensions: '',
});
const extensions = ref(initData());
const acceptParams = (op: string, extend: Runtime.PHPExtensions) => {
operate.value = op;
open.value = true;
extensions.value = initData();
if (operate.value == 'edit') {
extensions.value = extend;
updateID.value = extend.id;
}
};
const handleClose = () => {
open.value = false;
extensionsForm.value?.resetFields();
em('close', false);
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
if (operate.value == 'create') {
CreatePHPExtensions(extensions.value)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
} else {
UpdatePHPExtensions({
id: updateID.value,
name: extensions.value.name,
extensions: extensions.value.extensions,
})
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
}
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -13,6 +13,10 @@
<el-button type="primary" @click="openCreate"> <el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }} {{ $t('runtime.create') }}
</el-button> </el-button>
<el-button @click="openExtensions">
{{ $t('php.extensions') }}
</el-button>
</template> </template>
<template #main> <template #main>
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()"> <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
@ -74,6 +78,7 @@
<CreateRuntime ref="createRef" @close="search" @submit="openCreateLog" /> <CreateRuntime ref="createRef" @close="search" @submit="openCreateLog" />
<OpDialog ref="opRef" @search="search" /> <OpDialog ref="opRef" @search="search" />
<Log ref="logRef" @close="search" /> <Log ref="logRef" @close="search" />
<Extensions ref="extensionsRef" @close="search" />
</div> </div>
</template> </template>
@ -88,6 +93,7 @@ import Status from '@/components/status/index.vue';
import i18n from '@/lang'; import i18n from '@/lang';
import RouterMenu from '../index.vue'; import RouterMenu from '../index.vue';
import Log from '@/components/log-dialog/index.vue'; import Log from '@/components/log-dialog/index.vue';
import Extensions from './extensions/index.vue';
const paginationConfig = reactive({ const paginationConfig = reactive({
cacheSizeKey: 'runtime-page-size', cacheSizeKey: 'runtime-page-size',
@ -104,6 +110,7 @@ let req = reactive<Runtime.RuntimeReq>({
let timer: NodeJS.Timer | null = null; let timer: NodeJS.Timer | null = null;
const opRef = ref(); const opRef = ref();
const logRef = ref(); const logRef = ref();
const extensionsRef = ref();
const buttons = [ const buttons = [
{ {
@ -156,13 +163,17 @@ const openCreateLog = (id: number) => {
logRef.value.acceptParams({ id: id, type: 'php', tail: true }); logRef.value.acceptParams({ id: id, type: 'php', tail: true });
}; };
const openExtensions = () => {
extensionsRef.value.acceptParams();
};
const openDelete = async (row: Runtime.Runtime) => { const openDelete = async (row: Runtime.Runtime) => {
opRef.value.acceptParams({ opRef.value.acceptParams({
title: i18n.global.t('commons.msg.deleteTitle'), title: i18n.global.t('commons.msg.deleteTitle'),
names: [row.name], names: [row.name],
msg: i18n.global.t('commons.msg.operatorHelper', [ msg: i18n.global.t('commons.msg.operatorHelper', [
i18n.global.t('website.runtime'), i18n.global.t('website.runtime'),
i18n.global.t('commons.msg.delete'), i18n.global.t('commons.button.delete'),
]), ]),
api: DeleteRuntime, api: DeleteRuntime,
params: { id: row.id, forceDelete: true }, params: { id: row.id, forceDelete: true },