Merge pull request #9 in DNS/adguard-dns from consistent-stats to master
* commit '31893410892bd047c9f6ea8f602717e6996c9491': web interface -- Make refresh buttons reload all data, not just counters web interface -- change text from 'general counters' to 'general statistics' Fixup of previous commit -- errand keystroke crept in API /stats_top -- sort top entries by value API /stats_top -- show only top entries for last 3 minutes
This commit is contained in:
commit
ba836220b8
@ -5,7 +5,7 @@ import Card from '../ui/Card';
|
|||||||
import Tooltip from '../ui/Tooltip';
|
import Tooltip from '../ui/Tooltip';
|
||||||
|
|
||||||
const Counters = props => (
|
const Counters = props => (
|
||||||
<Card title="General counters" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
<Card title="General statistics" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||||
<table className="table card-table">
|
<table className="table card-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -27,8 +27,8 @@ class Dashboard extends Component {
|
|||||||
dashboard.processingTopStats;
|
dashboard.processingTopStats;
|
||||||
|
|
||||||
const disableButton = <button type="button" className="btn btn-outline-secondary btn-sm mr-2" onClick={() => this.props.disableDns()}>Disable DNS</button>;
|
const disableButton = <button type="button" className="btn btn-outline-secondary btn-sm mr-2" onClick={() => this.props.disableDns()}>Disable DNS</button>;
|
||||||
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.props.getStats()}>Refresh statistics</button>;
|
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.componentDidMount()}>Refresh statistics</button>;
|
||||||
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.props.getStats()}></button>;
|
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.componentDidMount()}></button>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
47
control.go
47
control.go
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -409,6 +410,9 @@ func handleStatsTop(w http.ResponseWriter, r *http.Request) {
|
|||||||
domains := map[string]int{}
|
domains := map[string]int{}
|
||||||
blocked := map[string]int{}
|
blocked := map[string]int{}
|
||||||
clients := map[string]int{}
|
clients := map[string]int{}
|
||||||
|
now := time.Now()
|
||||||
|
timeWindow := time.Minute * 3
|
||||||
|
notBefore := now.Add(timeWindow * -1)
|
||||||
|
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
entry, ok := value.(map[string]interface{})
|
entry, ok := value.(map[string]interface{})
|
||||||
@ -419,6 +423,11 @@ func handleStatsTop(w http.ResponseWriter, r *http.Request) {
|
|||||||
host := getHost(entry)
|
host := getHost(entry)
|
||||||
reason := getReason(entry)
|
reason := getReason(entry)
|
||||||
client := getClient(entry)
|
client := getClient(entry)
|
||||||
|
time := getTime(entry)
|
||||||
|
if time.Before(notBefore) {
|
||||||
|
// skip if the entry is before specified cutoff
|
||||||
|
continue
|
||||||
|
}
|
||||||
if len(host) > 0 {
|
if len(host) > 0 {
|
||||||
domains[host]++
|
domains[host]++
|
||||||
}
|
}
|
||||||
@ -430,21 +439,35 @@ func handleStatsTop(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toMarshal := map[string]interface{}{
|
// use manual json marshalling because we want maps to be sorted by value
|
||||||
"top_queried_domains": produceTop(domains, 50),
|
json := bytes.Buffer{}
|
||||||
"top_blocked_domains": produceTop(blocked, 50),
|
json.WriteString("{\n")
|
||||||
"top_clients": produceTop(clients, 50),
|
|
||||||
}
|
gen := func(json *bytes.Buffer, name string, top map[string]int, addComma bool) {
|
||||||
json, err := json.Marshal(toMarshal)
|
json.WriteString(" \"")
|
||||||
if err != nil {
|
json.WriteString(name)
|
||||||
errortext := fmt.Sprintf("Couldn't marshal into JSON: %s", err)
|
json.WriteString("\": {\n")
|
||||||
log.Println(errortext)
|
sorted := sortByValue(top)
|
||||||
http.Error(w, errortext, http.StatusBadGateway)
|
for i, key := range sorted {
|
||||||
return
|
fmt.Fprintf(json, " \"%s\": %d", key, top[key])
|
||||||
|
if i+1 != len(sorted) {
|
||||||
|
json.WriteByte(',')
|
||||||
|
}
|
||||||
|
json.WriteByte('\n')
|
||||||
|
}
|
||||||
|
json.WriteString(" }")
|
||||||
|
if addComma {
|
||||||
|
json.WriteByte(',')
|
||||||
|
}
|
||||||
|
json.WriteByte('\n')
|
||||||
}
|
}
|
||||||
|
gen(&json, "top_queried_domains", domains, true)
|
||||||
|
gen(&json, "top_blocked_domains", blocked, true)
|
||||||
|
gen(&json, "top_clients", clients, false)
|
||||||
|
json.WriteString("}\n")
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, err = w.Write(json)
|
_, err = w.Write(json.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errortext := fmt.Sprintf("Couldn't write body: %s", err)
|
errortext := fmt.Sprintf("Couldn't write body: %s", err)
|
||||||
log.Println(errortext)
|
log.Println(errortext)
|
||||||
|
17
helpers.go
17
helpers.go
@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func clamp(value, low, high int) int {
|
func clamp(value, low, high int) int {
|
||||||
@ -167,6 +168,22 @@ func getClient(entry map[string]interface{}) string {
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTime(entry map[string]interface{}) time.Time {
|
||||||
|
t, ok := entry["time"]
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
tstr, ok := t.(string)
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
value, err := time.Parse(time.RFC3339, tstr)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
// helper functions for parsing parameters from body
|
// helper functions for parsing parameters from body
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user