diff --git a/backend/app/api/v1/app.go b/backend/app/api/v1/app.go index 882ee59ba..90476030d 100644 --- a/backend/app/api/v1/app.go +++ b/backend/app/api/v1/app.go @@ -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 安装应用 diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index 22c67a6a3..f95170550 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -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 { diff --git a/backend/app/dto/response/runtime.go b/backend/app/dto/response/runtime.go index df85cc3c8..b6674d284 100644 --- a/backend/app/dto/response/runtime.go +++ b/backend/app/dto/response/runtime.go @@ -6,5 +6,4 @@ type RuntimeRes struct { model.Runtime AppParams []AppParam `json:"appParams"` AppID uint `json:"appId"` - Version string `json:"version"` } diff --git a/backend/app/model/website.go b/backend/app/model/website.go index 7f3a7a099..4b9866d87 100644 --- a/backend/app/model/website.go +++ b/backend/app/model/website.go @@ -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"` } diff --git a/backend/app/repo/runtime.go b/backend/app/repo/runtime.go index 3e4fe2abb..4833f8ad8 100644 --- a/backend/app/repo/runtime.go +++ b/backend/app/repo/runtime.go @@ -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) } } diff --git a/backend/app/service/app.go b/backend/app/service/app.go index 014b0142d..bd3823e72 100644 --- a/backend/app/service/app.go +++ b/backend/app/service/app.go @@ -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 { diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go index 6c488207d..41cf51fce 100644 --- a/backend/app/service/app_utils.go +++ b/backend/app/service/app_utils.go @@ -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) diff --git a/backend/app/service/runtime.go b/backend/app/service/runtime.go index 2f51b5ccc..11cf2c4bf 100644 --- a/backend/app/service/runtime.go +++ b/backend/app/service/runtime.go @@ -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 { diff --git a/backend/app/service/website.go b/backend/app/service/website.go index c6466c285..b2e2377f3 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -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 { diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index 08a8b3d27..ca44fa275 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -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 diff --git a/backend/constant/errs.go b/backend/constant/errs.go index 80b57b4c5..44700ec25 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -106,8 +106,8 @@ var ( // runtime var ( - ErrDirNotFound = "ErrDirNotFound" - ErrFileNotExist = "ErrFileNotExist" - ErrImageBuildErr = "ErrImageBuildErr" - ErrNameOrImageIsExist = "ErrNameOrImageIsExist" + ErrDirNotFound = "ErrDirNotFound" + ErrFileNotExist = "ErrFileNotExist" + ErrImageBuildErr = "ErrImageBuildErr" + ErrImageExist = "ErrImageExist" ) diff --git a/backend/constant/website.go b/backend/constant/website.go index 06fdba702..1ccd278f4 100644 --- a/backend/constant/website.go +++ b/backend/constant/website.go @@ -17,6 +17,7 @@ const ( Deployment = "deployment" Static = "static" Proxy = "proxy" + Runtime = "runtime" SSLExisted = "existed" SSLAuto = "auto" diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 841706a2a..485499932 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -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" \ No newline at end of file +ErrImageExist: "Image is already exist!" \ No newline at end of file diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index b002a7ecd..99e84072c 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -64,4 +64,4 @@ ErrObjectInUsed: "该对象正被使用,无法删除" ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!" ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!" ErrImageBuildErr: "镜像 build 失败" -ErrNameOrImageIsExist: "名称或者镜像重复" \ No newline at end of file +ErrImageExist: "镜像已存在!" \ No newline at end of file diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index 6c5919960..539605357 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -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{}) }, } diff --git a/backend/router/ro_app.go b/backend/router/ro_app.go index 9c253cbe4..7aeb00c92 100644 --- a/backend/router/ro_app.go +++ b/backend/router/ro_app.go @@ -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) diff --git a/backend/utils/files/file_op.go b/backend/utils/files/file_op.go index c6bb227c9..ef67bed5f 100644 --- a/backend/utils/files/file_op.go +++ b/backend/utils/files/file_op.go @@ -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) } diff --git a/backend/utils/nginx/components/server.go b/backend/utils/nginx/components/server.go index 961b71859..b5c12f4fa 100644 --- a/backend/utils/nginx/components/server.go +++ b/backend/utils/nginx/components/server.go @@ -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 diff --git a/cmd/server/nginx_conf/index.php b/cmd/server/nginx_conf/index.php new file mode 100644 index 000000000..5cd4e8c46 --- /dev/null +++ b/cmd/server/nginx_conf/index.php @@ -0,0 +1,25 @@ +欢迎使用 PHP!'; +echo '

版本信息

'; + +echo ''; + +echo '

已安装扩展

'; +printExtensions(); + +/** + * 获取已安装扩展列表 + */ +function printExtensions() +{ + echo '
    '; + foreach (get_loaded_extensions() as $i => $name) { + echo "
  1. ", $name, '=', phpversion($name), '
  2. '; + } + echo '
'; +} \ No newline at end of file diff --git a/cmd/server/nginx_conf/nginx_conf.go b/cmd/server/nginx_conf/nginx_conf.go index 8f396ce51..7a6b47ac1 100644 --- a/cmd/server/nginx_conf/nginx_conf.go +++ b/cmd/server/nginx_conf/nginx_conf.go @@ -12,3 +12,6 @@ var WebsiteDefault []byte //go:embed index.html var Index []byte + +//go:embed index.php +var IndexPHP []byte diff --git a/frontend/src/api/interface/runtime.ts b/frontend/src/api/interface/runtime.ts index c544ad901..85f6a7ce7 100644 --- a/frontend/src/api/interface/runtime.ts +++ b/frontend/src/api/interface/runtime.ts @@ -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 { diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts index 543a6af75..7b191458f 100644 --- a/frontend/src/api/modules/app.ts +++ b/frontend/src/api/modules/app.ts @@ -22,8 +22,12 @@ export const GetAppTags = () => { return http.get('apps/tags'); }; -export const GetAppDetail = (id: number, version: string, type: string) => { - return http.get(`apps/detail/${id}/${version}/${type}`); +export const GetAppDetail = (appID: number, version: string, type: string) => { + return http.get(`apps/detail/${appID}/${version}/${type}`); +}; + +export const GetAppDetailByID = (id: number) => { + return http.get(`apps/details/${id}`); }; export const InstallApp = (install: App.AppInstall) => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index c38c10c57..90ad8c246 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 95260cf77..0bad47c76 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1133,6 +1133,9 @@ const message = { restoreHelper: '确认使用此备份恢复?', wafValueHelper: '值', wafRemarkHelper: '描述', + runtimeProxyHelper: '使用从 1Panel 创建的运行环境', + runtime: '运行环境', + deleteRuntimeHelper: '运行环境应用需要跟网站一并删除,请谨慎处理', }, nginx: { serverNamesHashBucketSizeHelper: '服务器名字的hash表大小', diff --git a/frontend/src/views/website/runtime/create/index.vue b/frontend/src/views/website/runtime/create/index.vue index e84cde6c9..e1efe113a 100644 --- a/frontend/src/views/website/runtime/create/index.vue +++ b/frontend/src/views/website/runtime/create/index.vue @@ -22,15 +22,15 @@ v-model="runtime.resource" @change="changeResource(runtime.resource)" > - + {{ $t('runtime.appstore') }} - + {{ $t('runtime.local') }} -
+
@@ -134,7 +134,7 @@ const runtime = ref({ image: '', params: {}, type: 'php', - resource: 'AppStore', + resource: 'appstore', }); let rules = ref({ 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 { diff --git a/frontend/src/views/website/runtime/index.vue b/frontend/src/views/website/runtime/index.vue index fa55efef0..5133af0ae 100644 --- a/frontend/src/views/website/runtime/index.vue +++ b/frontend/src/views/website/runtime/index.vue @@ -18,7 +18,7 @@ diff --git a/frontend/src/views/website/website/create/index.vue b/frontend/src/views/website/website/create/index.vue index 56f563eeb..ba4a15fcd 100644 --- a/frontend/src/views/website/website/create/index.vue +++ b/frontend/src/views/website/website/create/index.vue @@ -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" /> +
+
+ + + + + + +
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(); 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({ 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(); let paramKey = ref(1); let preCheckRef = ref(); let staticPath = ref(''); +const runtimeReq = ref({ + page: 1, + pageSize: 20, +}); +const runtimes = ref([]); 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(); }; diff --git a/frontend/src/views/website/website/delete/index.vue b/frontend/src/views/website/website/delete/index.vue index 42f93d828..79c86cda7 100644 --- a/frontend/src/views/website/website/delete/index.vue +++ b/frontend/src/views/website/website/delete/index.vue @@ -14,11 +14,18 @@ {{ $t('website.forceDeleteHelper') }} - - + + {{ $t('website.deleteAppHelper') }} + + {{ $t('website.deleteRuntimeHelper') }} + @@ -66,6 +73,7 @@ const deleteForm = ref(); 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;