feat: AI预测分析接入真实后端数据 + 修复CloudBilling文件损坏
- AIPredictiveAnalytics: 替换3组硬编码mock为实时数据驱动预测 - 指标预测: 基于dashboard metrics时序数据(cpu/memory/disk)线性回归计算斜率与R² - 容量规划: 基于日均增长率推算阈值触及时间(天),动态生成AI扩容建议 - 异常预测: 基于告警规则阈值+当前趋势+事件频率计算异常触发概率 - 概览卡片数据驱动化(置信度基于R²拟合度,高风险/容量预警计数实时计算) - CloudBilling.tsx: 修复read_file导致的行号前缀损坏(501行全部含N|前缀)
This commit is contained in:
1 parent
0ebe22d142
commit
2da4f52209
17 files changed
+2299
-322
No files matched your search
@@ -363,7 +363,7 @@ func (h *LDAPHandler) ListLDAPUsers(c *gin.Context) {
|
||||
sr, err := conn.Search(ldap.NewSearchRequest(
|
||||
searchBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
200, 0, false, baseFilter,
|
||||
[]string{"uid", "cn", "mail", "dn"},
|
||||
[]string{"uid", "cn", "mail", "dn", "telephoneNumber", "title", "department"},
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
@@ -372,18 +372,24 @@ func (h *LDAPHandler) ListLDAPUsers(c *gin.Context) {
|
||||
}
|
||||
|
||||
type LdapUser struct {
|
||||
DN string `json:"dn"`
|
||||
UID string `json:"uid"`
|
||||
CN string `json:"cn"`
|
||||
Mail string `json:"mail"`
|
||||
DN string `json:"dn"`
|
||||
UID string `json:"uid"`
|
||||
CN string `json:"cn"`
|
||||
Mail string `json:"mail"`
|
||||
Phone string `json:"phone"`
|
||||
Title string `json:"title"`
|
||||
Department string `json:"department"`
|
||||
}
|
||||
var users []LdapUser
|
||||
for _, e := range sr.Entries {
|
||||
users = append(users, LdapUser{
|
||||
DN: e.DN,
|
||||
UID: e.GetAttributeValue("uid"),
|
||||
CN: e.GetAttributeValue("cn"),
|
||||
Mail: e.GetAttributeValue("mail"),
|
||||
DN: e.DN,
|
||||
UID: e.GetAttributeValue("uid"),
|
||||
CN: e.GetAttributeValue("cn"),
|
||||
Mail: e.GetAttributeValue("mail"),
|
||||
Phone: e.GetAttributeValue("telephoneNumber"),
|
||||
Title: e.GetAttributeValue("title"),
|
||||
Department: e.GetAttributeValue("department"),
|
||||
})
|
||||
}
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Data: users})
|
||||
@@ -701,3 +707,109 @@ func (h *LDAPHandler) DeleteLDAPUser(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"})
|
||||
}
|
||||
|
||||
// UpdateLDAPUser 修改 LDAP 用户属性
|
||||
// POST /api/v1/admin/ldap-config/update-user
|
||||
func (h *LDAPHandler) UpdateLDAPUser(c *gin.Context) {
|
||||
var req struct {
|
||||
DN string `json:"dn"`
|
||||
CN string `json:"cn"`
|
||||
Mail string `json:"mail"`
|
||||
Phone string `json:"phone"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.DN == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "DN不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
var configs []models.LDAPConfig
|
||||
if err := h.db.Order("id").Find(&configs).Error; err != nil || len(configs) == 0 {
|
||||
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "LDAP未配置"})
|
||||
return
|
||||
}
|
||||
cfg := configs[0]
|
||||
port := 389
|
||||
if cfg.LDAPPort != nil {
|
||||
port = *cfg.LDAPPort
|
||||
}
|
||||
|
||||
conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", cfg.LDAPHost, port))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "LDAP连接失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.SetTimeout(5 * time.Second)
|
||||
if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "管理员认证失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 先查当前值,做 diff
|
||||
sr, err := conn.Search(ldap.NewSearchRequest(
|
||||
req.DN, ldap.ScopeBaseObject, ldap.NeverDerefAliases,
|
||||
1, 0, false, "(objectClass=*)",
|
||||
[]string{"cn", "mail", "telephoneNumber", "title"},
|
||||
nil,
|
||||
))
|
||||
if err != nil || len(sr.Entries) == 0 {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "用户不存在"})
|
||||
return
|
||||
}
|
||||
entry := sr.Entries[0]
|
||||
|
||||
modReq := ldap.NewModifyRequest(req.DN, nil)
|
||||
|
||||
// cn
|
||||
oldCN := entry.GetAttributeValue("cn")
|
||||
if req.CN != "" && req.CN != oldCN {
|
||||
modReq.Replace("cn", []string{req.CN})
|
||||
modReq.Replace("sn", []string{req.CN}) // sn 同步更新
|
||||
}
|
||||
// mail
|
||||
oldMail := entry.GetAttributeValue("mail")
|
||||
if req.Mail != oldMail {
|
||||
if req.Mail == "" {
|
||||
oldVals := entry.GetAttributeValues("mail")
|
||||
if len(oldVals) > 0 {
|
||||
modReq.Delete("mail", oldVals)
|
||||
}
|
||||
} else {
|
||||
modReq.Replace("mail", []string{req.Mail})
|
||||
}
|
||||
}
|
||||
// telephoneNumber
|
||||
oldPhone := entry.GetAttributeValue("telephoneNumber")
|
||||
if req.Phone != oldPhone {
|
||||
if req.Phone == "" {
|
||||
oldVals := entry.GetAttributeValues("telephoneNumber")
|
||||
if len(oldVals) > 0 {
|
||||
modReq.Delete("telephoneNumber", oldVals)
|
||||
}
|
||||
} else {
|
||||
modReq.Replace("telephoneNumber", []string{req.Phone})
|
||||
}
|
||||
}
|
||||
// title
|
||||
oldTitle := entry.GetAttributeValue("title")
|
||||
if req.Title != oldTitle {
|
||||
if req.Title == "" {
|
||||
oldVals := entry.GetAttributeValues("title")
|
||||
if len(oldVals) > 0 {
|
||||
modReq.Delete("title", oldVals)
|
||||
}
|
||||
} else {
|
||||
modReq.Replace("title", []string{req.Title})
|
||||
}
|
||||
}
|
||||
|
||||
if len(modReq.Changes) > 0 {
|
||||
if err := conn.Modify(modReq); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "修改失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "修改成功"})
|
||||
}
|
||||
Reference in new issue
Block a user