| 177 | } |
| 178 | |
| 179 | func (m scanModel) renderSNIPanel() string { |
| 180 | title := sectionStyle.Render("SNI probes") |
| 181 | if m.sniTotal == 0 { |
| 182 | return title + "\n" + mutedStyle.Render("press s or a to start") |
| 183 | } |
| 184 | pct := 0.0 |
| 185 | if m.sniTotal > 0 { |
| 186 | pct = float64(m.sniDone) / float64(m.sniTotal) |
| 187 | } |
| 188 | bar := m.sniBar.ViewAs(pct) |
| 189 | counts := map[scanner.Outcome]int{} |
| 190 | for _, r := range m.snis { |
| 191 | counts[r.Outcome]++ |
| 192 | } |
| 193 | summary := fmt.Sprintf("%d/%d ok=%s reset=%s timeout=%s err=%s", |
| 194 | m.sniDone, m.sniTotal, |
| 195 | okStyle.Render(fmt.Sprintf("%d", counts[scanner.OutcomeOK])), |
| 196 | errStyle.Render(fmt.Sprintf("%d", counts[scanner.OutcomeDPIReset])), |
| 197 | warnStyle.Render(fmt.Sprintf("%d", counts[scanner.OutcomeTimeout])), |
| 198 | mutedStyle.Render(fmt.Sprintf("%d", counts[scanner.OutcomeTLSError]+counts[scanner.OutcomeTCPRefused])), |
| 199 | ) |
| 200 | |
| 201 | // Top-10 working SNIs. |
| 202 | top := scanner.RankSNIs(m.snis) |
| 203 | if len(top) > 10 { |
| 204 | top = top[:10] |
| 205 | } |
| 206 | var lines []string |
| 207 | for i, r := range top { |
| 208 | lines = append(lines, fmt.Sprintf("%2d. %-35s %s", |
| 209 | i+1, truncate(r.SNI, 35), r.Handshake.Round(time.Millisecond))) |
| 210 | } |
| 211 | if len(lines) == 0 { |
| 212 | lines = []string{mutedStyle.Render("no successful handshakes yet")} |
| 213 | } |
| 214 | return strings.Join([]string{title, bar, summary, "", strings.Join(lines, "\n")}, "\n") |
| 215 | } |
| 216 | |
| 217 | func (m scanModel) renderIPPanel() string { |
| 218 | title := sectionStyle.Render("IP probes") |