| package webug | 
 |  | 
 | import ( | 
 | 	"context" | 
 | 	"fmt" | 
 | 	"net/http" | 
 | 	"time" | 
 |  | 
 | 	"github.com/google/uuid" | 
 | 	"k8s.io/klog/v2" | 
 |  | 
 | 	"source.monogon.dev/cloud/bmaas/bmdb/model" | 
 | 	"source.monogon.dev/cloud/bmaas/bmdb/reflection" | 
 | ) | 
 |  | 
 | // baseParams are passed to all rendered templates, and are consumed by tags in | 
 | // templates/base.gohtml. | 
 | type baseParams struct { | 
 | 	// Address to display in page header. | 
 | 	BMDBAddress string | 
 | 	// Schema version to display in page header. | 
 | 	BMDBSchema string | 
 | } | 
 |  | 
 | // makeBase builds baseParams from information about the current connection. | 
 | func (s *server) makeBase() baseParams { | 
 | 	address := fmt.Sprintf("%s@%s", s.conn.DatabaseName, s.conn.Address) | 
 | 	if s.conn.InMemory { | 
 | 		address += " (in memory)" | 
 | 	} | 
 | 	return baseParams{ | 
 | 		BMDBAddress: address, | 
 | 		BMDBSchema:  s.curSchema().Version, | 
 | 	} | 
 | } | 
 |  | 
 | // viewMachines renders a list of all machines in the BMDB. | 
 | func (s *server) viewMachines(w http.ResponseWriter, r *http.Request, args ...string) { | 
 | 	start := time.Now() | 
 | 	res, err := s.curSchema().GetMachines(r.Context(), &reflection.GetMachinesOpts{Strict: s.strictConsistency}) | 
 | 	if err != nil { | 
 | 		w.WriteHeader(http.StatusInternalServerError) | 
 | 		fmt.Fprintf(w, "could not dump BMDB: %v", err) | 
 | 		return | 
 | 	} | 
 | 	duration := time.Since(start) | 
 |  | 
 | 	tagCount := make(map[string]int) | 
 | 	for _, d := range res.Data { | 
 | 		for _, t := range d.Tags { | 
 | 			tagCount[t.Type.Name()]++ | 
 | 		} | 
 | 	} | 
 |  | 
 | 	type params struct { | 
 | 		Base       baseParams | 
 | 		Query      string | 
 | 		Machines   []*reflection.Machine | 
 | 		NMachines  int | 
 | 		RenderTime time.Duration | 
 | 		TagCount   map[string]int | 
 | 	} | 
 | 	err = templates.ExecuteTemplate(w, "machines.gohtml", ¶ms{ | 
 | 		Base:       s.makeBase(), | 
 | 		Query:      res.Query, | 
 | 		Machines:   res.Data, | 
 | 		NMachines:  len(res.Data), | 
 | 		RenderTime: duration, | 
 | 		TagCount:   tagCount, | 
 | 	}) | 
 | 	if err != nil { | 
 | 		klog.Errorf("Template rendering failed: %v", err) | 
 | 	} | 
 | } | 
 |  | 
 | // viewMachineDetail renders a detailed page for a single machine. | 
 | func (s *server) viewMachineDetail(w http.ResponseWriter, r *http.Request, args ...string) { | 
 | 	mid, err := uuid.Parse(args[0]) | 
 | 	if err != nil { | 
 | 		w.WriteHeader(http.StatusUnprocessableEntity) | 
 | 		fmt.Fprintf(w, "invalid machine UUID") | 
 | 		return | 
 | 	} | 
 |  | 
 | 	opts := reflection.GetMachinesOpts{ | 
 | 		FilterMachine:   &mid, | 
 | 		Strict:          s.strictConsistency, | 
 | 		ExpiredBackoffs: true, | 
 | 	} | 
 | 	res, err := s.curSchema().GetMachines(r.Context(), &opts) | 
 | 	if err != nil { | 
 | 		w.WriteHeader(http.StatusInternalServerError) | 
 | 		fmt.Fprintf(w, "could not dump BMDB: %v", err) | 
 | 		return | 
 | 	} | 
 | 	if len(res.Data) == 0 { | 
 | 		w.WriteHeader(http.StatusNotFound) | 
 | 		fmt.Fprintf(w, "machine not found") | 
 | 		return | 
 | 	} | 
 | 	machine := res.Data[0] | 
 |  | 
 | 	// Params to pass to template. | 
 | 	type sessionOrError struct { | 
 | 		Session *model.Session | 
 | 		Error   string | 
 | 	} | 
 | 	type params struct { | 
 | 		Base    baseParams | 
 | 		Machine *reflection.Machine | 
 |  | 
 | 		HistoryError string | 
 | 		History      []model.WorkHistory | 
 |  | 
 | 		Sessions map[string]sessionOrError | 
 | 	} | 
 | 	p := params{ | 
 | 		Base:     s.makeBase(), | 
 | 		Machine:  machine, | 
 | 		Sessions: make(map[string]sessionOrError), | 
 | 	} | 
 |  | 
 | 	// History retrieval is performed with strict consistency guarantees, and thus | 
 | 	// might block. Make sure we don't block the entire page. | 
 | 	subQueriesCtx, subQueriesCtxC := context.WithTimeout(r.Context(), time.Second) | 
 | 	defer subQueriesCtxC() | 
 | 	history, err := s.conn.ListHistoryOf(subQueriesCtx, mid) | 
 | 	if err != nil { | 
 | 		p.HistoryError = err.Error() | 
 | 	} | 
 |  | 
 | 	// Same for sessions. | 
 | 	for name, work := range machine.Work { | 
 | 		sessions, err := s.conn.GetSession(subQueriesCtx, work.SessionID) | 
 | 		if err != nil { | 
 | 			p.Sessions[name] = sessionOrError{Error: err.Error()} | 
 | 		} else { | 
 | 			if len(sessions) == 0 { | 
 | 				// This can happen if the session literally just disappeared. | 
 | 				// | 
 | 				// TODO(q3k): put all of these operations in a DB TX so that we don't end up with | 
 | 				// possible inconsistencies? | 
 | 				p.Sessions[name] = sessionOrError{Error: "not found"} | 
 | 				continue | 
 | 			} | 
 | 			p.Sessions[name] = sessionOrError{Session: &sessions[0]} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	p.History = make([]model.WorkHistory, len(history)) | 
 | 	for i := 0; i < len(history); i += 1 { | 
 | 		p.History[i] = history[len(history)-(i+1)] | 
 | 	} | 
 | 	if err := templates.ExecuteTemplate(w, "machine.gohtml", &p); err != nil { | 
 | 		klog.Errorf("Template rendering failed: %v", err) | 
 | 	} | 
 | } | 
 |  | 
 | // viewProviderRedirects redirects a given provider and provider_id into a | 
 | // provider's web portal for more detailed information about an underlying | 
 | // machine. | 
 | func (s *server) viewProviderRedirect(w http.ResponseWriter, r *http.Request, args ...string) { | 
 | 	providerUrls := map[string]string{ | 
 | 		"Equinix": "https://console.equinix.com/devices/%s/overview", | 
 | 	} | 
 | 	if providerUrls[args[0]] == "" { | 
 | 		w.WriteHeader(http.StatusNotFound) | 
 | 		fmt.Fprintf(w, "Usage: /provider/Equinix/<id>") | 
 | 		return | 
 | 	} | 
 | 	url := fmt.Sprintf(providerUrls[args[0]], args[1]) | 
 | 	http.Redirect(w, r, url, http.StatusFound) | 
 | } | 
 |  | 
 | // viewSession shows detailed information about a BMDB session. | 
 | func (s *server) viewSession(w http.ResponseWriter, r *http.Request, args ...string) { | 
 | 	// TODO(q3k): implement this once we add session info to work history so that | 
 | 	// this can actually display something useful. | 
 | 	fmt.Fprintf(w, "underconstruction.gif") | 
 | } |