feat: 增加创建运行环境类型网站
This commit is contained in:
		 zhengkunwang223
					zhengkunwang223
				
			
				
					committed by
					
						 zhengkunwang223
						zhengkunwang223
					
				
			
			
				
	
			
			
			 zhengkunwang223
						zhengkunwang223
					
				
			
						parent
						
							d4c1caa26a
						
					
				
				
					commit
					c629fa9575
				
			| @@ -96,6 +96,28 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) { | ||||
| 	helper.SuccessWithData(c, appDetailDTO) | ||||
| } | ||||
|  | ||||
| // @Tags App | ||||
| // @Summary Search app detail by id | ||||
| // @Description 通过 id 获取应用详情 | ||||
| // @Accept json | ||||
| // @Param appId path integer true "id" | ||||
| // @Success 200 {object} response.AppDetailDTO | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /apps/detail/:id[get] | ||||
| func (b *BaseApi) GetAppDetailByID(c *gin.Context) { | ||||
| 	appDetailID, err := helper.GetIntParamByKey(c, "id") | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) | ||||
| 		return | ||||
| 	} | ||||
| 	appDetailDTO, err := appService.GetAppDetailByID(appDetailID) | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	helper.SuccessWithData(c, appDetailDTO) | ||||
| } | ||||
|  | ||||
| // @Tags App | ||||
| // @Summary Install app | ||||
| // @Description 安装应用 | ||||
|   | ||||
| @@ -23,6 +23,8 @@ type WebsiteCreate struct { | ||||
| 	AppInstall   NewAppInstall `json:"appInstall"` | ||||
| 	AppID        uint          `json:"appID"` | ||||
| 	AppInstallID uint          `json:"appInstallID"` | ||||
|  | ||||
| 	RuntimeID uint `json:"runtimeID"` | ||||
| } | ||||
|  | ||||
| type NewAppInstall struct { | ||||
|   | ||||
| @@ -6,5 +6,4 @@ type RuntimeRes struct { | ||||
| 	model.Runtime | ||||
| 	AppParams []AppParam `json:"appParams"` | ||||
| 	AppID     uint       `json:"appId"` | ||||
| 	Version   string     `json:"version"` | ||||
| } | ||||
|   | ||||
| @@ -19,6 +19,7 @@ type Website struct { | ||||
| 	ErrorLog       bool            `json:"errorLog"` | ||||
| 	AccessLog      bool            `json:"accessLog"` | ||||
| 	DefaultServer  bool            `json:"defaultServer"` | ||||
| 	RuntimeID      uint            `gorm:"type:integer" json:"runtimeID"` | ||||
| 	Domains        []WebsiteDomain `json:"domains" gorm:"-:migration"` | ||||
| 	WebsiteSSL     WebsiteSSL      `json:"webSiteSSL" gorm:"-:migration"` | ||||
| } | ||||
|   | ||||
| @@ -10,8 +10,9 @@ type RuntimeRepo struct { | ||||
| } | ||||
|  | ||||
| type IRuntimeRepo interface { | ||||
| 	WithNameOrImage(name string, image string) DBOption | ||||
| 	WithOtherNameOrImage(name string, image string, id uint) DBOption | ||||
| 	WithName(name string) DBOption | ||||
| 	WithImage(image string) DBOption | ||||
| 	WithNotId(id uint) DBOption | ||||
| 	Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) | ||||
| 	Create(ctx context.Context, runtime *model.Runtime) error | ||||
| 	Save(runtime *model.Runtime) error | ||||
| @@ -23,15 +24,21 @@ func NewIRunTimeRepo() IRuntimeRepo { | ||||
| 	return &RuntimeRepo{} | ||||
| } | ||||
|  | ||||
| func (r *RuntimeRepo) WithNameOrImage(name string, image string) DBOption { | ||||
| func (r *RuntimeRepo) WithName(name string) DBOption { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		return g.Where("name = ? or image = ?", name, image) | ||||
| 		return g.Where("name = ?", name) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *RuntimeRepo) WithOtherNameOrImage(name string, image string, id uint) DBOption { | ||||
| func (r *RuntimeRepo) WithImage(image string) DBOption { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		return g.Where("name = ? or image = ? and id != ?", name, image, id) | ||||
| 		return g.Where("image = ?", image) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *RuntimeRepo) WithNotId(id uint) DBOption { | ||||
| 	return func(g *gorm.DB) *gorm.DB { | ||||
| 		return g.Where("id != ?", id) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -36,6 +36,7 @@ type IAppService interface { | ||||
| 	Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) | ||||
| 	SyncAppList() error | ||||
| 	GetAppUpdate() (*response.AppUpdateRes, error) | ||||
| 	GetAppDetailByID(id uint) (*response.AppDetailDTO, error) | ||||
| } | ||||
|  | ||||
| func NewIAppService() IAppService { | ||||
| @@ -206,6 +207,20 @@ func (a AppService) GetAppDetail(appId uint, version, appType string) (response. | ||||
| 	} | ||||
| 	return appDetailDTO, nil | ||||
| } | ||||
| func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) { | ||||
| 	res := &response.AppDetailDTO{} | ||||
| 	appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(id)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	res.AppDetail = appDetail | ||||
| 	paramMap := make(map[string]interface{}) | ||||
| 	if err := json.Unmarshal([]byte(appDetail.Params), ¶mMap); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	res.Params = paramMap | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) { | ||||
| 	if err := docker.CreateDefaultDockerNetwork(); err != nil { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"github.com/subosito/gotenv" | ||||
| 	"math" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| @@ -207,7 +208,21 @@ func updateInstall(installId uint, detailId uint) error { | ||||
| 	if err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err = compose.Down(install.GetComposePath()); err != nil { | ||||
|  | ||||
| 	detailDir := path.Join(constant.ResourceDir, "apps", install.App.Key, "versions", detail.Version) | ||||
| 	cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath())) | ||||
| 	stdout, err := cmd.CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		if stdout != nil { | ||||
| 			return errors.New(string(stdout)) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if out, err := compose.Down(install.GetComposePath()); err != nil { | ||||
| 		if out != "" { | ||||
| 			return errors.New(out) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	install.DockerCompose = detail.DockerCompose | ||||
| @@ -218,7 +233,10 @@ func updateInstall(installId uint, detailId uint) error { | ||||
| 	if err := fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err = compose.Up(install.GetComposePath()); err != nil { | ||||
| 	if out, err := compose.Up(install.GetComposePath()); err != nil { | ||||
| 		if out != "" { | ||||
| 			return errors.New(out) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	return appInstallRepo.Save(&install) | ||||
|   | ||||
| @@ -35,19 +35,24 @@ func NewRuntimeService() IRuntimeService { | ||||
| } | ||||
|  | ||||
| func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) { | ||||
| 	exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithNameOrImage(create.Name, create.Image)) | ||||
| 	exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithName(create.Name)) | ||||
| 	if exist != nil { | ||||
| 		return buserr.New(constant.ErrNameOrImageIsExist) | ||||
| 		return buserr.New(constant.ErrNameIsExist) | ||||
| 	} | ||||
| 	if create.Resource == constant.ResourceLocal { | ||||
| 		runtime := &model.Runtime{ | ||||
| 			Name:     create.Name, | ||||
| 			Resource: create.Resource, | ||||
| 			Type:     create.Type, | ||||
| 			Version:  create.Version, | ||||
| 			Status:   constant.RuntimeNormal, | ||||
| 		} | ||||
| 		return runtimeRepo.Create(context.Background(), runtime) | ||||
| 	} | ||||
| 	exist, _ = runtimeRepo.GetFirst(runtimeRepo.WithImage(create.Image)) | ||||
| 	if exist != nil { | ||||
| 		return buserr.New(constant.ErrImageExist) | ||||
| 	} | ||||
| 	appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -134,6 +139,7 @@ func (r *RuntimeService) Delete(id uint) error { | ||||
| 		return err | ||||
| 	} | ||||
| 	//TODO 校验网站关联 | ||||
| 	//TODO 删除镜像 | ||||
| 	if runtime.Resource == constant.ResourceAppstore { | ||||
| 		runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) | ||||
| 		if err := files.NewFileOp().DeleteDir(runtimeDir); err != nil { | ||||
| @@ -158,7 +164,6 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeRes, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	res.AppID = appDetail.AppId | ||||
| 	res.Version = appDetail.Version | ||||
| 	var ( | ||||
| 		appForm   dto.AppForm | ||||
| 		appParams []response.AppParam | ||||
| @@ -207,10 +212,6 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeRes, error) { | ||||
| } | ||||
|  | ||||
| func (r *RuntimeService) Update(req request.RuntimeUpdate) error { | ||||
| 	exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithOtherNameOrImage(req.Name, req.Image, req.ID)) | ||||
| 	if exist != nil { | ||||
| 		return buserr.New(constant.ErrNameOrImageIsExist) | ||||
| 	} | ||||
| 	runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -219,6 +220,10 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error { | ||||
| 		runtime.Version = req.Version | ||||
| 		return runtimeRepo.Save(runtime) | ||||
| 	} | ||||
| 	exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithImage(req.Name), runtimeRepo.WithNotId(req.ID)) | ||||
| 	if exist != nil { | ||||
| 		return buserr.New(constant.ErrImageExist) | ||||
| 	} | ||||
| 	runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) | ||||
| 	composeContent, envContent, _, err := handleParams(req.Image, runtime.Type, runtimeDir, req.Params) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -131,7 +131,10 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit | ||||
| 		ErrorLog:       true, | ||||
| 	} | ||||
|  | ||||
| 	var appInstall *model.AppInstall | ||||
| 	var ( | ||||
| 		appInstall *model.AppInstall | ||||
| 		runtime    *model.Runtime | ||||
| 	) | ||||
| 	switch create.Type { | ||||
| 	case constant.Deployment: | ||||
| 		if create.AppType == constant.NewApp { | ||||
| @@ -153,6 +156,30 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit | ||||
| 			appInstall = &install | ||||
| 			website.AppInstallID = appInstall.ID | ||||
| 		} | ||||
| 	case constant.Runtime: | ||||
| 		var err error | ||||
| 		runtime, err = runtimeRepo.GetFirst(commonRepo.WithByID(create.RuntimeID)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if runtime.Resource == constant.ResourceAppstore { | ||||
| 			var req request.AppInstallCreate | ||||
| 			req.Name = create.PrimaryDomain | ||||
| 			req.AppDetailId = create.AppInstall.AppDetailId | ||||
| 			req.Params = create.AppInstall.Params | ||||
| 			req.Params["IMAGE_NAME"] = runtime.Image | ||||
| 			nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www") | ||||
| 			install, err := NewIAppService().Install(ctx, req) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			website.AppInstallID = install.ID | ||||
| 			appInstall = install | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err := websiteRepo.Create(ctx, website); err != nil { | ||||
| @@ -180,7 +207,7 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return configDefaultNginx(website, domains, appInstall) | ||||
| 	return configDefaultNginx(website, domains, appInstall, runtime) | ||||
| } | ||||
|  | ||||
| func (w WebsiteService) OpWebsite(req request.WebsiteOp) error { | ||||
|   | ||||
| @@ -43,15 +43,28 @@ func getDomain(domainStr string, websiteID uint) (model.WebsiteDomain, error) { | ||||
| 	return model.WebsiteDomain{}, nil | ||||
| } | ||||
|  | ||||
| func createStaticHtml(website *model.Website) error { | ||||
| func createIndexFile(website *model.Website, runtime *model.Runtime) error { | ||||
| 	nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	indexFolder := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", website.Alias, "index") | ||||
| 	indexPath := path.Join(indexFolder, "index.html") | ||||
| 	indexContent := string(nginx_conf.Index) | ||||
| 	indexPath := "" | ||||
| 	indexContent := "" | ||||
| 	switch website.Type { | ||||
| 	case constant.Static: | ||||
| 		indexPath = path.Join(indexFolder, "index.html") | ||||
| 		indexContent = string(nginx_conf.Index) | ||||
| 	case constant.Runtime: | ||||
| 		if runtime.Type == constant.RuntimePHP { | ||||
| 			indexPath = path.Join(indexFolder, "index.php") | ||||
| 			indexContent = string(nginx_conf.IndexPHP) | ||||
| 		} else { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	fileOp := files.NewFileOp() | ||||
| 	if !fileOp.Stat(indexFolder) { | ||||
| 		if err := fileOp.CreateDir(indexFolder, 0755); err != nil { | ||||
| @@ -69,7 +82,7 @@ func createStaticHtml(website *model.Website) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website) error { | ||||
| func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website, runtime *model.Runtime) error { | ||||
| 	nginxFolder := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name) | ||||
| 	siteFolder := path.Join(nginxFolder, "www", "sites", website.Alias) | ||||
| 	fileOp := files.NewFileOp() | ||||
| @@ -92,8 +105,8 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website) | ||||
| 		if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), 0755); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if website.Type == constant.Static { | ||||
| 			if err := createStaticHtml(website); err != nil { | ||||
| 		if website.Type == constant.Static || website.Type == constant.Runtime { | ||||
| 			if err := createIndexFile(website, runtime); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| @@ -101,12 +114,12 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website) | ||||
| 	return fileOp.CopyDir(path.Join(nginxFolder, "www", "common", "waf", "rules"), path.Join(siteFolder, "waf")) | ||||
| } | ||||
|  | ||||
| func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall) error { | ||||
| func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime) error { | ||||
| 	nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := createWebsiteFolder(nginxInstall, website); err != nil { | ||||
| 	if err := createWebsiteFolder(nginxInstall, website, runtime); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| @@ -140,9 +153,21 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a | ||||
| 		server.UpdateRootProxy([]string{proxy}) | ||||
| 	case constant.Static: | ||||
| 		server.UpdateRoot(path.Join("/www/sites", website.Alias, "index")) | ||||
| 		server.UpdateRootLocation() | ||||
| 		//server.UpdateRootLocation() | ||||
| 	case constant.Proxy: | ||||
| 		server.UpdateRootProxy([]string{website.Proxy}) | ||||
| 	case constant.Runtime: | ||||
| 		if runtime.Resource == constant.ResourceLocal { | ||||
| 			server.UpdateRoot(path.Join("/www/sites", website.Alias, "index")) | ||||
| 		} | ||||
| 		if runtime.Resource == constant.ResourceAppstore { | ||||
| 			switch runtime.Type { | ||||
| 			case constant.RuntimePHP: | ||||
| 				server.UpdateRoot(path.Join("/www/sites", website.Alias, "index")) | ||||
| 				proxy := fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) | ||||
| 				server.UpdatePHPProxy([]string{proxy}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	config.FilePath = configPath | ||||
|   | ||||
| @@ -109,5 +109,5 @@ var ( | ||||
| 	ErrDirNotFound   = "ErrDirNotFound" | ||||
| 	ErrFileNotExist  = "ErrFileNotExist" | ||||
| 	ErrImageBuildErr = "ErrImageBuildErr" | ||||
| 	ErrNameOrImageIsExist = "ErrNameOrImageIsExist" | ||||
| 	ErrImageExist    = "ErrImageExist" | ||||
| ) | ||||
|   | ||||
| @@ -17,6 +17,7 @@ const ( | ||||
| 	Deployment = "deployment" | ||||
| 	Static     = "static" | ||||
| 	Proxy      = "proxy" | ||||
| 	Runtime    = "runtime" | ||||
|  | ||||
| 	SSLExisted = "existed" | ||||
| 	SSLAuto    = "auto" | ||||
|   | ||||
| @@ -64,4 +64,4 @@ ErrObjectInUsed: "This object is in use and cannot be deleted" | ||||
| ErrDirNotFound: "The build folder does not exist! Please check file integrity!" | ||||
| ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity!" | ||||
| ErrImageBuildErr: "Image build failed" | ||||
| ErrNameOrImageIsExist: "Duplicate name or image" | ||||
| ErrImageExist: "Image is already exist!" | ||||
| @@ -64,4 +64,4 @@ ErrObjectInUsed: "该对象正被使用,无法删除" | ||||
| ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!" | ||||
| ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!" | ||||
| ErrImageBuildErr: "镜像 build 失败" | ||||
| ErrNameOrImageIsExist: "名称或者镜像重复" | ||||
| ErrImageExist: "镜像已存在!" | ||||
| @@ -251,6 +251,6 @@ var AddDefaultGroup = &gormigrate.Migration{ | ||||
| var AddTableRuntime = &gormigrate.Migration{ | ||||
| 	ID: "20230330-add-table-runtime", | ||||
| 	Migrate: func(tx *gorm.DB) error { | ||||
| 		return tx.AutoMigrate(&model.Runtime{}) | ||||
| 		return tx.AutoMigrate(&model.Runtime{}, &model.Website{}) | ||||
| 	}, | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) { | ||||
| 		appRouter.POST("/search", baseApi.SearchApp) | ||||
| 		appRouter.GET("/:key", baseApi.GetApp) | ||||
| 		appRouter.GET("/detail/:appId/:version/:type", baseApi.GetAppDetail) | ||||
| 		appRouter.GET("/details/:id", baseApi.GetAppDetailByID) | ||||
| 		appRouter.POST("/install", baseApi.InstallApp) | ||||
| 		appRouter.GET("/tags", baseApi.GetAppTags) | ||||
| 		appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions) | ||||
|   | ||||
| @@ -252,19 +252,15 @@ func (f FileOp) Copy(src, dst string) error { | ||||
| 	if src = path.Clean("/" + src); src == "" { | ||||
| 		return os.ErrNotExist | ||||
| 	} | ||||
|  | ||||
| 	if dst = path.Clean("/" + dst); dst == "" { | ||||
| 		return os.ErrNotExist | ||||
| 	} | ||||
|  | ||||
| 	if src == "/" || dst == "/" { | ||||
| 		return os.ErrInvalid | ||||
| 	} | ||||
|  | ||||
| 	if dst == src { | ||||
| 		return os.ErrInvalid | ||||
| 	} | ||||
|  | ||||
| 	info, err := f.Fs.Stat(src) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -272,7 +268,6 @@ func (f FileOp) Copy(src, dst string) error { | ||||
| 	if info.IsDir() { | ||||
| 		return f.CopyDir(src, dst) | ||||
| 	} | ||||
|  | ||||
| 	return f.CopyFile(src, dst) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -237,6 +237,29 @@ func (s *Server) UpdateRootProxy(proxy []string) { | ||||
| 	s.UpdateDirectiveBySecondKey("location", "/", newDir) | ||||
| } | ||||
|  | ||||
| func (s *Server) UpdatePHPProxy(proxy []string) { | ||||
| 	newDir := Directive{ | ||||
| 		Name:       "location", | ||||
| 		Parameters: []string{"~ [^/]\\.php(/|$)"}, | ||||
| 		Block:      &Block{}, | ||||
| 	} | ||||
| 	block := &Block{} | ||||
| 	block.Directives = append(block.Directives, &Directive{ | ||||
| 		Name:       "fastcgi_pass", | ||||
| 		Parameters: proxy, | ||||
| 	}) | ||||
| 	block.Directives = append(block.Directives, &Directive{ | ||||
| 		Name:       "include", | ||||
| 		Parameters: []string{"fastcgi-php.conf"}, | ||||
| 	}) | ||||
| 	block.Directives = append(block.Directives, &Directive{ | ||||
| 		Name:       "include", | ||||
| 		Parameters: []string{"fastcgi_params"}, | ||||
| 	}) | ||||
| 	newDir.Block = block | ||||
| 	s.UpdateDirectiveBySecondKey("location", "~ [^/]\\.php(/|$)", newDir) | ||||
| } | ||||
|  | ||||
| func (s *Server) UpdateDirectiveBySecondKey(name string, key string, directive Directive) { | ||||
| 	directives := s.Directives | ||||
| 	index := -1 | ||||
|   | ||||
							
								
								
									
										25
									
								
								cmd/server/nginx_conf/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								cmd/server/nginx_conf/index.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| <?php | ||||
| error_reporting(E_ALL); | ||||
| ini_set('display_errors', 1); | ||||
|  | ||||
| echo '<h1 style="text-align: center;">欢迎使用 PHP!</h1>'; | ||||
| echo '<h2>版本信息</h2>'; | ||||
|  | ||||
| echo '<ul>'; | ||||
| echo '<li>PHP版本:', PHP_VERSION, '</li>'; | ||||
| echo '</ul>'; | ||||
|  | ||||
| echo '<h2>已安装扩展</h2>'; | ||||
| printExtensions(); | ||||
|  | ||||
| /** | ||||
|  * 获取已安装扩展列表 | ||||
|  */ | ||||
| function printExtensions() | ||||
| { | ||||
|     echo '<ol>'; | ||||
|     foreach (get_loaded_extensions() as $i => $name) { | ||||
|         echo "<li>", $name, '=', phpversion($name), '</li>'; | ||||
|     } | ||||
|     echo '</ol>'; | ||||
| } | ||||
| @@ -12,3 +12,6 @@ var WebsiteDefault []byte | ||||
|  | ||||
| //go:embed index.html | ||||
| var Index []byte | ||||
|  | ||||
| //go:embed index.php | ||||
| var IndexPHP []byte | ||||
|   | ||||
| @@ -11,16 +11,16 @@ export namespace Runtime { | ||||
|         params: string; | ||||
|         type: string; | ||||
|         resource: string; | ||||
|         version: string; | ||||
|     } | ||||
|  | ||||
|     export interface RuntimeReq extends ReqPage { | ||||
|         name: string; | ||||
|         name?: string; | ||||
|     } | ||||
|  | ||||
|     export interface RuntimeDTO extends Runtime { | ||||
|         appParams: App.InstallParams[]; | ||||
|         appId: number; | ||||
|         version: string; | ||||
|     } | ||||
|  | ||||
|     export interface RuntimeCreate { | ||||
|   | ||||
| @@ -22,8 +22,12 @@ export const GetAppTags = () => { | ||||
|     return http.get<App.Tag[]>('apps/tags'); | ||||
| }; | ||||
|  | ||||
| export const GetAppDetail = (id: number, version: string, type: string) => { | ||||
|     return http.get<App.AppDetail>(`apps/detail/${id}/${version}/${type}`); | ||||
| export const GetAppDetail = (appID: number, version: string, type: string) => { | ||||
|     return http.get<App.AppDetail>(`apps/detail/${appID}/${version}/${type}`); | ||||
| }; | ||||
|  | ||||
| export const GetAppDetailByID = (id: number) => { | ||||
|     return http.get<App.AppDetail>(`apps/details/${id}`); | ||||
| }; | ||||
|  | ||||
| export const InstallApp = (install: App.AppInstall) => { | ||||
|   | ||||
| @@ -1134,6 +1134,10 @@ const message = { | ||||
|         websiteStatictHelper: 'Create a website directory on the host', | ||||
|         websiteProxyHelper: | ||||
|             'The proxy has existing services, for example, the machine has installed the halo service using port 8080, then the proxy address is http://127.0.0.1:8080', | ||||
|         runtimeProxyHelper: 'Use runtime created from 1Panel', | ||||
|         runtime: 'Runtime', | ||||
|         deleteRuntimeHelper: | ||||
|             'The Runtime application needs to be deleted together with the website, please handle it with caution', | ||||
|     }, | ||||
|     nginx: { | ||||
|         serverNamesHashBucketSizeHelper: 'The hash table size of the server name', | ||||
|   | ||||
| @@ -1133,6 +1133,9 @@ const message = { | ||||
|         restoreHelper: '确认使用此备份恢复?', | ||||
|         wafValueHelper: '值', | ||||
|         wafRemarkHelper: '描述', | ||||
|         runtimeProxyHelper: '使用从 1Panel 创建的运行环境', | ||||
|         runtime: '运行环境', | ||||
|         deleteRuntimeHelper: '运行环境应用需要跟网站一并删除,请谨慎处理', | ||||
|     }, | ||||
|     nginx: { | ||||
|         serverNamesHashBucketSizeHelper: '服务器名字的hash表大小', | ||||
|   | ||||
| @@ -22,15 +22,15 @@ | ||||
|                             v-model="runtime.resource" | ||||
|                             @change="changeResource(runtime.resource)" | ||||
|                         > | ||||
|                             <el-radio :label="'AppStore'" :value="'AppStore'"> | ||||
|                             <el-radio :label="'appstore'"> | ||||
|                                 {{ $t('runtime.appstore') }} | ||||
|                             </el-radio> | ||||
|                             <el-radio :label="'Local'" :value="'Local'"> | ||||
|                             <el-radio :label="'local'"> | ||||
|                                 {{ $t('runtime.local') }} | ||||
|                             </el-radio> | ||||
|                         </el-radio-group> | ||||
|                     </el-form-item> | ||||
|                     <div v-if="runtime.resource === 'AppStore'"> | ||||
|                     <div v-if="runtime.resource === 'appstore'"> | ||||
|                         <el-form-item :label="$t('runtime.app')" prop="appId"> | ||||
|                             <el-row :gutter="20"> | ||||
|                                 <el-col :span="12"> | ||||
| @@ -134,7 +134,7 @@ const runtime = ref<Runtime.RuntimeCreate>({ | ||||
|     image: '', | ||||
|     params: {}, | ||||
|     type: 'php', | ||||
|     resource: 'AppStore', | ||||
|     resource: 'appstore', | ||||
| }); | ||||
| let rules = ref<any>({ | ||||
|     name: [Rules.appName], | ||||
| @@ -152,7 +152,7 @@ const handleClose = () => { | ||||
| }; | ||||
|  | ||||
| const changeResource = (resource: string) => { | ||||
|     if (resource === 'Local') { | ||||
|     if (resource === 'local') { | ||||
|         runtime.value.appDetailId = undefined; | ||||
|         runtime.value.version = ''; | ||||
|         runtime.value.params = {}; | ||||
| @@ -257,7 +257,7 @@ const acceptParams = async (props: OperateRrops) => { | ||||
|             image: '', | ||||
|             params: {}, | ||||
|             type: props.type, | ||||
|             resource: 'AppStore', | ||||
|             resource: 'appstore', | ||||
|         }; | ||||
|         searchApp(null); | ||||
|     } else { | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
|                 <ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()"> | ||||
|                     <el-table-column :label="$t('commons.table.name')" fix prop="name" min-width="120px"> | ||||
|                         <template #default="{ row }"> | ||||
|                             <Tooltip :text="row.name" /> | ||||
|                             <Tooltip :text="row.name" @click="openDetail(row)" /> | ||||
|                         </template> | ||||
|                     </el-table-column> | ||||
|                     <el-table-column :label="$t('runtime.resource')" prop="resource"> | ||||
|   | ||||
| @@ -18,6 +18,10 @@ | ||||
|                                     label: i18n.global.t('website.proxy'), | ||||
|                                     value: 'proxy', | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     label: i18n.global.t('runtime.runtime'), | ||||
|                                     value: 'runtime', | ||||
|                                 }, | ||||
|                             ]" | ||||
|                             :key="item.value" | ||||
|                         > | ||||
| @@ -56,6 +60,12 @@ | ||||
|                     type="info" | ||||
|                     :closable="false" | ||||
|                 /> | ||||
|                 <el-alert | ||||
|                     v-if="website.type == 'runtime'" | ||||
|                     :title="$t('website.runtimeProxyHelper')" | ||||
|                     type="info" | ||||
|                     :closable="false" | ||||
|                 /> | ||||
|                 <br /> | ||||
|                 <el-form | ||||
|                     ref="websiteForm" | ||||
| @@ -140,6 +150,25 @@ | ||||
|                             ></Params> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div v-if="website.type === 'runtime'"> | ||||
|                         <el-form-item :label="$t('runtime.runtime')" prop="runtimeID"> | ||||
|                             <el-select v-model="website.runtimeID" @change="changeApp()"> | ||||
|                                 <el-option | ||||
|                                     v-for="(runtime, index) in runtimes" | ||||
|                                     :key="index" | ||||
|                                     :label="runtime.name" | ||||
|                                     :value="runtime.id" | ||||
|                                 ></el-option> | ||||
|                             </el-select> | ||||
|                         </el-form-item> | ||||
|                         <Params | ||||
|                             :key="paramKey" | ||||
|                             v-model:form="website.appinstall.params" | ||||
|                             v-model:rules="rules.appinstall.params" | ||||
|                             :params="appParams" | ||||
|                             :propStart="'appinstall.params.'" | ||||
|                         ></Params> | ||||
|                     </div> | ||||
|                     <el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain"> | ||||
|                         <el-input | ||||
|                             v-model.trim="website.primaryDomain" | ||||
| @@ -187,7 +216,7 @@ | ||||
| <script lang="ts" setup name="CreateWebSite"> | ||||
| import DrawerHeader from '@/components/drawer-header/index.vue'; | ||||
| import { App } from '@/api/interface/app'; | ||||
| import { GetApp, GetAppDetail, SearchApp, GetAppInstalled } from '@/api/modules/app'; | ||||
| import { GetApp, GetAppDetail, SearchApp, GetAppInstalled, GetAppDetailByID } from '@/api/modules/app'; | ||||
| import { CreateWebsite, PreCheck } from '@/api/modules/website'; | ||||
| import { Rules } from '@/global/form-rules'; | ||||
| import i18n from '@/lang'; | ||||
| @@ -198,6 +227,8 @@ import Check from '../check/index.vue'; | ||||
| import { MsgSuccess } from '@/utils/message'; | ||||
| import { GetGroupList } from '@/api/modules/group'; | ||||
| import { Group } from '@/api/interface/group'; | ||||
| import { SearchRuntimes } from '@/api/modules/runtime'; | ||||
| import { Runtime } from '@/api/interface/runtime'; | ||||
|  | ||||
| const websiteForm = ref<FormInstance>(); | ||||
| const website = ref({ | ||||
| @@ -210,6 +241,7 @@ const website = ref({ | ||||
|     webSiteGroupId: 1, | ||||
|     otherDomains: '', | ||||
|     proxy: '', | ||||
|     runtimeID: undefined, | ||||
|     appinstall: { | ||||
|         appId: 0, | ||||
|         name: '', | ||||
| @@ -227,6 +259,7 @@ let rules = ref<any>({ | ||||
|     appInstallId: [Rules.requiredSelectBusiness], | ||||
|     appType: [Rules.requiredInput], | ||||
|     proxy: [Rules.requiredInput], | ||||
|     runtimeID: [Rules.requiredSelectBusiness], | ||||
|     appinstall: { | ||||
|         name: [Rules.appName], | ||||
|         appId: [Rules.requiredSelectBusiness], | ||||
| @@ -251,6 +284,11 @@ let appParams = ref<App.AppParams>(); | ||||
| let paramKey = ref(1); | ||||
| let preCheckRef = ref(); | ||||
| let staticPath = ref(''); | ||||
| const runtimeReq = ref<Runtime.RuntimeReq>({ | ||||
|     page: 1, | ||||
|     pageSize: 20, | ||||
| }); | ||||
| const runtimes = ref<Runtime.RuntimeDTO[]>([]); | ||||
|  | ||||
| const em = defineEmits(['close']); | ||||
|  | ||||
| @@ -264,6 +302,8 @@ const changeType = (type: string) => { | ||||
|         if (appInstalles.value && appInstalles.value.length > 0) { | ||||
|             website.value.appInstallId = appInstalles.value[0].id; | ||||
|         } | ||||
|     } else if (type == 'runtime') { | ||||
|         getRuntimes(); | ||||
|     } else { | ||||
|         website.value.appInstallId = undefined; | ||||
|     } | ||||
| @@ -273,7 +313,7 @@ const changeType = (type: string) => { | ||||
| const searchAppInstalled = () => { | ||||
|     GetAppInstalled({ type: 'website', unused: true }).then((res) => { | ||||
|         appInstalles.value = res.data; | ||||
|         if (res.data.length > 0) { | ||||
|         if (res.data && res.data.length > 0) { | ||||
|             website.value.appInstallId = res.data[0].id; | ||||
|         } | ||||
|     }); | ||||
| @@ -318,6 +358,27 @@ const getAppDetail = (version: string) => { | ||||
|     }); | ||||
| }; | ||||
|  | ||||
| const getAppDetailByID = (id: number) => { | ||||
|     GetAppDetailByID(id).then((res) => { | ||||
|         website.value.appinstall.appDetailId = res.data.id; | ||||
|         appDetail.value = res.data; | ||||
|         appParams.value = res.data.params; | ||||
|         paramKey.value++; | ||||
|     }); | ||||
| }; | ||||
|  | ||||
| const getRuntimes = async () => { | ||||
|     try { | ||||
|         const res = await SearchRuntimes(runtimeReq.value); | ||||
|         runtimes.value = res.data.items || []; | ||||
|         if (runtimes.value.length > 0) { | ||||
|             const first = runtimes.value[0]; | ||||
|             website.value.runtimeID = first.id; | ||||
|             getAppDetailByID(first.appDetailId); | ||||
|         } | ||||
|     } catch (error) {} | ||||
| }; | ||||
|  | ||||
| const acceptParams = async (installPath: string) => { | ||||
|     if (websiteForm.value) { | ||||
|         websiteForm.value.resetFields(); | ||||
| @@ -328,7 +389,7 @@ const acceptParams = async (installPath: string) => { | ||||
|     groups.value = res.data; | ||||
|     open.value = true; | ||||
|     website.value.webSiteGroupId = res.data[0].id; | ||||
|  | ||||
|     website.value.type = 'deployment'; | ||||
|     searchAppInstalled(); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -14,11 +14,18 @@ | ||||
|                         {{ $t('website.forceDeleteHelper') }} | ||||
|                     </span> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item v-if="type === 'deployment'"> | ||||
|                     <el-checkbox v-model="deleteReq.deleteApp" :label="$t('website.deleteApp')" /> | ||||
|                 <el-form-item v-if="type === 'deployment' || runtimeApp"> | ||||
|                     <el-checkbox | ||||
|                         v-model="deleteReq.deleteApp" | ||||
|                         :disabled="runtimeApp" | ||||
|                         :label="$t('website.deleteApp')" | ||||
|                     /> | ||||
|                     <span class="input-help"> | ||||
|                         {{ $t('website.deleteAppHelper') }} | ||||
|                     </span> | ||||
|                     <span class="input-help" style="color: red"> | ||||
|                         {{ $t('website.deleteRuntimeHelper') }} | ||||
|                     </span> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item> | ||||
|                     <el-checkbox v-model="deleteReq.deleteBackup" :label="$t('website.deleteBackup')" /> | ||||
| @@ -66,6 +73,7 @@ const deleteForm = ref<FormInstance>(); | ||||
| let deleteInfo = ref(''); | ||||
| let websiteName = ref(''); | ||||
| let deleteHelper = ref(''); | ||||
| const runtimeApp = ref(false); | ||||
|  | ||||
| const handleClose = () => { | ||||
|     open.value = false; | ||||
| @@ -79,6 +87,10 @@ const acceptParams = async (website: Website.Website) => { | ||||
|         deleteBackup: false, | ||||
|         forceDelete: false, | ||||
|     }; | ||||
|     if (website.type === 'runtime' && website.appInstallId > 0) { | ||||
|         runtimeApp.value = true; | ||||
|         deleteReq.value.deleteApp = true; | ||||
|     } | ||||
|     deleteInfo.value = ''; | ||||
|     deleteReq.value.id = website.id; | ||||
|     websiteName.value = website.primaryDomain; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user