Implement filter in status, website and uri in CLI and Frontend
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
st "git.ipng.ch/ipng/nginx-logtail/internal/store"
|
||||
pb "git.ipng.ch/ipng/nginx-logtail/proto/logtailpb"
|
||||
)
|
||||
|
||||
@@ -44,12 +45,14 @@ type TableRow struct {
|
||||
DrillURL string
|
||||
}
|
||||
|
||||
// filterState holds the four optional filter fields parsed from URL params.
|
||||
// filterState holds the filter fields parsed from URL params.
|
||||
type filterState struct {
|
||||
Website string
|
||||
Prefix string
|
||||
URI string
|
||||
Status string // kept as string so empty means "unset"
|
||||
Website string
|
||||
Prefix string
|
||||
URI string
|
||||
Status string // expression: "200", "!=200", ">=400", etc.
|
||||
WebsiteRe string // RE2 regex against website
|
||||
URIRe string // RE2 regex against request URI
|
||||
}
|
||||
|
||||
// QueryParams holds all parsed URL parameters for one page request.
|
||||
@@ -65,16 +68,19 @@ type QueryParams struct {
|
||||
|
||||
// PageData is passed to the HTML template.
|
||||
type PageData struct {
|
||||
Params QueryParams
|
||||
Source string
|
||||
Entries []TableRow
|
||||
TotalCount int64
|
||||
Sparkline template.HTML
|
||||
Breadcrumbs []Crumb
|
||||
Windows []Tab
|
||||
GroupBys []Tab
|
||||
RefreshSecs int
|
||||
Error string
|
||||
Params QueryParams
|
||||
Source string
|
||||
Entries []TableRow
|
||||
TotalCount int64
|
||||
Sparkline template.HTML
|
||||
Breadcrumbs []Crumb
|
||||
Windows []Tab
|
||||
GroupBys []Tab
|
||||
RefreshSecs int
|
||||
Error string
|
||||
FilterExpr string // current filter serialised to mini-language for the input box
|
||||
FilterErr string // parse error from a submitted q= expression
|
||||
ClearFilterURL string // URL that removes all filter params
|
||||
}
|
||||
|
||||
var windowSpecs = []struct{ s, label string }{
|
||||
@@ -143,16 +149,18 @@ func (h *Handler) parseParams(r *http.Request) QueryParams {
|
||||
GroupByS: grpS,
|
||||
N: n,
|
||||
Filter: filterState{
|
||||
Website: q.Get("f_website"),
|
||||
Prefix: q.Get("f_prefix"),
|
||||
URI: q.Get("f_uri"),
|
||||
Status: q.Get("f_status"),
|
||||
Website: q.Get("f_website"),
|
||||
Prefix: q.Get("f_prefix"),
|
||||
URI: q.Get("f_uri"),
|
||||
Status: q.Get("f_status"),
|
||||
WebsiteRe: q.Get("f_website_re"),
|
||||
URIRe: q.Get("f_uri_re"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildFilter(f filterState) *pb.Filter {
|
||||
if f.Website == "" && f.Prefix == "" && f.URI == "" && f.Status == "" {
|
||||
if f.Website == "" && f.Prefix == "" && f.URI == "" && f.Status == "" && f.WebsiteRe == "" && f.URIRe == "" {
|
||||
return nil
|
||||
}
|
||||
out := &pb.Filter{}
|
||||
@@ -166,11 +174,17 @@ func buildFilter(f filterState) *pb.Filter {
|
||||
out.HttpRequestUri = &f.URI
|
||||
}
|
||||
if f.Status != "" {
|
||||
if n, err := strconv.Atoi(f.Status); err == nil {
|
||||
n32 := int32(n)
|
||||
out.HttpResponse = &n32
|
||||
if n, op, ok := st.ParseStatusExpr(f.Status); ok {
|
||||
out.HttpResponse = &n
|
||||
out.StatusOp = op
|
||||
}
|
||||
}
|
||||
if f.WebsiteRe != "" {
|
||||
out.WebsiteRegex = &f.WebsiteRe
|
||||
}
|
||||
if f.URIRe != "" {
|
||||
out.UriRegex = &f.URIRe
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -193,6 +207,12 @@ func (p QueryParams) toValues() url.Values {
|
||||
if p.Filter.Status != "" {
|
||||
v.Set("f_status", p.Filter.Status)
|
||||
}
|
||||
if p.Filter.WebsiteRe != "" {
|
||||
v.Set("f_website_re", p.Filter.WebsiteRe)
|
||||
}
|
||||
if p.Filter.URIRe != "" {
|
||||
v.Set("f_uri_re", p.Filter.URIRe)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -210,6 +230,14 @@ func (p QueryParams) buildURL(overrides map[string]string) string {
|
||||
return "/?" + v.Encode()
|
||||
}
|
||||
|
||||
// clearFilterURL returns a URL with all filter params removed.
|
||||
func (p QueryParams) clearFilterURL() string {
|
||||
return p.buildURL(map[string]string{
|
||||
"f_website": "", "f_prefix": "", "f_uri": "", "f_status": "",
|
||||
"f_website_re": "", "f_uri_re": "",
|
||||
})
|
||||
}
|
||||
|
||||
// nextGroupBy advances the drill-down dimension hierarchy (cycles at the end).
|
||||
func nextGroupBy(s string) string {
|
||||
switch s {
|
||||
@@ -273,6 +301,18 @@ func buildCrumbs(p QueryParams) []Crumb {
|
||||
RemoveURL: p.buildURL(map[string]string{"f_status": ""}),
|
||||
})
|
||||
}
|
||||
if p.Filter.WebsiteRe != "" {
|
||||
crumbs = append(crumbs, Crumb{
|
||||
Text: "website~=" + p.Filter.WebsiteRe,
|
||||
RemoveURL: p.buildURL(map[string]string{"f_website_re": ""}),
|
||||
})
|
||||
}
|
||||
if p.Filter.URIRe != "" {
|
||||
crumbs = append(crumbs, Crumb{
|
||||
Text: "uri~=" + p.Filter.URIRe,
|
||||
RemoveURL: p.buildURL(map[string]string{"f_uri_re": ""}),
|
||||
})
|
||||
}
|
||||
return crumbs
|
||||
}
|
||||
|
||||
@@ -326,6 +366,27 @@ func buildTableRows(entries []*pb.TopNEntry, p QueryParams) ([]TableRow, int64)
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
params := h.parseParams(r)
|
||||
|
||||
// Handle filter expression box submission (q= param).
|
||||
var filterErr string
|
||||
filterExprInput := FilterExprString(params.Filter)
|
||||
if qVals, ok := r.URL.Query()["q"]; ok {
|
||||
q := ""
|
||||
if len(qVals) > 0 {
|
||||
q = qVals[0]
|
||||
}
|
||||
fs, err := ParseFilterExpr(q)
|
||||
if err != nil {
|
||||
filterErr = err.Error()
|
||||
filterExprInput = q // show what the user typed so they can fix it
|
||||
// fall through: render page using existing filter params
|
||||
} else {
|
||||
params.Filter = fs
|
||||
http.Redirect(w, r, params.buildURL(nil), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filter := buildFilter(params.Filter)
|
||||
|
||||
conn, client, err := dial(params.Target)
|
||||
@@ -390,15 +451,18 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
data := PageData{
|
||||
Params: params,
|
||||
Source: tn.resp.Source,
|
||||
Entries: rows,
|
||||
TotalCount: total,
|
||||
Sparkline: sparkline,
|
||||
Breadcrumbs: buildCrumbs(params),
|
||||
Windows: buildWindowTabs(params),
|
||||
GroupBys: buildGroupByTabs(params),
|
||||
RefreshSecs: h.refreshSecs,
|
||||
Params: params,
|
||||
Source: tn.resp.Source,
|
||||
Entries: rows,
|
||||
TotalCount: total,
|
||||
Sparkline: sparkline,
|
||||
Breadcrumbs: buildCrumbs(params),
|
||||
Windows: buildWindowTabs(params),
|
||||
GroupBys: buildGroupByTabs(params),
|
||||
RefreshSecs: h.refreshSecs,
|
||||
FilterExpr: filterExprInput,
|
||||
FilterErr: filterErr,
|
||||
ClearFilterURL: params.clearFilterURL(),
|
||||
}
|
||||
h.render(w, http.StatusOK, data)
|
||||
}
|
||||
@@ -413,12 +477,14 @@ func (h *Handler) render(w http.ResponseWriter, status int, data PageData) {
|
||||
|
||||
func (h *Handler) errorPage(params QueryParams, msg string) PageData {
|
||||
return PageData{
|
||||
Params: params,
|
||||
Windows: buildWindowTabs(params),
|
||||
GroupBys: buildGroupByTabs(params),
|
||||
Breadcrumbs: buildCrumbs(params),
|
||||
RefreshSecs: h.refreshSecs,
|
||||
Error: msg,
|
||||
Params: params,
|
||||
Windows: buildWindowTabs(params),
|
||||
GroupBys: buildGroupByTabs(params),
|
||||
Breadcrumbs: buildCrumbs(params),
|
||||
RefreshSecs: h.refreshSecs,
|
||||
Error: msg,
|
||||
FilterExpr: FilterExprString(params.Filter),
|
||||
ClearFilterURL: params.clearFilterURL(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user