Files
devstar-create-from-template/routers/web/user/setting/appstore.go
2025-08-25 15:46:12 +08:00

339 lines
9.4 KiB
Go

// 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"})
}