k8s客户端获取从kubeconfig迁移到token
This commit is contained in:
4
go.mod
4
go.mod
@@ -161,10 +161,10 @@ require (
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
@@ -179,9 +179,7 @@ require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/ArtisanCloud/PowerLibs/v3 v3.3.2
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.8 // indirect
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.21
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/DataDog/zstd v1.5.7 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -871,6 +871,8 @@ golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ug
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
|
||||
@@ -28,8 +28,10 @@ type UserAppInstance struct {
|
||||
|
||||
// 部署信息
|
||||
DeployType string `xorm:"NOT NULL"` // 实际部署类型 (kubernetes/docker)
|
||||
Kubeconfig string `xorm:"TEXT"` // Kubeconfig内容(加密存储)
|
||||
KubeconfigContext string // Kubeconfig context
|
||||
|
||||
// Kubernetes 凭据(URL + Token)
|
||||
K8sURL string `xorm:"TEXT"`
|
||||
K8sToken string `xorm:"TEXT"`
|
||||
|
||||
// 元数据
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
|
||||
29
models/migrations/devstar_v1_0/dv5.go
Normal file
29
models/migrations/devstar_v1_0/dv5.go
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-24
|
||||
* Description: Migration dv5 adding Kubernetes credential columns.
|
||||
*/
|
||||
|
||||
package devstar_v1_0
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AddK8sCredentialColumns adds k8s_url and k8s_token columns to user_app_instance.
|
||||
func AddK8sCredentialColumns(x *xorm.Engine) error {
|
||||
if _, err := x.Exec("ALTER TABLE user_app_instance ADD COLUMN k8s_url TEXT DEFAULT ''"); err != nil {
|
||||
return ErrMigrateDevstarDatabase{
|
||||
Step: "add column 'k8s_url' to user_app_instance",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
if _, err := x.Exec("ALTER TABLE user_app_instance ADD COLUMN k8s_token TEXT DEFAULT ''"); err != nil {
|
||||
return ErrMigrateDevstarDatabase{
|
||||
Step: "add column 'k8s_token' to user_app_instance",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -92,6 +92,52 @@ func GetKubernetesClient(ctx context.Context, kubeconfig []byte, contextName str
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// GetKubernetesClientWithToken 通过用户提供的 k8sURL 和 token 获取动态客户端
|
||||
// 如果 k8sURL 和 token 为空,则优先使用配置文件中的 K8sConfig.Url 和 K8sConfig.Token
|
||||
// 如果配置文件也未配置,则返回错误(不回退到 kubeconfig)
|
||||
func GetKubernetesClientWithToken(ctx context.Context, k8sURL, token string) (dynamicclient.Interface, error) {
|
||||
// 如果未提供 URL 和 token,优先使用配置文件中的设置
|
||||
if k8sURL == "" || token == "" {
|
||||
// 优先使用配置文件中的 K8s 配置
|
||||
if setting.K8sConfig.Enable && setting.K8sConfig.Url != "" && setting.K8sConfig.Token != "" {
|
||||
k8sURL = setting.K8sConfig.Url
|
||||
token = setting.K8sConfig.Token
|
||||
log.Info("使用配置文件中的 K8s 配置: URL=%s", k8sURL)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然没有 URL 和 token,直接返回错误
|
||||
if k8sURL == "" || token == "" {
|
||||
return nil, fmt.Errorf("k8sURL and token are required, neither provided nor found in config")
|
||||
}
|
||||
|
||||
// 使用 token 认证创建配置
|
||||
config := &clientgorest.Config{
|
||||
Host: k8sURL,
|
||||
BearerToken: token,
|
||||
TLSClientConfig: clientgorest.TLSClientConfig{
|
||||
Insecure: true,
|
||||
},
|
||||
}
|
||||
|
||||
applyClientDefaults(config)
|
||||
|
||||
// 强制跳过 TLS 证书校验(与 GetKubernetesClient 保持一致)
|
||||
// 同时清空 CA 配置
|
||||
config.TLSClientConfig.Insecure = true
|
||||
config.TLSClientConfig.CAData = nil
|
||||
config.TLSClientConfig.CAFile = ""
|
||||
|
||||
// 尝试创建客户端,如果TLS验证失败则自动跳过验证
|
||||
client, err := dynamicclient.NewForConfig(config)
|
||||
if err != nil {
|
||||
// 再次兜底:若识别为 TLS 错误,已 Insecure,无需再次设置;否则将错误上抛
|
||||
return nil, fmt.Errorf("failed to create k8s client: %v", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// restConfigFromKubeconfigBytes 基于 kubeconfig 内容构造 *rest.Config,支持指定 context(为空则使用 current-context)
|
||||
func restConfigFromKubeconfigBytes(kubeconfig []byte, contextName string) (*clientgorest.Config, error) {
|
||||
|
||||
|
||||
@@ -257,8 +257,8 @@ func AppStoreInstall(ctx *context.Context) {
|
||||
appID := ctx.FormString("app_id")
|
||||
configJSON := ctx.FormString("config")
|
||||
installTarget := ctx.FormString("install_target")
|
||||
kubeconfig := ctx.FormString("kubeconfig")
|
||||
kubeconfigContext := ctx.FormString("kubeconfig_context")
|
||||
k8sURL := ctx.FormString("k8s_url")
|
||||
token := ctx.FormString("k8s_token")
|
||||
|
||||
if appID == "" {
|
||||
ctx.JSON(400, map[string]string{"error": "应用ID不能为空"})
|
||||
@@ -273,7 +273,7 @@ func AppStoreInstall(ctx *context.Context) {
|
||||
|
||||
// 创建 manager 并执行安装
|
||||
manager := appstore.NewManager(ctx, ctx.Doer.ID)
|
||||
if err := manager.InstallApp(appID, configJSON, installTarget, kubeconfig, kubeconfigContext); err != nil {
|
||||
if err := manager.InstallApp(appID, configJSON, installTarget, k8sURL, token); err != nil {
|
||||
// 根据错误类型返回相应的状态码和消息
|
||||
if appErr, ok := err.(*appstore.AppStoreError); ok {
|
||||
switch appErr.Code {
|
||||
@@ -293,7 +293,7 @@ func AppStoreInstall(ctx *context.Context) {
|
||||
}
|
||||
|
||||
// 安装成功
|
||||
if installTarget == "kubeconfig" && kubeconfig != "" {
|
||||
if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
|
||||
ctx.Flash.Success("应用已成功安装到自定义位置")
|
||||
} else {
|
||||
ctx.Flash.Success("应用已成功安装到默认位置")
|
||||
@@ -324,8 +324,8 @@ func AppStoreUpdate(ctx *context.Context) {
|
||||
appID := ctx.FormString("app_id")
|
||||
configJSON := ctx.FormString("config")
|
||||
installTarget := ctx.FormString("install_target")
|
||||
kubeconfig := ctx.FormString("kubeconfig")
|
||||
kubeconfigContext := ctx.FormString("kubeconfig_context")
|
||||
k8sURL := ctx.FormString("k8s_url")
|
||||
token := ctx.FormString("k8s_token")
|
||||
|
||||
if appID == "" {
|
||||
ctx.JSON(400, map[string]string{"error": "应用ID不能为空"})
|
||||
@@ -340,7 +340,7 @@ func AppStoreUpdate(ctx *context.Context) {
|
||||
|
||||
// 创建 manager 并执行更新
|
||||
manager := appstore.NewManager(ctx, ctx.Doer.ID)
|
||||
if err := manager.UpdateInstalledApp(appID, configJSON, installTarget, kubeconfig, kubeconfigContext); err != nil {
|
||||
if err := manager.UpdateInstalledApp(appID, configJSON, installTarget, k8sURL, token); err != nil {
|
||||
// 根据错误类型返回相应的状态码和消息
|
||||
if appErr, ok := err.(*appstore.AppStoreError); ok {
|
||||
switch appErr.Code {
|
||||
@@ -360,7 +360,7 @@ func AppStoreUpdate(ctx *context.Context) {
|
||||
}
|
||||
|
||||
// 更新成功
|
||||
if installTarget == "kubeconfig" && kubeconfig != "" {
|
||||
if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
|
||||
ctx.Flash.Success("应用已成功更新到自定义位置")
|
||||
} else {
|
||||
ctx.Flash.Success("应用已成功更新到默认位置")
|
||||
@@ -396,7 +396,7 @@ func AppStoreUninstall(ctx *context.Context) {
|
||||
}
|
||||
|
||||
// 创建 manager 并执行卸载
|
||||
// UninstallApp 会自动从数据库读取 kubeconfig 判断是外部集群还是本地集群
|
||||
// UninstallApp 会自动从数据库读取保存的 Kubernetes 凭据判断是外部集群还是本地集群
|
||||
manager := appstore.NewManager(ctx, ctx.Doer.ID)
|
||||
if err := manager.UninstallApp(appID); err != nil {
|
||||
// 根据错误类型返回相应的状态码和消息
|
||||
@@ -445,8 +445,8 @@ func AppStoreResume(ctx *context.Context) {
|
||||
// 解析表单数据
|
||||
appID := ctx.FormString("app_id")
|
||||
installTarget := ctx.FormString("install_target")
|
||||
kubeconfig := ctx.FormString("kubeconfig")
|
||||
kubeconfigContext := ctx.FormString("kubeconfig_context")
|
||||
k8sURL := ctx.FormString("k8s_url")
|
||||
token := ctx.FormString("k8s_token")
|
||||
|
||||
if appID == "" {
|
||||
ctx.JSON(400, map[string]string{"error": "应用ID不能为空"})
|
||||
@@ -455,7 +455,7 @@ func AppStoreResume(ctx *context.Context) {
|
||||
|
||||
// 创建 manager 并执行恢复
|
||||
manager := appstore.NewManager(ctx, ctx.Doer.ID)
|
||||
if err := manager.ResumeApp(appID, installTarget, []byte(kubeconfig), kubeconfigContext); err != nil {
|
||||
if err := manager.ResumeApp(appID, installTarget, k8sURL, token); err != nil {
|
||||
// 根据错误类型返回相应的状态码和消息
|
||||
if appErr, ok := err.(*appstore.AppStoreError); ok {
|
||||
switch appErr.Code {
|
||||
@@ -475,7 +475,7 @@ func AppStoreResume(ctx *context.Context) {
|
||||
}
|
||||
|
||||
// 恢复成功
|
||||
if installTarget == "kubeconfig" && kubeconfig != "" {
|
||||
if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
|
||||
ctx.Flash.Success("应用已成功在指定位置恢复")
|
||||
} else {
|
||||
ctx.Flash.Success("应用已成功恢复")
|
||||
@@ -506,8 +506,8 @@ func AppStoreStop(ctx *context.Context) {
|
||||
// 解析表单数据
|
||||
appID := ctx.FormString("app_id")
|
||||
installTarget := ctx.FormString("install_target")
|
||||
kubeconfig := ctx.FormString("kubeconfig")
|
||||
kubeconfigContext := ctx.FormString("kubeconfig_context")
|
||||
k8sURL := ctx.FormString("k8s_url")
|
||||
token := ctx.FormString("k8s_token")
|
||||
|
||||
if appID == "" {
|
||||
ctx.JSON(400, map[string]string{"error": "应用ID不能为空"})
|
||||
@@ -516,7 +516,7 @@ func AppStoreStop(ctx *context.Context) {
|
||||
|
||||
// 创建 manager 并执行暂停
|
||||
manager := appstore.NewManager(ctx, ctx.Doer.ID)
|
||||
if err := manager.StopApp(appID, installTarget, []byte(kubeconfig), kubeconfigContext); err != nil {
|
||||
if err := manager.StopApp(appID, installTarget, k8sURL, token); err != nil {
|
||||
// 根据错误类型返回相应的状态码和消息
|
||||
if appErr, ok := err.(*appstore.AppStoreError); ok {
|
||||
switch appErr.Code {
|
||||
@@ -536,7 +536,7 @@ func AppStoreStop(ctx *context.Context) {
|
||||
}
|
||||
|
||||
// 暂停成功
|
||||
if installTarget == "kubeconfig" && kubeconfig != "" {
|
||||
if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
|
||||
ctx.Flash.Success("应用已成功在指定位置暂停")
|
||||
} else {
|
||||
ctx.Flash.Success("应用已成功暂停")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "mengningsoftware-2",
|
||||
"name": "mengningsoftware-2",
|
||||
"id": "mengningsoftware",
|
||||
"name": "mengningsoftware",
|
||||
"description": "High-performance HTTP server and reverse proxy",
|
||||
"category": "web-server",
|
||||
"tags": ["web", "proxy", "http", "server"],
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/k8s/application"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
dynamicclient "k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
// K8sManager handles Kubernetes-specific application operations
|
||||
@@ -23,6 +24,12 @@ type K8sManager struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// K8sCredential describes how to reach a Kubernetes cluster.
|
||||
type K8sCredential struct {
|
||||
K8sURL string
|
||||
Token string
|
||||
}
|
||||
|
||||
// NewK8sManager creates a new Kubernetes manager
|
||||
func NewK8sManager(ctx context.Context) *K8sManager {
|
||||
return &K8sManager{
|
||||
@@ -30,8 +37,16 @@ func NewK8sManager(ctx context.Context) *K8sManager {
|
||||
}
|
||||
}
|
||||
|
||||
// buildK8sClient creates a dynamic client from the provided credential.
|
||||
func (km *K8sManager) buildK8sClient(cred *K8sCredential) (dynamicclient.Interface, error) {
|
||||
if cred != nil && cred.K8sURL != "" && cred.Token != "" {
|
||||
return k8s.GetKubernetesClientWithToken(km.ctx, cred.K8sURL, cred.Token)
|
||||
}
|
||||
return k8s.GetKubernetesClientWithToken(km.ctx, "", "")
|
||||
}
|
||||
|
||||
// InstallAppToKubernetes installs an application to a Kubernetes cluster
|
||||
func (km *K8sManager) InstallAppToKubernetes(app *App, kubeconfig []byte, contextName string) error {
|
||||
func (km *K8sManager) InstallAppToKubernetes(app *App, cred *K8sCredential) error {
|
||||
// Validate that the app supports Kubernetes deployment
|
||||
// 优先检查实际部署类型,如果为空则检查支持的部署类型
|
||||
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
|
||||
@@ -48,7 +63,7 @@ func (km *K8sManager) InstallAppToKubernetes(app *App, kubeconfig []byte, contex
|
||||
}
|
||||
|
||||
// Get Kubernetes client
|
||||
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName)
|
||||
k8sClient, err := km.buildK8sClient(cred)
|
||||
if err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_CLIENT_ERROR",
|
||||
@@ -81,7 +96,7 @@ func (km *K8sManager) InstallAppToKubernetes(app *App, kubeconfig []byte, contex
|
||||
}
|
||||
|
||||
// UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster
|
||||
func (km *K8sManager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, contextName string) error {
|
||||
func (km *K8sManager) UninstallAppFromKubernetes(app *App, cred *K8sCredential) error {
|
||||
// Validate that the app supports Kubernetes deployment
|
||||
// 优先检查实际部署类型,如果为空则检查支持的部署类型
|
||||
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
|
||||
@@ -98,7 +113,7 @@ func (km *K8sManager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, co
|
||||
}
|
||||
|
||||
// Get Kubernetes client
|
||||
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName)
|
||||
k8sClient, err := km.buildK8sClient(cred)
|
||||
if err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_CLIENT_ERROR",
|
||||
@@ -131,7 +146,7 @@ func (km *K8sManager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, co
|
||||
}
|
||||
|
||||
// GetAppFromKubernetes gets an application from a Kubernetes cluster
|
||||
func (km *K8sManager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextName string) (interface{}, error) {
|
||||
func (km *K8sManager) GetAppFromKubernetes(app *App, cred *K8sCredential) (interface{}, error) {
|
||||
// Validate that the app supports Kubernetes deployment
|
||||
// 优先检查实际部署类型,如果为空则检查支持的部署类型
|
||||
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
|
||||
@@ -148,7 +163,7 @@ func (km *K8sManager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextN
|
||||
}
|
||||
|
||||
// Get Kubernetes client
|
||||
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName)
|
||||
k8sClient, err := km.buildK8sClient(cred)
|
||||
if err != nil {
|
||||
return nil, &AppStoreError{
|
||||
Code: "KUBERNETES_CLIENT_ERROR",
|
||||
@@ -172,7 +187,7 @@ func (km *K8sManager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextN
|
||||
}
|
||||
|
||||
// ListAppsFromKubernetes lists applications from a Kubernetes cluster
|
||||
func (km *K8sManager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contextName string) (*applicationv1.ApplicationList, error) {
|
||||
func (km *K8sManager) ListAppsFromKubernetes(app *App, cred *K8sCredential) (*applicationv1.ApplicationList, error) {
|
||||
// Validate that the app supports Kubernetes deployment
|
||||
// 优先检查实际部署类型,如果为空则检查支持的部署类型
|
||||
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
|
||||
@@ -189,7 +204,7 @@ func (km *K8sManager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contex
|
||||
}
|
||||
|
||||
// Get Kubernetes client
|
||||
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName)
|
||||
k8sClient, err := km.buildK8sClient(cred)
|
||||
if err != nil {
|
||||
return nil, &AppStoreError{
|
||||
Code: "KUBERNETES_CLIENT_ERROR",
|
||||
@@ -213,7 +228,7 @@ func (km *K8sManager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contex
|
||||
}
|
||||
|
||||
// UpdateAppInKubernetes updates an application in a Kubernetes cluster
|
||||
func (km *K8sManager) UpdateAppInKubernetes(app *App, kubeconfig []byte, contextName string) error {
|
||||
func (km *K8sManager) UpdateAppInKubernetes(app *App, cred *K8sCredential) error {
|
||||
// Validate that the app supports Kubernetes deployment
|
||||
// 优先检查实际部署类型,如果为空则检查支持的部署类型
|
||||
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
|
||||
@@ -230,7 +245,7 @@ func (km *K8sManager) UpdateAppInKubernetes(app *App, kubeconfig []byte, context
|
||||
}
|
||||
|
||||
// Get Kubernetes client
|
||||
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName)
|
||||
k8sClient, err := km.buildK8sClient(cred)
|
||||
if err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_CLIENT_ERROR",
|
||||
@@ -240,7 +255,7 @@ func (km *K8sManager) UpdateAppInKubernetes(app *App, kubeconfig []byte, context
|
||||
}
|
||||
|
||||
// 先获取现有的应用,以保留元数据(特别是 resourceVersion)
|
||||
existingApp, err := km.GetAppFromKubernetes(app, kubeconfig, contextName)
|
||||
existingApp, err := km.GetAppFromKubernetes(app, cred)
|
||||
if err != nil {
|
||||
// 如果应用不存在,尝试创建新应用
|
||||
log.Warn("Application not found in Kubernetes, attempting to create new application: %v", err)
|
||||
|
||||
@@ -40,6 +40,29 @@ func NewManager(ctx *gitea_context.Context, userID int64) *Manager {
|
||||
}
|
||||
}
|
||||
|
||||
func buildCredentialFromInput(installTarget, k8sURL, token string) *K8sCredential {
|
||||
if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
|
||||
return &K8sCredential{
|
||||
K8sURL: k8sURL,
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func credentialFromInstance(instance *user_app_instance.UserAppInstance) *K8sCredential {
|
||||
if instance == nil {
|
||||
return nil
|
||||
}
|
||||
if instance.K8sURL != "" && instance.K8sToken != "" {
|
||||
return &K8sCredential{
|
||||
K8sURL: instance.K8sURL,
|
||||
Token: instance.K8sToken,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListApps returns all available applications from database
|
||||
func (m *Manager) ListApps() ([]App, error) {
|
||||
appStores, err := appstore_model.ListAppStores(m.ctx, nil)
|
||||
@@ -220,7 +243,7 @@ func (m *Manager) SearchApps(query string, category string, tags []string) ([]Ap
|
||||
tagMatch := false
|
||||
for _, searchTag := range tags {
|
||||
for _, appTag := range app.Tags {
|
||||
if strings.ToLower(appTag) == strings.ToLower(searchTag) {
|
||||
if strings.EqualFold(appTag, searchTag) {
|
||||
tagMatch = true
|
||||
break
|
||||
}
|
||||
@@ -439,7 +462,7 @@ func (m *Manager) ValidateUserConfig(appID string, userConfig UserConfig) error
|
||||
}
|
||||
|
||||
// InstallApp installs an application based on the provided parameters
|
||||
func (m *Manager) InstallApp(appID string, configJSON string, installTarget string, kubeconfig string, kubeconfigContext string) error {
|
||||
func (m *Manager) InstallApp(appID string, configJSON string, installTarget string, k8sURL string, token string) error {
|
||||
// 获取应用信息
|
||||
app, err := m.GetApp(appID)
|
||||
if err != nil {
|
||||
@@ -465,52 +488,53 @@ func (m *Manager) InstallApp(appID string, configJSON string, installTarget stri
|
||||
deployType := mergedApp.Deploy.Type
|
||||
if deployType == "" {
|
||||
// 如果 Deploy.Type 为空,根据 DeploymentType 推断
|
||||
if mergedApp.DeploymentType == "kubernetes" || mergedApp.DeploymentType == "both" {
|
||||
switch mergedApp.DeploymentType {
|
||||
case "kubernetes", "both":
|
||||
deployType = "kubernetes"
|
||||
} else if mergedApp.DeploymentType == "docker" {
|
||||
case "docker":
|
||||
deployType = "docker"
|
||||
}
|
||||
}
|
||||
|
||||
// 根据安装目标和应用实际部署类型决定安装方式
|
||||
if installTarget == "kubeconfig" && kubeconfig != "" {
|
||||
// 安装到外部 Kubernetes 集群
|
||||
if err := m.InstallAppToKubernetes(mergedApp, []byte(kubeconfig), kubeconfigContext); err != nil {
|
||||
inputCred := buildCredentialFromInput(installTarget, k8sURL, token)
|
||||
if installTarget == "kubeconfig" {
|
||||
if inputCred == nil {
|
||||
return &AppStoreError{
|
||||
Code: "INVALID_K8S_CREDENTIAL",
|
||||
Message: "自定义 Kubernetes 安装需要提供 API 地址和 Token",
|
||||
}
|
||||
}
|
||||
if deployType != "kubernetes" {
|
||||
return &AppStoreError{
|
||||
Code: "DEPLOYMENT_TYPE_ERROR",
|
||||
Message: "该应用不支持 Kubernetes 部署",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据应用实际部署类型决定安装方式
|
||||
log.Info("InstallApp: mergedApp.Deploy.Type = %s", mergedApp.Deploy.Type)
|
||||
switch deployType {
|
||||
case "kubernetes":
|
||||
if err := m.InstallAppToKubernetes(mergedApp, inputCred); err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_INSTALL_ERROR",
|
||||
Message: "Kubernetes 安装失败",
|
||||
Details: err.Error(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 根据应用实际部署类型决定本地安装方式
|
||||
log.Info("InstallApp: mergedApp.Deploy.Type = %s", mergedApp.Deploy.Type)
|
||||
switch deployType {
|
||||
case "kubernetes":
|
||||
// 应用要部署到 Kubernetes,安装到本地 K8s 集群
|
||||
if err := m.InstallAppToKubernetes(mergedApp, nil, ""); err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_INSTALL_ERROR",
|
||||
Message: "本地 Kubernetes 安装失败",
|
||||
Details: err.Error(),
|
||||
}
|
||||
}
|
||||
case "docker":
|
||||
// 应用要部署到 Docker,安装到本地 Docker
|
||||
// TODO: 实现 Docker 安装逻辑
|
||||
return &AppStoreError{
|
||||
Code: "NOT_IMPLEMENTED",
|
||||
Message: "本地 Docker 安装功能开发中",
|
||||
}
|
||||
default:
|
||||
// 未知部署类型,默认尝试 Docker
|
||||
// TODO: 实现 Docker 安装逻辑
|
||||
return &AppStoreError{
|
||||
Code: "NOT_IMPLEMENTED",
|
||||
Message: "本地安装功能开发中",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 安装成功后,保存用户应用实例到数据库
|
||||
mergedAppJSON, err := json.Marshal(mergedApp)
|
||||
@@ -526,12 +550,14 @@ func (m *Manager) InstallApp(appID string, configJSON string, installTarget stri
|
||||
instance := &user_app_instance.UserAppInstance{
|
||||
UserID: m.userID,
|
||||
AppID: appID,
|
||||
InstanceName: mergedApp.Name, // 使用应用名称作为实例名称
|
||||
InstanceName: mergedApp.Name,
|
||||
UserConfig: configJSON,
|
||||
MergedApp: string(mergedAppJSON),
|
||||
DeployType: deployType,
|
||||
Kubeconfig: kubeconfig,
|
||||
KubeconfigContext: kubeconfigContext,
|
||||
}
|
||||
if inputCred != nil && deployType == "kubernetes" {
|
||||
instance.K8sURL = inputCred.K8sURL
|
||||
instance.K8sToken = inputCred.Token
|
||||
}
|
||||
|
||||
if err := user_app_instance.CreateUserAppInstance(m.ctx, instance); err != nil {
|
||||
@@ -549,7 +575,7 @@ func (m *Manager) InstallApp(appID string, configJSON string, installTarget stri
|
||||
|
||||
// UpdateInstalledApp updates an already installed application with new configuration
|
||||
// The update flow mirrors InstallApp: merge config → choose target → call K8s/Docker updater
|
||||
func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTarget string, kubeconfig string, kubeconfigContext string) error {
|
||||
func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTarget string, k8sURL string, token string) error {
|
||||
// 获取用户的应用实例
|
||||
instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID)
|
||||
if err != nil {
|
||||
@@ -589,31 +615,38 @@ func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTar
|
||||
// 确定部署类型
|
||||
deployType := mergedApp.Deploy.Type
|
||||
if deployType == "" {
|
||||
deployType = instance.DeployType // 使用实例中保存的部署类型
|
||||
deployType = instance.DeployType
|
||||
}
|
||||
|
||||
// 根据安装目标和应用实际部署类型决定更新方式
|
||||
if installTarget == "kubeconfig" && kubeconfig != "" {
|
||||
// 更新外部 Kubernetes 集群中的应用实例
|
||||
if deployType == "kubernetes" {
|
||||
if err := m.UpdateAppInKubernetes(mergedApp, []byte(kubeconfig), kubeconfigContext); err != nil {
|
||||
inputCred := buildCredentialFromInput(installTarget, k8sURL, token)
|
||||
if installTarget == "kubeconfig" && inputCred == nil {
|
||||
return &AppStoreError{
|
||||
Code: "INVALID_K8S_CREDENTIAL",
|
||||
Message: "自定义 Kubernetes 更新需要提供 API 地址和 Token",
|
||||
}
|
||||
}
|
||||
if installTarget == "kubeconfig" && deployType != "kubernetes" {
|
||||
return &AppStoreError{
|
||||
Code: "DEPLOYMENT_TYPE_ERROR",
|
||||
Message: "该应用不支持 Kubernetes 部署",
|
||||
}
|
||||
}
|
||||
|
||||
// 根据部署类型执行更新
|
||||
switch deployType {
|
||||
case "kubernetes":
|
||||
targetCred := inputCred
|
||||
if targetCred == nil {
|
||||
targetCred = credentialFromInstance(instance)
|
||||
}
|
||||
if err := m.UpdateAppInKubernetes(mergedApp, targetCred); err != nil {
|
||||
return &AppStoreError{Code: "KUBERNETES_UPDATE_ERROR", Message: "Kubernetes 更新失败", Details: err.Error()}
|
||||
}
|
||||
} else {
|
||||
return &AppStoreError{Code: "NOT_IMPLEMENTED", Message: "外部环境 Docker 更新功能开发中"}
|
||||
}
|
||||
} else {
|
||||
// 本地更新(依据应用部署类型)
|
||||
if deployType == "kubernetes" {
|
||||
if err := m.UpdateAppInKubernetes(mergedApp, nil, ""); err != nil {
|
||||
return &AppStoreError{Code: "KUBERNETES_UPDATE_ERROR", Message: "本地 Kubernetes 更新失败", Details: err.Error()}
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
return &AppStoreError{Code: "NOT_IMPLEMENTED", Message: "本地 Docker 更新功能开发中"}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新成功后,更新用户应用实例
|
||||
// 更新成功后,写回数据库
|
||||
mergedAppJSON, err := json.Marshal(mergedApp)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal merged app: %v", err)
|
||||
@@ -627,8 +660,18 @@ func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTar
|
||||
instance.UserConfig = configJSON
|
||||
instance.MergedApp = string(mergedAppJSON)
|
||||
instance.DeployType = deployType
|
||||
instance.Kubeconfig = kubeconfig
|
||||
instance.KubeconfigContext = kubeconfigContext
|
||||
if deployType == "kubernetes" {
|
||||
if inputCred != nil {
|
||||
instance.K8sURL = inputCred.K8sURL
|
||||
instance.K8sToken = inputCred.Token
|
||||
} else if installTarget != "kubeconfig" {
|
||||
instance.K8sURL = ""
|
||||
instance.K8sToken = ""
|
||||
}
|
||||
} else {
|
||||
instance.K8sURL = ""
|
||||
instance.K8sToken = ""
|
||||
}
|
||||
|
||||
if err := user_app_instance.UpdateUserAppInstance(m.ctx, instance); err != nil {
|
||||
log.Error("Failed to update user app instance: %v", err)
|
||||
@@ -645,7 +688,7 @@ func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTar
|
||||
|
||||
// UninstallApp uninstalls an application
|
||||
// It automatically determines whether to uninstall from external cluster or local cluster
|
||||
// based on the kubeconfig stored in the database instance
|
||||
// based on the Kubernetes credential stored in the database instance
|
||||
func (m *Manager) UninstallApp(appID string) error {
|
||||
// 获取用户的应用实例
|
||||
instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID)
|
||||
@@ -673,45 +716,28 @@ func (m *Manager) UninstallApp(appID string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 根据数据库实例中保存的 kubeconfig 判断卸载方式
|
||||
// 如果数据库中有 kubeconfig,说明是安装在外部集群的,使用外部集群卸载
|
||||
// 如果数据库中没有 kubeconfig,说明是安装在本地集群的,使用本地卸载
|
||||
if instance.Kubeconfig != "" {
|
||||
// 从外部 Kubernetes 集群卸载
|
||||
// 根据数据库中保存的凭据判断卸载方式:存在 URL+Token 则视为外部集群,否则使用默认集群
|
||||
cred := credentialFromInstance(instance)
|
||||
if instance.DeployType == "kubernetes" {
|
||||
log.Info("UninstallApp: Uninstalling from external cluster, appID=%s, kubeconfig length=%d", appID, len(instance.Kubeconfig))
|
||||
if err := m.UninstallAppFromKubernetes(&app, []byte(instance.Kubeconfig), instance.KubeconfigContext); err != nil {
|
||||
if cred != nil {
|
||||
log.Info("UninstallApp: Uninstalling from external cluster, appID=%s, url=%s", appID, cred.K8sURL)
|
||||
} else {
|
||||
log.Info("UninstallApp: Uninstalling from default cluster, appID=%s", appID)
|
||||
}
|
||||
|
||||
if err := m.UninstallAppFromKubernetes(&app, cred); err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_UNINSTALL_ERROR",
|
||||
Message: "Kubernetes 卸载失败",
|
||||
Details: err.Error(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return &AppStoreError{
|
||||
Code: "NOT_IMPLEMENTED",
|
||||
Message: "外部环境 Docker 卸载功能开发中",
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 本地卸载(依据实例中保存的部署类型)
|
||||
if instance.DeployType == "kubernetes" {
|
||||
log.Info("UninstallApp: Uninstalling from local cluster, appID=%s", appID)
|
||||
if err := m.UninstallAppFromKubernetes(&app, nil, ""); err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_UNINSTALL_ERROR",
|
||||
Message: "本地 Kubernetes 卸载失败",
|
||||
Details: err.Error(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return &AppStoreError{
|
||||
Code: "NOT_IMPLEMENTED",
|
||||
Message: "本地 Docker 卸载功能开发中",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 卸载成功后,删除用户应用实例
|
||||
if err := user_app_instance.DeleteUserAppInstanceByAppID(m.ctx, m.userID, appID); err != nil {
|
||||
log.Error("Failed to delete user app instance: %v", err)
|
||||
@@ -725,28 +751,28 @@ func (m *Manager) UninstallApp(appID string) error {
|
||||
}
|
||||
|
||||
// InstallAppToKubernetes installs an application to a Kubernetes cluster
|
||||
func (m *Manager) InstallAppToKubernetes(app *App, kubeconfig []byte, contextName string) error {
|
||||
return m.k8s.InstallAppToKubernetes(app, kubeconfig, contextName)
|
||||
func (m *Manager) InstallAppToKubernetes(app *App, cred *K8sCredential) error {
|
||||
return m.k8s.InstallAppToKubernetes(app, cred)
|
||||
}
|
||||
|
||||
// UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster
|
||||
func (m *Manager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, contextName string) error {
|
||||
return m.k8s.UninstallAppFromKubernetes(app, kubeconfig, contextName)
|
||||
func (m *Manager) UninstallAppFromKubernetes(app *App, cred *K8sCredential) error {
|
||||
return m.k8s.UninstallAppFromKubernetes(app, cred)
|
||||
}
|
||||
|
||||
// GetAppFromKubernetes gets an application from a Kubernetes cluster
|
||||
func (m *Manager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextName string) (interface{}, error) {
|
||||
return m.k8s.GetAppFromKubernetes(app, kubeconfig, contextName)
|
||||
func (m *Manager) GetAppFromKubernetes(app *App, cred *K8sCredential) (interface{}, error) {
|
||||
return m.k8s.GetAppFromKubernetes(app, cred)
|
||||
}
|
||||
|
||||
// ListAppsFromKubernetes lists applications from a Kubernetes cluster
|
||||
func (m *Manager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contextName string) (*applicationv1.ApplicationList, error) {
|
||||
return m.k8s.ListAppsFromKubernetes(app, kubeconfig, contextName)
|
||||
func (m *Manager) ListAppsFromKubernetes(app *App, cred *K8sCredential) (*applicationv1.ApplicationList, error) {
|
||||
return m.k8s.ListAppsFromKubernetes(app, cred)
|
||||
}
|
||||
|
||||
// UpdateAppInKubernetes updates an application in a Kubernetes cluster
|
||||
func (m *Manager) UpdateAppInKubernetes(app *App, kubeconfig []byte, contextName string) error {
|
||||
return m.k8s.UpdateAppInKubernetes(app, kubeconfig, contextName)
|
||||
func (m *Manager) UpdateAppInKubernetes(app *App, cred *K8sCredential) error {
|
||||
return m.k8s.UpdateAppInKubernetes(app, cred)
|
||||
}
|
||||
|
||||
// IsInstalledAppReady 提供给上层的就绪判断入口,避免直接依赖 k8s 层实现
|
||||
@@ -788,14 +814,9 @@ func (m *Manager) GetAppStatus(appID string) (map[string]interface{}, error) {
|
||||
// 检查应用是否支持 Kubernetes 部署
|
||||
if instance.DeployType == "kubernetes" {
|
||||
// 尝试从 Kubernetes 获取状态
|
||||
var kubeconfig []byte
|
||||
var contextName string
|
||||
if instance.Kubeconfig != "" {
|
||||
kubeconfig = []byte(instance.Kubeconfig)
|
||||
contextName = instance.KubeconfigContext
|
||||
}
|
||||
cred := credentialFromInstance(instance)
|
||||
|
||||
k8sApp, err := m.GetAppFromKubernetes(&app, kubeconfig, contextName)
|
||||
k8sApp, err := m.GetAppFromKubernetes(&app, cred)
|
||||
if err != nil {
|
||||
// 如果应用实例存在于数据库中,但 Kubernetes 中找不到,可能是被暂停了
|
||||
// 返回暂停状态而不是未安装状态
|
||||
@@ -845,7 +866,7 @@ func (m *Manager) GetAppStatus(appID string) (map[string]interface{}, error) {
|
||||
}
|
||||
|
||||
// StopApp stops an application by setting replicas to 0
|
||||
func (m *Manager) StopApp(appID string, installTarget string, kubeconfig []byte, kubeconfigContext string) error {
|
||||
func (m *Manager) StopApp(appID string, installTarget string, k8sURL string, token string) error {
|
||||
// 获取用户的应用实例
|
||||
instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID)
|
||||
if err != nil {
|
||||
@@ -880,8 +901,12 @@ func (m *Manager) StopApp(appID string, installTarget string, kubeconfig []byte,
|
||||
stoppedApp.Deploy.Kubernetes.Replicas = 0
|
||||
}
|
||||
|
||||
// 调用 k8s 层的 UpdateAppInKubernetes 函数来停止应用
|
||||
if err := m.k8s.UpdateAppInKubernetes(&stoppedApp, kubeconfig, kubeconfigContext); err != nil {
|
||||
cred := buildCredentialFromInput(installTarget, k8sURL, token)
|
||||
if cred == nil {
|
||||
cred = credentialFromInstance(instance)
|
||||
}
|
||||
|
||||
if err := m.UpdateAppInKubernetes(&stoppedApp, cred); err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_STOP_ERROR",
|
||||
Message: "停止应用失败",
|
||||
@@ -899,7 +924,7 @@ func (m *Manager) StopApp(appID string, installTarget string, kubeconfig []byte,
|
||||
}
|
||||
|
||||
// ResumeApp resumes an application by restoring its original replica count
|
||||
func (m *Manager) ResumeApp(appID string, installTarget string, kubeconfig []byte, kubeconfigContext string) error {
|
||||
func (m *Manager) ResumeApp(appID string, installTarget string, k8sURL string, token string) error {
|
||||
// 获取用户的应用实例
|
||||
instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID)
|
||||
if err != nil {
|
||||
@@ -937,8 +962,12 @@ func (m *Manager) ResumeApp(appID string, installTarget string, kubeconfig []byt
|
||||
}
|
||||
}
|
||||
|
||||
// 调用 k8s 层的 UpdateAppInKubernetes 函数来恢复应用
|
||||
if err := m.k8s.UpdateAppInKubernetes(&resumedApp, kubeconfig, kubeconfigContext); err != nil {
|
||||
cred := buildCredentialFromInput(installTarget, k8sURL, token)
|
||||
if cred == nil {
|
||||
cred = credentialFromInstance(instance)
|
||||
}
|
||||
|
||||
if err := m.UpdateAppInKubernetes(&resumedApp, cred); err != nil {
|
||||
return &AppStoreError{
|
||||
Code: "KUBERNETES_RESUME_ERROR",
|
||||
Message: "恢复应用失败",
|
||||
|
||||
@@ -274,18 +274,18 @@
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" name="installTargetRadio" value="kubeconfig" {{if not .IsAdmin}}checked{{end}}>
|
||||
<label>自定义位置(Kubeconfig)</label>
|
||||
<label>自定义位置(Kubernetes URL + Token)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="kubeconfig-fields" style="display:none;">
|
||||
<div id="k8s-credential-fields" style="display:none;">
|
||||
<div class="field">
|
||||
<label>Kubeconfig(粘贴内容)</label>
|
||||
<textarea id="kubeconfig-content" rows="8" placeholder="粘贴 kubeconfig 内容"></textarea>
|
||||
<label>Kubernetes API 地址</label>
|
||||
<input type="text" id="k8s-url" placeholder="例如:https://your-cluster:6443">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Context 名称(可选)</label>
|
||||
<input type="text" id="kubeconfig-context" placeholder="不填则使用 current-context">
|
||||
<label>Bearer Token</label>
|
||||
<textarea id="k8s-token" rows="4" placeholder="粘贴访问该集群的 Bearer Token"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -377,10 +377,10 @@ let filteredApps = [];
|
||||
let currentApp = null;
|
||||
let currentAppStatus = null; // 存储当前应用的运行状态
|
||||
let storeSource = 'local'; // local | devstar
|
||||
// 安装位置:local | kubeconfig
|
||||
// 安装位置:local | remote
|
||||
let installTarget = 'local';
|
||||
let installKubeconfigContent = '';
|
||||
let installKubeconfigContext = '';
|
||||
let installK8sURL = '';
|
||||
let installK8sToken = '';
|
||||
|
||||
// Initialize the page
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
@@ -420,7 +420,7 @@ function setupInstallTargetUI() {
|
||||
const radioButtons = document.querySelectorAll('#install-modal input[name="installTargetRadio"]');
|
||||
radioButtons.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
const kubeconfigFields = document.querySelector('#install-modal #kubeconfig-fields');
|
||||
const kubeconfigFields = document.querySelector('#install-modal #k8s-credential-fields');
|
||||
if (kubeconfigFields) {
|
||||
kubeconfigFields.style.display = (this.value === 'kubeconfig') ? 'block' : 'none';
|
||||
}
|
||||
@@ -428,17 +428,17 @@ function setupInstallTargetUI() {
|
||||
installTarget = this.value;
|
||||
});
|
||||
});
|
||||
// kubeconfig 内容变化时,同步到全局变量
|
||||
const kcContentEl = document.querySelector('#install-modal #kubeconfig-content');
|
||||
// Kubernetes URL/Token 变化时,同步到全局变量
|
||||
const kcContentEl = document.querySelector('#install-modal #k8s-url');
|
||||
if (kcContentEl) {
|
||||
kcContentEl.addEventListener('input', function() {
|
||||
installKubeconfigContent = this.value.trim();
|
||||
installK8sURL = this.value.trim();
|
||||
});
|
||||
}
|
||||
const kcContextEl = document.querySelector('#install-modal #kubeconfig-context');
|
||||
const kcContextEl = document.querySelector('#install-modal #k8s-token');
|
||||
if (kcContextEl) {
|
||||
kcContextEl.addEventListener('input', function() {
|
||||
installKubeconfigContext = this.value.trim();
|
||||
installK8sToken = this.value.trim();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -813,14 +813,14 @@ function showInstallModal(appId) {
|
||||
// 追加安装位置区块已保留在模板中,这里只同步状态
|
||||
const radios = document.querySelectorAll('#install-modal input[name="installTargetRadio"]');
|
||||
radios.forEach(r => { r.checked = (r.value === installTarget); });
|
||||
const kubeconfigFields = document.querySelector('#install-modal #kubeconfig-fields');
|
||||
const kubeconfigFields = document.querySelector('#install-modal #k8s-credential-fields');
|
||||
if (kubeconfigFields) {
|
||||
kubeconfigFields.style.display = (installTarget === 'kubeconfig') ? 'block' : 'none';
|
||||
}
|
||||
const kcContent = document.querySelector('#install-modal #kubeconfig-content');
|
||||
const kcContext = document.querySelector('#install-modal #kubeconfig-context');
|
||||
if (kcContent) kcContent.value = installKubeconfigContent || '';
|
||||
if (kcContext) kcContext.value = installKubeconfigContext || '';
|
||||
const kcContent = document.querySelector('#install-modal #k8s-url');
|
||||
const kcContext = document.querySelector('#install-modal #k8s-token');
|
||||
if (kcContent) kcContent.value = installK8sURL || '';
|
||||
if (kcContext) kcContext.value = installK8sToken || '';
|
||||
|
||||
const modal = document.getElementById('install-modal');
|
||||
if (modal) {
|
||||
@@ -932,14 +932,14 @@ function showUpdateModal(appId) {
|
||||
// 设置安装位置表单
|
||||
const radios = document.querySelectorAll('#install-modal input[name="installTargetRadio"]');
|
||||
radios.forEach(r => { r.checked = (r.value === installTarget); });
|
||||
const kubeconfigFields = document.querySelector('#install-modal #kubeconfig-fields');
|
||||
const kubeconfigFields = document.querySelector('#install-modal #k8s-credential-fields');
|
||||
if (kubeconfigFields) {
|
||||
kubeconfigFields.style.display = (installTarget === 'kubeconfig') ? 'block' : 'none';
|
||||
}
|
||||
const kcContent = document.querySelector('#install-modal #kubeconfig-content');
|
||||
const kcContext = document.querySelector('#install-modal #kubeconfig-context');
|
||||
if (kcContent) kcContent.value = installKubeconfigContent || '';
|
||||
if (kcContext) kcContext.value = installKubeconfigContext || '';
|
||||
const kcContent = document.querySelector('#install-modal #k8s-url');
|
||||
const kcContext = document.querySelector('#install-modal #k8s-token');
|
||||
if (kcContent) kcContent.value = installK8sURL || '';
|
||||
if (kcContext) kcContext.value = installK8sToken || '';
|
||||
|
||||
// 修改安装按钮文字为"更新"
|
||||
const installBtn = document.querySelector('#install-modal .ui.primary.button[onclick="installApp()"]');
|
||||
@@ -978,12 +978,12 @@ function updateApp() {
|
||||
const selectedTarget = document.querySelector('#install-modal input[name="installTargetRadio"]:checked');
|
||||
installTarget = selectedTarget ? selectedTarget.value : 'local';
|
||||
if (installTarget === 'kubeconfig') {
|
||||
const kcContentEl = document.querySelector('#install-modal #kubeconfig-content');
|
||||
const kcContextEl = document.querySelector('#install-modal #kubeconfig-context');
|
||||
installKubeconfigContent = kcContentEl ? kcContentEl.value.trim() : '';
|
||||
installKubeconfigContext = kcContextEl ? kcContextEl.value.trim() : '';
|
||||
if (!installKubeconfigContent) {
|
||||
alert('请输入 kubeconfig 内容');
|
||||
const kcContentEl = document.querySelector('#install-modal #k8s-url');
|
||||
const kcContextEl = document.querySelector('#install-modal #k8s-token');
|
||||
installK8sURL = kcContentEl ? kcContentEl.value.trim() : '';
|
||||
installK8sToken = kcContextEl ? kcContextEl.value.trim() : '';
|
||||
if (!installK8sURL || !installK8sToken) {
|
||||
alert('请输入 Kubernetes API 地址和 Token');
|
||||
return;
|
||||
}
|
||||
config.deploy = { type: 'kubernetes' };
|
||||
@@ -1036,14 +1036,14 @@ function updateApp() {
|
||||
if (installTarget === 'kubeconfig') {
|
||||
const kcInput = document.createElement('input');
|
||||
kcInput.type = 'hidden';
|
||||
kcInput.name = 'kubeconfig';
|
||||
kcInput.value = installKubeconfigContent;
|
||||
kcInput.name = 'k8s_url';
|
||||
kcInput.value = installK8sURL;
|
||||
updateForm.appendChild(kcInput);
|
||||
|
||||
const kctxInput = document.createElement('input');
|
||||
kctxInput.type = 'hidden';
|
||||
kctxInput.name = 'kubeconfig_context';
|
||||
kctxInput.value = installKubeconfigContext;
|
||||
kctxInput.name = 'k8s_token';
|
||||
kctxInput.value = installK8sToken;
|
||||
updateForm.appendChild(kctxInput);
|
||||
}
|
||||
|
||||
@@ -1306,12 +1306,12 @@ async function installApp() {
|
||||
const selectedTarget = document.querySelector('#install-modal input[name="installTargetRadio"]:checked');
|
||||
installTarget = selectedTarget ? selectedTarget.value : 'local';
|
||||
if (installTarget === 'kubeconfig') {
|
||||
const kcContentEl = document.querySelector('#install-modal #kubeconfig-content');
|
||||
const kcContextEl = document.querySelector('#install-modal #kubeconfig-context');
|
||||
installKubeconfigContent = kcContentEl ? kcContentEl.value.trim() : '';
|
||||
installKubeconfigContext = kcContextEl ? kcContextEl.value.trim() : '';
|
||||
if (!installKubeconfigContent) {
|
||||
alert('请输入 kubeconfig 内容');
|
||||
const kcContentEl = document.querySelector('#install-modal #k8s-url');
|
||||
const kcContextEl = document.querySelector('#install-modal #k8s-token');
|
||||
installK8sURL = kcContentEl ? kcContentEl.value.trim() : '';
|
||||
installK8sToken = kcContextEl ? kcContextEl.value.trim() : '';
|
||||
if (!installK8sURL || !installK8sToken) {
|
||||
alert('请输入 Kubernetes API 地址和 Token');
|
||||
return;
|
||||
}
|
||||
config.deploy = { type: 'kubernetes' };
|
||||
@@ -1359,13 +1359,13 @@ async function installApp() {
|
||||
if (installTarget === 'kubeconfig') {
|
||||
const kcInput = document.createElement('input');
|
||||
kcInput.type = 'hidden';
|
||||
kcInput.name = 'kubeconfig';
|
||||
kcInput.value = installKubeconfigContent;
|
||||
kcInput.name = 'k8s_url';
|
||||
kcInput.value = installK8sURL;
|
||||
installForm.appendChild(kcInput);
|
||||
const kctxInput = document.createElement('input');
|
||||
kctxInput.type = 'hidden';
|
||||
kctxInput.name = 'kubeconfig_context';
|
||||
kctxInput.value = installKubeconfigContext;
|
||||
kctxInput.name = 'k8s_token';
|
||||
kctxInput.value = installK8sToken;
|
||||
installForm.appendChild(kctxInput);
|
||||
}
|
||||
// 提交安装表单
|
||||
|
||||
Reference in New Issue
Block a user