fix(diagnosis): prevent progress stream blocking and refine tunnel type chips (#230)

* fix(diagnosis): avoid result channel deadlock in progress stream

Close the diagnosis result channel asynchronously after workers complete so progress can stream without blocking, and improve tunnel card type chip contrast for clearer protocol distinction.

* feat(diagnosis): stream pending items and render in-progress states

Pre-populate diagnosis stream with pending targets so tunnel and forward dialogs can show per-item diagnosing status immediately. Update result typing and UI states to distinguish in-progress, success, and failure rows/cards consistently.
This commit is contained in:
sagit
2026-03-01 14:49:38 +08:00
committed by GitHub
parent 9ffeb83753
commit cf51b305b0
6 changed files with 237 additions and 93 deletions
@@ -58,6 +58,55 @@ type diagnosisProgress struct {
type diagnosisItemEmitter func(index int, item map[string]interface{}, progress diagnosisProgress)
func (h *Handler) buildDiagnosisStreamStartItems(workItems []diagnosisWorkItem) []map[string]interface{} {
if len(workItems) == 0 {
return []map[string]interface{}{}
}
nodeCache := map[int64]*nodeRecord{}
items := make([]map[string]interface{}, 0, len(workItems))
for _, workItem := range workItems {
targetIP := strings.TrimSpace(workItem.targetIP)
targetPort := workItem.targetPort
if workItem.hasChainHop {
fromNode, _ := h.cachedNode(nodeCache, workItem.fromNodeID)
targetNode, err := h.cachedNode(nodeCache, workItem.toNode.NodeID)
if err == nil {
resolvedIP, resolvedPort, resolveErr := resolveChainProbeTarget(fromNode, targetNode, workItem.toNode.Port, workItem.ipPreference)
if resolveErr == nil {
targetIP = resolvedIP
targetPort = resolvedPort
}
}
}
if targetPort <= 0 {
targetPort = 443
}
nodeName := fmt.Sprintf("node_%d", workItem.fromNodeID)
if node, err := h.cachedNode(nodeCache, workItem.fromNodeID); err == nil && strings.TrimSpace(node.Name) != "" {
nodeName = node.Name
}
item := map[string]interface{}{
"success": false,
"diagnosing": true,
"description": workItem.description,
"nodeName": nodeName,
"nodeId": strconv.FormatInt(workItem.fromNodeID, 10),
"targetIp": targetIP,
"targetPort": targetPort,
"message": "诊断中...",
}
for key, value := range workItem.metadata {
item[key] = value
}
items = append(items, item)
}
return items
}
const diagnosisMaxConcurrency = 8
const (
@@ -923,8 +972,10 @@ enqueueLoop:
}
}
close(jobs)
wg.Wait()
close(resultCh)
go func() {
wg.Wait()
close(resultCh)
}()
progress := diagnosisProgress{Total: len(workItems)}
for result := range resultCh {
@@ -81,6 +81,7 @@ func (h *Handler) streamDiagnosisRuntime(ctx context.Context, cancel context.Can
payload := map[string]interface{}{
"total": len(workItems),
"timestamp": time.Now().UnixMilli(),
"items": h.buildDiagnosisStreamStartItems(workItems),
}
for key, value := range startPayload {
payload[key] = value