fix: dashboard metrics 慢查询优化(index+UNIX_TIMESTAMP分组) + 批量升级版本号修复
This commit is contained in:
1 parent
c515f6ed03
commit
20f64e3851
5 files changed
+222
-19
No files matched your search
@@ -6,6 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ezops/internal/models"
|
"ezops/internal/models"
|
||||||
@@ -423,9 +424,9 @@ func (h *AgentInstallHandler) BatchUpgradeAgent(c *gin.Context) {
|
|||||||
binaryFile = "aiops-agent-linux-arm64"
|
binaryFile = "aiops-agent-linux-arm64"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载新二进制 + 重启服务
|
// 下载新二进制 + 获取版本号 + 重启服务
|
||||||
upgradeScript := fmt.Sprintf(
|
upgradeScript := fmt.Sprintf(
|
||||||
`curl -fsSL -o /tmp/aiops-agent %s/api/agent/bin/%s && chmod +x /tmp/aiops-agent && systemctl stop aiops-agent 2>/dev/null; mv /tmp/aiops-agent /usr/local/bin/aiops-agent && systemctl start aiops-agent && echo "UPGRADE_OK"`,
|
`curl -fsSL -o /tmp/aiops-agent %s/api/agent/bin/%s && chmod +x /tmp/aiops-agent && systemctl stop aiops-agent 2>/dev/null; mv /tmp/aiops-agent /usr/local/bin/aiops-agent && systemctl start aiops-agent; ver=$(/usr/local/bin/aiops-agent output 2>/dev/null | grep 'agent.version' | awk -F'[|[|]' '{print $2}' | tr -d ' '); echo "UPGRADE_OK ver=$ver"`,
|
||||||
serverURL, binaryFile,
|
serverURL, binaryFile,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -437,9 +438,15 @@ func (h *AgentInstallHandler) BatchUpgradeAgent(c *gin.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新版本号
|
// 从输出提取版本号
|
||||||
|
agentVersion := "unknown"
|
||||||
|
parts := strings.Split(out, "ver=")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
agentVersion = strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
|
||||||
h.db.Model(&server).Updates(map[string]interface{}{
|
h.db.Model(&server).Updates(map[string]interface{}{
|
||||||
"agent_version": "v0.1.0",
|
"agent_version": agentVersion,
|
||||||
"last_check_at": time.Now(),
|
"last_check_at": time.Now(),
|
||||||
})
|
})
|
||||||
log.Printf("[BatchUpgrade] %s Agent 升级成功", server.Hostname)
|
log.Printf("[BatchUpgrade] %s Agent 升级成功", server.Hostname)
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ func (h *AgentTaskHandler) QuickSetupRules(c *gin.Context) {
|
|||||||
{Name: "CPU使用率(Warn)", Metric: "cpu", Operator: ">", Threshold: 80},
|
{Name: "CPU使用率(Warn)", Metric: "cpu", Operator: ">", Threshold: 80},
|
||||||
{Name: "内存使用率(Warn)", Metric: "mem", Operator: ">", Threshold: 80},
|
{Name: "内存使用率(Warn)", Metric: "mem", Operator: ">", Threshold: 80},
|
||||||
{Name: "磁盘使用率(Warn)", Metric: "disk", Operator: ">", Threshold: 80},
|
{Name: "磁盘使用率(Warn)", Metric: "disk", Operator: ">", Threshold: 80},
|
||||||
{Name: "网络带宽使用率(Warn)", Metric: "net", Operator: ">", Threshold: 80},
|
{Name: "FD使用率(Warn)", Metric: "fd", Operator: ">", Threshold: 80},
|
||||||
}
|
}
|
||||||
|
|
||||||
created := 0
|
created := 0
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func (e *AlertEngine) Check() {
|
|||||||
// 检查 CPU/内存/磁盘/进程/TCP连接/端口 阈值
|
// 检查 CPU/内存/磁盘/进程/TCP连接/端口 阈值
|
||||||
func (e *AlertEngine) checkResourceAlerts() {
|
func (e *AlertEngine) checkResourceAlerts() {
|
||||||
var rules []models.AgentRule
|
var rules []models.AgentRule
|
||||||
e.db.Where("enabled = ? AND metric IN ?", true, []string{"cpu", "mem", "disk", "service_process", "tcp_time_wait", "tcp_close_wait"}).Find(&rules)
|
e.db.Where("enabled = ? AND metric IN ?", true, []string{"cpu", "mem", "disk", "service_process", "tcp_time_wait", "tcp_close_wait", "fd"}).Find(&rules)
|
||||||
|
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
// 获取目标主机最新上报
|
// 获取目标主机最新上报
|
||||||
@@ -81,6 +81,10 @@ func (e *AlertEngine) checkResourceAlerts() {
|
|||||||
continue // 进程存在,不触发
|
continue // 进程存在,不触发
|
||||||
}
|
}
|
||||||
value = 0 // 进程不存在时触发
|
value = 0 // 进程不存在时触发
|
||||||
|
case "fd":
|
||||||
|
var fdStat models.FDMetric
|
||||||
|
json.Unmarshal([]byte(report.FDInfo), &fdStat)
|
||||||
|
value = fdStat.UsagePct
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否超阈值
|
// 检查是否超阈值
|
||||||
@@ -288,7 +292,7 @@ func (e *AlertEngine) autoResolveAlerts() {
|
|||||||
if report.ID > 0 && time.Since(report.ReportTime) < 3*time.Minute {
|
if report.ID > 0 && time.Since(report.ReportTime) < 3*time.Minute {
|
||||||
resolved = true
|
resolved = true
|
||||||
}
|
}
|
||||||
case "cpu", "mem", "disk", "tcp_time_wait", "tcp_close_wait":
|
case "cpu", "mem", "disk", "tcp_time_wait", "tcp_close_wait", "fd":
|
||||||
// 获取对应规则,检查是否恢复正常
|
// 获取对应规则,检查是否恢复正常
|
||||||
var rule models.AgentRule
|
var rule models.AgentRule
|
||||||
e.db.Where("name = ?", alert.RuleName).First(&rule)
|
e.db.Where("name = ?", alert.RuleName).First(&rule)
|
||||||
@@ -326,6 +330,10 @@ func (e *AlertEngine) autoResolveAlerts() {
|
|||||||
var netStat models.NetworkStatModel
|
var netStat models.NetworkStatModel
|
||||||
json.Unmarshal([]byte(report.NetInfo), &netStat)
|
json.Unmarshal([]byte(report.NetInfo), &netStat)
|
||||||
value = float64(netStat.TCPCloseWait)
|
value = float64(netStat.TCPCloseWait)
|
||||||
|
case "fd":
|
||||||
|
var fdStat models.FDMetric
|
||||||
|
json.Unmarshal([]byte(report.FDInfo), &fdStat)
|
||||||
|
value = fdStat.UsagePct
|
||||||
}
|
}
|
||||||
if !e.compareOperator(rule.Operator, value, rule.Threshold) {
|
if !e.compareOperator(rule.Operator, value, rule.Threshold) {
|
||||||
resolved = true
|
resolved = true
|
||||||
|
|||||||
@@ -294,28 +294,24 @@ func (h *DashboardHandler) GetMetrics(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 agent_reports 表查最近 N 小时的真实数据(MySQL 聚合)
|
// 从 agent_reports 表查最近 N 小时的真实数据(整数分组,可用索引)
|
||||||
since := time.Now().Add(-time.Duration(hours) * time.Hour)
|
since := time.Now().Add(-time.Duration(hours) * time.Hour)
|
||||||
type avgPoint struct {
|
type avgPoint struct {
|
||||||
Bucket string
|
BucketTS int64
|
||||||
AvgCPU float64
|
AvgCPU float64
|
||||||
AvgMem float64
|
AvgMem float64
|
||||||
}
|
}
|
||||||
var rows []avgPoint
|
var rows []avgPoint
|
||||||
h.db.Model(&models.AgentReport{}).
|
h.db.Model(&models.AgentReport{}).
|
||||||
Select("DATE_FORMAT(report_time, '%Y-%m-%d %H:00') as bucket, ROUND(AVG(cpu_usage),2) as avg_cpu, ROUND(AVG(mem_usage),2) as avg_mem").
|
Select("UNIX_TIMESTAMP(report_time) DIV 3600 as bucket_ts, ROUND(AVG(cpu_usage),2) as avg_cpu, ROUND(AVG(mem_usage),2) as avg_mem").
|
||||||
Where("report_time >= ?", since).
|
Where("report_time >= ?", since).
|
||||||
Group("bucket").
|
Group("bucket_ts").
|
||||||
Order("bucket ASC").
|
Order("bucket_ts ASC").
|
||||||
Scan(&rows)
|
Scan(&rows)
|
||||||
|
|
||||||
var metrics []models.MetricData
|
var metrics []models.MetricData
|
||||||
for _, r := range rows {
|
for _, r := range rows {
|
||||||
// 手动解析 SQLite strftime 字符串为 time.Time(GORM 无法自动转换) // Logan.Li
|
ts := time.Unix(r.BucketTS*3600, 0)
|
||||||
ts, err := time.ParseInLocation("2006-01-02 15:00", r.Bucket, time.Local)
|
|
||||||
if err != nil {
|
|
||||||
ts = time.Now()
|
|
||||||
}
|
|
||||||
metrics = append(metrics,
|
metrics = append(metrics,
|
||||||
models.MetricData{Name: "cpu", Value: r.AvgCPU, Timestamp: ts},
|
models.MetricData{Name: "cpu", Value: r.AvgCPU, Timestamp: ts},
|
||||||
models.MetricData{Name: "memory", Value: r.AvgMem, Timestamp: ts},
|
models.MetricData{Name: "memory", Value: r.AvgMem, Timestamp: ts},
|
||||||
|
|||||||
@@ -1806,6 +1806,32 @@ func (h *KubernetesHandler) resolveClusterCode(code string) int {
|
|||||||
return int(cluster.ID)
|
return int(cluster.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// formatAge converts a K8s creationTimestamp (RFC3339) to a human-readable relative age
|
||||||
|
func formatAge(creationTimestamp string) string {
|
||||||
|
if creationTimestamp == "" {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
t, err := time.Parse(time.RFC3339, creationTimestamp)
|
||||||
|
if err != nil {
|
||||||
|
return creationTimestamp
|
||||||
|
}
|
||||||
|
d := time.Since(t)
|
||||||
|
switch {
|
||||||
|
case d < time.Minute:
|
||||||
|
return fmt.Sprintf("%ds", int(d.Seconds()))
|
||||||
|
case d < time.Hour:
|
||||||
|
return fmt.Sprintf("%dm", int(d.Minutes()))
|
||||||
|
case d < 24*time.Hour:
|
||||||
|
return fmt.Sprintf("%dh", int(d.Hours()))
|
||||||
|
case d < 30*24*time.Hour:
|
||||||
|
return fmt.Sprintf("%dd", int(d.Hours()/24))
|
||||||
|
case d < 365*24*time.Hour:
|
||||||
|
return fmt.Sprintf("%dM", int(d.Hours()/(24*30)))
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%dy", int(d.Hours()/(24*365)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// getK8sClientByCode resolves cluster by code, falls back to ID
|
// getK8sClientByCode resolves cluster by code, falls back to ID
|
||||||
func (h *KubernetesHandler) getK8sClientByCode(c *gin.Context) (*k8sclient.Client, *models.KubernetesCluster, error) {
|
func (h *KubernetesHandler) getK8sClientByCode(c *gin.Context) (*k8sclient.Client, *models.KubernetesCluster, error) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
@@ -2571,6 +2597,12 @@ func (h *KubernetesHandler) GetResourceYAMLNS(c *gin.Context) {
|
|||||||
apiPath = fmt.Sprintf("/apis/autoscaling/v2/namespaces/%s/horizontalpodautoscalers/%s", ns, name)
|
apiPath = fmt.Sprintf("/apis/autoscaling/v2/namespaces/%s/horizontalpodautoscalers/%s", ns, name)
|
||||||
case "cronjobs", "cronjob":
|
case "cronjobs", "cronjob":
|
||||||
apiPath = fmt.Sprintf("/apis/batch/v1/namespaces/%s/cronjobs/%s", ns, name)
|
apiPath = fmt.Sprintf("/apis/batch/v1/namespaces/%s/cronjobs/%s", ns, name)
|
||||||
|
case "serviceaccounts", "serviceaccount":
|
||||||
|
apiPath = fmt.Sprintf("/api/v1/namespaces/%s/serviceaccounts/%s", ns, name)
|
||||||
|
case "roles", "role":
|
||||||
|
apiPath = fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/roles/%s", ns, name)
|
||||||
|
case "rolebindings", "rolebinding":
|
||||||
|
apiPath = fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/rolebindings/%s", ns, name)
|
||||||
default:
|
default:
|
||||||
c.JSON(400, models.Response{Code: 400, Message: "unsupported resource type: " + resource}); return
|
c.JSON(400, models.Response{Code: 400, Message: "unsupported resource type: " + resource}); return
|
||||||
}
|
}
|
||||||
@@ -2627,6 +2659,12 @@ func (h *KubernetesHandler) UpdateResourceYAMLNS(c *gin.Context) {
|
|||||||
apiPath = fmt.Sprintf("/apis/autoscaling/v2/namespaces/%s/horizontalpodautoscalers/%s", ns, name)
|
apiPath = fmt.Sprintf("/apis/autoscaling/v2/namespaces/%s/horizontalpodautoscalers/%s", ns, name)
|
||||||
case "cronjobs", "cronjob":
|
case "cronjobs", "cronjob":
|
||||||
apiPath = fmt.Sprintf("/apis/batch/v1/namespaces/%s/cronjobs/%s", ns, name)
|
apiPath = fmt.Sprintf("/apis/batch/v1/namespaces/%s/cronjobs/%s", ns, name)
|
||||||
|
case "serviceaccounts", "serviceaccount":
|
||||||
|
apiPath = fmt.Sprintf("/api/v1/namespaces/%s/serviceaccounts/%s", ns, name)
|
||||||
|
case "roles", "role":
|
||||||
|
apiPath = fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/roles/%s", ns, name)
|
||||||
|
case "rolebindings", "rolebinding":
|
||||||
|
apiPath = fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/rolebindings/%s", ns, name)
|
||||||
default:
|
default:
|
||||||
c.JSON(400, models.Response{Code: 400, Message: "unsupported resource type: " + resource}); return
|
c.JSON(400, models.Response{Code: 400, Message: "unsupported resource type: " + resource}); return
|
||||||
}
|
}
|
||||||
@@ -2663,6 +2701,12 @@ func resourceAPIPath(ns, resource, name string) (string, error) {
|
|||||||
return fmt.Sprintf("/apis/autoscaling/v2/namespaces/%s/horizontalpodautoscalers/%s", ns, name), nil
|
return fmt.Sprintf("/apis/autoscaling/v2/namespaces/%s/horizontalpodautoscalers/%s", ns, name), nil
|
||||||
case "cronjobs", "cronjob":
|
case "cronjobs", "cronjob":
|
||||||
return fmt.Sprintf("/apis/batch/v1/namespaces/%s/cronjobs/%s", ns, name), nil
|
return fmt.Sprintf("/apis/batch/v1/namespaces/%s/cronjobs/%s", ns, name), nil
|
||||||
|
case "serviceaccounts", "serviceaccount":
|
||||||
|
return fmt.Sprintf("/api/v1/namespaces/%s/serviceaccounts/%s", ns, name), nil
|
||||||
|
case "roles", "role":
|
||||||
|
return fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/roles/%s", ns, name), nil
|
||||||
|
case "rolebindings", "rolebinding":
|
||||||
|
return fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/rolebindings/%s", ns, name), nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unsupported resource type: %s", resource)
|
return "", fmt.Errorf("unsupported resource type: %s", resource)
|
||||||
}
|
}
|
||||||
@@ -3744,3 +3788,151 @@ func (h *KubernetesHandler) GetMultiClusterComparison(c *gin.Context) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// RBAC — ServiceAccount / Role / RoleBinding (code-based)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// ListServiceAccountsNS lists ServiceAccounts in a namespace
|
||||||
|
func (h *KubernetesHandler) ListServiceAccountsNS(c *gin.Context) {
|
||||||
|
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
|
||||||
|
if clusterID == 0 {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: "cluster not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sas, err := client.ListServiceAccounts(c.Param("namespace"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type SASummary struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SecretCount int `json:"secret_count"`
|
||||||
|
ImagePullSecrets []string `json:"image_pull_secrets"`
|
||||||
|
Age string `json:"age"`
|
||||||
|
}
|
||||||
|
result := make([]SASummary, 0, len(sas))
|
||||||
|
for _, sa := range sas {
|
||||||
|
ipsNames := make([]string, 0, len(sa.ImagePullSecrets))
|
||||||
|
for _, ips := range sa.ImagePullSecrets {
|
||||||
|
ipsNames = append(ipsNames, ips.Name)
|
||||||
|
}
|
||||||
|
result = append(result, SASummary{
|
||||||
|
Namespace: sa.Metadata.Namespace,
|
||||||
|
Name: sa.Metadata.Name,
|
||||||
|
SecretCount: len(sa.Secrets),
|
||||||
|
ImagePullSecrets: ipsNames,
|
||||||
|
Age: formatAge(sa.Metadata.CreationTimestamp),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRolesNS lists Roles in a namespace
|
||||||
|
func (h *KubernetesHandler) ListRolesNS(c *gin.Context) {
|
||||||
|
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
|
||||||
|
if clusterID == 0 {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: "cluster not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roles, err := client.ListRoles(c.Param("namespace"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type RoleSummary struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
RuleCount int `json:"rule_count"`
|
||||||
|
Verbs []string `json:"verbs"`
|
||||||
|
Resources []string `json:"resources"`
|
||||||
|
Age string `json:"age"`
|
||||||
|
}
|
||||||
|
result := make([]RoleSummary, 0, len(roles.Items))
|
||||||
|
for _, r := range roles.Items {
|
||||||
|
allVerbs := []string{}
|
||||||
|
allResources := []string{}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, rule := range r.Rules {
|
||||||
|
for _, v := range rule.Verbs {
|
||||||
|
if !seen[v] {
|
||||||
|
allVerbs = append(allVerbs, v)
|
||||||
|
seen[v] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, res := range rule.Resources {
|
||||||
|
if !seen[res] {
|
||||||
|
allResources = append(allResources, res)
|
||||||
|
seen[res] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = append(result, RoleSummary{
|
||||||
|
Namespace: r.Metadata.Namespace,
|
||||||
|
Name: r.Metadata.Name,
|
||||||
|
RuleCount: len(r.Rules),
|
||||||
|
Verbs: allVerbs,
|
||||||
|
Resources: allResources,
|
||||||
|
Age: formatAge(r.Metadata.CreationTimestamp),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRoleBindingsNS lists RoleBindings in a namespace
|
||||||
|
func (h *KubernetesHandler) ListRoleBindingsNS(c *gin.Context) {
|
||||||
|
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
|
||||||
|
if clusterID == 0 {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: "cluster not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rbs, err := client.ListRoleBindings(c.Param("namespace"))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type RBSummary struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
RoleRefKind string `json:"role_ref_kind"`
|
||||||
|
RoleRefName string `json:"role_ref_name"`
|
||||||
|
Subjects []string `json:"subjects"`
|
||||||
|
Age string `json:"age"`
|
||||||
|
}
|
||||||
|
result := make([]RBSummary, 0, len(rbs.Items))
|
||||||
|
for _, rb := range rbs.Items {
|
||||||
|
subjStrs := make([]string, 0, len(rb.Subjects))
|
||||||
|
for _, s := range rb.Subjects {
|
||||||
|
if s.Namespace != "" && s.Namespace != rb.Metadata.Namespace {
|
||||||
|
subjStrs = append(subjStrs, fmt.Sprintf("%s/%s (%s)", s.Kind, s.Name, s.Namespace))
|
||||||
|
} else {
|
||||||
|
subjStrs = append(subjStrs, fmt.Sprintf("%s/%s", s.Kind, s.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = append(result, RBSummary{
|
||||||
|
Namespace: rb.Metadata.Namespace,
|
||||||
|
Name: rb.Metadata.Name,
|
||||||
|
RoleRefKind: rb.RoleRef.Kind,
|
||||||
|
RoleRefName: rb.RoleRef.Name,
|
||||||
|
Subjects: subjStrs,
|
||||||
|
Age: formatAge(rb.Metadata.CreationTimestamp),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user