fix: K8s 集群添加缺少 code 必填字段

- KubernetesClusterDialog: 新增 code 表单字段
- 名称变更自动生成 code(小写+连字符)
- handleSubmit 增加 code 校验 + toast 提示
- payload 增加 code 字段(后端 binding:required)
This commit is contained in:
attacker committed 2026-06-21 09:50:07 +00:00
1 parent f24fe43ac0
commit ce438c9f05
18 files changed
+3941 -144

No files matched your search

+168
View File
@@ -1316,3 +1316,171 @@ func (c *Client) resourceAPIPath(apiVersion, kind, namespace, name string) (stri
}
return "/api/v1/namespaces/" + ns + "/" + resource + "/" + name, true
}
// ============================================================================
// ReplicaSet — 用于 Deployment 回滚 / 历史版本查询 // Logan.Li
// ============================================================================
// ReplicaSetList response
type ReplicaSetList struct {
Items []ReplicaSetItem `json:"items"`
}
// ReplicaSetItem 表示一个 ReplicaSet(含完整 spec/status/metadata,用于回滚取 template)
type ReplicaSetItem struct {
Metadata ObjectMeta `json:"metadata"`
Spec ReplicaSetSpec `json:"spec"`
Status ReplicaSetStatus `json:"status"`
}
// ReplicaSetSpec 与 DeploymentSpec 结构一致,关键是 template
type ReplicaSetSpec struct {
Replicas int `json:"replicas"`
Template ReplicaSetTemplate `json:"template"`
}
// ReplicaSetTemplate pod 模板
type ReplicaSetTemplate struct {
Metadata ObjectMeta `json:"metadata"`
Spec PodSpec `json:"spec"`
}
// ReplicaSetStatus 副本状态
type ReplicaSetStatus struct {
Replicas int `json:"replicas"`
AvailableReplicas int `json:"availableReplicas"`
ReadyReplicas int `json:"readyReplicas"`
}
// OwnerReference 资源归属引用
type OwnerReference struct {
Kind string `json:"kind"`
Name string `json:"name"`
}
// ReplicaSetOwnerMeta 在 ListReplicaSets 时额外携带 ownerReferences
type ReplicaSetOwnerMeta struct {
Metadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
CreationTimestamp string `json:"creationTimestamp"`
OwnerReferences []OwnerReference `json:"ownerReferences"`
} `json:"metadata"`
Spec struct {
Replicas int `json:"replicas"`
Template ReplicaSetTemplate `json:"template"`
} `json:"spec"`
Status ReplicaSetStatus `json:"status"`
}
// ListReplicaSetsRaw 列出 namespace 下的 ReplicaSet(含 ownerReferences 原始字段)
func (c *Client) ListReplicaSetsRaw(namespace string) ([]ReplicaSetOwnerMeta, error) {
var result struct {
Items []ReplicaSetOwnerMeta `json:"items"`
}
path := fmt.Sprintf("/apis/apps/v1/namespaces/%s/replicasets", namespace)
if err := c.get(path, &result); err != nil {
return nil, err
}
return result.Items, nil
}
// GetReplicaSet 获取单个 ReplicaSet 的完整 JSON(map 形式,便于取 template 直接 patch)
func (c *Client) GetReplicaSet(namespace, name string) (map[string]interface{}, error) {
path := fmt.Sprintf("/apis/apps/v1/namespaces/%s/replicasets/%s", namespace, name)
var result map[string]interface{}
if err := c.get(path, &result); err != nil {
return nil, err
}
return result, nil
}
// PatchDeploymentSpecTemplate 用旧 ReplicaSet 的 template 回滚 Deployment
// 通过 merge-patch 将 deployment.spec.template 整体替换为 rsTemplate
func (c *Client) PatchDeploymentSpecTemplate(namespace, deploymentName string, rsTemplate map[string]interface{}) error {
body := map[string]interface{}{
"spec": map[string]interface{}{
"template": rsTemplate,
},
}
path := fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/%s", namespace, deploymentName)
return c.patch(path, body, nil)
}
// ============================================================================
// Pod 删除 / 高级日志参数 // Logan.Li
// ============================================================================
// DeletePod 删除指定 namespace 下的 Pod
func (c *Client) DeletePod(namespace, name string) error {
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s", namespace, name)
return c.delete(path)
}
// LogOptions 日志查询高级参数(container/sinceTime/previous/tailLines)
type LogOptions struct {
Container string
SinceTime string // RFC3339 格式,仅返回此时间之后的日志
Previous bool // 返回已终止容器(previous instance)的日志
TailLines int // 末尾 N 行,0 表示不限制
}
// buildLogPath 构造 K8s pod log API 的完整查询路径
func (c *Client) buildLogPath(namespace, podName string, opts LogOptions, follow bool) string {
q := []string{}
if opts.Container != "" {
q = append(q, "container="+opts.Container)
}
if opts.TailLines > 0 {
q = append(q, fmt.Sprintf("tailLines=%d", opts.TailLines))
}
if opts.SinceTime != "" {
q = append(q, "sinceTime="+opts.SinceTime)
}
if opts.Previous {
q = append(q, "previous=true")
}
if follow {
q = append(q, "follow=true")
}
path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/log", namespace, podName)
if len(q) > 0 {
path += "?" + strings.Join(q, "&")
}
return path
}
// GetPodLogsWithOptions 支持高级参数的日志查询(非流式)
func (c *Client) GetPodLogsWithOptions(namespace, podName string, opts LogOptions) (string, error) {
url := c.BaseURL + c.buildLogPath(namespace, podName, opts, false)
resp, err := c.HTTPClient.Get(url)
if err != nil {
return "", fmt.Errorf("GET logs: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode >= 400 {
return "", fmt.Errorf("K8s API %d: %s", resp.StatusCode, string(body[:min(len(body), 300)]))
}
return string(body), nil
}
// GetPodLogsStreamWithOptions 支持高级参数的日志流式查询(follow=true)
func (c *Client) GetPodLogsStreamWithOptions(namespace, podName string, opts LogOptions) (io.ReadCloser, error) {
url := c.BaseURL + c.buildLogPath(namespace, podName, opts, true)
resp, err := c.HTTPClient.Get(url)
if err != nil {
return nil, fmt.Errorf("GET logs stream: %w", err)
}
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return nil, fmt.Errorf("K8s API %d: %s", resp.StatusCode, string(body[:min(len(body), 300)]))
}
return resp.Body, nil
}