blob: 2a9bed3f6c3bfe7d3b76078dbc082d7f27b01151 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as dom;
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'dart:io';
import 'dart:core';
import 'dart:async';
import 'dart:convert';
import 'dart:ui' as ui;
enum BuildStatus {
UNKNOWN, NETWORKERROR, PARSEERROR, SUCCESS, FAILURE
}
// ----------------------------------------------------------------------------
// EDIT BELOW TO ADD configs
final String kBaseURL = 'https://luci-scheduler.appspot.com/jobs/';
class BuildInfo {
BuildStatus status = BuildStatus.UNKNOWN;
String url;
BuildInfo({this.status, this.url});
}
var targets_map = {
'fuchsia': [
['fuchsia/linux-x86-64-debug', 'linux-x86-64-debug'],
['fuchsia/linux-arm64-debug', 'linux-arm64-debug'],
['fuchsia/linux-x86-64-release', 'linux-x86-64-release'],
['fuchsia/linux-arm64-release', 'linux-arm64-release'],
],
'fuchsia-drivers': [
['fuchsia/drivers-linux-x86-64-debug', 'linux-x86-64-debug'],
['fuchsia/drivers-linux-arm64-debug', 'linux-arm64-debug'],
['fuchsia/drivers-linux-x86-64-release', 'linux-x86-64-release'],
['fuchsia/drivers-linux-arm64-release', 'linux-arm64-release'],
],
'magenta': [
['magenta/arm64-linux-gcc', 'arm64-linux-gcc'],
['magenta/x86-64-linux-gcc', 'x86-64-linux-gcc'],
['magenta/arm64-linux-clang', 'arm64-linux-clang'],
['magenta/x86-64-linux-clang', 'x86-64-linux-clang'],
],
'jiri': [
['jiri/linux-x86-64', 'linux-x86-64'],
['jiri/mac-x86-64', 'mac-x86-64'],
]
};
class DashboardApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Fuchsia Build Status',
theme: new ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see
// the application has a blue toolbar. Then, without quitting
// the app, try changing the primarySwatch below to Colors.green
// and press "r" in the console where you ran "flutter run".
// We call this a "hot reload".
primarySwatch: Colors.blue,
),
home: new DashboardPage(title: 'Fuchsia Build Status'),
);
}
}
class DashboardPage extends StatefulWidget {
DashboardPage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful,
// meaning that it has a State object (defined below) that contains
// fields that affect how it looks.
// This class is the configuration for the state. It holds the
// values (in this case the title) provided by the parent (in this
// case the App widget) and used by the build method of the State.
// Fields in a Widget subclass are always marked "final".
final String title;
@override
_DashboardPageState createState() => new _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
BuildStatus _status = BuildStatus.UNKNOWN;
var targets_results;
Timer _refresh_timer;
DateTime _start_time = new DateTime.now();
@override
initState() {
// From the targets map, create a data structure which we'll be populating
// the build results into, as they come in async.
targets_results = new Map();
targets_map.forEach((categoryName,buildConfigs) {
var this_map = new Map<String, BuildInfo>();
for(var config in buildConfigs) {
this_map[config[1]] = new BuildInfo(
status: BuildStatus.UNKNOWN,
url: "http://www.google.com"
);
}
targets_results[categoryName] = this_map;
});
_refresh_timer = new Timer.periodic(const Duration(seconds: 60), _refreshTimerFired);
_refreshStatus();
}
void _refreshTimerFired(Timer t) {
_refreshStatus();
}
// Refresh status an ALL builds.
void _refreshStatus() {
// fetch config status for ONE item.
_fetchConfigStatus(categoryName, buildName, url) async {
BuildStatus status = BuildStatus.PARSEERROR;
String html = null;
try {
var response = await http.get(url);
html = response.body;
} catch (error) {
status = BuildStatus.NETWORKERROR;
}
if (html == null) {
status = BuildStatus.NETWORKERROR;
} else {
var dom_tree = parse(html);
List<dom.Element> trs = dom_tree.querySelectorAll('tr');
for (var tr in trs) {
if (tr.className == "danger") {
status = BuildStatus.FAILURE;
break;
}
else if (tr.className == "success") {
status = BuildStatus.SUCCESS;
break;
}
}
}
targets_results['${categoryName}']['${buildName}'].status = status;
targets_results['${categoryName}']['${buildName}'].url = url;
setState( () {} );
} // _fetchConfigStatus
// kick off requests for all the build configs desired. As
// these reults come in they will be stuffed into the targets_results map.
targets_map.forEach((categoryName, buildConfigs){
for(var config in buildConfigs) {
String url = kBaseURL + config[0];
_fetchConfigStatus(categoryName, config[1], url);
}
}); // targets_forEach
} // _refreshStatus
void _launchUrl(String url) {
UrlLauncher.launch(url);
}
Color _colorFromBuildStatus(BuildStatus status) {
switch (status) {
case BuildStatus.SUCCESS:
return Colors.green[100];
case BuildStatus.FAILURE:
return Colors.red[400];
case BuildStatus.NETWORKERROR:
return Colors.purple[100];
default:
return Colors.black12;
}
}
Widget _buildResultWidget(String name, BuildInfo bi) {
return new Expanded( child: new GestureDetector(
onTap: () {
_launchUrl(bi.url);
},
child:
new Container(
decoration: new BoxDecoration(backgroundColor: _colorFromBuildStatus(bi.status)),
padding: const EdgeInsets.symmetric(vertical:16.0, horizontal: 4.0),
margin: const EdgeInsets.fromLTRB(0.0, 8.0, 8.0, 8.0),
child: new Text(name,
style:new TextStyle(color:Colors.black, fontSize:12.0)),
)) );
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance
// as done by the _refreshStatus method above.
if (ui.window.physicalSize.width > ui.window.physicalSize.height)
return _buildLandcape(context);
else
return _buildPortrait(context);
}
Widget _buildPortrait(BuildContext context) {
return _buildLandcape(context); // TODO
}
Widget _buildLandcape(BuildContext context) {
var rows = new List();
Duration uptime = new DateTime.now().difference(_start_time);
rows.add(
new Container(
child: new Text("${uptime.inDays}d ${uptime.inHours % 24}h ${uptime.inMinutes % 60}m uptime",
style: new TextStyle(fontSize:11.0)
)
)
);
targets_results.forEach((k,v) {
// the builds
var builds = new List();
v.forEach((name, status_obj) {
builds.add(_buildResultWidget("${k}\n${name}",status_obj));
});
rows.add(new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children:builds));
});
return new Scaffold(
appBar: new AppBar(
title: new Text('Fuchsia Build Status'),
),
body: new Container(
//padding: new EdgeInsets.all(20.0),
child: new Column (
children: rows,
), ),
floatingActionButton: new FloatingActionButton(
onPressed: _refreshStatus,
tooltip: 'Increment',
child: new Icon(Icons.refresh),
), // This trailing comma tells the Dart formatter to use
// a style that looks nicer for build methods.
);
}
}