[status] Add autoroller-status-sk.

-- Add GetAutorollerStatuses RPC.
-- Used the same pattern for a Twirp-version of the autoroller response
(ready to go response object that is occasionally refreshed)
-- Fixed bug in Repos template filling (was not filling the global due
to ':=')
-- autoroller-status-sk queries every minute (the server periodically
updates the response, so configurability of the refresh on the client
side has little value.
-- Refactored colors/classes in commits-table-sk and styles.scss so
they can be reused in autoroller-status-sk.
-- Adjusted reload input label CSS positioning to be more robust.
-- Added the new element to a temporary location in status-sk so it
shows up until it's in the eventual side panel.

Change-Id: I0e82801d5c561031d47380be988dfd55f019fac1
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/326953
Commit-Queue: Weston Tracey <westont@google.com>
Reviewed-by: Eric Boren <borenet@google.com>
diff --git a/status/go/rpc/server_impl.go b/status/go/rpc/server_impl.go
index 6704211..cbbd987 100644
--- a/status/go/rpc/server_impl.go
+++ b/status/go/rpc/server_impl.go
@@ -23,12 +23,13 @@
 //go:generate protoc --twirp_typescript_out=../../modules/rpc status.proto
 
 type statusServerImpl struct {
-	iCache               *incremental.IncrementalCache
-	taskDb               db.RemoteDB
-	getRepo              func(string) (string, string, error)
-	maxCommitsToLoad     int
-	defaultCommitsToLoad int
-	podID                string
+	iCache                *incremental.IncrementalCache
+	taskDb                db.RemoteDB
+	getAutorollerStatuses func() *GetAutorollerStatusesResponse
+	getRepo               func(string) (string, string, error)
+	maxCommitsToLoad      int
+	defaultCommitsToLoad  int
+	podID                 string
 }
 
 // This is incrementalJsonHandler, adjusted for Twirp, using ConvertUpdate to use generated types.
@@ -193,10 +194,15 @@
 	return &DeleteCommentResponse{}, nil
 }
 
+func (s *statusServerImpl) GetAutorollerStatuses(ctx context.Context, req *GetAutorollerStatusesRequest) (*GetAutorollerStatusesResponse, error) {
+	return s.getAutorollerStatuses(), nil
+}
+
 // NewStatusServer creates and returns a Twirp HTTP Server.
 func NewStatusServer(
 	iCache *incremental.IncrementalCache,
 	taskDb db.RemoteDB,
+	getAutorollStatuses func() *GetAutorollerStatusesResponse,
 	getRepo func(string) (string, string, error),
 	maxCommitsToLoad int,
 	defaultCommitsToLoad int,
@@ -204,6 +210,7 @@
 	return NewStatusServiceServer(&statusServerImpl{
 		iCache,
 		taskDb,
+		getAutorollStatuses,
 		getRepo,
 		maxCommitsToLoad,
 		defaultCommitsToLoad,
diff --git a/status/go/rpc/status.pb.go b/status/go/rpc/status.pb.go
index 761b386..6c9b57c 100644
--- a/status/go/rpc/status.pb.go
+++ b/status/go/rpc/status.pb.go
@@ -961,6 +961,187 @@
 	return file_status_proto_rawDescGZIP(), []int{11}
 }
 
+// Empty, no parameters needed to ask for latest autoroller status info.
+type GetAutorollerStatusesRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *GetAutorollerStatusesRequest) Reset() {
+	*x = GetAutorollerStatusesRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_status_proto_msgTypes[12]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetAutorollerStatusesRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetAutorollerStatusesRequest) ProtoMessage() {}
+
+func (x *GetAutorollerStatusesRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_status_proto_msgTypes[12]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetAutorollerStatusesRequest.ProtoReflect.Descriptor instead.
+func (*GetAutorollerStatusesRequest) Descriptor() ([]byte, []int) {
+	return file_status_proto_rawDescGZIP(), []int{12}
+}
+
+type GetAutorollerStatusesResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Rollers []*AutorollerStatus `protobuf:"bytes,1,rep,name=rollers,proto3" json:"rollers,omitempty"`
+}
+
+func (x *GetAutorollerStatusesResponse) Reset() {
+	*x = GetAutorollerStatusesResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_status_proto_msgTypes[13]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetAutorollerStatusesResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetAutorollerStatusesResponse) ProtoMessage() {}
+
+func (x *GetAutorollerStatusesResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_status_proto_msgTypes[13]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetAutorollerStatusesResponse.ProtoReflect.Descriptor instead.
+func (*GetAutorollerStatusesResponse) Descriptor() ([]byte, []int) {
+	return file_status_proto_rawDescGZIP(), []int{13}
+}
+
+func (x *GetAutorollerStatusesResponse) GetRollers() []*AutorollerStatus {
+	if x != nil {
+		return x.Rollers
+	}
+	return nil
+}
+
+type AutorollerStatus struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name           string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	CurrentRollRev string `protobuf:"bytes,2,opt,name=current_roll_rev,json=currentRollRev,proto3" json:"current_roll_rev,omitempty"`
+	LastRollRev    string `protobuf:"bytes,3,opt,name=last_roll_rev,json=lastRollRev,proto3" json:"last_roll_rev,omitempty"`
+	Mode           string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"`
+	NumFailed      int32  `protobuf:"varint,5,opt,name=num_failed,json=numFailed,proto3" json:"num_failed,omitempty"`
+	NumBehind      int32  `protobuf:"varint,6,opt,name=num_behind,json=numBehind,proto3" json:"num_behind,omitempty"`
+	Url            string `protobuf:"bytes,7,opt,name=url,proto3" json:"url,omitempty"`
+}
+
+func (x *AutorollerStatus) Reset() {
+	*x = AutorollerStatus{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_status_proto_msgTypes[14]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AutorollerStatus) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AutorollerStatus) ProtoMessage() {}
+
+func (x *AutorollerStatus) ProtoReflect() protoreflect.Message {
+	mi := &file_status_proto_msgTypes[14]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use AutorollerStatus.ProtoReflect.Descriptor instead.
+func (*AutorollerStatus) Descriptor() ([]byte, []int) {
+	return file_status_proto_rawDescGZIP(), []int{14}
+}
+
+func (x *AutorollerStatus) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *AutorollerStatus) GetCurrentRollRev() string {
+	if x != nil {
+		return x.CurrentRollRev
+	}
+	return ""
+}
+
+func (x *AutorollerStatus) GetLastRollRev() string {
+	if x != nil {
+		return x.LastRollRev
+	}
+	return ""
+}
+
+func (x *AutorollerStatus) GetMode() string {
+	if x != nil {
+		return x.Mode
+	}
+	return ""
+}
+
+func (x *AutorollerStatus) GetNumFailed() int32 {
+	if x != nil {
+		return x.NumFailed
+	}
+	return 0
+}
+
+func (x *AutorollerStatus) GetNumBehind() int32 {
+	if x != nil {
+		return x.NumBehind
+	}
+	return 0
+}
+
+func (x *AutorollerStatus) GetUrl() string {
+	if x != nil {
+		return x.Url
+	}
+	return ""
+}
+
 var File_status_proto protoreflect.FileDescriptor
 
 var file_status_proto_rawDesc = []byte{
@@ -1085,7 +1266,28 @@
 	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
 	0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22,
 	0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x88, 0x02, 0x0a, 0x0d, 0x53, 0x74, 0x61,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x41,
+	0x75, 0x74, 0x6f, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65,
+	0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x53, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41,
+	0x75, 0x74, 0x6f, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65,
+	0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x6f, 0x6c,
+	0x6c, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x74, 0x61,
+	0x74, 0x75, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74,
+	0x61, 0x74, 0x75, 0x73, 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x22, 0xd8, 0x01,
+	0x0a, 0x10, 0x41, 0x75, 0x74, 0x6f, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
+	0x74, 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x72, 0x65, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x52, 0x65, 0x76,
+	0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x72, 0x65,
+	0x76, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x6f, 0x6c,
+	0x6c, 0x52, 0x65, 0x76, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x75, 0x6d, 0x5f,
+	0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6e, 0x75,
+	0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x75, 0x6d, 0x5f, 0x62,
+	0x65, 0x68, 0x69, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6e, 0x75, 0x6d,
+	0x42, 0x65, 0x68, 0x69, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x32, 0xee, 0x02, 0x0a, 0x0d, 0x53, 0x74, 0x61,
 	0x74, 0x75, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x15, 0x47, 0x65,
 	0x74, 0x49, 0x6e, 0x63, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x6d,
 	0x69, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74,
@@ -1102,9 +1304,16 @@
 	0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
 	0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x44, 0x65,
 	0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x42, 0x21, 0x5a, 0x1f, 0x67, 0x6f, 0x2e, 0x73, 0x6b, 0x69, 0x61, 0x2e, 0x6f,
-	0x72, 0x67, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f,
-	0x67, 0x6f, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x72, 0x6f,
+	0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x73,
+	0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x72, 0x6f, 0x6c,
+	0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x25, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x41,
+	0x75, 0x74, 0x6f, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65,
+	0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x21, 0x5a, 0x1f, 0x67, 0x6f, 0x2e,
+	0x73, 0x6b, 0x69, 0x61, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x69, 0x6e, 0x66, 0x72, 0x61, 0x2f, 0x73,
+	0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -1119,7 +1328,7 @@
 	return file_status_proto_rawDescData
 }
 
-var file_status_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
+var file_status_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
 var file_status_proto_goTypes = []interface{}{
 	(*GetIncrementalCommitsRequest)(nil),  // 0: status.GetIncrementalCommitsRequest
 	(*GetIncrementalCommitsResponse)(nil), // 1: status.GetIncrementalCommitsResponse
@@ -1133,33 +1342,39 @@
 	(*AddCommentResponse)(nil),            // 9: status.AddCommentResponse
 	(*DeleteCommentRequest)(nil),          // 10: status.DeleteCommentRequest
 	(*DeleteCommentResponse)(nil),         // 11: status.DeleteCommentResponse
-	(*timestamp.Timestamp)(nil),           // 12: google.protobuf.Timestamp
+	(*GetAutorollerStatusesRequest)(nil),  // 12: status.GetAutorollerStatusesRequest
+	(*GetAutorollerStatusesResponse)(nil), // 13: status.GetAutorollerStatusesResponse
+	(*AutorollerStatus)(nil),              // 14: status.AutorollerStatus
+	(*timestamp.Timestamp)(nil),           // 15: google.protobuf.Timestamp
 }
 var file_status_proto_depIdxs = []int32{
-	12, // 0: status.GetIncrementalCommitsRequest.from:type_name -> google.protobuf.Timestamp
-	12, // 1: status.GetIncrementalCommitsRequest.to:type_name -> google.protobuf.Timestamp
+	15, // 0: status.GetIncrementalCommitsRequest.from:type_name -> google.protobuf.Timestamp
+	15, // 1: status.GetIncrementalCommitsRequest.to:type_name -> google.protobuf.Timestamp
 	7,  // 2: status.GetIncrementalCommitsResponse.metadata:type_name -> status.ResponseMetadata
 	2,  // 3: status.GetIncrementalCommitsResponse.update:type_name -> status.IncrementalUpdate
 	5,  // 4: status.IncrementalUpdate.commits:type_name -> status.LongCommit
 	3,  // 5: status.IncrementalUpdate.branch_heads:type_name -> status.Branch
 	4,  // 6: status.IncrementalUpdate.tasks:type_name -> status.Task
 	6,  // 7: status.IncrementalUpdate.comments:type_name -> status.Comment
-	12, // 8: status.LongCommit.timestamp:type_name -> google.protobuf.Timestamp
-	12, // 9: status.Comment.timestamp:type_name -> google.protobuf.Timestamp
-	12, // 10: status.ResponseMetadata.timestamp:type_name -> google.protobuf.Timestamp
-	12, // 11: status.AddCommentResponse.timestamp:type_name -> google.protobuf.Timestamp
-	12, // 12: status.DeleteCommentRequest.timestamp:type_name -> google.protobuf.Timestamp
-	0,  // 13: status.StatusService.GetIncrementalCommits:input_type -> status.GetIncrementalCommitsRequest
-	8,  // 14: status.StatusService.AddComment:input_type -> status.AddCommentRequest
-	10, // 15: status.StatusService.DeleteComment:input_type -> status.DeleteCommentRequest
-	1,  // 16: status.StatusService.GetIncrementalCommits:output_type -> status.GetIncrementalCommitsResponse
-	9,  // 17: status.StatusService.AddComment:output_type -> status.AddCommentResponse
-	11, // 18: status.StatusService.DeleteComment:output_type -> status.DeleteCommentResponse
-	16, // [16:19] is the sub-list for method output_type
-	13, // [13:16] is the sub-list for method input_type
-	13, // [13:13] is the sub-list for extension type_name
-	13, // [13:13] is the sub-list for extension extendee
-	0,  // [0:13] is the sub-list for field type_name
+	15, // 8: status.LongCommit.timestamp:type_name -> google.protobuf.Timestamp
+	15, // 9: status.Comment.timestamp:type_name -> google.protobuf.Timestamp
+	15, // 10: status.ResponseMetadata.timestamp:type_name -> google.protobuf.Timestamp
+	15, // 11: status.AddCommentResponse.timestamp:type_name -> google.protobuf.Timestamp
+	15, // 12: status.DeleteCommentRequest.timestamp:type_name -> google.protobuf.Timestamp
+	14, // 13: status.GetAutorollerStatusesResponse.rollers:type_name -> status.AutorollerStatus
+	0,  // 14: status.StatusService.GetIncrementalCommits:input_type -> status.GetIncrementalCommitsRequest
+	8,  // 15: status.StatusService.AddComment:input_type -> status.AddCommentRequest
+	10, // 16: status.StatusService.DeleteComment:input_type -> status.DeleteCommentRequest
+	12, // 17: status.StatusService.GetAutorollerStatuses:input_type -> status.GetAutorollerStatusesRequest
+	1,  // 18: status.StatusService.GetIncrementalCommits:output_type -> status.GetIncrementalCommitsResponse
+	9,  // 19: status.StatusService.AddComment:output_type -> status.AddCommentResponse
+	11, // 20: status.StatusService.DeleteComment:output_type -> status.DeleteCommentResponse
+	13, // 21: status.StatusService.GetAutorollerStatuses:output_type -> status.GetAutorollerStatusesResponse
+	18, // [18:22] is the sub-list for method output_type
+	14, // [14:18] is the sub-list for method input_type
+	14, // [14:14] is the sub-list for extension type_name
+	14, // [14:14] is the sub-list for extension extendee
+	0,  // [0:14] is the sub-list for field type_name
 }
 
 func init() { file_status_proto_init() }
@@ -1312,6 +1527,42 @@
 				return nil
 			}
 		}
+		file_status_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetAutorollerStatusesRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_status_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetAutorollerStatusesResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_status_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AutorollerStatus); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	file_status_proto_msgTypes[8].OneofWrappers = []interface{}{
 		(*AddCommentRequest_Commit)(nil),
@@ -1324,7 +1575,7 @@
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_status_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   12,
+			NumMessages:   15,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
diff --git a/status/go/rpc/status.proto b/status/go/rpc/status.proto
index 7c4b780..f0e88d7 100644
--- a/status/go/rpc/status.proto
+++ b/status/go/rpc/status.proto
@@ -12,6 +12,8 @@
   rpc AddComment(AddCommentRequest) returns (AddCommentResponse);
   // Method to delete a comment to a task, commit, or task spec.
   rpc DeleteComment(DeleteCommentRequest) returns (DeleteCommentResponse);
+  // Method to get latest status of various autorollers.
+  rpc GetAutorollerStatuses(GetAutorollerStatusesRequest) returns (GetAutorollerStatusesResponse);
 }
 
 // Request for updated commit/task/comment/branch/etc data.
@@ -118,3 +120,20 @@
 }
 // Empty, valid response to DeleteComment, only success/failure needs to be conveyed.
 message DeleteCommentResponse{}
+
+// Empty, no parameters needed to ask for latest autoroller status info.
+message GetAutorollerStatusesRequest{}
+
+message GetAutorollerStatusesResponse {
+  repeated AutorollerStatus rollers = 1;
+}
+
+message AutorollerStatus {
+  string name = 1;
+  string current_roll_rev = 2;
+  string last_roll_rev = 3;
+  string mode = 4;
+  int32 num_failed = 5;
+  int32 num_behind = 6;
+  string url = 7;
+}
diff --git a/status/go/rpc/status.twirp.go b/status/go/rpc/status.twirp.go
index cb289e0..b958e67 100644
--- a/status/go/rpc/status.twirp.go
+++ b/status/go/rpc/status.twirp.go
@@ -55,6 +55,9 @@
 
 	// Method to delete a comment to a task, commit, or task spec.
 	DeleteComment(context.Context, *DeleteCommentRequest) (*DeleteCommentResponse, error)
+
+	// Method to get latest status of various autorollers.
+	GetAutorollerStatuses(context.Context, *GetAutorollerStatusesRequest) (*GetAutorollerStatusesResponse, error)
 }
 
 // =============================
@@ -63,7 +66,7 @@
 
 type statusServiceProtobufClient struct {
 	client HTTPClient
-	urls   [3]string
+	urls   [4]string
 	opts   twirp.ClientOptions
 }
 
@@ -80,10 +83,11 @@
 	}
 
 	prefix := urlBase(addr) + StatusServicePathPrefix
-	urls := [3]string{
+	urls := [4]string{
 		prefix + "GetIncrementalCommits",
 		prefix + "AddComment",
 		prefix + "DeleteComment",
+		prefix + "GetAutorollerStatuses",
 	}
 
 	return &statusServiceProtobufClient{
@@ -153,13 +157,33 @@
 	return out, nil
 }
 
+func (c *statusServiceProtobufClient) GetAutorollerStatuses(ctx context.Context, in *GetAutorollerStatusesRequest) (*GetAutorollerStatusesResponse, error) {
+	ctx = ctxsetters.WithPackageName(ctx, "status")
+	ctx = ctxsetters.WithServiceName(ctx, "StatusService")
+	ctx = ctxsetters.WithMethodName(ctx, "GetAutorollerStatuses")
+	out := new(GetAutorollerStatusesResponse)
+	ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[3], in, out)
+	if err != nil {
+		twerr, ok := err.(twirp.Error)
+		if !ok {
+			twerr = twirp.InternalErrorWith(err)
+		}
+		callClientError(ctx, c.opts.Hooks, twerr)
+		return nil, err
+	}
+
+	callClientResponseReceived(ctx, c.opts.Hooks)
+
+	return out, nil
+}
+
 // =========================
 // StatusService JSON Client
 // =========================
 
 type statusServiceJSONClient struct {
 	client HTTPClient
-	urls   [3]string
+	urls   [4]string
 	opts   twirp.ClientOptions
 }
 
@@ -176,10 +200,11 @@
 	}
 
 	prefix := urlBase(addr) + StatusServicePathPrefix
-	urls := [3]string{
+	urls := [4]string{
 		prefix + "GetIncrementalCommits",
 		prefix + "AddComment",
 		prefix + "DeleteComment",
+		prefix + "GetAutorollerStatuses",
 	}
 
 	return &statusServiceJSONClient{
@@ -249,6 +274,26 @@
 	return out, nil
 }
 
+func (c *statusServiceJSONClient) GetAutorollerStatuses(ctx context.Context, in *GetAutorollerStatusesRequest) (*GetAutorollerStatusesResponse, error) {
+	ctx = ctxsetters.WithPackageName(ctx, "status")
+	ctx = ctxsetters.WithServiceName(ctx, "StatusService")
+	ctx = ctxsetters.WithMethodName(ctx, "GetAutorollerStatuses")
+	out := new(GetAutorollerStatusesResponse)
+	ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[3], in, out)
+	if err != nil {
+		twerr, ok := err.(twirp.Error)
+		if !ok {
+			twerr = twirp.InternalErrorWith(err)
+		}
+		callClientError(ctx, c.opts.Hooks, twerr)
+		return nil, err
+	}
+
+	callClientResponseReceived(ctx, c.opts.Hooks)
+
+	return out, nil
+}
+
 // ============================
 // StatusService Server Handler
 // ============================
@@ -306,6 +351,9 @@
 	case "/twirp/status.StatusService/DeleteComment":
 		s.serveDeleteComment(ctx, resp, req)
 		return
+	case "/twirp/status.StatusService/GetAutorollerStatuses":
+		s.serveGetAutorollerStatuses(ctx, resp, req)
+		return
 	default:
 		msg := fmt.Sprintf("no handler for path %q", req.URL.Path)
 		err = badRouteError(msg, req.Method, req.URL.Path)
@@ -701,6 +749,135 @@
 	callResponseSent(ctx, s.hooks)
 }
 
+func (s *statusServiceServer) serveGetAutorollerStatuses(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
+	header := req.Header.Get("Content-Type")
+	i := strings.Index(header, ";")
+	if i == -1 {
+		i = len(header)
+	}
+	switch strings.TrimSpace(strings.ToLower(header[:i])) {
+	case "application/json":
+		s.serveGetAutorollerStatusesJSON(ctx, resp, req)
+	case "application/protobuf":
+		s.serveGetAutorollerStatusesProtobuf(ctx, resp, req)
+	default:
+		msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type"))
+		twerr := badRouteError(msg, req.Method, req.URL.Path)
+		s.writeError(ctx, resp, twerr)
+	}
+}
+
+func (s *statusServiceServer) serveGetAutorollerStatusesJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
+	var err error
+	ctx = ctxsetters.WithMethodName(ctx, "GetAutorollerStatuses")
+	ctx, err = callRequestRouted(ctx, s.hooks)
+	if err != nil {
+		s.writeError(ctx, resp, err)
+		return
+	}
+
+	reqContent := new(GetAutorollerStatusesRequest)
+	unmarshaler := jsonpb.Unmarshaler{AllowUnknownFields: true}
+	if err = unmarshaler.Unmarshal(req.Body, reqContent); err != nil {
+		s.writeError(ctx, resp, malformedRequestError("the json request could not be decoded"))
+		return
+	}
+
+	// Call service method
+	var respContent *GetAutorollerStatusesResponse
+	func() {
+		defer ensurePanicResponses(ctx, resp, s.hooks)
+		respContent, err = s.StatusService.GetAutorollerStatuses(ctx, reqContent)
+	}()
+
+	if err != nil {
+		s.writeError(ctx, resp, err)
+		return
+	}
+	if respContent == nil {
+		s.writeError(ctx, resp, twirp.InternalError("received a nil *GetAutorollerStatusesResponse and nil error while calling GetAutorollerStatuses. nil responses are not supported"))
+		return
+	}
+
+	ctx = callResponsePrepared(ctx, s.hooks)
+
+	var buf bytes.Buffer
+	marshaler := &jsonpb.Marshaler{OrigName: true}
+	if err = marshaler.Marshal(&buf, respContent); err != nil {
+		s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response"))
+		return
+	}
+
+	ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK)
+	respBytes := buf.Bytes()
+	resp.Header().Set("Content-Type", "application/json")
+	resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes)))
+	resp.WriteHeader(http.StatusOK)
+
+	if n, err := resp.Write(respBytes); err != nil {
+		msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error())
+		twerr := twirp.NewError(twirp.Unknown, msg)
+		callError(ctx, s.hooks, twerr)
+	}
+	callResponseSent(ctx, s.hooks)
+}
+
+func (s *statusServiceServer) serveGetAutorollerStatusesProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
+	var err error
+	ctx = ctxsetters.WithMethodName(ctx, "GetAutorollerStatuses")
+	ctx, err = callRequestRouted(ctx, s.hooks)
+	if err != nil {
+		s.writeError(ctx, resp, err)
+		return
+	}
+
+	buf, err := ioutil.ReadAll(req.Body)
+	if err != nil {
+		s.writeError(ctx, resp, wrapInternal(err, "failed to read request body"))
+		return
+	}
+	reqContent := new(GetAutorollerStatusesRequest)
+	if err = proto.Unmarshal(buf, reqContent); err != nil {
+		s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded"))
+		return
+	}
+
+	// Call service method
+	var respContent *GetAutorollerStatusesResponse
+	func() {
+		defer ensurePanicResponses(ctx, resp, s.hooks)
+		respContent, err = s.StatusService.GetAutorollerStatuses(ctx, reqContent)
+	}()
+
+	if err != nil {
+		s.writeError(ctx, resp, err)
+		return
+	}
+	if respContent == nil {
+		s.writeError(ctx, resp, twirp.InternalError("received a nil *GetAutorollerStatusesResponse and nil error while calling GetAutorollerStatuses. nil responses are not supported"))
+		return
+	}
+
+	ctx = callResponsePrepared(ctx, s.hooks)
+
+	respBytes, err := proto.Marshal(respContent)
+	if err != nil {
+		s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response"))
+		return
+	}
+
+	ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK)
+	resp.Header().Set("Content-Type", "application/protobuf")
+	resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes)))
+	resp.WriteHeader(http.StatusOK)
+	if n, err := resp.Write(respBytes); err != nil {
+		msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error())
+		twerr := twirp.NewError(twirp.Unknown, msg)
+		callError(ctx, s.hooks, twerr)
+	}
+	callResponseSent(ctx, s.hooks)
+}
+
 func (s *statusServiceServer) ServiceDescriptor() ([]byte, int) {
 	return twirpFileDescriptor0, 0
 }
@@ -1229,62 +1406,71 @@
 }
 
 var twirpFileDescriptor0 = []byte{
-	// 898 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xdd, 0x6e, 0x1b, 0x45,
-	0x14, 0xee, 0xac, 0x1d, 0xc7, 0x7b, 0xe2, 0x98, 0x74, 0xd4, 0x36, 0x53, 0xb7, 0x51, 0xc3, 0xaa,
-	0x95, 0x2c, 0x40, 0x36, 0x0d, 0x5c, 0x70, 0x4b, 0x82, 0x20, 0x95, 0x4a, 0x41, 0x93, 0x72, 0xc3,
-	0x8d, 0x35, 0xf6, 0x8e, 0xd7, 0x4b, 0xbc, 0x3b, 0xcb, 0xcc, 0xd8, 0x28, 0x17, 0xdc, 0xf7, 0x39,
-	0x78, 0x04, 0x6e, 0x90, 0xb8, 0x45, 0xbc, 0x04, 0x4f, 0x83, 0xe6, 0x6f, 0x6d, 0xe7, 0xa7, 0x6d,
-	0x7a, 0x37, 0xe7, 0x9c, 0x6f, 0xcf, 0xcf, 0xf7, 0x9d, 0x99, 0x85, 0x8e, 0xd2, 0x4c, 0x2f, 0xd4,
-	0xa0, 0x92, 0x42, 0x0b, 0xdc, 0x72, 0x56, 0xef, 0x49, 0x26, 0x44, 0x36, 0xe7, 0x43, 0xeb, 0x1d,
-	0x2f, 0xa6, 0x43, 0x9d, 0x17, 0x5c, 0x69, 0x56, 0x54, 0x0e, 0x98, 0xfc, 0x85, 0xe0, 0xf1, 0x77,
-	0x5c, 0xbf, 0x28, 0x27, 0x92, 0x17, 0xbc, 0xd4, 0x6c, 0x7e, 0x22, 0x8a, 0x22, 0xd7, 0x8a, 0xf2,
-	0x5f, 0x17, 0x5c, 0x69, 0x3c, 0x80, 0xe6, 0x54, 0x8a, 0x82, 0xa0, 0x43, 0xd4, 0xdf, 0x39, 0xea,
-	0x0d, 0x5c, 0xc2, 0x41, 0x48, 0x38, 0x78, 0x1d, 0x12, 0x52, 0x8b, 0xc3, 0x9f, 0x40, 0xa4, 0x05,
-	0x89, 0xde, 0x89, 0x8e, 0xb4, 0xc0, 0x1d, 0x40, 0x25, 0x69, 0x1c, 0xa2, 0x7e, 0x83, 0xa2, 0x12,
-	0xef, 0x41, 0xa3, 0x12, 0x29, 0x69, 0x1e, 0xa2, 0x7e, 0x4c, 0xcd, 0x11, 0x3f, 0x82, 0x58, 0xf2,
-	0x4a, 0x8c, 0x2a, 0xa6, 0x67, 0x64, 0xcb, 0xfa, 0xdb, 0xc6, 0xf1, 0x23, 0xd3, 0xb3, 0xe4, 0x0d,
-	0x82, 0x83, 0x1b, 0x3a, 0x57, 0x95, 0x28, 0x15, 0xc7, 0x5f, 0x42, 0xbb, 0xe0, 0x9a, 0xa5, 0x4c,
-	0x33, 0xdf, 0x3e, 0x19, 0x78, 0x96, 0x02, 0xe6, 0x7b, 0x1f, 0xa7, 0x35, 0x12, 0x3f, 0x87, 0xd6,
-	0xa2, 0x4a, 0x99, 0xe6, 0x7e, 0x88, 0x87, 0xe1, 0x9b, 0xb5, 0x4a, 0x3f, 0x59, 0x00, 0xf5, 0xc0,
-	0xe4, 0x5f, 0x04, 0x77, 0xaf, 0x44, 0xf1, 0x67, 0xb0, 0x3d, 0x71, 0x1d, 0x11, 0x74, 0xd8, 0xe8,
-	0xef, 0x1c, 0xe1, 0x90, 0xe9, 0xa5, 0x28, 0x33, 0xd7, 0x2c, 0x0d, 0x10, 0xfc, 0x1c, 0x3a, 0x63,
-	0xc9, 0xca, 0xc9, 0x6c, 0x34, 0xe3, 0x2c, 0x55, 0x24, 0xb2, 0x9f, 0x74, 0xc3, 0x27, 0xc7, 0x36,
-	0x46, 0x77, 0x1c, 0xe6, 0xd4, 0x40, 0x70, 0x02, 0x5b, 0x9a, 0xa9, 0x73, 0x45, 0x1a, 0x16, 0xdb,
-	0x09, 0xd8, 0xd7, 0x4c, 0x9d, 0x53, 0x17, 0xc2, 0x9f, 0x42, 0xdb, 0x54, 0xe0, 0xa5, 0x56, 0xa4,
-	0x69, 0x61, 0x1f, 0x05, 0xd8, 0x89, 0xf3, 0xd3, 0x1a, 0x90, 0x7c, 0x0e, 0x2d, 0x57, 0x07, 0x63,
-	0x68, 0x96, 0xac, 0xe0, 0x96, 0xb6, 0x98, 0xda, 0xb3, 0xf1, 0x99, 0xd6, 0x2c, 0x2d, 0x31, 0xb5,
-	0xe7, 0xe4, 0x0f, 0x04, 0x4d, 0x53, 0x0e, 0x93, 0xcd, 0x61, 0xe3, 0xd5, 0x60, 0x21, 0x55, 0xb4,
-	0x96, 0xaa, 0x0b, 0x51, 0x9e, 0x5a, 0xe5, 0x63, 0x1a, 0xe5, 0x29, 0xee, 0x41, 0x5b, 0xf2, 0x65,
-	0xae, 0x72, 0x51, 0x7a, 0xfd, 0x6b, 0x1b, 0x3f, 0x00, 0xbf, 0xcc, 0x7e, 0x03, 0xbc, 0x85, 0xfb,
-	0xb0, 0xa7, 0x7e, 0x63, 0xb2, 0xc8, 0xcb, 0x6c, 0x64, 0x66, 0x1d, 0xe5, 0x29, 0x69, 0x59, 0x44,
-	0x37, 0xf8, 0x4d, 0x67, 0x2f, 0xd2, 0xe4, 0x6f, 0x04, 0xb0, 0xa2, 0xdc, 0xce, 0xc1, 0xd4, 0x2c,
-	0xcc, 0x66, 0xce, 0xa6, 0x08, 0x5b, 0xe8, 0x99, 0x90, 0xbe, 0x4d, 0x6f, 0x99, 0xb1, 0xd4, 0x62,
-	0xfc, 0x0b, 0x9f, 0x68, 0xdf, 0x6d, 0x30, 0x4d, 0xa4, 0x62, 0xb2, 0xe6, 0x35, 0xa6, 0xc1, 0x34,
-	0xf9, 0xc7, 0x22, 0xbd, 0xf0, 0xed, 0xda, 0x33, 0xfe, 0x0a, 0xe2, 0xfa, 0xe6, 0xd9, 0x2e, 0xdf,
-	0x7e, 0x39, 0x56, 0xe0, 0xe4, 0x9f, 0x08, 0xb6, 0xbd, 0x52, 0x9e, 0x36, 0x54, 0xd3, 0x86, 0xa1,
-	0x69, 0xae, 0x43, 0xa0, 0xd6, 0x9c, 0x37, 0x2b, 0x35, 0x6e, 0x51, 0xc9, 0x64, 0x5b, 0x28, 0x2e,
-	0xbd, 0x00, 0xf6, 0x6c, 0xa6, 0x2c, 0xb8, 0x52, 0x2c, 0xe3, 0x7e, 0x9c, 0x60, 0x9a, 0x48, 0xca,
-	0xe7, 0x5c, 0x73, 0xc7, 0x7a, 0x9b, 0x06, 0x13, 0x3f, 0x83, 0x6e, 0x9e, 0x95, 0x42, 0xf2, 0xd1,
-	0x94, 0xe5, 0xf3, 0x85, 0xe4, 0x64, 0xdb, 0x02, 0x76, 0x9d, 0xf7, 0x5b, 0xe7, 0xc4, 0xf7, 0x60,
-	0x6b, 0x3a, 0x67, 0xe7, 0x17, 0xa4, 0x6d, 0xa3, 0xce, 0xc0, 0x4f, 0xa1, 0x6b, 0xc5, 0x54, 0x15,
-	0x9f, 0x8c, 0xec, 0xde, 0xc4, 0xb6, 0x6e, 0xc7, 0x78, 0xcf, 0x2a, 0x3e, 0x79, 0x65, 0xf6, 0x67,
-	0x1f, 0xb6, 0x83, 0xe4, 0xe0, 0xf4, 0xd2, 0x56, 0x6a, 0xa3, 0xa3, 0xdb, 0x3b, 0xb2, 0xe3, 0xfc,
-	0xce, 0x4a, 0x7e, 0x87, 0xbd, 0xcb, 0x57, 0x1e, 0x1f, 0x00, 0x28, 0xcd, 0xa4, 0x1e, 0x89, 0x25,
-	0x97, 0x96, 0xd5, 0x36, 0x8d, 0xad, 0xe7, 0x87, 0x25, 0x97, 0xe1, 0x39, 0x8a, 0x56, 0xcf, 0xd1,
-	0x07, 0x53, 0x9b, 0xfc, 0x87, 0xe0, 0xee, 0xd7, 0x69, 0x1a, 0x6e, 0x9c, 0x7f, 0x5a, 0x83, 0x7c,
-	0x68, 0x4d, 0x3e, 0x52, 0x0f, 0x60, 0x0b, 0x9f, 0xde, 0x09, 0x23, 0xe0, 0x03, 0x88, 0x6b, 0x66,
-	0xdc, 0x32, 0x9e, 0xde, 0xa1, 0xed, 0x40, 0x0b, 0x7e, 0xb8, 0xa2, 0xa4, 0x19, 0xbe, 0xf4, 0xa4,
-	0xdc, 0x2c, 0x62, 0xad, 0x41, 0x6b, 0x5d, 0x83, 0xf7, 0x13, 0xf0, 0xb8, 0x05, 0x4d, 0x7d, 0x51,
-	0xf1, 0xe4, 0x15, 0xe0, 0xf5, 0xd9, 0xfc, 0xe3, 0xbb, 0x41, 0x16, 0xba, 0x0d, 0x59, 0x7f, 0x22,
-	0xb8, 0xf7, 0x8d, 0xdd, 0xa5, 0xf7, 0xe0, 0xeb, 0xc1, 0x26, 0x5f, 0x35, 0x5b, 0x8f, 0xae, 0xb0,
-	0xb5, 0xc6, 0xd5, 0xfe, 0x25, 0xae, 0x6a, 0xa6, 0x36, 0x9a, 0xde, 0xba, 0x4d, 0xd3, 0xfb, 0x70,
-	0xff, 0x52, 0xcf, 0x8e, 0x87, 0xa3, 0x37, 0x11, 0xec, 0x9e, 0xd9, 0x17, 0xeb, 0x8c, 0xcb, 0x65,
-	0x3e, 0xe1, 0x38, 0x85, 0xfb, 0xd7, 0xfe, 0xb7, 0xf0, 0xd3, 0xf0, 0x32, 0xbf, 0xed, 0x87, 0xdc,
-	0x7b, 0xf6, 0x0e, 0x94, 0xe7, 0xff, 0x04, 0x60, 0xa5, 0x0a, 0xae, 0x7f, 0x62, 0x57, 0xb6, 0xb0,
-	0xd7, 0xbb, 0x2e, 0xe4, 0x93, 0xbc, 0x84, 0xdd, 0x8d, 0xa9, 0xf0, 0xe3, 0x00, 0xbe, 0x4e, 0xa0,
-	0xde, 0xc1, 0x0d, 0x51, 0x97, 0xed, 0xf8, 0xe3, 0x9f, 0x9f, 0x64, 0x62, 0xa0, 0xce, 0x73, 0x36,
-	0x10, 0x32, 0x1b, 0xe6, 0xe5, 0x54, 0xb2, 0xa1, 0xfb, 0x62, 0x98, 0x89, 0xa1, 0xac, 0x26, 0xe3,
-	0x96, 0x65, 0xf9, 0x8b, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x95, 0xd0, 0xb8, 0xce, 0x08,
-	0x00, 0x00,
+	// 1046 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xcd, 0x8e, 0x1b, 0x45,
+	0x10, 0xce, 0x8c, 0xbd, 0x5e, 0x4f, 0xd9, 0x6b, 0x9c, 0x56, 0x92, 0x9d, 0x38, 0x59, 0xb2, 0x8c,
+	0x12, 0xc9, 0x02, 0x64, 0x93, 0x85, 0x03, 0xd7, 0xec, 0xa2, 0xb0, 0x91, 0x42, 0x40, 0xbd, 0xe1,
+	0xc2, 0xc5, 0x6a, 0x7b, 0xda, 0xf6, 0xb0, 0x33, 0xd3, 0x43, 0x77, 0x8f, 0xd1, 0x1e, 0xb8, 0xf3,
+	0x1c, 0x3c, 0x02, 0x17, 0x24, 0xae, 0x88, 0x77, 0x40, 0x3c, 0x04, 0xcf, 0x80, 0xfa, 0x6f, 0xfc,
+	0xb3, 0x76, 0x92, 0xcd, 0xad, 0xab, 0xea, 0xeb, 0xea, 0xaa, 0xaf, 0x7e, 0x66, 0xa0, 0x2d, 0x24,
+	0x91, 0xa5, 0x18, 0x14, 0x9c, 0x49, 0x86, 0x1a, 0x46, 0xea, 0x3d, 0x9a, 0x31, 0x36, 0x4b, 0xe9,
+	0x50, 0x6b, 0xc7, 0xe5, 0x74, 0x28, 0x93, 0x8c, 0x0a, 0x49, 0xb2, 0xc2, 0x00, 0xa3, 0x3f, 0x3c,
+	0x78, 0xf8, 0x35, 0x95, 0x2f, 0xf2, 0x09, 0xa7, 0x19, 0xcd, 0x25, 0x49, 0xcf, 0x58, 0x96, 0x25,
+	0x52, 0x60, 0xfa, 0x53, 0x49, 0x85, 0x44, 0x03, 0xa8, 0x4f, 0x39, 0xcb, 0x42, 0xef, 0xd8, 0xeb,
+	0xb7, 0x4e, 0x7a, 0x03, 0xe3, 0x70, 0xe0, 0x1c, 0x0e, 0x5e, 0x3b, 0x87, 0x58, 0xe3, 0xd0, 0xc7,
+	0xe0, 0x4b, 0x16, 0xfa, 0x6f, 0x45, 0xfb, 0x92, 0xa1, 0x36, 0x78, 0x79, 0x58, 0x3b, 0xf6, 0xfa,
+	0x35, 0xec, 0xe5, 0xa8, 0x0b, 0xb5, 0x82, 0xc5, 0x61, 0xfd, 0xd8, 0xeb, 0x07, 0x58, 0x1d, 0xd1,
+	0x03, 0x08, 0x38, 0x2d, 0xd8, 0xa8, 0x20, 0x72, 0x1e, 0xee, 0x69, 0x7d, 0x53, 0x29, 0xbe, 0x23,
+	0x72, 0x1e, 0xfd, 0xea, 0xc1, 0xd1, 0x8e, 0xc8, 0x45, 0xc1, 0x72, 0x41, 0xd1, 0x17, 0xd0, 0xcc,
+	0xa8, 0x24, 0x31, 0x91, 0xc4, 0x86, 0x1f, 0x0e, 0x2c, 0x4b, 0x0e, 0xf3, 0x8d, 0xb5, 0xe3, 0x0a,
+	0x89, 0x9e, 0x42, 0xa3, 0x2c, 0x62, 0x22, 0xa9, 0x4d, 0xe2, 0xbe, 0xbb, 0xb3, 0xf2, 0xd2, 0xf7,
+	0x1a, 0x80, 0x2d, 0x30, 0xfa, 0xdb, 0x83, 0xdb, 0xd7, 0xac, 0xe8, 0x53, 0xd8, 0x9f, 0x98, 0x88,
+	0x42, 0xef, 0xb8, 0xd6, 0x6f, 0x9d, 0x20, 0xe7, 0xe9, 0x25, 0xcb, 0x67, 0x26, 0x58, 0xec, 0x20,
+	0xe8, 0x29, 0xb4, 0xc7, 0x9c, 0xe4, 0x93, 0xf9, 0x68, 0x4e, 0x49, 0x2c, 0x42, 0x5f, 0x5f, 0xe9,
+	0xb8, 0x2b, 0xa7, 0xda, 0x86, 0x5b, 0x06, 0x73, 0xae, 0x20, 0x28, 0x82, 0x3d, 0x49, 0xc4, 0xa5,
+	0x08, 0x6b, 0x1a, 0xdb, 0x76, 0xd8, 0xd7, 0x44, 0x5c, 0x62, 0x63, 0x42, 0x9f, 0x40, 0x53, 0xbd,
+	0x40, 0x73, 0x29, 0xc2, 0xba, 0x86, 0x7d, 0xe0, 0x60, 0x67, 0x46, 0x8f, 0x2b, 0x40, 0xf4, 0x19,
+	0x34, 0xcc, 0x3b, 0x08, 0x41, 0x3d, 0x27, 0x19, 0xd5, 0xb4, 0x05, 0x58, 0x9f, 0x95, 0x4e, 0x85,
+	0xa6, 0x69, 0x09, 0xb0, 0x3e, 0x47, 0xbf, 0x79, 0x50, 0x57, 0xcf, 0xa1, 0x70, 0x3d, 0xd9, 0x60,
+	0x99, 0x98, 0x73, 0xe5, 0xaf, 0xb8, 0xea, 0x80, 0x9f, 0xc4, 0xba, 0xf2, 0x01, 0xf6, 0x93, 0x18,
+	0xf5, 0xa0, 0xc9, 0xe9, 0x22, 0x11, 0x09, 0xcb, 0x6d, 0xfd, 0x2b, 0x19, 0xdd, 0x03, 0xdb, 0xcc,
+	0xb6, 0x03, 0xac, 0x84, 0xfa, 0xd0, 0x15, 0x3f, 0x13, 0x9e, 0x25, 0xf9, 0x6c, 0xa4, 0x72, 0x1d,
+	0x25, 0x71, 0xd8, 0xd0, 0x88, 0x8e, 0xd3, 0xab, 0xc8, 0x5e, 0xc4, 0xd1, 0x9f, 0x1e, 0xc0, 0x92,
+	0x72, 0x9d, 0x07, 0x11, 0x73, 0x97, 0x9b, 0x3a, 0xab, 0x47, 0x48, 0x29, 0xe7, 0x8c, 0xdb, 0x30,
+	0xad, 0xa4, 0xd2, 0x12, 0xe5, 0xf8, 0x47, 0x3a, 0x91, 0x36, 0x5a, 0x27, 0x2a, 0x4b, 0x41, 0x78,
+	0xc5, 0x6b, 0x80, 0x9d, 0xa8, 0xfc, 0x8f, 0x59, 0x7c, 0x65, 0xc3, 0xd5, 0x67, 0xf4, 0x25, 0x04,
+	0xd5, 0xe4, 0xe9, 0x28, 0xdf, 0x3c, 0x1c, 0x4b, 0x70, 0xf4, 0x97, 0x0f, 0xfb, 0xb6, 0x52, 0x96,
+	0x36, 0xaf, 0xa2, 0x0d, 0x41, 0x5d, 0x8d, 0x83, 0xa3, 0x56, 0x9d, 0xd7, 0x5f, 0xaa, 0xdd, 0xe0,
+	0x25, 0xe5, 0xad, 0x14, 0x94, 0xdb, 0x02, 0xe8, 0xb3, 0xca, 0x32, 0xa3, 0x42, 0x90, 0x19, 0xb5,
+	0xe9, 0x38, 0x51, 0x59, 0x62, 0x9a, 0x52, 0x49, 0x0d, 0xeb, 0x4d, 0xec, 0x44, 0xf4, 0x04, 0x3a,
+	0xc9, 0x2c, 0x67, 0x9c, 0x8e, 0xa6, 0x24, 0x49, 0x4b, 0x4e, 0xc3, 0x7d, 0x0d, 0x38, 0x30, 0xda,
+	0xe7, 0x46, 0x89, 0xee, 0xc0, 0xde, 0x34, 0x25, 0x97, 0x57, 0x61, 0x53, 0x5b, 0x8d, 0x80, 0x1e,
+	0x43, 0x47, 0x17, 0x53, 0x14, 0x74, 0x32, 0xd2, 0x7d, 0x13, 0xe8, 0x77, 0xdb, 0x4a, 0x7b, 0x51,
+	0xd0, 0xc9, 0x2b, 0xd5, 0x3f, 0x87, 0xb0, 0xef, 0x4a, 0x0e, 0xa6, 0x5e, 0x52, 0x97, 0x5a, 0xd5,
+	0xd1, 0xf4, 0x5d, 0xd8, 0x32, 0x7a, 0x23, 0x45, 0xbf, 0x40, 0x77, 0x73, 0xe4, 0xd1, 0x11, 0x80,
+	0x90, 0x84, 0xcb, 0x11, 0x5b, 0x50, 0xae, 0x59, 0x6d, 0xe2, 0x40, 0x6b, 0xbe, 0x5d, 0x50, 0xee,
+	0xd6, 0x91, 0xbf, 0x5c, 0x47, 0xef, 0x4d, 0x6d, 0xf4, 0xaf, 0x07, 0xb7, 0x9f, 0xc5, 0xb1, 0x9b,
+	0x38, 0xbb, 0x5a, 0x5d, 0xf9, 0xbc, 0x95, 0xf2, 0x85, 0x55, 0x02, 0xfa, 0xe1, 0xf3, 0x5b, 0x2e,
+	0x05, 0x74, 0x04, 0x41, 0xc5, 0x8c, 0x69, 0xc6, 0xf3, 0x5b, 0xb8, 0xe9, 0x68, 0x41, 0xf7, 0x97,
+	0x94, 0xd4, 0xdd, 0x4d, 0x4b, 0xca, 0xee, 0x22, 0x56, 0x35, 0x68, 0xac, 0xd6, 0xe0, 0xdd, 0x0a,
+	0x78, 0xda, 0x80, 0xba, 0xbc, 0x2a, 0x68, 0xf4, 0x0a, 0xd0, 0x6a, 0x6e, 0x76, 0xf9, 0xae, 0x91,
+	0xe5, 0xdd, 0x84, 0xac, 0xdf, 0x3d, 0xb8, 0xf3, 0x95, 0xee, 0xa5, 0x77, 0xe0, 0xeb, 0xde, 0x3a,
+	0x5f, 0x15, 0x5b, 0x0f, 0xae, 0xb1, 0xb5, 0xc2, 0xd5, 0xe1, 0x06, 0x57, 0x15, 0x53, 0x6b, 0x41,
+	0xef, 0xdd, 0x24, 0xe8, 0x43, 0xb8, 0xbb, 0x11, 0xb3, 0xe1, 0x21, 0xfa, 0x50, 0x7f, 0x5f, 0x9f,
+	0x95, 0x92, 0x71, 0x96, 0xa6, 0x94, 0x5f, 0xe8, 0xed, 0x45, 0xdd, 0xf7, 0x35, 0xba, 0xd0, 0x5f,
+	0xb1, 0x6d, 0x76, 0x4b, 0xe4, 0x09, 0xec, 0x1b, 0x8b, 0xfb, 0x8c, 0x54, 0x1f, 0xb1, 0xcd, 0x4b,
+	0xd8, 0x01, 0xa3, 0x7f, 0x3c, 0xe8, 0x6e, 0x5a, 0xb7, 0xee, 0xf4, 0x3e, 0x74, 0x27, 0x25, 0x57,
+	0x7b, 0x6b, 0xa4, 0xb0, 0x23, 0x4e, 0x17, 0x96, 0xc8, 0x8e, 0xd5, 0x63, 0x96, 0xa6, 0x98, 0x2e,
+	0x50, 0x04, 0x07, 0x29, 0x11, 0x2b, 0x30, 0x43, 0x6a, 0x4b, 0x29, 0x1d, 0x06, 0x41, 0x3d, 0x63,
+	0x31, 0x75, 0x1b, 0x44, 0x9d, 0xd5, 0x94, 0xe5, 0x65, 0xa6, 0x3b, 0x89, 0xc6, 0x9a, 0xd3, 0x3d,
+	0x1c, 0xe4, 0x65, 0xf6, 0x5c, 0x2b, 0x9c, 0x79, 0x4c, 0xe7, 0x49, 0x6e, 0x36, 0x89, 0x31, 0x9f,
+	0x6a, 0x85, 0x1a, 0xc2, 0x92, 0xa7, 0xba, 0xff, 0x02, 0xac, 0x8e, 0x27, 0xff, 0xf9, 0x70, 0x60,
+	0x12, 0xba, 0xa0, 0x7c, 0x91, 0x4c, 0x28, 0x8a, 0xe1, 0xee, 0xd6, 0xff, 0x00, 0xf4, 0xd8, 0x11,
+	0xf5, 0xa6, 0x1f, 0x9c, 0xde, 0x93, 0xb7, 0xa0, 0x6c, 0x19, 0xce, 0x00, 0x96, 0x5d, 0x8e, 0xaa,
+	0x9f, 0x82, 0x6b, 0x53, 0xdd, 0xeb, 0x6d, 0x33, 0x59, 0x27, 0x2f, 0xe1, 0x60, 0xad, 0x4b, 0xd0,
+	0x43, 0x07, 0xde, 0xd6, 0xf0, 0xbd, 0xa3, 0x1d, 0x56, 0xeb, 0xcd, 0x24, 0x7e, 0xbd, 0x75, 0xd6,
+	0x12, 0xdf, 0xd9, 0x79, 0x6b, 0x89, 0xef, 0xee, 0xbf, 0xd3, 0x8f, 0x7e, 0x78, 0x34, 0x63, 0x03,
+	0x71, 0x99, 0x90, 0x01, 0xe3, 0xb3, 0x61, 0x92, 0x4f, 0x39, 0x19, 0x9a, 0x9b, 0xc3, 0x19, 0x1b,
+	0xf2, 0x62, 0x32, 0x6e, 0xe8, 0xd9, 0xf8, 0xfc, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x68, 0x40,
+	0x83, 0xac, 0x84, 0x0a, 0x00, 0x00,
 }
diff --git a/status/go/status/main.go b/status/go/status/main.go
index 0e9a0dd..140ce0c 100644
--- a/status/go/status/main.go
+++ b/status/go/status/main.go
@@ -14,6 +14,7 @@
 	"path"
 	"path/filepath"
 	"runtime"
+	"sort"
 	"strconv"
 	"strings"
 	"sync"
@@ -72,18 +73,19 @@
 
 var (
 	autorollMtx                 sync.RWMutex
-	autorollStatus              []byte                        = nil
-	capacityClient              *capacity.CapacityClient      = nil
-	capacityTemplate            *template.Template            = nil
-	commitsTemplate             *template.Template            = nil
-	experimentalCommitsTemplate *template.Template            = nil
-	iCache                      *incremental.IncrementalCache = nil
-	lkgrObj                     *lkgr.LKGR                    = nil
-	taskDb                      db.RemoteDB                   = nil
-	taskDriverDb                task_driver_db.DB             = nil
-	taskDriverLogs              *logs.LogsManager             = nil
-	tasksPerCommit              *tasksPerCommitCache          = nil
-	tCache                      cache.TaskCache               = nil
+	autorollStatus              []byte                             = nil
+	autorollStatusTwirp         *rpc.GetAutorollerStatusesResponse = nil
+	capacityClient              *capacity.CapacityClient           = nil
+	capacityTemplate            *template.Template                 = nil
+	commitsTemplate             *template.Template                 = nil
+	experimentalCommitsTemplate *template.Template                 = nil
+	iCache                      *incremental.IncrementalCache      = nil
+	lkgrObj                     *lkgr.LKGR                         = nil
+	taskDb                      db.RemoteDB                        = nil
+	taskDriverDb                task_driver_db.DB                  = nil
+	taskDriverLogs              *logs.LogsManager                  = nil
+	tasksPerCommit              *tasksPerCommitCache               = nil
+	tCache                      cache.TaskCache                    = nil
 
 	// AUTOROLLERS maps autoroll frontend host to maps of roller IDs to
 	// their human-friendly display names.
@@ -707,6 +709,12 @@
 	}
 }
 
+func getAutorollerStatusesTwirp() *rpc.GetAutorollerStatusesResponse {
+	autorollMtx.RLock()
+	defer autorollMtx.RUnlock()
+	return autorollStatusTwirp
+}
+
 func runServer(serverURL string, srv http.Handler) {
 	topLevelRouter := mux.NewRouter()
 	topLevelRouter.Use(login.RestrictViewer)
@@ -778,7 +786,7 @@
 		podId = uuid.New().String()
 	}
 
-	repoURLsByName := make(map[string]string)
+	repoURLsByName = make(map[string]string)
 	for _, repoURL := range *repoUrls {
 		repoURLsByName[repoUrlToName(repoURL)] = fmt.Sprintf(gitiles.CommitURL, repoURL, "")
 	}
@@ -874,25 +882,41 @@
 	autorollStatusDB := status.NewDatastoreDB()
 	updateAutorollStatus := func(ctx context.Context) error {
 		statuses := map[string]autoRollStatus{}
+		statusesTwirp := []*rpc.AutorollerStatus{}
 		for host, subMap := range AUTOROLLERS {
 			for roller, friendlyName := range subMap {
 				s, err := autorollStatusDB.Get(ctx, roller)
 				if err != nil {
 					return err
 				}
+				miniStatus := s.AutoRollMiniStatus
+				url := fmt.Sprintf("https://%s/r/%s", host, roller)
 				statuses[friendlyName] = autoRollStatus{
-					AutoRollMiniStatus: s.AutoRollMiniStatus,
-					Url:                fmt.Sprintf("https://%s/r/%s", host, roller),
+					AutoRollMiniStatus: miniStatus,
+					Url:                url,
 				}
+				statusesTwirp = append(statusesTwirp,
+					&rpc.AutorollerStatus{
+						Name:           friendlyName,
+						CurrentRollRev: miniStatus.CurrentRollRev,
+						LastRollRev:    miniStatus.LastRollRev,
+						Mode:           miniStatus.Mode,
+						NumBehind:      int32(miniStatus.NumNotRolledCommits),
+						NumFailed:      int32(miniStatus.NumFailedRolls),
+						Url:            url})
 			}
 		}
 		b, err := json.Marshal(statuses)
 		if err != nil {
 			return err
 		}
+		sort.Slice(statusesTwirp, func(i, j int) bool {
+			return statusesTwirp[i].Name < statusesTwirp[j].Name
+		})
 		autorollMtx.Lock()
 		defer autorollMtx.Unlock()
 		autorollStatus = b
+		autorollStatusTwirp = &rpc.GetAutorollerStatusesResponse{Rollers: statusesTwirp}
 		return nil
 	}
 	if err := updateAutorollStatus(ctx); err != nil {
@@ -916,7 +940,7 @@
 	}
 
 	// Create Twirp Server.
-	twirpServer := rpc.NewStatusServer(iCache, taskDb, getRepoTwirp, MAX_COMMITS_TO_LOAD, DEFAULT_COMMITS_TO_LOAD, podId)
+	twirpServer := rpc.NewStatusServer(iCache, taskDb, getAutorollerStatusesTwirp, getRepoTwirp, MAX_COMMITS_TO_LOAD, DEFAULT_COMMITS_TO_LOAD, podId)
 
 	// Run the server.
 	runServer(serverURL, twirpServer)
diff --git a/status/modules/autoroller-status-sk/autoroller-status-sk-demo.html b/status/modules/autoroller-status-sk/autoroller-status-sk-demo.html
new file mode 100644
index 0000000..fceb4d7
--- /dev/null
+++ b/status/modules/autoroller-status-sk/autoroller-status-sk-demo.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>autoroller-status-sk</title>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  </head>
+  <body>
+    <h1>autoroller-status-sk</h1>
+    <theme-chooser-sk></theme-chooser-sk>
+    <div id="container"></div>
+    <h2>Events</h2>
+    <pre id="events"></pre>
+  </body>
+</html>
diff --git a/status/modules/autoroller-status-sk/autoroller-status-sk-demo.ts b/status/modules/autoroller-status-sk/autoroller-status-sk-demo.ts
new file mode 100644
index 0000000..73b86b9
--- /dev/null
+++ b/status/modules/autoroller-status-sk/autoroller-status-sk-demo.ts
@@ -0,0 +1,7 @@
+import './index';
+import '../../../infra-sk/modules/theme-chooser-sk';
+import { getAutorollerStatusesResponse, SetupMocks } from '../rpc-mock';
+
+SetupMocks().expectGetAutorollerStatuses(getAutorollerStatusesResponse);
+const el = document.createElement('autoroller-status-sk');
+document.querySelector('#container')?.appendChild(el);
diff --git a/status/modules/autoroller-status-sk/autoroller-status-sk.scss b/status/modules/autoroller-status-sk/autoroller-status-sk.scss
new file mode 100644
index 0000000..0020ffc
--- /dev/null
+++ b/status/modules/autoroller-status-sk/autoroller-status-sk.scss
@@ -0,0 +1,63 @@
+@import '../styles.scss';
+
+autoroller-status-sk {
+  .table {
+    background-color: var(--surface);
+    color: var(--on-surface);
+    display: table;
+    border-collapse: collapse;
+  }
+
+  .tr {
+    display: table-row;
+    border-bottom: 1px solid var(--grey-200);
+  }
+
+  .tr.roller:hover {
+    background-color: var(--primary);
+    color: var(--on-primary);
+    fill: var(--on-primary);
+  }
+
+  .td,
+  .th {
+    display: table-cell;
+    padding: 10px;
+  }
+
+  .td {
+    font-size: 0.813em;
+    vertical-align: middle;
+  }
+
+  .th {
+    font-size: 0.75em;
+  }
+
+  .number {
+    text-align: right;
+  }
+
+  a {
+    color: inherit;
+    text-decoration: none;
+  }
+
+  a:hover {
+    text-decoration: underline;
+  }
+
+  .td {
+    font-size: 1em;
+    padding: 6px;
+  }
+
+  .th {
+    padding: 8px;
+  }
+
+  // Remove the alpha from the failure color to make it pop.
+  .bg-failure {
+    background-color: var(--failure);
+  }
+}
diff --git a/status/modules/autoroller-status-sk/autoroller-status-sk.ts b/status/modules/autoroller-status-sk/autoroller-status-sk.ts
new file mode 100644
index 0000000..be9dd70
--- /dev/null
+++ b/status/modules/autoroller-status-sk/autoroller-status-sk.ts
@@ -0,0 +1,88 @@
+/**
+ * @module modules/autoroller-status-sk
+ * @description <h2><code>autoroller-status-sk</code></h2>
+ *
+ * Custom element for displaying status of Skia autorollers.
+ */
+import { define } from 'elements-sk/define';
+import { html } from 'lit-html';
+import { ElementSk } from '../../../infra-sk/modules/ElementSk';
+import { StatusService, GetStatusService, AutorollerStatus } from '../rpc';
+
+function colorClass(status: AutorollerStatus) {
+  // Find a color class for the roller.
+  // TODO(borenet): These numbers (especially number of commits behind)
+  // are probably going to differ from roller to roller. How can we give
+  // each roller its own definition of "bad"?
+  var badness = status.numFailed / 2.0;
+  var badnessBehind = status.numBehind / 20.0;
+  if (status.mode !== 'dry run' && badnessBehind > badness) {
+    badness = badnessBehind;
+  }
+  if (status.mode === 'stopped') {
+    return 'bg-unknown';
+  } else if (badness < 0.5) {
+    return 'bg-success';
+  } else if (badness < 1.0) {
+    return 'bg-warning';
+  } else {
+    return 'bg-failure';
+  }
+}
+
+export class AutorollerStatusSk extends ElementSk {
+  private client: StatusService = GetStatusService();
+  private rollers: Array<AutorollerStatus> = [];
+
+  private static template = (el: AutorollerStatusSk) =>
+    html`
+      <div class="table">
+        <div class="tr">
+          <div class="th">Roller</div>
+          <div class="th">Mode</div>
+          <div class="th">Failed</div>
+          <div class="th">Behind</div>
+        </div>
+        ${el.rollers.map(
+          (roller) => html`
+            <a
+              class="tr roller ${colorClass(roller)}"
+              href=${roller.url}
+              target="_blank"
+              rel="noopener noreferrer"
+            >
+              <div class="td">${roller.name}</div>
+              <div class="td">${roller.mode}</div>
+              <div class="td number">${roller.numFailed}</div>
+              <div class="td number">${roller.numBehind}</div>
+            </a>
+          `
+        )}
+      </div>
+    `;
+
+  constructor() {
+    super(AutorollerStatusSk.template);
+  }
+
+  connectedCallback() {
+    super.connectedCallback();
+    this._render();
+    this.refresh();
+  }
+
+  private refresh() {
+    this.client
+      .getAutorollerStatuses({})
+      .then((resp) => {
+        this.rollers = resp.rollers || [];
+        this._render();
+      })
+      .finally(() => {
+        // Updates happen periodically on the backend, this being configurable provides no benefit.
+        window.setTimeout(() => this.refresh(), 60 * 1000);
+      });
+  }
+}
+
+define('autoroller-status-sk', AutorollerStatusSk);
diff --git a/status/modules/autoroller-status-sk/autoroller-status-sk_puppeteer_test.ts b/status/modules/autoroller-status-sk/autoroller-status-sk_puppeteer_test.ts
new file mode 100644
index 0000000..d0f1b9c
--- /dev/null
+++ b/status/modules/autoroller-status-sk/autoroller-status-sk_puppeteer_test.ts
@@ -0,0 +1,29 @@
+import * as path from 'path';
+import { expect } from 'chai';
+import { setUpPuppeteerAndDemoPageServer, takeScreenshot } from '../../../puppeteer-tests/util';
+
+describe('autoroller-status-sk', () => {
+  const testBed = setUpPuppeteerAndDemoPageServer(
+    path.join(__dirname, '..', '..', 'webpack.config.ts')
+  );
+
+  beforeEach(async () => {
+    await testBed.page.goto(`${testBed.baseUrl}/dist/autoroller-status-sk.html`);
+    await testBed.page.setViewport({ width: 400, height: 550 });
+  });
+
+  it('should render the demo page (smoke test)', async () => {
+    expect(await testBed.page.$$('autoroller-status-sk')).to.have.length(1);
+  });
+
+  describe('screenshots', () => {
+    it('shows the default view', async () => {
+      await takeScreenshot(testBed.page, 'status', 'autoroller-status-sk');
+    });
+
+    it('highlights hovering', async () => {
+      testBed.page.hover('.roller');
+      await takeScreenshot(testBed.page, 'status', 'autoroller-status-sk_hover');
+    });
+  });
+});
diff --git a/status/modules/autoroller-status-sk/autoroller-status-sk_test.ts b/status/modules/autoroller-status-sk/autoroller-status-sk_test.ts
new file mode 100644
index 0000000..3616ba5
--- /dev/null
+++ b/status/modules/autoroller-status-sk/autoroller-status-sk_test.ts
@@ -0,0 +1,27 @@
+import './index';
+import { AutorollerStatusSk } from './autoroller-status-sk';
+
+import { $, $$ } from 'common-sk/modules/dom';
+import { setUpElementUnderTest } from '../../../infra-sk/modules/test_util';
+import { expect } from 'chai';
+import { getAutorollerStatusesResponse, SetupMocks } from '../rpc-mock';
+
+describe('autoroller-status-sk', () => {
+  const newInstance = setUpElementUnderTest<AutorollerStatusSk>('autoroller-status-sk');
+
+  let element: AutorollerStatusSk;
+  beforeEach(async () => {
+    SetupMocks().expectGetAutorollerStatuses(getAutorollerStatusesResponse);
+    element = newInstance();
+    await new Promise((resolve) => setTimeout(resolve, 0));
+  });
+
+  describe('display', () => {
+    it('statuses', () => {
+      expect($('.roller', element)).to.have.length(7);
+      expect($('.bg-failure', element)).to.have.length(1);
+      expect($('.bg-success', element)).to.have.length(4);
+      expect($('.bg-warning', element)).to.have.length(2);
+    });
+  });
+});
diff --git a/status/modules/autoroller-status-sk/index.ts b/status/modules/autoroller-status-sk/index.ts
new file mode 100644
index 0000000..2dda62e
--- /dev/null
+++ b/status/modules/autoroller-status-sk/index.ts
@@ -0,0 +1,2 @@
+import './autoroller-status-sk';
+import './autoroller-status-sk.scss';
diff --git a/status/modules/commits-table-sk/commits-table-sk.scss b/status/modules/commits-table-sk/commits-table-sk.scss
index bd1e375..67b2d5b 100644
--- a/status/modules/commits-table-sk/commits-table-sk.scss
+++ b/status/modules/commits-table-sk/commits-table-sk.scss
@@ -98,9 +98,9 @@
 
   .reloadControls {
     #repoLabel {
-      position: absolute;
-      top: 104px;
-      left: 15px;
+      position: relative;
+      top: 30px;
+      left: 3px;
     }
 
     select {
@@ -208,39 +208,27 @@
     z-index: 2;
   }
 
-  .task-success {
-    background-color: rgba(102, 166, 30, 0.3);
+  .grow:hover.bg-success,
+  .grow:hover .bg-success {
+    background-color: var(--success);
   }
 
-  .task-failure {
-    background-color: rgba(217, 95, 2, 0.3);
+  .grow:hover.bg-failure,
+  .grow:hover .bg-failure {
+    background-color: var(--failure);
   }
 
-  .task-mishap {
-    background-color: rgba(117, 112, 179, 0.3);
-  }
-
-  .grow:hover.task-success,
-  .grow:hover .task-success {
-    background-color: rgb(102, 166, 30);
-  }
-
-  .grow:hover.task-failure,
-  .grow:hover .task-failure {
-    background-color: rgb(217, 95, 2);
-  }
-
-  .grow:hover.task-mishap,
-  .grow:hover .task-mishap {
-    background-color: rgb(117, 112, 179);
+  .grow:hover.bg-mishap,
+  .grow:hover .bg-mishap {
+    background-color: var(--unexpected);
   }
 
   .fill-green {
-    fill: rgb(102, 166, 30);
+    fill: var(--success);
   }
 
   .fill-red {
-    fill: #d95f02;
+    fill: var(--failure);
   }
 }
 
@@ -255,31 +243,4 @@
       background-color: var(--surface-2dp);
     }
   }
-
-  .task-success {
-    background-color: rgba(102, 166, 30, 0.9);
-  }
-
-  .task-failure {
-    background-color: rgba(217, 95, 2, 0.9);
-  }
-
-  .task-mishap {
-    background-color: rgba(117, 112, 179, 0.9);
-  }
-
-  .grow:hover.task-success,
-  .grow:hover .task-success {
-    background-color: rgb(102, 166, 30);
-  }
-
-  .grow:hover.task-failure,
-  .grow:hover .task-failure {
-    background-color: rgb(217, 95, 2);
-  }
-
-  .grow:hover.task-mishap,
-  .grow:hover .task-mishap {
-    background-color: rgb(117, 112, 179);
-  }
 }
diff --git a/status/modules/commits-table-sk/commits-table-sk.ts b/status/modules/commits-table-sk/commits-table-sk.ts
index 409a7f2..a0e63a9 100644
--- a/status/modules/commits-table-sk/commits-table-sk.ts
+++ b/status/modules/commits-table-sk/commits-table-sk.ts
@@ -1047,7 +1047,7 @@
 
 function taskClasses(task: Task, ...classes: Array<string>) {
   const map: Record<string, any> = { task: true };
-  map[`task-${(task.status || 'PENDING').toLowerCase()}`] = true;
+  map[`bg-${(task.status || 'PENDING').toLowerCase()}`] = true;
   classes.forEach((c) => (map[c] = true));
   return classMap(map);
 }
diff --git a/status/modules/commits-table-sk/commits-table-sk_test.ts b/status/modules/commits-table-sk/commits-table-sk_test.ts
index 08a7620..5be041d 100644
--- a/status/modules/commits-table-sk/commits-table-sk_test.ts
+++ b/status/modules/commits-table-sk/commits-table-sk_test.ts
@@ -36,7 +36,7 @@
   it('displays multiple commit tasks', async () => {
     const table = await setupWithResponse(responseMultiCommitTask);
     expect($('.task', table)).to.have.length(1);
-    expect($$('.task', table)!.classList.value).to.include('task-failure');
+    expect($$('.task', table)!.classList.value).to.include('bg-failure');
   });
 
   it('displays noncontiguous tasks', async () => {
@@ -144,7 +144,7 @@
     expect($('.task[title="Test-Some-Stuff @ parentofabc123"]', table)).to.have.length(1);
     expect(
       $$('.task[title="Test-Some-Stuff @ parentofabc123"]', table)?.classList.value
-    ).to.contain('task-failure');
+    ).to.contain('bg-failure');
     // Mock an incremental update, and change the reload interval to trigger it.
     mocker.expectGetIncrementalCommits(incrementalResponse1);
     const reloadInput = $$('#reloadInput input', table) as HTMLInputElement;
@@ -172,7 +172,7 @@
     // Old task is updated.
     expect(
       $$('.task[title="Test-Some-Stuff @ parentofabc123"]', table)?.classList.value
-    ).to.contain('task-success');
+    ).to.contain('bg-success');
   });
 
   it('resets with startOver update', async () => {
diff --git a/status/modules/rpc-mock/index.ts b/status/modules/rpc-mock/index.ts
index d1f00b8..807eec4 100644
--- a/status/modules/rpc-mock/index.ts
+++ b/status/modules/rpc-mock/index.ts
@@ -30,6 +30,9 @@
   private processDeleteComment:
     | ((req: status.DeleteCommentRequest) => status.DeleteCommentResponse)
     | null = null;
+  private processGetAutorollerStatuses:
+    | ((req: status.GetAutorollerStatusesRequest) => status.GetAutorollerStatusesResponse)
+    | null = null;
   private processGetIncrementalCommits:
     | ((req: status.GetIncrementalCommitsRequest) => status.GetIncrementalCommitsResponse)
     | null = null;
@@ -80,6 +83,18 @@
     return this;
   }
 
+  // Set the GetAutorollerStatuses response.
+  expectGetAutorollerStatuses(
+    resp: status.GetAutorollerStatusesResponse,
+    check: (req: status.GetAutorollerStatusesRequest) => void = (req) => {}
+  ): MockStatusService {
+    this.processGetAutorollerStatuses = (req) => {
+      check(req);
+      return resp;
+    };
+    return this;
+  }
+
   getIncrementalCommits(
     req: status.GetIncrementalCommitsRequest
   ): Promise<status.GetIncrementalCommitsResponse> {
@@ -99,4 +114,12 @@
     this.processDeleteComment = null;
     return process ? Promise.resolve(process(req)) : Promise.reject('No mock response set');
   }
+
+  getAutorollerStatuses(
+    req: status.GetAutorollerStatusesRequest
+  ): Promise<status.GetAutorollerStatusesResponse> {
+    const process = this.processGetAutorollerStatuses;
+    this.processGetAutorollerStatuses = null;
+    return process ? Promise.resolve(process(req)) : Promise.reject('No mock response set');
+  }
 }
diff --git a/status/modules/rpc-mock/test_data.ts b/status/modules/rpc-mock/test_data.ts
index 0973c45..fe2c55f 100644
--- a/status/modules/rpc-mock/test_data.ts
+++ b/status/modules/rpc-mock/test_data.ts
@@ -2,7 +2,14 @@
  * Deterministic data for tests, crafted to cover various use cases, whereas mock_data is for
  * visualizing a complete status table via nondeterministically generated tasks/commits.
  */
-import { Branch, GetIncrementalCommitsResponse, Comment, Task, LongCommit } from '../rpc/status';
+import {
+  Branch,
+  GetIncrementalCommitsResponse,
+  Comment,
+  Task,
+  LongCommit,
+  GetAutorollerStatusesResponse,
+} from '../rpc/status';
 
 function copy<T>(obj: T): T {
   return JSON.parse(JSON.stringify(obj));
@@ -241,3 +248,71 @@
   r.metadata!.startOver = true;
   return r;
 })();
+
+export const getAutorollerStatusesResponse: GetAutorollerStatusesResponse = {
+  rollers: [
+    {
+      name: 'ANGLE',
+      mode: 'running',
+      numBehind: 0,
+      numFailed: 0,
+      currentRollRev: 'abc123',
+      lastRollRev: 'def456',
+      url: 'https://autoroll.skia.org/r/angle-skia-autoroll',
+    },
+    {
+      name: 'Android',
+      mode: 'running',
+      numBehind: 0,
+      numFailed: 1,
+      currentRollRev: 'abc123',
+      lastRollRev: 'def456',
+      url: 'https://autoroll.skia.org/r/android-skia-autoroll',
+    },
+    {
+      name: 'Chrome',
+      mode: 'running',
+      numBehind: 0,
+      numFailed: 1,
+      currentRollRev: 'abc123',
+      lastRollRev: 'def456',
+      url: 'https://autoroll.skia.org/r/chrome-skia-autoroll',
+    },
+    {
+      name: 'Flutter',
+      mode: 'running',
+      numBehind: 3,
+      numFailed: 6,
+      currentRollRev: 'abc123',
+      lastRollRev: 'def456',
+      url: 'https://autoroll.skia.org/r/flutter-skia-autoroll',
+    },
+    {
+      name: 'Google3',
+      mode: 'running',
+      numBehind: 0,
+      numFailed: 0,
+      currentRollRev: 'abc123',
+      lastRollRev: 'def456',
+      url: 'https://autoroll.skia.org/r/g3-skia-autoroll',
+    },
+    {
+      name: 'SwiftSh',
+      mode: 'running',
+      numBehind: 0,
+      numFailed: 0,
+      currentRollRev: 'abc123',
+      lastRollRev: 'def456',
+      url: 'https://autoroll.skia.org/r/swiftsh-skia-autoroll',
+    },
+    {
+      name: 'skcms',
+      mode: 'running',
+      numBehind: 0,
+      numFailed: 0,
+      currentRollRev: 'abc123',
+      lastRollRev: 'def456',
+      url: 'https://autoroll.skia.org/r/skcms-skia-autoroll',
+    },
+  ],
+};
diff --git a/status/modules/rpc/status.ts b/status/modules/rpc/status.ts
index bfa49e6..6c3b267 100644
--- a/status/modules/rpc/status.ts
+++ b/status/modules/rpc/status.ts
@@ -288,10 +288,68 @@
   };
 };
 
+export interface GetAutorollerStatusesRequest {
+}
+
+interface GetAutorollerStatusesRequestJSON {
+}
+
+const GetAutorollerStatusesRequestToJSON = (m: GetAutorollerStatusesRequest): GetAutorollerStatusesRequestJSON => {
+  return {
+  };
+};
+
+export interface GetAutorollerStatusesResponse {
+  rollers?: AutorollerStatus[];
+}
+
+interface GetAutorollerStatusesResponseJSON {
+  rollers?: AutorollerStatusJSON[];
+}
+
+const JSONToGetAutorollerStatusesResponse = (m: GetAutorollerStatusesResponseJSON): GetAutorollerStatusesResponse => {
+  return {
+    rollers: m.rollers && m.rollers.map(JSONToAutorollerStatus),
+  };
+};
+
+export interface AutorollerStatus {
+  name: string;
+  currentRollRev: string;
+  lastRollRev: string;
+  mode: string;
+  numFailed: number;
+  numBehind: number;
+  url: string;
+}
+
+interface AutorollerStatusJSON {
+  name?: string;
+  current_roll_rev?: string;
+  last_roll_rev?: string;
+  mode?: string;
+  num_failed?: number;
+  num_behind?: number;
+  url?: string;
+}
+
+const JSONToAutorollerStatus = (m: AutorollerStatusJSON): AutorollerStatus => {
+  return {
+    name: m.name || "",
+    currentRollRev: m.current_roll_rev || "",
+    lastRollRev: m.last_roll_rev || "",
+    mode: m.mode || "",
+    numFailed: m.num_failed || 0,
+    numBehind: m.num_behind || 0,
+    url: m.url || "",
+  };
+};
+
 export interface StatusService {
   getIncrementalCommits: (getIncrementalCommitsRequest: GetIncrementalCommitsRequest) => Promise<GetIncrementalCommitsResponse>;
   addComment: (addCommentRequest: AddCommentRequest) => Promise<AddCommentResponse>;
   deleteComment: (deleteCommentRequest: DeleteCommentRequest) => Promise<DeleteCommentResponse>;
+  getAutorollerStatuses: (getAutorollerStatusesRequest: GetAutorollerStatusesRequest) => Promise<GetAutorollerStatusesResponse>;
 }
 
 export class StatusServiceClient implements StatusService {
@@ -352,4 +410,19 @@
       return resp.json().then(JSONToDeleteCommentResponse);
     });
   }
+
+  getAutorollerStatuses(getAutorollerStatusesRequest: GetAutorollerStatusesRequest): Promise<GetAutorollerStatusesResponse> {
+    const url = this.hostname + this.pathPrefix + "GetAutorollerStatuses";
+    let body: GetAutorollerStatusesRequest | GetAutorollerStatusesRequestJSON = getAutorollerStatusesRequest;
+    if (!this.writeCamelCase) {
+      body = GetAutorollerStatusesRequestToJSON(getAutorollerStatusesRequest);
+    }
+    return this.fetch(createTwirpRequest(url, body, this.optionsOverride)).then((resp) => {
+      if (!resp.ok) {
+        return throwTwirpError(resp);
+      }
+
+      return resp.json().then(JSONToGetAutorollerStatusesResponse);
+    });
+  }
 }
diff --git a/status/modules/status-sk/status-sk-demo.ts b/status/modules/status-sk/status-sk-demo.ts
index c605e5a..6566a71 100644
--- a/status/modules/status-sk/status-sk-demo.ts
+++ b/status/modules/status-sk/status-sk-demo.ts
@@ -1,9 +1,11 @@
 import './index';
-import { incrementalResponse0, SetupMocks } from '../rpc-mock';
+import { getAutorollerStatusesResponse, incrementalResponse0, SetupMocks } from '../rpc-mock';
 import { $$ } from 'common-sk/modules/dom';
 import { SetTestSettings } from '../settings';
 
-SetupMocks().expectGetIncrementalCommits(incrementalResponse0);
+SetupMocks()
+  .expectGetIncrementalCommits(incrementalResponse0)
+  .expectGetAutorollerStatuses(getAutorollerStatusesResponse);
 SetTestSettings({
   swarmingUrl: 'example.com/swarming',
   taskSchedulerUrl: 'example.com/ts',
diff --git a/status/modules/status-sk/status-sk.scss b/status/modules/status-sk/status-sk.scss
index b90a41c..c8fdbc2 100644
--- a/status/modules/status-sk/status-sk.scss
+++ b/status/modules/status-sk/status-sk.scss
@@ -1 +1,9 @@
 @import '../styles.scss';
+
+status-sk {
+  autoroller-status-sk {
+    // TODO(westont): Temporary, this will be going into a fixed panel.
+    float: left;
+    margin: 10px;
+  }
+}
diff --git a/status/modules/status-sk/status-sk.ts b/status/modules/status-sk/status-sk.ts
index 7dc5e50..2c388d0 100644
--- a/status/modules/status-sk/status-sk.ts
+++ b/status/modules/status-sk/status-sk.ts
@@ -10,6 +10,7 @@
 
 import '../../../infra-sk/modules/theme-chooser-sk';
 import '../commits-table-sk';
+import '../autoroller-status-sk';
 import 'elements-sk/error-toast-sk';
 
 export class StatusSk extends ElementSk {
@@ -17,6 +18,7 @@
     html`
       <h2>lit-html/TS/Twirp Status Page Under Development</h2>
       <theme-chooser-sk></theme-chooser-sk>
+      <autoroller-status-sk></autoroller-status-sk>
       <commits-table-sk></commits-table-sk>
       <error-toast-sk></error-toast-sk>
     `;
diff --git a/status/modules/styles.scss b/status/modules/styles.scss
index c6088fd..03cf78c 100644
--- a/status/modules/styles.scss
+++ b/status/modules/styles.scss
@@ -9,8 +9,47 @@
   min-height: fit-content;
 }
 
+.bg-success {
+  background-color: var(--success-alpha);
+}
+
+.bg-warning {
+  background-color: var(--warning-alpha);
+  color: var(--black);
+}
+
+.bg-failure {
+  background-color: var(--failure-alpha);
+}
+
+.bg-mishap {
+  background-color: var(--unexpected-alpha);
+}
+
+.bg-unknown {
+  background-color: var(--unexpected-alpha);
+}
+
 :root {
   --primary: var(--light-green-700);
   // TODO(westont): Move to named colors, unify theme.
   --secondary: #e6ab02;
+
+  --success: rgb(102, 166, 30);
+  --success-alpha: rgba(102, 166, 30, 0.3);
+  --warning: var(--orange-400);
+  --warning-alpha: var(--orange-a200);
+  --failure: rgb(217, 95, 2);
+  --failure-alpha: rgba(217, 95, 2, 0.3);
+  --unexpected: rgb(117, 112, 179);
+  --unexpected-alpha: rgba(117, 112, 179, 0.3);
+  --disabled: var(--grey-500);
+  --disabled-alpha: rgba(117, 117, 117, 0.3);
+
+  .darkmode {
+    --success-alpha: rgba(102, 166, 30, 0.9);
+    --failure-alpha: rgba(217, 95, 2, 0.9);
+    --unexpected-alpha: rgba(117, 112, 179, 0.9);
+    --disabled-alpha: rgba(117, 117, 117, 0.9);
+  }
 }