diff --git a/frontend/api_test.go b/frontend/api_test.go index 56caa60..795bf43 100644 --- a/frontend/api_test.go +++ b/frontend/api_test.go @@ -73,13 +73,14 @@ func TestApiSummaryHandler(t *testing.T) { summary := response.Result[0].(*apiSummaryResultPair) assert.Equal(t, summary.Server, "alpha") + assert.Equal(t, len(summary.Data), 7) // Protocol list will be sorted - assert.Equal(t, summary.Data[1].Name, "device1") - assert.Equal(t, summary.Data[1].Proto, "Device") - assert.Equal(t, summary.Data[1].Table, "---") - assert.Equal(t, summary.Data[1].State, "up") - assert.Equal(t, summary.Data[1].Since, "2021-08-27") - assert.Equal(t, summary.Data[1].Info, "") + assert.Equal(t, summary.Data[0].Name, "device1") + assert.Equal(t, summary.Data[0].Proto, "Device") + assert.Equal(t, summary.Data[0].Table, "---") + assert.Equal(t, summary.Data[0].State, "up") + assert.Equal(t, summary.Data[0].Since, "2021-08-27") + assert.Equal(t, summary.Data[0].Info, "") } func TestApiSummaryHandlerError(t *testing.T) { diff --git a/frontend/render.go b/frontend/render.go index a9eb0a7..564cd95 100644 --- a/frontend/render.go +++ b/frontend/render.go @@ -35,15 +35,6 @@ var optionsMap = map[string]string{ "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 func renderPageTemplate(w http.ResponseWriter, r *http.Request, title string, content template.HTML) { path := r.URL.Path[1:] @@ -143,63 +134,23 @@ func summaryParse(data string, serverName string) (TemplateSummary, error) { // parse each line for _, line := range rows { - - // Ignore empty lines - line = strings.TrimSpace(line) - if len(line) == 0 { + row := SummaryRowDataFromLine(line) + if row == nil { continue } - // Parse a total of 6 columns from bird summary - lineSplitted := splitSummaryLine.FindStringSubmatch(line) - if lineSplitted == nil { + // Filter row name + if setting.nameFilter != "" && nameFilterRegexp.MatchString(row.Name) { continue } - var row SummaryRowData - - if len(lineSplitted) >= 2 { - 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"] + // Filter away unwanted protocol types, if setting.protocolFilter is non-empty + if len(setting.protocolFilter) > 0 && !row.ProtocolMatches(setting.protocolFilter) { + continue } // add to the result - args.Rows = append(args.Rows, row) + args.Rows = append(args.Rows, *row) } return args, nil diff --git a/frontend/render_test.go b/frontend/render_test.go index a923963..682f49e 100644 --- a/frontend/render_test.go +++ b/frontend/render_test.go @@ -8,8 +8,7 @@ import ( "testing" ) -const BirdSummaryData = `BIRD 2.0.8 ready. -Name Proto Table State Since Info +const BirdSummaryData = `Name Proto Table State Since Info static1 Static master4 up 2021-08-27 static2 Static master6 up 2021-08-27 device1 Device --- up 2021-08-27 diff --git a/frontend/template.go b/frontend/template.go index d735d46..069c7e0 100644 --- a/frontend/template.go +++ b/frontend/template.go @@ -3,11 +3,13 @@ package main import ( "embed" "html/template" - "net/url" + "net/url" + "regexp" "strings" ) // import templates and other assets +// //go:embed assets var assets embed.FS @@ -64,6 +66,47 @@ func (r SummaryRowData) NameContains(prefix string) bool { 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 { ServerName string Raw string @@ -108,7 +151,7 @@ var requiredTemplates = [...]string{ // define functions to be made available in templates var funcMap = template.FuncMap{ - "pathescape": url.PathEscape, + "pathescape": url.PathEscape, } // import templates from embedded assets diff --git a/frontend/template_test.go b/frontend/template_test.go index 87f9725..97b7e17 100644 --- a/frontend/template_test.go +++ b/frontend/template_test.go @@ -23,3 +23,56 @@ func TestSummaryRowDataNameContains(t *testing.T) { assert.Equal(t, data.NameContains("oc"), true) 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") +}