From b54a1c9686eec3c1114e9b58cb67679ba59c45bd Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 14 Mar 2018 18:18:31 -0400 Subject: directories --- .gitignore | 18 +- Makefile | 58 ++- bin-src/cron-daily | 5 + bin-src/crtsh-getcerts | 28 ++ bin-src/crtsh-pem2html.go | 150 +++++++ bin-src/diff-pem2html.go | 109 +++++ bin-src/pem-diff.go | 140 ++++++ bin-src/tls-getcerts.go | 192 ++++++++ bin-src/tls-pem2html.go | 160 +++++++ bin-src/util/date.go | 51 +++ bin-src/util/html.go | 14 + cfg/domains.txt | 4 + cfg/jarmon-proton.js | 155 +++++++ cfg/jarmon-winston.js | 154 +++++++ cfg/sockets.txt | 15 + colordate.js | 41 -- config-domains.txt | 4 - config-jarmon-proton.js | 155 ------- config-jarmon-winston.js | 154 ------- config-sockets.txt | 15 - cron-daily | 5 - crtsh-getcerts | 28 -- crtsh-pem2html.go | 150 ------- dashboard-daily.service | 6 - dashboard-daily.timer | 9 - diff-pem2html.go | 109 ----- diff.go | 140 ------ index.html.gen | 44 -- jarmon-dependencies.js | 1 - jarmon-style/jquerytools.tabs.tabs-no-images.scss | 69 --- jarmon-style/loading.gif | 1 - jarmon-style/next.gif | 1 - jarmon-style/prev.gif | 1 - jarmon-style/style.scss | 119 ----- jarmon.html.in | 29 -- jarmon.js | 1 - public-src/colordate.js | 41 ++ public-src/index.html.gen | 46 ++ public-src/jarmon-dependencies.js | 1 + .../jquerytools.tabs.tabs-no-images.scss | 69 +++ public-src/jarmon-style/loading.gif | 1 + public-src/jarmon-style/next.gif | 1 + public-src/jarmon-style/prev.gif | 1 + public-src/jarmon-style/style.scss | 119 +++++ public-src/jarmon.html.in | 29 ++ public-src/jarmon.js | 1 + public-src/sorttable.js | 495 +++++++++++++++++++++ public-src/style.scss | 76 ++++ sorttable.js | 495 --------------------- style.scss | 76 ---- systemd/dashboard-daily.service | 6 + systemd/dashboard-daily.timer | 9 + tls-getcerts.go | 192 -------- tls-pem2html.go | 160 ------- util/date.go | 51 --- util/html.go | 14 - 56 files changed, 2112 insertions(+), 2106 deletions(-) create mode 100755 bin-src/cron-daily create mode 100755 bin-src/crtsh-getcerts create mode 100644 bin-src/crtsh-pem2html.go create mode 100644 bin-src/diff-pem2html.go create mode 100644 bin-src/pem-diff.go create mode 100644 bin-src/tls-getcerts.go create mode 100644 bin-src/tls-pem2html.go create mode 100644 bin-src/util/date.go create mode 100644 bin-src/util/html.go create mode 100644 cfg/domains.txt create mode 100644 cfg/jarmon-proton.js create mode 100644 cfg/jarmon-winston.js create mode 100644 cfg/sockets.txt delete mode 100644 colordate.js delete mode 100644 config-domains.txt delete mode 100644 config-jarmon-proton.js delete mode 100644 config-jarmon-winston.js delete mode 100644 config-sockets.txt delete mode 100755 cron-daily delete mode 100755 crtsh-getcerts delete mode 100644 crtsh-pem2html.go delete mode 100644 dashboard-daily.service delete mode 100644 dashboard-daily.timer delete mode 100644 diff-pem2html.go delete mode 100644 diff.go delete mode 100755 index.html.gen delete mode 120000 jarmon-dependencies.js delete mode 100644 jarmon-style/jquerytools.tabs.tabs-no-images.scss delete mode 120000 jarmon-style/loading.gif delete mode 120000 jarmon-style/next.gif delete mode 120000 jarmon-style/prev.gif delete mode 100644 jarmon-style/style.scss delete mode 100644 jarmon.html.in delete mode 120000 jarmon.js create mode 100644 public-src/colordate.js create mode 100755 public-src/index.html.gen create mode 120000 public-src/jarmon-dependencies.js create mode 100644 public-src/jarmon-style/jquerytools.tabs.tabs-no-images.scss create mode 120000 public-src/jarmon-style/loading.gif create mode 120000 public-src/jarmon-style/next.gif create mode 120000 public-src/jarmon-style/prev.gif create mode 100644 public-src/jarmon-style/style.scss create mode 100644 public-src/jarmon.html.in create mode 120000 public-src/jarmon.js create mode 100644 public-src/sorttable.js create mode 100644 public-src/style.scss delete mode 100644 sorttable.js delete mode 100644 style.scss create mode 100644 systemd/dashboard-daily.service create mode 100644 systemd/dashboard-daily.timer delete mode 100644 tls-getcerts.go delete mode 100644 tls-pem2html.go delete mode 100644 util/date.go delete mode 100644 util/html.go diff --git a/.gitignore b/.gitignore index 9eb8838..56cfb60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,5 @@ -*.css -*.html -*.html.part -*.map -*.pem -*.txt +/bin/ +/public/ NET-* -!config-*.txt -!jarmon.html.part - -# programs -crtsh-pem2html -tls-getcerts -tls-pem2html -diff-pem2html -diff - .sass-cache diff --git a/Makefile b/Makefile index 3ce825e..2f62fac 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,48 @@ -all: index.html style.css jarmon-style/jquerytools.tabs.tabs-no-images.css jarmon-style/style.css +all: public/index.html +all: public/style.css +all: public/jarmon-style/jquerytools.tabs.tabs-no-images.css +all: public/jarmon-style/style.css -%: %.go util $(wildcard util/*.go) - go build $< +NET-%: + date > $@ -index.html: tls.html.part crtsh.html.part diff.html.part jarmon.html.in +.DELETE_ON_ERROR: +.SECONDARY: -crtsh.pem: crtsh-getcerts config-domains.txt NET-crtsh - ./crtsh-getcerts $$(sed 's/#.*//' config-domains.txt) > $@ +# bin/ -tls.pem: tls-getcerts config-sockets.txt NET-tls - ./tls-getcerts $$(sed 's/#.*//' config-sockets.txt) > $@ +bin/%: bin-src/%.go bin-src/util $(wildcard bin-src/util/*.go) + go build -o $@ $< -diff.pem: diff tls.pem crtsh.pem - ./diff tls.pem crtsh.pem > $@ +bin/%: bin-src/% + @mkdir -p '$(@D)' + ln -srTf '$<' '$@' -tls.html.part crtsh.html.part diff.html.part: %.html.part: %.pem %-pem2html - ./$*-pem2html < $< > $@ +# public/ -%: %.gen - ./$< $(filter-out $<,$^) > $@ +public/%: public-src/% + @mkdir -p '$(@D)' + ln -srTf '$<' '$@' -%.css: %.scss - scss $< $@ +public/index.html: public/tls.html.part public/crtsh.html.part public/diff.html.part public/jarmon.html.in -NET-%: - date > $@ +public/crtsh.pem: bin/crtsh-getcerts cfg/domains.txt NET-crtsh + @mkdir -p '$(@D)' + bin/crtsh-getcerts $$(sed 's/#.*//' cfg/domains.txt) > $@ -.DELETE_ON_ERROR: -.SECONDARY: +public/tls.pem: bin/tls-getcerts cfg/sockets.txt NET-tls + @mkdir -p '$(@D)' + bin/tls-getcerts $$(sed 's/#.*//' cfg/sockets.txt) > $@ + +public/diff.pem: bin/pem-diff public/tls.pem public/crtsh.pem + $^ > $@ + +public/tls.html.part public/crtsh.html.part public/diff.html.part: \ +public/%.html.part: public/%.pem bin/%-pem2html + bin/$*-pem2html < $< > $@ + +public/%: public/%.gen + $^ > $@ + +public/%.css: public/%.scss + scss $< $@ diff --git a/bin-src/cron-daily b/bin-src/cron-daily new file mode 100755 index 0000000..7b71669 --- /dev/null +++ b/bin-src/cron-daily @@ -0,0 +1,5 @@ +#!/bin/sh +cd "$(dirname -- "$0")/.." +date > NET-crtsh +date > NET-tls +make diff --git a/bin-src/crtsh-getcerts b/bin-src/crtsh-getcerts new file mode 100755 index 0000000..0191e2e --- /dev/null +++ b/bin-src/crtsh-getcerts @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby +require 'nokogiri' +require 'open-uri' + +certs = {} +ARGV.each do |domain| + [ domain, "%.#{domain}" ].each do |pattern| + Nokogiri::XML(open("https://crt.sh/atom?identity=#{pattern}&exclude=expired")).css('feed > entry').each do |entry| + url = entry.css('id').first.text.split("#").first + + updated = entry.css('updated').first.text + + html = Nokogiri::HTML(entry.css('summary').first.text) + html.css('br').each{|br| br.replace("\n")} + pem = html.css('div').first.text + + lines = pem.split("\n") + lines.insert(1, "X-Crt-Sh-Url: #{url}", "X-Crt-Sh-Updated: #{updated}") + pem = lines.join("\n")+"\n" + + certs[url] = pem + end + end +end + +certs.each do |url, pem| + print pem +end diff --git a/bin-src/crtsh-pem2html.go b/bin-src/crtsh-pem2html.go new file mode 100644 index 0000000..109917c --- /dev/null +++ b/bin-src/crtsh-pem2html.go @@ -0,0 +1,150 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "html/template" + "io/ioutil" + "os" + "sort" + "time" + + "./util" +) + +func handleErr(err error, str string, a ...interface{}) { + a = append([]interface{}{err}, a...) + if err != nil { + fmt.Fprintf(os.Stderr, str, a...) + os.Exit(1) + } +} + +func handleBool(ok bool, str string, a ...interface{}) { + if !ok { + fmt.Fprintf(os.Stderr, str, a...) + os.Exit(1) + } +} + +var tmpl = template.Must(template.New("pem2html"). + Funcs(template.FuncMap{ + "red": red, + "green": green, + "date": util.Date2HTML, + "datetime": util.DateTime2HTML, + "colorDatetime": util.DateTime2ColorHTML, + }).Parse(` + + + + + + + + +{{range $cert := .certs}} + + + + + + + +{{end}} +
+

CT log (Updated {{.now | colorDatetime}})

+
LoggedNotBeforeNotAfterSubject.CNIssuer.O
{{$cert.Updated | date}}{{$cert.X509.NotBefore | date}}{{$cert.X509.NotAfter | date}}{{$cert.X509.Subject.CommonName}}{{$cert.X509.Issuer.Organization}}
+`)) + +func getNow() time.Time { + stat, err := os.Stdin.Stat() + if err == nil { + return stat.ModTime() + } else { + return time.Now() + } +} + +var now = getNow() + +func green(t time.Time) string { + max := byte(0xF3) + // When did we get the cert? + // - 30 days ago => 0 green + // - just now => max green + greenness := util.MapRange( + util.TimeRange{now.AddDate(0, 0, -30), now}, + util.ByteRange{0, max}, + t) + return fmt.Sprintf("#%02X%02X%02X", max-greenness, max, max-greenness) +} + +func red(t time.Time) string { + max := byte(0xF3) + // When with the cert expire? + // - now => max red + // - 30 days from now => 0 red + redness := util.MapRange( + util.TimeRange{now, now.AddDate(0, 0, 30)}, + util.ByteRange{max, 0}, + t) + return fmt.Sprintf("#%02X%02X%02X", max, max-redness, max-redness) +} + +type Cert struct { + Url string + Updated time.Time + X509 *x509.Certificate +} + +type Certs []Cert + +// Len is the number of elements in the collection. +func (l Certs) Len() int { + return len(l) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (l Certs) Less(i, j int) bool { + return l[i].Updated.After(l[j].Updated) +} + +// Swap swaps the elements with indexes i and j. +func (l Certs) Swap(i, j int) { + tmp := l[i] + l[i] = l[j] + l[j] = tmp +} + +func main() { + data, err := ioutil.ReadAll(os.Stdin) + handleErr(err, "Error reading stdin: %v\n") + + var certs Certs + for len(data) > 0 { + var certPem *pem.Block + certPem, data = pem.Decode(data) + + var ok bool + var cert Cert + + cert.Url, ok = certPem.Headers["X-Crt-Sh-Url"] + handleBool(ok, "Did not get X-Crt-Sh-Url\n") + + str, ok := certPem.Headers["X-Crt-Sh-Updated"] + handleBool(ok, "Did not get X-Crt-Sh-Updated\n") + cert.Updated, err = time.Parse("2006-01-02T15:04:05Z", str) + handleErr(err, "Could not parse updated time") + + cert.X509, err = x509.ParseCertificate(certPem.Bytes) + handleErr(err, "Error parsing cert: %v\n") + + certs = append(certs, cert) + } + + sort.Sort(certs) + handleErr(tmpl.Execute(os.Stdout, map[string]interface{}{"certs": certs, "now": now}), "Could not execute template: %v\n") +} diff --git a/bin-src/diff-pem2html.go b/bin-src/diff-pem2html.go new file mode 100644 index 0000000..f3b25ff --- /dev/null +++ b/bin-src/diff-pem2html.go @@ -0,0 +1,109 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "html/template" + "io/ioutil" + "os" + + "./util" +) + +func handleErr(err error, str string, a ...interface{}) { + a = append([]interface{}{err}, a...) + if err != nil { + fmt.Fprintf(os.Stderr, str, a...) + os.Exit(1) + } +} + +func handleBool(ok bool, str string, a ...interface{}) { + if !ok { + fmt.Fprintf(os.Stderr, str, a...) + os.Exit(1) + } +} + +var tmpl = template.Must(template.New("pem2html"). + Funcs(template.FuncMap{ + "htmlcell": util.HTMLCellEscapeString, + }).Parse(` + + + +{{range $cert := .certs}} + + + + + + +{{end}} +
--- tls.pem
+++ crtsh.pem
@@ -1,{{.nTLS}} +1,{{.nCrtSh}} @@
{{$cert.Pfix | htmlcell}}{{$cert.X509.Subject.CommonName | htmlcell}}{{$cert.X509.NotBefore.Local.Format "2006-01-02 15:04:05"}}{{$cert.X509.NotAfter.Local.Format "2006-01-02 15:04:05"}}
+`)) + +type Cert struct { + Url string + action string + X509 *x509.Certificate +} + +func (cert Cert) Pfix() string { + return map[string]string{ + "add": "+", + "del": "-", + "ctx": " ", + }[cert.action] +} + +func (cert Cert) Class() string { + return "diff-" + cert.action +} + +func main() { + data, err := ioutil.ReadAll(os.Stdin) + handleErr(err, "Error reading stdin: %v\n") + + var certs []Cert + a := 0 + b := 0 + for len(data) > 0 { + var certPem *pem.Block + certPem, data = pem.Decode(data) + + var ok bool + var cert Cert + + cert.Url, ok = certPem.Headers["X-Crt-Sh-Url"] + handleBool(ok, "Did not get X-Crt-Sh-Url\n") + + cert.action, ok = certPem.Headers["X-Diff-Action"] + handleBool(ok, "Did not get X-Diff-Action\n") + switch cert.action { + case "add": + b++ + case "del": + a++ + case "ctx": + a++ + b++ + default: + handleBool(false, "Unknown X-Diff-Action: %q\n", cert.action) + } + + cert.X509, err = x509.ParseCertificate(certPem.Bytes) + if err != nil { + cert.X509 = new(x509.Certificate) + } + + certs = append(certs, cert) + } + + handleErr(tmpl.Execute(os.Stdout, map[string]interface{}{ + "certs": certs, + "nTLS": a, + "nCrtSh": b, + }), "Could not execute template: %v\n") +} diff --git a/bin-src/pem-diff.go b/bin-src/pem-diff.go new file mode 100644 index 0000000..da27a62 --- /dev/null +++ b/bin-src/pem-diff.go @@ -0,0 +1,140 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "sort" + "strings" +) + +func handleErr(err error, str string, a ...interface{}) { + a = append([]interface{}{err}, a...) + if err != nil { + fmt.Fprintf(os.Stderr, str, a...) + os.Exit(1) + } +} + +type Cert struct { + Url string + X509 *x509.Certificate +} + +func (cert Cert) WriteTo(w io.Writer, action string) error { + block := pem.Block{ + Type: "CERTIFICATE", + Headers: map[string]string{ + "X-Crt-Sh-Url": cert.Url, + "X-Diff-Action": action, + }, + Bytes: cert.X509.Raw, + } + return pem.Encode(w, &block) +} + +func readTLS(filename string) (map[string]Cert, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + + ret := make(map[string]Cert) + for len(data) > 0 { + var certPem *pem.Block + certPem, data = pem.Decode(data) + certX509, err := x509.ParseCertificate(certPem.Bytes) + if err != nil { + url, err2 := url.Parse(certPem.Headers["X-Socket"]) + if err2 != nil { + fmt.Fprintf(os.Stderr, "Could not get cert or even parse URL:\ncert: %v\nurl: %v\n", err, err2) + os.Exit(1) + } + ret[strings.Split(url.Host, ":")[0]] = Cert{ + X509: new(x509.Certificate), + } + } else { + ret[certX509.Subject.CommonName] = Cert{ + Url: fmt.Sprintf("https://crt.sh/?serial=%036x", certX509.SerialNumber), + X509: certX509, + } + } + } + return ret, nil +} + +func readCrtSh(filename string, hosts []string) (map[string]Cert, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + + ret := make(map[string]Cert) + for len(data) > 0 { + var certPem *pem.Block + certPem, data = pem.Decode(data) + certX509, err := x509.ParseCertificate(certPem.Bytes) + if err != nil { + return nil, err + } + for _, host := range hosts { + if certX509.VerifyHostname(host) == nil { + if old, haveold := ret[host]; !haveold || certX509.NotBefore.After(old.X509.NotBefore) { + ret[host] = Cert{ + Url: certPem.Headers["X-Crt-Sh-Url"], + X509: certX509, + } + } + } + } + } + return ret, nil +} + +func keys(m map[string]Cert) []string { + ret := make([]string, len(m)) + i := 0 + for k := range m { + ret[i] = k + i++ + } + sort.Strings(ret) + return ret +} + +func main() { + if len(os.Args) != 3 { + fmt.Fprintf(os.Stderr, "Usage: %s TLS-file crt.sh-file\n", os.Args[0]) + } + certsTLS, err := readTLS(os.Args[1]) + handleErr(err, "Could load TLS file: %v\n") + hostsTLS := keys(certsTLS) + certsCrtSh, err := readCrtSh(os.Args[2], hostsTLS) + handleErr(err, "Could load crt.sh file: %v\n") + + for _, host := range hostsTLS { + certTLS := certsTLS[host] + certCrtSh, haveCrtSh := certsCrtSh[host] + + if !haveCrtSh { + handleErr(certTLS.WriteTo(os.Stdout, "del"), "Could not encode PEM: %v\n") + } else if !certTLS.X509.Equal(certCrtSh.X509) { + handleErr(certTLS.WriteTo(os.Stdout, "del"), "Could not encode PEM: %v\n") + handleErr(certCrtSh.WriteTo(os.Stdout, "add"), "Could not encode PEM: %v\n") + } else { + handleErr(certCrtSh.WriteTo(os.Stdout, "ctx"), "Could not encode PEM: %v\n") + } + } +} diff --git a/bin-src/tls-getcerts.go b/bin-src/tls-getcerts.go new file mode 100644 index 0000000..34e25e5 --- /dev/null +++ b/bin-src/tls-getcerts.go @@ -0,0 +1,192 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "encoding/pem" + "encoding/xml" + "fmt" + "io" + "net" + "net/textproto" + "net/url" + "os" + "strings" + "time" +) + +type xmppStreamsFeatures struct { + XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"` +} + +type xmppTlsProceed struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"` +} + +func xmppStartTLS(connRaw net.Conn, host string) error { + decoder := xml.NewDecoder(connRaw) + + // send start + _, err := fmt.Fprintf(connRaw, "", host) + if err != nil { + return err + } + // read start + for { + t, err := decoder.Token() + if err != nil || t == nil { + return err + } + if se, ok := t.(xml.StartElement); ok { + if se.Name.Local != "stream" { + return xml.UnmarshalError(fmt.Sprintf("expected element of type <%s> but have <%s>", "stream", se.Name.Local)) + } + break + } + } + // read + var features xmppStreamsFeatures + err = decoder.DecodeElement(&features, nil) + if err != nil { + return err + } + // send + _, err = io.WriteString(connRaw, "") + if err != nil { + return err + } + // read + var proceed xmppTlsProceed + err = decoder.DecodeElement(&proceed, nil) + if err != nil { + return err + } + return nil +} + +// smtpCmd is a convenience function that sends a command, and reads +// (but discards) the response +func smtpCmd(tp *textproto.Conn, expectCode int, format string, args ...interface{}) error { + id, err := tp.Cmd(format, args...) + if err != nil { + return err + } + tp.StartResponse(id) + defer tp.EndResponse(id) + _, _, err = tp.ReadResponse(expectCode) + return err +} + +func smtpStartTLS(connRaw net.Conn, host string) error { + tp := textproto.NewConn(connRaw) + + // let the server introduce itself + _, _, err := tp.ReadResponse(220) + if err != nil { + return err + } + // introduce ourself + localhost, err := os.Hostname() + if err != nil { + localhost = "localhost" + } + err = smtpCmd(tp, 250, "EHLO %s", localhost) + if err != nil { + err := smtpCmd(tp, 250, "HELO %s", localhost) + if err != nil { + return err + } + } + // starttls + err = smtpCmd(tp, 220, "STARTTLS") + if err != nil { + return err + } + return nil +} + +func getcert(socket string) (*x509.Certificate, error) { + u, err := url.Parse(socket) + if err != nil { + return nil, err + } + host, _, err := net.SplitHostPort(u.Host) + if err != nil { + return nil, err + } + + connRaw, err := net.Dial(u.Scheme, u.Host) + if err != nil { + return nil, err + } + err = connRaw.SetDeadline(time.Now().Add(5 * time.Second)) + if err != nil { + return nil, err + } + + switch u.Path { + case "", "/": + // do nothing + case "/xmpp": + err = xmppStartTLS(connRaw, host) + if err != nil { + return nil, err + } + case "/smtp": + err = smtpStartTLS(connRaw, host) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("Unknown negotiation path: %q", u.Path) + } + + connTLS := tls.Client(connRaw, &tls.Config{InsecureSkipVerify: true}) + defer connTLS.Close() + err = connTLS.Handshake() + if err != nil { + return nil, err + } + + cstate := connTLS.ConnectionState() + + opts := x509.VerifyOptions{ + DNSName: host, + Intermediates: x509.NewCertPool(), + } + for _, cert := range cstate.PeerCertificates[1:] { + opts.Intermediates.AddCert(cert) + } + + cert := cstate.PeerCertificates[0] + _, err = cert.Verify(opts) + return cert, err +} + +func split(socket string) (net, addr string) { + ary := strings.SplitN(socket, ":", 2) + if len(ary) == 1 { + return "tcp", ary[0] + } + return ary[0], ary[1] +} + +func main() { + for _, socket := range os.Args[1:] { + fmt.Fprintf(os.Stderr, "Getting %q... ", socket) + block := pem.Block{ + Type: "CERTIFICATE", + Headers: map[string]string{"X-Socket": socket}, + Bytes: nil, + } + cert, err := getcert(socket) + if cert != nil { + block.Bytes = cert.Raw + } + if err != nil { + block.Headers["X-Error"] = err.Error() + } + pem.Encode(os.Stdout, &block) + fmt.Fprintln(os.Stderr, "[done]") + } +} diff --git a/bin-src/tls-pem2html.go b/bin-src/tls-pem2html.go new file mode 100644 index 0000000..bc14f9a --- /dev/null +++ b/bin-src/tls-pem2html.go @@ -0,0 +1,160 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "html/template" + "io/ioutil" + "os" + "sort" + "time" + + "./util" +) + +func handleErr(err error, str string, a ...interface{}) { + a = append([]interface{}{err}, a...) + if err != nil { + fmt.Fprintf(os.Stderr, str, a...) + os.Exit(1) + } +} + +func handleBool(ok bool, str string, a ...interface{}) { + if !ok { + fmt.Fprintf(os.Stderr, str, a...) + os.Exit(1) + } +} + +var tmpl = template.Must(template.New("pem2html"). + Funcs(template.FuncMap{ + "red": red, + "green": green, + "date": util.Date2HTML, + "datetime": util.DateTime2HTML, + "colorDatetime": util.DateTime2ColorHTML, + "htmlcell": util.HTMLCellEscapeString, + }).Parse(` + + + + + + + +{{range $cert := .certs}} + + + + + + +{{end}} +
+

Live Certs (Updated {{.now | colorDatetime}})

+
NotBeforeNotAfterSubject.CNSocket
{{$cert.X509.NotBefore | date}}{{$cert.X509.NotAfter | date}}{{$cert.X509.Subject.CommonName | htmlcell}}{{$cert.Socket | htmlcell}}
+`)) + +func getNow() time.Time { + stat, err := os.Stdin.Stat() + if err == nil { + return stat.ModTime() + } else { + return time.Now() + } +} + +var now = getNow() + +func green(t time.Time) string { + max := byte(0xF3) + // When did we get the cert? + // - 30 days ago => 0 green + // - just now => max green + greenness := util.MapRange( + util.TimeRange{now.AddDate(0, 0, -30), now}, + util.ByteRange{0, max}, + t) + return fmt.Sprintf("#%02X%02X%02X", max-greenness, max, max-greenness) +} + +func red(t time.Time) string { + max := byte(0xF3) + // When with the cert expire? + // - now => max red + // - 30 days from now => 0 red + redness := util.MapRange( + util.TimeRange{now, now.AddDate(0, 0, 30)}, + util.ByteRange{max, 0}, + t) + return fmt.Sprintf("#%02X%02X%02X", max, max-redness, max-redness) +} + +type Cert struct { + Socket string + Error string + X509 *x509.Certificate +} + +func (cert Cert) Url() string { + return fmt.Sprintf("https://crt.sh/?serial=%036x", cert.X509.SerialNumber) +} + +func (cert Cert) Class() string { + if cert.Error == "" { + return "" + } else { + return "invalid" + } +} + +type Certs []Cert + +// Len is the number of elements in the collection. +func (l Certs) Len() int { + return len(l) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (l Certs) Less(i, j int) bool { + return l[i].X509.NotAfter.After(l[j].X509.NotAfter) +} + +// Swap swaps the elements with indexes i and j. +func (l Certs) Swap(i, j int) { + tmp := l[i] + l[i] = l[j] + l[j] = tmp +} + +func main() { + data, err := ioutil.ReadAll(os.Stdin) + handleErr(err, "Error reading stdin: %v\n") + + var certs Certs + for len(data) > 0 { + var certPem *pem.Block + certPem, data = pem.Decode(data) + + var ok bool + var cert Cert + + cert.Socket, ok = certPem.Headers["X-Socket"] + handleBool(ok, "Did not get X-Socket\n") + + cert.Error, ok = certPem.Headers["X-Error"] + + cert.X509, err = x509.ParseCertificate(certPem.Bytes) + if err != nil { + cert.X509 = new(x509.Certificate) + } + + certs = append(certs, cert) + } + + sort.Sort(certs) + handleErr(tmpl.Execute(os.Stdout, map[string]interface{}{"certs": certs, "now": now}), "Could not execute template: %v\n") +} diff --git a/bin-src/util/date.go b/bin-src/util/date.go new file mode 100644 index 0000000..3b5c457 --- /dev/null +++ b/bin-src/util/date.go @@ -0,0 +1,51 @@ +package util + +import ( + "html/template" + "time" +) + +func Date2HTML(t time.Time) template.HTML { + return template.HTML(t.Local().Format("")) +} + +func DateTime2HTML(t time.Time) template.HTML { + return template.HTML(t.Local().Format("")) +} + +func DateTime2ColorHTML(t time.Time) template.HTML { + return template.HTML(t.Local().Format("")) +} + +type TimeRange struct { + A, B time.Time +} + +func (tr TimeRange) ToPct(point time.Time) float64 { + dur_ab := tr.B.Sub(tr.A) + dur_ap := point.Sub(tr.A) + return float64(dur_ap) / float64(dur_ab) +} + +type ByteRange struct { + A, B byte +} + +func (br ByteRange) FromPct(pct float64) byte { + ab := int16(br.B) - int16(br.A) + ap := int16(pct * float64(ab)) + return byte(int16(br.A) + ap) +} + +func PctCap(pct float64) float64 { + if pct < 0 { + pct = 0 + } else if pct > 1 { + pct = 1 + } + return pct +} + +func MapRange(tr TimeRange, br ByteRange, t time.Time) byte { + return br.FromPct(PctCap(tr.ToPct(t))) +} diff --git a/bin-src/util/html.go b/bin-src/util/html.go new file mode 100644 index 0000000..af2ce60 --- /dev/null +++ b/bin-src/util/html.go @@ -0,0 +1,14 @@ +package util + +import ( + "html/template" + "strings" +) + +func HTMLCellEscapeString(s string) template.HTML { + html := template.HTMLEscapeString(s) + if strings.TrimSpace(html) == "" { + html = " " + } + return template.HTML(html) +} diff --git a/cfg/domains.txt b/cfg/domains.txt new file mode 100644 index 0000000..1d5fef5 --- /dev/null +++ b/cfg/domains.txt @@ -0,0 +1,4 @@ +parabola.nu +parabolagnulinux.org +lukeshu.com +andrewdm.me diff --git a/cfg/jarmon-proton.js b/cfg/jarmon-proton.js new file mode 100644 index 0000000..532f802 --- /dev/null +++ b/cfg/jarmon-proton.js @@ -0,0 +1,155 @@ +/* Copyright (c) Richard Wall + * See LICENSE for details. + * + * Some example recipes for Collectd RRD data - you *will* need to modify this + * based on the RRD data available on your system. + */ + +$(function() { + + for (var i = 0; i < jarmon.timeRangeShortcuts.length; i++) { + if (jarmon.timeRangeShortcuts[i][0] === 'last day') { + jarmon.timeRangeShortcuts[i][2] = true; + } + } + + var srv = 'https://proton.parabola.nu/collectd/proton.parabola.nu/' + + var tabRecipes = [ + ['Overview', ['cpu', 'memory', 'swap-use']], + ['Iface', ['interface-inet', 'interface-lvpn', 'interface-lo']], + ['Other', ['load', 'swap-use', 'swap-io', 'users', 'entropy', 'uptime']], + ]; + + var chartRecipes = { + 'cpu': { + title: 'CPU Usage', + data: [ + [srv+'cpu-0/cpu-steal.rrd', 0, 'Steal', 'jiffy'], + [srv+'cpu-0/cpu-interrupt.rrd', 0, 'IRQ', 'jiffy'], + [srv+'cpu-0/cpu-softirq.rrd', 0, 'SoftIRQ', 'jiffy'], + [srv+'cpu-0/cpu-system.rrd', 0, 'System', 'jiffy'], + [srv+'cpu-0/cpu-wait.rrd', 0, 'IO', 'jiffy'], + [srv+'cpu-0/cpu-user.rrd', 0, 'User', 'jiffy'], + //[srv+'cpu-0/cpu-nice.rrd', 0, 'Nice', 'jiffy'], + [srv+'cpu-0/cpu-idle.rrd', 0, 'Idle', 'jiffy'], + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + jarmon.Chart.STACKED_OPTIONS, + {yaxis: {min: 0, max: 110}}) + }, + + 'memory': { + title: 'Memory', + data: [ + [srv+'memory/memory-used.rrd', 0, 'Used', 'B'], + [srv+'memory/memory-slab_unrecl.rrd', 0, 'Slab', 'B'], + [srv+'memory/memory-slab_recl.rrd', 0, 'Slab (Recl)', 'B'], + [srv+'memory/memory-cached.rrd', 0, 'Cached', 'B'], + [srv+'memory/memory-buffered.rrd', 0, 'Buffered', 'B'], + [srv+'memory/memory-free.rrd', 0, 'Free', 'B'] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS, + jarmon.Chart.STACKED_OPTIONS) + }, + + 'load': { + title: 'Load Average', + data: [ + [srv+'load/load.rrd', 'shortterm', 'Short Term (1m)', ''], + [srv+'load/load.rrd', 'midterm', 'Medium Term (5m)', ''], + [srv+'load/load.rrd', 'longterm', 'Long Term (15m)', ''] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) + }, + + 'interface-inet': { + title: 'ens18 Throughput', + data: [ + [srv+'interface-ens18/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], + [srv+'interface-ens18/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) + }, + + 'interface-lvpn': { + title: 'lvpn Throughput', + data: [ + [srv+'interface-lvpn/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], + [srv+'interface-lvpn/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) + }, + + 'interface-lo': { + title: 'lo Throughput', + data: [ + [srv+'interface-lo/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], + [srv+'interface-lo/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) + }, + + 'entropy': { + title: 'Entropy', + data: [ + [srv+'entropy/entropy.rrd', 0, 'Entropy', 'b'] + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + {series: {lines: {fill: 0.5}}}) + }, + + 'users': { + title: 'Users', + data: [ + [srv+'users/users.rrd', 0, 'Users', 'users'] + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + {series: {lines: {fill: 0.5}}}) + }, + + 'uptime': { + title: 'Uptime', + data: [ + [srv+'uptime/uptime.rrd', 0, 'Uptime', 'days', function(v) { return v/(60*60*24); }] + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + {series: {lines: {fill: 0.5}}}) + }, + + 'swap-use': { + title: 'Swap Usage', + data: [ + [srv+'swap/swap-used.rrd', 0, 'Used', 'B'], + [srv+'swap/swap-cached.rrd', 0, 'Cached', 'B'], + [srv+'swap/swap-free.rrd', 0, 'Free', 'B'] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS, + jarmon.Chart.STACKED_OPTIONS) + }, + + 'swap-io': { + title: 'Swap IO', + data: [ + // In pages unless the Swap.ReportBytes option is set + [srv+'swap/swap_io-in.rrd', 0, 'In', 'page'], + [srv+'swap/swap_io-out.rrd', 0, 'Out', 'page'] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) + } + }; + + var chartTemplate = $(".jarmon.proton .chart-container").remove(); + + jarmon.buildTabbedChartUi( + chartTemplate, + chartRecipes, + $('.jarmon.proton .tabbed-chart-interface'), + tabRecipes, + $('.jarmon.proton .chartRangeControl') + ); +}); diff --git a/cfg/jarmon-winston.js b/cfg/jarmon-winston.js new file mode 100644 index 0000000..1349928 --- /dev/null +++ b/cfg/jarmon-winston.js @@ -0,0 +1,154 @@ +/* Copyright (c) Richard Wall + * See LICENSE for details. + * + * Some example recipes for Collectd RRD data - you *will* need to modify this + * based on the RRD data available on your system. + */ + +$(function() { + + for (var i = 0; i < jarmon.timeRangeShortcuts.length; i++) { + if (jarmon.timeRangeShortcuts[i][0] === 'last day') { + jarmon.timeRangeShortcuts[i][2] = true; + } + } + + var srv = 'https://winston.parabola.nu/collectd/winston.parabola.nu/' + + var tabRecipes = [ + ['wOverview', ['wload', 'wmemory', 'winterface-inet']], + //['wOverview', [/*'cpu', */'wmemory'/*, 'swap-use'*/]], + //['wIface', ['winterface-inet', /*'interface-lvpn',*/ 'winterface-lo']], + //['wOther', ['wload', /*'swap-use', 'swap-io', 'users', 'entropy', 'uptime'*/]], + ]; + + var chartRecipes = { + /* + 'cpu': { + title: 'CPU Usage', + data: [ + [srv+'cpu-0/cpu-steal.rrd', 0, 'Steal', 'jiffy'], + [srv+'cpu-0/cpu-interrupt.rrd', 0, 'IRQ', 'jiffy'], + [srv+'cpu-0/cpu-softirq.rrd', 0, 'SoftIRQ', 'jiffy'], + [srv+'cpu-0/cpu-system.rrd', 0, 'System', 'jiffy'], + [srv+'cpu-0/cpu-wait.rrd', 0, 'IO', 'jiffy'], + [srv+'cpu-0/cpu-user.rrd', 0, 'User', 'jiffy'], + //[srv+'cpu-0/cpu-nice.rrd', 0, 'Nice', 'jiffy'], + [srv+'cpu-0/cpu-idle.rrd', 0, 'Idle', 'jiffy'], + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + jarmon.Chart.STACKED_OPTIONS, + {yaxis: {min: 0, max: 110}}) + }, + */ + + 'wmemory': { + title: 'Memory', + data: [ + [srv+'memory/memory-used.rrd', 0, 'Used', 'B'], + [srv+'memory/memory-slab_unrecl.rrd', 0, 'Slab', 'B'], + [srv+'memory/memory-slab_recl.rrd', 0, 'Slab (Recl)', 'B'], + [srv+'memory/memory-cached.rrd', 0, 'Cached', 'B'], + [srv+'memory/memory-buffered.rrd', 0, 'Buffered', 'B'], + [srv+'memory/memory-free.rrd', 0, 'Free', 'B'] + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + jarmon.Chart.STACKED_OPTIONS) + }, + + 'wload': { + title: 'Load Average', + data: [ + [srv+'load/load.rrd', 'shortterm', 'Short Term (1m)', ''], + [srv+'load/load.rrd', 'midterm', 'Medium Term (5m)', ''], + [srv+'load/load.rrd', 'longterm', 'Long Term (15m)', ''] + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + {yaxis: {min: 0}}) + }, + + 'winterface-inet': { + title: 'eth0 Throughput', + data: [ + [srv+'interface-eth0/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], + [srv+'interface-eth0/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) + }, + + 'winterface-lo': { + title: 'lo Throughput', + data: [ + [srv+'interface-lo/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], + [srv+'interface-lo/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) + }, + + /* + 'entropy': { + title: 'Entropy', + data: [ + [srv+'entropy/entropy.rrd', 0, 'Entropy', 'b'] + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + {series: {lines: {fill: 0.5}}}) + }, + + 'users': { + title: 'Users', + data: [ + [srv+'users/users.rrd', 0, 'Users', 'users'] + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + {series: {lines: {fill: 0.5}}}) + }, + + 'uptime': { + title: 'Uptime', + data: [ + [srv+'uptime/uptime.rrd', 0, 'Uptime', 'days', function(v) { return v/(60*60*24); }] + ], + options: jQuery.extend(true, {}, + jarmon.Chart.BASE_OPTIONS, + {series: {lines: {fill: 0.5}}}) + }, + + 'swap-use': { + title: 'Swap Usage', + data: [ + [srv+'swap/swap-used.rrd', 0, 'Used', 'B'], + [srv+'swap/swap-cached.rrd', 0, 'Cached', 'B'], + [srv+'swap/swap-free.rrd', 0, 'Free', 'B'] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS, + jarmon.Chart.STACKED_OPTIONS) + }, + + 'swap-io': { + title: 'Swap IO', + data: [ + // In pages unless the Swap.ReportBytes option is set + [srv+'swap/swap_io-in.rrd', 0, 'In', 'page'], + [srv+'swap/swap_io-out.rrd', 0, 'Out', 'page'] + ], + options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) + } + */ + }; + + var chartTemplate = $(".jarmon.winston .chart-container").remove(); + + jarmon.buildTabbedChartUi( + chartTemplate, + chartRecipes, + $('.jarmon.winston .tabbed-chart-interface'), + tabRecipes, + $('.jarmon.winston .chartRangeControl') + ); +}); diff --git a/cfg/sockets.txt b/cfg/sockets.txt new file mode 100644 index 0000000..b32a114 --- /dev/null +++ b/cfg/sockets.txt @@ -0,0 +1,15 @@ +tcp://parabola.nu:5222/xmpp +#tcp://proton.parabola.nu:443 +tcp://proton.parabola.nu:465 +tcp://parabola.nu:587/smtp + +tcp://winston.parabola.nu:443 + +tcp://ramhost.lukeshu.com:443 + +tcp://mav.lukeshu.com:443 +#tcp://mav.lukeshu.com:25/smtp +tcp://mav.lukeshu.com:587/smtp +tcp://mav.lukeshu.com:993 + +tcp://neo.andrewdm.me:443 diff --git a/colordate.js b/colordate.js deleted file mode 100644 index e5331eb..0000000 --- a/colordate.js +++ /dev/null @@ -1,41 +0,0 @@ -(function() { - // in milliseconds - var now = Date.now(); - var oneday = 1000*60*60*24; - - // maps from a point on iRange to a point on oRange - var mapRange = function(iRange, oRange, iPoint) { - var pct = (iPoint - iRange[0])/(iRange[1]-iRange[0]); - if (pct < 0) { - pct = 0; - } else if (pct > 1) { - pct = 1; - } - var oPoint = oRange[0] + (pct * (oRange[1]-oRange[0])); - return oPoint; - } - - - var rgb = function(r, g, b) { - return "rgb(" + Math.trunc(r) + "," + Math.trunc(g) + "," + Math.trunc(b) + ")"; - }; - - var date2color = function(t) { - var max = 0xFF; - var red = mapRange([now-oneday, now-(oneday/2)], - [max, 0], - t); - var green = mapRange([now-(oneday/2), now], - [0, max], - t); - return rgb(max-green, max-red, max-green-red); - }; - - var main = function() { - document.querySelectorAll('time.daily').forEach(function(time) { - time.style.backgroundColor = date2color(Date.parse(time.dateTime)); - }); - }; - - document.addEventListener("DOMContentLoaded", main, false); -})(); diff --git a/config-domains.txt b/config-domains.txt deleted file mode 100644 index 1d5fef5..0000000 --- a/config-domains.txt +++ /dev/null @@ -1,4 +0,0 @@ -parabola.nu -parabolagnulinux.org -lukeshu.com -andrewdm.me diff --git a/config-jarmon-proton.js b/config-jarmon-proton.js deleted file mode 100644 index 532f802..0000000 --- a/config-jarmon-proton.js +++ /dev/null @@ -1,155 +0,0 @@ -/* Copyright (c) Richard Wall - * See LICENSE for details. - * - * Some example recipes for Collectd RRD data - you *will* need to modify this - * based on the RRD data available on your system. - */ - -$(function() { - - for (var i = 0; i < jarmon.timeRangeShortcuts.length; i++) { - if (jarmon.timeRangeShortcuts[i][0] === 'last day') { - jarmon.timeRangeShortcuts[i][2] = true; - } - } - - var srv = 'https://proton.parabola.nu/collectd/proton.parabola.nu/' - - var tabRecipes = [ - ['Overview', ['cpu', 'memory', 'swap-use']], - ['Iface', ['interface-inet', 'interface-lvpn', 'interface-lo']], - ['Other', ['load', 'swap-use', 'swap-io', 'users', 'entropy', 'uptime']], - ]; - - var chartRecipes = { - 'cpu': { - title: 'CPU Usage', - data: [ - [srv+'cpu-0/cpu-steal.rrd', 0, 'Steal', 'jiffy'], - [srv+'cpu-0/cpu-interrupt.rrd', 0, 'IRQ', 'jiffy'], - [srv+'cpu-0/cpu-softirq.rrd', 0, 'SoftIRQ', 'jiffy'], - [srv+'cpu-0/cpu-system.rrd', 0, 'System', 'jiffy'], - [srv+'cpu-0/cpu-wait.rrd', 0, 'IO', 'jiffy'], - [srv+'cpu-0/cpu-user.rrd', 0, 'User', 'jiffy'], - //[srv+'cpu-0/cpu-nice.rrd', 0, 'Nice', 'jiffy'], - [srv+'cpu-0/cpu-idle.rrd', 0, 'Idle', 'jiffy'], - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - jarmon.Chart.STACKED_OPTIONS, - {yaxis: {min: 0, max: 110}}) - }, - - 'memory': { - title: 'Memory', - data: [ - [srv+'memory/memory-used.rrd', 0, 'Used', 'B'], - [srv+'memory/memory-slab_unrecl.rrd', 0, 'Slab', 'B'], - [srv+'memory/memory-slab_recl.rrd', 0, 'Slab (Recl)', 'B'], - [srv+'memory/memory-cached.rrd', 0, 'Cached', 'B'], - [srv+'memory/memory-buffered.rrd', 0, 'Buffered', 'B'], - [srv+'memory/memory-free.rrd', 0, 'Free', 'B'] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS, - jarmon.Chart.STACKED_OPTIONS) - }, - - 'load': { - title: 'Load Average', - data: [ - [srv+'load/load.rrd', 'shortterm', 'Short Term (1m)', ''], - [srv+'load/load.rrd', 'midterm', 'Medium Term (5m)', ''], - [srv+'load/load.rrd', 'longterm', 'Long Term (15m)', ''] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) - }, - - 'interface-inet': { - title: 'ens18 Throughput', - data: [ - [srv+'interface-ens18/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], - [srv+'interface-ens18/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) - }, - - 'interface-lvpn': { - title: 'lvpn Throughput', - data: [ - [srv+'interface-lvpn/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], - [srv+'interface-lvpn/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) - }, - - 'interface-lo': { - title: 'lo Throughput', - data: [ - [srv+'interface-lo/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], - [srv+'interface-lo/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) - }, - - 'entropy': { - title: 'Entropy', - data: [ - [srv+'entropy/entropy.rrd', 0, 'Entropy', 'b'] - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - {series: {lines: {fill: 0.5}}}) - }, - - 'users': { - title: 'Users', - data: [ - [srv+'users/users.rrd', 0, 'Users', 'users'] - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - {series: {lines: {fill: 0.5}}}) - }, - - 'uptime': { - title: 'Uptime', - data: [ - [srv+'uptime/uptime.rrd', 0, 'Uptime', 'days', function(v) { return v/(60*60*24); }] - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - {series: {lines: {fill: 0.5}}}) - }, - - 'swap-use': { - title: 'Swap Usage', - data: [ - [srv+'swap/swap-used.rrd', 0, 'Used', 'B'], - [srv+'swap/swap-cached.rrd', 0, 'Cached', 'B'], - [srv+'swap/swap-free.rrd', 0, 'Free', 'B'] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS, - jarmon.Chart.STACKED_OPTIONS) - }, - - 'swap-io': { - title: 'Swap IO', - data: [ - // In pages unless the Swap.ReportBytes option is set - [srv+'swap/swap_io-in.rrd', 0, 'In', 'page'], - [srv+'swap/swap_io-out.rrd', 0, 'Out', 'page'] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) - } - }; - - var chartTemplate = $(".jarmon.proton .chart-container").remove(); - - jarmon.buildTabbedChartUi( - chartTemplate, - chartRecipes, - $('.jarmon.proton .tabbed-chart-interface'), - tabRecipes, - $('.jarmon.proton .chartRangeControl') - ); -}); diff --git a/config-jarmon-winston.js b/config-jarmon-winston.js deleted file mode 100644 index 1349928..0000000 --- a/config-jarmon-winston.js +++ /dev/null @@ -1,154 +0,0 @@ -/* Copyright (c) Richard Wall - * See LICENSE for details. - * - * Some example recipes for Collectd RRD data - you *will* need to modify this - * based on the RRD data available on your system. - */ - -$(function() { - - for (var i = 0; i < jarmon.timeRangeShortcuts.length; i++) { - if (jarmon.timeRangeShortcuts[i][0] === 'last day') { - jarmon.timeRangeShortcuts[i][2] = true; - } - } - - var srv = 'https://winston.parabola.nu/collectd/winston.parabola.nu/' - - var tabRecipes = [ - ['wOverview', ['wload', 'wmemory', 'winterface-inet']], - //['wOverview', [/*'cpu', */'wmemory'/*, 'swap-use'*/]], - //['wIface', ['winterface-inet', /*'interface-lvpn',*/ 'winterface-lo']], - //['wOther', ['wload', /*'swap-use', 'swap-io', 'users', 'entropy', 'uptime'*/]], - ]; - - var chartRecipes = { - /* - 'cpu': { - title: 'CPU Usage', - data: [ - [srv+'cpu-0/cpu-steal.rrd', 0, 'Steal', 'jiffy'], - [srv+'cpu-0/cpu-interrupt.rrd', 0, 'IRQ', 'jiffy'], - [srv+'cpu-0/cpu-softirq.rrd', 0, 'SoftIRQ', 'jiffy'], - [srv+'cpu-0/cpu-system.rrd', 0, 'System', 'jiffy'], - [srv+'cpu-0/cpu-wait.rrd', 0, 'IO', 'jiffy'], - [srv+'cpu-0/cpu-user.rrd', 0, 'User', 'jiffy'], - //[srv+'cpu-0/cpu-nice.rrd', 0, 'Nice', 'jiffy'], - [srv+'cpu-0/cpu-idle.rrd', 0, 'Idle', 'jiffy'], - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - jarmon.Chart.STACKED_OPTIONS, - {yaxis: {min: 0, max: 110}}) - }, - */ - - 'wmemory': { - title: 'Memory', - data: [ - [srv+'memory/memory-used.rrd', 0, 'Used', 'B'], - [srv+'memory/memory-slab_unrecl.rrd', 0, 'Slab', 'B'], - [srv+'memory/memory-slab_recl.rrd', 0, 'Slab (Recl)', 'B'], - [srv+'memory/memory-cached.rrd', 0, 'Cached', 'B'], - [srv+'memory/memory-buffered.rrd', 0, 'Buffered', 'B'], - [srv+'memory/memory-free.rrd', 0, 'Free', 'B'] - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - jarmon.Chart.STACKED_OPTIONS) - }, - - 'wload': { - title: 'Load Average', - data: [ - [srv+'load/load.rrd', 'shortterm', 'Short Term (1m)', ''], - [srv+'load/load.rrd', 'midterm', 'Medium Term (5m)', ''], - [srv+'load/load.rrd', 'longterm', 'Long Term (15m)', ''] - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - {yaxis: {min: 0}}) - }, - - 'winterface-inet': { - title: 'eth0 Throughput', - data: [ - [srv+'interface-eth0/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], - [srv+'interface-eth0/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) - }, - - 'winterface-lo': { - title: 'lo Throughput', - data: [ - [srv+'interface-lo/if_octets.rrd', 'tx', 'Transmit', 'bit/s', function (v) { return -v*8; }], - [srv+'interface-lo/if_octets.rrd', 'rx', 'Receive', 'bit/s', function (v) { return v*8; }] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) - }, - - /* - 'entropy': { - title: 'Entropy', - data: [ - [srv+'entropy/entropy.rrd', 0, 'Entropy', 'b'] - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - {series: {lines: {fill: 0.5}}}) - }, - - 'users': { - title: 'Users', - data: [ - [srv+'users/users.rrd', 0, 'Users', 'users'] - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - {series: {lines: {fill: 0.5}}}) - }, - - 'uptime': { - title: 'Uptime', - data: [ - [srv+'uptime/uptime.rrd', 0, 'Uptime', 'days', function(v) { return v/(60*60*24); }] - ], - options: jQuery.extend(true, {}, - jarmon.Chart.BASE_OPTIONS, - {series: {lines: {fill: 0.5}}}) - }, - - 'swap-use': { - title: 'Swap Usage', - data: [ - [srv+'swap/swap-used.rrd', 0, 'Used', 'B'], - [srv+'swap/swap-cached.rrd', 0, 'Cached', 'B'], - [srv+'swap/swap-free.rrd', 0, 'Free', 'B'] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS, - jarmon.Chart.STACKED_OPTIONS) - }, - - 'swap-io': { - title: 'Swap IO', - data: [ - // In pages unless the Swap.ReportBytes option is set - [srv+'swap/swap_io-in.rrd', 0, 'In', 'page'], - [srv+'swap/swap_io-out.rrd', 0, 'Out', 'page'] - ], - options: jQuery.extend(true, {}, jarmon.Chart.BASE_OPTIONS) - } - */ - }; - - var chartTemplate = $(".jarmon.winston .chart-container").remove(); - - jarmon.buildTabbedChartUi( - chartTemplate, - chartRecipes, - $('.jarmon.winston .tabbed-chart-interface'), - tabRecipes, - $('.jarmon.winston .chartRangeControl') - ); -}); diff --git a/config-sockets.txt b/config-sockets.txt deleted file mode 100644 index b32a114..0000000 --- a/config-sockets.txt +++ /dev/null @@ -1,15 +0,0 @@ -tcp://parabola.nu:5222/xmpp -#tcp://proton.parabola.nu:443 -tcp://proton.parabola.nu:465 -tcp://parabola.nu:587/smtp - -tcp://winston.parabola.nu:443 - -tcp://ramhost.lukeshu.com:443 - -tcp://mav.lukeshu.com:443 -#tcp://mav.lukeshu.com:25/smtp -tcp://mav.lukeshu.com:587/smtp -tcp://mav.lukeshu.com:993 - -tcp://neo.andrewdm.me:443 diff --git a/cron-daily b/cron-daily deleted file mode 100755 index 117eac4..0000000 --- a/cron-daily +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -cd "$(dirname -- "$0")" -date > NET-crtsh -date > NET-tls -make diff --git a/crtsh-getcerts b/crtsh-getcerts deleted file mode 100755 index 0191e2e..0000000 --- a/crtsh-getcerts +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env ruby -require 'nokogiri' -require 'open-uri' - -certs = {} -ARGV.each do |domain| - [ domain, "%.#{domain}" ].each do |pattern| - Nokogiri::XML(open("https://crt.sh/atom?identity=#{pattern}&exclude=expired")).css('feed > entry').each do |entry| - url = entry.css('id').first.text.split("#").first - - updated = entry.css('updated').first.text - - html = Nokogiri::HTML(entry.css('summary').first.text) - html.css('br').each{|br| br.replace("\n")} - pem = html.css('div').first.text - - lines = pem.split("\n") - lines.insert(1, "X-Crt-Sh-Url: #{url}", "X-Crt-Sh-Updated: #{updated}") - pem = lines.join("\n")+"\n" - - certs[url] = pem - end - end -end - -certs.each do |url, pem| - print pem -end diff --git a/crtsh-pem2html.go b/crtsh-pem2html.go deleted file mode 100644 index 109917c..0000000 --- a/crtsh-pem2html.go +++ /dev/null @@ -1,150 +0,0 @@ -package main - -import ( - "crypto/x509" - "encoding/pem" - "fmt" - "html/template" - "io/ioutil" - "os" - "sort" - "time" - - "./util" -) - -func handleErr(err error, str string, a ...interface{}) { - a = append([]interface{}{err}, a...) - if err != nil { - fmt.Fprintf(os.Stderr, str, a...) - os.Exit(1) - } -} - -func handleBool(ok bool, str string, a ...interface{}) { - if !ok { - fmt.Fprintf(os.Stderr, str, a...) - os.Exit(1) - } -} - -var tmpl = template.Must(template.New("pem2html"). - Funcs(template.FuncMap{ - "red": red, - "green": green, - "date": util.Date2HTML, - "datetime": util.DateTime2HTML, - "colorDatetime": util.DateTime2ColorHTML, - }).Parse(` - - - - - - - - -{{range $cert := .certs}} - - - - - - - -{{end}} -
-

CT log (Updated {{.now | colorDatetime}})

-
LoggedNotBeforeNotAfterSubject.CNIssuer.O
{{$cert.Updated | date}}{{$cert.X509.NotBefore | date}}{{$cert.X509.NotAfter | date}}{{$cert.X509.Subject.CommonName}}{{$cert.X509.Issuer.Organization}}
-`)) - -func getNow() time.Time { - stat, err := os.Stdin.Stat() - if err == nil { - return stat.ModTime() - } else { - return time.Now() - } -} - -var now = getNow() - -func green(t time.Time) string { - max := byte(0xF3) - // When did we get the cert? - // - 30 days ago => 0 green - // - just now => max green - greenness := util.MapRange( - util.TimeRange{now.AddDate(0, 0, -30), now}, - util.ByteRange{0, max}, - t) - return fmt.Sprintf("#%02X%02X%02X", max-greenness, max, max-greenness) -} - -func red(t time.Time) string { - max := byte(0xF3) - // When with the cert expire? - // - now => max red - // - 30 days from now => 0 red - redness := util.MapRange( - util.TimeRange{now, now.AddDate(0, 0, 30)}, - util.ByteRange{max, 0}, - t) - return fmt.Sprintf("#%02X%02X%02X", max, max-redness, max-redness) -} - -type Cert struct { - Url string - Updated time.Time - X509 *x509.Certificate -} - -type Certs []Cert - -// Len is the number of elements in the collection. -func (l Certs) Len() int { - return len(l) -} - -// Less reports whether the element with -// index i should sort before the element with index j. -func (l Certs) Less(i, j int) bool { - return l[i].Updated.After(l[j].Updated) -} - -// Swap swaps the elements with indexes i and j. -func (l Certs) Swap(i, j int) { - tmp := l[i] - l[i] = l[j] - l[j] = tmp -} - -func main() { - data, err := ioutil.ReadAll(os.Stdin) - handleErr(err, "Error reading stdin: %v\n") - - var certs Certs - for len(data) > 0 { - var certPem *pem.Block - certPem, data = pem.Decode(data) - - var ok bool - var cert Cert - - cert.Url, ok = certPem.Headers["X-Crt-Sh-Url"] - handleBool(ok, "Did not get X-Crt-Sh-Url\n") - - str, ok := certPem.Headers["X-Crt-Sh-Updated"] - handleBool(ok, "Did not get X-Crt-Sh-Updated\n") - cert.Updated, err = time.Parse("2006-01-02T15:04:05Z", str) - handleErr(err, "Could not parse updated time") - - cert.X509, err = x509.ParseCertificate(certPem.Bytes) - handleErr(err, "Error parsing cert: %v\n") - - certs = append(certs, cert) - } - - sort.Sort(certs) - handleErr(tmpl.Execute(os.Stdout, map[string]interface{}{"certs": certs, "now": now}), "Could not execute template: %v\n") -} diff --git a/dashboard-daily.service b/dashboard-daily.service deleted file mode 100644 index bafdbdd..0000000 --- a/dashboard-daily.service +++ /dev/null @@ -1,6 +0,0 @@ -[Unit] -Description=Dashboard daily update - -[Service] -Type=oneshot -ExecStart=/bin/sh -c '~/dashboard/cron-daily' diff --git a/dashboard-daily.timer b/dashboard-daily.timer deleted file mode 100644 index de7df23..0000000 --- a/dashboard-daily.timer +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=Dashboard daily update - -[Timer] -OnCalendar=daily -Persistent=true - -[Install] -WantedBy=timers.target diff --git a/diff-pem2html.go b/diff-pem2html.go deleted file mode 100644 index f3b25ff..0000000 --- a/diff-pem2html.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -import ( - "crypto/x509" - "encoding/pem" - "fmt" - "html/template" - "io/ioutil" - "os" - - "./util" -) - -func handleErr(err error, str string, a ...interface{}) { - a = append([]interface{}{err}, a...) - if err != nil { - fmt.Fprintf(os.Stderr, str, a...) - os.Exit(1) - } -} - -func handleBool(ok bool, str string, a ...interface{}) { - if !ok { - fmt.Fprintf(os.Stderr, str, a...) - os.Exit(1) - } -} - -var tmpl = template.Must(template.New("pem2html"). - Funcs(template.FuncMap{ - "htmlcell": util.HTMLCellEscapeString, - }).Parse(` - - - -{{range $cert := .certs}} - - - - - - -{{end}} -
--- tls.pem
+++ crtsh.pem
@@ -1,{{.nTLS}} +1,{{.nCrtSh}} @@
{{$cert.Pfix | htmlcell}}{{$cert.X509.Subject.CommonName | htmlcell}}{{$cert.X509.NotBefore.Local.Format "2006-01-02 15:04:05"}}{{$cert.X509.NotAfter.Local.Format "2006-01-02 15:04:05"}}
-`)) - -type Cert struct { - Url string - action string - X509 *x509.Certificate -} - -func (cert Cert) Pfix() string { - return map[string]string{ - "add": "+", - "del": "-", - "ctx": " ", - }[cert.action] -} - -func (cert Cert) Class() string { - return "diff-" + cert.action -} - -func main() { - data, err := ioutil.ReadAll(os.Stdin) - handleErr(err, "Error reading stdin: %v\n") - - var certs []Cert - a := 0 - b := 0 - for len(data) > 0 { - var certPem *pem.Block - certPem, data = pem.Decode(data) - - var ok bool - var cert Cert - - cert.Url, ok = certPem.Headers["X-Crt-Sh-Url"] - handleBool(ok, "Did not get X-Crt-Sh-Url\n") - - cert.action, ok = certPem.Headers["X-Diff-Action"] - handleBool(ok, "Did not get X-Diff-Action\n") - switch cert.action { - case "add": - b++ - case "del": - a++ - case "ctx": - a++ - b++ - default: - handleBool(false, "Unknown X-Diff-Action: %q\n", cert.action) - } - - cert.X509, err = x509.ParseCertificate(certPem.Bytes) - if err != nil { - cert.X509 = new(x509.Certificate) - } - - certs = append(certs, cert) - } - - handleErr(tmpl.Execute(os.Stdout, map[string]interface{}{ - "certs": certs, - "nTLS": a, - "nCrtSh": b, - }), "Could not execute template: %v\n") -} diff --git a/diff.go b/diff.go deleted file mode 100644 index da27a62..0000000 --- a/diff.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "crypto/x509" - "encoding/pem" - "fmt" - "io" - "io/ioutil" - "net/url" - "os" - "sort" - "strings" -) - -func handleErr(err error, str string, a ...interface{}) { - a = append([]interface{}{err}, a...) - if err != nil { - fmt.Fprintf(os.Stderr, str, a...) - os.Exit(1) - } -} - -type Cert struct { - Url string - X509 *x509.Certificate -} - -func (cert Cert) WriteTo(w io.Writer, action string) error { - block := pem.Block{ - Type: "CERTIFICATE", - Headers: map[string]string{ - "X-Crt-Sh-Url": cert.Url, - "X-Diff-Action": action, - }, - Bytes: cert.X509.Raw, - } - return pem.Encode(w, &block) -} - -func readTLS(filename string) (map[string]Cert, error) { - file, err := os.Open(filename) - if err != nil { - return nil, err - } - data, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - - ret := make(map[string]Cert) - for len(data) > 0 { - var certPem *pem.Block - certPem, data = pem.Decode(data) - certX509, err := x509.ParseCertificate(certPem.Bytes) - if err != nil { - url, err2 := url.Parse(certPem.Headers["X-Socket"]) - if err2 != nil { - fmt.Fprintf(os.Stderr, "Could not get cert or even parse URL:\ncert: %v\nurl: %v\n", err, err2) - os.Exit(1) - } - ret[strings.Split(url.Host, ":")[0]] = Cert{ - X509: new(x509.Certificate), - } - } else { - ret[certX509.Subject.CommonName] = Cert{ - Url: fmt.Sprintf("https://crt.sh/?serial=%036x", certX509.SerialNumber), - X509: certX509, - } - } - } - return ret, nil -} - -func readCrtSh(filename string, hosts []string) (map[string]Cert, error) { - file, err := os.Open(filename) - if err != nil { - return nil, err - } - data, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - - ret := make(map[string]Cert) - for len(data) > 0 { - var certPem *pem.Block - certPem, data = pem.Decode(data) - certX509, err := x509.ParseCertificate(certPem.Bytes) - if err != nil { - return nil, err - } - for _, host := range hosts { - if certX509.VerifyHostname(host) == nil { - if old, haveold := ret[host]; !haveold || certX509.NotBefore.After(old.X509.NotBefore) { - ret[host] = Cert{ - Url: certPem.Headers["X-Crt-Sh-Url"], - X509: certX509, - } - } - } - } - } - return ret, nil -} - -func keys(m map[string]Cert) []string { - ret := make([]string, len(m)) - i := 0 - for k := range m { - ret[i] = k - i++ - } - sort.Strings(ret) - return ret -} - -func main() { - if len(os.Args) != 3 { - fmt.Fprintf(os.Stderr, "Usage: %s TLS-file crt.sh-file\n", os.Args[0]) - } - certsTLS, err := readTLS(os.Args[1]) - handleErr(err, "Could load TLS file: %v\n") - hostsTLS := keys(certsTLS) - certsCrtSh, err := readCrtSh(os.Args[2], hostsTLS) - handleErr(err, "Could load crt.sh file: %v\n") - - for _, host := range hostsTLS { - certTLS := certsTLS[host] - certCrtSh, haveCrtSh := certsCrtSh[host] - - if !haveCrtSh { - handleErr(certTLS.WriteTo(os.Stdout, "del"), "Could not encode PEM: %v\n") - } else if !certTLS.X509.Equal(certCrtSh.X509) { - handleErr(certTLS.WriteTo(os.Stdout, "del"), "Could not encode PEM: %v\n") - handleErr(certCrtSh.WriteTo(os.Stdout, "add"), "Could not encode PEM: %v\n") - } else { - handleErr(certCrtSh.WriteTo(os.Stdout, "ctx"), "Could not encode PEM: %v\n") - } - } -} diff --git a/index.html.gen b/index.html.gen deleted file mode 100755 index b0e681a..0000000 --- a/index.html.gen +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh - -col() { - echo '
' - eval "$*" - echo '
' -} - -echo ' - - - - Dashboard - - - - - - - - - - - - - - - - - - - - - - - - - -' -col sed 's/@name@/proton/' jarmon.html.in -col sed 's/@name@/winston/' jarmon.html.in -col cat tls.html.part crtsh.html.part diff.html.part -echo ' -' diff --git a/jarmon-dependencies.js b/jarmon-dependencies.js deleted file mode 120000 index 9874026..0000000 --- a/jarmon-dependencies.js +++ /dev/null @@ -1 +0,0 @@ -../graph/jarmon-git/docs/examples/assets/js/dependencies.js \ No newline at end of file diff --git a/jarmon-style/jquerytools.tabs.tabs-no-images.scss b/jarmon-style/jquerytools.tabs.tabs-no-images.scss deleted file mode 100644 index d97f579..0000000 --- a/jarmon-style/jquerytools.tabs.tabs-no-images.scss +++ /dev/null @@ -1,69 +0,0 @@ -/* Skin for jQuery Tools tabs. - * - * Based on . - * - * Documentation on tabs: - */ - -$tabs-tab-height: 15px; -$tabs-tab-vpad: 2px; -$tabs-tab-hpad: 10px; -$tabs-border-width: 1px; -$tabs-border-style: solid #666; -$tabs-background-primary: #ddd; -$tabs-background-secondary: #efefef; - -/* root element for tab bar */ -ul.css-tabs { - margin: 0; - padding: 0; - height: $tabs-tab-height; - border-bottom: $tabs-border-width $tabs-border-style; - - /* single tab */ - li { - float: left; - margin: 0; - padding: 0; - list-style-type: none; - - /* link inside the tab */ - a { - display:block; - height: $tabs-tab-height; /* $tabs-tab-height - 2*($tabs-tab-vpad+$tabs-border-width) */ - padding: $tabs-tab-vpad $tabs-tab-hpad; - border: $tabs-border-width $tabs-border-style; - border-bottom: 0; - - margin-right: 2px; - border-radius: 4px 4px 0 0; - text-decoration: none; - - background: $tabs-background-secondary; - color: #777; - - &:hover { - background-color: #F7F7F7; - color: #333; - } - - /* selected tab */ - &.current { - background: $tabs-background-primary; - border-bottom: $tabs-border-width solid $tabs-background-primary; - color: #000; - cursor:default; - } - } - } -} - -/* tab pane */ -.css-panes > div { - display: none; - border: $tabs-border-width $tabs-border-style; - border-top: 0; - background: $tabs-background-primary; - - padding: 15px 20px; -} diff --git a/jarmon-style/loading.gif b/jarmon-style/loading.gif deleted file mode 120000 index 03b9781..0000000 --- a/jarmon-style/loading.gif +++ /dev/null @@ -1 +0,0 @@ -../../graph/jarmon-git/docs/examples/assets/icons/loading.gif \ No newline at end of file diff --git a/jarmon-style/next.gif b/jarmon-style/next.gif deleted file mode 120000 index 16a6d9d..0000000 --- a/jarmon-style/next.gif +++ /dev/null @@ -1 +0,0 @@ -../../graph/jarmon-git/docs/examples/assets/icons/next.gif \ No newline at end of file diff --git a/jarmon-style/prev.gif b/jarmon-style/prev.gif deleted file mode 120000 index 72199f2..0000000 --- a/jarmon-style/prev.gif +++ /dev/null @@ -1 +0,0 @@ -../../graph/jarmon-git/docs/examples/assets/icons/prev.gif \ No newline at end of file diff --git a/jarmon-style/style.scss b/jarmon-style/style.scss deleted file mode 100644 index 5641550..0000000 --- a/jarmon-style/style.scss +++ /dev/null @@ -1,119 +0,0 @@ -.jarmon { - border: solid 1px black; - border-radius: 4px; - - .chartRangeControl { - .range-inputs { - display: flex; - flex-direction: row; - align-items: center; - & > * { - width: 20%; - margin: 1px; - padding: 0; - border: solid 1px #666; - border-radius: 2px; - &[type="button"] { - width: auto; - } - &[type="datetime-local"] { - width: 25%; - } - } - } - .range-preview { - height: 42px; - width: 100%; - } - } - - .css-panes > div { - padding: 0; - padding-left: 15px; - } - - h2 { - margin: 0; - font-size: 100%; - text-align: center; - } - - .tabbed-chart-interface { - .css-panes { - height: 550px; - & > div { - height: 100%; - overflow-y: scroll; - overflow-x: hidden; - } - } - .chart-container { - &.loading .title { - background-repeat: no-repeat; - background-position: 0 50%; - background-image: url(loading.gif); - } - - .chart { - height: 125px; - width: 100%; - margin: 0 auto 0 auto; - clear: both; - - .tickLabel, /* flot 0.7 */ - .tick-label /* flot 0.8 */ - { - overflow:hidden; - } - - .yaxisUnitLabel { - /* Y */ - position: absolute; - top: 50%; - /* In this translateY, the 75% is: 50% for the `top:50%` overshooting - * by our `height/2`; the additional 25% accounts - * for 1 line-height being chopped off the bottom - * of the graph for the x-axis labels. */ - transform: translateY(-75%); - - /* X */ - width: 100px; - margin-left: -100px; - text-align: right; - - /* styling */ - padding: 2px; - - /* rotate */ - transform: rotate(-90deg); - width: 0; - margin-left: -10px; - } - } - - .graph-legend { - width: 100%; - padding: 2px 0; - margin: 2px auto 0; - background-color: #f7f7f7; - - .legendItem { - float: left; - cursor: pointer; - margin-right: 20px; - margin-top: 5px; - margin-left: 5px; - - .legendColorBox { - float: left; - margin-right: 5px; - } - - &.disabled { - text-decoration: line-through; - } - } - } - } - } -} diff --git a/jarmon.html.in b/jarmon.html.in deleted file mode 100644 index 3486edb..0000000 --- a/jarmon.html.in +++ /dev/null @@ -1,29 +0,0 @@ -
-
-
-

-
- - -
-
-
-
-
-
-
-
- - - - - -
-
-
-
-
-
-
diff --git a/jarmon.js b/jarmon.js deleted file mode 120000 index 1c0e4a9..0000000 --- a/jarmon.js +++ /dev/null @@ -1 +0,0 @@ -../graph/jarmon-git/jarmon/jarmon.js \ No newline at end of file diff --git a/public-src/colordate.js b/public-src/colordate.js new file mode 100644 index 0000000..e5331eb --- /dev/null +++ b/public-src/colordate.js @@ -0,0 +1,41 @@ +(function() { + // in milliseconds + var now = Date.now(); + var oneday = 1000*60*60*24; + + // maps from a point on iRange to a point on oRange + var mapRange = function(iRange, oRange, iPoint) { + var pct = (iPoint - iRange[0])/(iRange[1]-iRange[0]); + if (pct < 0) { + pct = 0; + } else if (pct > 1) { + pct = 1; + } + var oPoint = oRange[0] + (pct * (oRange[1]-oRange[0])); + return oPoint; + } + + + var rgb = function(r, g, b) { + return "rgb(" + Math.trunc(r) + "," + Math.trunc(g) + "," + Math.trunc(b) + ")"; + }; + + var date2color = function(t) { + var max = 0xFF; + var red = mapRange([now-oneday, now-(oneday/2)], + [max, 0], + t); + var green = mapRange([now-(oneday/2), now], + [0, max], + t); + return rgb(max-green, max-red, max-green-red); + }; + + var main = function() { + document.querySelectorAll('time.daily').forEach(function(time) { + time.style.backgroundColor = date2color(Date.parse(time.dateTime)); + }); + }; + + document.addEventListener("DOMContentLoaded", main, false); +})(); diff --git a/public-src/index.html.gen b/public-src/index.html.gen new file mode 100755 index 0000000..c43beec --- /dev/null +++ b/public-src/index.html.gen @@ -0,0 +1,46 @@ +#!/bin/sh +set -e +cd "$(dirname -- "$0")" + +col() { + echo '
' + eval "$*" + echo '
' +} + +echo ' + + + + Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + +' +col sed 's/@name@/proton/' jarmon.html.in +col sed 's/@name@/winston/' jarmon.html.in +col cat tls.html.part crtsh.html.part diff.html.part +echo ' +' diff --git a/public-src/jarmon-dependencies.js b/public-src/jarmon-dependencies.js new file mode 120000 index 0000000..9874026 --- /dev/null +++ b/public-src/jarmon-dependencies.js @@ -0,0 +1 @@ +../graph/jarmon-git/docs/examples/assets/js/dependencies.js \ No newline at end of file diff --git a/public-src/jarmon-style/jquerytools.tabs.tabs-no-images.scss b/public-src/jarmon-style/jquerytools.tabs.tabs-no-images.scss new file mode 100644 index 0000000..d97f579 --- /dev/null +++ b/public-src/jarmon-style/jquerytools.tabs.tabs-no-images.scss @@ -0,0 +1,69 @@ +/* Skin for jQuery Tools tabs. + * + * Based on . + * + * Documentation on tabs: + */ + +$tabs-tab-height: 15px; +$tabs-tab-vpad: 2px; +$tabs-tab-hpad: 10px; +$tabs-border-width: 1px; +$tabs-border-style: solid #666; +$tabs-background-primary: #ddd; +$tabs-background-secondary: #efefef; + +/* root element for tab bar */ +ul.css-tabs { + margin: 0; + padding: 0; + height: $tabs-tab-height; + border-bottom: $tabs-border-width $tabs-border-style; + + /* single tab */ + li { + float: left; + margin: 0; + padding: 0; + list-style-type: none; + + /* link inside the tab */ + a { + display:block; + height: $tabs-tab-height; /* $tabs-tab-height - 2*($tabs-tab-vpad+$tabs-border-width) */ + padding: $tabs-tab-vpad $tabs-tab-hpad; + border: $tabs-border-width $tabs-border-style; + border-bottom: 0; + + margin-right: 2px; + border-radius: 4px 4px 0 0; + text-decoration: none; + + background: $tabs-background-secondary; + color: #777; + + &:hover { + background-color: #F7F7F7; + color: #333; + } + + /* selected tab */ + &.current { + background: $tabs-background-primary; + border-bottom: $tabs-border-width solid $tabs-background-primary; + color: #000; + cursor:default; + } + } + } +} + +/* tab pane */ +.css-panes > div { + display: none; + border: $tabs-border-width $tabs-border-style; + border-top: 0; + background: $tabs-background-primary; + + padding: 15px 20px; +} diff --git a/public-src/jarmon-style/loading.gif b/public-src/jarmon-style/loading.gif new file mode 120000 index 0000000..03b9781 --- /dev/null +++ b/public-src/jarmon-style/loading.gif @@ -0,0 +1 @@ +../../graph/jarmon-git/docs/examples/assets/icons/loading.gif \ No newline at end of file diff --git a/public-src/jarmon-style/next.gif b/public-src/jarmon-style/next.gif new file mode 120000 index 0000000..16a6d9d --- /dev/null +++ b/public-src/jarmon-style/next.gif @@ -0,0 +1 @@ +../../graph/jarmon-git/docs/examples/assets/icons/next.gif \ No newline at end of file diff --git a/public-src/jarmon-style/prev.gif b/public-src/jarmon-style/prev.gif new file mode 120000 index 0000000..72199f2 --- /dev/null +++ b/public-src/jarmon-style/prev.gif @@ -0,0 +1 @@ +../../graph/jarmon-git/docs/examples/assets/icons/prev.gif \ No newline at end of file diff --git a/public-src/jarmon-style/style.scss b/public-src/jarmon-style/style.scss new file mode 100644 index 0000000..5641550 --- /dev/null +++ b/public-src/jarmon-style/style.scss @@ -0,0 +1,119 @@ +.jarmon { + border: solid 1px black; + border-radius: 4px; + + .chartRangeControl { + .range-inputs { + display: flex; + flex-direction: row; + align-items: center; + & > * { + width: 20%; + margin: 1px; + padding: 0; + border: solid 1px #666; + border-radius: 2px; + &[type="button"] { + width: auto; + } + &[type="datetime-local"] { + width: 25%; + } + } + } + .range-preview { + height: 42px; + width: 100%; + } + } + + .css-panes > div { + padding: 0; + padding-left: 15px; + } + + h2 { + margin: 0; + font-size: 100%; + text-align: center; + } + + .tabbed-chart-interface { + .css-panes { + height: 550px; + & > div { + height: 100%; + overflow-y: scroll; + overflow-x: hidden; + } + } + .chart-container { + &.loading .title { + background-repeat: no-repeat; + background-position: 0 50%; + background-image: url(loading.gif); + } + + .chart { + height: 125px; + width: 100%; + margin: 0 auto 0 auto; + clear: both; + + .tickLabel, /* flot 0.7 */ + .tick-label /* flot 0.8 */ + { + overflow:hidden; + } + + .yaxisUnitLabel { + /* Y */ + position: absolute; + top: 50%; + /* In this translateY, the 75% is: 50% for the `top:50%` overshooting + * by our `height/2`; the additional 25% accounts + * for 1 line-height being chopped off the bottom + * of the graph for the x-axis labels. */ + transform: translateY(-75%); + + /* X */ + width: 100px; + margin-left: -100px; + text-align: right; + + /* styling */ + padding: 2px; + + /* rotate */ + transform: rotate(-90deg); + width: 0; + margin-left: -10px; + } + } + + .graph-legend { + width: 100%; + padding: 2px 0; + margin: 2px auto 0; + background-color: #f7f7f7; + + .legendItem { + float: left; + cursor: pointer; + margin-right: 20px; + margin-top: 5px; + margin-left: 5px; + + .legendColorBox { + float: left; + margin-right: 5px; + } + + &.disabled { + text-decoration: line-through; + } + } + } + } + } +} diff --git a/public-src/jarmon.html.in b/public-src/jarmon.html.in new file mode 100644 index 0000000..3486edb --- /dev/null +++ b/public-src/jarmon.html.in @@ -0,0 +1,29 @@ +
+
+
+

+
+ + +
+
+
+
+
+
+
+
+ + + + + +
+
+
+
+
+
+
diff --git a/public-src/jarmon.js b/public-src/jarmon.js new file mode 120000 index 0000000..1c0e4a9 --- /dev/null +++ b/public-src/jarmon.js @@ -0,0 +1 @@ +../graph/jarmon-git/jarmon/jarmon.js \ No newline at end of file diff --git a/public-src/sorttable.js b/public-src/sorttable.js new file mode 100644 index 0000000..38b0fc6 --- /dev/null +++ b/public-src/sorttable.js @@ -0,0 +1,495 @@ +/* + SortTable + version 2 + 7th April 2007 + Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/ + + Instructions: + Download this file + Add to your HTML + Add class="sortable" to any table you'd like to make sortable + Click on the headers to sort + + Thanks to many, many people for contributions and suggestions. + Licenced as X11: http://www.kryogenix.org/code/browser/licence.html + This basically means: do what you want with it. +*/ + + +var stIsIE = /*@cc_on!@*/false; + +sorttable = { + init: function() { + // quit if this function has already been called + if (arguments.callee.done) return; + // flag this function so we don't do the same thing twice + arguments.callee.done = true; + // kill the timer + if (_timer) clearInterval(_timer); + + if (!document.createElement || !document.getElementsByTagName) return; + + sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; + + forEach(document.getElementsByTagName('table'), function(table) { + if (table.className.search(/\bsortable\b/) != -1) { + sorttable.makeSortable(table); + } + }); + + }, + + makeSortable: function(table) { + if (table.getElementsByTagName('thead').length == 0) { + // table doesn't have a tHead. Since it should have, create one and + // put the first table row in it. + the = document.createElement('thead'); + the.appendChild(table.rows[0]); + table.insertBefore(the,table.firstChild); + } + // Safari doesn't support table.tHead, sigh + if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0]; + + if (table.tHead.rows.length != 1) return; // can't cope with two header rows + + // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as + // "total" rows, for example). This is B&R, since what you're supposed + // to do is put them in a tfoot. So, if there are sortbottom rows, + // for backwards compatibility, move them to tfoot (creating it if needed). + sortbottomrows = []; + for (var i=0; i5' : ' ▴'; + this.appendChild(sortrevind); + return; + } + if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { + // if we're already sorted by this column in reverse, just + // re-reverse the table, which is quicker + sorttable.reverse(this.sorttable_tbody); + this.className = this.className.replace('sorttable_sorted_reverse', + 'sorttable_sorted'); + this.removeChild(document.getElementById('sorttable_sortrevind')); + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; + this.appendChild(sortfwdind); + return; + } + + // remove sorttable_sorted classes + theadrow = this.parentNode; + forEach(theadrow.childNodes, function(cell) { + if (cell.nodeType == 1) { // an element + cell.className = cell.className.replace('sorttable_sorted_reverse',''); + cell.className = cell.className.replace('sorttable_sorted',''); + } + }); + sortfwdind = document.getElementById('sorttable_sortfwdind'); + if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } + sortrevind = document.getElementById('sorttable_sortrevind'); + if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } + + this.className += ' sorttable_sorted'; + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; + this.appendChild(sortfwdind); + + // build an array to sort. This is a Schwartzian transform thing, + // i.e., we "decorate" each row with the actual sort key, + // sort based on the sort keys, and then put the rows back in order + // which is a lot faster because you only do getInnerText once per row + row_array = []; + col = this.sorttable_columnindex; + rows = this.sorttable_tbody.rows; + for (var j=0; j 12) { + // definitely dd/mm + return sorttable.sort_ddmm; + } else if (second > 12) { + return sorttable.sort_mmdd; + } else { + // looks like a date, but we can't tell which, so assume + // that it's dd/mm (English imperialism!) and keep looking + sortfn = sorttable.sort_ddmm; + } + } + } + } + return sortfn; + }, + + getInnerText: function(node) { + // gets the text we want to use for sorting for a cell. + // strips leading and trailing whitespace. + // this is *not* a generic getInnerText function; it's special to sorttable. + // for example, you can override the cell text with a customkey attribute. + // it also gets .value for fields. + + if (!node) return ""; + + hasInputs = (typeof node.getElementsByTagName == 'function') && + node.getElementsByTagName('input').length; + + if (node.getAttribute("sorttable_customkey") != null) { + return node.getAttribute("sorttable_customkey"); + } + else if (typeof node.textContent != 'undefined' && !hasInputs) { + return node.textContent.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.innerText != 'undefined' && !hasInputs) { + return node.innerText.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.text != 'undefined' && !hasInputs) { + return node.text.replace(/^\s+|\s+$/g, ''); + } + else { + switch (node.nodeType) { + case 3: + if (node.nodeName.toLowerCase() == 'input') { + return node.value.replace(/^\s+|\s+$/g, ''); + } + case 4: + return node.nodeValue.replace(/^\s+|\s+$/g, ''); + break; + case 1: + case 11: + var innerText = ''; + for (var i = 0; i < node.childNodes.length; i++) { + innerText += sorttable.getInnerText(node.childNodes[i]); + } + return innerText.replace(/^\s+|\s+$/g, ''); + break; + default: + return ''; + } + } + }, + + reverse: function(tbody) { + // reverse the rows in a tbody + newrows = []; + for (var i=0; i=0; i--) { + tbody.appendChild(newrows[i]); + } + delete newrows; + }, + + /* sort functions + each sort function takes two parameters, a and b + you are comparing a[0] and b[0] */ + sort_numeric: function(a,b) { + aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); + if (isNaN(aa)) aa = 0; + bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); + if (isNaN(bb)) bb = 0; + return aa-bb; + }, + sort_alpha: function(a,b) { + if (a[0]==b[0]) return 0; + if (a[0] 0 ) { + var q = list[i]; list[i] = list[i+1]; list[i+1] = q; + swap = true; + } + } // for + t--; + + if (!swap) break; + + for(var i = t; i > b; --i) { + if ( comp_func(list[i], list[i-1]) < 0 ) { + var q = list[i]; list[i] = list[i-1]; list[i-1] = q; + swap = true; + } + } // for + b++; + + } // while(swap) + } +} + +/* ****************************************************************** + Supporting functions: bundled here to avoid depending on a library + ****************************************************************** */ + +// Dean Edwards/Matthias Miller/John Resig + +/* for Mozilla/Opera9 */ +if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", sorttable.init, false); +} + +/* for Internet Explorer */ +/*@cc_on @*/ +/*@if (@_win32) + document.write(" to your HTML - Add class="sortable" to any table you'd like to make sortable - Click on the headers to sort - - Thanks to many, many people for contributions and suggestions. - Licenced as X11: http://www.kryogenix.org/code/browser/licence.html - This basically means: do what you want with it. -*/ - - -var stIsIE = /*@cc_on!@*/false; - -sorttable = { - init: function() { - // quit if this function has already been called - if (arguments.callee.done) return; - // flag this function so we don't do the same thing twice - arguments.callee.done = true; - // kill the timer - if (_timer) clearInterval(_timer); - - if (!document.createElement || !document.getElementsByTagName) return; - - sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; - - forEach(document.getElementsByTagName('table'), function(table) { - if (table.className.search(/\bsortable\b/) != -1) { - sorttable.makeSortable(table); - } - }); - - }, - - makeSortable: function(table) { - if (table.getElementsByTagName('thead').length == 0) { - // table doesn't have a tHead. Since it should have, create one and - // put the first table row in it. - the = document.createElement('thead'); - the.appendChild(table.rows[0]); - table.insertBefore(the,table.firstChild); - } - // Safari doesn't support table.tHead, sigh - if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0]; - - if (table.tHead.rows.length != 1) return; // can't cope with two header rows - - // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as - // "total" rows, for example). This is B&R, since what you're supposed - // to do is put them in a tfoot. So, if there are sortbottom rows, - // for backwards compatibility, move them to tfoot (creating it if needed). - sortbottomrows = []; - for (var i=0; i5' : ' ▴'; - this.appendChild(sortrevind); - return; - } - if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { - // if we're already sorted by this column in reverse, just - // re-reverse the table, which is quicker - sorttable.reverse(this.sorttable_tbody); - this.className = this.className.replace('sorttable_sorted_reverse', - 'sorttable_sorted'); - this.removeChild(document.getElementById('sorttable_sortrevind')); - sortfwdind = document.createElement('span'); - sortfwdind.id = "sorttable_sortfwdind"; - sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; - this.appendChild(sortfwdind); - return; - } - - // remove sorttable_sorted classes - theadrow = this.parentNode; - forEach(theadrow.childNodes, function(cell) { - if (cell.nodeType == 1) { // an element - cell.className = cell.className.replace('sorttable_sorted_reverse',''); - cell.className = cell.className.replace('sorttable_sorted',''); - } - }); - sortfwdind = document.getElementById('sorttable_sortfwdind'); - if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } - sortrevind = document.getElementById('sorttable_sortrevind'); - if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } - - this.className += ' sorttable_sorted'; - sortfwdind = document.createElement('span'); - sortfwdind.id = "sorttable_sortfwdind"; - sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; - this.appendChild(sortfwdind); - - // build an array to sort. This is a Schwartzian transform thing, - // i.e., we "decorate" each row with the actual sort key, - // sort based on the sort keys, and then put the rows back in order - // which is a lot faster because you only do getInnerText once per row - row_array = []; - col = this.sorttable_columnindex; - rows = this.sorttable_tbody.rows; - for (var j=0; j 12) { - // definitely dd/mm - return sorttable.sort_ddmm; - } else if (second > 12) { - return sorttable.sort_mmdd; - } else { - // looks like a date, but we can't tell which, so assume - // that it's dd/mm (English imperialism!) and keep looking - sortfn = sorttable.sort_ddmm; - } - } - } - } - return sortfn; - }, - - getInnerText: function(node) { - // gets the text we want to use for sorting for a cell. - // strips leading and trailing whitespace. - // this is *not* a generic getInnerText function; it's special to sorttable. - // for example, you can override the cell text with a customkey attribute. - // it also gets .value for fields. - - if (!node) return ""; - - hasInputs = (typeof node.getElementsByTagName == 'function') && - node.getElementsByTagName('input').length; - - if (node.getAttribute("sorttable_customkey") != null) { - return node.getAttribute("sorttable_customkey"); - } - else if (typeof node.textContent != 'undefined' && !hasInputs) { - return node.textContent.replace(/^\s+|\s+$/g, ''); - } - else if (typeof node.innerText != 'undefined' && !hasInputs) { - return node.innerText.replace(/^\s+|\s+$/g, ''); - } - else if (typeof node.text != 'undefined' && !hasInputs) { - return node.text.replace(/^\s+|\s+$/g, ''); - } - else { - switch (node.nodeType) { - case 3: - if (node.nodeName.toLowerCase() == 'input') { - return node.value.replace(/^\s+|\s+$/g, ''); - } - case 4: - return node.nodeValue.replace(/^\s+|\s+$/g, ''); - break; - case 1: - case 11: - var innerText = ''; - for (var i = 0; i < node.childNodes.length; i++) { - innerText += sorttable.getInnerText(node.childNodes[i]); - } - return innerText.replace(/^\s+|\s+$/g, ''); - break; - default: - return ''; - } - } - }, - - reverse: function(tbody) { - // reverse the rows in a tbody - newrows = []; - for (var i=0; i=0; i--) { - tbody.appendChild(newrows[i]); - } - delete newrows; - }, - - /* sort functions - each sort function takes two parameters, a and b - you are comparing a[0] and b[0] */ - sort_numeric: function(a,b) { - aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); - if (isNaN(aa)) aa = 0; - bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); - if (isNaN(bb)) bb = 0; - return aa-bb; - }, - sort_alpha: function(a,b) { - if (a[0]==b[0]) return 0; - if (a[0] 0 ) { - var q = list[i]; list[i] = list[i+1]; list[i+1] = q; - swap = true; - } - } // for - t--; - - if (!swap) break; - - for(var i = t; i > b; --i) { - if ( comp_func(list[i], list[i-1]) < 0 ) { - var q = list[i]; list[i] = list[i-1]; list[i-1] = q; - swap = true; - } - } // for - b++; - - } // while(swap) - } -} - -/* ****************************************************************** - Supporting functions: bundled here to avoid depending on a library - ****************************************************************** */ - -// Dean Edwards/Matthias Miller/John Resig - -/* for Mozilla/Opera9 */ -if (document.addEventListener) { - document.addEventListener("DOMContentLoaded", sorttable.init, false); -} - -/* for Internet Explorer */ -/*@cc_on @*/ -/*@if (@_win32) - document.write("