From 04a76fd94dee410512468fde092ce751b092bc16 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 Date: Tue, 4 Apr 2023 18:54:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=85=8D=E7=BD=AE=20?= =?UTF-8?q?php=20=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/app_install.go | 5 +- backend/app/api/v1/website.go | 6 +- backend/app/dto/request/website.go | 6 ++ backend/app/repo/app_install.go | 8 ++ backend/app/service/app_install.go | 10 +- backend/app/service/website.go | 70 ++++++++++---- backend/app/service/website_utils.go | 32 ++++++- backend/constant/runtime.go | 3 + backend/constant/website.go | 3 + backend/router/ro_website.go | 2 +- backend/utils/nginx/components/server.go | 13 ++- frontend/src/api/interface/website.ts | 1 + frontend/src/api/modules/website.ts | 4 +- frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/zh.ts | 5 + .../website/website/config/resource/index.vue | 36 +++++++- .../website/config/resource/nginx/index.vue | 4 +- .../website/config/resource/php-fpm/index.vue | 91 +++++++++++++++++++ .../views/website/website/create/index.vue | 41 +++++++-- 19 files changed, 295 insertions(+), 46 deletions(-) create mode 100644 frontend/src/views/website/website/config/resource/php-fpm/index.vue diff --git a/backend/app/api/v1/app_install.go b/backend/app/api/v1/app_install.go index ea0051a3a..b4c54bec9 100644 --- a/backend/app/api/v1/app_install.go +++ b/backend/app/api/v1/app_install.go @@ -166,10 +166,13 @@ func (b *BaseApi) OperateInstalled(c *gin.Context) { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return } - if err := appInstallService.Operate(req); err != nil { + tx, ctx := helper.GetTxAndContext() + if err := appInstallService.Operate(ctx, req); err != nil { + tx.Rollback() helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } + tx.Commit() helper.SuccessWithData(c, nil) } diff --git a/backend/app/api/v1/website.go b/backend/app/api/v1/website.go index dd4e36480..55a0a76cc 100644 --- a/backend/app/api/v1/website.go +++ b/backend/app/api/v1/website.go @@ -189,14 +189,16 @@ func (b *BaseApi) GetWebsite(c *gin.Context) { // @Param id path integer true "request" // @Success 200 {object} response.FileInfo // @Security ApiKeyAuth -// @Router /websites/:id/nginx [get] +// @Router /websites/:id/config/:type [get] func (b *BaseApi) GetWebsiteNginx(c *gin.Context) { id, err := helper.GetParamID(c) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) return } - fileInfo, err := websiteService.GetWebsiteNginxConfig(id) + configType := c.Param("type") + + fileInfo, err := websiteService.GetWebsiteNginxConfig(id, configType) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index f95170550..1c7eb8f8b 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -25,6 +25,12 @@ type WebsiteCreate struct { AppInstallID uint `json:"appInstallID"` RuntimeID uint `json:"runtimeID"` + RuntimeConfig +} + +type RuntimeConfig struct { + ProxyType string `json:"proxyType"` + Port int `json:"port"` } type NewAppInstall struct { diff --git a/backend/app/repo/app_install.go b/backend/app/repo/app_install.go index 8810966e2..d036038b0 100644 --- a/backend/app/repo/app_install.go +++ b/backend/app/repo/app_install.go @@ -29,6 +29,7 @@ type IAppInstallRepo interface { Page(page, size int, opts ...DBOption) (int64, []model.AppInstall, error) BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error LoadBaseInfo(key string, name string) (*RootInfo, error) + GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error) } func NewIAppInstallRepo() IAppInstallRepo { @@ -97,6 +98,13 @@ func (a *AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) { return install, err } +func (a *AppInstallRepo) GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error) { + var install model.AppInstall + db := getTx(ctx, opts...).Model(&model.AppInstall{}) + err := db.Preload("App").First(&install).Error + return install, err +} + func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error { db := getTx(ctx).Model(&model.AppInstall{}) return db.Create(&install).Error diff --git a/backend/app/service/app_install.go b/backend/app/service/app_install.go index 9545c9089..29e1c1a2c 100644 --- a/backend/app/service/app_install.go +++ b/backend/app/service/app_install.go @@ -1,6 +1,7 @@ package service import ( + "context" "encoding/json" "fmt" "io/ioutil" @@ -40,7 +41,7 @@ type IAppInstallService interface { LoadPort(key string) (int64, error) LoadPassword(key string) (string, error) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) - Operate(req request.AppInstalledOperate) error + Operate(ctx context.Context, req request.AppInstalledOperate) error Update(req request.AppInstalledUpdate) error SyncAll(systemInit bool) error GetServices(key string) ([]response.AppService, error) @@ -174,8 +175,8 @@ func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([] return handleInstalled(installs, false) } -func (a *AppInstallService) Operate(req request.AppInstalledOperate) error { - install, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId)) +func (a *AppInstallService) Operate(ctx context.Context, req request.AppInstalledOperate) error { + install, err := appInstallRepo.GetFirstByCtx(ctx, commonRepo.WithByID(req.InstallId)) if err != nil { return err } @@ -202,12 +203,9 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error { } return syncById(install.ID) case constant.Delete: - tx, ctx := getTxAndContext() if err := deleteAppInstall(ctx, install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete { - tx.Rollback() return err } - tx.Commit() return nil case constant.Sync: return syncById(install.ID) diff --git a/backend/app/service/website.go b/backend/app/service/website.go index b2e2377f3..3a22ea900 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -8,6 +8,7 @@ import ( "os" "path" "reflect" + "regexp" "strings" "time" @@ -42,7 +43,7 @@ type IWebsiteService interface { DeleteWebsiteDomain(domainId uint) error GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error) UpdateNginxConfigByScope(req request.NginxConfigUpdate) error - GetWebsiteNginxConfig(websiteId uint) (response.FileInfo, error) + GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) @@ -104,7 +105,7 @@ func (w WebsiteService) GetWebsites() ([]response.WebsiteDTO, error) { return websiteDTOs, nil } -func (w WebsiteService) CreateWebsite(ctx context.Context, create request.WebsiteCreate) error { +func (w WebsiteService) CreateWebsite(ctx context.Context, create request.WebsiteCreate) (err error) { if exist, _ := websiteRepo.GetBy(websiteRepo.WithDomain(create.PrimaryDomain)); len(exist) > 0 { return buserr.New(constant.ErrDomainIsExist) } @@ -125,6 +126,7 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit ExpireDate: defaultDate, AppInstallID: create.AppInstallID, WebsiteGroupID: create.WebsiteGroupID, + RuntimeID: create.RuntimeID, Protocol: constant.ProtocolHTTP, Proxy: create.Proxy, AccessLog: true, @@ -135,6 +137,22 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit appInstall *model.AppInstall runtime *model.Runtime ) + + defer func() { + if err != nil { + if website.AppInstallID > 0 { + req := request.AppInstalledOperate{ + InstallId: website.AppInstallID, + Operate: constant.Delete, + ForceDelete: true, + } + if err := NewIAppInstalledService().Operate(ctx, req); err != nil { + global.LOG.Errorf(err.Error()) + } + } + } + }() + switch create.Type { case constant.Deployment: if create.AppType == constant.NewApp { @@ -164,7 +182,8 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit } if runtime.Resource == constant.ResourceAppstore { var req request.AppInstallCreate - req.Name = create.PrimaryDomain + reg, _ := regexp.Compile("[^a-z0-9_\\-]+") + req.Name = reg.ReplaceAllString(create.PrimaryDomain, "") req.AppDetailId = create.AppInstall.AppDetailId req.Params = create.AppInstall.Params req.Params["IMAGE_NAME"] = runtime.Image @@ -182,7 +201,7 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit } } - if err := websiteRepo.Create(ctx, website); err != nil { + if err = websiteRepo.Create(ctx, website); err != nil { return err } var domains []model.WebsiteDomain @@ -203,11 +222,11 @@ func (w WebsiteService) CreateWebsite(ctx context.Context, create request.Websit domains = append(domains, domainModel) } if len(domains) > 0 { - if err := websiteDomainRepo.BatchCreate(ctx, domains); err != nil { + if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil { return err } } - return configDefaultNginx(website, domains, appInstall, runtime) + return configDefaultNginx(website, domains, appInstall, runtime, create.RuntimeConfig) } func (w WebsiteService) OpWebsite(req request.WebsiteOp) error { @@ -429,23 +448,38 @@ func (w WebsiteService) UpdateNginxConfigByScope(req request.NginxConfigUpdate) return updateNginxConfig(constant.NginxScopeServer, params, &website) } -func (w WebsiteService) GetWebsiteNginxConfig(websiteId uint) (response.FileInfo, error) { +func (w WebsiteService) GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteId)) if err != nil { return response.FileInfo{}, err } - - nginxApp, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty)) - if err != nil { - return response.FileInfo{}, err + configPath := "" + switch configType { + case constant.AppOpenresty: + nginxApp, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty)) + if err != nil { + return response.FileInfo{}, err + } + nginxInstall, err := appInstallRepo.GetFirst(appInstallRepo.WithAppId(nginxApp.ID)) + if err != nil { + return response.FileInfo{}, err + } + configPath = path.Join(nginxInstall.GetPath(), "conf", "conf.d", website.Alias+".conf") + case constant.ConfigFPM: + runtimeInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) + if err != nil { + return response.FileInfo{}, err + } + runtimeInstall.GetPath() + configPath = path.Join(runtimeInstall.GetPath(), "conf", "php-fpm.conf") + case constant.ConfigPHP: + runtimeInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) + if err != nil { + return response.FileInfo{}, err + } + runtimeInstall.GetPath() + configPath = path.Join(runtimeInstall.GetPath(), "conf", "php.ini") } - nginxInstall, err := appInstallRepo.GetFirst(appInstallRepo.WithAppId(nginxApp.ID)) - if err != nil { - return response.FileInfo{}, err - } - - configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", website.Alias+".conf") - info, err := files.NewFileInfo(files.FileOption{ Path: configPath, Expand: true, diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index ca44fa275..09d7459cd 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -105,6 +105,15 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website, if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), 0755); err != nil { return err } + if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceLocal { + phpPoolDir := path.Join(siteFolder, "php-pool") + if err := fileOp.CreateDir(phpPoolDir, 0755); err != nil { + return err + } + if err := fileOp.CreateFile(path.Join(phpPoolDir, "php-fpm.sock")); err != nil { + return err + } + } if website.Type == constant.Static || website.Type == constant.Runtime { if err := createIndexFile(website, runtime); err != nil { return err @@ -114,7 +123,7 @@ 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, runtime *model.Runtime) error { +func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime, runtimeConfig request.RuntimeConfig) error { nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err @@ -147,25 +156,38 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a server.UpdateDirective("set", []string{"$RulePath", path.Join(siteFolder, "waf", "rules")}) server.UpdateDirective("set", []string{"$logdir", path.Join(siteFolder, "log")}) + rootIndex := path.Join("/www/sites", website.Alias, "index") switch website.Type { case constant.Deployment: proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort) server.UpdateRootProxy([]string{proxy}) case constant.Static: - server.UpdateRoot(path.Join("/www/sites", website.Alias, "index")) + server.UpdateRoot(rootIndex) //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")) + switch runtime.Type { + case constant.RuntimePHP: + server.UpdateRoot(rootIndex) + proxy := "" + localPath := path.Join(nginxInstall.GetPath(), rootIndex, "index.php") + if runtimeConfig.ProxyType == constant.RuntimeProxyUnix { + proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock")) + } + if runtimeConfig.ProxyType == constant.RuntimeProxyTcp { + proxy = fmt.Sprintf("127.0.0.1:%d", runtimeConfig.Port) + } + server.UpdatePHPProxy([]string{proxy}, localPath) + } } if runtime.Resource == constant.ResourceAppstore { switch runtime.Type { case constant.RuntimePHP: - server.UpdateRoot(path.Join("/www/sites", website.Alias, "index")) + server.UpdateRoot(rootIndex) proxy := fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) - server.UpdatePHPProxy([]string{proxy}) + server.UpdatePHPProxy([]string{proxy}, "") } } } diff --git a/backend/constant/runtime.go b/backend/constant/runtime.go index 6af7a1fb3..e854c8648 100644 --- a/backend/constant/runtime.go +++ b/backend/constant/runtime.go @@ -9,4 +9,7 @@ const ( RuntimeBuildIng = "building" RuntimePHP = "php" + + RuntimeProxyUnix = "unix" + RuntimeProxyTcp = "tcp" ) diff --git a/backend/constant/website.go b/backend/constant/website.go index 1ccd278f4..354909de2 100644 --- a/backend/constant/website.go +++ b/backend/constant/website.go @@ -41,4 +41,7 @@ const ( AccessLog = "access.log" ErrorLog = "error.log" + + ConfigPHP = "php" + ConfigFPM = "fpm" ) diff --git a/backend/router/ro_website.go b/backend/router/ro_website.go index 079b5eaed..f9a70ad2d 100644 --- a/backend/router/ro_website.go +++ b/backend/router/ro_website.go @@ -31,7 +31,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) { groupRouter.POST("/domains/del", baseApi.DeleteWebDomain) groupRouter.POST("/domains", baseApi.CreateWebDomain) - groupRouter.GET("/:id/nginx", baseApi.GetWebsiteNginx) + groupRouter.GET("/:id/config/:type", baseApi.GetWebsiteNginx) groupRouter.POST("/config", baseApi.GetNginxConfig) groupRouter.POST("/config/update", baseApi.UpdateNginxConfig) groupRouter.POST("/nginx/update", baseApi.UpdateWebsiteNginxConfig) diff --git a/backend/utils/nginx/components/server.go b/backend/utils/nginx/components/server.go index b5c12f4fa..a33f8e008 100644 --- a/backend/utils/nginx/components/server.go +++ b/backend/utils/nginx/components/server.go @@ -237,7 +237,7 @@ func (s *Server) UpdateRootProxy(proxy []string) { s.UpdateDirectiveBySecondKey("location", "/", newDir) } -func (s *Server) UpdatePHPProxy(proxy []string) { +func (s *Server) UpdatePHPProxy(proxy []string, localPath string) { newDir := Directive{ Name: "location", Parameters: []string{"~ [^/]\\.php(/|$)"}, @@ -256,6 +256,17 @@ func (s *Server) UpdatePHPProxy(proxy []string) { Name: "include", Parameters: []string{"fastcgi_params"}, }) + if localPath == "" { + block.Directives = append(block.Directives, &Directive{ + Name: "fastcgi_param", + Parameters: []string{"SCRIPT_FILENAME", "$document_root$fastcgi_script_name"}, + }) + } else { + block.Directives = append(block.Directives, &Directive{ + Name: "fastcgi_param", + Parameters: []string{"SCRIPT_FILENAME", localPath}, + }) + } newDir.Block = block s.UpdateDirectiveBySecondKey("location", "~ [^/]\\.php(/|$)", newDir) } diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 0884fa5c6..ae616c975 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -16,6 +16,7 @@ export namespace Website { autoRenew: boolean; appinstall?: NewAppInstall; webSiteSSL: SSL; + runtimeID: number; } export interface WebsiteDTO extends Website { diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 8bbfedefe..3774165af 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -35,8 +35,8 @@ export const GetWebsiteOptions = () => { return http.get>(`/websites/options`); }; -export const GetWebsiteNginx = (id: number) => { - return http.get(`/websites/${id}/nginx`); +export const GetWebsiteConfig = (id: number, type: string) => { + return http.get(`/websites/${id}/config/${type}`); }; export const DeleteWebsite = (req: Website.WebSiteDel) => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 90ad8c246..f54f77ea4 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1138,6 +1138,7 @@ const message = { runtime: 'Runtime', deleteRuntimeHelper: 'The Runtime application needs to be deleted together with the website, please handle it with caution', + proxyType: 'Listening Network Type', }, 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 0bad47c76..2bf0e457b 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1136,6 +1136,11 @@ const message = { runtimeProxyHelper: '使用从 1Panel 创建的运行环境', runtime: '运行环境', deleteRuntimeHelper: '运行环境应用需要跟网站一并删除,请谨慎处理', + proxyType: '监听网络类型', + unix: 'Uinx 网络', + tcp: 'TCP/IP 网络', + phpFPM: 'FPM 配置文件', + phpConfig: 'PHP 配置文件', }, nginx: { serverNamesHashBucketSizeHelper: '服务器名字的hash表大小', diff --git a/frontend/src/views/website/website/config/resource/index.vue b/frontend/src/views/website/website/config/resource/index.vue index 2f5e41bf5..fbb1bf06a 100644 --- a/frontend/src/views/website/website/config/resource/index.vue +++ b/frontend/src/views/website/website/config/resource/index.vue @@ -1,10 +1,23 @@ diff --git a/frontend/src/views/website/website/config/resource/nginx/index.vue b/frontend/src/views/website/website/config/resource/nginx/index.vue index 0966ad185..52a8bf441 100644 --- a/frontend/src/views/website/website/config/resource/nginx/index.vue +++ b/frontend/src/views/website/website/config/resource/nginx/index.vue @@ -23,7 +23,7 @@ diff --git a/frontend/src/views/website/website/create/index.vue b/frontend/src/views/website/website/create/index.vue index ba4a15fcd..cde2eba5e 100644 --- a/frontend/src/views/website/website/create/index.vue +++ b/frontend/src/views/website/website/create/index.vue @@ -139,7 +139,7 @@ - +
- + +
+ + + + + + + + + +
({ primaryDomain: [Rules.domain], @@ -265,6 +279,8 @@ let rules = ref({ appId: [Rules.requiredSelectBusiness], params: {}, }, + proxyType: [Rules.requiredSelect], + port: [Rules.port], }); let open = ref(false); @@ -284,6 +300,7 @@ let appParams = ref(); let paramKey = ref(1); let preCheckRef = ref(); let staticPath = ref(''); +let runtimeResource = ref('appstore'); const runtimeReq = ref({ page: 1, pageSize: 20, @@ -367,6 +384,14 @@ const getAppDetailByID = (id: number) => { }); }; +const changeRuntime = (runID: number) => { + runtimes.value.forEach((item) => { + if (item.id === runID) { + runtimeResource.value = item.resource; + } + }); +}; + const getRuntimes = async () => { try { const res = await SearchRuntimes(runtimeReq.value); @@ -374,6 +399,7 @@ const getRuntimes = async () => { if (runtimes.value.length > 0) { const first = runtimes.value[0]; website.value.runtimeID = first.id; + runtimeResource.value = first.resource; getAppDetailByID(first.appDetailId); } } catch (error) {} @@ -387,10 +413,13 @@ const acceptParams = async (installPath: string) => { const res = await GetGroupList({ type: 'website' }); groups.value = res.data; - open.value = true; website.value.webSiteGroupId = res.data[0].id; website.value.type = 'deployment'; + runtimeResource.value = 'appstore'; + searchAppInstalled(); + + open.value = true; }; const changeAppType = (type: string) => {