1
mirror of https://github.com/xddxdd/bird-lg-go synced 2025-10-21 01:42:15 +02:00

34 Commits

Author SHA1 Message Date
Lan Tian
e9750a8278 release: v1.3.10 2025-07-02 19:58:51 -07:00
Lan Tian
d40dd3a4d3 frontend: handle protocol names with dash 2025-07-01 17:45:12 -07:00
Lan Tian
ffdeeac06e frontend: refactor summary table parsing 2025-07-01 00:55:15 -07:00
Lan Tian
7eb4d75bbf frontend: handle protocol names starting with number 2025-07-01 00:04:03 -07:00
Lan Tian
6e5e190d32 frontend: try fix TestWhoisConnectionError on darwin 2025-06-08 22:01:39 -07:00
Lan Tian
1b2573d87c release: v1.3.9 2025-04-23 21:13:36 -07:00
Lan Tian
0d5337508b frontend: update API test to adapt to changes in #118 2025-04-23 21:12:40 -07:00
Mxmilu666
b9094d3d6c fix(template): typo 2025-04-23 21:08:40 -07:00
Mxmilu666
ec7f348418 feat(api): return data from available services even if one backend fails 2025-04-23 21:08:40 -07:00
dependabot[bot]
a632739443 Merge pull request #117 from xddxdd/dependabot/go_modules/proxy/github.com/magiconair/properties-1.8.10 2025-04-10 00:55:19 +00:00
dependabot[bot]
a9e278357a build(deps): bump github.com/magiconair/properties in /proxy
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.9 to 1.8.10.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.9...v1.8.10)

---
updated-dependencies:
- dependency-name: github.com/magiconair/properties
  dependency-version: 1.8.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 00:53:10 +00:00
dependabot[bot]
e4c00c897f Merge pull request #116 from xddxdd/dependabot/go_modules/frontend/github.com/magiconair/properties-1.8.10 2025-04-10 00:22:47 +00:00
dependabot[bot]
4df3918b35 build(deps): bump github.com/magiconair/properties in /frontend
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.9 to 1.8.10.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.9...v1.8.10)

---
updated-dependencies:
- dependency-name: github.com/magiconair/properties
  dependency-version: 1.8.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 00:20:55 +00:00
dependabot[bot]
45dc24470d Merge pull request #115 from xddxdd/dependabot/go_modules/frontend/github.com/jarcoal/httpmock-1.4.0 2025-04-07 00:54:10 +00:00
dependabot[bot]
55ea5c3b28 build(deps): bump github.com/jarcoal/httpmock in /frontend
Bumps [github.com/jarcoal/httpmock](https://github.com/jarcoal/httpmock) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/jarcoal/httpmock/releases)
- [Commits](https://github.com/jarcoal/httpmock/compare/v1.3.1...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/jarcoal/httpmock
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 00:52:24 +00:00
dependabot[bot]
7eb44c3828 Merge pull request #110 from xddxdd/dependabot/go_modules/proxy/github.com/spf13/pflag-1.0.6 2025-01-30 00:16:58 +00:00
dependabot[bot]
124fdedbda build(deps): bump github.com/spf13/pflag from 1.0.5 to 1.0.6 in /proxy
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.5...v1.0.6)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-30 00:14:41 +00:00
dependabot[bot]
e6a98358b5 Merge pull request #109 from xddxdd/dependabot/go_modules/frontend/github.com/spf13/pflag-1.0.6 2025-01-30 00:04:45 +00:00
dependabot[bot]
761eb2160a build(deps): bump github.com/spf13/pflag in /frontend
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.5...v1.0.6)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-30 00:02:32 +00:00
dependabot[bot]
cc2a146a88 Merge pull request #108 from xddxdd/dependabot/go_modules/frontend/github.com/magiconair/properties-1.8.9 2024-12-09 00:51:25 +00:00
dependabot[bot]
3db9454350 build(deps): bump github.com/magiconair/properties in /frontend
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.7 to 1.8.9.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.7...v1.8.9)

---
updated-dependencies:
- dependency-name: github.com/magiconair/properties
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-09 00:50:06 +00:00
dependabot[bot]
c30bed112c Merge pull request #107 from xddxdd/dependabot/go_modules/proxy/github.com/magiconair/properties-1.8.9 2024-12-09 00:18:14 +00:00
dependabot[bot]
af5ab3c78f build(deps): bump github.com/magiconair/properties in /proxy
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.7 to 1.8.9.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.7...v1.8.9)

---
updated-dependencies:
- dependency-name: github.com/magiconair/properties
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-09 00:16:23 +00:00
Marc 'risson' Schmitt
0fdde8afc7 frontend: allow webserver to trust proxy headers (#106)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-07-14 15:50:18 -07:00
Lan Tian
39a129db9d release: v1.3.8 2024-07-01 21:31:55 -07:00
Lan Tian
0dd1c07b66 frontend: disable escaping of special HTML chars for BGPMap graph 2024-07-01 21:17:43 -07:00
Lan Tian
f0f072c4a6 frontend: handle UTF-8 characters in GraphViz graphs 2024-06-30 13:04:15 -07:00
dependabot[bot]
657565857b Merge pull request #104 from xddxdd/dependabot/go_modules/proxy/github.com/spf13/viper-1.19.0 2024-06-03 00:40:25 +00:00
dependabot[bot]
7ac2158e70 build(deps): bump github.com/spf13/viper from 1.18.2 to 1.19.0 in /proxy
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.18.2 to 1.19.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.18.2...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 00:38:37 +00:00
dependabot[bot]
5c433bc27a Merge pull request #103 from xddxdd/dependabot/go_modules/frontend/github.com/spf13/viper-1.19.0 2024-06-03 00:35:26 +00:00
dependabot[bot]
1b0b923da9 build(deps): bump github.com/spf13/viper in /frontend
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.18.2 to 1.19.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.18.2...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 00:32:44 +00:00
Lan Tian
01438edaef release: v1.3.7.1 2024-03-12 18:05:50 -07:00
Lan Tian
90f36610dc ci: fix failing whois test 2024-03-12 18:03:23 -07:00
Lan Tian
6174208d07 ci: fix typo in release workflow 2024-03-12 17:56:52 -07:00
21 changed files with 976 additions and 157 deletions

View File

@@ -47,7 +47,7 @@ jobs:
- name: Test whois binary in frontend image - name: Test whois binary in frontend image
run: | run: |
docker build -t local/frontend frontend/ docker build -t local/frontend frontend/
docker run --rm --net host --entrypoint whois local/frontend github.com || exit 1 docker run --rm --net host --entrypoint whois local/frontend -I github.com || exit 1
docker run --rm --net host --entrypoint whois local/frontend -h whois.ripe.net github.com || exit 1 docker run --rm --net host --entrypoint whois local/frontend -h whois.ripe.net github.com || exit 1
docker run --rm --net host --entrypoint whois local/frontend -h whois.ripe.net:43 github.com || exit 1 docker run --rm --net host --entrypoint whois local/frontend -h whois.ripe.net:43 github.com || exit 1

View File

@@ -67,7 +67,7 @@ jobs:
with: with:
context: '{{defaultContext}}:frontend' context: '{{defaultContext}}:frontend'
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
push: true{context}/Dockerfile push: true
tags: | tags: |
xddxdd/bird-lg-go:latest xddxdd/bird-lg-go:latest
xddxdd/bird-lg-go:${{ github.event.release.tag_name }} xddxdd/bird-lg-go:${{ github.event.release.tag_name }}

View File

@@ -1 +1 @@
v1.3.7 v1.3.10

View File

@@ -20,6 +20,7 @@ type apiGenericResultPair struct {
type apiSummaryResultPair struct { type apiSummaryResultPair struct {
Server string `json:"server"` Server string `json:"server"`
Data []SummaryRowData `json:"data"` Data []SummaryRowData `json:"data"`
Error string `json:"error,omitempty"`
} }
type apiResponse struct { type apiResponse struct {
@@ -70,9 +71,12 @@ func apiSummaryHandler(request apiRequest) apiResponse {
for i, result := range results { for i, result := range results {
parsedSummary, err := summaryParse(result, request.Servers[i]) parsedSummary, err := summaryParse(result, request.Servers[i])
if err != nil { if err != nil {
return apiResponse{ response.Result = append(response.Result, &apiSummaryResultPair{
Error: err.Error(), Server: request.Servers[i],
} Data: []SummaryRowData{},
Error: err.Error(),
})
continue
} }
response.Result = append(response.Result, &apiSummaryResultPair{ response.Result = append(response.Result, &apiSummaryResultPair{

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) {
@@ -100,7 +101,10 @@ func TestApiSummaryHandlerError(t *testing.T) {
} }
response := apiSummaryHandler(request) response := apiSummaryHandler(request)
assert.Equal(t, response.Error, "Mock backend error") assert.Equal(t, response.Error, "")
summary := response.Result[0].(*apiSummaryResultPair)
assert.Equal(t, summary.Error, "Mock backend error")
} }
func TestApiWhoisHandler(t *testing.T) { func TestApiWhoisHandler(t *testing.T) {

View File

@@ -5,8 +5,19 @@
<script src="/static/jsdelivr/npm/viz.js@2.1.2/viz.min.js" crossorigin="anonymous"></script> <script src="/static/jsdelivr/npm/viz.js@2.1.2/viz.min.js" crossorigin="anonymous"></script>
<script src="/static/jsdelivr/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script> <script src="/static/jsdelivr/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script>
<script> <script>
function decodeBase64(base64) {
const text = atob(base64);
const length = text.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = text.charCodeAt(i);
}
const decoder = new TextDecoder();
return decoder.decode(bytes);
}
var viz = new Viz(); var viz = new Viz();
viz.renderSVGElement(atob({{ .Result }})) viz.renderSVGElement(decodeBase64({{ .Result }}))
.then(element => { .then(element => {
document.getElementById("bgpmap").appendChild(element); document.getElementById("bgpmap").appendChild(element);
}) })

View File

@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<title>{{ html .Title }}</title> <title>{{ html .Title }}</title>
<link rel="stylesheet" href="/static/jsdelivr/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css" integrity="sha256-VoFZSlmyTXsegReQCNmbXrS4hBBUl/cexZvPmPWoJsY=" crossorigin="anonymous"> <link rel="stylesheet" href="/static/jsdelivr/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css" crossorigin="anonymous">
<style> <style>
.navbar-nav { .navbar-nav {
flex-wrap: wrap; flex-wrap: wrap;
@@ -87,8 +87,8 @@
{{ .Content }} {{ .Content }}
</div> </div>
<script src="/static/jsdelivr/npm/jquery@3.5.1/dist/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> <script src="/static/jsdelivr/npm/jquery@3.5.1/dist/jquery.min.js" crossorigin="anonymous"></script>
<script src="/static/jsdelivr/npm/bootstrap@4.5.1/dist/js/bootstrap.min.js" integrity="sha256-0IiaoZCI++9oAAvmCb5Y0r93XkuhvJpRalZLffQXLok=" crossorigin="anonymous"></script> <script src="/static/jsdelivr/npm/bootstrap@4.5.1/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script src="/static/sortTable.js"></script> <script src="/static/sortTable.js"></script>
<script> <script>

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
@@ -69,11 +70,15 @@ func (graph *RouteGraph) attrsToString(attrs RouteAttrs) string {
} }
func (graph *RouteGraph) escape(s string) string { func (graph *RouteGraph) escape(s string) string {
result, err := json.Marshal(s) buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
err := encoder.Encode(s)
if err != nil { if err != nil {
return err.Error() return err.Error()
} else { } else {
return string(result) return string(buffer.Bytes())
} }
} }

View File

@@ -33,7 +33,7 @@ func TestBirdRouteToGraphvizXSS(t *testing.T) {
fakeResult, fakeResult,
}, fakeResult) }, fakeResult)
if strings.Contains(result, "<script>") { if strings.Contains(result, fakeResult) {
t.Errorf("XSS injection succeeded: %s", result) t.Errorf("XSS injection succeeded: %s", result)
} }
} }

View File

@@ -5,18 +5,18 @@ go 1.17
require ( require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/gorilla/handlers v1.5.2 github.com/gorilla/handlers v1.5.2
github.com/jarcoal/httpmock v1.3.1 github.com/jarcoal/httpmock v1.4.0
github.com/magiconair/properties v1.8.7 github.com/magiconair/properties v1.8.10
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.6
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.19.0
) )
require ( require (
github.com/felixge/httpsnoop v1.0.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
@@ -26,7 +26,7 @@ require (
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@ type settingType struct {
nameFilter string nameFilter string
timeOut int timeOut int
connectionTimeOut int connectionTimeOut int
trustProxyHeaders bool
} }
var setting settingType var setting settingType

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

@@ -27,6 +27,7 @@ type viperSettingType struct {
NameFilter string `mapstructure:"name_filter"` NameFilter string `mapstructure:"name_filter"`
TimeOut int `mapstructure:"timeout"` TimeOut int `mapstructure:"timeout"`
ConnectionTimeOut int `mapstructure:"connection_timeout"` ConnectionTimeOut int `mapstructure:"connection_timeout"`
TrustProxyHeaders bool `mapstructure:"trust_proxy_headers"`
} }
// Parse settings with viper, and convert to legacy setting format // Parse settings with viper, and convert to legacy setting format
@@ -94,6 +95,9 @@ func parseSettings() {
pflag.Int("connection-time-out", 5, "time before backend TCP connection times out, in seconds; defaults to 5 if not set") pflag.Int("connection-time-out", 5, "time before backend TCP connection times out, in seconds; defaults to 5 if not set")
viper.BindPFlag("connection_timeout", pflag.Lookup("connection-time-out")) viper.BindPFlag("connection_timeout", pflag.Lookup("connection-time-out"))
pflag.Bool("trust-proxy-headers", false, "Trust X-Forwared-For, X-Real-IP, X-Forwarded-Proto, X-Forwarded-Scheme and X-Forwarded-Host sent by the client")
viper.BindPFlag("trust_proxy_headers", pflag.Lookup("trust-proxy-headers"))
pflag.Parse() pflag.Parse()
if err := viper.ReadInConfig(); err != nil { if err := viper.ReadInConfig(); err != nil {
@@ -144,6 +148,7 @@ func parseSettings() {
setting.nameFilter = viperSettings.NameFilter setting.nameFilter = viperSettings.NameFilter
setting.timeOut = viperSettings.TimeOut setting.timeOut = viperSettings.TimeOut
setting.connectionTimeOut = viperSettings.ConnectionTimeOut setting.connectionTimeOut = viperSettings.ConnectionTimeOut
setting.trustProxyHeaders = viperSettings.TrustProxyHeaders
fmt.Printf("%#v\n", setting) fmt.Printf("%#v\n", setting)
} }

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,67 @@ 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")
}
func TestSummaryRowDataFromLineWithDash(t *testing.T) {
data := SummaryRowDataFromLine("ibgp_test-01 BGP --- up 07:16:51.656 Established")
assert.Equal(t, data.Name, "ibgp_test-01")
assert.Equal(t, data.Proto, "BGP")
assert.Equal(t, data.Table, "---")
assert.Equal(t, data.State, "up")
assert.Equal(t, data.Since, "07:16:51.656")
assert.Equal(t, data.Info, "Established")
}

View File

@@ -18,13 +18,13 @@ import (
var primitiveMap = map[string]string{ var primitiveMap = map[string]string{
"summary": "show protocols", "summary": "show protocols",
"detail": "show protocols all %s", "detail": "show protocols all \"%s\"",
"route_from_protocol": "show route protocol %s", "route_from_protocol": "show route protocol \"%s\"",
"route_from_protocol_all": "show route protocol %s all", "route_from_protocol_all": "show route protocol \"%s\" all",
"route_from_protocol_primary": "show route protocol %s primary", "route_from_protocol_primary": "show route protocol \"%s\" primary",
"route_from_protocol_all_primary": "show route protocol %s all primary", "route_from_protocol_all_primary": "show route protocol \"%s\" all primary",
"route_filtered_from_protocol": "show route filtered protocol %s", "route_filtered_from_protocol": "show route filtered protocol \"%s\"",
"route_filtered_from_protocol_all": "show route filtered protocol %s all", "route_filtered_from_protocol_all": "show route filtered protocol \"%s\" all",
"route_from_origin": "show route where bgp_path.last = %s", "route_from_origin": "show route where bgp_path.last = %s",
"route_from_origin_all": "show route where bgp_path.last = %s all", "route_from_origin_all": "show route where bgp_path.last = %s all",
"route_from_origin_primary": "show route where bgp_path.last = %s primary", "route_from_origin_primary": "show route where bgp_path.last = %s primary",
@@ -75,7 +75,6 @@ func webHandlerWhois(w http.ResponseWriter, r *http.Request) {
// serve up results from bird // serve up results from bird
func webBackendCommunicator(endpoint string, command string) func(w http.ResponseWriter, r *http.Request) { func webBackendCommunicator(endpoint string, command string) func(w http.ResponseWriter, r *http.Request) {
backendCommandPrimitive, commandPresent := primitiveMap[command] backendCommandPrimitive, commandPresent := primitiveMap[command]
if !commandPresent { if !commandPresent {
panic("invalid command: " + command) panic("invalid command: " + command)
@@ -195,7 +194,6 @@ func webHandlerBGPMap(endpoint string, command string) func(w http.ResponseWrite
// set up routing paths and start webserver // set up routing paths and start webserver
func webServerStart(l net.Listener) { func webServerStart(l net.Listener) {
// redirect main page to all server summary // redirect main page to all server summary
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/summary/"+url.PathEscape(strings.Join(setting.servers, "+")), 302) http.Redirect(w, r, "/summary/"+url.PathEscape(strings.Join(setting.servers, "+")), 302)
@@ -239,5 +237,11 @@ func webServerStart(l net.Listener) {
http.HandleFunc("/telegram/", webHandlerTelegramBot) http.HandleFunc("/telegram/", webHandlerTelegramBot)
// Start HTTP server // Start HTTP server
http.Serve(l, handlers.LoggingHandler(os.Stdout, http.DefaultServeMux)) var handler http.Handler
handler = http.DefaultServeMux
if setting.trustProxyHeaders {
handler = handlers.ProxyHeaders(handler)
}
handler = handlers.LoggingHandler(os.Stdout, handler)
http.Serve(l, handler)
} }

View File

@@ -88,7 +88,7 @@ func TestWhoisWithoutServer(t *testing.T) {
} }
func TestWhoisConnectionError(t *testing.T) { func TestWhoisConnectionError(t *testing.T) {
setting.whoisServer = "127.0.0.1:0" setting.whoisServer = "127.0.0.1:1"
result := whois("AS6939") result := whois("AS6939")
if !strings.Contains(result, "connect: connection refused") { if !strings.Contains(result, "connect: connection refused") {
t.Errorf("Whois AS6939 without server produced output, got %s", result) t.Errorf("Whois AS6939 without server produced output, got %s", result)

View File

@@ -5,17 +5,17 @@ go 1.17
require ( require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/gorilla/handlers v1.5.2 github.com/gorilla/handlers v1.5.2
github.com/magiconair/properties v1.8.7 github.com/magiconair/properties v1.8.10
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.6
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.19.0
) )
require ( require (
github.com/felixge/httpsnoop v1.0.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
@@ -25,7 +25,7 @@ require (
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

File diff suppressed because it is too large Load Diff