app/appengine, app/key: delete

The appengine command is no longer needed since
the coordinator is serving the build dashboard.

The key package is now unused.

For golang/go#34744.

Change-Id: I74218bac6b7457434975d13842e59e150fc078be
Reviewed-on: https://go-review.googlesource.com/c/build/+/336792
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
diff --git a/app/appengine/.gcloudignore b/app/appengine/.gcloudignore
deleted file mode 100644
index 199e6d9..0000000
--- a/app/appengine/.gcloudignore
+++ /dev/null
@@ -1,25 +0,0 @@
-# This file specifies files that are *not* uploaded to Google Cloud Platform
-# using gcloud. It follows the same syntax as .gitignore, with the addition of
-# "#!include" directives (which insert the entries of the given .gitignore-style
-# file at that point).
-#
-# For more information, run:
-#   $ gcloud topic gcloudignore
-#
-.gcloudignore
-# If you would like to upload your .git directory, .gitignore file or files
-# from your .gitignore file, remove the corresponding line
-# below:
-.git
-.gitignore
-
-# Binaries for programs and plugins
-*.exe
-*.exe~
-*.dll
-*.so
-*.dylib
-# Test binary, build with `go test -c`
-*.test
-# Output of the go coverage tool, specifically when used with LiteIDE
-*.out
\ No newline at end of file
diff --git a/app/appengine/Makefile b/app/appengine/Makefile
deleted file mode 100644
index c9d55ba..0000000
--- a/app/appengine/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-usage:
-	echo "See Makefile for usage"
-	exit 1
-
-deploy-prod:
-	go install golang.org/x/build/cmd/xb
-	GO111MODULE=on gcloud app --account=$$(xb google-email) --project=golang-org deploy app.yaml
-
-deploy-test:
-	go install golang.org/x/build/cmd/xb
-	GO111MODULE=on gcloud app --account=$$(xb google-email) --project=golang-org deploy --no-promote app.yaml
diff --git a/app/appengine/README.md b/app/appengine/README.md
deleted file mode 100644
index bdff720..0000000
--- a/app/appengine/README.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# build.golang.org App Engine App
-
-This is the code that runs https://build.golang.org/
-
-## Local development
-
-To use production maintner data (for the GetDashboard RPC containing
-the list of commits, etc) and production active builds (from the
-coordinator), both of which are open to anybody, use:
-
-```
-go run . --dev --fake-results
-```
-
-If you also want to use the production datastore for real commit data,
-or you want to work on the handlers that mutate data in the datastore,
-use:
-
-```
-go run . --dev
-```
-
-That requires access to the "golang-org" GCP project's datastore.
-
-Environment variables you can change:
-
-* `PORT`: plain port number or Go-style listen address
-* `DATASTORE_PROJECT_ID`: defaults to `"golang-org"` in dev mode
-* `MAINTNER_ADDR`: defaults to "maintner.golang.org"
-
-## Deploying a test version
-
-To deploy to the production project but to a version that's not promoted to the default URL:
-
-```sh
-make deploy-test
-```
-
-It will tell you what URL it deployed to. You can then check it and
-either delete it or promote it with either the gcloud or web UIs. Or
-just ignore it. They'll scale to zero and are only visual clutter
-until somebody deletes a batch of old ones.
-
-## Deploying to production
-
-To deploy to https://build.golang.org:
-
-```sh
-make deploy-prod
-```
-
diff --git a/app/appengine/app.yaml b/app/appengine/app.yaml
deleted file mode 100644
index 057f29d..0000000
--- a/app/appengine/app.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-runtime: go113
-service: build
-
-handlers:
-  - url: /.*
-    script: auto
-    secure: always
diff --git a/app/appengine/build.go b/app/appengine/build.go
deleted file mode 100644
index d2ced21..0000000
--- a/app/appengine/build.go
+++ /dev/null
@@ -1,456 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"bytes"
-	"compress/gzip"
-	"context"
-	"errors"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"math/rand"
-	pathpkg "path"
-	"strings"
-
-	"cloud.google.com/go/datastore"
-	"golang.org/x/build/dashboard"
-	"golang.org/x/build/internal/loghash"
-)
-
-const (
-	maxDatastoreStringLen = 500
-)
-
-func dsKey(kind, name string, parent *datastore.Key) *datastore.Key {
-	dk := datastore.NameKey(kind, name, parent)
-	dk.Namespace = "Git"
-	return dk
-}
-
-// A Package describes a package that is listed on the dashboard.
-type Package struct {
-	Name string // "Go", "arch", "net", ...
-	Path string // empty for the main Go tree, else "golang.org/x/foo"
-}
-
-func (p *Package) String() string {
-	return fmt.Sprintf("%s: %q", p.Path, p.Name)
-}
-
-func (p *Package) Key() *datastore.Key {
-	key := p.Path
-	if key == "" {
-		key = "go"
-	}
-	return dsKey("Package", key, nil)
-}
-
-// filterDatastoreError returns err, unless it's just about datastore
-// not being able to load an entity with old legacy struct fields into
-// the Commit type that has since removed those fields.
-func filterDatastoreError(err error) error {
-	return filterAppEngineError(err, func(err error) bool {
-		if em, ok := err.(*datastore.ErrFieldMismatch); ok {
-			switch em.FieldName {
-			case "NeedsBenchmarking", "TryPatch", "FailNotificationSent":
-				// Removed in CLs 208397 and 208324.
-				return true
-			case "PackagePath", "ParentHash", "Num", "User", "Desc", "Time", "Branch", "NextNum", "Kind":
-				// Removed in move to maintner in CL 208697.
-				return true
-			}
-		}
-		return false
-	})
-}
-
-// filterNoSuchEntity returns err, unless it's just about datastore
-// not being able to load an entity because it doesn't exist.
-func filterNoSuchEntity(err error) error {
-	return filterAppEngineError(err, func(err error) bool {
-		return err == datastore.ErrNoSuchEntity
-	})
-}
-
-// filterAppEngineError returns err, unless ignore(err) is true,
-// in which case it returns nil. If err is an datastore.MultiError,
-// it returns either nil (if all errors are ignored) or a deep copy
-// with the non-ignored errors.
-func filterAppEngineError(err error, ignore func(error) bool) error {
-	if err == nil || ignore(err) {
-		return nil
-	}
-	if me, ok := err.(datastore.MultiError); ok {
-		me2 := make(datastore.MultiError, 0, len(me))
-		for _, err := range me {
-			if e2 := filterAppEngineError(err, ignore); e2 != nil {
-				me2 = append(me2, e2)
-			}
-		}
-		if len(me2) == 0 {
-			return nil
-		}
-		return me2
-	}
-	return err
-}
-
-// getOrMakePackageInTx fetches a Package by path from the datastore,
-// creating it if necessary.
-func getOrMakePackageInTx(ctx context.Context, tx *datastore.Transaction, path string) (*Package, error) {
-	p := &Package{Path: path}
-	if path != "" {
-		p.Name = pathpkg.Base(path)
-	} else {
-		p.Name = "Go"
-	}
-	err := tx.Get(p.Key(), p)
-	err = filterDatastoreError(err)
-	if err == datastore.ErrNoSuchEntity {
-		if _, err := tx.Put(p.Key(), p); err != nil {
-			return nil, err
-		}
-		return p, nil
-	}
-	if err != nil {
-		return nil, err
-	}
-	return p, nil
-}
-
-type builderAndGoHash struct {
-	builder, goHash string
-}
-
-// A Commit describes an individual commit in a package.
-//
-// Each Commit entity is a descendant of its associated Package entity.
-// In other words, all Commits with the same PackagePath belong to the same
-// datastore entity group.
-type Commit struct {
-	PackagePath string // (empty for main repo commits)
-	Hash        string
-
-	// ResultData is the Data string of each build Result for this Commit.
-	// For non-Go commits, only the Results for the current Go tip, weekly,
-	// and release Tags are stored here. This is purely de-normalized data.
-	// The complete data set is stored in Result entities.
-	ResultData []string `datastore:",noindex"`
-}
-
-func (com *Commit) Key() *datastore.Key {
-	if com.Hash == "" {
-		panic("tried Key on Commit with empty Hash")
-	}
-	p := Package{Path: com.PackagePath}
-	key := com.PackagePath + "|" + com.Hash
-	return dsKey("Commit", key, p.Key())
-}
-
-// Valid reports whether the commit is valid.
-func (c *Commit) Valid() bool {
-	// Valid really just means the hash is populated.
-	return validHash(c.Hash)
-}
-
-// each result line is approx 105 bytes. This constant is a tradeoff between
-// build history and the AppEngine datastore limit of 1mb.
-const maxResults = 1000
-
-// AddResult adds the denormalized Result data to the Commit's
-// ResultData field.
-func (com *Commit) AddResult(tx *datastore.Transaction, r *Result) error {
-	err := tx.Get(com.Key(), com)
-	if err == datastore.ErrNoSuchEntity {
-		// If it doesn't exist, we create it below.
-	} else {
-		err = filterDatastoreError(err)
-		if err != nil {
-			return fmt.Errorf("Commit.AddResult, getting Commit: %v", err)
-		}
-	}
-
-	var resultExists bool
-	for i, s := range com.ResultData {
-		// if there already exists result data for this builder at com, overwrite it.
-		if strings.HasPrefix(s, r.Builder+"|") && strings.HasSuffix(s, "|"+r.GoHash) {
-			resultExists = true
-			com.ResultData[i] = r.Data()
-		}
-	}
-	if !resultExists {
-		// otherwise, add the new result data for this builder.
-		com.ResultData = trim(append(com.ResultData, r.Data()), maxResults)
-	}
-	if !com.Valid() {
-		return errors.New("putting Commit: commit is not valid")
-	}
-	if _, err := tx.Put(com.Key(), com); err != nil {
-		return fmt.Errorf("putting Commit: %v", err)
-	}
-	return nil
-}
-
-// removeResult removes the denormalized Result data from the ResultData field
-// for the given builder and go hash.
-// It must be called from within the datastore transaction that gets and puts
-// the Commit. Note this is slightly different to AddResult, above.
-func (com *Commit) RemoveResult(r *Result) {
-	var rd []string
-	for _, s := range com.ResultData {
-		if strings.HasPrefix(s, r.Builder+"|") && strings.HasSuffix(s, "|"+r.GoHash) {
-			continue
-		}
-		rd = append(rd, s)
-	}
-	com.ResultData = rd
-}
-
-func trim(s []string, n int) []string {
-	l := min(len(s), n)
-	return s[len(s)-l:]
-}
-
-func min(a, b int) int {
-	if a < b {
-		return a
-	}
-	return b
-}
-
-// Result returns the build Result for this Commit for the given builder/goHash.
-//
-// For the main Go repo, goHash is the empty string.
-func (c *Commit) Result(builder, goHash string) *Result {
-	return result(c.ResultData, c.Hash, c.PackagePath, builder, goHash)
-}
-
-// Result returns the build Result for this commit for the given builder/goHash.
-//
-// For the main Go repo, goHash is the empty string.
-func (c *CommitInfo) Result(builder, goHash string) *Result {
-	if r := result(c.ResultData, c.Hash, c.PackagePath, builder, goHash); r != nil {
-		return r
-	}
-	if u, ok := c.BuildingURLs[builderAndGoHash{builder, goHash}]; ok {
-		return &Result{
-			Builder:     builder,
-			BuildingURL: u,
-			Hash:        c.Hash,
-			GoHash:      goHash,
-		}
-	}
-	if *fakeResults {
-		switch rand.Intn(3) {
-		default:
-			return nil
-		case 1:
-			return &Result{
-				Builder: builder,
-				Hash:    c.Hash,
-				GoHash:  goHash,
-				OK:      true,
-			}
-		case 2:
-			return &Result{
-				Builder: builder,
-				Hash:    c.Hash,
-				GoHash:  goHash,
-				LogHash: "fakefailureurl",
-			}
-		}
-	}
-	return nil
-}
-
-func result(resultData []string, hash, packagePath, builder, goHash string) *Result {
-	for _, r := range resultData {
-		if !strings.HasPrefix(r, builder) {
-			// Avoid strings.SplitN alloc in the common case.
-			continue
-		}
-		p := strings.SplitN(r, "|", 4)
-		if len(p) != 4 || p[0] != builder || p[3] != goHash {
-			continue
-		}
-		return partsToResult(hash, packagePath, p)
-	}
-	return nil
-}
-
-// isUntested reports whether a cell in the build.golang.org grid is
-// an untested configuration.
-//
-// repo is "go", "net", etc.
-// branch is the branch of repo "master" or "release-branch.go1.12"
-// goBranch applies only if repo != "go" and is of form "master" or "release-branch.go1.N"
-//
-// As a special case, "tip" is an alias for "master", since this app
-// still uses a bunch of hg terms from when we used hg.
-func isUntested(builder, repo, branch, goBranch string) bool {
-	if branch == "tip" {
-		branch = "master"
-	}
-	if goBranch == "tip" {
-		goBranch = "master"
-	}
-	bc, ok := dashboard.Builders[builder]
-	if !ok {
-		// Unknown builder, so not tested.
-		return true
-	}
-	return !bc.BuildsRepoPostSubmit(repo, branch, goBranch)
-}
-
-// knownIssue returns a known issue for the named builder,
-// or zero if there isn't a known issue.
-func knownIssue(builder string) int {
-	bc, ok := dashboard.Builders[builder]
-	if !ok {
-		// Unknown builder.
-		return 0
-	}
-	return bc.KnownIssue
-}
-
-// Results returns the build Results for this Commit.
-func (c *CommitInfo) Results() (results []*Result) {
-	for _, r := range c.ResultData {
-		p := strings.SplitN(r, "|", 4)
-		if len(p) != 4 {
-			continue
-		}
-		results = append(results, partsToResult(c.Hash, c.PackagePath, p))
-	}
-	return
-}
-
-// ResultGoHashes, for non-go repos, returns the list of Go hashes that
-// this repo has been (or should be) built at.
-//
-// For the main Go repo it always returns a slice with 1 element: the
-// empty string.
-func (c *CommitInfo) ResultGoHashes() []string {
-	// For the main repo, just return the empty string
-	// (there's no corresponding main repo hash for a main repo Commit).
-	// This function is only really useful for sub-repos.
-	if c.PackagePath == "" {
-		return []string{""}
-	}
-	var hashes []string
-	for _, r := range c.ResultData {
-		p := strings.SplitN(r, "|", 4)
-		if len(p) != 4 {
-			continue
-		}
-		// Append only new results (use linear scan to preserve order).
-		if !contains(hashes, p[3]) {
-			hashes = append(hashes, p[3])
-		}
-	}
-	// Return results in reverse order (newest first).
-	reverse(hashes)
-	return hashes
-}
-
-func contains(t []string, s string) bool {
-	for _, s2 := range t {
-		if s2 == s {
-			return true
-		}
-	}
-	return false
-}
-
-func reverse(s []string) {
-	for i := 0; i < len(s)/2; i++ {
-		j := len(s) - i - 1
-		s[i], s[j] = s[j], s[i]
-	}
-}
-
-// partsToResult creates a Result from ResultData substrings.
-func partsToResult(hash, packagePath string, p []string) *Result {
-	return &Result{
-		Builder:     p[0],
-		Hash:        hash,
-		PackagePath: packagePath,
-		GoHash:      p[3],
-		OK:          p[1] == "true",
-		LogHash:     p[2],
-	}
-}
-
-// A Result describes a build result for a Commit on an OS/architecture.
-//
-// Each Result entity is a descendant of its associated Package entity.
-type Result struct {
-	Builder     string // "os-arch[-note]"
-	PackagePath string // (empty for Go commits, else "golang.org/x/foo")
-	Hash        string
-
-	// The Go Commit this was built against (when PackagePath != ""; empty for Go commits).
-	GoHash string
-
-	BuildingURL string `datastore:"-"` // non-empty if currently building
-	OK          bool
-	Log         string `datastore:"-"`        // for JSON unmarshaling only
-	LogHash     string `datastore:",noindex"` // Key to the Log record.
-
-	RunTime int64 // time to build+test in nanoseconds
-}
-
-func (r *Result) Key() *datastore.Key {
-	p := Package{Path: r.PackagePath}
-	key := r.Builder + "|" + r.PackagePath + "|" + r.Hash + "|" + r.GoHash
-	return dsKey("Result", key, p.Key())
-}
-
-func (r *Result) Valid() error {
-	if !validHash(r.Hash) {
-		return errors.New("invalid Hash")
-	}
-	if r.PackagePath != "" && !validHash(r.GoHash) {
-		return errors.New("invalid GoHash")
-	}
-	return nil
-}
-
-// Data returns the Result in string format
-// to be stored in Commit's ResultData field.
-func (r *Result) Data() string {
-	return fmt.Sprintf("%v|%v|%v|%v", r.Builder, r.OK, r.LogHash, r.GoHash)
-}
-
-// A Log is a gzip-compressed log file stored under the SHA1 hash of the
-// uncompressed log text.
-type Log struct {
-	CompressedLog []byte `datastore:",noindex"`
-}
-
-func (l *Log) Text() ([]byte, error) {
-	d, err := gzip.NewReader(bytes.NewBuffer(l.CompressedLog))
-	if err != nil {
-		return nil, fmt.Errorf("reading log data: %v", err)
-	}
-	b, err := ioutil.ReadAll(d)
-	if err != nil {
-		return nil, fmt.Errorf("reading log data: %v", err)
-	}
-	return b, nil
-}
-
-func PutLog(c context.Context, text string) (hash string, err error) {
-	b := new(bytes.Buffer)
-	z, _ := gzip.NewWriterLevel(b, gzip.BestCompression)
-	io.WriteString(z, text)
-	z.Close()
-	hash = loghash.New(text)
-	key := dsKey("Log", hash, nil)
-	_, err = datastoreClient.Put(c, key, &Log{b.Bytes()})
-	return
-}
diff --git a/app/appengine/dash.go b/app/appengine/dash.go
deleted file mode 100644
index 691efaa..0000000
--- a/app/appengine/dash.go
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"context"
-	crand "crypto/rand"
-	"crypto/tls"
-	"flag"
-	"fmt"
-	"log"
-	"net/http"
-	"os"
-	"sort"
-	"strings"
-
-	"cloud.google.com/go/datastore"
-	"github.com/NYTimes/gziphandler"
-	"golang.org/x/build/maintner/maintnerd/apipb"
-	"golang.org/x/build/repos"
-	"golang.org/x/net/http2"
-	"grpc.go4.org" // simpler, uses x/net/http2.Transport; we use this elsewhere in x/build
-)
-
-var (
-	maintnerClient  = createMaintnerClient()
-	datastoreClient *datastore.Client // not done at init as createDatastoreClient fails under test environments
-)
-
-var (
-	dev         = flag.Bool("dev", false, "whether to run in local development mode")
-	fakeResults = flag.Bool("fake-results", false, "dev mode option: whether to make up fake random results. If true, datastore is not used.")
-)
-
-func main() {
-	flag.Parse()
-	if *fakeResults && !*dev {
-		log.Fatalf("--fake-results requires --dev mode")
-	}
-	if *dev {
-		randBytes := make([]byte, 20)
-		if _, err := crand.Read(randBytes[:]); err != nil {
-			panic(err)
-		}
-		devModeMasterKey = fmt.Sprintf("%x", randBytes)
-		if !*fakeResults {
-			log.Printf("Running in dev mode. Temporary master key is %v", devModeMasterKey)
-			if os.Getenv("DATASTORE_PROJECT_ID") == "" {
-				log.Printf("DATASTORE_PROJECT_ID not set; defaulting to production golang-org")
-				os.Setenv("DATASTORE_PROJECT_ID", "golang-org")
-			}
-		}
-	}
-
-	datastoreClient = createDatastoreClient()
-
-	if *dev && !*fakeResults {
-		// Test early whether user has datastore access.
-		key := dsKey("Log", "bogus-want-no-such-entity", nil)
-		if err := datastoreClient.Get(context.Background(), key, new(Log)); err != datastore.ErrNoSuchEntity {
-			log.Printf("Failed to access datastore: %v", err)
-			log.Printf("Run with --fake-results to avoid hitting a real datastore.")
-			os.Exit(1)
-		}
-	}
-
-	// authenticated handlers
-	handleFunc("/clear-results", AuthHandler(clearResultsHandler)) // called by x/build/cmd/retrybuilds
-	handleFunc("/result", AuthHandler(resultHandler))              // called by coordinator after build
-
-	// public handlers
-	handleFunc("/", uiHandler)
-	handleFunc("/log/", logHandler)
-
-	// We used to use App Engine's static file handling support, declared in app.yaml,
-	// but it's currently broken with dev_appserver.py with the go111 runtime we use.
-	// So just do it ourselves. It doesn't buy us enough to be worth it.
-	fs := http.StripPrefix("/static", http.FileServer(http.Dir(staticDir())))
-	handleFunc("/static/", fs.ServeHTTP)
-	handleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
-		http.Redirect(w, r, "https://golang.org/favicon.ico", http.StatusFound)
-	})
-
-	listen := os.Getenv("PORT")
-	if listen == "" {
-		listen = "8080"
-	}
-	if !strings.Contains(listen, ":") {
-		listen = ":" + listen
-	}
-
-	log.Printf("Serving dashboard on %s", listen)
-	if err := http.ListenAndServe(listen, nil); err != nil {
-		log.Fatal(err)
-	}
-}
-
-func staticDir() string {
-	if pwd, _ := os.Getwd(); strings.HasSuffix(pwd, "app/appengine") {
-		return "static"
-	}
-	return "app/appengine/static"
-}
-
-func createDatastoreClient() *datastore.Client {
-	if *fakeResults {
-		return nil
-	}
-	// First try with an empty project ID, so $DATASTORE_PROJECT_ID will be respected
-	// if set.
-	c, err := datastore.NewClient(context.Background(), "")
-	if err == nil {
-		return c
-	}
-	// Otherwise auto-detect it from the environment (that is,
-	// work automatically in prod).
-	c, err = datastore.NewClient(context.Background(), datastore.DetectProjectID)
-	if err != nil {
-		log.Fatalf("datastore.NewClient: %v", err)
-	}
-	return c
-}
-
-func createMaintnerClient() apipb.MaintnerServiceClient {
-	addr := os.Getenv("MAINTNER_ADDR") // host[:port]
-	if addr == "" {
-		addr = "maintner.golang.org"
-	}
-	tr := &http.Transport{
-		TLSClientConfig: &tls.Config{
-			NextProtos:         []string{"h2"},
-			InsecureSkipVerify: strings.HasPrefix(addr, "localhost:"),
-		},
-	}
-	hc := &http.Client{Transport: tr}
-	http2.ConfigureTransport(tr)
-
-	cc, err := grpc.NewClient(hc, "https://"+addr)
-	if err != nil {
-		log.Fatal(err)
-	}
-	return apipb.NewMaintnerServiceClient(cc)
-}
-
-func handleFunc(path string, h http.HandlerFunc) {
-	http.Handle(path, hstsHandler(gziphandler.GzipHandler(h)))
-}
-
-// hstsHandler returns a Handler that sets the HSTS header but
-// otherwise just wraps h.
-func hstsHandler(h http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload")
-		h.ServeHTTP(w, r)
-	})
-}
-
-// Dashboard describes a unique build dashboard.
-//
-// (There used to be more than one dashboard, so this is now somewhat
-// less important than it once was.)
-type Dashboard struct {
-	Name     string     // This dashboard's name (always "Go" nowadays)
-	Packages []*Package // The project's packages to build
-}
-
-// packageWithPath returns the Package in d with the provided importPath,
-// or nil if none is found.
-func (d *Dashboard) packageWithPath(importPath string) *Package {
-	for _, p := range d.Packages {
-		if p.Path == importPath {
-			return p
-		}
-	}
-	return nil
-}
-
-// goDash is the dashboard for the main go repository.
-var goDash = &Dashboard{
-	Name: "Go",
-	Packages: []*Package{
-		{Name: "Go"},
-	},
-}
-
-func init() {
-	var add []*Package
-	for _, r := range repos.ByGerritProject {
-		if !r.ShowOnDashboard() {
-			continue
-		}
-		add = append(add, &Package{
-			Name: r.GoGerritProject,
-			Path: r.ImportPath,
-		})
-	}
-	sort.Slice(add, func(i, j int) bool {
-		return add[i].Name < add[j].Name
-	})
-	goDash.Packages = append(goDash.Packages, add...)
-}
diff --git a/app/appengine/handler.go b/app/appengine/handler.go
deleted file mode 100644
index 0283bd6..0000000
--- a/app/appengine/handler.go
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"context"
-	"crypto/hmac"
-	"crypto/md5"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"html"
-	"log"
-	"net/http"
-	"strconv"
-	"strings"
-	"unicode/utf8"
-
-	"cloud.google.com/go/datastore"
-	"golang.org/x/build/app/key"
-)
-
-const (
-	commitsPerPage = 30
-	builderVersion = 1 // must match x/build/cmd/coordinator/dash.go's value
-)
-
-// resultHandler records a build result.
-// It reads a JSON-encoded Result value from the request body,
-// creates a new Result entity, and creates or updates the relevant Commit entity.
-// If the Log field is not empty, resultHandler creates a new Log entity
-// and updates the LogHash field before putting the Commit entity.
-func resultHandler(r *http.Request) (interface{}, error) {
-	if r.Method != "POST" {
-		return nil, errBadMethod(r.Method)
-	}
-
-	v, _ := strconv.Atoi(r.FormValue("version"))
-	if v != builderVersion {
-		return nil, fmt.Errorf("rejecting POST from builder; need version %v instead of %v",
-			builderVersion, v)
-	}
-
-	ctx := r.Context()
-	res := new(Result)
-	defer r.Body.Close()
-	if err := json.NewDecoder(r.Body).Decode(res); err != nil {
-		return nil, fmt.Errorf("decoding Body: %v", err)
-	}
-	if err := res.Valid(); err != nil {
-		return nil, fmt.Errorf("validating Result: %v", err)
-	}
-	// store the Log text if supplied
-	if len(res.Log) > 0 {
-		hash, err := PutLog(ctx, res.Log)
-		if err != nil {
-			return nil, fmt.Errorf("putting Log: %v", err)
-		}
-		res.LogHash = hash
-	}
-	tx := func(tx *datastore.Transaction) error {
-		if _, err := getOrMakePackageInTx(ctx, tx, res.PackagePath); err != nil {
-			return fmt.Errorf("GetPackage: %v", err)
-		}
-		// put Result
-		if _, err := tx.Put(res.Key(), res); err != nil {
-			return fmt.Errorf("putting Result: %v", err)
-		}
-		// add Result to Commit
-		com := &Commit{PackagePath: res.PackagePath, Hash: res.Hash}
-		if err := com.AddResult(tx, res); err != nil {
-			return fmt.Errorf("AddResult: %v", err)
-		}
-		return nil
-	}
-	_, err := datastoreClient.RunInTransaction(ctx, tx)
-	return nil, err
-}
-
-// logHandler displays log text for a given hash.
-// It handles paths like "/log/hash".
-func logHandler(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Content-type", "text/plain; charset=utf-8")
-	c := r.Context()
-	hash := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
-	key := dsKey("Log", hash, nil)
-	l := new(Log)
-	if err := datastoreClient.Get(c, key, l); err != nil {
-		if err == datastore.ErrNoSuchEntity {
-			// Fall back to default namespace;
-			// maybe this was on the old dashboard.
-			key.Namespace = ""
-			err = datastoreClient.Get(c, key, l)
-		}
-		if err != nil {
-			logErr(w, r, err)
-			return
-		}
-	}
-	b, err := l.Text()
-	if err != nil {
-		logErr(w, r, err)
-		return
-	}
-	w.Write(b)
-}
-
-// clearResultsHandler purge a single build failure from the dashboard.
-// It currently only supports the main Go repo.
-func clearResultsHandler(r *http.Request) (interface{}, error) {
-	if r.Method != "POST" {
-		return nil, errBadMethod(r.Method)
-	}
-	builder := r.FormValue("builder")
-	hash := r.FormValue("hash")
-	if builder == "" {
-		return nil, errors.New("missing 'builder'")
-	}
-	if hash == "" {
-		return nil, errors.New("missing 'hash'")
-	}
-
-	ctx := r.Context()
-
-	_, err := datastoreClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
-		c := &Commit{
-			PackagePath: "", // TODO(adg): support clearing sub-repos
-			Hash:        hash,
-		}
-		err := tx.Get(c.Key(), c)
-		err = filterDatastoreError(err)
-		if err == datastore.ErrNoSuchEntity {
-			// Doesn't exist, so no build to clear.
-			return nil
-		}
-		if err != nil {
-			return err
-		}
-
-		r := c.Result(builder, "")
-		if r == nil {
-			// No result, so nothing to clear.
-			return nil
-		}
-		c.RemoveResult(r)
-		_, err = tx.Put(c.Key(), c)
-		if err != nil {
-			return err
-		}
-		return tx.Delete(r.Key())
-	})
-	return nil, err
-}
-
-type dashHandler func(*http.Request) (interface{}, error)
-
-type dashResponse struct {
-	Response interface{}
-	Error    string
-}
-
-// errBadMethod is returned by a dashHandler when
-// the request has an unsuitable method.
-type errBadMethod string
-
-func (e errBadMethod) Error() string {
-	return "bad method: " + string(e)
-}
-
-func builderKeyRevoked(builder string) bool {
-	switch builder {
-	case "plan9-amd64-mischief":
-		// Broken and unmaintained for months.
-		// It's polluting the dashboard.
-		return true
-	case "linux-arm-onlinenet":
-		// Requested to be revoked by Dave Cheney.
-		// The machine is in a fail+report loop
-		// and can't be accessed. Revoke it for now.
-		return true
-	}
-	return false
-}
-
-// AuthHandler wraps a http.HandlerFunc with a handler that validates the
-// supplied key and builder query parameters.
-func AuthHandler(h dashHandler) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		c := r.Context()
-
-		// Put the URL Query values into r.Form to avoid parsing the
-		// request body when calling r.FormValue.
-		r.Form = r.URL.Query()
-
-		var err error
-		var resp interface{}
-
-		// Validate key query parameter for POST requests only.
-		key := r.FormValue("key")
-		builder := r.FormValue("builder")
-		if r.Method == "POST" && !validKey(c, key, builder) {
-			err = fmt.Errorf("invalid key %q for builder %q", key, builder)
-		}
-
-		// Call the original HandlerFunc and return the response.
-		if err == nil {
-			resp, err = h(r)
-		}
-
-		// Write JSON response.
-		dashResp := &dashResponse{Response: resp}
-		if err != nil {
-			log.Printf("%v", err)
-			dashResp.Error = err.Error()
-		}
-		w.Header().Set("Content-Type", "application/json")
-		if err = json.NewEncoder(w).Encode(dashResp); err != nil {
-			log.Printf("encoding response: %v", err)
-		}
-	}
-}
-
-// validHash reports whether hash looks like a valid git commit hash.
-func validHash(hash string) bool {
-	// TODO: correctly validate a hash: check that it's exactly 40
-	// lowercase hex digits. But this is what we historically did:
-	return hash != ""
-}
-
-func validKey(c context.Context, key, builder string) bool {
-	if isMasterKey(c, key) {
-		return true
-	}
-	if builderKeyRevoked(builder) {
-		return false
-	}
-	return key == builderKey(c, builder)
-}
-
-var devModeMasterKey string
-
-func masterKey(ctx context.Context) string {
-	if *dev {
-		return devModeMasterKey
-	}
-	return key.Secret(ctx, datastoreClient)
-}
-
-func isMasterKey(ctx context.Context, k string) bool {
-	return k == masterKey(ctx)
-}
-
-func builderKey(ctx context.Context, builder string) string {
-	h := hmac.New(md5.New, []byte(masterKey(ctx)))
-	h.Write([]byte(builder))
-	return fmt.Sprintf("%x", h.Sum(nil))
-}
-
-func logErr(w http.ResponseWriter, r *http.Request, err error) {
-	log.Printf("Error: %v", err)
-	w.WriteHeader(http.StatusInternalServerError)
-	fmt.Fprint(w, "Error: ", html.EscapeString(err.Error()))
-}
-
-// limitStringLength essentially does return s[:max],
-// but it ensures that we dot not split UTF-8 rune in half.
-// Otherwise appengine python scripts will break badly.
-func limitStringLength(s string, max int) string {
-	if len(s) <= max {
-		return s
-	}
-	for {
-		s = s[:max]
-		r, size := utf8.DecodeLastRuneInString(s)
-		if r != utf8.RuneError || size != 1 {
-			return s
-		}
-		max--
-	}
-}
diff --git a/app/appengine/static/status_alert.gif b/app/appengine/static/status_alert.gif
deleted file mode 100644
index 495d9d2..0000000
--- a/app/appengine/static/status_alert.gif
+++ /dev/null
Binary files differ
diff --git a/app/appengine/static/status_good.gif b/app/appengine/static/status_good.gif
deleted file mode 100644
index ef9c5a8..0000000
--- a/app/appengine/static/status_good.gif
+++ /dev/null
Binary files differ
diff --git a/app/appengine/static/style.css b/app/appengine/static/style.css
deleted file mode 100644
index 34c6603..0000000
--- a/app/appengine/static/style.css
+++ /dev/null
@@ -1,317 +0,0 @@
-* { box-sizing: border-box; }
-
-.dashboards {
-  padding: 0.5em;
-}
-.dashboards > a {
-  padding: 0.5em;
-  background: #eee;
-  color: blue;
-}
-
-body {
-  margin: 0;
-  font-family: sans-serif;
-  padding: 0; margin: 0;
-  color: #222;
-  display: inline-block;
-  min-width: 100%;
-}
-
-.container {
-  max-width: 900px;
-  margin: 0 auto;
-}
-
-p, pre, ul, ol { margin: 20px; }
-
-h1, h2, h3, h4 {
-  margin: 20px 0;
-  padding: 0;
-  color: #375EAB;
-  font-weight: bold;
-}
- 
-h1 { font-size: 24px; }
-h2 { font-size: 20px; }
-h3 { font-size: 20px; }
-h4 { font-size: 16px; }
- 
-h2 { background: #E0EBF5; padding: 2px 5px; }
-h3, h4 { margin: 20px 5px; }
- 
-dl, dd { font-size: 14px; }
-dl { margin: 20px; }
-dd { margin: 2px 20px; }
- 
-.clear {
-  clear: both;
-}
- 
-.button {
-  padding: 10px;
-  
-  color: #222;
-  border: 1px solid #375EAB;
-  background: #E0EBF5;
-  
-  border-radius: 5px;
-  
-  cursor: pointer;
-  
-  margin-left: 60px;
-}
- 
-/* navigation bar */
- 
-#topbar {
-  padding: 10px 10px;
-  background: #E0EBF5;
-}
- 
-#topbar a {
-  color: #222;
-}
-#topbar h1 {
-  float: left;
-  margin: 0;
-  padding-top: 5px;
-}
- 
-#topbar nav { 
-  float: left;
-  margin-left: 20px;
-}
-#topbar nav a {
-  display: inline-block;
-  padding: 10px;
-  
-  margin: 0;
-  margin-right: 5px;
- 
-  color: white;
-  background: #375EAB;
-    
-  text-decoration: none;
-  font-size: 16px;
-    
-  border: 1px solid #375EAB;
-  -webkit-border-radius: 5px;
-  -moz-border-radius: 5px;
-  border-radius: 5px;
-}
- 
-.page {
-  margin-top: 20px;
-}
- 
-/* settings panels */
-aside {
-  margin-top: 5px;
-}
- 
-.panel {
-  border: 1px solid #aaa;
-  border-radius: 5px;
-  margin-bottom: 5px;
-}
- 
-.panel h1 {
-  font-size: 16px;
-  margin: 0;
-  padding: 2px 8px;
-}
- 
-.panel select {
-  padding: 5px;
-  border: 0;
-  width: 100%;
-}
- 
-/* results table */
- 
-table {
-  margin: 5px;
-  border-collapse: collapse;
-  font-size: 11px;
-}
- 
-table td, table th, table td, table th {
-  vertical-align: top;
-  padding: 2px 6px;
-}
- 
-table tr:nth-child(2n+1) {
-  background: #F4F4F4;
-}
-
-table tr.commit:hover {
-  background-color: #ffff99 !important;
-}
- 
-table thead tr {
-  background: #fff !important;
-}
- 
-/* build results */
- 
-.build td, .build th, .packages td, .packages th {
-  vertical-align: top;
-  padding: 2px 4px;
-  font-size: 10pt;
-}
-      
-.build .hash {
-  font-family: monospace;
-  font-size: 9pt;
-}
- 
-.build .result {
-  text-align: center;
-  width: 2em;
-}
- 
-.build .col-desc, .build .col-result, .build .col-metric, .build .col-numresults {
-  border-right: 1px solid #ccc;
-}
-
-.build .row-commit {
-  border-top: 2px solid #ccc;
-}
-
-.build .arch {
-  font-size: 83%;
-  font-weight: normal;
-}
-
-.build .time {
-  color: #666;
-}
-
-.build .ok {
-  font-size: 83%;
-}
-
-.build .desc, .build .time, .build .user {
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-}
- 
-.build .desc {
-  max-width: 150px;
-}
-
-.build .user {
-  max-width: 50px;
-}
-
-tr.subheading2 th {
-  max-width: 4em;
-  overflow: hidden;
-  word-wrap: none;
-}
- 
-.good   { text-decoration: none; color: #000000; border: 2px solid #00E700}
-.bad    { text-decoration: none; text-shadow: 1px 1px 0 #000000; color: #FFFFFF; background: #E70000;}
-.noise  { text-decoration: none; color: #888; }
-.fail   { color: #C00; }
-
-/* pagination */
- 
-.paginate nav {
-  padding: 0.5em;
-  margin: 10px 0;
-}
- 
-.paginate nav a {
-  padding: 0.5em;
-  background: #E0EBF5;
-  color: blue;
-
-  -webkit-border-radius: 5px;
-  -moz-border-radius: 5px;
-  border-radius: 5px;
-}
- 
-.paginate nav a.inactive {
-  color: #888;
-  cursor: default;
-  text-decoration: none;
-}
-
-/* diffs */
- 
-.diff-meta {
-  font-family: monospace;
-  margin-bottom: 10px;
-}
- 
-.diff-container {
-  padding: 10px;
-}
- 
-.diff table .metric {
-  font-weight: bold;
-}
- 
-.diff {
-  border: 1px solid #aaa;
-  border-radius: 5px;
-  margin-bottom: 5px;
-  margin-right: 10px;
-  float: left;
-}
- 
-.diff h1 {
-  font-size: 16px;
-  margin: 0;
-  padding: 2px 8px;
-}
-
-/* positioning elements */
- 
-.page {
-  position: relative;
-  width: 100%;
-}
- 
-aside {
-  position: absolute;
-  top: 0;
-  left: 0;
-  bottom: 0;
-  width: 200px;
-}
- 
-.main-content {
-  position: absolute;
-  top: 0;
-  left: 210px;
-  right: 5px;
-  min-height: 200px;
-  overflow: hidden;
-}
- 
-@media only screen and (max-width: 900px) {
-  aside {
-    position: relative;
-    display: block;
-    width: auto;
-  }
- 
-  .main-content {
-    position: static;
-    padding: 0;
-  }
-  
-  aside .panel {
-    float: left;
-    width: auto;
-    margin-right: 5px;
-  }
-  aside .button {
-    float: left;
-    margin: 0;
-  }
-}
diff --git a/app/appengine/ui.go b/app/appengine/ui.go
deleted file mode 100644
index 0083a3c..0000000
--- a/app/appengine/ui.go
+++ /dev/null
@@ -1,972 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"html/template"
-	"log"
-	"net/http"
-	"os"
-	"path/filepath"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-
-	"cloud.google.com/go/datastore"
-	"golang.org/x/build/dashboard"
-	"golang.org/x/build/maintner/maintnerd/apipb"
-	"golang.org/x/build/repos"
-	"golang.org/x/build/types"
-	"golang.org/x/sync/errgroup"
-	"grpc.go4.org"
-	"grpc.go4.org/codes"
-)
-
-// uiHandler is the HTTP handler for the https://build.golang.org/.
-func uiHandler(w http.ResponseWriter, r *http.Request) {
-	view, err := viewForRequest(r)
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusBadRequest)
-		return
-	}
-
-	dashReq, err := dashboardRequest(view, r)
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusBadRequest)
-		return
-	}
-
-	ctx := r.Context()
-	tb := &uiTemplateDataBuilder{
-		view: view,
-		req:  dashReq,
-	}
-	var rpcs errgroup.Group
-	rpcs.Go(func() error {
-		var err error
-		tb.res, err = maintnerClient.GetDashboard(ctx, dashReq)
-		return err
-	})
-	if view.ShowsActiveBuilds() {
-		rpcs.Go(func() error {
-			tb.activeBuilds = getActiveBuilds(ctx)
-			return nil
-		})
-	}
-	if err := rpcs.Wait(); err != nil {
-		http.Error(w, "maintner.GetDashboard: "+err.Error(), httpStatusOfErr(err))
-		return
-	}
-	data, err := tb.buildTemplateData(ctx)
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-	view.ServeDashboard(w, r, data)
-}
-
-// dashboardView is something that can render uiTemplateData.
-// See viewForRequest.
-type dashboardView interface {
-	ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData)
-	// ShowsActiveBuilds reports whether this view uses
-	// information about the currently active builds.
-	ShowsActiveBuilds() bool
-}
-
-// viewForRequest selects the dashboardView based on the HTTP
-// request's "mode" parameter. Any error should be considered
-// an HTTP 400 Bad Request.
-func viewForRequest(r *http.Request) (dashboardView, error) {
-	if r.Method != "GET" && r.Method != "HEAD" {
-		return nil, errors.New("unsupported method")
-	}
-	switch r.FormValue("mode") {
-	case "failures":
-		return failuresView{}, nil
-	case "json":
-		return jsonView{}, nil
-	case "":
-		return htmlView{}, nil
-	}
-	return nil, errors.New("unsupported mode argument")
-}
-
-type commitInPackage struct {
-	packagePath string // "" for Go, else package import path
-	commit      string // git commit hash
-}
-
-// uiTemplateDataBuilder builds the uiTemplateData used by the various
-// dashboardViews. That is, it maps the maintner protobuf response to
-// the data structure needed by the dashboardView/template.
-type uiTemplateDataBuilder struct {
-	view         dashboardView
-	req          *apipb.DashboardRequest
-	res          *apipb.DashboardResponse
-	activeBuilds []types.ActivePostSubmitBuild // optional; for blue gopher links
-
-	// testCommitData, if non-nil, provides an alternate data
-	// source to use for testing instead of making real datastore
-	// calls. The keys are stringified datastore.Keys.
-	testCommitData map[string]*Commit
-}
-
-// getCommitsToLoad returns a set (all values are true) of which commits to load from
-// the datastore.
-func (tb *uiTemplateDataBuilder) getCommitsToLoad() map[commitInPackage]bool {
-	if *fakeResults {
-		return nil
-	}
-	m := make(map[commitInPackage]bool)
-	add := func(packagePath, commit string) {
-		m[commitInPackage{packagePath: packagePath, commit: commit}] = true
-	}
-
-	for _, dc := range tb.res.Commits {
-		add(tb.req.Repo, dc.Commit)
-	}
-	// We also want to load the Commits for the x/repo heads.
-	if tb.showXRepoSection() {
-		for _, rh := range tb.res.RepoHeads {
-			if path := repoImportPath(rh); path != "" {
-				add(path, rh.Commit.Commit)
-			}
-		}
-	}
-	return m
-}
-
-// loadDatastoreCommits loads the commits given in the keys of the
-// want map. The returned map is keyed by the git hash and may not
-// contain items that didn't exist in the datastore. (It is not an
-// error if 1 or all don't exist.)
-func (tb *uiTemplateDataBuilder) loadDatastoreCommits(ctx context.Context, want map[commitInPackage]bool) (map[string]*Commit, error) {
-	ret := map[string]*Commit{}
-
-	// Allow tests to fake what the datastore would've loaded, and
-	// thus also allow tests to be run without a real (or
-	// dev_appserver-based fake) datastore.
-	if m := tb.testCommitData; m != nil {
-		for k := range want {
-			if c, ok := m[k.commit]; ok {
-				ret[k.commit] = c
-			}
-		}
-		return ret, nil
-	}
-
-	var keys []*datastore.Key
-	for k := range want {
-		key := (&Commit{
-			PackagePath: k.packagePath,
-			Hash:        k.commit,
-		}).Key()
-		keys = append(keys, key)
-	}
-	commits, err := fetchCommits(ctx, keys)
-	if err != nil {
-		return nil, fmt.Errorf("fetchCommits: %v", err)
-	}
-	for _, c := range commits {
-		ret[c.Hash] = c
-	}
-	return ret, nil
-}
-
-// formatGitAuthor formats the git author name and email (as split by
-// maintner) back into the unified string how they're stored in a git
-// commit, so the shortUser func (used by the HTML template) can parse
-// back out the email part's username later. Maybe we could plumb down
-// the parsed proto into the template later.
-func formatGitAuthor(name, email string) string {
-	name = strings.TrimSpace(name)
-	email = strings.TrimSpace(email)
-	if name != "" && email != "" {
-		return fmt.Sprintf("%s <%s>", name, email)
-	}
-	if name != "" {
-		return name
-	}
-	return "<" + email + ">"
-}
-
-// newCommitInfo returns a new CommitInfo populated for the template
-// data given a repo name and a dashboard commit from that repo, using
-// previously loaded datastore commit info in tb.
-func (tb *uiTemplateDataBuilder) newCommitInfo(dsCommits map[string]*Commit, repo string, dc *apipb.DashCommit) *CommitInfo {
-	branch := dc.Branch
-	if branch == "" {
-		branch = "master"
-	}
-	ci := &CommitInfo{
-		Hash:        dc.Commit,
-		PackagePath: repo,
-		User:        formatGitAuthor(dc.AuthorName, dc.AuthorEmail),
-		Desc:        cleanTitle(dc.Title, tb.req.Branch),
-		Time:        time.Unix(dc.CommitTimeSec, 0),
-		Branch:      branch,
-	}
-	if dsc, ok := dsCommits[dc.Commit]; ok {
-		ci.ResultData = dsc.ResultData
-	}
-	// For non-go repos, add the rows for the Go commits that were
-	// at HEAD overlapping in time with dc.Commit.
-	if !tb.isGoRepo() {
-		if dc.GoCommitAtTime != "" {
-			ci.addEmptyResultGoHash(dc.GoCommitAtTime)
-		}
-		if dc.GoCommitLatest != "" && dc.GoCommitLatest != dc.GoCommitAtTime {
-			ci.addEmptyResultGoHash(dc.GoCommitLatest)
-		}
-	}
-	return ci
-}
-
-// showXRepoSection reports whether the dashboard should show the state of the x/foo repos at the bottom of
-// the page in the three branches (master, latest release branch, two releases ago).
-func (tb *uiTemplateDataBuilder) showXRepoSection() bool {
-	return tb.req.Page == 0 &&
-		(tb.branch() == "master" || tb.req.Branch == "mixed") &&
-		tb.isGoRepo()
-}
-
-func (tb *uiTemplateDataBuilder) isGoRepo() bool { return tb.req.Repo == "" || tb.req.Repo == "go" }
-
-// repoGerritProj returns the Gerrit project name on go.googlesource.com for
-// the repo requested, or empty if unknown.
-func (tb *uiTemplateDataBuilder) repoGerritProj() string {
-	if tb.isGoRepo() {
-		return "go"
-	}
-	if r, ok := repos.ByImportPath[tb.req.Repo]; ok {
-		return r.GoGerritProject
-	}
-	return ""
-}
-
-// branch returns the request branch, or "master" if empty.
-func (tb *uiTemplateDataBuilder) branch() string {
-	if tb.req.Branch == "" {
-		return "master"
-	}
-	return tb.req.Branch
-}
-
-// repoImportPath returns the import path for rh, unless rh is the
-// main "go" repo or is configured to be hidden from the dashboard, in
-// which case it returns the empty string.
-func repoImportPath(rh *apipb.DashRepoHead) string {
-	if rh.GerritProject == "go" {
-		return ""
-	}
-	ri, ok := repos.ByGerritProject[rh.GerritProject]
-	if !ok || !ri.ShowOnDashboard() {
-		return ""
-	}
-	return ri.ImportPath
-}
-
-func (tb *uiTemplateDataBuilder) buildTemplateData(ctx context.Context) (*uiTemplateData, error) {
-	dsCommits, err := tb.loadDatastoreCommits(ctx, tb.getCommitsToLoad())
-	if err != nil {
-		return nil, err
-	}
-
-	var commits []*CommitInfo
-	for _, dc := range tb.res.Commits {
-		ci := tb.newCommitInfo(dsCommits, tb.req.Repo, dc)
-		commits = append(commits, ci)
-	}
-
-	// x/ repo sections at bottom (each is a "TagState", for historical reasons)
-	var xRepoSections []*TagState
-	if tb.showXRepoSection() {
-		for _, gorel := range tb.res.Releases {
-			ts := &TagState{
-				Name: gorel.BranchName,
-				Tag: &CommitInfo{ // only a minimally populated version is needed by the template
-					Hash: gorel.BranchCommit,
-				},
-			}
-			for _, rh := range tb.res.RepoHeads {
-				path := repoImportPath(rh)
-				if path == "" {
-					continue
-				}
-				ts.Packages = append(ts.Packages, &PackageState{
-					Package: &Package{
-						Name: rh.GerritProject,
-						Path: path,
-					},
-					Commit: tb.newCommitInfo(dsCommits, path, rh.Commit),
-				})
-			}
-			builders := map[string]bool{}
-			for _, pkg := range ts.Packages {
-				addBuilders(builders, pkg.Package.Name, ts.Branch())
-			}
-			ts.Builders = builderKeys(builders)
-
-			sort.Slice(ts.Packages, func(i, j int) bool {
-				return ts.Packages[i].Package.Name < ts.Packages[j].Package.Name
-			})
-			xRepoSections = append(xRepoSections, ts)
-		}
-	}
-
-	// Release Branches
-	var releaseBranches []string
-	for _, gr := range tb.res.Releases {
-		if gr.BranchName != "master" {
-			releaseBranches = append(releaseBranches, gr.BranchName)
-		}
-	}
-
-	gerritProject := "go"
-	if repo := repos.ByImportPath[tb.req.Repo]; repo != nil {
-		gerritProject = repo.GoGerritProject
-	}
-
-	data := &uiTemplateData{
-		Dashboard:  goDash,
-		Package:    goDash.packageWithPath(tb.req.Repo),
-		Commits:    commits,
-		TagState:   xRepoSections,
-		Pagination: &Pagination{},
-		Branches:   tb.res.Branches,
-		Branch:     tb.req.Branch,
-		Repo:       gerritProject,
-	}
-
-	builders := buildersOfCommits(commits)
-	if tb.branch() == "mixed" {
-		for _, gr := range tb.res.Releases {
-			addBuilders(builders, tb.repoGerritProj(), gr.BranchName)
-		}
-	} else {
-		addBuilders(builders, tb.repoGerritProj(), tb.branch())
-	}
-	data.Builders = builderKeys(builders)
-
-	if tb.res.CommitsTruncated {
-		data.Pagination.Next = int(tb.req.Page) + 1
-	}
-	if tb.req.Page > 0 {
-		data.Pagination.Prev = int(tb.req.Page) - 1
-		data.Pagination.HasPrev = true
-	}
-
-	if tb.view.ShowsActiveBuilds() {
-		// Populate building URLs for the HTML UI only.
-		data.populateBuildingURLs(ctx, tb.activeBuilds)
-	}
-
-	return data, nil
-}
-
-// htmlView renders the HTML (default) form of https://build.golang.org/ with no mode parameter.
-type htmlView struct{}
-
-func (htmlView) ShowsActiveBuilds() bool { return true }
-func (htmlView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
-	var buf bytes.Buffer
-	if err := uiTemplate.Execute(&buf, data); err != nil {
-		logErr(w, r, err)
-		return
-	}
-	buf.WriteTo(w)
-}
-
-// dashboardRequest is a pure function that maps the provided HTTP
-// request to a maintner DashboardRequest and lightly validates the
-// HTTP request for the root dashboard handler. (It does not validate
-// that, say, branches or repos are valid.)
-// Any returned error is an HTTP 400 Bad Request.
-func dashboardRequest(view dashboardView, r *http.Request) (*apipb.DashboardRequest, error) {
-	page := 0
-	if s := r.FormValue("page"); s != "" {
-		var err error
-		page, err = strconv.Atoi(r.FormValue("page"))
-		if err != nil {
-			return nil, fmt.Errorf("invalid page value %q", s)
-		}
-		if page < 0 {
-			return nil, errors.New("negative page")
-		}
-	}
-
-	repo := r.FormValue("repo") // empty for main go repo, else e.g. "golang.org/x/net"
-
-	branch := r.FormValue("branch")
-	if branch == "" {
-		branch = "master"
-	}
-	return &apipb.DashboardRequest{
-		Page:       int32(page),
-		Branch:     branch,
-		Repo:       repo,
-		MaxCommits: commitsPerPage,
-	}, nil
-}
-
-// cleanTitle returns a cleaned version of the provided title for
-// users viewing the provided viewBranch.
-func cleanTitle(title, viewBranch string) string {
-	// Don't rewrite anything for master and mixed.
-	if viewBranch == "master" || viewBranch == "mixed" {
-		return title
-	}
-	// Strip the "[release-branch.go1.n]" prefixes from commit messages
-	// when looking at a branch.
-	if strings.HasPrefix(title, "[") {
-		if i := strings.IndexByte(title, ']'); i != -1 {
-			return strings.TrimSpace(title[i+1:])
-		}
-	}
-	return title
-}
-
-// failuresView renders https://build.golang.org/?mode=failures, where it outputs
-// one line per failure on the front page, in the form:
-//    hash builder failure-url
-type failuresView struct{}
-
-func (failuresView) ShowsActiveBuilds() bool { return false }
-func (failuresView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
-	w.Header().Set("Content-Type", "text/plain")
-	for _, c := range data.Commits {
-		for _, b := range data.Builders {
-			res := c.Result(b, "")
-			if res == nil || res.OK || res.LogHash == "" {
-				continue
-			}
-			url := fmt.Sprintf("https://%v/log/%v", r.Host, res.LogHash)
-			fmt.Fprintln(w, c.Hash, b, url)
-		}
-	}
-	// TODO: this doesn't include the TagState commit. It would be
-	// needed if we want to do golang.org/issue/36131, to permit
-	// the retrybuilds command to wipe flaky non-go builds.
-}
-
-// jsonView renders https://build.golang.org/?mode=json.
-// The output is a types.BuildStatus JSON object.
-type jsonView struct{}
-
-func (jsonView) ShowsActiveBuilds() bool { return false }
-func (jsonView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
-	res := toBuildStatus(r.Host, data)
-	v, _ := json.MarshalIndent(res, "", "\t")
-	w.Header().Set("Content-Type", "text/json; charset=utf-8")
-	w.Write(v)
-}
-
-func toBuildStatus(host string, data *uiTemplateData) types.BuildStatus {
-	// cell returns one of "" (no data), "ok", or a failure URL.
-	cell := func(res *Result) string {
-		switch {
-		case res == nil:
-			return ""
-		case res.OK:
-			return "ok"
-		}
-		return fmt.Sprintf("https://%v/log/%v", host, res.LogHash)
-	}
-
-	builders := data.allBuilders()
-
-	var res types.BuildStatus
-	res.Builders = builders
-
-	// First the commits from the main section (the requested repo)
-	for _, c := range data.Commits {
-		// The logic below works for both the go repo and other subrepos: if c is
-		// in the main go repo, ResultGoHashes returns a slice of length 1
-		// containing the empty string.
-		for _, h := range c.ResultGoHashes() {
-			rev := types.BuildRevision{
-				Repo:       data.Repo,
-				Results:    make([]string, len(res.Builders)),
-				GoRevision: h,
-			}
-			commitToBuildRevision(c, &rev)
-			for i, b := range res.Builders {
-				rev.Results[i] = cell(c.Result(b, h))
-			}
-			res.Revisions = append(res.Revisions, rev)
-		}
-	}
-
-	// Then the one commit each for the subrepos for each of the tracked tags.
-	// (tip, Go 1.4, etc)
-	for _, ts := range data.TagState {
-		for _, pkgState := range ts.Packages {
-			goRev := ts.Tag.Hash
-			goBranch := ts.Name
-			if goBranch == "tip" {
-				// Normalize old hg terminology into
-				// our git branch name.
-				goBranch = "master"
-			}
-			rev := types.BuildRevision{
-				Repo:       pkgState.Package.Name,
-				GoRevision: goRev,
-				Results:    make([]string, len(res.Builders)),
-				GoBranch:   goBranch,
-			}
-			commitToBuildRevision(pkgState.Commit, &rev)
-			for i, b := range res.Builders {
-				rev.Results[i] = cell(pkgState.Commit.Result(b, goRev))
-			}
-			res.Revisions = append(res.Revisions, rev)
-		}
-	}
-	return res
-}
-
-// commitToBuildRevision fills in the fields of BuildRevision rev that
-// are derived from Commit c.
-func commitToBuildRevision(c *CommitInfo, rev *types.BuildRevision) {
-	rev.Revision = c.Hash
-	rev.Date = c.Time.Format(time.RFC3339)
-	rev.Author = c.User
-	rev.Desc = c.Desc
-	rev.Branch = c.Branch
-}
-
-type Pagination struct {
-	Next, Prev int
-	HasPrev    bool
-}
-
-// fetchCommits loads any commits that exist given by keys.
-// It is not an error if a commit doesn't exist.
-// Only commits that were found in datastore are returned,
-// in an unspecified order.
-func fetchCommits(ctx context.Context, keys []*datastore.Key) ([]*Commit, error) {
-	if len(keys) == 0 {
-		return nil, nil
-	}
-	out := make([]*Commit, len(keys))
-	for i := range keys {
-		out[i] = new(Commit)
-	}
-
-	err := datastoreClient.GetMulti(ctx, keys, out)
-	err = filterDatastoreError(err)
-	err = filterNoSuchEntity(err)
-	if err != nil {
-		return nil, err
-	}
-	filtered := out[:0]
-	for _, c := range out {
-		if c.Valid() { // that is, successfully loaded
-			filtered = append(filtered, c)
-		}
-	}
-	return filtered, nil
-}
-
-// buildersOfCommits returns the set of builders that provided
-// Results for the provided commits.
-func buildersOfCommits(commits []*CommitInfo) map[string]bool {
-	m := make(map[string]bool)
-	for _, commit := range commits {
-		for _, r := range commit.Results() {
-			if r.Builder != "" {
-				m[r.Builder] = true
-			}
-		}
-	}
-	return m
-}
-
-// addBuilders adds builders to the provide map that should be active for
-// the named Gerrit project & branch. (Issue 19930)
-func addBuilders(builders map[string]bool, gerritProj, branch string) {
-	for name, bc := range dashboard.Builders {
-		if bc.BuildsRepoPostSubmit(gerritProj, branch, branch) {
-			builders[name] = true
-		}
-	}
-}
-
-func builderKeys(m map[string]bool) (s []string) {
-	s = make([]string, 0, len(m))
-	for k := range m {
-		s = append(s, k)
-	}
-	sort.Sort(builderOrder(s))
-	return
-}
-
-// builderOrder implements sort.Interface, sorting builder names
-// ("darwin-amd64", etc) first by builderPriority and then alphabetically.
-type builderOrder []string
-
-func (s builderOrder) Len() int      { return len(s) }
-func (s builderOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s builderOrder) Less(i, j int) bool {
-	pi, pj := builderPriority(s[i]), builderPriority(s[j])
-	if pi == pj {
-		return s[i] < s[j]
-	}
-	return pi < pj
-}
-
-func builderPriority(builder string) (p int) {
-	// Group race builders together.
-	if isRace(builder) {
-		return 2
-	}
-	// If the OS has a specified priority, use it.
-	if p, ok := osPriority[builderOS(builder)]; ok {
-		return p
-	}
-	// The rest.
-	return 10
-}
-
-func isRace(s string) bool {
-	return strings.Contains(s, "-race-") || strings.HasSuffix(s, "-race")
-}
-
-func unsupported(builder string) bool {
-	return unsupportedOS(builderOS(builder))
-}
-
-func unsupportedOS(os string) bool {
-	if os == "race" || os == "android" || os == "all" {
-		return false
-	}
-	p, ok := osPriority[os]
-	return !ok || p > 1
-}
-
-// Priorities for specific operating systems.
-var osPriority = map[string]int{
-	"all":     0,
-	"darwin":  1,
-	"freebsd": 1,
-	"linux":   1,
-	"windows": 1,
-	// race == 2
-	"android":   3,
-	"openbsd":   4,
-	"netbsd":    5,
-	"dragonfly": 6,
-}
-
-// TagState represents the state of all Packages at a branch.
-type TagState struct {
-	Name     string      // Go branch name: "master", "release-branch.go1.4", etc
-	Tag      *CommitInfo // current Go commit on the Name branch
-	Packages []*PackageState
-	Builders []string
-}
-
-// Branch returns the git branch name, converting from the old
-// terminology we used from Go's hg days into git terminology.
-func (ts *TagState) Branch() string {
-	if ts.Name == "tip" {
-		return "master"
-	}
-	return ts.Name
-}
-
-// PackageState represents the state of a Package (x/foo repo) for given Go branch.
-type PackageState struct {
-	Package *Package
-	Commit  *CommitInfo
-}
-
-// A CommitInfo is a struct for use by html/template package.
-// It is not stored in the datastore.
-type CommitInfo struct {
-	Hash string
-
-	// ResultData is a copy of the Commit.ResultData field from datastore.
-	ResultData []string
-
-	// BuildingURLs contains the status URL values for builds that
-	// are currently in progress for this commit.
-	BuildingURLs map[builderAndGoHash]string
-
-	PackagePath string    // (empty for main repo commits)
-	User        string    // "Foo Bar <foo@bar.com>"
-	Desc        string    // git commit title
-	Time        time.Time // commit time
-	Branch      string    // "master", "release-branch.go1.14"
-}
-
-// addEmptyResultGoHash adds an empty result containing goHash to
-// ci.ResultData, unless ci already contains a result for that hash.
-// This is used for non-go repos to show the go commits (both earliest
-// and latest) that correspond to this repo's commit time. We add an
-// empty result so it shows up on the dashboard (both for humans, and
-// in JSON form for the coordinator to pick up as work). Once the
-// coordinator does that work and posts its result, then ResultData
-// will be populate and this turns into a no-op.
-func (ci *CommitInfo) addEmptyResultGoHash(goHash string) {
-	for _, exist := range ci.ResultData {
-		if strings.Contains(exist, goHash) {
-			return
-		}
-	}
-	ci.ResultData = append(ci.ResultData, (&Result{GoHash: goHash}).Data())
-}
-
-type uiTemplateData struct {
-	Dashboard  *Dashboard
-	Package    *Package
-	Commits    []*CommitInfo
-	Builders   []string    // builders for just the main section; not the "TagState" sections
-	TagState   []*TagState // x/foo repo overviews at master + last two releases
-	Pagination *Pagination
-	Branches   []string
-	Branch     string
-	Repo       string // the repo gerrit project name. "go" if unspecified in the request.
-}
-
-// getActiveBuilds returns the builds that coordinator is currently doing.
-// This isn't critical functionality so errors are logged but otherwise ignored for now.
-// Once this is merged into the coordinator we won't need to make an RPC to get
-// this info. See https://github.com/golang/go/issues/34744#issuecomment-563398753.
-func getActiveBuilds(ctx context.Context) (builds []types.ActivePostSubmitBuild) {
-	ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
-	defer cancel()
-	req, _ := http.NewRequest("GET", "https://farmer.golang.org/status/post-submit-active.json", nil)
-	req = req.WithContext(ctx)
-	res, err := http.DefaultClient.Do(req)
-	if err != nil {
-		log.Printf("getActiveBuilds: Do: %v", err)
-		return
-	}
-	defer res.Body.Close()
-	if res.StatusCode != 200 {
-		log.Printf("getActiveBuilds: %v", res.Status)
-		return
-	}
-	if err := json.NewDecoder(res.Body).Decode(&builds); err != nil {
-		log.Printf("getActiveBuilds: JSON decode: %v", err)
-	}
-	return builds
-}
-
-// populateBuildingURLs populates each commit in Commits' buildingURLs map with the
-// URLs of builds which are currently in progress.
-func (td *uiTemplateData) populateBuildingURLs(ctx context.Context, activeBuilds []types.ActivePostSubmitBuild) {
-	// active maps from a build record with its status URL zeroed
-	// out to to the actual value of that status URL.
-	active := map[types.ActivePostSubmitBuild]string{}
-	for _, rec := range activeBuilds {
-		statusURL := rec.StatusURL
-		rec.StatusURL = ""
-		active[rec] = statusURL
-	}
-
-	condAdd := func(c *CommitInfo, rec types.ActivePostSubmitBuild) {
-		su, ok := active[rec]
-		if !ok {
-			return
-		}
-		if c.BuildingURLs == nil {
-			c.BuildingURLs = make(map[builderAndGoHash]string)
-		}
-		c.BuildingURLs[builderAndGoHash{rec.Builder, rec.GoCommit}] = su
-	}
-
-	for _, b := range td.Builders {
-		for _, c := range td.Commits {
-			condAdd(c, types.ActivePostSubmitBuild{Builder: b, Commit: c.Hash})
-		}
-	}
-
-	// Gather pending commits for sub-repos.
-	for _, ts := range td.TagState {
-		goHash := ts.Tag.Hash
-		for _, b := range td.Builders {
-			for _, pkg := range ts.Packages {
-				c := pkg.Commit
-				condAdd(c, types.ActivePostSubmitBuild{
-					Builder:  b,
-					Commit:   c.Hash,
-					GoCommit: goHash,
-				})
-			}
-		}
-	}
-}
-
-// allBuilders returns the list of builders, unified over the main
-// section and any x/foo branch overview (TagState) sections.
-func (td *uiTemplateData) allBuilders() []string {
-	m := map[string]bool{}
-	for _, b := range td.Builders {
-		m[b] = true
-	}
-	for _, ts := range td.TagState {
-		for _, b := range ts.Builders {
-			m[b] = true
-		}
-	}
-	return builderKeys(m)
-}
-
-var uiTemplate = template.Must(
-	template.New("ui.html").Funcs(tmplFuncs).ParseFiles(templateFile("ui.html")),
-)
-
-var tmplFuncs = template.FuncMap{
-	"builderSpans":       builderSpans,
-	"builderSubheading":  builderSubheading,
-	"builderSubheading2": builderSubheading2,
-	"shortDesc":          shortDesc,
-	"shortHash":          shortHash,
-	"shortUser":          shortUser,
-	"unsupported":        unsupported,
-	"isUntested":         isUntested,
-	"knownIssue":         knownIssue,
-	"formatTime":         formatTime,
-}
-
-func formatTime(t time.Time) string {
-	if t.Year() != time.Now().Year() {
-		return t.Format("02 Jan 06")
-	}
-	return t.Format("02 Jan 15:04")
-}
-
-func splitDash(s string) (string, string) {
-	i := strings.Index(s, "-")
-	if i >= 0 {
-		return s[:i], s[i+1:]
-	}
-	return s, ""
-}
-
-// builderOS returns the os tag for a builder string
-func builderOS(s string) string {
-	os, _ := splitDash(s)
-	return os
-}
-
-// builderOSOrRace returns the builder OS or, if it is a race builder, "race".
-func builderOSOrRace(s string) string {
-	if isRace(s) {
-		return "race"
-	}
-	return builderOS(s)
-}
-
-// builderArch returns the arch tag for a builder string
-func builderArch(s string) string {
-	_, arch := splitDash(s)
-	arch, _ = splitDash(arch) // chop third part
-	return arch
-}
-
-// builderSubheading returns a short arch tag for a builder string
-// or, if it is a race builder, the builder OS.
-func builderSubheading(s string) string {
-	if isRace(s) {
-		return builderOS(s)
-	}
-	return builderArch(s)
-}
-
-// builderSubheading2 returns any third part of a hyphenated builder name.
-// For instance, for "linux-amd64-nocgo", it returns "nocgo".
-// For race builders it returns the empty string.
-func builderSubheading2(s string) string {
-	if isRace(s) {
-		return ""
-	}
-	_, secondThird := splitDash(s)
-	_, third := splitDash(secondThird)
-	return third
-}
-
-type builderSpan struct {
-	N           int
-	OS          string
-	Unsupported bool
-}
-
-// builderSpans creates a list of tags showing
-// the builder's operating system names, spanning
-// the appropriate number of columns.
-func builderSpans(s []string) []builderSpan {
-	var sp []builderSpan
-	for len(s) > 0 {
-		i := 1
-		os := builderOSOrRace(s[0])
-		u := unsupportedOS(os)
-		for i < len(s) && builderOSOrRace(s[i]) == os {
-			i++
-		}
-		sp = append(sp, builderSpan{i, os, u})
-		s = s[i:]
-	}
-	return sp
-}
-
-// shortDesc returns the first line of a description.
-func shortDesc(desc string) string {
-	if i := strings.Index(desc, "\n"); i != -1 {
-		desc = desc[:i]
-	}
-	return limitStringLength(desc, 100)
-}
-
-// shortHash returns a short version of a hash.
-func shortHash(hash string) string {
-	if len(hash) > 7 {
-		hash = hash[:7]
-	}
-	return hash
-}
-
-// shortUser returns a shortened version of a user string.
-func shortUser(user string) string {
-	if i, j := strings.Index(user, "<"), strings.Index(user, ">"); 0 <= i && i < j {
-		user = user[i+1 : j]
-	}
-	if i := strings.Index(user, "@"); i >= 0 {
-		return user[:i]
-	}
-	return user
-}
-
-// templateFile returns the path to the provided HTML template file,
-// conditionally prepending a relative path depending on the
-// environment.
-func templateFile(base string) string {
-	// In tests the current directory is ".", but in prod it's up
-	// two levels. So just look to see if it's in . first.
-	if _, err := os.Stat(base); err == nil {
-		return base
-	}
-	return filepath.Join("app/appengine", base)
-}
-
-func httpStatusOfErr(err error) int {
-	fmt.Fprintf(os.Stderr, "Got error: %#v, code %v\n", err, grpc.Code(err))
-	switch grpc.Code(err) {
-	case codes.NotFound:
-		return http.StatusNotFound
-	case codes.InvalidArgument:
-		return http.StatusBadRequest
-	default:
-		return http.StatusInternalServerError
-	}
-}
diff --git a/app/appengine/ui.html b/app/appengine/ui.html
deleted file mode 100644
index 3ff7ac8..0000000
--- a/app/appengine/ui.html
+++ /dev/null
@@ -1,250 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-  <head>
-    <title>{{$.Dashboard.Name}} Build Dashboard</title>
-    <link rel="stylesheet" href="/static/style.css"/>
-    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
-    <script>
-    var showUnsupported = window.location.hash.substr(1) != "short";
-    function redraw() {
-        showUnsupported = !$("#showshort").prop('checked');
-        $('.unsupported')[showUnsupported?'show':'hide']();
-        window.location.hash = showUnsupported?'':'short';
-    }
-    $(document).ready(function() {
-        $("#showshort").attr('checked', !showUnsupported).change(redraw);
-        redraw();
-    })
-    </script>
-  </head>
-
-  <body>
-    <header id="topbar">
-      <h1>Go Dashboard</h1>
-      <div class="clear"></div>
-    </header>
-
-    <form action="." method="GET">
-    <input type="hidden" name="repo" value="{{.Package.Path}}">
-    <nav class="dashboards">
-      {{if not (eq .Branch "")}}
-      <label>
-        <select name="branch" onchange="this.form.submit()">
-        {{range $.Branches}}
-          <option value="{{.}}"{{if eq $.Branch .}} selected{{end}}>{{.}}</option>
-        {{end}}
-        </select>
-      </label>
-      {{end}}
-      <label>
-        <input type=checkbox id="showshort">
-        show only <a href="http://golang.org/wiki/PortingPolicy">first-class ports</a>
-      </label>
-    </nav>
-    </form>
-    {{with $.Package.Name}}<h2>{{.}}</h2>{{end}}
-
-  <div class="page">
-
-    {{if $.Commits}}
-
-    <table class="build">
-      <colgroup class="col-hash" {{if $.Package.Path}}span="2"{{end}}></colgroup>
-      <colgroup class="col-user"></colgroup>
-      <colgroup class="col-time"></colgroup>
-      <colgroup class="col-desc"></colgroup>
-    {{range $.Builders | builderSpans}}
-      <colgroup class="col-result{{if .Unsupported}} unsupported{{end}}" span="{{.N}}"></colgroup>
-    {{end}}
-      <tr>
-        <!-- extra row to make alternating colors use dark for first result -->
-      </tr>
-      <tr>
-    {{if $.Package.Path}}
-        <th colspan="2">revision</th>
-    {{else}}
-        <th>&nbsp;</th>
-    {{end}}
-        <th></th>
-        <th></th>
-        <th></th>
-    {{range $.Builders | builderSpans}}
-        <th {{if .Unsupported}}class="unsupported"{{end}} colspan="{{.N}}">{{.OS}}</th>
-    {{end}}
-      </tr>
-
-      <tr>
-    {{if $.Package.Path}}
-        <th class="result arch">repo</th>
-        <th class="result arch">{{$.Dashboard.Name}}</th>
-    {{else}}
-        <th>&nbsp;</th>
-    {{end}}
-        <th></th>
-        <th></th>
-        <th></th>
-    {{range $.Builders}}
-        <th class="result arch{{if (unsupported .)}} unsupported{{end}}{{if knownIssue .}} noise{{end}}" title="{{.}}">{{builderSubheading .}}</th>
-    {{end}}
-      </tr>
-
-      <tr class="subheading2">
-        <th {{if $.Package.Path}}colspan="2"{{end}}>&nbsp;</th>
-        <th></th>
-        <th></th>
-        <th></th>
-    {{range $.Builders}}
-        <th class="result arch{{if (unsupported .)}} unsupported{{end}}{{if knownIssue .}} noise{{end}}" title="{{.}}">{{builderSubheading2 .}}</th>
-    {{end}}
-      </tr>
-
-      {{range $c := $.Commits}}
-      {{range $i, $h := $c.ResultGoHashes}}
-        <tr class="commit">
-        {{if $i}}
-          <td>&nbsp;</td>
-        {{if $h}}
-          <td class="hash"><a href="https://go-review.googlesource.com/q/{{$h}}">{{shortHash $h}}</a></td>
-        {{end}}
-          <td>&nbsp;</td>
-          <td>&nbsp;</td>
-          <td>&nbsp;</td>
-        {{else}}
-          <td class="hash"><a href="https://go-review.googlesource.com/q/{{$c.Hash}}">{{shortHash $c.Hash}}</a></td>
-        {{if $h}}
-          <td class="hash"><a href="https://go-review.googlesource.com/q/{{$h}}">{{shortHash $h}}</a></td>
-        {{end}}
-          <td class="user" title="{{$c.User}}">{{shortUser $c.User}}</td>
-          <td class="time">{{formatTime $c.Time}}</td>
-          <td class="desc" title="{{$c.Desc}}">{{shortDesc $c.Desc}}</td>
-        {{end}}
-          {{range $builderName := $.Builders}}
-            <td class="result{{if (unsupported .)}} unsupported{{end}}">
-            {{if and (eq $.Repo "go") (isUntested $builderName "go" $.Branch "")}}•{{else}}
-              {{with $c.Result $builderName $h}}
-                {{if .BuildingURL}}
-                  <a href="{{.BuildingURL}}"><img src="https://golang.org/favicon.ico" height=16 width=16 border=0></a>
-                {{else if .OK}}
-                  <span class="ok{{if knownIssue $builderName}} noise{{end}}">ok</span>
-                {{else if knownIssue $builderName}}
-                  <a href="/log/{{.LogHash}}" class="noise" title="Builder {{$builderName}} has a known issue. See golang.org/issue/{{knownIssue $builderName}}.">fail</a>
-                {{else}}
-                  <a href="/log/{{.LogHash}}" class="fail">fail</a>
-                {{end}}
-              {{else}}
-                &nbsp;
-              {{end}}
-            {{end}}
-            </td>
-          {{end}}
-        </tr>
-      {{end}}
-    {{end}}
-    </table>
-
-    {{with $.Pagination}}
-    <div class="paginate">
-      <nav>
-        <a {{if .HasPrev}}href="?{{with $.Package.Path}}repo={{.}}&{{end}}page={{.Prev}}{{with $.Branch}}&branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>newer</a>
-        <a {{if .Next}}href="?{{with $.Package.Path}}repo={{.}}&{{end}}page={{.Next}}{{with $.Branch}}&branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>older</a>
-        <a {{if .HasPrev}}href=".{{with $.Branch}}?branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>latest</a>
-      </nav>
-    </div>
-    {{end}}
-
-  {{else}}
-    <p>No commits to display. Hm.</p>
-  {{end}}
-
-  {{range $.TagState}}
-    {{$goHash := .Tag.Hash}}
-    {{$goBranch := .Branch}}
-    {{$builders := .Builders}}
-    {{if .Packages}}
-      <h2>
-        golang.org/x repos at Go {{$goBranch}}
-        <small>(<a href="https://go-review.googlesource.com/q/{{.Tag.Hash}}">{{shortHash .Tag.Hash}}</a>)</small>
-      </h2>
-
-      <table class="build">
-      <colgroup class="col-package"></colgroup>
-      <colgroup class="col-hash"></colgroup>
-      <colgroup class="col-user"></colgroup>
-      <colgroup class="col-time"></colgroup>
-      <colgroup class="col-desc"></colgroup>
-      {{range $builders | builderSpans}}
-        <colgroup class="col-result{{if .Unsupported}} unsupported{{end}}" span="{{.N}}"></colgroup>
-      {{end}}
-      <tr>
-        <!-- extra row to make alternating colors use dark for first result -->
-      </tr>
-      <tr>
-        <th></th>
-        <th></th>
-        <th></th>
-        <th></th>
-        <th></th>
-        {{range $builders | builderSpans}}
-          <th {{if .Unsupported}}class="unsupported"{{end}} colspan="{{.N}}">{{.OS}}</th>
-        {{end}}
-      </tr>
-      <tr>
-        <th></th>
-        <th></th>
-        <th></th>
-        <th></th>
-        <th></th>
-        {{range $builders}}
-          <th class="result arch{{if (unsupported .)}} unsupported{{end}}" title="{{.}}">{{builderSubheading .}}</th>
-        {{end}}
-      </tr>
-      <tr class="subheading2">
-        <th>&nbsp;</th>
-        <th></th>
-        <th></th>
-        <th></th>
-        <th></th>
-        {{range $builders}}
-          <th class="result arch{{if (unsupported .)}} unsupported{{end}}" title="{{.}}">{{builderSubheading2 .}}</th>
-        {{end}}
-      </tr>
-    {{range $pkg := .Packages}}
-      <tr class="commit">
-        <td><a title="{{.Package.Path}}" href="?repo={{.Package.Path}}">{{.Package.Name}}</a></td>
-        <td class="hash">
-          {{$h := $pkg.Commit.Hash}}
-          <a href="https://go-review.googlesource.com/q/{{$h}}">{{shortHash $h}}</a>
-        </td>
-        {{with $pkg.Commit}}
-          <td class="user" title="{{.User}}">{{shortUser .User}}</td>
-          <td class="time">{{formatTime .Time}}</td>
-          <td class="desc" title="{{.Desc}}">{{shortDesc .Desc}}</td>
-        {{end}}
-        {{range $builderName := $builders}}
-          <td class="result{{if (unsupported .)}} unsupported{{end}}">
-            {{if isUntested $builderName $pkg.Package.Name "master" $goBranch}}
-              •
-            {{else}}
-              {{with $pkg.Commit.Result $builderName $goHash}}
-                {{if .BuildingURL}}
-                  <a href="{{.BuildingURL}}"><img src="https://golang.org/favicon.ico" height=16 width=16 border=0></a>
-                {{else if .OK}}
-                  <span class="ok">ok</span>
-                {{else}}
-                  <a href="/log/{{.LogHash}}" class="fail">fail</a>
-                {{end}}
-              {{else}}
-                &nbsp;
-              {{end}}
-            {{end}}
-          </td>
-        {{end}}
-      </tr>
-    {{end}}
-    </table>
-   {{end}}
-  {{end}}
-
-  </div>
-  </body>
-</html>
diff --git a/app/appengine/ui_test.go b/app/appengine/ui_test.go
deleted file mode 100644
index 16f946f..0000000
--- a/app/appengine/ui_test.go
+++ /dev/null
@@ -1,541 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
-	"context"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-cmp/cmp/cmpopts"
-	"golang.org/x/build/dashboard"
-	"golang.org/x/build/maintner/maintnerd/apipb"
-	"golang.org/x/build/types"
-)
-
-func TestUITemplateDataBuilder(t *testing.T) {
-	// Thin the list of builders to make this test's data lighter
-	// and require less maintenance keeping it in sync.
-	origBuilders := dashboard.Builders
-	defer func() { dashboard.Builders = origBuilders }()
-	dashboard.Builders = map[string]*dashboard.BuildConfig{
-		"linux-amd64": origBuilders["linux-amd64"],
-		"linux-386":   origBuilders["linux-386"],
-	}
-
-	tests := []struct {
-		name           string                   // test subname
-		view           dashboardView            // one of htmlView{}, jsonView{}, or failuresView{}
-		req            *apipb.DashboardRequest  // what we pretend we sent to maintner
-		res            *apipb.DashboardResponse // what we pretend we got back from maintner
-		testCommitData map[string]*Commit       // what we pretend we loaded from datastore
-		activeBuilds   []types.ActivePostSubmitBuild
-		want           *uiTemplateData // what we're hoping we generated for the view/template
-	}{
-		// Basic test.
-		{
-			name: "html,zero_value_req,no_commits",
-			view: htmlView{},
-			req:  &apipb.DashboardRequest{},
-			res: &apipb.DashboardResponse{
-				Branches: []string{"release.foo", "release.bar", "dev.blah"},
-			},
-			want: &uiTemplateData{
-				Dashboard:  goDash,
-				Repo:       "go",
-				Package:    &Package{Name: "Go", Path: ""},
-				Branches:   []string{"release.foo", "release.bar", "dev.blah"},
-				Builders:   []string{"linux-386", "linux-amd64"},
-				Pagination: &Pagination{},
-			},
-		},
-
-		// Basic test + two commits: one that's in datastore and one that's not.
-		{
-			name: "html,zero_value_req,has_commit",
-			view: htmlView{},
-			req:  &apipb.DashboardRequest{},
-			// Have only one commit load from the datastore:
-			testCommitData: map[string]*Commit{
-				"26957168c4c0cdcc7ca4f0b19d0eb19474d224ac": {
-					PackagePath: "",
-					Hash:        "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-					ResultData: []string{
-						"openbsd-amd64|true||", // pretend openbsd-amd64 passed (and thus exists)
-					},
-				},
-			},
-			activeBuilds: []types.ActivePostSubmitBuild{
-				{Builder: "linux-amd64", Commit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac", StatusURL: "http://fake-status"},
-			},
-			res: &apipb.DashboardResponse{
-				Branches: []string{"release.foo", "release.bar", "dev.blah"},
-				Commits: []*apipb.DashCommit{
-					// This is the maintner commit response that is in the datastore:
-					{
-						Commit:        "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						AuthorName:    "Foo Bar",
-						AuthorEmail:   "foo@example.com",
-						CommitTimeSec: 1257894001,
-						Title:         "runtime: fix all the bugs",
-						Branch:        "master",
-					},
-					// And another commit that's not in the datastore:
-					{
-						Commit:        "ffffffffffffffffffffffffffffffffffffffff",
-						AuthorName:    "Fancy Fred",
-						AuthorEmail:   "f@eff.tld",
-						CommitTimeSec: 1257894000,
-						Title:         "all: add effs",
-						Branch:        "master",
-					},
-				},
-				CommitsTruncated: true, // pretend there's a page 2
-			},
-			want: &uiTemplateData{
-				Dashboard: goDash,
-				Repo:      "go",
-				Package:   &Package{Name: "Go", Path: ""},
-				Branches:  []string{"release.foo", "release.bar", "dev.blah"},
-				Builders:  []string{"linux-386", "linux-amd64", "openbsd-amd64"},
-				Pagination: &Pagination{
-					Next: 1,
-				},
-				Commits: []*CommitInfo{
-					{
-						Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						User: "Foo Bar <foo@example.com>",
-						Desc: "runtime: fix all the bugs",
-						Time: time.Unix(1257894001, 0),
-						ResultData: []string{
-							"openbsd-amd64|true||",
-						},
-						Branch:       "master",
-						BuildingURLs: map[builderAndGoHash]string{{builder: "linux-amd64"}: "http://fake-status"},
-					},
-					{
-						Hash:   "ffffffffffffffffffffffffffffffffffffffff",
-						User:   "Fancy Fred <f@eff.tld>",
-						Desc:   "all: add effs",
-						Time:   time.Unix(1257894000, 0),
-						Branch: "master",
-					},
-				},
-			},
-		},
-
-		// Test that we generate the TagState (sections at
-		// bottom with the x/foo repo state).
-		{
-			name:           "html,zero_value_req,has_xrepos",
-			view:           htmlView{},
-			req:            &apipb.DashboardRequest{},
-			testCommitData: map[string]*Commit{},
-			res: &apipb.DashboardResponse{
-				Branches: []string{"release.foo", "release.bar", "dev.blah"},
-				Commits: []*apipb.DashCommit{
-					{
-						Commit:        "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						AuthorName:    "Foo Bar",
-						AuthorEmail:   "foo@example.com",
-						CommitTimeSec: 1257894001,
-						Title:         "runtime: fix all the bugs",
-						Branch:        "master",
-					},
-				},
-				Releases: []*apipb.GoRelease{
-					{
-						BranchName:   "master",
-						BranchCommit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-					},
-					{
-						BranchName:   "release-branch.go1.99",
-						BranchCommit: "ffffffffffffffffffffffffffffffffffffffff",
-					},
-				},
-				RepoHeads: []*apipb.DashRepoHead{
-					{
-						GerritProject: "go",
-						Commit: &apipb.DashCommit{
-							Commit:        "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-							AuthorName:    "Foo Bar",
-							AuthorEmail:   "foo@example.com",
-							CommitTimeSec: 1257894001,
-							Title:         "runtime: fix all the bugs",
-							Branch:        "master",
-						},
-					},
-					{
-						GerritProject: "net",
-						Commit: &apipb.DashCommit{
-							Commit:        "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
-							AuthorName:    "Ee Yore",
-							AuthorEmail:   "e@e.net",
-							CommitTimeSec: 1257894001,
-							Title:         "all: fix networking",
-							Branch:        "master",
-						},
-					},
-					{
-						GerritProject: "sys",
-						Commit: &apipb.DashCommit{
-							Commit:        "dddddddddddddddddddddddddddddddddddddddd",
-							AuthorName:    "Sys Tem",
-							AuthorEmail:   "sys@s.net",
-							CommitTimeSec: 1257894001,
-							Title:         "sys: support more systems",
-							Branch:        "master",
-						},
-					},
-				},
-			},
-			want: &uiTemplateData{
-				Dashboard:  goDash,
-				Repo:       "go",
-				Package:    &Package{Name: "Go", Path: ""},
-				Branches:   []string{"release.foo", "release.bar", "dev.blah"},
-				Builders:   []string{"linux-386", "linux-amd64"},
-				Pagination: &Pagination{},
-				Commits: []*CommitInfo{
-					{
-						Hash:   "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						User:   "Foo Bar <foo@example.com>",
-						Desc:   "runtime: fix all the bugs",
-						Time:   time.Unix(1257894001, 0),
-						Branch: "master",
-					},
-				},
-				TagState: []*TagState{
-					{
-						Name:     "master",
-						Tag:      &CommitInfo{Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac"},
-						Builders: []string{"linux-386", "linux-amd64"},
-						Packages: []*PackageState{
-							{
-								Package: &Package{Name: "net", Path: "golang.org/x/net"},
-								Commit: &CommitInfo{
-									Hash:        "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
-									PackagePath: "golang.org/x/net",
-									User:        "Ee Yore <e@e.net>",
-									Desc:        "all: fix networking",
-									Time:        time.Unix(1257894001, 0),
-									Branch:      "master",
-								},
-							},
-							{
-								Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
-								Commit: &CommitInfo{
-									Hash:        "dddddddddddddddddddddddddddddddddddddddd",
-									PackagePath: "golang.org/x/sys",
-									User:        "Sys Tem <sys@s.net>",
-									Desc:        "sys: support more systems",
-									Time:        time.Unix(1257894001, 0),
-									Branch:      "master",
-								},
-							},
-						},
-					},
-					{
-						Name:     "release-branch.go1.99",
-						Tag:      &CommitInfo{Hash: "ffffffffffffffffffffffffffffffffffffffff"},
-						Builders: []string{"linux-386", "linux-amd64"},
-						Packages: []*PackageState{
-							{
-								Package: &Package{Name: "net", Path: "golang.org/x/net"},
-								Commit: &CommitInfo{
-									Hash:        "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
-									PackagePath: "golang.org/x/net",
-									User:        "Ee Yore <e@e.net>",
-									Desc:        "all: fix networking",
-									Time:        time.Unix(1257894001, 0),
-									Branch:      "master",
-								},
-							},
-							{
-								Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
-								Commit: &CommitInfo{
-									Hash:        "dddddddddddddddddddddddddddddddddddddddd",
-									PackagePath: "golang.org/x/sys",
-									User:        "Sys Tem <sys@s.net>",
-									Desc:        "sys: support more systems",
-									Time:        time.Unix(1257894001, 0),
-									Branch:      "master",
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-
-		// Test viewing a non-go repo.
-		{
-			name:           "html,other_repo",
-			view:           htmlView{},
-			req:            &apipb.DashboardRequest{Repo: "golang.org/x/net"},
-			testCommitData: map[string]*Commit{},
-			res: &apipb.DashboardResponse{
-				Branches: []string{"master", "dev.blah"},
-				Commits: []*apipb.DashCommit{
-					{
-						Commit:         "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						AuthorName:     "Foo Bar",
-						AuthorEmail:    "foo@example.com",
-						CommitTimeSec:  1257894001,
-						Title:          "net: fix all the bugs",
-						Branch:         "master",
-						GoCommitAtTime: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-						GoCommitLatest: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
-					},
-				},
-			},
-			want: &uiTemplateData{
-				Dashboard:  goDash,
-				Repo:       "net",
-				Package:    &Package{Name: "net", Path: "golang.org/x/net"},
-				Branches:   []string{"master", "dev.blah"},
-				Builders:   []string{"linux-386", "linux-amd64"},
-				Pagination: &Pagination{},
-				Commits: []*CommitInfo{
-					{
-						PackagePath: "golang.org/x/net",
-						Hash:        "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						User:        "Foo Bar <foo@example.com>",
-						Desc:        "net: fix all the bugs",
-						Time:        time.Unix(1257894001, 0),
-						Branch:      "master",
-						ResultData: []string{
-							"|false||aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-							"|false||bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
-						},
-					},
-				},
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			tb := &uiTemplateDataBuilder{
-				view:           tt.view,
-				req:            tt.req,
-				res:            tt.res,
-				activeBuilds:   tt.activeBuilds,
-				testCommitData: tt.testCommitData,
-			}
-			data, err := tb.buildTemplateData(context.Background())
-			if err != nil {
-				t.Fatal(err)
-			}
-			diff := cmp.Diff(tt.want, data, cmpopts.IgnoreUnexported(CommitInfo{}))
-			if diff != "" {
-				t.Errorf("mismatch want->got:\n%s", diff)
-			}
-		})
-	}
-}
-
-func TestToBuildStatus(t *testing.T) {
-	tests := []struct {
-		name string
-		data *uiTemplateData
-		want types.BuildStatus
-	}{
-		{
-			name: "go repo",
-			data: &uiTemplateData{
-				Dashboard:  goDash,
-				Repo:       "go",
-				Package:    &Package{Name: "Go", Path: ""},
-				Branches:   []string{"release.foo", "release.bar", "dev.blah"},
-				Builders:   []string{"linux-386", "linux-amd64"},
-				Pagination: &Pagination{},
-				Commits: []*CommitInfo{
-					{
-						Hash:   "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						User:   "Foo Bar <foo@example.com>",
-						Desc:   "runtime: fix all the bugs",
-						Time:   time.Unix(1257894001, 0).UTC(),
-						Branch: "master",
-					},
-				},
-				TagState: []*TagState{
-					{
-						Name:     "master",
-						Tag:      &CommitInfo{Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac"},
-						Builders: []string{"linux-386", "linux-amd64"},
-						Packages: []*PackageState{
-							{
-								Package: &Package{Name: "net", Path: "golang.org/x/net"},
-								Commit: &CommitInfo{
-									Hash:        "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
-									PackagePath: "golang.org/x/net",
-									User:        "Ee Yore <e@e.net>",
-									Desc:        "all: fix networking",
-									Time:        time.Unix(1257894001, 0).UTC(),
-									Branch:      "master",
-								},
-							},
-							{
-								Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
-								Commit: &CommitInfo{
-									Hash:        "dddddddddddddddddddddddddddddddddddddddd",
-									PackagePath: "golang.org/x/sys",
-									User:        "Sys Tem <sys@s.net>",
-									Desc:        "sys: support more systems",
-									Time:        time.Unix(1257894001, 0).UTC(),
-									Branch:      "master",
-								},
-							},
-						},
-					},
-					{
-						Name:     "release-branch.go1.99",
-						Tag:      &CommitInfo{Hash: "ffffffffffffffffffffffffffffffffffffffff"},
-						Builders: []string{"linux-386", "linux-amd64"},
-						Packages: []*PackageState{
-							{
-								Package: &Package{Name: "net", Path: "golang.org/x/net"},
-								Commit: &CommitInfo{
-									Hash:        "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
-									PackagePath: "golang.org/x/net",
-									User:        "Ee Yore <e@e.net>",
-									Desc:        "all: fix networking",
-									Time:        time.Unix(1257894001, 0).UTC(),
-									Branch:      "master",
-								},
-							},
-							{
-								Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
-								Commit: &CommitInfo{
-									Hash:        "dddddddddddddddddddddddddddddddddddddddd",
-									PackagePath: "golang.org/x/sys",
-									User:        "Sys Tem <sys@s.net>",
-									Desc:        "sys: support more systems",
-									Time:        time.Unix(1257894001, 0).UTC(),
-									Branch:      "master",
-								},
-							},
-						},
-					},
-				},
-			},
-			want: types.BuildStatus{
-				Builders: []string{"linux-386", "linux-amd64"},
-				Revisions: []types.BuildRevision{
-					{
-						Repo:     "go",
-						Revision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						Date:     "2009-11-10T23:00:01Z",
-						Branch:   "master",
-						Author:   "Foo Bar <foo@example.com>",
-						Desc:     "runtime: fix all the bugs",
-						Results:  []string{"", ""},
-					},
-					{
-						Repo:       "net",
-						Revision:   "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
-						GoRevision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						Date:       "2009-11-10T23:00:01Z",
-						Branch:     "master",
-						GoBranch:   "master",
-						Author:     "Ee Yore <e@e.net>",
-						Desc:       "all: fix networking",
-						Results:    []string{"", ""},
-					},
-					{
-						Repo:       "sys",
-						Revision:   "dddddddddddddddddddddddddddddddddddddddd",
-						GoRevision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						Date:       "2009-11-10T23:00:01Z",
-						Branch:     "master",
-						GoBranch:   "master",
-						Author:     "Sys Tem <sys@s.net>",
-						Desc:       "sys: support more systems",
-						Results:    []string{"", ""},
-					},
-					{
-						Repo:       "net",
-						Revision:   "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
-						GoRevision: "ffffffffffffffffffffffffffffffffffffffff",
-						Date:       "2009-11-10T23:00:01Z",
-						Branch:     "master",
-						GoBranch:   "release-branch.go1.99",
-						Author:     "Ee Yore <e@e.net>",
-						Desc:       "all: fix networking",
-						Results:    []string{"", ""},
-					},
-					{
-						Repo:       "sys",
-						Revision:   "dddddddddddddddddddddddddddddddddddddddd",
-						GoRevision: "ffffffffffffffffffffffffffffffffffffffff",
-						Date:       "2009-11-10T23:00:01Z",
-						Branch:     "master",
-						GoBranch:   "release-branch.go1.99",
-						Author:     "Sys Tem <sys@s.net>",
-						Desc:       "sys: support more systems",
-						Results:    []string{"", ""},
-					},
-				},
-			},
-		},
-		{
-			name: "other repo",
-			data: &uiTemplateData{
-				Dashboard: goDash,
-				Repo:      "tools",
-				Builders:  []string{"linux", "windows"},
-				Commits: []*CommitInfo{
-					{
-						PackagePath: "golang.org/x/tools",
-						Hash:        "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						User:        "Foo Bar <foo@example.com>",
-						Desc:        "tools: fix all the bugs",
-						Time:        time.Unix(1257894001, 0).UTC(),
-						Branch:      "master",
-						ResultData: []string{
-							"linux|false|123|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-							"windows|false|456|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
-						},
-					},
-				},
-			},
-			want: types.BuildStatus{
-				Builders: []string{"linux", "windows"},
-				Revisions: []types.BuildRevision{
-					{
-						Repo:       "tools",
-						Revision:   "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						GoRevision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
-						Date:       "2009-11-10T23:00:01Z",
-						Branch:     "master",
-						Author:     "Foo Bar <foo@example.com>",
-						Desc:       "tools: fix all the bugs",
-						Results:    []string{"", "https://build.golang.org/log/456"},
-					},
-					{
-						Repo:       "tools",
-						Revision:   "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
-						GoRevision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-						Date:       "2009-11-10T23:00:01Z",
-						Branch:     "master",
-						Author:     "Foo Bar <foo@example.com>",
-						Desc:       "tools: fix all the bugs",
-						Results:    []string{"https://build.golang.org/log/123", ""},
-					},
-				},
-			},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got := toBuildStatus("build.golang.org", tt.data)
-			if diff := cmp.Diff(tt.want, got); diff != "" {
-				t.Errorf("buildStatus(...) mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
diff --git a/app/key/key.go b/app/key/key.go
deleted file mode 100644
index c903e24..0000000
--- a/app/key/key.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package key
-
-import (
-	"context"
-	"sync"
-
-	"cloud.google.com/go/datastore"
-)
-
-var theKey struct {
-	sync.RWMutex
-	builderKey
-}
-
-type builderKey struct {
-	Secret string
-}
-
-var dsKey = datastore.NameKey("BuilderKey", "root", nil)
-
-func Secret(ctx context.Context, c *datastore.Client) string {
-	// check with rlock
-	theKey.RLock()
-	k := theKey.Secret
-	theKey.RUnlock()
-	if k != "" {
-		return k
-	}
-
-	// prepare to fill; check with lock and keep lock
-	theKey.Lock()
-	defer theKey.Unlock()
-	if theKey.Secret != "" {
-		return theKey.Secret
-	}
-
-	// fill
-	if err := c.Get(ctx, dsKey, &theKey.builderKey); err != nil {
-		if err == datastore.ErrNoSuchEntity {
-			// If the key is not stored in datastore, write it.
-			// This only happens at the beginning of a new deployment.
-			// The code is left here for SDK use and in case a fresh
-			// deployment is ever needed.  "gophers rule" is not the
-			// real key.
-			panic("lost key from datastore")
-			theKey.Secret = "gophers rule"
-			c.Put(ctx, dsKey, &theKey.builderKey)
-			return theKey.Secret
-		}
-		panic("cannot load builder key: " + err.Error())
-	}
-
-	return theKey.Secret
-}