MySQL: alpha3

Signed-off-by: Adrian Schmutzler <freifunk@adrianschmutzler.de>
This commit is contained in:
Adrian Schmutzler 2017-11-05 19:48:29 +01:00
parent 1b4ae0fe51
commit e3fe995407
25 changed files with 1687 additions and 929 deletions

25
copyusers.py Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/python3
from ffmap.mysqltools import FreifunkMySQL
import pymongo
from bson.json_util import dumps as bson2json
from bson.objectid import ObjectId
import base64
import datetime
client = MongoClient(tz_aware=True, connect=False)
db = client.freifunk
users = db.users.find({}, {"nickname": 1, "password":1, "email": 1, "token": 1, "created": 1, "admin": 1})
mysql = FreifunkMySQL()
cur = mysql.cursor()
for u in users:
#print(u)
cur.execute("""
INSERT INTO users (nickname, password, token, email, created, admin)
VALUES (%s, %s, %s, %s, %s, %s)
""",(u.get("nickname"),u.get("password"),u.get("token"),u.get("email",""),u.get("created"),u.get("admin",0),))
mysql.commit()
mysql.close()

196
ffmap/db/hooddata.py Executable file
View File

@ -0,0 +1,196 @@
#!/usr/bin/python
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '../..'))
from ffmap.mysqltools import FreifunkMySQL
import math
mysql = FreifunkMySQL()
hoods = [
{
"keyxchange_id": 1,
"name": "Default",
"net": "10.50.16.0/20"
},
{
"keyxchange_id": 2,
"name": "Fuerth",
"net": "10.50.32.0/21",
"position": {"lng": 10.966, "lat": 49.4814}
},
{
"keyxchange_id": 3,
"name": "Nuernberg",
"net": "10.50.40.0/21",
"position": {"lng": 11.05, "lat": 49.444}
},
{
"keyxchange_id": 4,
"name": "Ansbach",
"net": "10.50.48.0/21",
"position": {"lng": 10.571667, "lat": 49.300833}
},
{
"keyxchange_id": 5,
"name": "Hassberge",
"net": "10.50.56.0/21",
"position": {"lng": 10.568013390003, "lat": 50.093555895082}
},
{
"keyxchange_id": 6,
"name": "Erlangen",
"net": "10.50.64.0/21",
"position": {"lng": 11.0019221, "lat": 49.6005981}
},
{
"keyxchange_id": 7,
"name": "Wuerzburg",
"net": "10.50.72.0/21",
"position": {"lng": 9.93489, "lat": 49.79688}
},
{
"keyxchange_id": 8,
"name": "Bamberg",
"net": "10.50.124.0/22",
"position": {"lng": 10.95, "lat": 49.89}
},
{
"keyxchange_id": 9,
"name": "BGL",
"net": "10.50.80.0/21",
"position": {"lng": 12.8825, "lat": 47.7314}
},
{
"keyxchange_id": 10,
"name": "HassbergeSued",
"net": "10.50.60.0/22",
"position": {"lng": 10.568013390003, "lat": 50.04501}
},
{
"keyxchange_id": 11,
"name": "NbgLand",
"net": "10.50.88.0/21",
"position": {"lng": 11.162796020507812, "lat": 49.39200496388418}
},
{
"keyxchange_id": 12,
"name": "Hof",
"net": "10.50.104.0/21",
"position": {"lng": 11.917545, "lat": 50.312209}
},
{
"keyxchange_id": 13,
"name": "Aschaffenburg",
"net": "10.50.96.0/22",
"position": {"lng": 9.146826, "lat": 49.975661}
},
{
"keyxchange_id": 14,
"name": "Marktredwitz",
"net": "10.50.112.0/22",
"position": {"lng": 12.084797, "lat": 50.002915}
},
{
"keyxchange_id": 15,
"name": "Forchheim",
"net": "10.50.116.0/22",
"position": {"lng": 11.059474, "lat": 49.718820}
},
{
"keyxchange_id": 16,
"name": "Muenchberg",
"net": "10.50.120.0/22",
"position": {"lng": 11.79, "lat": 50.19}
},
{
"keyxchange_id": 17,
"name": "Adelsdorf",
"net": "10.50.144.0/22",
"position": {"lng": 10.894235, "lat": 49.709945}
},
{
"keyxchange_id": 18,
"name": "Schweinfurt",
"net": "10.50.160.0/22",
"position": {"lng": 10.21267, "lat": 50.04683}
},
{
"keyxchange_id": 19,
"name": "ErlangenWest",
"net": "10.50.152.0/22",
"position": {"lng": 10.984488, "lat": 49.6035981}
},
{
"keyxchange_id": 20,
"name": "Ebermannstadt",
"net": "10.50.148.0/22",
"position": {"lng": 11.18538, "lat": 49.78173}
},
{
"keyxchange_id": 21,
"name": "Lauf",
"net": "10.50.156.0/22",
"position": {"lng": 11.278789, "lat": 49.509972}
},
{
"keyxchange_id": 22,
"name": "Bayreuth",
"net": "10.50.168.0/22",
"position": {"lng": 11.580566, "lat": 49.94814}
},
{
"keyxchange_id": 23,
"name": "Fichtelberg",
"net": "10.50.172.0/22",
"position": {"lng": 11.852292, "lat": 49.998920}
},
{
"keyxchange_id": 24,
"name": "Rehau",
"net": "10.50.176.0/22",
"position": {"lng": 12.035305, "lat": 50.247594}
},
{
"keyxchange_id": 25,
"name": "Coburg",
"net": "10.50.180.0/22",
"position": {"lng": 10.964414, "lat": 50.259675}
},
{
"keyxchange_id": 26,
"name": "Ebern",
"net": "10.50.184.0/22",
"position": {"lng": 10.798395, "lat": 50.095572}
},
{
"keyxchange_id": 27,
"name": "Arnstein",
"net": "10.50.188.0/22",
"position": {"lng": 9.970957, "lat": 49.978117}
},
{
"keyxchange_id": 28,
"name": "Erlenbach",
"net": "10.50.192.0/22",
"position": {"lng": 9.157491, "lat": 49.803930}
}]
for h in hoods:
coord = h.get("position",{})
if coord.get("lat"):
cos_lat = math.cos(math.radians(coord.get("lat")))
sin_lat = math.sin(math.radians(coord.get("lat")))
else:
cos_lat = None
sin_lat = None
mysql.execute("""
INSERT INTO hoods (id, name, net, lat, lng, cos_lat, sin_lat)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""",(h["keyxchange_id"],h["name"],h["net"],coord.get("lat"),coord.get("lng"),cos_lat,sin_lat,))
mysql.commit()
mysql.close()

205
ffmap/db/hoods.py Normal file → Executable file
View File

@ -1,182 +1,33 @@
#!/usr/bin/python
#!/usr/bin/python3
from pymongo import MongoClient
client = MongoClient()
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '../..'))
db = client.freifunk
from ffmap.mysqltools import FreifunkMySQL
# create db indexes
db.hoods.delete_many({})
db.hoods.create_index([("position", "2dsphere")])
mysql = FreifunkMySQL()
hoods = [
{
"keyxchange_id": 1,
"name": "Default",
"net": "10.50.16.0/20"
},
{
"keyxchange_id": 2,
"name": "Fuerth",
"net": "10.50.32.0/21",
"position": {"type": "Point", "coordinates": [10.966, 49.4814]}
},
{
"keyxchange_id": 3,
"name": "Nuernberg",
"net": "10.50.40.0/21",
"position": {"type": "Point", "coordinates": [11.05, 49.444]}
},
{
"keyxchange_id": 4,
"name": "Ansbach",
"net": "10.50.48.0/21",
"position": {"type": "Point", "coordinates": [10.571667, 49.300833]}
},
{
"keyxchange_id": 5,
"name": "Hassberge",
"net": "10.50.56.0/21",
"position": {"type": "Point", "coordinates": [10.568013390003, 50.093555895082]}
},
{
"keyxchange_id": 6,
"name": "Erlangen",
"net": "10.50.64.0/21",
"position": {"type": "Point", "coordinates": [11.0019221, 49.6005981]}
},
{
"keyxchange_id": 7,
"name": "Wuerzburg",
"net": "10.50.72.0/21",
"position": {"type": "Point", "coordinates": [9.93489, 49.79688]}
},
{
"keyxchange_id": 8,
"name": "Bamberg",
"net": "10.50.124.0/22",
"position": {"type": "Point", "coordinates": [10.95, 49.89]}
},
{
"keyxchange_id": 9,
"name": "BGL",
"net": "10.50.80.0/21",
"position": {"type": "Point", "coordinates": [12.8825, 47.7314]}
},
{
"keyxchange_id": 10,
"name": "HassbergeSued",
"net": "10.50.60.0/22",
"position": {"type": "Point", "coordinates": [10.568013390003, 50.04501]}
},
{
"keyxchange_id": 11,
"name": "NbgLand",
"net": "10.50.88.0/21",
"position": {"type": "Point", "coordinates": [11.162796020507812, 49.39200496388418]}
},
{
"keyxchange_id": 12,
"name": "Hof",
"net": "10.50.104.0/21",
"position": {"type": "Point", "coordinates": [11.917545, 50.312209]}
},
{
"keyxchange_id": 13,
"name": "Aschaffenburg",
"net": "10.50.96.0/22",
"position": {"type": "Point", "coordinates": [9.146826, 49.975661]}
},
{
"keyxchange_id": 14,
"name": "Marktredwitz",
"net": "10.50.112.0/22",
"position": {"type": "Point", "coordinates": [12.084797, 50.002915]}
},
{
"keyxchange_id": 15,
"name": "Forchheim",
"net": "10.50.116.0/22",
"position": {"type": "Point", "coordinates": [11.059474, 49.718820]}
},
{
"keyxchange_id": 16,
"name": "Muenchberg",
"net": "10.50.120.0/22",
"position": {"type": "Point", "coordinates": [11.79, 50.19]}
},
{
"keyxchange_id": 17,
"name": "Adelsdorf",
"net": "10.50.144.0/22",
"position": {"type": "Point", "coordinates": [10.894235, 49.709945]}
},
{
"keyxchange_id": 18,
"name": "Schweinfurt",
"net": "10.50.160.0/22",
"position": {"type": "Point", "coordinates": [10.21267, 50.04683]}
},
{
"keyxchange_id": 19,
"name": "ErlangenWest",
"net": "10.50.152.0/22",
"position": {"type": "Point", "coordinates": [10.984488, 49.6035981]}
},
{
"keyxchange_id": 20,
"name": "Ebermannstadt",
"net": "10.50.148.0/22",
"position": {"type": "Point", "coordinates": [11.18538, 49.78173]}
},
{
"keyxchange_id": 21,
"name": "Lauf",
"net": "10.50.156.0/22",
"position": {"type": "Point", "coordinates": [11.278789, 49.509972]}
},
{
"keyxchange_id": 22,
"name": "Bayreuth",
"net": "10.50.168.0/22",
"position": {"type": "Point", "coordinates": [11.580566, 49.94814]}
},
{
"keyxchange_id": 23,
"name": "Fichtelberg",
"net": "10.50.172.0/22",
"position": {"type": "Point", "coordinates": [11.852292, 49.998920]}
},
{
"keyxchange_id": 24,
"name": "Rehau",
"net": "10.50.176.0/22",
"position": {"type": "Point", "coordinates": [12.035305, 50.247594]}
},
{
"keyxchange_id": 25,
"name": "Coburg",
"net": "10.50.180.0/22",
"position": {"type": "Point", "coordinates": [10.964414, 50.259675]}
},
{
"keyxchange_id": 26,
"name": "Ebern",
"net": "10.50.184.0/22",
"position": {"type": "Point", "coordinates": [10.798395, 50.095572]}
},
{
"keyxchange_id": 27,
"name": "Arnstein",
"net": "10.50.188.0/22",
"position": {"type": "Point", "coordinates": [9.970957, 49.978117]}
},
{
"keyxchange_id": 28,
"name": "Erlenbach",
"net": "10.50.192.0/22",
"position": {"type": "Point", "coordinates": [9.157491, 49.803930]}
}]
mysql.execute("""
CREATE TABLE hoods (
`id` int(11) NOT NULL,
`name` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`net` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`lat` double DEFAULT NULL,
`lng` double DEFAULT NULL,
`cos_lat` double DEFAULT NULL,
`sin_lat` double DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
for hood in hoods:
db.hoods.insert_one(hood)
mysql.execute("""
ALTER TABLE hoods
ADD PRIMARY KEY (`id`),
ADD KEY `name` (`name`),
ADD KEY `lat` (`lat`),
ADD KEY `lng` (`lng`),
ADD KEY `cos_lat` (`cos_lat`),
ADD KEY `sin_lat` (`sin_lat`)
""")
mysql.close()

View File

@ -3,3 +3,5 @@
import routers
import hoods
import stats
import users
import hooddata

184
ffmap/db/routers.py Normal file → Executable file
View File

@ -1,14 +1,178 @@
#!/usr/bin/python3
from pymongo import MongoClient
client = MongoClient()
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '../..'))
db = client.freifunk
from ffmap.mysqltools import FreifunkMySQL
# create db indexes
db.routers.create_index("hostname")
db.routers.create_index("status")
db.routers.create_index("created")
db.routers.create_index("last_contact")
db.routers.create_index("netifs.mac")
db.routers.create_index([("position", "2dsphere")])
mysql = FreifunkMySQL()
mysql.execute("""
CREATE TABLE router (
`id` int(11) NOT NULL,
`status` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`hostname` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`last_contact` datetime NOT NULL,
`sys_time` datetime NOT NULL,
`sys_uptime` int(11) NOT NULL,
`sys_memfree` int(11) NOT NULL,
`sys_membuff` int(11) NOT NULL,
`sys_memcache` int(11) NOT NULL,
`sys_loadavg` double NOT NULL,
`sys_procrun` smallint(6) NOT NULL,
`sys_proctot` smallint(6) NOT NULL,
`clients` smallint(6) NOT NULL,
`wan_uplink` tinyint(1) NOT NULL,
`cpu` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`chipset` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`hardware` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`os` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`batman` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`kernel` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`nodewatcher` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`firmware` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`firmware_rev` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`description` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`position_comment` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`community` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`hood` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL,
`status_text` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`contact` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`lng` double DEFAULT NULL,
`lat` double DEFAULT NULL,
`neighbors` smallint(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
CREATE TABLE router_events (
`router` int(11) NOT NULL,
`time` datetime NOT NULL,
`type` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`comment` varchar(200) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
CREATE TABLE router_ipv6 (
`router` int(11) NOT NULL,
`netif` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`ipv6` varchar(60) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
CREATE TABLE router_neighbor (
`router` int(11) NOT NULL,
`mac` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`quality` smallint(6) NOT NULL,
`net_if` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(10) COLLATE utf8_unicode_ci DEFAULT 'l2'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
CREATE TABLE router_netif (
`router` int(11) NOT NULL,
`netif` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`mtu` smallint(6) NOT NULL,
`rx_bytes` bigint(20) NOT NULL,
`tx_bytes` bigint(20) NOT NULL,
`rx` bigint(20) NOT NULL,
`tx` bigint(20) NOT NULL,
`fe80_addr` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
`ipv4_addr` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`mac` varchar(30) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
CREATE TABLE router_stats (
`router` int(11) NOT NULL,
`time` datetime NOT NULL,
`sys_proctot` smallint(6) NOT NULL,
`sys_procrun` smallint(6) NOT NULL,
`sys_memcache` int(11) NOT NULL,
`sys_membuff` int(11) NOT NULL,
`sys_memfree` int(11) NOT NULL,
`loadavg` double NOT NULL,
`clients` smallint(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
CREATE TABLE router_stats_neighbor (
`router` int(11) NOT NULL,
`mac` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`time` datetime NOT NULL,
`quality` smallint(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
CREATE TABLE router_stats_netif (
`router` int(11) NOT NULL,
`netif` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`rx` bigint(20) NOT NULL,
`tx` bigint(20) NOT NULL,
`time` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
ALTER TABLE router
ADD PRIMARY KEY (`id`),
ADD KEY `created` (`created`),
ADD KEY `hostname` (`hostname`),
ADD KEY `status` (`status`),
ADD KEY `last_contact` (`last_contact`),
ADD KEY `lat` (`lat`),
ADD KEY `lng` (`lng`),
ADD KEY `contact` (`contact`),
ADD KEY `hood` (`hood`)
""")
mysql.execute("""
ALTER TABLE router_events
ADD PRIMARY KEY (`router`,`time`,`type`)
""")
mysql.execute("""
ALTER TABLE router_ipv6
ADD PRIMARY KEY (`router`,`netif`,`ipv6`)
""")
mysql.execute("""
ALTER TABLE router_neighbor
ADD PRIMARY KEY (`router`,`mac`,`net_if`)
""")
mysql.execute("""
ALTER TABLE router_netif
ADD PRIMARY KEY (`router`,`netif`),
ADD KEY `mac` (`mac`)
""")
mysql.execute("""
ALTER TABLE router_stats
ADD PRIMARY KEY (`router`,`time`)
""")
mysql.execute("""
ALTER TABLE router_stats_neighbor
ADD PRIMARY KEY (`router`,`mac`,`time`)
""")
mysql.execute("""
ALTER TABLE router_stats_netif
ADD PRIMARY KEY (`router`,`netif`,`time`)
""")
mysql.execute("""
ALTER TABLE router
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT
""")
mysql.close()

27
ffmap/db/stats.py Normal file → Executable file
View File

@ -1,9 +1,26 @@
#!/usr/bin/python3
from pymongo import MongoClient
client = MongoClient()
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '../..'))
db = client.freifunk
from ffmap.mysqltools import FreifunkMySQL
# create capped collection
db.create_collection("stats", capped=True, size=10*1024*1024, max=4320)
mysql = FreifunkMySQL()
mysql.execute("""
CREATE TABLE `stats_global` (
`time` datetime NOT NULL,
`clients` int(11) NOT NULL,
`online` int(11) NOT NULL,
`offline` int(11) NOT NULL,
`unknown` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
ALTER TABLE `stats_global`
ADD PRIMARY KEY (`time`)
""")
mysql.close()

38
ffmap/db/users.py Normal file → Executable file
View File

@ -1,10 +1,36 @@
#!/usr/bin/python3
from pymongo import MongoClient
client = MongoClient()
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '../..'))
db = client.freifunk
from ffmap.mysqltools import FreifunkMySQL
# create db indexes
db.users.create_index("email")
db.users.create_index("nickname")
mysql = FreifunkMySQL()
mysql.execute("""
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`nickname` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`password` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL,
`token` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL,
`email` varchar(200) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`admin` tinyint(4) NOT NULL DEFAULT '0'
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
""")
mysql.execute("""
ALTER TABLE `users`
ADD PRIMARY KEY (`id`),
ADD KEY `nickname` (`nickname`),
ADD KEY `email` (`email`)
""")
mysql.execute("""
ALTER TABLE `users`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT
""")
mysql.close()

View File

@ -1,15 +0,0 @@
#!/usr/bin/python3
from pymongo import MongoClient
class FreifunkDB(object):
client = None
db = None
@classmethod
def handle(cls):
if not cls.client:
cls.client = MongoClient(tz_aware=True, connect=False)
if not cls.db:
cls.db = cls.client.freifunk
return cls.db

View File

@ -4,7 +4,7 @@ import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
from ffmap.dbtools import FreifunkDB
from ffmap.mysqltools import FreifunkMySQL
import math
import numpy as np
@ -12,8 +12,6 @@ from scipy.spatial import Voronoi
import urllib.request, json
db = FreifunkDB().handle()
CONFIG = {
"csv_dir": "/var/lib/ffmap/csv"
}
@ -73,83 +71,88 @@ def draw_voronoi_lines(csv, hoods):
csv.write("\"LINESTRING (%f %f,%f %f)\"\n" % (lng1, lat1, lng2, lat2))
def update_mapnik_csv():
def update_mapnik_csv(mysql):
with open(os.path.join(CONFIG["csv_dir"], "routers.csv"), "w") as csv:
csv.write("lng,lat,status\n")
for router in db.routers.find({"position.coordinates": {"$exists": True}}, {"status": 1, "position": 1}):
routers = mysql.fetchall("""
SELECT status, lat, lng FROM router
WHERE lat IS NOT NULL AND lng IS NOT NULL
""")
for router in routers:
csv.write("%f,%f,%s\n" % (
router["position"]["coordinates"][0],
router["position"]["coordinates"][1],
router["lng"],
router["lat"],
router["status"]
))
dblinks = mysql.fetchall("""
SELECT r1.lat AS rlat, r1.lng AS rlng, r2.lat AS nlat, r2.lng AS nlng, n.type AS type, quality
FROM router AS r1
INNER JOIN router_neighbor AS n ON r1.id = n.router
INNER JOIN (
SELECT router, mac FROM router_netif GROUP BY mac, router
) AS net ON n.mac = net.mac
INNER JOIN router AS r2 ON net.router = r2.id
WHERE r1.lat IS NOT NULL AND r1.lng IS NOT NULL AND r2.lat IS NOT NULL AND r2.lng IS NOT NULL
AND r1.status = 'online'
""")
links = []
linksl3 = []
for row in dblinks:
if row.get("type")=="l3":
linksl3.append((
row["rlng"],
row["rlat"],
row["nlng"],
row["nlat"]
))
else:
links.append((
row["rlng"],
row["rlat"],
row["nlng"],
row["nlat"],
row["quality"]
))
with open(os.path.join(CONFIG["csv_dir"], "links.csv"), "w") as csv:
csv.write("WKT,quality\n")
links = []
for router in db.routers.find(
{
"position.coordinates": {"$exists": True},
"neighbours": {"$exists": True},
"status": "online"
},
{"position": 1, "neighbours": 1}
):
for neighbour in router["neighbours"]:
if "position" in neighbour and not neighbour.get("type"):
links.append((
router["position"]["coordinates"][0],
router["position"]["coordinates"][1],
neighbour["position"]["coordinates"][0],
neighbour["position"]["coordinates"][1],
neighbour["quality"]
))
for link in sorted(links, key=lambda l: l[4]):
csv.write("\"LINESTRING (%f %f,%f %f)\",%i\n" % link)
with open(os.path.join(CONFIG["csv_dir"], "l3_links.csv"), "w") as csv:
csv.write("WKT\n")
for router in db.routers.find(
{
"position.coordinates": {"$exists": True},
"neighbours": {"$exists": True},
"status": "online"
},
{"position": 1, "neighbours": 1}
):
for neighbour in router["neighbours"]:
if "position" in neighbour and neighbour.get("type") and neighbour["type"] == "l3":
csv.write("\"LINESTRING (%f %f,%f %f)\"\n" % (
router["position"]["coordinates"][0],
router["position"]["coordinates"][1],
neighbour["position"]["coordinates"][0],
neighbour["position"]["coordinates"][1]
))
for link in linksl3:
csv.write("\"LINESTRING (%f %f,%f %f)\"\n" % link)
dbhoods = mysql.fetchall("""
SELECT name, lat, lng FROM hoods
WHERE lat IS NOT NULL AND lng IS NOT NULL
""")
with open(os.path.join(CONFIG["csv_dir"], "hood-points.csv"), "w", encoding="UTF-8") as csv:
csv.write("lng,lat,name\n")
for hood in db.hoods.find({"position": {"$exists": True}}):
for hood in dbhoods:
csv.write("%f,%f,\"%s\"\n" % (
hood["position"]["coordinates"][0],
hood["position"]["coordinates"][1],
hood["lng"],
hood["lat"],
hood["name"]
))
with open(os.path.join(CONFIG["csv_dir"], "hoods.csv"), "w") as csv:
csv.write("WKT\n")
hoods = []
for hood in db.hoods.find({"position": {"$exists": True}}):
for hood in dbhoods:
# convert coordinates info marcator sphere as voronoi doesn't work with lng/lat
x, y = merc_sphere(hood["position"]["coordinates"][0], hood["position"]["coordinates"][1])
x, y = merc_sphere(hood["lng"], hood["lat"])
hoods.append([x, y])
draw_voronoi_lines(csv, hoods)
with urllib.request.urlopen("http://keyserver.freifunk-franken.de/v2/hoods.php") as url:
dbhoodsv2 = json.loads(url.read().decode())
with open(os.path.join(CONFIG["csv_dir"], "hood-points-v2.csv"), "w", encoding="UTF-8") as csv:
csv.write("lng,lat,name\n")
with urllib.request.urlopen("http://keyserver.freifunk-franken.de/v2/hoods.php") as url:
data = json.loads(url.read().decode())
for hood in data:
for hood in dbhoodsv2:
if not ( 'lon' in hood and 'lat' in hood ):
continue
csv.write("%f,%f,\"%s\"\n" % (
@ -161,10 +164,8 @@ def update_mapnik_csv():
with open(os.path.join(CONFIG["csv_dir"], "hoodsv2.csv"), "w") as csv:
csv.write("WKT\n")
hoods = []
with urllib.request.urlopen("http://keyserver.freifunk-franken.de/v2/hoods.php") as url:
data = json.loads(url.read().decode())
for hood in data:
for hood in dbhoodsv2:
if not ( 'lon' in hood and 'lat' in hood ):
continue
# convert coordinates info marcator sphere as voronoi doesn't work with lng/lat

View File

@ -0,0 +1,8 @@
#!/usr/bin/python3
mysq = {
"host":"localhost",
"user":"root",
"passwd":"password",
"db":"dbname"
}

98
ffmap/mysqltools.py Normal file
View File

@ -0,0 +1,98 @@
#!/usr/bin/python3
import MySQLdb
from ffmap.mysqlconfig import mysq
from ffmap.misc import *
#import pytz
class FreifunkMySQL:
db = None
cur = None
def __init__(self):
#global mysq
self.db = MySQLdb.connect(host=mysq["host"], user=mysq["user"], passwd=mysq["passwd"], db=mysq["db"])
self.cur = self.db.cursor(MySQLdb.cursors.DictCursor)
def close(self):
self.db.close()
def cursor(self):
return self.cur
def commit(self):
self.db.commit()
def fetchall(self,str,tup=(),key=None):
self.cur.execute(str,tup)
result = self.cur.fetchall()
if len(result) > 0:
if key:
rnew = []
for r in result:
rnew.append(r[key])
return rnew
else:
return result
else:
return ()
def fetchdict(self,str,tup,key,value=None):
self.cur.execute(str,tup)
dict = {}
for d in self.cur.fetchall():
if value:
dict[d[key]] = d[value]
else:
dict[d[key]] = d
return dict
def findone(self,str,tup,sel=None):
self.cur.execute(str,tup)
result = self.cur.fetchall()
if len(result) > 0:
if sel:
return result[0][sel]
else:
return result[0]
else:
return False
def execute(self,a,b=None):
if b:
return self.cur.execute(a,b)
else:
return self.cur.execute(a)
def utcnow(self):
return utcnow().strftime('%Y-%m-%d %H:%M:%S')
def formatdt(self,dt):
return dt.strftime('%Y-%m-%d %H:%M:%S')
def utcaware(self,data,keys=None):
if keys:
for k in keys:
#self.utcaware(data[k])
#data[k] = pytz.utc.localize(data[k])
data[k] = data[k].replace(tzinfo=datetime.timezone.utc)
else:
#data = pytz.utc.localize(data)
data = data.replace(tzinfo=datetime.timezone.utc)
return data
def utcawaretuple(self,data,index=None):
if index:
for r in data:
#self.utcaware(r[index])
#r[index] = pytz.utc.localize(r[index])
r[index] = r[index].replace(tzinfo=datetime.timezone.utc)
else:
for r in data:
#self.utcaware(r)
#r = pytz.utc.localize(r)
r = r.replace(tzinfo=datetime.timezone.utc)
return data

View File

@ -4,7 +4,7 @@ import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
from ffmap.dbtools import FreifunkDB
from ffmap.mysqltools import FreifunkMySQL
from ffmap.misc import *
import lxml.etree
@ -13,8 +13,6 @@ import requests
from bson import SON
from contextlib import suppress
db = FreifunkDB().handle()
CONFIG = {
"vpn_netif": "fffVPN",
"vpn_netif_l2tp": "l2tp",
@ -26,9 +24,23 @@ CONFIG = {
router_rate_limit_list = {}
def import_nodewatcher_xml(mac, xml):
def delete_router(mysql,dbid):
cur = mysql.cursor()
cur.execute("DELETE FROM router WHERE id = %s",(dbid,))
cur.execute("DELETE FROM router_netif WHERE router = %s",(dbid,))
cur.execute("DELETE FROM router_ipv6 WHERE router = %s",(dbid,))
cur.execute("DELETE FROM router_neighbor WHERE router = %s",(dbid,))
cur.execute("DELETE FROM router_events WHERE router = %s",(dbid,))
cur.execute("DELETE FROM router_stats WHERE router = %s",(dbid,))
cur.execute("DELETE FROM router_stats_neighbor WHERE router = %s",(dbid,))
cur.execute("DELETE FROM router_stats_netif WHERE router = %s",(dbid,))
mysql.commit()
def import_nodewatcher_xml(mysql, mac, xml):
global router_rate_limit_list
cur = mysql.cursor()
t = utcnow()
if mac in router_rate_limit_list:
if (t - router_rate_limit_list[mac]) < datetime.timedelta(minutes=5):
@ -36,188 +48,335 @@ def import_nodewatcher_xml(mac, xml):
router_rate_limit_list[mac] = t
router_id = None
olddata = []
uptime = 0
events = []
status_comment = ""
try:
router = db.routers.find_one({"netifs.mac": mac.lower()}, {"stats": 0, "events": 0})
if router:
router_id = router["_id"]
cur.execute("SELECT router FROM router_netif WHERE mac = %s LIMIT 1",(mac.lower(),))
result = cur.fetchall()
if len(result)>0:
router_id = result[0]["router"]
cur.execute("SELECT sys_uptime AS uptime, firmware, hostname, hood, status, lat, lng FROM router WHERE id = %s LIMIT 1",(router_id,))
result = cur.fetchall()
if len(result)>0:
olddata = result[0]
uptime = olddata["uptime"]
router_update = parse_nodewatcher_xml(xml)
# keep hood up to date
if not "hood" in router_update:
if not router_update["hood"]:
# router didn't send his hood in XML
if "position" in router_update:
# router has new position info from netmon
router_update["hood"] = db.hoods.find_one({"position": {"$near": {"$geometry": router_update["position"]}}})["name"]
elif router and "position" in router:
lat = router_update.get("lat")
lng = router_update.get("lng")
if olddata and not lat and not lng:
# hoods might change as well
router_update["hood"] = db.hoods.find_one({"position": {"$near": {"$geometry": router["position"]}}})["name"]
if router:
lat = olddata.get("lat")
lng = olddata.get("lng")
if lat and lng:
router_update["hood"] = mysql.findone("""
SELECT name,
( acos( cos( radians(%s) )
* cos_lat
* cos( radians( lng ) - radians(%s) )
+ sin( radians(%s) ) * sin_lat
)
) AS distance
FROM
hoods
WHERE lat IS NOT NULL AND lng IS NOT NULL
ORDER BY
distance ASC
LIMIT 1
""",(lat,lng,lat,),"name")
else:
router_update["hood"] = None
if router_id:
# statistics
calculate_network_io(router, router_update)
db.routers.update_one({"_id": router_id}, {
"$set": router_update,
"$push": {"stats": SON([
("$each", new_router_stats(router, router_update)),
("$slice", int(CONFIG["router_stat_days"] * -1 * 24 * (3600 / 300)))
])
}})
calculate_network_io(cur, router_id, uptime, router_update)
ru = router_update
rus = router_update["system"]
ruh = router_update["hardware"]
ruso = router_update["software"]
cur.execute("""
UPDATE router
SET status = %s, hostname = %s, last_contact = %s, sys_time = %s, sys_uptime = %s, sys_memfree = %s, sys_membuff = %s, sys_memcache = %s,
sys_loadavg = %s, sys_procrun = %s, sys_proctot = %s, clients = %s, wan_uplink = %s, cpu = %s, chipset = %s, hardware = %s, os = %s,
batman = %s, kernel = %s, nodewatcher = %s, firmware = %s, firmware_rev = %s, description = %s, position_comment = %s, community = %s, hood = %s,
status_text = %s, contact = %s, lng = %s, lat = %s, neighbors = %s
WHERE id = %s
""",(
ru["status"],ru["hostname"],ru["last_contact"],rus["time"],rus["uptime"],rus["memory"]["free"],rus["memory"]["buffering"],rus["memory"]["caching"],
rus["loadavg"],rus["processes"]["runnable"],rus["processes"]["total"],rus["clients"],rus["has_wan_uplink"],ruh["cpu"],ruh["chipset"],ruh["name"],ruso["os"],
ruso["batman_adv"],ruso["kernel"],ruso["nodewatcher"],ruso["firmware"],ruso["firmware_rev"],ru["description"],ru["position_comment"],ru["community"],ru["hood"],
ru["system"]["status_text"],ru["system"]["contact"],ru["lng"],ru["lat"],rus["visible_neighbours"],router_id,))
cur.execute("DELETE FROM router_netif WHERE router = %s",(router_id,))
cur.execute("DELETE FROM router_ipv6 WHERE router = %s",(router_id,))
cur.execute("DELETE FROM router_neighbor WHERE router = %s",(router_id,))
uptime = 0
new_router_stats(mysql, router_id, uptime, router_update)
else:
# insert new router
router_update["created"] = utcnow()
router_update["stats"] = []
events = [] # don't fire sub-events of created events
router_update["events"] = [{
"time": utcnow(),
"type": "created",
}]
router_id = db.routers.insert_one(router_update).inserted_id
created = mysql.utcnow()
#events = [] # don't fire sub-events of created events
#router_update["events"] = [{
# "time": utcnow(),
# "type": "created",
#}]
ru = router_update
rus = router_update["system"]
ruh = router_update["hardware"]
ruso = router_update["software"]
cur.execute("""
INSERT INTO router (status, hostname, created, last_contact, sys_time, sys_uptime, sys_memfree, sys_membuff, sys_memcache,
sys_loadavg, sys_procrun, sys_proctot, clients, wan_uplink, cpu, chipset, hardware, os,
batman, kernel, nodewatcher, firmware, firmware_rev, description, position_comment, community, hood,
status_text, contact, lng, lat, neighbors)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""",(
ru["status"],ru["hostname"],created,ru["last_contact"],rus["time"],rus["uptime"],rus["memory"]["free"],rus["memory"]["buffering"],rus["memory"]["caching"],
rus["loadavg"],rus["processes"]["runnable"],rus["processes"]["total"],rus["clients"],rus["has_wan_uplink"],ruh["cpu"],ruh["chipset"],ruh["name"],ruso["os"],
ruso["batman_adv"],ruso["kernel"],ruso["nodewatcher"],ruso["firmware"],ruso["firmware_rev"],ru["description"],ru["position_comment"],ru["community"],ru["hood"],
ru["system"]["status_text"],ru["system"]["contact"],ru["lng"],ru["lat"],rus["visible_neighbours"],))
router_id = cur.lastrowid
events_append(mysql,router_id,"created","")
for n in router_update["netifs"]:
cur.execute("""
INSERT INTO router_netif (router, netif, mtu, rx_bytes, tx_bytes, rx, tx, fe80_addr, ipv4_addr, mac)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""",(
router_id,n["name"],n["mtu"],n["traffic"]["rx_bytes"],n["traffic"]["tx_bytes"],n["traffic"]["rx"],n["traffic"]["tx"],n["ipv6_fe80_addr"],n["ipv4_addr"],n["mac"],))
for a in n["ipv6_addrs"]:
cur.execute("INSERT INTO router_ipv6 (router, netif, ipv6) VALUES (%s, %s, %s)",(router_id,n["name"],a,))
for n in router_update["neighbours"]:
cur.execute("INSERT INTO router_neighbor (router, mac, quality, net_if, type) VALUES (%s, %s, %s, %s, %s)",(router_id,n["mac"],n["quality"],n["net_if"],n["type"],))
status = router_update["status"]
except ValueError as e:
import traceback
print("Warning: Unable to parse xml from %s: %s\n__%s" % (mac, e, traceback.format_exc().replace("\n", "\n__")))
if router:
db.routers.update_one({"_id": router_id}, {"$set": {
"status": "unknown",
"last_contact": utcnow()
}})
if router_id:
set_status(mysql,router_id,"unknown")
status = "unknown"
status_comment = "Invalid XML"
except OverflowError as e:
import traceback
print("Warning: Overflow Error when saving %s: %s\n__%s" % (mac, e, traceback.format_exc().replace("\n", "\n__")))
if router:
db.routers.update_one({"_id": router_id}, {"$set": {
"status": "unknown",
"last_contact": utcnow()
}})
if router_id:
set_status(mysql,router_id,"unknown")
status = "unknown"
status_comment = "Integer Overflow"
except Exception as e:
import traceback
print("Warning: Exception occurred when saving %s: %s\n__%s" % (mac, e, traceback.format_exc().replace("\n", "\n__")))
if router:
db.routers.update_one({"_id": router_id}, {"$set": {
"status": "unknown",
"last_contact": utcnow()
}})
if router_id:
set_status(mysql,router_id,"unknown")
status = "unknown"
status_comment = "Exception occurred"
if router_id:
if olddata:
# fire events
with suppress(KeyError, TypeError, UnboundLocalError):
if router["system"]["uptime"] > router_update["system"]["uptime"]:
events.append({
"time": utcnow(),
"type": "reboot",
})
if olddata["uptime"] > router_update["system"]["uptime"]:
events_append(mysql,router_id,"reboot","")
with suppress(KeyError, TypeError, UnboundLocalError):
if router["software"]["firmware"] != router_update["software"]["firmware"]:
events.append({
"time": utcnow(),
"type": "update",
"comment": "%s -> %s" % (router["software"]["firmware"], router_update["software"]["firmware"]),
})
if olddata["firmware"] != router_update["software"]["firmware"]:
events_append(mysql,router_id,"update",
"%s -> %s" % (olddata["firmware"], router_update["software"]["firmware"]))
#events.append({
# "time": utcnow(),
# "type": "update",
# "comment": "%s -> %s" % (olddata["firmware"], router_update["software"]["firmware"]),
#})
with suppress(KeyError, TypeError, UnboundLocalError):
if router["hostname"] != router_update["hostname"]:
events.append({
"time": utcnow(),
"type": "hostname",
"comment": "%s -> %s" % (router["hostname"], router_update["hostname"]),
})
if olddata["hostname"] != router_update["hostname"]:
events_append(mysql,router_id,"hostname",
"%s -> %s" % (olddata["hostname"], router_update["hostname"]))
#events.append({
# "time": utcnow(),
# "type": "hostname",
# "comment": "%s -> %s" % (olddata["hostname"], router_update["hostname"]),
#})
with suppress(KeyError, TypeError, UnboundLocalError):
if router["hood"] != router_update["hood"]:
events.append({
"time": utcnow(),
"type": "hood",
"comment": "%s -> %s" % (router["hood"], router_update["hood"]),
})
if olddata["hood"] != router_update["hood"]:
events_append(mysql,router_id,"hood",
"%s -> %s" % (olddata["hood"], router_update["hood"]))
#events.append({
# "time": utcnow(),
# "type": "hood",
# "comment": "%s -> %s" % (olddata["hood"], router_update["hood"]),
#})
with suppress(KeyError, TypeError):
if router["status"] != status:
event = {
"time": utcnow(),
"type": status,
}
with suppress(NameError):
event["comment"] = status_comment
events.append(event)
if olddata["status"] != status:
events_append(mysql,router_id,status,status_comment)
#event = {
# "time": utcnow(),
# "type": status,
#}
#with suppress(NameError):
# event["comment"] = status_comment
#events.append(event)
if len(events) > 0:
db.routers.update_one({"_id": router_id}, {"$push": {"events": SON([
("$each", events),
("$slice", -10),
])}})
def detect_offline_routers(mysql):
cur = mysql.cursor()
threshold=mysql.formatdt(utcnow() - datetime.timedelta(minutes=CONFIG["offline_threshold_minutes"]))
now=mysql.utcnow()
cur.execute("""
SELECT id
FROM router
WHERE last_contact < %s AND status <> 'offline'
""",(threshold,))
result = cur.fetchall()
for r in result:
cur.execute("""
INSERT INTO router_events ( router, time, type, comment )
VALUES (%s, %s, 'offline', '')
""",(r["id"],now,))
cur.execute("""
UPDATE router
SET status = 'offline', clients = 0
WHERE last_contact < %s AND status <> 'offline'
""",(threshold,))
mysql.commit()
def detect_offline_routers():
db.routers.update_many({
"last_contact": {"$lt": utcnow() - datetime.timedelta(minutes=CONFIG["offline_threshold_minutes"])},
"status": {"$ne": "offline"}
}, {
"$set": {"status": "offline", "system.clients": 0},
"$push": {"events": {
"time": utcnow(),
"type": "offline"
}
}})
def delete_orphaned_routers(mysql):
cur = mysql.cursor()
threshold=mysql.formatdt(utcnow() - datetime.timedelta(days=CONFIG["orphan_threshold_days"]))
cur.execute("""
DELETE r, e, i, nb, net FROM router AS r
INNER JOIN router_events AS e ON r.id = e.router
INNER JOIN router_ipv6 AS i ON r.id = i.router
INNER JOIN router_neighbor AS nb ON r.id = nb.router
INNER JOIN router_netif AS net ON r.id = net.router
WHERE r.last_contact < %s AND r.status <> 'offline'
""",(threshold,))
mysql.commit()
def delete_orphaned_routers():
db.routers.delete_many({
"last_contact": {"$lt": utcnow() - datetime.timedelta(days=CONFIG["orphan_threshold_days"])},
"status": "offline"
})
def delete_old_stats(mysql):
threshold=mysql.formatdt(utcnow() - datetime.timedelta(days=CONFIG["router_stat_days"]))
mysql.execute("""
DELETE FROM router_stats
WHERE time < %s
""",(threshold,))
def new_router_stats(router, router_update):
if router["system"]["uptime"] < router_update["system"]["uptime"]:
netifs = {}
neighbours = {}
mysql.execute("""
DELETE FROM router_stats_neighbor
WHERE time < %s
""",(threshold,))
mysql.execute("""
DELETE FROM router_stats_netif
WHERE time < %s
""",(threshold,))
mysql.commit()
def events_append(mysql,router_id,event,comment):
mysql.execute("""
INSERT INTO router_events (router, time, type, comment)
VALUES (%s, %s, %s, %s)
""",(
router_id,
mysql.utcnow(),
event,
comment,))
def set_status(mysql,router_id,status):
mysql.execute("""
UPDATE router
SET status = %s, last_contact = %s
WHERE id = %s
""",(
status,
mysql.utcnow(),
router_id,))
def new_router_stats(mysql, router_id, uptime, router_update):
if uptime < router_update["system"]["uptime"]:
time = mysql.utcnow()
mysql.execute("""
INSERT INTO router_stats (router, time, sys_memfree, sys_membuff, sys_memcache, loadavg, sys_procrun, sys_proctot, clients)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
""",(
router_id,
time,
router_update["system"]["memory"]['free'],
router_update["system"]["memory"]['buffering'],
router_update["system"]["memory"]['caching'],
router_update["system"]["loadavg"],
router_update["system"]["processes"]['runnable'],
router_update["system"]["processes"]['total'],
router_update["system"]["clients"],))
for netif in router_update["netifs"]:
# sanitize name
name = netif["name"].replace(".", "").replace("$", "")
with suppress(KeyError):
netifs[name] = {"rx": netif["traffic"]["rx"], "tx": netif["traffic"]["tx"]}
mysql.execute("""
INSERT INTO router_stats_netif (router, netif, time, rx, tx)
VALUES (%s, %s, %s, %s, %s)
""",(
router_id,
name,
time,
netif["traffic"]["rx"],
netif["traffic"]["tx"],))
for neighbour in router_update["neighbours"]:
with suppress(KeyError):
neighbours[neighbour["mac"]] = neighbour["quality"]
return [{
"time": utcnow(),
"netifs": netifs,
"neighbours": neighbours,
"memory": router_update["system"]["memory"],
"loadavg": router_update["system"]["loadavg"],
"processes": router_update["system"]["processes"],
"clients": router_update["system"]["clients"],
}]
else:
# don't push old data
return []
mysql.execute("""
INSERT INTO router_stats_neighbor (router, mac, time, quality)
VALUES (%s, %s, %s, %s)
""",(
router_id,
neighbour["mac"],
time,
neighbour["quality"],))
def calculate_network_io(router, router_update):
def calculate_network_io(cur, router_id, uptime, router_update):
"""
router: old router dict
router_update: new router dict (which will be updated with new data)
"""
cur.execute("SELECT netif, rx_bytes, tx_bytes, rx, tx FROM router_netif WHERE router = %s",(router_id,));
results = cur.fetchall()
with suppress(KeyError, StopIteration):
if router["system"]["uptime"] < router_update["system"]["uptime"]:
timediff = router_update["system"]["uptime"] - router["system"]["uptime"]
for netif in router["netifs"]:
netif_update = next(filter(lambda n: n["name"] == netif["name"], router_update["netifs"]))
rx_diff = netif_update["traffic"]["rx_bytes"] - netif["traffic"]["rx_bytes"]
tx_diff = netif_update["traffic"]["tx_bytes"] - netif["traffic"]["tx_bytes"]
if uptime < router_update["system"]["uptime"]:
timediff = router_update["system"]["uptime"] - uptime
for row in results:
netif_update = next(filter(lambda n: n["name"] == row["netif"], router_update["netifs"]))
rx_diff = netif_update["traffic"]["rx_bytes"] - int(row["rx_bytes"])
tx_diff = netif_update["traffic"]["tx_bytes"] - int(row["tx_bytes"])
if rx_diff >= 0 and tx_diff >= 0:
netif_update["traffic"]["rx"] = int(rx_diff / timediff)
netif_update["traffic"]["tx"] = int(tx_diff / timediff)
else:
for netif in router["netifs"]:
netif_update = next(filter(lambda n: n["name"] == netif["name"], router_update["netifs"]))
netif_update["traffic"]["rx"] = netif["traffic"]["rx"]
netif_update["traffic"]["tx"] = netif["traffic"]["tx"]
for row in results:
netif_update = next(filter(lambda n: n["name"] == row["netif"], router_update["netifs"]))
netif_update["traffic"]["rx"] = int(row["rx"])
netif_update["traffic"]["tx"] = int(row["tx"])
return uptime
def parse_nodewatcher_xml(xml):
try:
@ -227,11 +386,11 @@ def parse_nodewatcher_xml(xml):
router_update = {
"status": tree.xpath("/data/system_data/status/text()")[0],
"hostname": tree.xpath("/data/system_data/hostname/text()")[0],
"last_contact": utcnow(),
"last_contact": utcnow().strftime('%Y-%m-%d %H:%M:%S'),
"neighbours": [],
"netifs": [],
"system": {
"time": datetime.datetime.fromtimestamp(int(tree.xpath("/data/system_data/local_time/text()")[0])),
"time": datetime.datetime.fromtimestamp(int(tree.xpath("/data/system_data/local_time/text()")[0])).strftime('%Y-%m-%d %H:%M:%S'),
"uptime": int(float(tree.xpath("/data/system_data/uptime/text()")[0])),
"memory": {
"free": int(tree.xpath("/data/system_data/memory_free/text()")[0]),
@ -276,38 +435,41 @@ def parse_nodewatcher_xml(xml):
router_update["hardware"]["name"] = "Legacy"
router_update["hardware"]["name"] = tree.xpath("/data/system_data/model/text()")[0]
# data.system_data.chipset
with suppress(IndexError):
router_update["hardware"]["chipset"] = "Unknown"
router_update["hardware"]["chipset"] = tree.xpath("/data/system_data/chipset/text()")[0]
# data.system_data.description
with suppress(IndexError):
router_update["description"] = ""
router_update["description"] = tree.xpath("/data/system_data/description/text()")[0]
# data.system_data.position_comment
with suppress(IndexError):
router_update["position_comment"] = ""
router_update["position_comment"] = tree.xpath("/data/system_data/position_comment/text()")[0]
# data.system_data.firmware_community
with suppress(IndexError):
router_update["community"] = ""
router_update["community"] = tree.xpath("/data/system_data/firmware_community/text()")[0]
# data.system_data.hood
with suppress(IndexError):
router_update["hood"] = ""
router_update["hood"] = tree.xpath("/data/system_data/hood/text()")[0].lower()
# data.system_data.status_text
with suppress(IndexError):
router_update["system"]["status_text"] = ""
router_update["system"]["status_text"] = tree.xpath("/data/system_data/status_text/text()")[0]
# data.system_data.contact
with suppress(IndexError):
router_update["system"]["contact"] = ""
#router_update["user"] = ""
router_update["system"]["contact"] = tree.xpath("/data/system_data/contact/text()")[0]
user = db.users.find_one({"email": router_update["system"]["contact"]})
if user:
# post-netmon router gets its user assigned
router_update["user"] = {"nickname": user["nickname"], "_id": user["_id"]}
#user = db.users.find_one({"email": router_update["system"]["contact"]})
#if user:
# # post-netmon router gets its user assigned
# #router_update["user"] = {"nickname": user["nickname"], "_id": user["_id"]}
# router_update["user"] = user["nickname"]
# data.system_data.geo
with suppress(AssertionError, IndexError):
@ -316,10 +478,8 @@ def parse_nodewatcher_xml(xml):
assert lng != 0
assert lat != 0
router_update["position"] = {
"type": "Point",
"coordinates": [lng, lat]
}
router_update["lng"] = lng
router_update["lat"] = lat
#FIXME: tmp workaround to get similar hardware names
router_update["hardware"]["name"] = router_update["hardware"]["name"].replace("nanostation-m", "Ubiquiti Nanostation M")
@ -341,15 +501,19 @@ def parse_nodewatcher_xml(xml):
"traffic": {
"rx_bytes": int(netif.xpath("traffic_rx/text()")[0]),
"tx_bytes": int(netif.xpath("traffic_tx/text()")[0]),
"rx": 0,
"tx": 0,
},
}
with suppress(IndexError):
interface["ipv6_fe80_addr"] = ""
interface["ipv6_fe80_addr"] = netif.xpath("ipv6_link_local_addr/text()")[0].lower().split("/")[0]
interface["ipv6_addrs"] = []
if len(netif.xpath("ipv6_addr/text()")) > 0:
interface["ipv6_addrs"] = []
for ipv6_addr in netif.xpath("ipv6_addr/text()"):
interface["ipv6_addrs"].append(ipv6_addr.lower().split("/")[0])
with suppress(IndexError):
interface["ipv4_addr"] = ""
interface["ipv4_addr"] = netif.xpath("ipv4_addr/text()")[0]
with suppress(IndexError):
@ -379,8 +543,8 @@ def parse_nodewatcher_xml(xml):
"mac": o_mac.lower(),
"quality": int(o_link_quality),
"net_if": o_out_if,
"type": "l2"
}
set_hostname_and_pos_for_neighbour(neighbour)
router_update["neighbours"].append(neighbour)
l3_neighbours = get_l3_neighbours(tree)
@ -392,20 +556,6 @@ def parse_nodewatcher_xml(xml):
except (AssertionError, lxml.etree.XMLSyntaxError, IndexError) as e:
raise ValueError("%s: %s" % (e.__class__.__name__, str(e)))
def set_hostname_and_pos_for_neighbour(neighbour):
with suppress(AssertionError, TypeError):
neighbour_router = db.routers.find_one(
{"netifs.mac": neighbour["mac"]}, {"hostname": 1, "position": 1})
neighbour["_id"] = neighbour_router["_id"]
neighbour["hostname"] = neighbour_router["hostname"]
assert "position" in neighbour_router
assert "coordinates" in neighbour_router["position"]
assert neighbour_router["position"]["coordinates"][0] != 0
assert neighbour_router["position"]["coordinates"][1] != 0
neighbour["position"] = neighbour_router["position"]
def get_l3_neighbours(tree):
l3_neighbours = list()
for neighbour in tree.xpath("/data/babel_neighbours/*"):
@ -417,7 +567,6 @@ def get_l3_neighbours(tree):
"net_if": out_if,
"type": "l3"
}
set_hostname_and_pos_for_neighbour(neighbour)
l3_neighbours.append(neighbour)
return l3_neighbours

View File

@ -4,91 +4,102 @@ import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
from ffmap.dbtools import FreifunkDB
from ffmap.mysqltools import FreifunkMySQL
from ffmap.misc import *
db = FreifunkDB().handle()
CONFIG = {
"global_stat_days": 30,
}
def total_clients():
r = db.routers.aggregate([{"$group": {
"_id": None,
"clients": {"$sum": "$system.clients"}
}}])
return next(r)["clients"]
def total_clients(mysql):
return mysql.findone("""
SELECT SUM(clients) AS clients
FROM router
""",(),"clients")
def router_status():
r = db.routers.aggregate([{"$group": {
"_id": "$status",
"count": {"$sum": 1}
}}])
def router_status(mysql):
return mysql.fetchdict("""
SELECT status, COUNT(id) AS count
FROM router
GROUP BY status
""",(),"status","count")
def router_models(mysql):
return mysql.fetchdict("""
SELECT hardware, COUNT(id) AS count
FROM router
GROUP BY hardware
""",(),"hardware","count")
def router_firmwares(mysql):
return mysql.fetchdict("""
SELECT firmware, COUNT(id) AS count
FROM router
GROUP BY firmware
""",(),"firmware","count")
def hoods(mysql):
data = mysql.fetchall("""
SELECT hood, status, COUNT(id) AS count
FROM router
GROUP BY hood, status
""")
result = {}
for rs in r:
result[rs["_id"]] = rs["count"]
for rs in data:
if not rs["hood"]:
rs["hood"] = "default"
if not rs["hood"] in result:
result[rs["hood"]] = {}
result[rs["hood"]][rs["status"]] = rs["count"]
return result
def router_models():
r = db.routers.aggregate([{"$group": {
"_id": "$hardware.name",
"count": {"$sum": 1}
}}])
def hoods_sum(mysql):
data = mysql.fetchall("""
SELECT hood, COUNT(id) AS count, SUM(clients) AS clients
FROM router
GROUP BY hood
""")
result = {}
for rs in r:
result[rs["_id"]] = rs["count"]
for rs in data:
if not rs["hood"]:
rs["hood"] = "default"
result[rs["hood"]] = {"routers": rs["count"], "clients": rs["clients"]}
return result
def router_firmwares():
r = db.routers.aggregate([{"$group": {
"_id": "$software.firmware",
"count": {"$sum": 1}
}}])
def record_global_stats(mysql):
threshold=mysql.formatdt(utcnow() - datetime.timedelta(days=CONFIG["global_stat_days"]))
time = mysql.utcnow()
status = router_status(mysql)
old = mysql.findone("SELECT time FROM stats_global WHERE time = %s LIMIT 1",(time,))
if old:
mysql.execute("""
UPDATE stats_global
SET clients = %s, online = %s, offline = %s, unknown = %s
WHERE time = %s
""",(total_clients(mysql),status.get("online",0),status.get("offline",0),status.get("unknown",0),time,))
else:
mysql.execute("""
INSERT INTO stats_global (time, clients, online, offline, unknown)
VALUES (%s, %s, %s, %s, %s)
""",(time,total_clients(mysql),status.get("online",0),status.get("offline",0),status.get("unknown",0),))
mysql.execute("""
DELETE FROM stats_global
WHERE time < %s
""",(threshold,))
mysql.commit()
def router_user_sum(mysql):
data = mysql.fetchall("""
SELECT contact, COUNT(id) AS count, SUM(clients) AS clients
FROM router
GROUP BY contact
""")
result = {}
for rs in r:
result[rs["_id"]] = rs["count"]
return result
def hoods():
r = db.routers.aggregate([{"$group": {
"_id": {"hood": "$hood", "status": "$status"},
"count": {"$sum": 1},
}}])
result = {}
for rs in r:
if not "hood" in rs["_id"]:
rs["_id"]["hood"] = "default"
if not rs["_id"]["hood"] in result:
result[rs["_id"]["hood"]] = {}
result[rs["_id"]["hood"]][rs["_id"]["status"]] = rs["count"]
return result
def hoods_sum():
r = db.routers.aggregate([{"$group": {
"_id": "$hood",
"count": {"$sum": 1},
"clients": {"$sum": "$system.clients"}
}}])
result = {}
for rs in r:
if not rs["_id"]:
rs["_id"] = "default"
result[rs["_id"]] = {"routers": rs["count"], "clients": rs["clients"]}
return result
def record_global_stats():
db.stats.insert_one({
"time": utcnow(),
"router_status": router_status(),
"total_clients": total_clients()
})
def router_user_sum():
r = db.routers.aggregate([{"$group": {
"_id": "$user.nickname",
"count": {"$sum": 1},
"clients": {"$sum": "$system.clients"}
}}])
result = {}
for rs in r:
if rs["_id"]:
result[rs["_id"]] = {"routers": rs["count"], "clients": rs["clients"]}
for rs in data:
if rs["contact"]:
result[rs["contact"]] = {"routers": rs["count"], "clients": rs["clients"]}
return result

View File

@ -4,13 +4,11 @@ import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
from ffmap.dbtools import FreifunkDB
from ffmap.mysqltools import FreifunkMySQL
from ffmap.misc import *
from werkzeug.security import generate_password_hash, check_password_hash
db = FreifunkDB().handle()
class AccountWithEmailExists(Exception):
pass
@ -24,67 +22,107 @@ class InvalidToken(Exception):
pass
def register_user(nickname, email, password):
user_with_nick = db.users.find_one({"nickname": nickname})
user_with_email = db.users.find_one({"email": email})
mysql = FreifunkMySQL()
user_with_nick = mysql.findone("SELECT id, email FROM users WHERE nickname = %s LIMIT 1",(nickname,))
user_with_email = mysql.findone("SELECT id FROM users WHERE email = %s LIMIT 1",(email,),"id")
pw = generate_password_hash(password)
if user_with_email:
mysql.close()
raise AccountWithEmailExists()
elif user_with_nick and "email" in user_with_nick:
elif user_with_nick and user_with_nick["email"]:
mysql.close()
raise AccountWithNicknameExists()
else:
user_update = {
"nickname": nickname,
"password": generate_password_hash(password),
"email": email,
"created": utcnow()
}
time = mysql.utcnow()
if user_with_nick:
db.users.update_one({"_id": user_with_nick["_id"]}, {"$set": user_update})
return user_with_nick["_id"]
mysql.execute("""
UPDATE users
SET password = %s, email = %s, created = %s, token = NULL
WHERE id = %s
LIMIT 1
""",(pw,email,time,user_with_nick["id"],))
mysql.commit()
mysql.close()
return user_with_nick["id"]
else:
return db.users.insert_one(user_update).inserted_id
mysql.execute("""
INSERT INTO users (nickname, password, email, created, token)
VALUES (%s, %s, %s, %s, NULL)
""",(nickname,pw,email,time,))
userid = mysql.cursor().lastrowid
mysql.commit()
mysql.close()
return userid
def check_login_details(nickname, password):
user = db.users.find_one({"nickname": nickname})
mysql = FreifunkMySQL()
user = mysql.findone("SELECT * FROM users WHERE nickname = %s LIMIT 1",(nickname,))
mysql.close()
if user and check_password_hash(user.get('password', ''), password):
return user
else:
return False
def reset_user_password(email, token=None, password=None):
user = db.users.find_one({"email": email})
def reset_user_password(mysql, email, token=None, password=None):
userid = mysql.findone("SELECT id FROM users WHERE email = %s LIMIT 1",(email,),"id")
if not user:
raise AccountNotExisting()
elif password:
if user.get("token") == token:
db.users.update_one({"_id": user["_id"]}, {
"$set": {"password": generate_password_hash(password)},
"$unset": {"token": 1},
})
mysql.execute("""
UPDATE users
SET password = %s, token = NULL
WHERE id = %s
LIMIT 1
""",(generate_password_hash(password),userid,))
mysql.commit()
else:
raise InvalidToken()
elif token:
db.users.update_one({"_id": user["_id"]}, {"$set": {"token": token}})
mysql.execute("""
UPDATE users
SET token = %s
WHERE id = %s
LIMIT 1
""",(token,userid,))
mysql.commit()
def set_user_password(nickname, password):
user = db.users.find_one({"nickname": nickname})
if not user:
def set_user_password(mysql, nickname, password):
userid = mysql.findone("SELECT id FROM users WHERE nickname = %s LIMIT 1",(nickname,),"id")
if not userid:
raise AccountNotExisting()
elif password:
db.users.update_one({"_id": user["_id"]}, {
"$set": {"password": generate_password_hash(password)},
})
mysql.execute("""
UPDATE users
SET password = %s
WHERE id = %s
LIMIT 1
""",(generate_password_hash(password),userid,))
mysql.commit()
def set_user_email(nickname, email):
user = db.users.find_one({"nickname": nickname})
user_with_email = db.users.find_one({"email": email})
if user_with_email:
def set_user_email(mysql, nickname, email):
userid = mysql.findone("SELECT id FROM users WHERE nickname = %s LIMIT 1",(nickname,),"id")
useridemail = mysql.findone("SELECT id FROM users WHERE email = %s LIMIT 1",(email,),"id")
if useridemail:
raise AccountWithEmailExists()
if not user:
if not userid:
raise AccountNotExisting()
elif email:
db.users.update_one({"_id": user["_id"]}, {
"$set": {"email": email},
})
mysql.execute("""
UPDATE users
SET email = %s
WHERE id = %s
LIMIT 1
""",(email,userid,))
mysql.commit()
def set_user_admin(nickname, admin):
db.users.update({"nickname": nickname}, {"$set": {"admin": admin}})
def set_user_admin(mysql, nickname, admin):
mysql.execute("""
UPDATE users
SET admin = %s
WHERE nickname = %s
LIMIT 1
""",(admin,nickname,))
mysql.commit()

View File

@ -2,11 +2,10 @@
from ffmap.routertools import *
from ffmap.maptools import *
from ffmap.dbtools import FreifunkDB
from ffmap.mysqltools import FreifunkMySQL
from ffmap.stattools import record_global_stats
from flask import Blueprint, request, make_response, redirect, url_for, jsonify, Response
from pymongo import MongoClient
from bson.json_util import dumps as bson2json
import json
@ -14,23 +13,39 @@ from operator import itemgetter
api = Blueprint("api", __name__)
db = FreifunkDB().handle()
# map ajax
@api.route('/get_nearest_router')
def get_nearest_router():
res_router = db.routers.find_one(
{"position": {"$near": {"$geometry": {
"type": "Point",
"coordinates": [float(request.args.get("lng")), float(request.args.get("lat"))]
}}}},
{
"hostname": 1,
"neighbours": 1,
"position": 1,
"description": 1,
}
)
lng = float(request.args.get("lng"))
lat = float(request.args.get("lat"))
mysql = FreifunkMySQL()
res_router = mysql.findone("""
SELECT id, hostname, lat, lng, description,
( acos( cos( radians(%s) )
* cos( radians( lat ) )
* cos( radians( lng ) - radians(%s) )
+ sin( radians(%s) ) * sin( radians( lat ) )
)
) AS distance
FROM
router
WHERE lat IS NOT NULL AND lng IS NOT NULL
ORDER BY
distance ASC
LIMIT 1
""",(lat,lng,lat,))
res_router["neighbours"] = mysql.fetchall("""
SELECT nb.mac, nb.quality, nb.net_if, r.hostname, r.id
FROM router_neighbor AS nb
INNER JOIN (
SELECT router, mac FROM router_netif GROUP BY mac, router
) AS net ON nb.mac = net.mac
INNER JOIN router as r ON net.router = r.id
WHERE nb.router = %s""",(res_router["id"],))
mysql.close()
r = make_response(bson2json(res_router))
r.mimetype = 'application/json'
return r
@ -38,71 +53,104 @@ def get_nearest_router():
# router by mac (link from router webui)
@api.route('/get_router_by_mac/<mac>')
def get_router_by_mac(mac):
res_routers = db.routers.find({"netifs.mac": mac.lower()}, {"_id": 1})
if res_routers.count() != 1:
mysql = FreifunkMySQL()
res_routers = mysql.fetchall("""
SELECT id
FROM router
INNER JOIN router_netif ON router.id = router_netif.router
WHERE mac = %s
GROUP BY mac, id
""",(mac.lower(),))
mysql.close()
if len(res_routers) != 1:
return redirect(url_for("router_list", q="netifs.mac:%s" % mac))
else:
return redirect(url_for("router_info", dbid=next(res_routers)["_id"]))
return redirect(url_for("router_info", dbid=res_routers[0]["id"]))
@api.route('/alfred', methods=['GET', 'POST'])
def alfred():
#set_alfred_data = {65: "hallo", 66: "welt"}
set_alfred_data = {}
r = make_response(json.dumps(set_alfred_data))
#import cProfile, pstats, io
#pr = cProfile.Profile()
#pr.enable()
if request.method == 'POST':
alfred_data = request.get_json()
if alfred_data:
# load router status xml data
for mac, xml in alfred_data.get("64", {}).items():
import_nodewatcher_xml(mac, xml)
r.headers['X-API-STATUS'] = "ALFRED data imported"
detect_offline_routers()
delete_orphaned_routers()
record_global_stats()
update_mapnik_csv()
#pr.disable()
#s = io.StringIO()
#sortby = 'cumulative'
#ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
#ps.print_stats()
#print(s.getvalue())
r.mimetype = 'application/json'
return r
try:
mysql = FreifunkMySQL()
#cur = mysql.cursor()
#set_alfred_data = {65: "hallo", 66: "welt"}
set_alfred_data = {}
r = make_response(json.dumps(set_alfred_data))
#import cProfile, pstats, io
#pr = cProfile.Profile()
#pr.enable()
if request.method == 'POST':
alfred_data = request.get_json()
if alfred_data:
# load router status xml data
for mac, xml in alfred_data.get("64", {}).items():
import_nodewatcher_xml(mysql, mac, xml)
mysql.commit()
r.headers['X-API-STATUS'] = "ALFRED data imported"
detect_offline_routers(mysql)
delete_orphaned_routers(mysql)
delete_old_stats(mysql)
record_global_stats(mysql)
update_mapnik_csv(mysql)
mysql.close()
#pr.disable()
#s = io.StringIO()
#sortby = 'cumulative'
#ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
#ps.print_stats()
#print(s.getvalue())
r.mimetype = 'application/json'
return r
except Exception as e: # most generic exception you can catch
logf = open("/data/fff/fail00.txt", "a")
logf.write("{}\n".format(e))
logf.close()
# https://github.com/ffansbach/de-map/blob/master/schema/nodelist-schema-1.0.0.json
@api.route('/nodelist')
def nodelist():
router_data = db.routers.find(projection=['_id', 'hostname', 'status', 'system.clients', 'position.coordinates', 'last_contact'])
mysql = FreifunkMySQL()
router_data = mysql.fetchall("""
SELECT id, hostname, status, clients, last_contact, lat, lng
FROM router
""",())
mysql.utcawaretuple(router_data,"last_contact")
mysql.close()
nodelist_data = {'version': '1.0.0'}
nodelist_data['nodes'] = list()
for router in router_data:
nodelist_data['nodes'].append(
{
'id': str(router['_id']),
'id': str(router['id']),
'name': router['hostname'],
'node_type': 'AccessPoint',
'href': 'https://monitoring.freifunk-franken.de/routers/' + str(router['_id']),
'href': 'https://monitoring.freifunk-franken.de/routers/' + str(router['id']),
'status': {
'online': router['status'] == 'online',
'clients': router['system']['clients'],
'clients': router['clients'],
'lastcontact': router['last_contact'].isoformat()
}
}
)
if 'position' in router:
nodelist_data['nodes'][-1]['position'] = {
'lat': router['position']['coordinates'][1],
'long': router['position']['coordinates'][0]
'lat': router['lat'],
'long': router['lng']
}
return jsonify(nodelist_data)
@api.route('/wifianal/<selecthood>')
def wifianal(selecthood):
router_data = db.routers.find({'hood': selecthood}, projection=['hostname','netifs'])
mysql = FreifunkMySQL()
router_data = mysql.fetchall("""
SELECT hostname, mac, netif
FROM router
INNER JOIN router_netif ON router.id = router_netif.router
WHERE hood = %s
GROUP BY id, netif
""",(selecthood,))
mysql.close()
s = "#----------WifiAnalyzer alias file----------\n"
s += "# \n"
@ -118,64 +166,76 @@ def wifianal(selecthood):
s += "# \n"
for router in router_data:
if not 'netifs' in router:
if not router['mac']:
continue
for netif in router['netifs']:
if not 'mac' in netif:
continue
if netif['name'] == 'br-mesh':
s += netif["mac"] + "|Mesh_" + router['hostname'] + "\n"
elif netif['name'] == 'w2ap':
s += netif["mac"] + "|" + router['hostname'] + "\n"
elif netif['name'] == 'w5ap':
s += netif["mac"] + "|W5_" + router['hostname'] + "\n"
elif netif['name'] == 'w5mesh':
s += netif["mac"] + "|W5Mesh_" + router['hostname'] + "\n"
if router["netif"] == 'br-mesh':
s += router["mac"] + "|Mesh_" + router['hostname'] + "\n"
elif router["netif"] == 'w2ap':
s += router["mac"] + "|" + router['hostname'] + "\n"
elif router["netif"] == 'w5ap':
s += router["mac"] + "|W5_" + router['hostname'] + "\n"
elif router["netif"] == 'w5mesh':
s += router["mac"] + "|W5Mesh_" + router['hostname'] + "\n"
return Response(s,mimetype='text/plain')
@api.route('/routers')
def routers():
router_data = db.routers.find(projection=['_id', 'hostname', 'status', 'hood', 'user.nickname', 'hardware.name', 'software.firmware', 'system.clients', 'position.coordinates', 'last_contact', 'netifs'])
# Suppresses routers without br-mesh
mysql = FreifunkMySQL()
router_data = mysql.fetchall("""
SELECT router.id, hostname, status, hood, contact, nickname, hardware, firmware, clients, lat, lng, last_contact, mac
FROM router
INNER JOIN router_netif ON router.id = router_netif.router
LEFT JOIN users ON router.contact = users.email
WHERE netif = 'br-mesh'
""")
mysql.utcawaretuple(router_data,"last_contact")
router_net = mysql.fetchall("""
SELECT id, netif, COUNT(router) AS count
FROM router
INNER JOIN router_netif ON router.id = router_netif.router
GROUP BY id, netif
""")
mysql.close()
net_dict = {}
for rs in router_net:
if not rs["id"] in net_dict:
net_dict[rs["id"]] = []
net_dict[rs["id"]].append(rs["netif"])
nodelist_data = {'version': '1.0.0'}
nodelist_data['nodes'] = list()
for router in router_data:
hood = ""
user = ""
firmware = ""
mac = ""
fastd = 0
l2tp = 0
if 'hood' in router:
hood = router['hood']
if 'user' in router:
user = router['user']['nickname']
if 'software' in router:
firmware = router['software']['firmware']
hood = router['hood']
user = router['nickname']
firmware = router['firmware']
mac = router['mac']
if 'netifs' in router:
for netif in router['netifs']:
if netif['name'] == 'fffVPN':
if router["id"] in net_dict:
for netif in net_dict[router["id"]]:
if netif == 'fffVPN':
fastd += 1
elif netif['name'].startswith('l2tp'):
elif netif.startswith('l2tp'):
l2tp += 1
elif netif['name'] == 'br-mesh' and 'mac' in netif:
mac = netif["mac"]
#elif netif['netif'] == 'br-mesh' and 'mac' in netif:
# mac = netif["mac"]
nodelist_data['nodes'].append(
{
'id': str(router['_id']),
'id': str(router['id']),
'name': router['hostname'],
'mac': mac,
'hood': hood,
'status': router['status'],
'user': user,
'hardware': router['hardware']['name'],
'hardware': router['hardware'],
'firmware': firmware,
'href': 'https://monitoring.freifunk-franken.de/routers/' + str(router['_id']),
'clients': router['system']['clients'],
'href': 'https://monitoring.freifunk-franken.de/routers/' + str(router['id']),
'clients': router['clients'],
'lastcontact': router['last_contact'].isoformat(),
'uplink': {
'fastd': fastd,
@ -183,101 +243,118 @@ def routers():
}
}
)
if 'position' in router:
nodelist_data['nodes'][-1]['position'] = {
'lat': router['position']['coordinates'][1],
'lng': router['position']['coordinates'][0]
}
nodelist_data['nodes'][-1]['position'] = {
'lat': router['lat'],
'lng': router['lng']
}
return jsonify(nodelist_data)
@api.route('/nopos')
def no_position():
router_data = db.routers.find(filter={'position': { '$exists': False}}, projection=['_id', 'hostname', 'system.contact', 'user.nickname', 'software.firmware'])
#nodelist_data = dict()
nodelist_data = list()
for router in router_data:
nodelist_data.append(
{
'name': router['hostname'],
'href': 'https://monitoring.freifunk-franken.de/routers/' + str(router['_id']),
'firmware': router['software']['firmware']
}
)
if 'system' in router and 'contact' in router['system']:
nodelist_data[-1]['contact'] = router['system']['contact']
if 'user' in router and 'nickname' in router['user']:
nodelist_data[-1]['owner'] = router['user']['nickname']
else:
nodelist_data[-1]['owner'] = ''
mysql = FreifunkMySQL()
router_data = mysql.fetchall("""
SELECT router.id, hostname, contact, nickname, firmware
FROM router
LEFT JOIN users ON router.contact = users.email
WHERE lat IS NULL OR lng IS NULL
""")
mysql.close()
#nodelist_data = dict()
nodelist_data = list()
for router in router_data:
nick = router['nickname']
if not nick:
nick = ""
nodelist_data.append(
{
'name': router['hostname'],
'href': 'https://monitoring.freifunk-franken.de/routers/' + str(router['id']),
'firmware': router['firmware'],
'contact': router['contact'],
'owner': nick
}
)
nodelist_data2 = sorted(nodelist_data, key=itemgetter('owner'), reverse=False)
nodes = dict()
nodes['nodes'] = list(nodelist_data2)
nodelist_data2 = sorted(nodelist_data, key=itemgetter('owner'), reverse=False)
nodes = dict()
nodes['nodes'] = list(nodelist_data2)
return jsonify(nodes)
return jsonify(nodes)
import pymongo
@api.route('/routers_by_nickname/<nickname>')
def get_routers_by_nickname(nickname):
try:
user = db.users.find_one({"nickname": nickname})
assert user
except AssertionError:
return "User not found"
nodelist_data = dict()
nodelist_data['nodes'] = list()
routers=db.routers.find({"user._id": user["_id"]}, {"hostname": 1, "netifs": 1, "_id": 1}).sort("hostname", pymongo.ASCENDING)
for router in routers:
#print(router['hostname'])
for netif in router['netifs']:
if netif['name'] == 'br-mesh':
#print(netif['ipv6_fe80_addr'])
nodelist_data['nodes'].append(
{
'name': router['hostname'],
'oid': str(router['_id']),
'ipv6_fe80_addr': netif['ipv6_fe80_addr']
}
)
return jsonify(nodelist_data)
mysql = FreifunkMySQL()
users = mysql.fetchall("""
SELECT id
FROM users
WHERE nickname = %s
LIMIT 1
""",(nickname,))
if len(users)==0:
mysql.close()
return "User not found"
nodelist_data = dict()
nodelist_data['nodes'] = list()
routers = mysql.fetchall("""
SELECT router.id, hostname, contact, nickname, firmware, mac, fe80_addr
FROM router
INNER JOIN users ON router.contact = users.email
INNER JOIN router_netif ON router.id = router_netif.router
WHERE nickname = %s AND netif = 'br-mesh'
ORDER BY hostname ASC
""",(nickname,))
mysql.close()
for router in routers:
nodelist_data['nodes'].append(
{
'name': router['hostname'],
'oid': str(router['id']),
'mac': router['mac'],
'ipv6_fe80_addr': router['fe80_addr']
}
)
return jsonify(nodelist_data)
@api.route('/routers_by_keyxchange_id/<keyxchange_id>')
def get_routers_by_keyxchange_id(keyxchange_id):
try:
hood = db.hoods.find_one({"keyxchange_id": int(keyxchange_id)})
assert hood
except AssertionError:
return "Hood not found"
nodelist_data = dict()
nodelist_data['nodes'] = list()
routers = db.routers.find({"hood": hood["name"]}, {"hostname": 1, "hardware": 1, "netifs": 1, "_id": 1, "software": 1, "position": 1, "system": 1, "position_comment": 1, "description": 1}).sort("hostname", pymongo.ASCENDING)
for router in routers:
for netif in router['netifs']:
if netif['name'] == 'br-mesh':
if 'ipv6_fe80_addr' not in netif:
continue
nodelist_data['nodes'].append(
{
'name': router['hostname'],
'ipv6_fe80_addr': netif['ipv6_fe80_addr'],
'href': 'https://monitoring.freifunk-franken.de/routers/' + str(router['_id']),
'firmware': router['software']['firmware'],
'hardware': router['hardware']['name']
}
)
if 'position' in router:
nodelist_data['nodes'][-1]['position'] = {
'lat': router['position']['coordinates'][1],
'long': router['position']['coordinates'][0]
}
if 'system' in router and 'contact' in router['system']:
nodelist_data['nodes'][-1]['contact'] = router['system']['contact']
if 'description' in router:
nodelist_data['nodes'][-1]['description'] = router['description']
mysql = FreifunkMySQL()
hood = mysql.findone("""
SELECT name
FROM hoods
WHERE id = %s
LIMIT 1
""",(int(keyxchange_id),))
if not hood:
mysql.close()
return "Hood not found"
if 'position_comment' in router:
nodelist_data['nodes'][-1]['position']['comment'] = router['position_comment']
return jsonify(nodelist_data)
nodelist_data = dict()
nodelist_data['nodes'] = list()
routers = mysql.fetchall("""
SELECT router.id, hostname, hardware, mac, fe80_addr, firmware, lat, lng, contact, position_comment, description
FROM router
INNER JOIN router_netif ON router.id = router_netif.router
WHERE hood = %s AND netif = 'br-mesh'
ORDER BY hostname ASC
""",(hood["name"],))
mysql.close()
for router in routers:
nodelist_data['nodes'].append(
{
'name': router['hostname'],
'ipv6_fe80_addr': router['fe80_addr'],
'href': 'https://monitoring.freifunk-franken.de/routers/' + str(router['id']),
'firmware': router['firmware'],
'hardware': router['hardware'],
'contact': router['contact'],
'description': router['description']
}
)
nodelist_data['nodes'][-1]['position'] = {
'lat': router['lat'],
'long': router['lng']
}
if router['position_comment']:
nodelist_data['nodes'][-1]['position']['comment'] = router['position_comment']
return jsonify(nodelist_data)

View File

@ -6,24 +6,23 @@ sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '../..'))
from ffmap.web.api import api
from ffmap.web.filters import filters
from ffmap.dbtools import FreifunkDB
from ffmap.mysqltools import FreifunkMySQL
from ffmap import stattools
from ffmap.usertools import *
from ffmap.routertools import delete_router
from ffmap.web.helpers import *
from flask import Flask, render_template, request, Response, redirect, url_for, flash, session
import bson
import pymongo
from bson.json_util import dumps as bson2json
from bson.objectid import ObjectId
import base64
import datetime
app = Flask(__name__)
app.register_blueprint(api, url_prefix='/api')
app.register_blueprint(filters)
db = FreifunkDB().handle()
tileurls = {
"links_and_routers": "/tiles/links_and_routers",
"hoods": "/tiles/hoods",
@ -45,131 +44,221 @@ def router_map():
@app.route('/routers')
def router_list():
query, query_str = parse_router_list_search_query(request.args)
return render_template("router_list.html", query_str=query_str, routers=db.routers.find(query, {
"hostname": 1,
"status": 1,
"hood": 1,
"user.nickname": 1,
"hardware.name": 1,
"created": 1,
"system.uptime": 1,
"system.clients": 1,
}).sort("hostname", pymongo.ASCENDING))
where, tuple = query2where(query)
mysql = FreifunkMySQL()
routers = mysql.fetchall("""
SELECT router.id, hostname, status, hood, contact, nickname, hardware, router.created, sys_uptime, clients
FROM router
LEFT JOIN users ON router.contact = users.email
{}
ORDER BY hostname ASC
""".format(where),tuple)
mysql.close()
mysql.utcawaretuple(routers,"created")
return render_template("router_list.html", query_str=query_str, routers=routers, numrouters=len(routers))
@app.route('/routers/<dbid>', methods=['GET', 'POST'])
def router_info(dbid):
try:
router = db.routers.find_one({"_id": ObjectId(dbid)})
assert router
if request.method == 'POST':
if request.form.get("act") == "delete":
user = None
# a router may not have a owner, but admin users still can delete it
if ("user" in router) and ("nickname" in router["user"]):
user = router["user"]["nickname"]
if is_authorized(user, session):
db.routers.delete_one({"_id": ObjectId(dbid)})
flash("<b>Router <i>%s</i> deleted!</b>" % router["hostname"], "success")
return redirect(url_for("index"))
else:
flash("<b>You are not authorized to perform this action!</b>", "danger")
except (bson.errors.InvalidId, AssertionError):
return "Router not found"
if request.args.get('json', None) != None:
del router["stats"]
#FIXME: Only as admin
return Response(bson2json(router, sort_keys=True, indent=4), mimetype='application/json')
else:
return render_template("router.html", router=router, tileurls=tileurls)
mysql = FreifunkMySQL()
router = mysql.findone("""SELECT * FROM router WHERE id = %s LIMIT 1""",(dbid,))
if router:
mysql.utcaware(router,["created","last_contact"])
router["user"] = mysql.findone("SELECT nickname FROM users WHERE email = %s",(router["contact"],),"nickname")
router["netifs"] = mysql.fetchall("""SELECT * FROM router_netif WHERE router = %s""",(dbid,))
for n in router["netifs"]:
n["ipv6_addrs"] = mysql.fetchall("""SELECT ipv6 FROM router_ipv6 WHERE router = %s AND netif = %s""",(dbid,n["netif"],),"ipv6")
router["neighbours"] = mysql.fetchall("""
SELECT nb.mac, nb.quality, nb.net_if, r.hostname, r.id
FROM router_neighbor AS nb
INNER JOIN (
SELECT router, mac FROM router_netif GROUP BY mac, router
) AS net ON nb.mac = net.mac
INNER JOIN router as r ON net.router = r.id
WHERE nb.router = %s""",(dbid,))
# FIX SQL: only one from router_netif
router["events"] = mysql.fetchall("""SELECT * FROM router_events WHERE router = %s""",(dbid,))
mysql.utcawaretuple(router["events"],"time")
router["stats"] = mysql.fetchall("""SELECT * FROM router_stats WHERE router = %s""",(dbid,))
for s in router["stats"]:
s["netifs"] = mysql.fetchdict("""
SELECT netif, rx, tx FROM router_stats_netif WHERE router = %s AND time = %s
""",(dbid,s["time"],),"netif")
s["neighbours"] = mysql.fetchdict("""
SELECT quality, mac FROM router_stats_neighbor WHERE router = %s AND time = %s
""",(dbid,s["time"],),"mac","quality")
#s["neighbours"] = mysql.fetchdict("""
# SELECT nb.mac, nb.quality, r.hostname, r.id
# FROM router_stats_neighbor AS nb
# INNER JOIN (
# SELECT router, mac FROM router_netif GROUP BY mac, router
# ) AS net ON nb.mac = net.mac
# INNER JOIN router as r ON net.router = r.id
# WHERE nb.router = %s AND time = %s""",(dbid,s["time"],),"mac")
mysql.utcaware(s["time"])
if request.method == 'POST':
if request.form.get("act") == "delete":
user = None
# a router may not have a owner, but admin users still can delete it
if ("user" in router):
user = router["user"]
if is_authorized(user, session):
#if True:
delete_router(mysql,dbid)
flash("<b>Router <i>%s</i> deleted!</b>" % router["hostname"], "success")
mysql.close()
return redirect(url_for("index"))
else:
flash("<b>You are not authorized to perform this action!</b>", "danger")
mysql.close()
else:
mysql.close()
return "Router not found"
if request.args.get('json', None) != None:
del router["stats"]
#FIXME: Only as admin
return Response(bson2json(router, sort_keys=True, indent=4), mimetype='application/json')
else:
return render_template("router.html", router=router, tileurls=tileurls)
except Exception as e: # most generic exception you can catch
logf = open("/data/fff/fail3.txt", "a")
logf.write("{}\n".format(str(e)))
logf.close()
@app.route('/users')
def user_list():
mysql = FreifunkMySQL()
users = mysql.fetchall("SELECT id, nickname, email, created, admin FROM users ORDER BY nickname ASC")
user_routers = stattools.router_user_sum(mysql)
mysql.close()
mysql.utcawaretuple(users,"created")
return render_template("user_list.html",
user_routers = stattools.router_user_sum(),
users = db.users.find({}, {"nickname": 1, "email": 1, "created": 1, "admin": 1}).sort("nickname", pymongo.ASCENDING)
user_routers = user_routers,
users = users,
users_count = len(users)
)
@app.route('/users/<nickname>', methods=['GET', 'POST'])
def user_info(nickname):
try:
user = db.users.find_one({"nickname": nickname})
assert user
except AssertionError:
mysql = FreifunkMySQL()
user = mysql.findone("SELECT * FROM users WHERE nickname = %s LIMIT 1",(nickname,))
user["created"] = mysql.utcaware(user["created"])
if not user:
mysql.close()
return "User not found"
if request.method == 'POST':
if is_authorized(user["nickname"], session):
if request.form.get("action") == "changepw":
if request.form["password"] != request.form["password_rep"]:
flash("<b>Passwords did not match!</b>", "danger")
elif request.form["password"] == "":
flash("<b>Password must not be empty!</b>", "danger")
else:
set_user_password(user["nickname"], request.form["password"])
flash("<b>Password changed!</b>", "success")
elif request.form.get("action") == "changemail":
if request.form["email"] != request.form["email_rep"]:
flash("<b>E-Mail addresses do not match!</b>", "danger")
elif not "@" in request.form["email"]:
flash("<b>Invalid E-Mail addresse!</b>", "danger")
else:
try:
set_user_email(user["nickname"], request.form["email"])
flash("<b>E-Mail changed!</b>", "success")
if not session.get('admin'):
password = base64.b32encode(os.urandom(10)).decode()
set_user_password(user["nickname"], password)
send_email(
recipient = request.form['email'],
subject = "Password for %s" % user['nickname'],
content = "Hello %s,\n\n" % user["nickname"] +
"You changed your email address on https://monitoring.freifunk-franken.de/\n" +
"To verify your new email address your password was changed to %s\n" % password +
"... and sent to your new address. Please log in and change it.\n\n" +
"Regards,\nFreifunk Franken Monitoring System"
)
return logout()
else:
# force db data reload
user = db.users.find_one({"nickname": nickname})
except AccountWithEmailExists:
flash("<b>There is already an account with this E-Mail Address!</b>", "danger")
elif request.form.get("action") == "changeadmin":
if session.get('admin'):
set_user_admin(nickname, request.form.get("admin") == "true")
# force db data reload
user = db.users.find_one({"nickname": nickname})
elif request.form.get("action") == "deleteaccount":
if session.get('admin'):
db.users.delete_one({"nickname": nickname})
flash("<b>User <i>%s</i> deleted!</b>" % nickname, "success")
return redirect(url_for("user_list"))
else:
flash("<b>You are not authorized to perform this action!</b>", "danger")
routers=db.routers.find({"user._id": user["_id"]}, {
"hostname": 1,
"status": 1,
"hood": 1,
"software.firmware": 1,
"hardware.name": 1,
"created": 1,
"system.uptime": 1,
"system.clients": 1,
}).sort("hostname", pymongo.ASCENDING)
return render_template("user.html", user=user, routers=routers)
try:
if request.method == 'POST':
if is_authorized(user["nickname"], session):
if request.form.get("action") == "changepw":
if request.form["password"] != request.form["password_rep"]:
flash("<b>Passwords did not match!</b>", "danger")
elif request.form["password"] == "":
flash("<b>Password must not be empty!</b>", "danger")
else:
set_user_password(mysql, user["nickname"], request.form["password"])
flash("<b>Password changed!</b>", "success")
elif request.form.get("action") == "changemail":
if request.form["email"] != request.form["email_rep"]:
flash("<b>E-Mail addresses do not match!</b>", "danger")
elif not "@" in request.form["email"]:
flash("<b>Invalid E-Mail addresse!</b>", "danger")
else:
try:
set_user_email(mysql, user["nickname"], request.form["email"])
flash("<b>E-Mail changed!</b>", "success")
if not session.get('admin'):
password = base64.b32encode(os.urandom(10)).decode()
set_user_password(mysql, user["nickname"], password)
send_email(
recipient = request.form['email'],
subject = "Password for %s" % user['nickname'],
content = "Hello %s,\n\n" % user["nickname"] +
"You changed your email address on https://monitoring.freifunk-franken.de/\n" +
"To verify your new email address your password was changed to %s\n" % password +
"... and sent to your new address. Please log in and change it.\n\n" +
"Regards,\nFreifunk Franken Monitoring System"
)
mysql.close()
return logout()
else:
# force db data reload
mysql.findone("SELECT * FROM users WHERE nickname = %s LIMIT 1",(nickname,))
except AccountWithEmailExists:
flash("<b>There is already an account with this E-Mail Address!</b>", "danger")
elif request.form.get("action") == "changeadmin":
if session.get('admin'):
set_user_admin(mysql, nickname, request.form.get("admin") == "true")
# force db data reload
mysql.findone("SELECT * FROM users WHERE nickname = %s LIMIT 1",(nickname,))
elif request.form.get("action") == "deleteaccount":
if session.get('admin'):
cur.execute("DELETE FROM users WHERE nickname = %s LIMIT 1",(nickname,))
mysql.commit()
flash("<b>User <i>%s</i> deleted!</b>" % nickname, "success")
mysql.close()
return redirect(url_for("user_list"))
else:
flash("<b>You are not authorized to perform this action!</b>", "danger")
routers = mysql.fetchall("""
SELECT id, hostname, status, hood, firmware, hardware, created, sys_uptime, clients
FROM router
WHERE contact = %s
ORDER BY hostname ASC
""",(user["email"],))
mysql.close()
mysql.utcawaretuple(routers,"created")
return render_template("user.html", user=user, routers=routers, routers_count=len(routers))
except Exception as e:
logf = open("/data/fff/fail626.txt", "a")
logf.write("{}\n".format(str(e)))
logf.close()
mysql.close()
@app.route('/statistics')
def global_statistics():
hoods = stattools.hoods()
mysql = FreifunkMySQL()
hoods = stattools.hoods(mysql)
stats = mysql.fetchall("SELECT * FROM stats_global")
mysql.utcawaretuple(stats,"time")
newest_routers = mysql.fetchall("""
SELECT id, hostname, hood, created
FROM router
WHERE hardware <> 'Legacy'
ORDER BY created DESC
LIMIT %s
""",(len(hoods)+1,))
mysql.utcawaretuple(newest_routers,"created")
clients = stattools.total_clients(mysql)
router_status = stattools.router_status(mysql)
router_models = stattools.router_models(mysql)
router_firmwares = stattools.router_firmwares(mysql)
hoods_sum = stattools.hoods_sum(mysql)
mysql.close()
return render_template("statistics.html",
stats = db.stats.find({}, {"_id": 0}),
clients = stattools.total_clients(),
router_status = stattools.router_status(),
router_models = stattools.router_models(),
router_firmwares = stattools.router_firmwares(),
stats = stats,
clients = clients,
router_status = router_status,
router_models = router_models,
router_firmwares = router_firmwares,
hoods = hoods,
hoods_sum = stattools.hoods_sum(),
newest_routers = db.routers.find({"hardware.name": {"$ne": "Legacy"}}, {"hostname": 1, "hood": 1, "created": 1}).sort("created", pymongo.DESCENDING).limit(len(hoods)+1)
hoods_sum = hoods_sum,
newest_routers = newest_routers
)
mysql.close()
@app.route('/register', methods=['GET', 'POST'])
def register():
@ -181,10 +270,10 @@ def register():
recipient = request.form['email'],
subject = "Password for %s" % request.form['user'],
content = "Hello %s,\n\n" % request.form['user'] +
"You created an account on https://monitoring.freifunk-franken.de/\n" +
"To verify your new email address your password was autogenerated to %s\n" % password +
"... and sent to your address. Please log in and change it.\n\n" +
"Regards,\nFreifunk Franken Monitoring System"
"You created an account on https://monitoring.freifunk-franken.de/\n" +
"To verify your new email address your password was autogenerated to %s\n" % password +
"... and sent to your address. Please log in and change it.\n\n" +
"Regards,\nFreifunk Franken Monitoring System"
)
flash("<b>Registration successful!</b> - Your password was sent to %s" % request.form['email'], "success")
except AccountWithEmailExists:
@ -198,8 +287,9 @@ def resetpw():
try:
if request.method == 'POST':
token = base64.b32encode(os.urandom(10)).decode()
user = db.users.find_one({"email": request.form['email']})
reset_user_password(request.form['email'], token)
mysql = FreifunkMySQL()
user = mysql.findone("SELECT nickname FROM users WHERE email = %s",(request.form['email'],))
reset_user_password(mysql, request.form['email'], token)
send_email(
recipient = request.form['email'],
subject = "Password reset link",
@ -211,10 +301,12 @@ def resetpw():
"Regards,\nFreifunk Franken Monitoring System"
)
flash("<b>A password reset link was sent to %s</b>" % request.form['email'], "success")
mysql.close()
elif "token" in request.args:
password = base64.b32encode(os.urandom(10)).decode()
reset_user_password(request.args['email'], request.args['token'], password)
user = db.users.find_one({"email": request.args['email']})
mysql = FreifunkMySQL()
reset_user_password(mysql, request.args['email'], request.args['token'], password)
user = mysql.findone("SELECT nickname FROM users WHERE email = %s",(request.args['email'],))
send_email(
recipient = request.args['email'],
subject = "Your new Password",
@ -225,6 +317,7 @@ def resetpw():
"Regards,\nFreifunk Franken Monitoring System"
)
flash("<b>Password reset successful!</b> - Your password was sent to %s" % request.args['email'], "success")
mysql.close()
except AccountNotExisting:
flash("<b>No Account found with this E-Mail address!</b>", "danger")
except InvalidToken:

View File

@ -8,7 +8,6 @@ import sys
import json
import datetime
import re
import pymongo
import hashlib
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/' + '../..'))
@ -99,8 +98,6 @@ def bson_to_json(bsn):
@filters.app_template_filter('statbson2json')
def statbson_to_json(bsn):
if isinstance(bsn, pymongo.cursor.Cursor):
bsn = list(bsn)
for point in bsn:
point["time"] = {"$date": int(point["time"].timestamp()*1000)}
return json.dumps(bsn)
@ -178,7 +175,7 @@ def gravatar_url(email):
@filters.app_template_filter('webui_addr')
def webui_addr(router_netifs):
try:
for br_mesh in filter(lambda n: n["name"] == "br-mesh", router_netifs):
for br_mesh in filter(lambda n: n["netif"] == "br-mesh", router_netifs):
for ipv6 in br_mesh["ipv6_addrs"]:
if ipv6.startswith("fd") and len(ipv6) > 25:
# This selects the first ULA address, if present

View File

@ -19,19 +19,40 @@ allowed_filters = (
'status',
'hood',
'community',
'user.nickname',
'hardware.name',
'software.firmware',
'netifs.mac',
'netifs.name',
'nickname',
'hardware',
'firmware',
'mac',
'netif',
'netmon_id',
'hostname',
'system.contact',
'contact',
)
def query2where(query):
s = ""
t = []
i = 0
for k, v in query.items():
if not k in allowed_filters:
# prevent SQL injection
continue
if i==0:
prefix = " WHERE "
else:
prefix = " AND "
i += 1
s += prefix + k + " = %s"
t.append(v)
return (s,tuple(t))
def parse_router_list_search_query(args):
query_usr = bson.SON()
if "q" in args:
for word in args["q"].strip().split(" "):
if not word:
# Case of "q=" without arguments
break
if not ':' in word:
key = "hostname"
value = word
@ -41,26 +62,28 @@ def parse_router_list_search_query(args):
query_usr[key] = query_usr.get(key, "") + value
query = {}
for key, value in query_usr.items():
if value == "EXISTS":
query[key] = {"$exists": True}
elif value == "EXISTS_NOT":
query[key] = {"$exists": False}
elif key == 'netifs.mac':
#if value == "EXISTS":
# query[key] = {"$exists": True}
#elif value == "EXISTS_NOT":
# query[key] = {"$exists": False}
if key == 'mac':
query[key] = value.lower()
elif key == 'netifs.name':
query[key] = {"$regex": value.replace('.', '\.'), "$options": 'i'}
#elif key == 'netif':
# query[key] = {"$regex": value.replace('.', '\.'), "$options": 'i'}
elif key == 'hostname':
query[key] = {"$regex": value.replace('.', '\.'), "$options": 'i'}
elif key == 'hardware.name':
query[key] = {"$regex": value.replace('.', '\.').replace('_', ' '), "$options": 'i'}
elif key == 'netmon_id':
query[key] = int(value)
elif key == 'system.contact':
if not '\.' in value:
value = re.escape(value)
query[key] = {"$regex": value, "$options": 'i'}
elif value.startswith('!'):
query[key] = {"$ne": value.replace('!', '', 1)}
query[key] = value.replace('\\', '')
elif key == 'hardware':
query[key] = value.replace('\\', '').replace('_', ' ')
#elif key == 'netmon_id':
# query[key] = int(value)
elif key == 'contact':
query[key] = value.replace('\\', '')
#elif key == 'contact':
# if not '\.' in value:
# value = re.escape(value)
# query[key] = {"$regex": value, "$options": 'i'}
#elif value.startswith('!'):
# query[key] = {"$ne": value.replace('!', '', 1)}
else:
query[key] = value
return (query, format_query(query_usr))

View File

@ -146,9 +146,9 @@ function memory_graph() {
var len, i;
for (len=router_stats.length, i=0; i<len; i++) {
try {
var free_value = router_stats[i].memory.free*1024;
var caching_value = router_stats[i].memory.caching*1024;
var buffering_value = router_stats[i].memory.buffering*1024;
var free_value = router_stats[i].sys_memfree*1024;
var caching_value = router_stats[i].sys_memcache*1024;
var buffering_value = router_stats[i].sys_membuff*1024;
var date_value = router_stats[i].time.$date;
if(free_value != null && caching_value != null && buffering_value != null) {
free.push([date_value, free_value]);
@ -181,8 +181,8 @@ function process_graph() {
var len, i;
for (len=router_stats.length, i=0; i<len; i++) {
try {
var runnable_value = router_stats[i].processes.runnable;
var total_value = router_stats[i].processes.total;
var runnable_value = router_stats[i].sys_procrun;
var total_value = router_stats[i].sys_proctot;
var date_value = router_stats[i].time.$date;
if(runnable_value != null && total_value != null) {
runnable.push([date_value, runnable_value]);
@ -274,7 +274,7 @@ function global_client_graph() {
var len, i;
for (len=global_stats.length, i=0; i<len; i++) {
try {
var client_value = global_stats[i].total_clients;
var client_value = global_stats[i].clients;
var date_value = global_stats[i].time.$date;
if(client_value != null) {
clients.push([date_value, client_value]);
@ -304,9 +304,9 @@ function global_router_graph() {
var len, i;
for (len=global_stats.length, i=0; i<len; i++) {
try {
var offline_value = global_stats[i].router_status.offline;
var online_value = global_stats[i].router_status.online;
var unknown_value = global_stats[i].router_status.unknown;
var offline_value = global_stats[i].offline;
var online_value = global_stats[i].online;
var unknown_value = global_stats[i].unknown;
var date_value = global_stats[i].time.$date;
if (offline_value == null) offline_value = 0;
if (online_value == null) online_value = 0;
@ -354,7 +354,7 @@ function global_router_firmwares_graph() {
});
placeholder.bind("plotclick", function(event, pos, obj) {
if (obj && obj.series.label != "Other") {
window.location.href = routers_page_url + "?q=software.firmware:" + obj.series.label;
window.location.href = routers_page_url + "?q=firmware:" + obj.series.label;
}
});
}
@ -379,7 +379,7 @@ function global_router_models_graph() {
});
placeholder.bind("plotclick", function(event, pos, obj) {
if (obj && obj.series.label != "Other") {
window.location.href = routers_page_url + "?q=hardware.name:" + obj.series.label.replace(/ /g, '_');
window.location.href = routers_page_url + "?q=hardware:" + obj.series.label.replace(/ /g, '_');
}
});
}

View File

@ -75,8 +75,8 @@ map.on('click', function(pos) {
ajax_get_request(url_get_nearest_router + "?lng=" + lng + "&lat=" + lat, function(router) {
// decide if router is close enough
var lng_delta = Math.abs(lng - router.position.coordinates[0])
var lat_delta = Math.abs(lat - router.position.coordinates[1])
var lng_delta = Math.abs(lng - router.lng)
var lat_delta = Math.abs(lat - router.lat)
// convert degree distances into px distances on the map
var x_delta_px = lng_delta * px_per_deg_lng;
@ -99,19 +99,21 @@ map.on('click', function(pos) {
has_neighbours = false;
for (var i = 0; i < router.neighbours.length; i++) {
neighbour = router.neighbours[i];
if ('_id' in neighbour) {
if ('id' in neighbour) {
has_neighbours = true;
}
}
}
if (has_neighbours) {
console.log("Has "+router.neighbours.length+" neighbours.");
popup_html += "<div class=\"popup-headline with-neighbours\">";
}
else {
console.log("Has no neighbours.");
popup_html += "<div class=\"popup-headline\">";
}
popup_html += '<b>Router <a href="' + url_router_info + router._id.$oid +'">'+router.hostname+'</a></b>';
popup_html += '<b>Router <a href="' + url_router_info + router.id +'">'+router.hostname+'</a></b>';
popup_html += "</div>"
if (has_neighbours) {
popup_html += '<table class="neighbours" style="width: 100%;">';
@ -123,7 +125,7 @@ map.on('click', function(pos) {
for (var i = 0; i < router.neighbours.length; i++) {
neighbour = router.neighbours[i];
// skip unknown neighbours
if ('_id' in neighbour) {
if ('id' in neighbour) {
var tr_color = "#04ff0a";
if (neighbour.quality == -1) { tr_color = "#0684c4"; }
else if (neighbour.quality < 105) { tr_color = "#ff1e1e"; }
@ -133,7 +135,7 @@ map.on('click', function(pos) {
else if (neighbour.quality < 205) { tr_color = "#ffeb79"; }
else if (neighbour.quality < 230) { tr_color = "#79ff7c"; }
popup_html += "<tr style=\"background-color: "+tr_color+";\">";
popup_html += '<td><a href="'+url_router_info+neighbour._id.$oid+'" title="'+escapeHTML(neighbour.mac)+'">'+escapeHTML(neighbour.hostname)+'</a></td>';
popup_html += '<td><a href="'+url_router_info+neighbour.id+'" title="'+escapeHTML(neighbour.mac)+'">'+escapeHTML(neighbour.hostname)+'</a></td>';
popup_html += "<td>"+neighbour.quality+"</td>";
popup_html += "<td>"+escapeHTML(neighbour.net_if)+"</td>";
popup_html += "</tr>";
@ -142,7 +144,7 @@ map.on('click', function(pos) {
popup_html += "</table>";
}
popup = L.popup({offset: new L.Point(1, 1), maxWidth: 500})
.setLatLng([router.position.coordinates[1], router.position.coordinates[0]])
.setLatLng([router.lat, router.lng])
.setContent(popup_html)
.openOn(map);
}

View File

@ -67,8 +67,8 @@
</script>
<script src="{{ url_for('static', filename='js/map.js') }}"></script>
<script type="text/javascript">
{%- if router.position %}
var router_pos = [{{ router.position.coordinates[1] }}, {{ router.position.coordinates[0] }}];
{%- if router.lng!="" and router.lat!="" %}
var router_pos = [{{ router.lat }}, {{ router.lng }}];
map.setView(router_pos, 18);
var marker = L.marker(router_pos, {
icon: L.icon({
@ -97,7 +97,7 @@
</td></tr>
<tr><th>Status</th><td><span class="{{ router.status|status2css }}">{{ router.status }}</span>
{%- if router.status == "online" %}
({{ router.system.uptime|format_ts_diff }} up)
({{ router.sys_uptime|format_ts_diff }} up)
{%- endif -%}
</td></tr>
<tr><th>Created</th><td>
@ -105,10 +105,10 @@
</td></tr>
<tr><th class="text-nowrap">Last contact</th><td>
{{ router.last_contact|utc2local|format_dt }}
({{ router.last_contact|format_dt_ago }}){{- "" -}}
({{ router.last_contact|utc2local|format_dt_ago }}){{- "" -}}
</td></tr>
{%- if router.system.status_text %}
<tr><th>Status Text</th><td>{{ router.system.status_text }}</td></tr>
{%- if router.status_text %}
<tr><th>Status Text</th><td>{{ router.status_text }}</td></tr>
{%- endif %}
{%- if router.description %}
<tr><th>Description</th><td>{{ router.description }}</td></tr>
@ -123,47 +123,43 @@
{%- endif -%}
</td></tr>
{%- endif %}
{%- if router.user or router.system.contact %}
{%- if router.user or router.contact %}
<tr><th>User</th><td>
{%- if router.user -%}
<a href="{{ url_for('user_info', nickname=router.user.nickname) }}">{{ router.user.nickname }}</a>
{%- if router.system.contact %}
(<a href="{{ url_for('router_list', q='system.contact:%s' % router.system.contact|anon_email_regex) }}">{{ router.system.contact|anon_email }}</a>)
<a href="{{ url_for('user_info', nickname=router.user) }}">{{ router.user }}</a>
{%- if router.contact %}
(<a href="{{ url_for('router_list', q='contact:%s' % router.contact|anon_email_regex) }}">{{ router.contact|anon_email }}</a>)
{%- endif -%}
{%- else -%}
<a href="{{ url_for('router_list', q='system.contact:%s' % router.system.contact|anon_email_regex) }}">{{ router.system.contact|anon_email }}</a>
<a href="{{ url_for('router_list', q='contact:%s' % router.contact|anon_email_regex) }}">{{ router.contact|anon_email }}</a>
{%- endif -%}
</td></tr>
{%- endif %}
<tr><th>Hardware</th><td><span title="{{ router.hardware.chipset }}">{{ router.hardware.name }}</span></td></tr>
<tr><th>WAN Uplink</th><td><span class="{{ "glyphicon glyphicon-ok" if router.system.has_wan_uplink else "glyphicon glyphicon-remove" }}"></span></td></tr>
<tr><th>Clients</th><td>{{ router.system.clients }}</td></tr>
<tr><th>Hardware</th><td><span title="{{ router.chipset }}">{{ router.hardware }}</span></td></tr>
<tr><th>WAN Uplink</th><td><span class="{{ "glyphicon glyphicon-ok" if router.wan_uplink else "glyphicon glyphicon-remove" }}"></span></td></tr>
<tr><th>Clients</th><td>{{ router.clients }}</td></tr>
</table>
</div>
</div>
</div>
</div>
<div class="row" style="display: flex;">
{%- if router.software is defined %}
<div class="col-xs-12 col-md-6" style="display: flex; flex-flow: column;">
<div class="panel panel-default">
<div class="panel-heading">Software</div>
<div class="panel-body">
<table class="table table-condensed">
<tr><th>Firmware</th><td><span title="{{ router.software.firmware_rev }}">{{ router.software.firmware }}</span></td></tr>
<tr><th>Operating&nbsp;System</th><td>{{ router.software.os }}</td></tr>
<tr><th>Kernel</th><td>{{ router.software.kernel }}</td></tr>
<tr><th>B.A.T.M.A.N. adv</th><td>{{ router.software.batman_adv }}</td></tr>
<tr><th>Nodewatcher</th><td>{{ router.software.nodewatcher }}</td></tr>
<tr><th>Firmware</th><td><span title="{{ router.firmware_rev }}">{{ router.firmware }}</span></td></tr>
<tr><th>Operating&nbsp;System</th><td>{{ router.os }}</td></tr>
<tr><th>Kernel</th><td>{{ router.kernel }}</td></tr>
<tr><th>B.A.T.M.A.N. adv</th><td>{{ router.batman }}</td></tr>
<tr><th>Nodewatcher</th><td>{{ router.nodewatcher }}</td></tr>
</table>
</div>
</div>
{%- if not router.neighbours|length > 0 %}
</div>
<div class="col-xs-12 col-md-6" style="display: flex; flex-flow: column;">
{%- endif %}
{%- else %}
<div class="col-xs-12 col-md-6" style="display: flex; flex-flow: column;">
{%- endif %}
<div class="panel panel-default" style="flex: 1 1 auto;">
<div class="panel-heading">Events</div>
@ -195,7 +191,7 @@
</tr>
{%- for neighbour in router.neighbours %}
<tr style="background-color: {{ neighbour.quality|neighbour_color }};">
<td><a href="{{ url_for('router_info', dbid=neighbour._id) }}">{{ neighbour.hostname }}</a></td>
<td><a href="{{ url_for('router_info', dbid=neighbour.id) }}">{{ neighbour.hostname }}</a></td>
<td>{{ neighbour.mac }}</td>
<td>{{ neighbour.quality }}</td>
<td>{{ neighbour.net_if }}</td>
@ -224,10 +220,10 @@
</div>
<ul class="list-group" id="netif-list">
{# make sure that br-mesh is on top of the list #}
{%- for netif in router.netifs if netif.name == 'br-mesh' %}
<li class="list-group-item active" data-name="{{ netif.name|replace('.', '')|replace('$', '') }}">
{%- for netif in router.netifs if netif.netif == 'br-mesh' %}
<li class="list-group-item active" data-name="{{ netif.netif|replace('.', '')|replace('$', '') }}">
<h4 class="list-group-item-heading"><div class="row">
<div class="col-xs-6 col-sm-6">{{ netif.name }}</div>
<div class="col-xs-6 col-sm-6">{{ netif.netif }}</div>
<div class="col-xs-6 col-sm-6 text-right" style="text-transform: uppercase;">{{ netif.mac }}</div>
</div></h4>
<p class="list-group-item-text"><div class="row">
@ -244,19 +240,19 @@
<br />{{ ipv6_addr }}
{%- endfor -%}
</div>
{%- if netif.traffic.rx is defined %}
{%- if netif.rx is defined %}
<div class="col-xs-7 col-sm-7 text-right">
<span class="glyphicon glyphicon-arrow-down"></span>{{ netif.traffic.rx|humanize_bytes }}/s
<span class="glyphicon glyphicon-arrow-up"></span>{{ netif.traffic.tx|humanize_bytes }}/s
<span class="glyphicon glyphicon-arrow-down"></span>{{ netif.rx|humanize_bytes }}/s
<span class="glyphicon glyphicon-arrow-up"></span>{{ netif.tx|humanize_bytes }}/s
</div>
{%- endif %}
</div></p>
</li>
{%- endfor %}
{%- for netif in router.netifs if netif.name != 'br-mesh' %}
<li class="list-group-item" data-name="{{ netif.name|replace('.', '')|replace('$', '') }}">
{%- for netif in router.netifs if netif.netif != 'br-mesh' %}
<li class="list-group-item" data-name="{{ netif.netif|replace('.', '')|replace('$', '') }}">
<h4 class="list-group-item-heading"><div class="row">
<div class="col-xs-6 col-sm-6">{{ netif.name }}</div>
<div class="col-xs-6 col-sm-6">{{ netif.netif }}</div>
<div class="col-xs-6 col-sm-6 text-right" style="text-transform: uppercase;">{{ netif.mac }}</div>
</div></h4>
<p class="list-group-item-text"><div class="row">
@ -273,10 +269,10 @@
<br />{{ ipv6_addr }}
{%- endfor -%}
</div>
{%- if netif.traffic.rx is defined %}
{%- if netif.rx is defined %}
<div class="col-xs-7 col-sm-7 text-right">
<span class="glyphicon glyphicon-arrow-down"></span>{{ netif.traffic.rx|humanize_bytes }}/s
<span class="glyphicon glyphicon-arrow-up"></span>{{ netif.traffic.tx|humanize_bytes }}/s
<span class="glyphicon glyphicon-arrow-down"></span>{{ netif.rx|humanize_bytes }}/s
<span class="glyphicon glyphicon-arrow-up"></span>{{ netif.tx|humanize_bytes }}/s
</div>
{%- endif %}
</div></p>

View File

@ -42,21 +42,21 @@
<tbody>
{%- for router in routers %}
<tr>
<td class="text-nowrap-responsive"><a href="{{ url_for("router_info", dbid=router._id) }}">{{ router.hostname }}</a></td>
<td class="text-nowrap-responsive"><a href="{{ url_for("router_info", dbid=router.id) }}">{{ router.hostname }}</a></td>
<td class="text-center"><span class="{{ router.status|status2css }}">{{ router.status }}</span></td>
<td>{{ router.hood }}</td>
<td>{{ router.user.nickname if router.user }}</td>
<td class="text-nowrap">{{ router.get("hardware", {}).get("name", "") }}</td>
<td>{{ router.nickname if router.nickname else "" }}</td>
<td class="text-nowrap">{{ router.hardware }}</td>
<td class="text-nowrap">{{ router.created|utc2local|format_dt_date }}</td>
<td class="text-nowrap">{{ router.system.uptime|format_ts_diff }}</td>
<td>{{ router.system.clients }}</td>
<td class="text-nowrap">{{ router.sys_uptime|format_ts_diff }}</td>
<td>{{ router.clients }}</td>
</tr>
{%- endfor %}
</tbody>
</table>
</div>
<div style="margin-bottom: 20px;">
{{ routers.count() }} Router{{ "s" if (routers.count() == 1) else "" }} found.
{{ numrouters }} Router{{ "s" if (numrouters == 1) else "" }} found.
</div>
<script type="text/javascript">
$(document).ready(function() {

View File

@ -86,7 +86,7 @@
</tr>
{%- for router in newest_routers|reverse %}
<tr>
<td><a href="{{ url_for('router_info', dbid=router._id) }}">{{ router.hostname }}</a></td>
<td><a href="{{ url_for('router_info', dbid=router.id) }}">{{ router.hostname }}</a></td>
<td>{{ router.hood }}</td>
<td class="text-nowrap">{{ router.created|utc2local|format_dt }}</td>
</tr>

View File

@ -57,12 +57,11 @@
<table class="table table-condensed">
<tr><th>Nickname</th><td>
{{ user.nickname }}
(<a href="{{ url_for('router_list', q='user.nickname:%s' % user.nickname) }}">Filter</a>)
(<a href="{{ url_for('router_list', q='nickname:%s' % user.nickname) }}">Filter</a>)
</td></tr>
{%- if user.email %}
<tr><th>E-Mail</th><td>
{{ user.email|anon_email }}
(<a href="{{ url_for('router_list', q='system.contact:%s' % user.email|anon_email_regex) }}">Filter</a>)
</td></tr>
{%- endif %}
<tr><th>Created</th><td>
@ -99,22 +98,22 @@
{%- set total_clients = 0 %}
{%- for router in routers %}
<tr>
<td class="text-nowrap-responsive"><a href="{{ url_for("router_info", dbid=router._id) }}">{{ router.hostname }}</a></td>
<td class="text-nowrap-responsive"><a href="{{ url_for("router_info", dbid=router.id) }}">{{ router.hostname }}</a></td>
<td class="text-center"><span class="{{ router.status|status2css }}">{{ router.status }}</span></td>
<td>{{ router.hood }}</td>
<td>{{ router.software.firmware }}</td>
<td class="text-nowrap">{{ router.get("hardware", {}).get("name", "") }}</td>
<td>{{ router.firmware }}</td>
<td class="text-nowrap">{{ router.get("hardware", "") }}</td>
<td class="text-nowrap">{{ router.created|utc2local|format_dt_date }}</td>
<td class="text-nowrap">{{ router.system.uptime|format_ts_diff }}</td>
<td>{{ router.system.clients }}</td>
{%- set total_clients = total_clients + router.system.clients %}
<td class="text-nowrap">{{ router.sys_uptime|format_ts_diff }}</td>
<td>{{ router.clients }}</td>
{%- set total_clients = total_clients + router.clients %}
</tr>
{%- endfor %}
</tbody>
</table>
</div>
<div style="margin-bottom: 20px;">
{{ routers.count() }} Router{{ "s" if (routers.count() == 1) else "" }} found.
{{ routers_count }} Router{{ "s" if (routers_count == 1) else "" }} found.
</div>
<script type="text/javascript">
$(document).ready(function() {

View File

@ -41,15 +41,15 @@
<span class="glyphicon glyphicon-remove" title="Automatically imported stub from netmon"></span>
{%- endif -%}
</td>
<td>{{ user_routers.get(user.nickname, {}).get('routers', 0) }}</td>
<td>{{ user_routers.get(user.nickname, {}).get('clients', 0) }}</td>
<td>{{ user_routers.get(user.email, {}).get('routers', 0) }}</td>
<td>{{ user_routers.get(user.email, {}).get('clients', 0) }}</td>
</tr>
{%- endfor %}
</tbody>
</table>
</div>
<div style="margin-bottom: 20px;">
{{ users.count() }} User{{ "s" if (users.count() > 1) else "" }} found.
{{ users_count }} User{{ "s" if (users_count > 1) else "" }} found.
</div>
<script type="text/javascript">
$(document).ready(function() {