diff --git a/cmd/dhcp4d/dhcp4d.go b/cmd/dhcp4d/dhcp4d.go index 7eb798d..6514b1a 100644 --- a/cmd/dhcp4d/dhcp4d.go +++ b/cmd/dhcp4d/dhcp4d.go @@ -16,14 +16,17 @@ package main import ( + "bytes" "encoding/json" "flag" "fmt" + "html/template" "io/ioutil" "net" "net/http" "os" "os/signal" + "sort" "syscall" "time" @@ -65,6 +68,106 @@ func updateNonExpired(leases []*dhcp4d.Lease) { var ouiDB = oui.NewDB("/perm/dhcp4d/oui") +var leases []*dhcp4d.Lease + +var ( + timefmt = func(t time.Time) string { + return t.Format("2006-01-02 15:04") + } + leasesTmpl = template.Must(template.New("").Funcs(template.FuncMap{ + "timefmt": timefmt, + "since": func(t time.Time) string { + dur := time.Since(t) + if dur.Hours() > 24 { + return timefmt(t) + } + return dur.Truncate(1 * time.Second).String() + }, + }).Parse(` + + +DHCPv4 status + + + +{{ define "table" }} + +IP address +Hostname +MAC address +Vendor +Expiry + +{{ range $idx, $l := . }} + +{{$l.Addr}} +{{$l.Hostname}} +{{$l.HardwareAddr}} +{{$l.Vendor}} + +{{ if $l.Expired }} +{{ since $l.Expiry }} +expired +{{ else }} +{{ if $l.Static }} +static +{{ else }} +{{ timefmt $l.Expiry }} +active +{{ end }} +{{ end }} + + +{{ end }} +{{ end }} + + +{{ template "table" .StaticLeases }} +{{ template "table" .DynamicLeases }} +
+ + +`)) +) + func loadLeases(h *dhcp4d.Handler, fn string) error { b, err := ioutil.ReadFile(fn) if err != nil { @@ -73,7 +176,7 @@ func loadLeases(h *dhcp4d.Handler, fn string) error { } return err } - var leases []*dhcp4d.Lease + if err := json.Unmarshal(b, &leases); err != nil { return err } @@ -94,9 +197,48 @@ func loadLeases(h *dhcp4d.Handler, fn string) error { http.Error(w, fmt.Sprintf("access from %v forbidden", ip), http.StatusForbidden) return } - // TODO: html template + + type tmplLease struct { + dhcp4d.Lease + + Vendor string + Expired bool + Static bool + } + + static := make([]tmplLease, 0, len(leases)) + dynamic := make([]tmplLease, 0, len(leases)) + tl := func(l *dhcp4d.Lease) tmplLease { + return tmplLease{ + Lease: *l, + Vendor: ouiDB.Lookup(l.HardwareAddr[:8]), + Expired: l.Expired(time.Now()), + Static: l.Expiry.IsZero(), + } + } for _, l := range leases { - fmt.Fprintf(w, "• %+v (vendor %v)\n", l, ouiDB.Lookup(l.HardwareAddr[:8])) + if l.Expiry.IsZero() { + static = append(static, tl(l)) + } else { + dynamic = append(dynamic, tl(l)) + } + } + sort.Slice(static, func(i, j int) bool { + return static[i].Num < static[j].Num + }) + sort.Slice(dynamic, func(i, j int) bool { + return !dynamic[i].Expiry.Before(dynamic[j].Expiry) + }) + + if err := leasesTmpl.Execute(w, struct { + StaticLeases []tmplLease + DynamicLeases []tmplLease + }{ + StaticLeases: static, + DynamicLeases: dynamic, + }); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return } }) @@ -150,7 +292,8 @@ func logic() error { if err := loadLeases(handler, "/perm/dhcp4d/leases.json"); err != nil { return err } - handler.Leases = func(leases []*dhcp4d.Lease, latest *dhcp4d.Lease) { + handler.Leases = func(newLeases []*dhcp4d.Lease, latest *dhcp4d.Lease) { + leases = newLeases log.Printf("DHCPACK %+v", latest) b, err := json.Marshal(leases) if err != nil {