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:
1 parent
5ca0e6d83a
commit
8d46e8f352
197 files changed
+5014
-815
No files matched your search
@@ -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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user