carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// The Running tab's and the instances API's shared list controls: a
2// substring match, a health-word filter, and an opaque after-this-host
3// cursor. GET-param driven and server-computed on purpose — a filter the
4// server applies is one the API and CLI can apply identically (every carlos
5// command works for a user with no box access), and a bookmarked filtered
6// URL keeps meaning the same thing.
7package console
8
9import (
10 "encoding/base64"
11 "encoding/json"
12 "net/url"
13 "sort"
14 "strings"
15)
16
17// instancePageSize is the Running tab's page length.
18const instancePageSize = 50
19
20// healthWords is every word healthWord can emit, in the order the header
21// lists them. A new word joins healthWord, this list, and nothing else —
22// slugs, filters and links all derive from here. Waking/Crashed/Never
23// started are sidecarHealthWord's own vocabulary (overview.go); Running and
24// Asleep are shared with the instance family, not duplicated here.
25var healthWords = []string{"Running", "Serving", "Setting up", "Asleep", "No readings", "Not responding", "Failed", "Waking", "Crashed", "Never started"}
26
27// instanceQuery is one request's list controls. Zero value = everything,
28// first page.
29type instanceQuery struct {
30 Channel string
31 Group bool
32
33 Q string // substring match on host, trimmed
34 Health string // canonical health word ("" = all)
35 Cursor string // opaque; list resumes after the host inside it
36}
37
38func parseInstanceQuery(v url.Values) instanceQuery {
39 return instanceQuery{
40 Channel: strings.TrimSpace(v.Get("channel")),
41 Group: v.Get("group") == "channel",
42 Q: strings.TrimSpace(v.Get("q")),
43 Health: healthFromSlug(v.Get("health")),
44 Cursor: v.Get("cursor"),
45 }
46}
47
48// healthSlug is a health word's URL form: "Not responding" ↔
49// "not-responding".
50func healthSlug(word string) string {
51 return strings.ReplaceAll(strings.ToLower(word), " ", "-")
52}
53
54func healthTone(word string) string {
55 switch word {
56 case "Running", "Serving":
57 return "ok"
58 case "Failed", "Not responding":
59 return "attention"
60 case "Asleep", "Setting up":
61 return "notdeployed"
62 default:
63 return "unknown"
64 }
65}
66
67func hasInstanceChannel(channels []string, selected string) bool {
68 for _, channel := range channels {
69 if channel == selected {
70 return true
71 }
72 }
73 return false
74}
75
76// healthFromSlug inverts healthSlug. An unknown slug returns "" — no filter:
77// a mistyped URL shows everything rather than a mysteriously empty list.
78func healthFromSlug(slug string) string {
79 for _, w := range healthWords {
80 if healthSlug(w) == slug {
81 return w
82 }
83 }
84 return ""
85}
86
87type instanceCursor struct {
88 Channel string `json:"channel,omitempty"`
89 Grouped bool `json:"grouped,omitempty"`
90
91 Host string `json:"host"`
92}
93
94func encodeInstanceCursor(host string) string {
95 b, _ := json.Marshal(instanceCursor{Host: host})
96 return base64.RawURLEncoding.EncodeToString(b)
97}
98
99// decodeInstanceCursor degrades on any malformed input to "no cursor" —
100// page one, never an error page (applog.DecodeCursor's rule).
101func decodeInstanceCursor(s string) (string, bool) {
102 b, err := base64.RawURLEncoding.DecodeString(s)
103 if err != nil {
104 return "", false
105 }
106 var c instanceCursor
107 if json.Unmarshal(b, &c) != nil || c.Host == "" {
108 return "", false
109 }
110 return c.Host, true
111}
112
113// healthLink is one header chip: a word, its count over ALL the app's rows,
114// and the link that filters to it. Precomputed — templates render facts.
115type healthLink struct {
116 Word, Slug string
117 Count int
118 Active bool
119 URL string
120}
121
122// instancePage is one screen of the list: the rows to render plus the
123// numbers and links the header states.
124type instancePage struct {
125 Rows []InstanceRow
126 Matching []InstanceRow // rows matching Q+Health+Channel, across all pages
127 Counts map[string]int // health word -> count over ALL rows
128 Total int // rows matching Q+Health+Channel, across all pages
129 Links []healthLink // non-zero words, healthWords order
130 NextCursor string // "" on the last page
131 NextURL string // the whole next-page href; "" on the last page
132}
133
134// pageInstanceRows applies q to rows (which arrive in host order and stay in
135// it) and cuts one page. Counts cover every row regardless of filter — the
136// header's chips are the app's whole health story, and double as filters.
137func pageInstanceRows(rows []InstanceRow, q instanceQuery, limit int) instancePage {
138 p := instancePage{Counts: map[string]int{}}
139 var matched []InstanceRow
140 for _, r := range rows {
141 p.Counts[r.HealthWord]++
142 if q.Q != "" && !strings.Contains(r.Host, q.Q) {
143 continue
144 }
145 if q.Channel != "" && r.Channel != q.Channel {
146 continue
147 }
148 if q.Health != "" && r.HealthWord != q.Health {
149 continue
150 }
151 matched = append(matched, r)
152 }
153 if q.Group {
154 sort.SliceStable(matched, func(i, j int) bool {
155 if matched[i].Channel != matched[j].Channel {
156 return matched[i].Channel < matched[j].Channel
157 }
158 return matched[i].Host < matched[j].Host
159 })
160 }
161 p.Total = len(matched)
162 p.Matching = matched
163 start := 0
164 if after, ok := decodePageCursor(q.Cursor, q.Group); ok {
165 for i, r := range matched {
166 if (!q.Group && r.Host > after.Host) || (q.Group && (r.Channel > after.Channel || (r.Channel == after.Channel && r.Host > after.Host))) {
167 start = i
168 break
169 }
170 start = i + 1
171 }
172 }
173 end := start + limit
174 if end < len(matched) {
175 p.NextCursor = encodePageCursor(matched[end-1], q.Group)
176 } else {
177 end = len(matched)
178 }
179 p.Rows = matched[start:end]
180 for _, w := range healthWords {
181 if p.Counts[w] == 0 {
182 continue
183 }
184 linkQuery := q
185 linkQuery.Health, linkQuery.Cursor = w, ""
186 p.Links = append(p.Links, healthLink{Word: w, Slug: healthSlug(w), Count: p.Counts[w], Active: q.Health == w, URL: instanceQuerySuffix(linkQuery)})
187 }
188 if p.NextCursor != "" {
189 next := q
190 next.Cursor = p.NextCursor
191 p.NextURL = instanceQuerySuffix(next)
192 }
193 return p
194}
195