frontend: refactor summary table parsing

This commit is contained in:
Lan Tian
2025-07-01 00:55:15 -07:00
parent 7eb4d75bbf
commit ffdeeac06e
5 changed files with 114 additions and 67 deletions

View File

@@ -73,13 +73,14 @@ func TestApiSummaryHandler(t *testing.T) {
summary := response.Result[0].(*apiSummaryResultPair) summary := response.Result[0].(*apiSummaryResultPair)
assert.Equal(t, summary.Server, "alpha") assert.Equal(t, summary.Server, "alpha")
assert.Equal(t, len(summary.Data), 7)
// Protocol list will be sorted // Protocol list will be sorted
assert.Equal(t, summary.Data[1].Name, "device1") assert.Equal(t, summary.Data[0].Name, "device1")
assert.Equal(t, summary.Data[1].Proto, "Device") assert.Equal(t, summary.Data[0].Proto, "Device")
assert.Equal(t, summary.Data[1].Table, "---") assert.Equal(t, summary.Data[0].Table, "---")
assert.Equal(t, summary.Data[1].State, "up") assert.Equal(t, summary.Data[0].State, "up")
assert.Equal(t, summary.Data[1].Since, "2021-08-27") assert.Equal(t, summary.Data[0].Since, "2021-08-27")
assert.Equal(t, summary.Data[1].Info, "") assert.Equal(t, summary.Data[0].Info, "")
} }
func TestApiSummaryHandlerError(t *testing.T) { func TestApiSummaryHandlerError(t *testing.T) {

View File

@@ -35,15 +35,6 @@ var optionsMap = map[string]string{
"traceroute": "traceroute ...", "traceroute": "traceroute ...",
} }
// pre-compiled regexp and constant statemap for summary rendering
var splitSummaryLine = regexp.MustCompile(`(\w+)(\s+)(\w+)(\s+)([\w-]+)(\s+)(\w+)(\s+)([0-9\-\. :]+)(.*)`)
var summaryStateMap = map[string]string{
"up": "success",
"down": "secondary",
"start": "danger",
"passive": "info",
}
// render the page template // render the page template
func renderPageTemplate(w http.ResponseWriter, r *http.Request, title string, content template.HTML) { func renderPageTemplate(w http.ResponseWriter, r *http.Request, title string, content template.HTML) {
path := r.URL.Path[1:] path := r.URL.Path[1:]
@@ -143,63 +134,23 @@ func summaryParse(data string, serverName string) (TemplateSummary, error) {
// parse each line // parse each line
for _, line := range rows { for _, line := range rows {
row := SummaryRowDataFromLine(line)
// Ignore empty lines if row == nil {
line = strings.TrimSpace(line)
if len(line) == 0 {
continue continue
} }
// Parse a total of 6 columns from bird summary // Filter row name
lineSplitted := splitSummaryLine.FindStringSubmatch(line) if setting.nameFilter != "" && nameFilterRegexp.MatchString(row.Name) {
if lineSplitted == nil {
continue continue
} }
var row SummaryRowData // Filter away unwanted protocol types, if setting.protocolFilter is non-empty
if len(setting.protocolFilter) > 0 && !row.ProtocolMatches(setting.protocolFilter) {
if len(lineSplitted) >= 2 { continue
row.Name = strings.TrimSpace(lineSplitted[1])
if setting.nameFilter != "" && nameFilterRegexp.MatchString(row.Name) {
continue
}
}
if len(lineSplitted) >= 4 {
row.Proto = strings.TrimSpace(lineSplitted[3])
// Filter away unwanted protocol types, if setting.protocolFilter is non-empty
found := false
for _, protocol := range setting.protocolFilter {
if strings.EqualFold(row.Proto, protocol) {
found = true
break
}
}
if len(setting.protocolFilter) > 0 && !found {
continue
}
}
if len(lineSplitted) >= 6 {
row.Table = strings.TrimSpace(lineSplitted[5])
}
if len(lineSplitted) >= 8 {
row.State = strings.TrimSpace(lineSplitted[7])
row.MappedState = summaryStateMap[row.State]
}
if len(lineSplitted) >= 10 {
row.Since = strings.TrimSpace(lineSplitted[9])
}
if len(lineSplitted) >= 11 {
row.Info = strings.TrimSpace(lineSplitted[10])
}
// Dynamic BGP session, show without any color
if strings.Contains(row.Info, "Passive") {
row.MappedState = summaryStateMap["passive"]
} }
// add to the result // add to the result
args.Rows = append(args.Rows, row) args.Rows = append(args.Rows, *row)
} }
return args, nil return args, nil

View File

@@ -8,8 +8,7 @@ import (
"testing" "testing"
) )
const BirdSummaryData = `BIRD 2.0.8 ready. const BirdSummaryData = `Name Proto Table State Since Info
Name Proto Table State Since Info
static1 Static master4 up 2021-08-27 static1 Static master4 up 2021-08-27
static2 Static master6 up 2021-08-27 static2 Static master6 up 2021-08-27
device1 Device --- up 2021-08-27 device1 Device --- up 2021-08-27

View File

@@ -3,11 +3,13 @@ package main
import ( import (
"embed" "embed"
"html/template" "html/template"
"net/url" "net/url"
"regexp"
"strings" "strings"
) )
// import templates and other assets // import templates and other assets
//
//go:embed assets //go:embed assets
var assets embed.FS var assets embed.FS
@@ -64,6 +66,47 @@ func (r SummaryRowData) NameContains(prefix string) bool {
return strings.Contains(r.Name, prefix) return strings.Contains(r.Name, prefix)
} }
func (r SummaryRowData) ProtocolMatches(protocols []string) bool {
for _, protocol := range protocols {
if strings.EqualFold(r.Proto, protocol) {
return true
}
}
return false
}
// pre-compiled regexp and constant statemap for summary rendering
var splitSummaryLine = regexp.MustCompile(`(\w+)\s+(\w+)\s+([\w-]+)\s+(\w+)\s+([0-9\-\. :]+)(.*)`)
var summaryStateMap = map[string]string{
"up": "success",
"down": "secondary",
"start": "danger",
"passive": "info",
}
func SummaryRowDataFromLine(line string) *SummaryRowData {
lineSplitted := splitSummaryLine.FindStringSubmatch(line)
if lineSplitted == nil {
return nil
}
var row SummaryRowData
row.Name = strings.TrimSpace(lineSplitted[1])
row.Proto = strings.TrimSpace(lineSplitted[2])
row.Table = strings.TrimSpace(lineSplitted[3])
row.State = strings.TrimSpace(lineSplitted[4])
row.Since = strings.TrimSpace(lineSplitted[5])
row.Info = strings.TrimSpace(lineSplitted[6])
if strings.Contains(row.Info, "Passive") {
row.MappedState = summaryStateMap["passive"]
} else {
row.MappedState = summaryStateMap[row.State]
}
return &row
}
type TemplateSummary struct { type TemplateSummary struct {
ServerName string ServerName string
Raw string Raw string
@@ -108,7 +151,7 @@ var requiredTemplates = [...]string{
// define functions to be made available in templates // define functions to be made available in templates
var funcMap = template.FuncMap{ var funcMap = template.FuncMap{
"pathescape": url.PathEscape, "pathescape": url.PathEscape,
} }
// import templates from embedded assets // import templates from embedded assets

View File

@@ -23,3 +23,56 @@ func TestSummaryRowDataNameContains(t *testing.T) {
assert.Equal(t, data.NameContains("oc"), true) assert.Equal(t, data.NameContains("oc"), true)
assert.Equal(t, data.NameContains("no"), false) assert.Equal(t, data.NameContains("no"), false)
} }
func TestSummaryRowDataFromLine(t *testing.T) {
data := SummaryRowDataFromLine("sys_device Device --- up 2025-06-27 21:23:08")
assert.Equal(t, data.Name, "sys_device")
assert.Equal(t, data.Proto, "Device")
assert.Equal(t, data.Table, "---")
assert.Equal(t, data.State, "up")
assert.Equal(t, data.Since, "2025-06-27 21:23:08")
}
func TestSummaryRowDataFromLineNumeric(t *testing.T) {
data := SummaryRowDataFromLine("12345 Device --- up 2025-06-27 21:23:08")
assert.Equal(t, data.Name, "12345")
assert.Equal(t, data.Proto, "Device")
assert.Equal(t, data.Table, "---")
assert.Equal(t, data.State, "up")
assert.Equal(t, data.Since, "2025-06-27 21:23:08")
}
func TestSummaryRowDataFromLinePipe(t *testing.T) {
data := SummaryRowDataFromLine("pipe Pipe --- up 2025-06-27 21:23:08 master4 <=> pipe_v4")
assert.Equal(t, data.Name, "pipe")
assert.Equal(t, data.Proto, "Pipe")
assert.Equal(t, data.Table, "---")
assert.Equal(t, data.State, "up")
assert.Equal(t, data.Since, "2025-06-27 21:23:08")
assert.Equal(t, data.Info, "master4 <=> pipe_v4")
}
func TestSummaryRowDataFromLineBGP(t *testing.T) {
data := SummaryRowDataFromLine("bgp BGP --- up 2025-06-30 20:45:33 Established")
assert.Equal(t, data.Name, "bgp")
assert.Equal(t, data.Proto, "BGP")
assert.Equal(t, data.Table, "---")
assert.Equal(t, data.State, "up")
assert.Equal(t, data.Since, "2025-06-30 20:45:33")
assert.Equal(t, data.Info, "Established")
}
func TestSummaryRowDataFromLineBGPPassive(t *testing.T) {
data := SummaryRowDataFromLine("passive BGP --- start 2025-06-27 21:23:08 Passive")
assert.Equal(t, data.Name, "passive")
assert.Equal(t, data.Proto, "BGP")
assert.Equal(t, data.Table, "---")
assert.Equal(t, data.State, "start")
assert.Equal(t, data.Since, "2025-06-27 21:23:08")
assert.Equal(t, data.Info, "Passive")
}