blob: 7e6d91fa1bed3017482e17020b5fc36bdc47ca34 [file] [log] [blame]
Serge Bazanski77628312023-02-15 23:33:22 +01001package webug
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "time"
8
9 "github.com/google/uuid"
10 "k8s.io/klog/v2"
11
12 "source.monogon.dev/cloud/bmaas/bmdb/model"
13 "source.monogon.dev/cloud/bmaas/bmdb/reflection"
14)
15
16// baseParams are passed to all rendered templates, and are consumed by tags in
17// templates/base.html.
18type baseParams struct {
19 // Address to display in page header.
20 BMDBAddress string
21 // Schema version to display in page header.
22 BMDBSchema string
23}
24
25// makeBase builds baseParams from information about the current connection.
26func (s *server) makeBase() baseParams {
27 address := fmt.Sprintf("%s@%s", s.conn.DatabaseName, s.conn.Address)
28 if s.conn.InMemory {
29 address += " (in memory)"
30 }
31 return baseParams{
32 BMDBAddress: address,
33 BMDBSchema: s.curSchema().Version,
34 }
35}
36
37// viewMachines renders a list of all machines in the BMDB.
38func (s *server) viewMachines(w http.ResponseWriter, r *http.Request, args ...string) {
39 start := time.Now()
40 res, err := s.curSchema().GetMachines(r.Context(), &reflection.GetMachinesOpts{Strict: true})
41 if err != nil {
42 w.WriteHeader(http.StatusInternalServerError)
43 fmt.Fprintf(w, "could not dump BMDB: %v", err)
44 return
45 }
46 duration := time.Since(start)
47
48 type params struct {
49 Base baseParams
50 Query string
51 Machines []*reflection.Machine
52 NMachines int
53 RenderTime time.Duration
54 }
55 err = templates.ExecuteTemplate(w, "machines.html", &params{
56 Base: s.makeBase(),
57 Query: res.Query,
58 Machines: res.Data,
59 NMachines: len(res.Data),
60 RenderTime: duration,
61 })
62 if err != nil {
63 klog.Errorf("Template rendering failed: %v", err)
64 }
65}
66
67// viewMachineDetail renders a detailed page for a single machine.
68func (s *server) viewMachineDetail(w http.ResponseWriter, r *http.Request, args ...string) {
69 mid, err := uuid.Parse(args[0])
70 if err != nil {
71 w.WriteHeader(http.StatusUnprocessableEntity)
72 fmt.Fprintf(w, "invalid machine UUID")
73 return
74 }
75
76 opts := reflection.GetMachinesOpts{
77 FilterMachine: &mid,
78 Strict: true,
79 ExpiredBackoffs: true,
80 }
81 res, err := s.curSchema().GetMachines(r.Context(), &opts)
82 if err != nil {
83 w.WriteHeader(http.StatusInternalServerError)
84 fmt.Fprintf(w, "could not dump BMDB: %v", err)
85 return
86 }
87 if len(res.Data) == 0 {
88 w.WriteHeader(http.StatusNotFound)
89 fmt.Fprintf(w, "machine not found")
90 return
91 }
92 machine := res.Data[0]
93
94 // Params to pass to template.
95 type sessionOrError struct {
96 Session *model.Session
97 Error string
98 }
99 type params struct {
100 Base baseParams
101 Machine *reflection.Machine
102
103 HistoryError string
104 History []model.WorkHistory
105
106 Sessions map[string]sessionOrError
107 }
108 p := params{
109 Base: s.makeBase(),
110 Machine: machine,
111 Sessions: make(map[string]sessionOrError),
112 }
113
114 // History retrieval is performed with strict consistency guarantees, and thus
115 // might block. Make sure we don't block the entire page.
116 subQueriesCtx, subQueriesCtxC := context.WithTimeout(r.Context(), time.Second)
117 defer subQueriesCtxC()
118 history, err := s.conn.ListHistoryOf(subQueriesCtx, mid)
119 if err != nil {
120 p.HistoryError = err.Error()
121 }
122
123 // Same for sessions.
124 for name, work := range machine.Work {
125 sessions, err := s.conn.GetSession(subQueriesCtx, work.SessionID)
126 if err != nil {
127 p.Sessions[name] = sessionOrError{Error: err.Error()}
128 } else {
129 if len(sessions) == 0 {
130 // This can happen if the session literally just disappeared.
131 //
132 // TODO(q3k): put all of these operations in a DB TX so that we don't end up with
133 // possible inconsistencies?
134 p.Sessions[name] = sessionOrError{Error: "not found"}
135 continue
136 }
137 p.Sessions[name] = sessionOrError{Session: &sessions[0]}
138 }
139 }
140
141 p.History = make([]model.WorkHistory, len(history))
142 for i := 0; i < len(history); i += 1 {
143 p.History[i] = history[len(history)-(i+1)]
144 }
145 if err := templates.ExecuteTemplate(w, "machine.html", &p); err != nil {
146 klog.Errorf("Template rendering failed: %v", err)
147 }
148}
149
150// viewProviderRedirects redirects a given provider and provider_id into a
151// provider's web portal for more detailed information about an underlying
152// machine.
153func (s *server) viewProviderRedirect(w http.ResponseWriter, r *http.Request, args ...string) {
154 providerUrls := map[string]string{
155 "Equinix": "https://console.equinix.com/devices/%s/overview",
156 }
157 if providerUrls[args[0]] == "" {
158 w.WriteHeader(http.StatusNotFound)
159 fmt.Fprintf(w, "Usage: /provider/Equinix/<id>")
160 return
161 }
162 url := fmt.Sprintf(providerUrls[args[0]], args[1])
163 http.Redirect(w, r, url, http.StatusFound)
164}
165
166// viewSession shows detailed information about a BMDB session.
167func (s *server) viewSession(w http.ResponseWriter, r *http.Request, args ...string) {
168 // TODO(q3k): implement this once we add session info to work history so that
169 // this can actually display something useful.
170 fmt.Fprintf(w, "underconstruction.gif")
171}