Remove API and simplify webserver
This commit is contained in:
parent
c66e1120d3
commit
4cc93891ee
|
@ -1,6 +1,5 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.7
|
|
||||||
- tip
|
- tip
|
||||||
install:
|
install:
|
||||||
- go get -t github.com/FreifunkBremen/respond-collector/...
|
- go get -t github.com/FreifunkBremen/respond-collector/...
|
||||||
|
@ -8,3 +7,5 @@ install:
|
||||||
- go get golang.org/x/tools/cmd/cover
|
- go get golang.org/x/tools/cmd/cover
|
||||||
script:
|
script:
|
||||||
- ./.test-coverage
|
- ./.test-coverage
|
||||||
|
- go install github.com/FreifunkBremen/respond-collector/cmd/respond-collector
|
||||||
|
- go install github.com/FreifunkBremen/respond-collector/cmd/respond-query
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/FreifunkBremen/respond-collector/models"
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GEOROUND : 7 nachkommerstellen sollten genug sein (7cm genau)
|
|
||||||
// http://blog.3960.org/post/7309573249/genauigkeit-bei-geo-koordinaten
|
|
||||||
const GEOROUND = 0.0000001
|
|
||||||
|
|
||||||
func geoEqual(a, b float64) bool {
|
|
||||||
if (a-b) < GEOROUND && (b-a) < GEOROUND {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// AliasesAPI struct for API
|
|
||||||
type AliasesAPI struct {
|
|
||||||
aliases *models.Aliases
|
|
||||||
config *models.Config
|
|
||||||
nodes *models.Nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAliases Bind to API
|
|
||||||
func NewAliases(config *models.Config, router *httprouter.Router, prefix string, nodes *models.Nodes) {
|
|
||||||
api := &AliasesAPI{
|
|
||||||
aliases: models.NewAliases(config),
|
|
||||||
nodes: nodes,
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
router.GET(prefix, api.GetAll)
|
|
||||||
router.GET(prefix+"/ansible", api.Ansible)
|
|
||||||
router.GET(prefix+"/alias/:nodeid", api.GetOne)
|
|
||||||
router.POST(prefix+"/alias/:nodeid", BasicAuth(api.SaveOne, []byte(config.Webserver.API.Passphrase)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll request for get all aliases
|
|
||||||
func (api *AliasesAPI) GetAll(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
||||||
jsonOutput(w, r, api.aliases.List)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOne request for get one alias
|
|
||||||
func (api *AliasesAPI) GetOne(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
if alias := api.aliases.List[ps.ByName("nodeid")]; alias != nil {
|
|
||||||
jsonOutput(w, r, alias)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, "Not found: ", ps.ByName("nodeid"), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveOne request for save a alias
|
|
||||||
func (api *AliasesAPI) SaveOne(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
var alias models.Alias
|
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&alias)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
fmt.Fprint(w, "Decode: ", ps.ByName("nodeid"), "\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
api.aliases.Update(ps.ByName("nodeid"), &alias)
|
|
||||||
fmt.Print("[api] node updated '", ps.ByName("nodeid"), "'\n")
|
|
||||||
jsonOutput(w, r, alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ansible json output
|
|
||||||
func (api *AliasesAPI) Ansible(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
||||||
fmt.Print("[api] ansible\n")
|
|
||||||
jsonOutput(w, r, models.GenerateAnsible(api.nodes, api.aliases.List))
|
|
||||||
}
|
|
63
api/lib.go
63
api/lib.go
|
@ -1,63 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
)
|
|
||||||
|
|
||||||
func jsonOutput(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|
||||||
js, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
if origin := r.Header.Get("Origin"); origin != "" {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
||||||
}
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
w.Write(js)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasicAuth for API request
|
|
||||||
func BasicAuth(h httprouter.Handle, pass []byte) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
if origin := r.Header.Get("Origin"); origin != "" {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
||||||
}
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
|
|
||||||
const basicAuthPrefix string = "Basic "
|
|
||||||
|
|
||||||
// Get the Basic Authentication credentials
|
|
||||||
auth := r.Header.Get("Authorization")
|
|
||||||
if strings.HasPrefix(auth, basicAuthPrefix) {
|
|
||||||
// Check credentials
|
|
||||||
payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):])
|
|
||||||
if err == nil {
|
|
||||||
pair := bytes.SplitN(payload, []byte(":"), 2)
|
|
||||||
if len(pair) == 2 &&
|
|
||||||
bytes.Equal(pair[1], pass) {
|
|
||||||
|
|
||||||
// Delegate request to the given handle
|
|
||||||
h(w, r, ps)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request Basic Authentication otherwise
|
|
||||||
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
27
api/nodes.go
27
api/nodes.go
|
@ -1,27 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/FreifunkBremen/respond-collector/models"
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodesAPI struct for API
|
|
||||||
type NodesAPI struct {
|
|
||||||
config *models.Config
|
|
||||||
nodes *models.Nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNodes Bind to API
|
|
||||||
func NewNodes(config *models.Config, router *httprouter.Router, prefix string, nodes *models.Nodes) {
|
|
||||||
api := &NodesAPI{
|
|
||||||
nodes: nodes,
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
router.GET(prefix, api.GetAll)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll request for get all nodes
|
|
||||||
func (api *NodesAPI) GetAll(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
||||||
jsonOutput(w, r, api.nodes.List)
|
|
||||||
}
|
|
|
@ -3,20 +3,15 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/NYTimes/gziphandler"
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
|
|
||||||
"github.com/FreifunkBremen/respond-collector/api"
|
|
||||||
"github.com/FreifunkBremen/respond-collector/database"
|
"github.com/FreifunkBremen/respond-collector/database"
|
||||||
"github.com/FreifunkBremen/respond-collector/models"
|
"github.com/FreifunkBremen/respond-collector/models"
|
||||||
"github.com/FreifunkBremen/respond-collector/respond"
|
"github.com/FreifunkBremen/respond-collector/respond"
|
||||||
"github.com/FreifunkBremen/respond-collector/rrd"
|
"github.com/FreifunkBremen/respond-collector/rrd"
|
||||||
|
"github.com/FreifunkBremen/respond-collector/webserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -54,21 +49,9 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Webserver.Enable {
|
if config.Webserver.Enable {
|
||||||
router := httprouter.New()
|
log.Println("starting webserver on", config.Webserver.Bind)
|
||||||
if config.Webserver.API.NewNodes {
|
srv := webserver.New(config.Webserver.Bind, config.Webserver.Webroot)
|
||||||
api.NewNodes(config, router, "/api/nodes", nodes)
|
go srv.Close()
|
||||||
log.Println("api nodes started")
|
|
||||||
}
|
|
||||||
if config.Webserver.API.Aliases {
|
|
||||||
api.NewAliases(config, router, "/api/aliases", nodes)
|
|
||||||
log.Println("api aliases started")
|
|
||||||
}
|
|
||||||
router.NotFound = gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webserver.Webroot)))
|
|
||||||
|
|
||||||
address := net.JoinHostPort(config.Webserver.Address, config.Webserver.Port)
|
|
||||||
log.Println("starting webserver on", address)
|
|
||||||
// TODO bad
|
|
||||||
log.Fatal(http.ListenAndServe(address, router))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for INT/TERM
|
// Wait for INT/TERM
|
||||||
|
|
|
@ -3,18 +3,18 @@ enable = true
|
||||||
interface = "eth0"
|
interface = "eth0"
|
||||||
collect_interval = "1m"
|
collect_interval = "1m"
|
||||||
|
|
||||||
|
|
||||||
[webserver]
|
[webserver]
|
||||||
enable = false
|
enable = false
|
||||||
port = "8080"
|
bind = "127.0.0.1:8080"
|
||||||
address = "127.0.0.1"
|
|
||||||
webroot = "webroot"
|
webroot = "webroot"
|
||||||
|
|
||||||
|
|
||||||
[nodes]
|
[nodes]
|
||||||
enable = true
|
enable = true
|
||||||
nodes_version = 2
|
nodes_version = 2
|
||||||
nodes_path = "/var/www/html/meshviewer/data/nodes_all.json"
|
nodes_path = "/var/www/html/meshviewer/data/nodes_all.json"
|
||||||
graphs_path = "/var/www/html/meshviewer/data/graph.json"
|
graph_path = "/var/www/html/meshviewer/data/graph.json"
|
||||||
aliases_path = "/var/www/html/meshviewer/data/aliases.json"
|
|
||||||
|
|
||||||
# Export nodes and graph periodically
|
# Export nodes and graph periodically
|
||||||
save_interval = "5s"
|
save_interval = "5s"
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/FreifunkBremen/respond-collector/data"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Alias a change request for other nodes
|
|
||||||
type Alias struct {
|
|
||||||
Hostname string `json:"hostname,omitempty"`
|
|
||||||
Location *data.Location `json:"location,omitempty"`
|
|
||||||
Wireless *data.Wireless `json:"wireless,omitempty"`
|
|
||||||
Owner string `json:"owner,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aliases struct: cache DB of Node's structs
|
|
||||||
type Aliases struct {
|
|
||||||
List map[string]*Alias `json:"nodes"` // the current nodemap, indexed by node ID
|
|
||||||
config *Config
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAliases create Nodes structs
|
|
||||||
func NewAliases(config *Config) *Aliases {
|
|
||||||
aliases := &Aliases{
|
|
||||||
List: make(map[string]*Alias),
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Nodes.AliasesPath != "" {
|
|
||||||
aliases.load()
|
|
||||||
}
|
|
||||||
go aliases.worker()
|
|
||||||
|
|
||||||
return aliases
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update a alias in aliases cache
|
|
||||||
func (e *Aliases) Update(nodeID string, newalias *Alias) {
|
|
||||||
e.Lock()
|
|
||||||
e.List[nodeID] = newalias
|
|
||||||
e.Unlock()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Aliases) load() {
|
|
||||||
path := e.config.Nodes.AliasesPath
|
|
||||||
log.Println("loading", path)
|
|
||||||
|
|
||||||
if data, err := ioutil.ReadFile(path); err == nil {
|
|
||||||
if err = json.Unmarshal(data, e); err == nil {
|
|
||||||
log.Println("loaded", len(e.List), "aliases")
|
|
||||||
} else {
|
|
||||||
log.Println("failed to unmarshal nodes:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.Println("failed loading cached nodes:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Periodically saves the cached DB to json file
|
|
||||||
func (e *Aliases) worker() {
|
|
||||||
c := time.Tick(time.Second * 5)
|
|
||||||
|
|
||||||
for range c {
|
|
||||||
log.Println("saving", len(e.List), "aliases")
|
|
||||||
e.Lock()
|
|
||||||
save(e, e.config.Nodes.AliasesPath)
|
|
||||||
e.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
// Ansible struct
|
|
||||||
type Ansible struct {
|
|
||||||
Nodes []string `json:"nodes"`
|
|
||||||
Meta struct {
|
|
||||||
HostVars map[string]*AnsibleHostVars `json:"hostvars,omitempty"`
|
|
||||||
} `json:"_meta"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AnsibleHostVars new values for a node
|
|
||||||
type AnsibleHostVars struct {
|
|
||||||
Address string `json:"ansible_ssh_host"`
|
|
||||||
Hostname string `json:"node_name,omitempty"`
|
|
||||||
Owner string `json:"owner,omitempty"`
|
|
||||||
Channel24 uint32 `json:"radio24_channel,omitempty"`
|
|
||||||
TxPower24 uint32 `json:"radio24_txpower,omitempty"`
|
|
||||||
Channel5 uint32 `json:"radio5_channel,omitempty"`
|
|
||||||
TxPower5 uint32 `json:"radio5_txpower,omitempty"`
|
|
||||||
GeoLatitude float64 `json:"geo_latitude,omitempty"`
|
|
||||||
GeoLongitude float64 `json:"geo_longitude,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateAnsible but nodes and aliases together to a ansible change output
|
|
||||||
func GenerateAnsible(nodes *Nodes, aliases map[string]*Alias) *Ansible {
|
|
||||||
ansible := &Ansible{Nodes: make([]string, 0)}
|
|
||||||
ansible.Meta.HostVars = make(map[string]*AnsibleHostVars)
|
|
||||||
for nodeid, alias := range aliases {
|
|
||||||
if node := nodes.List[nodeid]; node != nil {
|
|
||||||
|
|
||||||
ansible.Nodes = append(ansible.Nodes, nodeid)
|
|
||||||
|
|
||||||
vars := &AnsibleHostVars{
|
|
||||||
Hostname: alias.Hostname,
|
|
||||||
Owner: alias.Owner,
|
|
||||||
}
|
|
||||||
if node.Nodeinfo.Network.Addresses != nil {
|
|
||||||
vars.Address = node.Nodeinfo.Network.Addresses[0]
|
|
||||||
}
|
|
||||||
if alias.Wireless != nil {
|
|
||||||
vars.Channel24 = alias.Wireless.Channel24
|
|
||||||
vars.TxPower24 = alias.Wireless.TxPower24
|
|
||||||
vars.Channel5 = alias.Wireless.Channel5
|
|
||||||
vars.TxPower5 = alias.Wireless.TxPower5
|
|
||||||
}
|
|
||||||
if alias.Location != nil {
|
|
||||||
vars.GeoLatitude = alias.Location.Latitude
|
|
||||||
vars.GeoLongitude = alias.Location.Longtitude
|
|
||||||
}
|
|
||||||
ansible.Meta.HostVars[nodeid] = vars
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ansible
|
|
||||||
}
|
|
|
@ -15,22 +15,15 @@ type Config struct {
|
||||||
}
|
}
|
||||||
Webserver struct {
|
Webserver struct {
|
||||||
Enable bool
|
Enable bool
|
||||||
Port string
|
Bind string
|
||||||
Address string
|
|
||||||
Webroot string
|
Webroot string
|
||||||
API struct {
|
|
||||||
Passphrase string
|
|
||||||
NewNodes bool
|
|
||||||
Aliases bool
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Nodes struct {
|
Nodes struct {
|
||||||
Enable bool
|
Enable bool
|
||||||
NodesDynamicPath string
|
|
||||||
NodesPath string
|
|
||||||
NodesVersion int
|
NodesVersion int
|
||||||
GraphsPath string
|
NodesPath string
|
||||||
AliasesPath string
|
NodesDynamicPath string
|
||||||
|
GraphPath string
|
||||||
SaveInterval Duration // Save nodes periodically
|
SaveInterval Duration // Save nodes periodically
|
||||||
PruneAfter Duration // Remove nodes after n days of inactivity
|
PruneAfter Duration // Remove nodes after n days of inactivity
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,7 +211,7 @@ func (nodes *Nodes) save() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if path := nodes.config.Nodes.GraphsPath; path != "" {
|
if path := nodes.config.Nodes.GraphPath; path != "" {
|
||||||
save(nodes.BuildGraph(), path)
|
save(nodes.BuildGraph(), path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package webserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/NYTimes/gziphandler"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new webserver and starts it
|
||||||
|
func New(bindAddr, webroot string) *http.Server {
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: bindAddr,
|
||||||
|
Handler: gziphandler.GzipHandler(http.FileServer(http.Dir(webroot))),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// service connections
|
||||||
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return srv
|
||||||
|
}
|
Loading…
Reference in New Issue