Compare commits
2 Commits
943da8807b
...
9c147d1b79
| Author | SHA1 | Date |
|---|---|---|
|
|
9c147d1b79 | |
|
|
1c43e4479a |
31
Dockerfile
31
Dockerfile
|
|
@ -0,0 +1,31 @@
|
||||||
|
FROM node:lts-alpine as build-client
|
||||||
|
|
||||||
|
COPY . /build/
|
||||||
|
|
||||||
|
RUN apk add --no-cache make
|
||||||
|
|
||||||
|
RUN make build-client
|
||||||
|
|
||||||
|
FROM golang:alpine as build-server
|
||||||
|
|
||||||
|
COPY --from=build-client /build /build
|
||||||
|
|
||||||
|
RUN apk add --no-cache make
|
||||||
|
|
||||||
|
RUN make .bin/gopherhole
|
||||||
|
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
RUN apl add --no-cache ca-certificates
|
||||||
|
|
||||||
|
RUN addgroup -g 1000 gopherhole \
|
||||||
|
&& adduser -H -D -u 1000 gopherhole gopherhole
|
||||||
|
|
||||||
|
COPY --chown=gopherhole:gopherhole --from=build-server /build/.bin/gopherhole /opt/gopherhole
|
||||||
|
|
||||||
|
USER gopherhole
|
||||||
|
|
||||||
|
VOLUME "/data"
|
||||||
|
|
||||||
|
ENTRYPOINT /opt/gopherhole
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,14 +1,15 @@
|
||||||
import { sub } from 'date-fns'
|
import { sub, getUnixTime } from 'date-fns'
|
||||||
import { formatDate } from './metrics';
|
import { API_HOST } from './util';
|
||||||
|
|
||||||
|
|
||||||
export const getLogs = async function ({
|
export const getLogs = async function ({
|
||||||
start = sub(new Date(), { hours: 12 }),
|
start = sub(new Date(), { hours: 12 }),
|
||||||
end = new Date(),
|
end = new Date(),
|
||||||
|
page = 0,
|
||||||
filter = ""
|
filter = ""
|
||||||
} = {}) {
|
} = {}) {
|
||||||
try {
|
try {
|
||||||
const data = await fetch(`http://localhost:8080/api/v1/metrics/log?filter=${filter}`, {
|
const data = await fetch(API_HOST(`metrics/log?filter=${filter}&start=${getUnixTime(start)}&end=${getUnixTime(end)}&page=${page}`), {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"headers": { "Accept": "application/json" }
|
"headers": { "Accept": "application/json" }
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { sub, format } from 'date-fns';
|
import { getUnixTime, sub, format } from 'date-fns';
|
||||||
import { readable } from "svelte/store";
|
import { readable } from "svelte/store";
|
||||||
|
import { API_HOST } from './util';
|
||||||
|
|
||||||
export const SearchDateFormatStr = "yyyy-MM-dd HH:mm:ss.SSS";
|
export const SearchDateFormatStr = "yyyy-MM-dd HH:mm:ss.SSS";
|
||||||
export const formatDateForSearch = date => format(date, SearchDateFormatStr);
|
export const formatDateForSearch = date => format(date, SearchDateFormatStr);
|
||||||
|
|
@ -26,9 +27,9 @@ export const fetchMetrics = async function ({
|
||||||
} = {}) {
|
} = {}) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`http://localhost:8080/api/v1/metrics/stats?start=${formatDateForSearch(
|
API_HOST(`metrics/stats?start=${getUnixTime(
|
||||||
start
|
start
|
||||||
)}&end=${formatDateForSearch(end)}&key=${key}&interval=${interval}`,
|
)}&end=${getUnixTime(end)}&key=${key}&interval=${interval}`),
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: { Accept: "application/json" }
|
headers: { Accept: "application/json" }
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ interface APIResponse<T> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const API_HOST = `http://localhost:8080/api/v1`;
|
export const API_HOST = (url = "") => `http://localhost:8000/api/v1/${url}`;
|
||||||
|
|
||||||
export const apiCall = async function<T>(url: string, method: string = 'GET'): Promise<APIResponse<T>> {
|
export const apiCall = async function<T>(url: string, method: string = 'GET'): Promise<APIResponse<T>> {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -47,5 +47,8 @@
|
||||||
{row.TotalTimeMs}
|
{row.TotalTimeMs}
|
||||||
</Column>
|
</Column>
|
||||||
</Table>
|
</Table>
|
||||||
|
{:else}
|
||||||
|
<p>No Logs yet!</p>
|
||||||
|
<p><em>TODO:</em> Link to docs on how to point your router at this server</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"database": "./db.sqlite",
|
"database": "./db.sqlite",
|
||||||
"cache": "in-memory",
|
"cache": "in-memory",
|
||||||
"http-addr": "localhost:8080",
|
"http-addr": "localhost:8000",
|
||||||
"dns-addr": "localhost:5353",
|
"dns-addr": "localhost:5353",
|
||||||
"recursors": ["192.168.1.15:8600", "1.1.1.1", "8.8.8.8"],
|
"recursors": ["192.168.1.15:8600", "1.1.1.1", "8.8.8.8"],
|
||||||
"rules": [
|
"rules": [
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,26 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
var (
|
||||||
|
LogUpdated = EventType("LOG_UPDATE")
|
||||||
|
DNSLog = EventSource("DNS_LOG")
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventType string
|
||||||
|
|
||||||
|
type EventSource string
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
Type EventType `json:"type"`
|
||||||
|
Source EventSource `json:"source"`
|
||||||
|
Payload interface{} `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventPublisher interface {
|
||||||
|
Publish(Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventSubscriber interface {
|
||||||
|
Subscribe(context.Context) <-chan Event
|
||||||
|
}
|
||||||
333
internal/http.go
333
internal/http.go
|
|
@ -1,12 +1,8 @@
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
@ -23,6 +19,8 @@ var upgrader = websocket.Upgrader{
|
||||||
type adminHandler struct {
|
type adminHandler struct {
|
||||||
Cache
|
Cache
|
||||||
Storage
|
Storage
|
||||||
|
EventSubscriber
|
||||||
|
EventPublisher
|
||||||
*RuleEngine
|
*RuleEngine
|
||||||
h http.Handler
|
h http.Handler
|
||||||
}
|
}
|
||||||
|
|
@ -57,17 +55,23 @@ func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler {
|
||||||
r.Get("/metrics/log", RestHandler(a.getLog).ToHF())
|
r.Get("/metrics/log", RestHandler(a.getLog).ToHF())
|
||||||
r.Get("/metrics/stats", RestHandler(a.getStats).ToHF())
|
r.Get("/metrics/stats", RestHandler(a.getStats).ToHF())
|
||||||
|
|
||||||
r.Get("/rules", RestHandler(a.getRules).ToHF())
|
|
||||||
r.Put("/rules", RestHandler(a.createRule).ToHF())
|
r.Put("/rules", RestHandler(a.createRule).ToHF())
|
||||||
|
r.Get("/rules", RestHandler(a.getRules).ToHF())
|
||||||
|
r.Get("/rules/{id:[0-9]+}", RestHandler(a.getRule).ToHF())
|
||||||
|
r.Post("/rules/{id:[0-9]+}", RestHandler(a.updateRule).ToHF())
|
||||||
r.Delete("/rules/{id:[0-9]+}", RestHandler(a.deleteRule).ToHF())
|
r.Delete("/rules/{id:[0-9]+}", RestHandler(a.deleteRule).ToHF())
|
||||||
|
|
||||||
r.Get("/recursors", RestHandler(a.getRecursors).ToHF())
|
|
||||||
r.Put("/recursors", RestHandler(a.addRecursor).ToHF())
|
r.Put("/recursors", RestHandler(a.addRecursor).ToHF())
|
||||||
|
r.Get("/recursors", RestHandler(a.getRecursors).ToHF())
|
||||||
|
r.Get("/recursors/{id:[0-9]+}", RestHandler(a.getRecursor).ToHF())
|
||||||
|
r.Delete("/recursor/{id:[0-9]+}", RestHandler(a.deleteRecursor).ToHF())
|
||||||
|
|
||||||
// r.Put("/rules/lists", a.addRulelist)
|
// r.Put("/rules/lists", a.addRulelist)
|
||||||
// r.Get("/rules/lists", a.getRuleLists)
|
// r.Get("/rules/lists", a.getRuleLists)
|
||||||
// r.Delete("/rules/lists/{id}", a.deleteRuleList)
|
|
||||||
// r.Post("/rules/lists/reload/{id}", a.reloadRuleLists)
|
// r.Post("/rules/lists/reload/{id}", a.reloadRuleLists)
|
||||||
|
// r.Delete("/rules/lists/{id}", a.deleteRuleList)
|
||||||
|
|
||||||
|
// r.Get("/updates", RestHandler(a.getStats).ToHF())
|
||||||
// r.Delete("/cache/purgeall", RestHandler(a.purgeAll).ToHF())
|
// r.Delete("/cache/purgeall", RestHandler(a.purgeAll).ToHF())
|
||||||
// r.Delete("/cache/purge", a.purgeKey)
|
// r.Delete("/cache/purge", a.purgeKey)
|
||||||
// r.Get("/cache", a.getCacheContents)
|
// r.Get("/cache", a.getCacheContents)
|
||||||
|
|
@ -75,7 +79,6 @@ func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler {
|
||||||
})
|
})
|
||||||
|
|
||||||
return a
|
return a
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (a *adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -91,319 +94,7 @@ func (a *adminHandler) signal(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
for {
|
for {
|
||||||
|
|
||||||
// send any updates that come through to the client
|
// send any updates that come through to the client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (a *adminHandler) addRecursor(r *http.Request) (*RestResponse, error) {
|
|
||||||
var recursorHttpInput RecursorRow
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&recursorHttpInput); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipAddr, port, ok := recursorHttpInput.ValidIp(); ok {
|
|
||||||
if err := a.Storage.AddRecursors(ipAddr, port, recursorHttpInput.TimeoutMs, recursorHttpInput.Weight); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: true,
|
|
||||||
Payload: recursorHttpInput,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *adminHandler) getRecursors(r *http.Request) (*RestResponse, error) {
|
|
||||||
recursors, err := a.Storage.GetRecursors()
|
|
||||||
if err != nil {
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Payload: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: true,
|
|
||||||
Payload: recursors,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *adminHandler) deleteRule(r *http.Request) (*RestResponse, error) {
|
|
||||||
ruleIdParam := chi.URLParam(r, "id")
|
|
||||||
ruleId, err := strconv.Atoi(ruleIdParam)
|
|
||||||
if err != nil {
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: false,
|
|
||||||
Payload: "Invalid rule ID",
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.Storage.DeleteRule(ruleId); err != nil {
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: false,
|
|
||||||
Payload: err.Error(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: true,
|
|
||||||
Payload: ruleId,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *adminHandler) createRule(r *http.Request) (*RestResponse, error) {
|
|
||||||
var rr RuleRow
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&rr); err != nil {
|
|
||||||
return &RestResponse{
|
|
||||||
Status: http.StatusUnprocessableEntity,
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: false,
|
|
||||||
Payload: err.Error(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.Storage.AddRule(rr); err != nil {
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: false,
|
|
||||||
Payload: err.Error(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: true,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
func (a *adminHandler) getRules(r *http.Request) (*RestResponse, error) {
|
|
||||||
results, err := a.Storage.GetRules()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(results) <= 0 {
|
|
||||||
results = []RuleRow{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RestResponse{
|
|
||||||
Payload: struct {
|
|
||||||
Success bool "json:\"success\""
|
|
||||||
Payload interface{} "json:\"payload\""
|
|
||||||
}{
|
|
||||||
Success: true,
|
|
||||||
Payload: results,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *adminHandler) getStats(r *http.Request) (*RestResponse, error) {
|
|
||||||
q := r.URL.Query()
|
|
||||||
startFilter := q.Get("start")
|
|
||||||
endFilter := q.Get("end")
|
|
||||||
key := q.Get("key")
|
|
||||||
intervalSecondsStr := q.Get("interval")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
start := time.Now().UTC().Add(time.Hour * -86400)
|
|
||||||
end := time.Now().UTC()
|
|
||||||
|
|
||||||
if startFilter != "" {
|
|
||||||
if start, err = time.Parse(ISO8601, startFilter); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if endFilter != "" {
|
|
||||||
if end, err = time.Parse(ISO8601, endFilter); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lai := LogAggregateInput{
|
|
||||||
Start: start,
|
|
||||||
End: end,
|
|
||||||
Column: key,
|
|
||||||
}
|
|
||||||
|
|
||||||
if intervalSecondsStr != "" {
|
|
||||||
if lai.IntervalSeconds, err = strconv.Atoi(intervalSecondsStr); err != nil {
|
|
||||||
return nil, errors.New("interval query param must be a valid whole number")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
la, err := a.Storage.GetLogAggregate(lai)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RestResponse{
|
|
||||||
Status: http.StatusOK,
|
|
||||||
Payload: struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Payload interface{} `json:"payload"`
|
|
||||||
}{
|
|
||||||
Success: true,
|
|
||||||
Payload: la,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *adminHandler) getLog(r *http.Request) (*RestResponse, error) {
|
|
||||||
q := r.URL.Query()
|
|
||||||
startFilter := q.Get("start")
|
|
||||||
endFilter := q.Get("end")
|
|
||||||
// filter := q.Get("filter")
|
|
||||||
pageStr := q.Get("page")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var page int
|
|
||||||
start := time.Now().UTC().Add(time.Hour * -86400)
|
|
||||||
end := time.Now().UTC()
|
|
||||||
|
|
||||||
if startFilter != "" {
|
|
||||||
if start, err = time.Parse(ISO8601, startFilter); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if endFilter != "" {
|
|
||||||
if end, err = time.Parse(ISO8601, endFilter); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pageStr != "" {
|
|
||||||
page, _ = strconv.Atoi(pageStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
ql, err := a.Storage.GetLog(GetLogInput{
|
|
||||||
Start: start,
|
|
||||||
End: end,
|
|
||||||
Limit: 250,
|
|
||||||
Page: page,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RestResponse{
|
|
||||||
Status: http.StatusOK,
|
|
||||||
Payload: struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Payload interface{} `json:"payload"`
|
|
||||||
}{
|
|
||||||
Success: true,
|
|
||||||
Payload: ql,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type RestResponse struct {
|
|
||||||
Status int
|
|
||||||
Headers http.Header
|
|
||||||
Payload struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Payload interface{} `json:"payload"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *RestResponse) Write(w http.ResponseWriter) error {
|
|
||||||
if rr.Status != 0 && rr.Status != 200 {
|
|
||||||
w.WriteHeader(rr.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range rr.Headers {
|
|
||||||
for _, ve := range v {
|
|
||||||
w.Header().Add(k, ve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e := json.NewEncoder(w)
|
|
||||||
e.SetIndent("\n", "\t")
|
|
||||||
|
|
||||||
if err := e.Encode(rr.Payload); err != nil {
|
|
||||||
return fmt.Errorf("could not serialize struct for http response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type RestHandler func(request *http.Request) (*RestResponse, error)
|
|
||||||
|
|
||||||
func (rh RestHandler) ToHF() http.HandlerFunc {
|
|
||||||
return func(rw http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
rid := r.Context().Value(middleware.RequestIDKey)
|
|
||||||
rw.Header().Set(middleware.RequestIDHeader, rid.(string))
|
|
||||||
rh.ServeHTTP(rw, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rh RestHandler) Error(e error) *RestResponse {
|
|
||||||
return &RestResponse{
|
|
||||||
Status: http.StatusInternalServerError,
|
|
||||||
Payload: struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Payload interface{} `json:"payload"`
|
|
||||||
}{
|
|
||||||
Success: false,
|
|
||||||
Payload: e.Error(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
response, err := r(req)
|
|
||||||
if err != nil {
|
|
||||||
response = r.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := response.Write(w); err != nil {
|
|
||||||
log.Printf("Error occurred handling rest response: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *adminHandler) addRecursor(r *http.Request) (*RestResponse, error) {
|
||||||
|
var recursorHttpInput RecursorRow
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&recursorHttpInput); err != nil {
|
||||||
|
return ErrorResponse(http.StatusUnprocessableEntity, err), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddr, port, ok := recursorHttpInput.ValidIp()
|
||||||
|
if !ok {
|
||||||
|
return BasicResponse(false, "The ip address provided is invalid."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Storage.AddRecursors(ipAddr, port, recursorHttpInput.TimeoutMs, recursorHttpInput.Weight); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, recursorHttpInput), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adminHandler) getRecursors(r *http.Request) (*RestResponse, error) {
|
||||||
|
recursors, err := a.Storage.GetRecursors()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, recursors), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adminHandler) getRecursor(r *http.Request) (*RestResponse, error) {
|
||||||
|
return BasicResponse(false, "Nothing here yet"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adminHandler) deleteRecursor(r *http.Request) (*RestResponse, error) {
|
||||||
|
return BasicResponse(false, "Nothing here yet"), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *adminHandler) createRule(r *http.Request) (*RestResponse, error) {
|
||||||
|
var rr RuleRow
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&rr); err != nil {
|
||||||
|
return ErrorResponse(http.StatusUnprocessableEntity, err), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Storage.AddRule(rr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adminHandler) getRule(r *http.Request) (*RestResponse, error) {
|
||||||
|
ruleIdParam := chi.URLParam(r, "id")
|
||||||
|
ruleId, err := strconv.Atoi(ruleIdParam)
|
||||||
|
if err != nil {
|
||||||
|
return BasicResponse(false, "`id` must be a valid integer."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := a.Storage.GetRule(ruleId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, results), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adminHandler) getRules(r *http.Request) (*RestResponse, error) {
|
||||||
|
results, err := a.Storage.GetRules()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) <= 0 {
|
||||||
|
results = []RuleRow{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, results), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adminHandler) deleteRule(r *http.Request) (*RestResponse, error) {
|
||||||
|
ruleIdParam := chi.URLParam(r, "id")
|
||||||
|
ruleId, err := strconv.Atoi(ruleIdParam)
|
||||||
|
if err != nil {
|
||||||
|
return BasicResponse(false, "`id` must be a valid integer"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Storage.DeleteRule(ruleId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := a.Storage.GetRule(ruleId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, rule), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adminHandler) updateRule(r *http.Request) (*RestResponse, error) {
|
||||||
|
var rr RuleRow
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&rr); err != nil {
|
||||||
|
return ErrorResponse(http.StatusUnprocessableEntity, err), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.UpdateRule(rr.ID, rr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rule, err := a.GetRule(rr.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, rule), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *adminHandler) getStats(r *http.Request) (*RestResponse, error) {
|
||||||
|
q := r.URL.Query()
|
||||||
|
startFilter := q.Get("start")
|
||||||
|
endFilter := q.Get("end")
|
||||||
|
key := q.Get("key")
|
||||||
|
intervalSecondsStr := q.Get("interval")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
startTime := time.Now().Add(time.Hour * -86400)
|
||||||
|
endTime := time.Now()
|
||||||
|
|
||||||
|
if startFilter != "" {
|
||||||
|
var startUnixTime int64
|
||||||
|
if startUnixTime, err = strconv.ParseInt(startFilter, 10, strconv.IntSize); err != nil {
|
||||||
|
return BasicResponse(false, "start: must be a valid unix timestamp"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = time.Unix(startUnixTime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endFilter != "" {
|
||||||
|
var endUnixTime int64
|
||||||
|
if endUnixTime, err = strconv.ParseInt(endFilter, 10, strconv.IntSize); err != nil {
|
||||||
|
return BasicResponse(false, "end: must be a valid unix timestamp"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endTime = time.Unix(endUnixTime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
lai := LogAggregateInput{
|
||||||
|
Start: startTime,
|
||||||
|
End: endTime,
|
||||||
|
Column: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
if intervalSecondsStr != "" {
|
||||||
|
if lai.IntervalSeconds, err = strconv.Atoi(intervalSecondsStr); err != nil {
|
||||||
|
return BasicResponse(false, "interval query param must be a valid whole number"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
la, err := a.Storage.GetLogAggregate(lai)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, la), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogFilter struct {
|
||||||
|
Expression string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adminHandler) getLog(r *http.Request) (*RestResponse, error) {
|
||||||
|
q := r.URL.Query()
|
||||||
|
startFilter := q.Get("start")
|
||||||
|
endFilter := q.Get("end")
|
||||||
|
// filter := LogFilter{Expression: q.Get("filter")}
|
||||||
|
pageStr := q.Get("page")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var page int
|
||||||
|
startTime := time.Now().Add(time.Hour * -86400)
|
||||||
|
endTime := time.Now()
|
||||||
|
|
||||||
|
if startFilter != "" {
|
||||||
|
var startUnixTime int64
|
||||||
|
if startUnixTime, err = strconv.ParseInt(startFilter, 10, strconv.IntSize); err != nil {
|
||||||
|
return BasicResponse(false, "start: must be a valid unix timestamp"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = time.Unix(startUnixTime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endFilter != "" {
|
||||||
|
var endUnixTime int64
|
||||||
|
if endUnixTime, err = strconv.ParseInt(endFilter, 10, strconv.IntSize); err != nil {
|
||||||
|
return BasicResponse(false, "end: must be a valid unix timestamp"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endTime = time.Unix(endUnixTime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageStr != "" {
|
||||||
|
if page, err = strconv.Atoi(pageStr); err != nil {
|
||||||
|
return BasicResponse(false, "page: must be a valid integer"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gli := GetLogInput{
|
||||||
|
// Filter: filter,
|
||||||
|
Start: startTime,
|
||||||
|
End: endTime,
|
||||||
|
Limit: 250,
|
||||||
|
Page: page,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if err := gli.Validate(); err != nil {
|
||||||
|
// return BasicResponse(false, err.Error()), nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
ql, err := a.Storage.GetLog(gli)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicResponse(true, ql), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RestResponse struct {
|
||||||
|
Status int
|
||||||
|
Headers http.Header
|
||||||
|
Payload struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Payload interface{} `json:"payload"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BasicResponse(success bool, model interface{}) *RestResponse {
|
||||||
|
return &RestResponse{
|
||||||
|
Status: http.StatusOK,
|
||||||
|
Payload: struct {
|
||||||
|
Success bool "json:\"success\""
|
||||||
|
Payload interface{} "json:\"payload\""
|
||||||
|
}{Success: success, Payload: model},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorResponse(status int, err error) *RestResponse {
|
||||||
|
return &RestResponse{
|
||||||
|
Status: status,
|
||||||
|
Payload: struct {
|
||||||
|
Success bool "json:\"success\""
|
||||||
|
Payload interface{} "json:\"payload\""
|
||||||
|
}{
|
||||||
|
Success: false,
|
||||||
|
Payload: err.Error(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rr *RestResponse) Write(w http.ResponseWriter) error {
|
||||||
|
if rr.Status != 0 && rr.Status != 200 {
|
||||||
|
w.WriteHeader(rr.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range rr.Headers {
|
||||||
|
for _, ve := range v {
|
||||||
|
w.Header().Add(k, ve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e := json.NewEncoder(w)
|
||||||
|
e.SetIndent("\n", "\t")
|
||||||
|
|
||||||
|
if err := e.Encode(rr.Payload); err != nil {
|
||||||
|
return fmt.Errorf("could not serialize struct for http response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RestHandler func(request *http.Request) (*RestResponse, error)
|
||||||
|
|
||||||
|
func (rh RestHandler) ToHF() http.HandlerFunc {
|
||||||
|
return func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
// rid := r.Context().Value(middleware.RequestIDKey)
|
||||||
|
// rw.Header().Set(middleware.RequestIDHeader, rid.(string))
|
||||||
|
rh.ServeHTTP(rw, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rh RestHandler) Error(e error) *RestResponse {
|
||||||
|
return &RestResponse{
|
||||||
|
Status: http.StatusInternalServerError,
|
||||||
|
Payload: struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Payload interface{} `json:"payload"`
|
||||||
|
}{
|
||||||
|
Success: false,
|
||||||
|
Payload: e.Error(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
response, err := r(req)
|
||||||
|
if err != nil {
|
||||||
|
response = r.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := response.Write(w); err != nil {
|
||||||
|
log.Printf("Error occurred handling rest response: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -18,13 +19,16 @@ type Storage interface {
|
||||||
io.Closer
|
io.Closer
|
||||||
Open() error
|
Open() error
|
||||||
AddRecursors(net.IP, int, int, int) error
|
AddRecursors(net.IP, int, int, int) error
|
||||||
DeleteRecursors(int) error
|
|
||||||
GetRecursors() ([]RecursorRow, error)
|
GetRecursors() ([]RecursorRow, error)
|
||||||
// UpdateRule(RuleRow) error
|
UpdateRecursor(int, RecursorRow) error
|
||||||
|
DeleteRecursors(int) error
|
||||||
|
|
||||||
AddRule(RuleRow) error
|
AddRule(RuleRow) error
|
||||||
GetRule(int) (RuleRow, error)
|
GetRule(int) (RuleRow, error)
|
||||||
GetRules() ([]RuleRow, error)
|
GetRules() ([]RuleRow, error)
|
||||||
|
UpdateRule(int, RuleRow) error
|
||||||
DeleteRule(int) error
|
DeleteRule(int) error
|
||||||
|
|
||||||
Log(QueryLog) error
|
Log(QueryLog) error
|
||||||
GetLog(GetLogInput) ([]QueryLog, error)
|
GetLog(GetLogInput) ([]QueryLog, error)
|
||||||
GetLogAggregate(LogAggregateInput) ([]LogAggregateDataPoint, error)
|
GetLogAggregate(LogAggregateInput) ([]LogAggregateDataPoint, error)
|
||||||
|
|
@ -97,9 +101,17 @@ func (rr RecursorRow) ValidIp() (net.IP, int, bool) {
|
||||||
return parsedIp, parsedPort, true
|
return parsedIp, parsedPort, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ss *Sqlite) UpdateRecursor(id int, in RecursorRow) error {
|
||||||
|
sql := `UPDATE recursors SET ipAddress = ?, timeoutMs = ?, weight = ? WHERE id = ?;`
|
||||||
|
if _, err := ss.Exec(sql, in.IpAddress, in.TimeoutMs, in.Weight); err != nil {
|
||||||
|
return fmt.Errorf("could not update recursor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ss *Sqlite) AddRecursors(ip net.IP, port, timeout, weight int) error {
|
func (ss *Sqlite) AddRecursors(ip net.IP, port, timeout, weight int) error {
|
||||||
sql := `INSERT INTO recursors (ipAddress, timeoutMs, weight) VALUES (?, ?, ?);`
|
sql := `INSERT INTO recursors (ipAddress, timeoutMs, weight) VALUES (?, ?, ?);`
|
||||||
|
|
||||||
if _, err := ss.Exec(sql, fmt.Sprintf("%s:%d", ip.String(), port), timeout, weight); err != nil {
|
if _, err := ss.Exec(sql, fmt.Sprintf("%s:%d", ip.String(), port), timeout, weight); err != nil {
|
||||||
return fmt.Errorf("could not insert recursor: %w", err)
|
return fmt.Errorf("could not insert recursor: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -123,6 +135,24 @@ type RuleRow struct {
|
||||||
Rule
|
Rule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ss *Sqlite) UpdateRule(id int, in RuleRow) error {
|
||||||
|
sql := `UPDATE rules SET
|
||||||
|
name = ?,
|
||||||
|
expression = ?,
|
||||||
|
answerType = ?,
|
||||||
|
answerValue = ?,
|
||||||
|
ttl = ?,
|
||||||
|
weight = ?,
|
||||||
|
enabled = ?
|
||||||
|
WHERE id = ?;`
|
||||||
|
|
||||||
|
if _, err := ss.Exec(sql, in.Name, in.Value, in.Answer.Type, in.Answer.Value, in.TTL, in.Weight, in.Enabled); err != nil {
|
||||||
|
return fmt.Errorf("could not update rule with id: %v", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ss *Sqlite) AddRule(rr RuleRow) error {
|
func (ss *Sqlite) AddRule(rr RuleRow) error {
|
||||||
sql := `INSERT INTO rules (name, expression, answerType, answerValue, ttl, weight, enabled, created)
|
sql := `INSERT INTO rules (name, expression, answerType, answerValue, ttl, weight, enabled, created)
|
||||||
VALUES (?, ? , ?, ?, ?, ?, 1, ?);`
|
VALUES (?, ? , ?, ?, ?, ?, 1, ?);`
|
||||||
|
|
@ -171,7 +201,11 @@ func (ss *Sqlite) GetRules() ([]RuleRow, error) {
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var results []RuleRow
|
if rerr := rows.Err(); rerr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
results := []RuleRow{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var rule RuleRow
|
var rule RuleRow
|
||||||
var createdTime string
|
var createdTime string
|
||||||
|
|
@ -207,36 +241,43 @@ func (ss *Sqlite) GetLog(in GetLogInput) ([]QueryLog, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.Start.IsZero() {
|
if in.Start.IsZero() {
|
||||||
in.Start = time.Now().UTC().Add(time.Hour * -86400)
|
in.Start = time.Now().Add(time.Hour * -86400)
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.End.IsZero() {
|
if in.End.IsZero() {
|
||||||
in.End = time.Now().UTC()
|
in.End = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
sql := `
|
sql := `
|
||||||
SELECT
|
SELECT
|
||||||
started, clientIp, protocol, domain, totalTimeMs, error, recurseRoundTripTimeMs, recurseUpstreamIp, status
|
started, clientIp, protocol, domain, totalTimeMs,
|
||||||
|
error, recurseRoundTripTimeMs, recurseUpstreamIp, status
|
||||||
FROM
|
FROM
|
||||||
log
|
log
|
||||||
WHERE
|
WHERE
|
||||||
id > ? AND started > ? AND started < ?
|
id > ?
|
||||||
|
AND strftime('%s', started) > strftime('%s', ?)
|
||||||
|
AND strftime('%s', started) < strftime('%s', ?)
|
||||||
ORDER BY started DESC
|
ORDER BY started DESC
|
||||||
LIMIT ?;
|
LIMIT ?;
|
||||||
`
|
`
|
||||||
|
|
||||||
rows, err := ss.DB.Query(sql, in.Page*in.Limit, in.Start.Format(ISO8601), in.End.Format(ISO8601), in.Limit)
|
rows, err := ss.DB.Query(sql, in.Page*in.Limit, in.Start.UTC().Format(ISO8601), in.End.UTC().Format(ISO8601), in.Limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("issue with GetLog sql query: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var ql []QueryLog
|
if rerr := rows.Err(); rerr != nil {
|
||||||
|
return nil, fmt.Errorf("issue with rows object: %w", rerr)
|
||||||
|
}
|
||||||
|
|
||||||
|
ql := []QueryLog{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var q QueryLog
|
var q QueryLog
|
||||||
var started string
|
var started string
|
||||||
|
|
||||||
rows.Scan(
|
if err := rows.Scan(
|
||||||
&started,
|
&started,
|
||||||
&q.ClientIP,
|
&q.ClientIP,
|
||||||
&q.Protocol,
|
&q.Protocol,
|
||||||
|
|
@ -246,14 +287,18 @@ func (ss *Sqlite) GetLog(in GetLogInput) ([]QueryLog, error) {
|
||||||
&q.RecurseRoundTripTimeMs,
|
&q.RecurseRoundTripTimeMs,
|
||||||
&q.RecurseUpstreamIP,
|
&q.RecurseUpstreamIP,
|
||||||
&q.Status,
|
&q.Status,
|
||||||
)
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("issues scanning rows: %w", err)
|
||||||
if q.Started, err = time.Parse(ISO8601, started); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse time '%s': %v", started, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if q.Started, err = time.Parse(ISO8601, started); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse time '%s': %w", started, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%+v", q)
|
||||||
ql = append(ql, q)
|
ql = append(ql, q)
|
||||||
}
|
}
|
||||||
|
log.Printf("%+v", ql)
|
||||||
|
|
||||||
return ql, nil
|
return ql, nil
|
||||||
}
|
}
|
||||||
|
|
@ -321,6 +366,10 @@ func (ss *Sqlite) GetLogAggregate(la LogAggregateInput) ([]LogAggregateDataPoint
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var results []LogAggregateDataPoint
|
var results []LogAggregateDataPoint
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var ladp LogAggregateDataPoint
|
var ladp LogAggregateDataPoint
|
||||||
|
|
@ -367,7 +416,7 @@ func (ss *Sqlite) Log(ql QueryLog) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Sqlite) Open() error {
|
func (ss *Sqlite) Open() error {
|
||||||
db, err := sql.Open("sqlite3", fmt.Sprintf("%s?cache=shared", ss.Path))
|
db, err := sql.Open("sqlite3", fmt.Sprintf("%s?cache=shared&_journal=WAL", ss.Path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not open db: %w", err)
|
return fmt.Errorf("could not open db: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
main.go
5
main.go
|
|
@ -16,6 +16,9 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configFilePath = flag.String("config", "./config.json", "Config file")
|
configFilePath = flag.String("config", "./config.json", "Config file")
|
||||||
|
// dbPath = flag.String("db-path", ".", "Directory to write database files to")
|
||||||
|
// httpAddr = flag.String("http-address", ":8080", "Bind address for http server")
|
||||||
|
// dnsAddr = flag.String("dns-address", ":53", "Bind address for dns server")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -25,6 +28,8 @@ func main() {
|
||||||
if err := LoadStartupConfig(&conf, *configFilePath); err != nil {
|
if err := LoadStartupConfig(&conf, *configFilePath); err != nil {
|
||||||
log.Fatalf("%+v", err)
|
log.Fatalf("%+v", err)
|
||||||
}
|
}
|
||||||
|
// conf.HTTPAddr = *httpAddr
|
||||||
|
// conf.DNSAddr = *dnsAddr
|
||||||
log.Printf("%+v", conf)
|
log.Printf("%+v", conf)
|
||||||
store := &internal.Sqlite{
|
store := &internal.Sqlite{
|
||||||
Path: conf.DatabaseURL,
|
Path: conf.DatabaseURL,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue