feat: 新增日志中心、功能优化与代码重构

- 新增 log_datasource.go / log_proxy.go / user_api_token.go 后端控制器
- 新增 log_datasource.go model
- 新增 monitor/log-center.ts API 模块
- 新增 LogCenter 前端组件及页面
- 重构 deployment 相关组件 (HPDA/UpdateImageModal/bundle-yaml 等)
- 新增 ResetPasswordDialog / code-snippet-block / copy-button 等 UI 组件
- 重构多个 k8s 相关组件 (TopologyView/ResourceVisualEditor/PodFileBrowser 等)
- 更新多个 API 和工具函数 (auth.ts/monitor/index.ts/request.ts 等)
- 更新静态资源 (index.html + 新 JS/CSS bundle)
- 删除废弃静态资源文件
This commit is contained in:
Logan committed 2026-06-26 23:18:05 +08:00
1 parent 5ca0e6d83a
commit 8d46e8f352
197 files changed
+5014 -815

No files matched your search

+152 -18
View File
@@ -140,10 +140,46 @@ func NewClient(kubeconfigYAML string) (*Client, error) {
}, nil
}
// NewClientFromToken 支持直接用 apiServer + token 创建客户端(对应 config_type=0 场景)
// 生成一个最小 kubeconfig 复用 NewClient 解析逻辑 // Logan.Li
func NewClientFromToken(apiServer, token string) (*Client, error) {
if apiServer == "" || token == "" {
return nil, fmt.Errorf("apiServer and token are required for token auth")
}
server := strings.TrimRight(apiServer, "/")
// 为 token 模式生成最小化 kubeconfig(insecure-skip 便于测试集群;生产建议用完整 kubeconfig 携带 CA)
kubeYAML := fmt.Sprintf(`apiVersion: v1
clusters:
- cluster:
server: %s
insecure-skip-tls-verify: true
name: cluster
contexts:
- context:
cluster: cluster
user: user
name: ctx
current-context: ctx
kind: Config
preferences: {}
users:
- name: user
user:
token: %s
`, server, token)
return NewClient(kubeYAML)
}
// get performs a GET request to the K8s API
func (c *Client) get(path string, result interface{}) error {
url := c.BaseURL + path
resp, err := c.HTTPClient.Get(url)
req, err := http.NewRequest("GET", c.BaseURL+path, nil)
if err != nil {
return fmt.Errorf("GET %s: %w", path, err)
}
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("GET %s: %w", path, err)
}
@@ -169,6 +205,9 @@ func (c *Client) getRaw(path string, accept string) ([]byte, error) {
req, err := http.NewRequest("GET", c.BaseURL+path, nil)
if err != nil { return nil, err }
req.Header.Set("Accept", accept)
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil { return nil, fmt.Errorf("GET %s: %w", path, err) }
defer resp.Body.Close()
@@ -185,6 +224,9 @@ func (c *Client) putRaw(path string, contentType string, data []byte) error {
req, err := http.NewRequest("PUT", c.BaseURL+path, strings.NewReader(string(data)))
if err != nil { return err }
req.Header.Set("Content-Type", contentType)
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil { return fmt.Errorf("PUT %s: %w", path, err) }
defer resp.Body.Close()
@@ -279,9 +321,19 @@ type ContainerRes struct {
}
type PodStatus struct {
Phase string `json:"phase"`
PodIP string `json:"podIP"`
ContainerStatuses []ContainerStatus `json:"containerStatuses"`
Phase string `json:"phase"`
PodIP string `json:"podIP"`
ContainerStatuses []ContainerStatus `json:"containerStatuses"`
Conditions []PodCondition `json:"conditions,omitempty"`
}
// PodCondition for pod status conditions (Scheduled, Initialized, Ready etc)
type PodCondition struct {
Type string `json:"type"`
Status string `json:"status"`
LastTransitionTime string `json:"lastTransitionTime,omitempty"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}
type ContainerStatus struct {
@@ -395,6 +447,18 @@ type IngressServicePort struct {
Name string `json:"name,omitempty"`
}
// IngressClassList 用于 IngressClass 下拉选择
type IngressClassList struct {
Items []IngressClassItem `json:"items"`
}
type IngressClassItem struct {
Metadata ObjectMeta `json:"metadata"`
Spec struct {
Controller string `json:"controller"`
} `json:"spec"`
}
// ObjectMeta
type ObjectMeta struct {
Name string `json:"name"`
@@ -406,11 +470,12 @@ type ObjectMeta struct {
// SyncResult holds sync counts
type SyncResult struct {
Nodes int `json:"nodes"`
Pods int `json:"pods"`
Deployments int `json:"deployments"`
Services int `json:"services"`
Ingresses int `json:"ingresses"`
Nodes int `json:"nodes"`
Pods int `json:"pods"`
Deployments int `json:"deployments"`
Services int `json:"services"`
Ingresses int `json:"ingresses"`
Errors []string `json:"errors,omitempty"` // 各资源同步错误,便于排查 // Logan.Li
}
// GetServerVersion returns K8s server version
@@ -460,6 +525,16 @@ func (c *Client) ListDeployments() (*DeploymentList, error) {
return &result, nil
}
// GetDeployment 获取单个 Deployment // Logan.Li
func (c *Client) GetDeployment(namespace, name string) (*DeploymentItem, error) {
var result DeploymentItem
path := fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/%s", namespace, name)
if err := c.get(path, &result); err != nil {
return nil, err
}
return &result, nil
}
// ListServices fetches all services across namespaces
func (c *Client) ListServices() (*ServiceList, error) {
var result ServiceList
@@ -478,6 +553,15 @@ func (c *Client) ListIngresses() (*IngressList, error) {
return &result, nil
}
// ListIngressClasses fetches all IngressClass (cluster-scoped) for dropdown selection in Ingress visual editor
func (c *Client) ListIngressClasses() (*IngressClassList, error) {
var result IngressClassList
if err := c.get("/apis/networking.k8s.io/v1/ingressclasses", &result); err != nil {
return nil, err
}
return &result, nil
}
// --- HTTP helpers (post/patch/put/delete) ---
func (c *Client) post(path string, body, result interface{}) error {
@@ -490,6 +574,9 @@ func (c *Client) post(path string, body, result interface{}) error {
return err
}
req.Header.Set("Content-Type", "application/json")
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("POST %s: %w", path, err)
@@ -515,6 +602,9 @@ func (c *Client) patch(path string, body, result interface{}) error {
return err
}
req.Header.Set("Content-Type", "application/merge-patch+json")
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("PATCH %s: %w", path, err)
@@ -535,6 +625,9 @@ func (c *Client) delete(path string) error {
if err != nil {
return err
}
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("DELETE %s: %w", path, err)
@@ -555,8 +648,14 @@ func (c *Client) GetPodLogs(namespace, podName, container string, tailLines int)
if container != "" {
path += "&container=" + container
}
url := c.BaseURL + path
resp, err := c.HTTPClient.Get(url)
req, err := http.NewRequest("GET", c.BaseURL+path, nil)
if err != nil {
return "", fmt.Errorf("GET logs: %w", err)
}
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return "", fmt.Errorf("GET logs: %w", err)
}
@@ -577,8 +676,14 @@ func (c *Client) GetPodLogsStream(namespace, podName, container string, tailLine
if container != "" {
path += "&container=" + container
}
url := c.BaseURL + path
resp, err := c.HTTPClient.Get(url)
req, err := http.NewRequest("GET", c.BaseURL+path, nil)
if err != nil {
return nil, fmt.Errorf("GET logs stream: %w", err)
}
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("GET logs stream: %w", err)
}
@@ -607,6 +712,21 @@ func (c *Client) ScaleDeployment(namespace, name string, replicas int) error {
// UpdateDeploymentImage patches the container image of a deployment
func (c *Client) UpdateDeploymentImage(namespace, name, containerName, image string) error {
image = strings.TrimSpace(image)
if image == "" {
return fmt.Errorf("image 不能为空")
}
containerName = strings.TrimSpace(containerName)
if containerName == "" {
dep, err := c.GetDeployment(namespace, name)
if err != nil {
return fmt.Errorf("获取 Deployment 失败: %w", err)
}
if len(dep.Spec.Template.Spec.Containers) == 0 {
return fmt.Errorf("Deployment 无容器定义")
}
containerName = dep.Spec.Template.Spec.Containers[0].Name
}
body := map[string]interface{}{
"spec": map[string]interface{}{
"template": map[string]interface{}{
@@ -1872,8 +1992,15 @@ func (c *Client) buildLogPath(namespace, podName string, opts LogOptions, follow
// 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)
u := c.BaseURL + c.buildLogPath(namespace, podName, opts, false)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return "", fmt.Errorf("GET logs: %w", err)
}
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return "", fmt.Errorf("GET logs: %w", err)
}
@@ -1890,8 +2017,15 @@ func (c *Client) GetPodLogsWithOptions(namespace, podName string, opts LogOption
// 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)
u := c.BaseURL + c.buildLogPath(namespace, podName, opts, true)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return nil, fmt.Errorf("GET logs stream: %w", err)
}
if c.BearerToken != "" {
req.Header.Set("Authorization", "Bearer "+c.BearerToken)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("GET logs stream: %w", err)
}