1c80b07f2c
build / build (push) Successful in 1m59s
Dockerfile 运行阶段补齐 dws_dingtalk_scan(F12),并新增 runtime-scripts 门禁 做脚本依赖闭包正负例;就绪探测每次解析当前端点/绑定/凭据并区分 404、未配置、 不可达(F13);路由写入来源路径与父目录,历史空值仅按同连接同 item 的意图读时 补齐(F14);目标筛选显示短 ID 与当前绑定/历史并提供快捷筛选(F15);刷新终态 移除后台运行、展示失败时间/旧快照/脱敏原因/作业 ID 与重试(F16)。 验证:make verify 17 项全过(identity 本地按设计 skipped);一次性 schema 的 Monitor 目录/路由集成用例通过;8088 部署与真实 UI 验收仍由父会话执行。
107 lines
3.8 KiB
Go
107 lines
3.8 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
postgresstore "knowledge-governance/internal/platform/postgres"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type monitorRemovalRetrier interface {
|
|
RetryMonitorRemoval(context.Context, string, string, time.Time) error
|
|
}
|
|
|
|
func retryMonitorRemoval(w http.ResponseWriter, r *http.Request, config Config) {
|
|
identity, ok := requireSessionIdentity(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
if identity.Role != roleEngineer && identity.Role != roleAdmin {
|
|
writeJSON(w, 403, map[string]string{"error": "forbidden"})
|
|
return
|
|
}
|
|
if r.Header.Get("X-Governance-Action") != "monitor-removal-retry" {
|
|
writeJSON(w, 403, map[string]string{"error": "write intent required"})
|
|
return
|
|
}
|
|
id := r.PathValue("id")
|
|
if id == "" || len(id) > 128 {
|
|
writeJSON(w, 400, map[string]string{"error": "invalid publication"})
|
|
return
|
|
}
|
|
store, ok := config.Store.(monitorRemovalRetrier)
|
|
if !ok {
|
|
writeJSON(w, 501, map[string]string{"error": "removal retry not configured"})
|
|
return
|
|
}
|
|
err := store.RetryMonitorRemoval(r.Context(), id, actorFor(r), time.Now().UTC())
|
|
if errors.Is(err, postgresstore.ErrPublicationStateConflict) {
|
|
writeJSON(w, 409, map[string]string{"error": "状态或治理规则已变化,不能重复提交"})
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeJSON(w, 500, map[string]string{"error": "测试库移除重试失败"})
|
|
return
|
|
}
|
|
writeJSON(w, 202, map[string]string{"status": "accepted"})
|
|
}
|
|
|
|
type monitorTaskReader interface {
|
|
ListMonitorTasks(context.Context, postgresstore.MonitorTaskFilter) (postgresstore.MonitorTaskPage, error)
|
|
}
|
|
|
|
func listMonitorTasks(w http.ResponseWriter, r *http.Request, config Config) {
|
|
if _, ok := requireSessionIdentity(w, r); !ok {
|
|
return
|
|
}
|
|
q := r.URL.Query()
|
|
f := postgresstore.MonitorTaskFilter{Search: q.Get("q"), Source: q.Get("source"), Scope: q.Get("scope"), Action: q.Get("action"), Status: q.Get("status"), Stage: q.Get("stage"), ID: q.Get("id"), Target: q.Get("target"), Since: q.Get("since"), Until: q.Get("until"), Limit: 20}
|
|
for k, p := range map[string]*int{"limit": &f.Limit, "offset": &f.Offset} {
|
|
if q.Get(k) != "" {
|
|
n, e := strconv.Atoi(q.Get(k))
|
|
if e != nil {
|
|
writeJSON(w, 400, map[string]string{"error": "invalid pagination"})
|
|
return
|
|
}
|
|
*p = n
|
|
}
|
|
}
|
|
if f.Limit < 1 || f.Limit > 100 || f.Offset < 0 || len(f.Search) > 512 || len(f.Source) > 128 || len(f.ID) > 128 {
|
|
writeJSON(w, 400, map[string]string{"error": "invalid filter"})
|
|
return
|
|
}
|
|
for _, pair := range [][2]string{{f.Scope, "|internal|external"}, {f.Action, "|add|update|remove"}, {f.Status, "|active|waiting|running|retrying|attention|completed|cancelled"}, {f.Stage, "|waiting|fetch|upload|parsing|indexing|removal|finished"}} {
|
|
if !strings.Contains("|"+pair[1]+"|", "|"+pair[0]+"|") {
|
|
writeJSON(w, 400, map[string]string{"error": "invalid filter"})
|
|
return
|
|
}
|
|
}
|
|
for _, v := range []string{f.Since, f.Until} {
|
|
if v != "" {
|
|
if _, e := time.Parse(time.RFC3339, v); e != nil {
|
|
writeJSON(w, 400, map[string]string{"error": "invalid date"})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
store, ok := config.Store.(monitorTaskReader)
|
|
if !ok {
|
|
writeJSON(w, 501, map[string]string{"error": "monitor tasks not configured"})
|
|
return
|
|
}
|
|
page, e := store.ListMonitorTasks(r.Context(), f)
|
|
if e != nil {
|
|
writeJSON(w, 500, map[string]string{"error": "无法读取文件处理状态"})
|
|
return
|
|
}
|
|
// 目标同名不罕见(两侧都叫“对内知识库”):把当前生效绑定随页返回,
|
|
// 前端才能在同一口径下标注“当前绑定/历史”,而不是拿本页条目反推全局。
|
|
internal, external := config.effectiveRoutingTargets(r.Context())
|
|
page.Bindings = postgresstore.MonitorBindings{Internal: internal, External: external}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
writeJSON(w, 200, map[string]any{"data": page})
|
|
}
|