diff --git a/index.html b/index.html
index 7ac59c2..af6cffa 100644
--- a/index.html
+++ b/index.html
@@ -26,6 +26,11 @@
{{ block "update" .Update }}
{{ with .Content }}{{ . }}{{ end }}
{{ end }}
+ {{ block "svg" .SVG }}
+
+ {{ . }}
+
+ {{ end }}
{{ block "used" .Api }}
diff --git a/ipalloc/ipalloc.go b/ipalloc/ipalloc.go
index d552975..45ed0f2 100644
--- a/ipalloc/ipalloc.go
+++ b/ipalloc/ipalloc.go
@@ -159,7 +159,7 @@ func (t *tree) Prefix() netip.Prefix {
}
func (t *tree) Split() {
- lo, hi := splitPrefix(t.prefix)
+ lo, hi := SplitPrefix(t.prefix)
t.lo = &tree{prefix: lo, parent: t}
t.hi = &tree{prefix: hi, parent: t}
@@ -214,7 +214,7 @@ func (t *tree) Alloc(bits int) *tree {
}
for best.prefix.Bits() < bits {
- lo, hi := splitPrefix(best.prefix)
+ lo, hi := SplitPrefix(best.prefix)
//log.Println(lo, hi)
switch {
case best.lo == nil:
@@ -236,7 +236,7 @@ func (t *tree) Insert(p netip.Prefix) *tree {
var ret *tree
for t.prefix.Bits() < p.Bits() {
- lo, hi := splitPrefix(t.prefix)
+ lo, hi := SplitPrefix(t.prefix)
//log.Println(lo, hi)
switch {
case lo.Overlaps(p):
@@ -435,6 +435,11 @@ func (db *DB) Find(p netip.Prefix) bool {
p = p.Masked()
return db.provisions.Find(p) != nil
}
+func (db *DB) Root() netip.Prefix {
+ db.Lock()
+ defer db.Unlock()
+ return db.provisions.prefix
+}
//func (db *DB) Provision(p netip.Prefix) error {
// db.Lock()
@@ -446,7 +451,7 @@ func (db *DB) Find(p netip.Prefix) bool {
// return nil
//}
-func splitPrefix(p netip.Prefix) (netip.Prefix, netip.Prefix) {
+func SplitPrefix(p netip.Prefix) (netip.Prefix, netip.Prefix) {
if p.IsSingleIP() {
log.Fatal("can't split single ip prefix", p)
}
diff --git a/ipalloc/ipalloc_test.go b/ipalloc/ipalloc_test.go
index 15abb2c..a8246c0 100644
--- a/ipalloc/ipalloc_test.go
+++ b/ipalloc/ipalloc_test.go
@@ -15,7 +15,7 @@ func TestSplitPrefix(t *testing.T) {
}
for _, test := range tests {
- lo, hi := splitPrefix(netip.MustParsePrefix(test.in))
+ lo, hi := SplitPrefix(netip.MustParsePrefix(test.in))
if lo != netip.MustParsePrefix(test.lo) || hi != netip.MustParsePrefix(test.hi) {
t.Fatal(test.in, lo, hi)
}
diff --git a/main.go b/main.go
index 992910c..b145935 100644
--- a/main.go
+++ b/main.go
@@ -9,6 +9,7 @@ import (
"net/netip"
"strconv"
"strings"
+
"git.freifunk-franken.de/jkimmel/sub/ipalloc"
)
@@ -50,6 +51,7 @@ func (s *server) apiPrefix(w http.ResponseWriter, r *http.Request) {
type PageContent struct {
Api *ipalloc.DB
Update UpdateMessage
+ SVG template.HTML
}
type UpdateMessage struct {
Type string
@@ -57,7 +59,7 @@ type UpdateMessage struct {
}
func (s *server) template(w http.ResponseWriter, r *http.Request) {
- err := s.tmpl.Execute(w, PageContent{Api: s.ipdb})
+ err := s.tmpl.Execute(w, PageContent{Api: s.ipdb, SVG: renderSVG(s.ipdb.Used(), s.ipdb.Root())})
if err != nil {
log.Println(err)
}
@@ -101,6 +103,7 @@ func (s *server) apiAlloc(w http.ResponseWriter, r *http.Request) {
updmsg.Type = "hint"
updmsg.Content = fmt.Sprintf("Added %s", alloc.Prefix())
}
+ s.tmpl.ExecuteTemplate(w, "svg", renderSVG(s.ipdb.Used(), s.ipdb.Root()))
if err := s.tmpl.ExecuteTemplate(w, "update", updmsg); err != nil {
log.Println(err)
}
@@ -132,6 +135,7 @@ func (s *server) apiDealloc(w http.ResponseWriter, r *http.Request) {
updmsg.Type = "hint"
updmsg.Content = fmt.Sprintf("Removed %s", prefix)
}
+ s.tmpl.ExecuteTemplate(w, "svg", renderSVG(s.ipdb.Used(), s.ipdb.Root()))
s.tmpl.ExecuteTemplate(w, "update", updmsg)
s.tmpl.ExecuteTemplate(w, "used", s.ipdb)
}
@@ -155,3 +159,91 @@ func main() {
s.setup()
http.ListenAndServe("[::1]:8080", &s)
}
+
+type rect struct {
+ y, x, h, w int
+}
+
+func prefixShape(p netip.Prefix) rect {
+ bits := p.Addr().BitLen() - p.Bits()
+
+ return rect{
+ y: 0,
+ x: 0,
+ h: 1 << (bits / 2),
+ w: 1 << ((bits + 1) / 2),
+ }
+}
+
+func placeInto(p, into netip.Prefix) rect {
+ return placeIntoRect(p, into, prefixShape(into))
+}
+func placeIntoRect(p, into netip.Prefix, r rect) rect {
+ if p == into {
+ return r
+ }
+ if p.Bits() < into.Bits() {
+ panic("unreachable")
+ }
+ lo, hi := ipalloc.SplitPrefix(into)
+
+ switch {
+ case lo.Overlaps(p):
+ var subr rect
+ if r.w > r.h {
+ subr.y = r.y
+ subr.x = r.x
+ subr.h = r.h
+ subr.w = r.w / 2
+ } else {
+ subr.y = r.y
+ subr.x = r.x
+ subr.h = r.h / 2
+ subr.w = r.w
+ }
+ return placeIntoRect(p, lo, subr)
+ case hi.Overlaps(p):
+ var subr rect
+ if r.w > r.h {
+ subr.y = r.y
+ subr.x = (r.x + (r.x + r.w)) / 2
+ subr.h = r.h
+ subr.w = r.w / 2
+ } else {
+ subr.y = (r.y + (r.y + r.h)) / 2
+ subr.x = r.x
+ subr.h = r.h / 2
+ subr.w = r.w
+ }
+ return placeIntoRect(p, hi, subr)
+ default:
+ panic("unreachable")
+ }
+}
+func renderSVG(ps []netip.Prefix, into netip.Prefix) template.HTML {
+ var b strings.Builder
+ shape := prefixShape(into)
+ fmt.Fprintf(&b, "`)
+ return template.HTML(b.String())
+}