PRE-RELEASE 0.9.2: frontend rename source→collector, tab layout fit
Rename the collector-picker concept to 'collector' throughout the frontend so it no longer collides with the ipng_source_tag group-by (which is labelled 'source'). Affects PageData.Collector, the raw JSON output key, template labels, and tests. Proto Source field is untouched (wire-level name used by CLI and aggregator too). Shrink tab padding/gap/font-size and add window:/filter: labels so the four tab rows (window, filter, collector, tor) line up and 7+ collectors fit on one line at the 1100px body width. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
Makefile
2
Makefile
@@ -5,7 +5,7 @@ PROTO_FILE := $(PROTO_DIR)/logtail.proto
|
|||||||
GEN_FILES := proto/logtailpb/logtail.pb.go proto/logtailpb/logtail_grpc.pb.go
|
GEN_FILES := proto/logtailpb/logtail.pb.go proto/logtailpb/logtail_grpc.pb.go
|
||||||
|
|
||||||
NATIVE_ARCH := $(shell go env GOARCH)
|
NATIVE_ARCH := $(shell go env GOARCH)
|
||||||
VERSION := 0.9.1
|
VERSION := 0.9.2
|
||||||
COMMIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
COMMIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||||
DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||||
LDFLAGS := -s -w \
|
LDFLAGS := -s -w \
|
||||||
|
|||||||
@@ -368,10 +368,10 @@ func TestHandlerRaw(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var result struct {
|
var result struct {
|
||||||
Source string `json:"source"`
|
Collector string `json:"collector"`
|
||||||
Window string `json:"window"`
|
Window string `json:"window"`
|
||||||
GroupBy string `json:"group_by"`
|
GroupBy string `json:"group_by"`
|
||||||
Entries []struct {
|
Entries []struct {
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Count int64 `json:"count"`
|
Count int64 `json:"count"`
|
||||||
} `json:"entries"`
|
} `json:"entries"`
|
||||||
@@ -379,8 +379,8 @@ func TestHandlerRaw(t *testing.T) {
|
|||||||
if err := json.Unmarshal([]byte(body), &result); err != nil {
|
if err := json.Unmarshal([]byte(body), &result); err != nil {
|
||||||
t.Fatalf("JSON parse: %v\nbody: %s", err, body)
|
t.Fatalf("JSON parse: %v\nbody: %s", err, body)
|
||||||
}
|
}
|
||||||
if result.Source != "agg" {
|
if result.Collector != "agg" {
|
||||||
t.Errorf("source = %q", result.Source)
|
t.Errorf("collector = %q", result.Collector)
|
||||||
}
|
}
|
||||||
if result.Window != "15m" {
|
if result.Window != "15m" {
|
||||||
t.Errorf("window = %q", result.Window)
|
t.Errorf("window = %q", result.Window)
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ type QueryParams struct {
|
|||||||
// PageData is passed to the HTML template.
|
// PageData is passed to the HTML template.
|
||||||
type PageData struct {
|
type PageData struct {
|
||||||
Params QueryParams
|
Params QueryParams
|
||||||
Source string
|
Collector string
|
||||||
Entries []TableRow
|
Entries []TableRow
|
||||||
TotalCount int64
|
TotalCount int64
|
||||||
Sparkline template.HTML
|
Sparkline template.HTML
|
||||||
Breadcrumbs []Crumb
|
Breadcrumbs []Crumb
|
||||||
Windows []Tab
|
Windows []Tab
|
||||||
GroupBys []Tab
|
GroupBys []Tab
|
||||||
Targets []Tab // source/target picker; empty when only one target available
|
Targets []Tab // collector picker; empty when only one target available
|
||||||
TorTabs []Tab // all / tor / no-tor toggle
|
TorTabs []Tab // all / tor / no-tor toggle
|
||||||
RefreshSecs int
|
RefreshSecs int
|
||||||
Error string
|
Error string
|
||||||
@@ -455,7 +455,7 @@ func buildTorTabs(p QueryParams) []Tab {
|
|||||||
return tabs
|
return tabs
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildTargetTabs builds the source/target picker tabs from a ListTargets response.
|
// buildTargetTabs builds the collector picker tabs from a ListTargets response.
|
||||||
// Returns nil (hide picker) when only one endpoint is reachable.
|
// Returns nil (hide picker) when only one endpoint is reachable.
|
||||||
func (h *Handler) buildTargetTabs(p QueryParams, lt *pb.ListTargetsResponse) []Tab {
|
func (h *Handler) buildTargetTabs(p QueryParams, lt *pb.ListTargetsResponse) []Tab {
|
||||||
// "all" always points at the configured aggregator default.
|
// "all" always points at the configured aggregator default.
|
||||||
@@ -577,7 +577,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
// Always query the default target for ListTargets so we get the full
|
// Always query the default target for ListTargets so we get the full
|
||||||
// list of available sources even when viewing a specific collector.
|
// list of available collectors even when viewing a specific one.
|
||||||
ltClient := client
|
ltClient := client
|
||||||
var ltConn *grpc.ClientConn
|
var ltConn *grpc.ClientConn
|
||||||
if params.Target != h.defaultTarget {
|
if params.Target != h.defaultTarget {
|
||||||
@@ -623,7 +623,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
data := PageData{
|
data := PageData{
|
||||||
Params: params,
|
Params: params,
|
||||||
Source: tn.resp.Source,
|
Collector: tn.resp.Source,
|
||||||
Entries: rows,
|
Entries: rows,
|
||||||
TotalCount: total,
|
TotalCount: total,
|
||||||
Sparkline: sparkline,
|
Sparkline: sparkline,
|
||||||
@@ -668,16 +668,16 @@ func writeRawJSON(w http.ResponseWriter, params QueryParams, resp *pb.TopNRespon
|
|||||||
Count int64 `json:"count"`
|
Count int64 `json:"count"`
|
||||||
}
|
}
|
||||||
type out struct {
|
type out struct {
|
||||||
Source string `json:"source"`
|
Collector string `json:"collector"`
|
||||||
Window string `json:"window"`
|
Window string `json:"window"`
|
||||||
GroupBy string `json:"group_by"`
|
GroupBy string `json:"group_by"`
|
||||||
Entries []entry `json:"entries"`
|
Entries []entry `json:"entries"`
|
||||||
}
|
}
|
||||||
o := out{
|
o := out{
|
||||||
Source: resp.Source,
|
Collector: resp.Source,
|
||||||
Window: params.WindowS,
|
Window: params.WindowS,
|
||||||
GroupBy: params.GroupByS,
|
GroupBy: params.GroupByS,
|
||||||
Entries: make([]entry, len(resp.Entries)),
|
Entries: make([]entry, len(resp.Entries)),
|
||||||
}
|
}
|
||||||
for i, e := range resp.Entries {
|
for i, e := range resp.Entries {
|
||||||
o.Entries[i] = entry{Label: e.Label, Count: e.Count}
|
o.Entries[i] = entry{Label: e.Label, Count: e.Count}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
body { font-family: monospace; font-size: 14px; max-width: 1100px; margin: 2em auto; padding: 0 1.5em; color: #222; }
|
body { font-family: monospace; font-size: 14px; max-width: 1100px; margin: 2em auto; padding: 0 1.5em; color: #222; }
|
||||||
h1 { font-size: 1.1em; font-weight: bold; margin: 0 0 1em; letter-spacing: 0.05em; }
|
h1 { font-size: 1.1em; font-weight: bold; margin: 0 0 1em; letter-spacing: 0.05em; }
|
||||||
.tabs { display: flex; gap: 0.3em; margin-bottom: 0.7em; flex-wrap: wrap; }
|
.tabs { display: flex; gap: 0.2em; margin-bottom: 0.5em; flex-wrap: wrap; align-items: center; }
|
||||||
.tabs a { text-decoration: none; padding: 0.2em 0.8em; border: 1px solid #aaa; color: #444; }
|
.tabs a { text-decoration: none; padding: 0.1em 0.5em; border: 1px solid #aaa; color: #444; font-size: 0.9em; }
|
||||||
.tabs a:hover { background: #f0f0f0; }
|
.tabs a:hover { background: #f0f0f0; }
|
||||||
.tabs a.active { background: #222; color: #fff; border-color: #222; }
|
.tabs a.active { background: #222; color: #fff; border-color: #222; }
|
||||||
.crumbs { margin-bottom: 0.8em; font-size: 0.9em; }
|
.crumbs { margin-bottom: 0.8em; font-size: 0.9em; }
|
||||||
@@ -34,9 +34,7 @@ a:hover { text-decoration: underline; }
|
|||||||
.error { color: #c00; border: 1px solid #fbb; background: #fff5f5; padding: 0.7em 1em; margin: 1em 0; border-radius: 3px; }
|
.error { color: #c00; border: 1px solid #fbb; background: #fff5f5; padding: 0.7em 1em; margin: 1em 0; border-radius: 3px; }
|
||||||
.nodata { color: #999; margin: 2em 0; font-style: italic; }
|
.nodata { color: #999; margin: 2em 0; font-style: italic; }
|
||||||
footer { margin-top: 2em; padding-top: 0.6em; border-top: 1px solid #e0e0e0; font-size: 0.8em; color: #999; }
|
footer { margin-top: 2em; padding-top: 0.6em; border-top: 1px solid #e0e0e0; font-size: 0.8em; color: #999; }
|
||||||
.tabs-targets { margin-top: -0.4em; }
|
.tabs-label { font-size: 0.8em; color: #888; margin-right: 0.15em; min-width: 5.5em; }
|
||||||
.tabs-tor { margin-top: -0.4em; }
|
|
||||||
.tabs-label { font-size: 0.85em; color: #888; margin-right: 0.2em; align-self: center; }
|
|
||||||
.filter-form { display: flex; gap: 0.4em; align-items: center; margin-bottom: 0.7em; }
|
.filter-form { display: flex; gap: 0.4em; align-items: center; margin-bottom: 0.7em; }
|
||||||
.filter-input { flex: 1; font-family: monospace; font-size: 13px; padding: 0.25em 0.5em; border: 1px solid #aaa; }
|
.filter-input { flex: 1; font-family: monospace; font-size: 13px; padding: 0.25em 0.5em; border: 1px solid #aaa; }
|
||||||
.filter-form button { padding: 0.25em 0.8em; border: 1px solid #aaa; background: #f4f4f4; cursor: pointer; font-family: monospace; }
|
.filter-form button { padding: 0.25em 0.8em; border: 1px solid #aaa; background: #f4f4f4; cursor: pointer; font-family: monospace; }
|
||||||
|
|||||||
@@ -2,19 +2,21 @@
|
|||||||
<h1>nginx-logtail</h1>
|
<h1>nginx-logtail</h1>
|
||||||
|
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
|
<span class="tabs-label">window:</span>
|
||||||
{{- range .Windows}}
|
{{- range .Windows}}
|
||||||
<a href="{{.URL}}"{{if .Active}} class="active"{{end}}>{{.Label}}</a>
|
<a href="{{.URL}}"{{if .Active}} class="active"{{end}}>{{.Label}}</a>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
|
<span class="tabs-label">filter:</span>
|
||||||
{{- range .GroupBys}}
|
{{- range .GroupBys}}
|
||||||
<a href="{{.URL}}"{{if .Active}} class="active"{{end}}>{{.Label}}</a>
|
<a href="{{.URL}}"{{if .Active}} class="active"{{end}}>{{.Label}}</a>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .Targets}}<div class="tabs tabs-targets">
|
{{if .Targets}}<div class="tabs tabs-targets">
|
||||||
<span class="tabs-label">source:</span>
|
<span class="tabs-label">collector:</span>
|
||||||
{{- range .Targets}}
|
{{- range .Targets}}
|
||||||
<a href="{{.URL}}"{{if .Active}} class="active"{{end}}>{{.Label}}</a>
|
<a href="{{.URL}}"{{if .Active}} class="active"{{end}}>{{.Label}}</a>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
@@ -53,7 +55,7 @@
|
|||||||
|
|
||||||
{{if .Sparkline}}
|
{{if .Sparkline}}
|
||||||
<div class="sparkline">
|
<div class="sparkline">
|
||||||
<small>{{.Params.WindowS}} trend · by {{.Params.GroupByS}}{{if .Source}} · source: {{.Source}}{{end}}</small>
|
<small>{{.Params.WindowS}} trend · by {{.Params.GroupByS}}{{if .Collector}} · collector: {{.Collector}}{{end}}</small>
|
||||||
{{.Sparkline}}
|
{{.Sparkline}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -88,7 +90,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
{{- if .Source}}source: {{.Source}} · {{end -}}
|
{{- if .Collector}}collector: {{.Collector}} · {{end -}}
|
||||||
{{fmtCount .TotalCount}} requests · {{.Params.WindowS}} window · by {{.Params.GroupByS}}
|
{{fmtCount .TotalCount}} requests · {{.Params.WindowS}} window · by {{.Params.GroupByS}}
|
||||||
{{- if gt .RefreshSecs 0}} · auto-refresh {{.RefreshSecs}}s{{end}}
|
{{- if gt .RefreshSecs 0}} · auto-refresh {{.RefreshSecs}}s{{end}}
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package version
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "0.9.1"
|
Version = "0.9.2"
|
||||||
Commit = "unknown"
|
Commit = "unknown"
|
||||||
Date = "unknown"
|
Date = "unknown"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user