parent
31267858a6
commit
80d42433d8
|
@ -25,7 +25,7 @@ func TestReadConfig(t *testing.T) {
|
||||||
assert.Contains(config.Respondd.Sites["ffhb"].Domains, "city")
|
assert.Contains(config.Respondd.Sites["ffhb"].Domains, "city")
|
||||||
|
|
||||||
// Test output plugins
|
// Test output plugins
|
||||||
assert.Len(config.Nodes.Output, 3)
|
assert.Len(config.Nodes.Output, 4)
|
||||||
outputs := config.Nodes.Output["meshviewer"].([]interface{})
|
outputs := config.Nodes.Output["meshviewer"].([]interface{})
|
||||||
assert.Len(outputs, 1)
|
assert.Len(outputs, 1)
|
||||||
meshviewer := outputs[0]
|
meshviewer := outputs[0]
|
||||||
|
|
|
@ -93,6 +93,11 @@ offline_after = "10m"
|
||||||
#longitude_max = 39.72
|
#longitude_max = 39.72
|
||||||
|
|
||||||
|
|
||||||
|
# outputs all nodes as points into nodes.geojson
|
||||||
|
[[nodes.output.geojson]]
|
||||||
|
enable = true
|
||||||
|
path = "/var/www/html/meshviewer/data/nodes.geojson"
|
||||||
|
|
||||||
# definition for the new more compressed meshviewer.json
|
# definition for the new more compressed meshviewer.json
|
||||||
[[nodes.output.meshviewer-ffrgb]]
|
[[nodes.output.meshviewer-ffrgb]]
|
||||||
enable = true
|
enable = true
|
||||||
|
|
|
@ -387,6 +387,31 @@ longitude_max = 39.72
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [[nodes.output.geojson]]
|
||||||
|
{% method %}
|
||||||
|
The geojson output produces a geojson file which contains the location data of all monitored nodes to be used to visualize the location of the nodes.
|
||||||
|
It is optimized to be used with [UMap](https://github.com/umap-project/umap) but should work with other tools as well.
|
||||||
|
Here is a public demo provided by Freifunk Muenchen: http://u.osmfr.org/m/328494/
|
||||||
|
{% sample lang="toml" %}
|
||||||
|
```toml
|
||||||
|
[[nodes.output.geojson]]
|
||||||
|
enable = true
|
||||||
|
path = "/var/www/html/meshviewer/data/nodes.geojson"
|
||||||
|
```
|
||||||
|
{% endmethod %}
|
||||||
|
|
||||||
|
|
||||||
|
### path
|
||||||
|
{% method %}
|
||||||
|
The path, where to store nodes.geojson
|
||||||
|
{% sample lang="toml" %}
|
||||||
|
```toml
|
||||||
|
path = "/var/www/html/meshviewer/data/nodes.geojson"
|
||||||
|
```
|
||||||
|
{% endmethod %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [[nodes.output.meshviewer-ffrgb]]
|
## [[nodes.output.meshviewer-ffrgb]]
|
||||||
{% method %}
|
{% method %}
|
||||||
The new json file format for the [meshviewer](https://github.com/ffrgb/meshviewer) developed in Regensburg.
|
The new json file format for the [meshviewer](https://github.com/ffrgb/meshviewer) developed in Regensburg.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package all
|
package all
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/FreifunkBremen/yanic/output/geojson"
|
||||||
_ "github.com/FreifunkBremen/yanic/output/meshviewer"
|
_ "github.com/FreifunkBremen/yanic/output/meshviewer"
|
||||||
_ "github.com/FreifunkBremen/yanic/output/meshviewer-ffrgb"
|
_ "github.com/FreifunkBremen/yanic/output/meshviewer-ffrgb"
|
||||||
_ "github.com/FreifunkBremen/yanic/output/nodelist"
|
_ "github.com/FreifunkBremen/yanic/output/nodelist"
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
package geojson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
"github.com/paulmach/go.geojson"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
POINT_UMAP_CLASS = "Circle"
|
||||||
|
POINT_UMAP_ONLINE_COLOR = "Green"
|
||||||
|
POINT_UMAP_OFFLINE_COLOR = "Red"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newNodePoint(n *runtime.Node) (point *geojson.Feature) {
|
||||||
|
nodeinfo := n.Nodeinfo
|
||||||
|
location := nodeinfo.Location
|
||||||
|
point = geojson.NewPointFeature([]float64{
|
||||||
|
location.Longitude,
|
||||||
|
location.Latitude,
|
||||||
|
})
|
||||||
|
point.Properties["id"] = nodeinfo.NodeID
|
||||||
|
point.Properties["name"] = nodeinfo.Hostname
|
||||||
|
|
||||||
|
point.Properties["online"] = n.Online
|
||||||
|
var description strings.Builder
|
||||||
|
if n.Online {
|
||||||
|
description.WriteString("Online\n")
|
||||||
|
if statistics := n.Statistics; statistics != nil {
|
||||||
|
point.Properties["clients"] = statistics.Clients.Total
|
||||||
|
description.WriteString("Clients: " + strconv.Itoa(int(statistics.Clients.Total)) + "\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
description.WriteString("Offline\n")
|
||||||
|
}
|
||||||
|
if nodeinfo.Hardware.Model != "" {
|
||||||
|
point.Properties["model"] = nodeinfo.Hardware.Model
|
||||||
|
description.WriteString("Model: " + nodeinfo.Hardware.Model + "\n")
|
||||||
|
}
|
||||||
|
if fw := nodeinfo.Software.Firmware; fw.Release != "" {
|
||||||
|
point.Properties["firmware"] = fw.Release
|
||||||
|
description.WriteString("Firmware: " + fw.Release + "\n")
|
||||||
|
}
|
||||||
|
if nodeinfo.System.SiteCode != "" {
|
||||||
|
point.Properties["site"] = nodeinfo.System.SiteCode
|
||||||
|
description.WriteString("Site: " + nodeinfo.System.SiteCode + "\n")
|
||||||
|
}
|
||||||
|
if nodeinfo.System.DomainCode != "" {
|
||||||
|
point.Properties["domain"] = nodeinfo.System.DomainCode
|
||||||
|
description.WriteString("Domain: " + nodeinfo.System.DomainCode + "\n")
|
||||||
|
}
|
||||||
|
if owner := nodeinfo.Owner; owner != nil && owner.Contact != "" {
|
||||||
|
point.Properties["contact"] = owner.Contact
|
||||||
|
description.WriteString("Contact: " + owner.Contact + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
point.Properties["description"] = description.String()
|
||||||
|
point.Properties["_umap_options"] = getUMapOptions(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUMapOptions(n *runtime.Node) map[string]string {
|
||||||
|
result := map[string]string{
|
||||||
|
"iconClass": POINT_UMAP_CLASS,
|
||||||
|
}
|
||||||
|
if n.Online {
|
||||||
|
result["color"] = POINT_UMAP_ONLINE_COLOR
|
||||||
|
} else {
|
||||||
|
result["color"] = POINT_UMAP_OFFLINE_COLOR
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func transform(nodes *runtime.Nodes) *geojson.FeatureCollection {
|
||||||
|
nodelist := geojson.NewFeatureCollection()
|
||||||
|
|
||||||
|
for _, n := range nodes.List {
|
||||||
|
if n.Nodeinfo == nil || n.Nodeinfo.Location == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
point := newNodePoint(n)
|
||||||
|
if point != nil {
|
||||||
|
nodelist.Features = append(nodelist.Features, point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodelist
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package geojson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/FreifunkBremen/yanic/data"
|
||||||
|
"github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testNodeDescription string = "Online\nClients: 42\nModel: TP-Link 841\n" +
|
||||||
|
"Site: mysite\nDomain: domain_42\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTransform(t *testing.T) {
|
||||||
|
testNodes := createTestNodes()
|
||||||
|
nodes := transform(testNodes)
|
||||||
|
|
||||||
|
assert := assert.New(t)
|
||||||
|
assert.Len(testNodes.List, 4)
|
||||||
|
assert.Len(nodes.Features, 3)
|
||||||
|
|
||||||
|
node := testNodes.List["abcdef012425"]
|
||||||
|
|
||||||
|
umap := getUMapOptions(node)
|
||||||
|
assert.Len(umap, 2)
|
||||||
|
|
||||||
|
nodePoint := newNodePoint(node)
|
||||||
|
assert.Equal(
|
||||||
|
"abcdef012425",
|
||||||
|
nodePoint.Properties["id"],
|
||||||
|
)
|
||||||
|
assert.Equal(
|
||||||
|
"TP-Link 841",
|
||||||
|
nodePoint.Properties["model"],
|
||||||
|
)
|
||||||
|
assert.Equal(
|
||||||
|
uint32(42),
|
||||||
|
nodePoint.Properties["clients"],
|
||||||
|
)
|
||||||
|
assert.Equal(
|
||||||
|
testNodeDescription,
|
||||||
|
nodePoint.Properties["description"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestNodes() *runtime.Nodes {
|
||||||
|
nodes := runtime.NewNodes(&runtime.NodesConfig{})
|
||||||
|
|
||||||
|
nodes.AddNode(&runtime.Node{
|
||||||
|
Online: true,
|
||||||
|
Statistics: &data.Statistics{
|
||||||
|
Clients: data.Clients{
|
||||||
|
Total: 42,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Nodeinfo: &data.Nodeinfo{
|
||||||
|
NodeID: "abcdef012425",
|
||||||
|
Hardware: data.Hardware{
|
||||||
|
Model: "TP-Link 841",
|
||||||
|
},
|
||||||
|
Location: &data.Location{
|
||||||
|
Latitude: 24,
|
||||||
|
Longitude: 2,
|
||||||
|
},
|
||||||
|
System: data.System{
|
||||||
|
SiteCode: "mysite",
|
||||||
|
DomainCode: "domain_42",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeData := &runtime.Node{
|
||||||
|
Online: true,
|
||||||
|
Statistics: &data.Statistics{
|
||||||
|
Clients: data.Clients{
|
||||||
|
Total: 23,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Nodeinfo: &data.Nodeinfo{
|
||||||
|
NodeID: "abcdef012345",
|
||||||
|
Hardware: data.Hardware{
|
||||||
|
Model: "TP-Link 842",
|
||||||
|
},
|
||||||
|
System: data.System{
|
||||||
|
SiteCode: "mysite",
|
||||||
|
DomainCode: "domain_42",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nodeData.Nodeinfo.Software.Firmware.Release = "2019.1~exp42"
|
||||||
|
nodes.AddNode(nodeData)
|
||||||
|
|
||||||
|
nodes.AddNode(&runtime.Node{
|
||||||
|
Statistics: &data.Statistics{
|
||||||
|
Clients: data.Clients{
|
||||||
|
Total: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Nodeinfo: &data.Nodeinfo{
|
||||||
|
NodeID: "112233445566",
|
||||||
|
Hardware: data.Hardware{
|
||||||
|
Model: "TP-Link 843",
|
||||||
|
},
|
||||||
|
Location: &data.Location{
|
||||||
|
Latitude: 23,
|
||||||
|
Longitude: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
nodes.AddNode(&runtime.Node{
|
||||||
|
Nodeinfo: &data.Nodeinfo{
|
||||||
|
NodeID: "0xdeadbeef0x",
|
||||||
|
VPN: true,
|
||||||
|
Hardware: data.Hardware{
|
||||||
|
Model: "Xeon Multi-Core",
|
||||||
|
},
|
||||||
|
Location: &data.Location{
|
||||||
|
Latitude: 23,
|
||||||
|
Longitude: 22,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package geojson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/FreifunkBremen/yanic/output"
|
||||||
|
"github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Output struct {
|
||||||
|
output.Output
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config map[string]interface{}
|
||||||
|
|
||||||
|
func (c Config) Path() string {
|
||||||
|
if path, ok := c["path"]; ok {
|
||||||
|
return path.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
output.RegisterAdapter("geojson", Register)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Register(configuration map[string]interface{}) (output.Output, error) {
|
||||||
|
var config Config
|
||||||
|
config = configuration
|
||||||
|
|
||||||
|
if path := config.Path(); path != "" {
|
||||||
|
return &Output{
|
||||||
|
path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("no path given")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Output) Save(nodes *runtime.Nodes) {
|
||||||
|
nodes.RLock()
|
||||||
|
defer nodes.RUnlock()
|
||||||
|
|
||||||
|
runtime.SaveJSON(transform(nodes), o.path)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package geojson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOutput(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
out, err := Register(map[string]interface{}{})
|
||||||
|
assert.Error(err)
|
||||||
|
assert.Nil(out)
|
||||||
|
|
||||||
|
out, err = Register(map[string]interface{}{
|
||||||
|
"path": "/tmp/nodes.geojson",
|
||||||
|
})
|
||||||
|
os.Remove("/tmp/nodes.geojson")
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.NotNil(out)
|
||||||
|
|
||||||
|
out.Save(&runtime.Nodes{})
|
||||||
|
_, err = os.Stat("/tmp/nodes.geojson")
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
Loading…
Reference in New Issue