// Copyright 2024 The Devstar Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting import ( "io" "net/http" "strings" appstore_model "code.gitea.io/gitea/models/appstore" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/services/appstore" "code.gitea.io/gitea/services/context" ) const ( tplSettingsAppStore templates.TplName = "user/settings/appstore" ) // AppStore displays the app store page func AppStore(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.appstore") ctx.Data["PageIsSettingsAppStore"] = true ctx.HTML(http.StatusOK, tplSettingsAppStore) } // AppStoreAPI handles API requests for app store data func AppStoreAPI(ctx *context.Context) { action := ctx.PathParam("action") switch action { case "apps": handleGetApps(ctx) case "categories": handleGetCategories(ctx) case "tags": handleGetTags(ctx) default: ctx.JSON(http.StatusBadRequest, map[string]interface{}{ "error": "Invalid action", }) } } // handleGetApps returns the list of apps from database func handleGetApps(ctx *context.Context) { // Get query parameters category := ctx.FormString("category") tag := ctx.FormString("tag") search := ctx.FormString("search") deployment := ctx.FormString("deployment") // docker, kubernetes, all source := ctx.FormString("source") // local | devstar manager := appstore.NewManager(ctx) var apps []appstore.App var err error if source == "devstar" { apps, err = manager.ListAppsFromDevstar() if err == nil { // 与本地一致的过滤逻辑(先拿全量,再在服务端筛选) if category != "" || tag != "" || search != "" || deployment != "" { var filtered []appstore.App for _, a := range apps { if category != "" && a.Category != category { continue } if tag != "" { matched := false for _, t := range a.Tags { if t == tag { matched = true break } } if !matched { continue } } if deployment != "" && deployment != "all" { // 处理部署类型过滤,包括 'both' 类型 appDeployment := a.DeploymentType if appDeployment == "" { // 如果没有 deployment_type 字段,尝试从其他字段获取 if a.Deploy.Type != "" { appDeployment = a.Deploy.Type } else { appDeployment = "docker" // 默认值 } } if appDeployment != "both" && appDeployment != deployment { continue } } if search != "" { low := strings.ToLower(search) nameOk := strings.Contains(strings.ToLower(a.Name), low) descOk := strings.Contains(strings.ToLower(a.Description), low) authorOk := strings.Contains(strings.ToLower(a.Author), low) if !(nameOk || descOk || authorOk) { continue } } filtered = append(filtered, a) } apps = filtered } } } else { if search != "" { // Convert tag parameter to tags slice var tags []string if tag != "" { tags = strings.Split(tag, ",") } apps, err = manager.SearchApps(search, category, tags) } else { apps, err = manager.ListApps() // Filter by category, tag and deployment if specified if category != "" || tag != "" || deployment != "" { var filteredApps []appstore.App for _, app := range apps { matchCategory := category == "" || app.Category == category matchTag := tag == "" if tag != "" { for _, appTag := range app.Tags { if appTag == tag { matchTag = true break } } } // 处理部署类型过滤,包括 'both' 类型 matchDeployment := deployment == "" || deployment == "all" if deployment != "" && deployment != "all" { appDeployment := app.DeploymentType if appDeployment == "" { // 如果没有 deployment_type 字段,尝试从其他字段获取 if app.Deploy.Type != "" { appDeployment = app.Deploy.Type } else { appDeployment = "docker" // 默认值 } } matchDeployment = appDeployment == "both" || appDeployment == deployment } if matchCategory && matchTag && matchDeployment { filteredApps = append(filteredApps, app) } } apps = filteredApps } } } if err != nil { ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ "error": err.Error(), }) return } ctx.JSON(http.StatusOK, map[string]interface{}{ "apps": apps, }) } // handleGetCategories returns the list of categories func handleGetCategories(ctx *context.Context) { categories, err := appstore_model.GetCategories(ctx) if err != nil { ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ "error": err.Error(), }) return } ctx.JSON(http.StatusOK, map[string]interface{}{ "categories": categories, }) } // handleGetTags returns the list of tags func handleGetTags(ctx *context.Context) { tags, err := appstore_model.GetTags(ctx) if err != nil { ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ "error": err.Error(), }) return } ctx.JSON(http.StatusOK, map[string]interface{}{ "tags": tags, }) } // AppStoreInstall handles app installation func AppStoreInstall(ctx *context.Context) { if ctx.Req.Method != "POST" { ctx.JSON(405, map[string]string{"error": "Method Not Allowed"}) return } // 解析表单数据 appID := ctx.FormString("app_id") configJSON := ctx.FormString("config") installTarget := ctx.FormString("install_target") kubeconfig := ctx.FormString("kubeconfig") kubeconfigContext := ctx.FormString("kubeconfig_context") if appID == "" { ctx.JSON(400, map[string]string{"error": "应用ID不能为空"}) return } // 创建 manager 并执行安装 manager := appstore.NewManager(ctx) if err := manager.InstallApp(appID, configJSON, installTarget, kubeconfig, kubeconfigContext); err != nil { // 根据错误类型返回相应的状态码和消息 if appErr, ok := err.(*appstore.AppStoreError); ok { switch appErr.Code { case "APP_NOT_FOUND", "CONFIG_MERGE_ERROR": ctx.JSON(400, map[string]string{"error": appErr.Message + ": " + appErr.Details}) case "KUBERNETES_INSTALL_ERROR": ctx.JSON(500, map[string]string{"error": appErr.Message + ": " + appErr.Details}) case "NOT_IMPLEMENTED": ctx.JSON(501, map[string]string{"error": appErr.Message}) default: ctx.JSON(500, map[string]string{"error": appErr.Message}) } } else { ctx.JSON(500, map[string]string{"error": "安装失败: " + err.Error()}) } return } // 安装成功 if installTarget == "kubeconfig" && kubeconfig != "" { ctx.Flash.Success("应用已成功安装到外部 Kubernetes 集群") } else { ctx.Flash.Success("应用配置已准备完成,本地安装功能开发中") } ctx.Redirect(setting.AppSubURL + "/user/settings/appstore") } // AppStoreUninstall handles app uninstallation func AppStoreUninstall(ctx *context.Context) { if ctx.Req.Method != "POST" { ctx.JSON(405, map[string]string{"error": "Method Not Allowed"}) return } // 解析表单数据 appID := ctx.FormString("app_id") installTarget := ctx.FormString("install_target") kubeconfig := ctx.FormString("kubeconfig") kubeconfigContext := ctx.FormString("kubeconfig_context") if appID == "" { ctx.JSON(400, map[string]string{"error": "应用ID不能为空"}) return } // 创建 manager 并执行卸载 manager := appstore.NewManager(ctx) if err := manager.UninstallApp(appID, installTarget, kubeconfig, kubeconfigContext); err != nil { // 根据错误类型返回相应的状态码和消息 if appErr, ok := err.(*appstore.AppStoreError); ok { switch appErr.Code { case "APP_NOT_FOUND": ctx.JSON(400, map[string]string{"error": appErr.Message + ": " + appErr.Details}) case "KUBERNETES_UNINSTALL_ERROR": ctx.JSON(500, map[string]string{"error": appErr.Message + ": " + appErr.Details}) case "NOT_IMPLEMENTED": ctx.JSON(501, map[string]string{"error": appErr.Message}) default: ctx.JSON(500, map[string]string{"error": appErr.Message}) } } else { ctx.JSON(500, map[string]string{"error": "卸载失败: " + err.Error()}) } return } // 卸载成功 if installTarget == "kubeconfig" && kubeconfig != "" { ctx.Flash.Success("应用已成功从外部 Kubernetes 集群卸载") } else { ctx.Flash.Success("本地卸载功能开发中") } ctx.Redirect(setting.AppSubURL + "/user/settings/appstore") } // AppStoreConfigure handles app configuration func AppStoreConfigure(ctx *context.Context) { // TODO: Load app configuration form ctx.Data["Title"] = "App Configuration" ctx.Data["PageIsSettingsAppStore"] = true ctx.HTML(http.StatusOK, tplSettingsAppStore) } // AppStoreConfigurePost handles app configuration form submission func AppStoreConfigurePost(ctx *context.Context) { // TODO: Handle configuration form submission ctx.Flash.Success("App configuration feature coming soon") ctx.Redirect(setting.AppSubURL + "/user/settings/appstore") } // 添加应用API func AddAppAPI(ctx *context.Context) { manager := appstore.NewManager(ctx) if ctx.Req.Method != "POST" { ctx.JSON(405, map[string]string{"error": "Method Not Allowed"}) return } defer ctx.Req.Body.Close() jsonBytes, err := io.ReadAll(ctx.Req.Body) if err != nil { ctx.JSON(400, map[string]string{"error": "读取请求体失败"}) return } if err := manager.AddAppFromJSON(jsonBytes); err != nil { ctx.JSON(400, map[string]string{"error": err.Error()}) return } ctx.JSON(200, map[string]string{"msg": "ok"}) }