339 lines
9.4 KiB
Go
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"})
|
|
}
|