TBR: oncall: update oncall dashboard.

Change-Id: Ie41b46e90bedf448d410f449e5f6d7747d4e4896
diff --git a/oncall/client/browser/appstate-manager.js b/oncall/client/browser/appstate-manager.js
deleted file mode 100644
index 22c39b1..0000000
--- a/oncall/client/browser/appstate-manager.js
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * App state manager.
- */
-
-var hg = require('mercury');
-var qs = require('qs');
-var url = require('url');
-
-module.exports = {
-  init: init,
-  getAppState: getAppState,
-  setAppState: setAppState,
-  getCurState: getCurState,
-};
-
-/**
- * An observable to store app's state which will be encoded in url parameters.
- */
-var appState = hg.varhash({});
-
-/** The default app state. */
-var defaultAppState = {
-  // The level of view.
-  // - global: all (aggregated) metrics in all zones.
-  // - zone: all metrics in a specific zone.
-  // - instance: all metrics for a specific instance.
-  level: 'global',
-
-  // The data aggregation type for global view.
-  // In global view, each metric (e.g. nginx qps) in a certain zone might have
-  // multiple instances (e.g. multiple nginx workers). We need to aggregate data
-  // from all those instances to a single one.
-  // We currently support 'Max' and 'Average'.
-  globalLevelAggType: 'Max',
-
-  // The zone to show in the zone level.
-  zoneLevelZone: '',
-
-  // The type of metrics to show in the zone level.
-  // This could either be 'CloudServices' or 'Nginx'.
-  zoneLevelType: '',
-
-  // The instance to show in the instance level.
-  instanceLevelInstance: '',
-
-  // The zone of the instance in the instance level.
-  instanceLevelZone: ''
-};
-
-/**
- * A flag indicating whether to trigger history.pushState when
- * app state changes.
- */
-var pushHistoryState = true;
-
-/**
- * Sets up app state and its various event listeners.
- * @param {callback} stateChangedListener - The callback that handles app state
- *     changes.
- */
-function init(stateChangedListener) {
-  // When appState changes, push the state to browser's history.
-  appState(function(data) {
-    if (pushHistoryState) {
-      var str = qs.stringify(data);
-      window.history.pushState(undefined, '',
-          window.location.origin + window.location.pathname + '?' + str);
-    }
-    stateChangedListener(getCurState());
-  });
-
-  // Get the current state from url parameters.
-  var initState = qs.parse(url.parse(window.location.href).query);
-  // Fill in default values.
-  Object.keys(defaultAppState).forEach(function(key) {
-    if (!initState[key]) {
-      initState[key] = defaultAppState[key];
-    }
-  });
-  appState.set(initState);
-
-  // When the history state changes, we update the appState observable
-  // which will trigger its change listener defined above.
-  window.addEventListener('popstate', function(event) {
-    // We don't want to mess with history states in this case.
-    pushHistoryState = false;
-    appState.set(qs.parse(url.parse(window.location.href).query));
-    pushHistoryState = true;
-  });
-}
-
-/**
- * Gets an app state entry for the given name.
- * @param {string} name - The name of the app state entry.
- * @return {string}
- */
-function getAppState(name) {
-  return appState()[name];
-}
-
-/**
- * Sets app state.
- * @param {Object} stateObj - The state object to set.
- */
-function setAppState(stateObj) {
-  var curState = appState();
-  Object.keys(stateObj).forEach(function(key) {
-    curState[key] = stateObj[key];
-  });
-  appState.set(curState);
-}
-
-/**
- * Gets the current app state.
- * @return {Object}
- */
-function getCurState() {
-  // For some reason, the object returned by appState() doesn't have
-  // Object.prototype as its prototype, which will cause issues in some other
-  // places. To workaround this, we return a cloned object which has
-  // Object.prototype.
-  var obj = {};
-  var curAppState = appState();
-  Object.keys(curAppState).forEach(function(key) {
-    obj[key] = curAppState[key];
-  });
-  return obj;
-}
diff --git a/oncall/client/browser/components/data-table/index.js b/oncall/client/browser/components/data-table/index.js
deleted file mode 100644
index 53bdf2d..0000000
--- a/oncall/client/browser/components/data-table/index.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * A simple data table.
- */
-
-var hg = require('mercury');
-var h = require('mercury').h;
-
-module.exports = create;
-module.exports.render = render;
-
-/** Constructor. */
-function create(data) {
-  var state = hg.state({
-    title: data.title,
-    rows: data.rows
-  });
-  return state;
-}
-
-/** The main render function. */
-function render(state) {
-  return h('div.data-table', [
-    h('div.data-table-title', state.title),
-    h('table', state.rows.map(function(row) {
-      return h('tr', row.map(function(col) {
-        return h('td', col !== '' ? col : 'n/a');
-      }));
-    }))
-  ]);
-}
diff --git a/oncall/client/browser/components/full-graph/index.js b/oncall/client/browser/components/full-graph/index.js
deleted file mode 100644
index ada32a1..0000000
--- a/oncall/client/browser/components/full-graph/index.js
+++ /dev/null
@@ -1,266 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * A graph showing multiple metrics in different colors along with their
- * summary items showing their current/pass values and line colors. User can
- * "select" one or more metrics by clicking on their summary items. The graph
- * then will only show selected metrics (if the selection is not empty).
- *
- * The graph will also overlay a graph of the metric stored in the
- * "hoveredMetric" observable passed from some other component.
- */
-
-var hg = require('mercury');
-var h = require('mercury').h;
-var svg = require('virtual-dom/virtual-hyperscript/svg');
-var dateformat = require('dateformat');
-
-var Consts = require('../../constants');
-var MouseMoveEvent = require('../../mousemove-handler');
-var Util = require('../../util.js');
-
-module.exports = create;
-module.exports.render = render;
-
-var colors = [
-  '#16a085',
-  '#2980b9',
-  '#8e44ad',
-  '#808B96',
-  '#f39c12',
-  '#d35400',
-  '#27ae60',
-  '#c0392b'
-];
-
-/** Constructor. */
-function create(data) {
-  // Transform the raw data to metrics array.
-  // Also calculate time and value range across all metrics.
-  var minTime = Number.MAX_VALUE;
-  var maxTime = 0;
-  var minValue = Number.MAX_VALUE;
-  var maxValue = 0;
-  var metrics = data.metrics.map(function(metric, index) {
-    minTime = Math.min(minTime, metric.MinTime);
-    maxTime = Math.max(maxTime, metric.MaxTime);
-    minValue = Math.min(minValue, metric.MinValue);
-    maxValue = Math.max(maxValue, metric.MaxValue);
-    return {
-      label: metric.Name,
-      value: metric.CurrentValue,
-      color: colors[index % colors.length],
-      threshold: metric.Threshold,
-      healthy: metric.Healthy,
-      historyTimestamps: metric.HistoryTimestamps,
-      historyValues: metric.HistoryValues,
-    };
-  });
-
-  // Calculate the overall health for all metrics.
-  var healthy = true;
-  metrics.forEach(function(metric) {
-    healthy &= metric.healthy;
-  });
-
-  var state = hg.state({
-    // The timestamp when the data is loaded from the backend server.
-    collectionTimestamp: data.collectionTimestamp,
-
-    // The graph title.
-    title: data.title,
-
-    // Time range.
-    minTime: minTime,
-    maxTime: maxTime,
-
-    // Value range.
-    minValue: minValue,
-    maxValue: maxValue,
-
-    // Metrics shown in this graph.
-    metrics: metrics,
-
-    // Overall health.
-    healthy: healthy,
-
-    // Keep track of selected metrics.
-    // A metric line will be visible if it is selected.
-    selectedMetrics: hg.varhash({}),
-
-    // See comments in instance-view/index.js.
-    mouseOffsetFactor: data.mouseOffsetFactor,
-    hoveredMetric: data.hoveredMetric,
-
-    channels: {
-      mouseMove: mouseMove,
-      mouseOut: mouseOut,
-      mouseClickOnSummaryItem: mouseClickOnSummaryItem
-    }
-  });
-
-  return state;
-}
-
-/** Callback when mouse is moving on the graph. */
-function mouseMove(state, data) {
-  if (state.mouseOffsetFactor() !== data.f) {
-    state.mouseOffsetFactor.set(data.f);
-  }
-}
-
-/** Callback when mouse moves out of the graph. */
-function mouseOut(state) {
-  state.mouseOffsetFactor.set(-1);
-}
-
-/** Callback when user clicks on a metric's summary item. */
-function mouseClickOnSummaryItem(state, label) {
-  if (!state.selectedMetrics.get(label)) {
-    state.selectedMetrics.put(label, 1);
-  } else {
-    state.selectedMetrics.delete(label);
-  }
-}
-
-/** The main render function. */
-function render(state) {
-  // Render graphs.
-  var items = [
-    renderContent(state),
-    renderThreshold(state)
-  ];
-
-  // Render an overlay graph for the metric stored in state.hoveredMetric.
-  if (Object.keys(state.hoveredMetric).length !== 0) {
-    var metric = state.hoveredMetric;
-    var numPoints = metric.historyTimestamps.length;
-    var minTime = metric.historyTimestamps[0];
-    var maxTime = metric.historyTimestamps[numPoints - 1];
-    items.push(svg('svg', {
-      'viewBox': '0 0 100 100',
-      'preserveAspectRatio': 'none'
-    }, [
-      Util.renderMetric(state.hoveredMetric,
-        minTime, maxTime, metric.minValue, metric.maxValue, 1, '#AAA', 1)
-    ]));
-  }
-
-  // Render mouse line.
-  var offset = 100 * state.mouseOffsetFactor;
-  items.push(svg('svg', {
-   'class': 'overlay',
-   'viewBox': '0 0 100 100',
-   'preserveAspectRatio': 'none'
-  }, [
-    svg('polyline', {
-      'points': offset + ',0 ' + offset + ',100'
-    })
-  ]));
-
-  var endDate = new Date(state.collectionTimestamp * 1000);
-  var startDate = new Date((state.collectionTimestamp - 3600) * 1000);
-  return h('div.full-graph-container', [
-      h('div.full-graph-title', state.title),
-      h('div.content-container', [
-        // The summary row at the top of the graph.
-        renderSummaryRow(state),
-        // The main graph.
-        h('div.full-graph', {
-          'ev-mousemove': new MouseMoveEvent(state.channels.mouseMove),
-          'ev-mouseout': hg.send(state.channels.mouseOut)
-        }, items),
-        // The time axis at the bottom.
-        h('div.time', [
-          h('div.time-label', dateformat(startDate, 'HH:MM')),
-          h('div.time-label', dateformat(endDate, 'HH:MM'))
-        ])
-      ])
-  ]);
-}
-
-/** Renders the main graph. */
-function renderContent(state) {
-  var items = state.metrics.map(function(metric) {
-    var strokeOpacity = isMetricVisible(state.selectedMetrics, metric.label) ?
-        1 : 0.1;
-    var metricLine = Util.renderMetric(
-        metric, state.minTime, state.maxTime,
-        state.minValue, state.maxValue, 1.5, metric.color, strokeOpacity);
-    return metricLine;
-  });
-  return svg('svg', {
-    'class': 'content',
-    'viewBox': '0 0 100 100',
-    'preserveAspectRatio': 'none'
-  }, items);
-}
-
-/** Renders the threshold line. */
-function renderThreshold(state) {
-  var minThreshold = Number.MAX_VALUE;
-  state.metrics.forEach(function(metric) {
-    minThreshold = Math.min(minThreshold, metric.threshold);
-  });
-  var thresholdOffset = Util.getOffsetForValue(
-      minThreshold, state.minValue, state.maxValue);
-  return svg('svg', {
-    'class': 'threshold-line',
-    'viewBox': '0 0 100 100',
-    'preserveAspectRatio': 'none',
-  }, [
-    svg('path', {
-      'd': 'M0 ' + thresholdOffset + ' L 100 ' + thresholdOffset,
-      'stroke-dasharray': '2,2'
-    })
-  ]);
-}
-
-/** Renders the summary row. */
-function renderSummaryRow(state) {
-  var items = state.metrics.map(function(metric) {
-    var curValue = Util.formatValue(
-        Util.interpolateValue(metric.value, state.mouseOffsetFactor,
-          metric.historyTimestamps, metric.historyValues));
-    var metricSummaryClassNames = [];
-    if (!isMetricVisible(state.selectedMetrics, metric.label)) {
-      metricSummaryClassNames.push('hidden');
-    }
-    if (!metric.healthy) {
-      metricSummaryClassNames.push('unhealthy');
-    }
-    var metricValueClassNames = [];
-    if (state.mouseOffsetFactor >= 0) {
-      metricValueClassNames.push('historyValue');
-    }
-    if (!metric.healthy) {
-      metricValueClassNames.push('unhealthy');
-    }
-    return h('div.metric-summary-item', {
-      className: metricSummaryClassNames.join(' '),
-      'ev-click': hg.send(state.channels.mouseClickOnSummaryItem, metric.label)
-    }, [
-        h('div.metric-summary-title', Consts.getDisplayName(metric.label)),
-        h('div.metric-summary-value', {
-          className: metricValueClassNames.join(' ')
-        }, curValue),
-        h('div.metric-summary-color', {
-          'style': {backgroundColor: metric.color}
-        })
-    ]);
-  });
-  return h('div.metric-summary-container', items);
-}
-
-/**
- * Checks whether the given metric is visible based on the metrics stored in
- * the selectedMetrics object.
- * @param {Object} selectedMetrics - An object indexed by metric names.
- * @param {string} metric - The name of the metric to check.
- * @return {boolean}
- */
-function isMetricVisible(selectedMetrics, metric) {
-  return Object.keys(selectedMetrics).length === 0 || selectedMetrics[metric];
-}
diff --git a/oncall/client/browser/components/instance-view/index.js b/oncall/client/browser/components/instance-view/index.js
deleted file mode 100644
index c6f9c05..0000000
--- a/oncall/client/browser/components/instance-view/index.js
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/*
- * The instance view.
- *
- * In this view, we show detailed data of vanadium services or nginx servers for
- * a single GCE instance.
- *
- * The page is divided into several sections:
- * - We first show a large graph for the main metrics we care about the most,
- *   such as "vanadium services request latency".
- * - We then show various "helper" stats that can help people diagnose problems
- *   in the main metrics, such as "mounted nodes in mounttable".
- * - After that, we show metrics for the GCE instance itself, such as CPU usage,
- *   disk usage, etc. It also has links to let user ssh into the machine and see
- *   its log.
- * - For vanadium services, it will also show a table for services'
- *   metadata/buildinfo.
- */
-
-var hg = require('mercury');
-var h = require('mercury').h;
-var dateformat = require('dateformat');
-
-var Consts = require('../../constants');
-var fullGraphComponent = require('../full-graph');
-var metricsGroupComponent = require('../metrics-group');
-var dataTableComponent = require('../data-table');
-var Util = require('../../util');
-
-module.exports = create;
-module.exports.render = render;
-
-/** Constructor. */
-function create(data) {
-  if (data === null) {
-    return null;
-  }
-
-  var instance = data.appState.instanceLevelInstance;
-  var isVanadiumServices = instance.startsWith('vanadium');
-
-  // Main metrics.
-  // For vanadium services, we show request latency as the main metrics.
-  // For nginx servers, we show its load.
-  var mainMetrics = isVanadiumServices ?
-      data.data.CloudServiceLatency : data.data.NginxLoad;
-  var mainMetricsTitle = isVanadiumServices ? 'REQUEST LATENCY' : 'SERVER LOAD';
-  var mainMetricKeys = isVanadiumServices ?
-      Consts.metricKeys.latency : Consts.metricKeys.nginxLoad;
-  var sortedMainMetrics = mainMetricKeys.map(function(metric) {
-    return mainMetrics[metric];
-  });
-
-  // Helper metrics.
-  // For now, we only have helper metrics for vanadium services, which are
-  // various stats from mounttable.
-  var helperMetrics = isVanadiumServices ? data.data.CloudServiceStats : null;
-  var helperMetricsTitle = isVanadiumServices ? 'STATS' : '';
-  var helperMetricKeys = isVanadiumServices ? Consts.metricKeys.stats : null;
-  var sortedHelperMetrics = [];
-  if (isVanadiumServices) {
-    sortedHelperMetrics = helperMetricKeys.map(function(metric) {
-      return helperMetrics[metric];
-    });
-  }
-
-  // GCE metrics.
-  var gceMetrics = isVanadiumServices ?
-      data.data.CloudServiceGCE : data.data.NginxGCE;
-  var gceMetricsTitle = 'GCE';
-  var gceMetricKeys = Consts.metricKeys.gce;
-  var sortedGCEMetrics = gceMetricKeys.map(function(metric) {
-    return gceMetrics[metric];
-  });
-  var instanceId = data.data.GCEInfo.Id;
-
-  // Data table for showing vanadium services' metadata/buildinfo.
-  var dataTableData = data.data.CloudServiceBuildInfo;
-  var rows = [];
-  if (!Util.isEmptyObj(dataTableData)) {
-    rows.push(['SERVICE', 'PRISTINE', 'SNAPSHOT', 'TIME', 'USER', 'LINKS']);
-    Object.keys(dataTableData).sort().forEach(function(serviceName) {
-      var rowData = dataTableData[serviceName];
-      var curRow = [
-        Consts.getDisplayName(serviceName),
-        rowData.IsPristine, rowData.Snapshot,
-        dateformat(new Date(rowData.Time * 1000), 'yyyymmdd-HH:MM'),
-        rowData.User,
-        h('a', {
-          href: genLogsLink(instanceId, 'v-' + rowData.ServiceName + '.info'),
-          target: '_blank'
-        }, 'log')];
-      rows.push(curRow);
-    });
-  }
-
-  // These two observables keep track of which non-main metric the mouse cursor
-  // is currently hovering over, as well as its relative X position.
-  //
-  // When a non-main metric is hovered over, we will overlay its data points in
-  // the main metrics graph for easier comparison. We also render a "mouse line"
-  // across all graphs.
-  var hoveredMetric = hg.struct({});
-  var mouseOffsetFactor = hg.value(-1);
-
-  var state = hg.state({
-    data: data.data,
-    appState: hg.struct(data.appState),
-
-    mainMetricsGraph: fullGraphComponent({
-      title: mainMetricsTitle,
-      collectionTimestamp: data.collectionTimestamp,
-      metrics: sortedMainMetrics,
-      mouseOffsetFactor: mouseOffsetFactor,
-      hoveredMetric: hoveredMetric
-    }),
-
-    helperMetricsGroup: isVanadiumServices ? metricsGroupComponent({
-      title: helperMetricsTitle,
-      metrics: sortedHelperMetrics,
-      mouseOffsetFactor: mouseOffsetFactor,
-      hoveredMetric: hoveredMetric
-    }) : null,
-
-    gceMetricsGroup: metricsGroupComponent({
-      title: gceMetricsTitle,
-      metrics: sortedGCEMetrics,
-      mouseOffsetFactor: mouseOffsetFactor,
-      hoveredMetric: hoveredMetric,
-      links: [
-        {
-          name: 'ssh',
-          link: genCloudSSHLink(data.appState.instanceLevelZone, instance)
-        },
-        {
-          name: 'log',
-          link: genLogsLink(instanceId, '')
-        }
-      ],
-    }),
-
-    dataTable: rows.length > 0 ? dataTableComponent({
-      title: 'BUILD INFO',
-      rows: rows
-    }) : null
-  });
-
-  return state;
-}
-
-/** The main render function. */
-function render(state) {
-  var items = [
-    fullGraphComponent.render(state.mainMetricsGraph)
-  ];
-  if (state.helperMetricsGroup) {
-    items.push(metricsGroupComponent.render(state.helperMetricsGroup));
-  }
-  items.push(metricsGroupComponent.render(state.gceMetricsGroup));
-  if (state.dataTable) {
-    items.push(dataTableComponent.render(state.dataTable));
-  }
-  return h('div.instance-view', items);
-}
-
-function genCloudSSHLink(zone, instance) {
-  return 'https://cloudssh.developers.google.com/projects/' +
-      'vanadium-production/zones/' + zone +
-      '/instances/' + instance + '?authuser=0&hl=en_US';
-}
-
-function genLogsLink(instanceId, logName) {
-  return 'https://pantheon.corp.google.com/project/vanadium-production/logs?' +
-      'service=compute.googleapis.com&key1=instance&key2=' + instanceId +
-      '&logName=' + logName +
-      '&minLogLevel=0&expandAll=false&timezone=America%2FLos_Angeles';
-}
diff --git a/oncall/client/browser/components/metric-actions-panel/index.js b/oncall/client/browser/components/metric-actions-panel/index.js
new file mode 100644
index 0000000..6ddec8e
--- /dev/null
+++ b/oncall/client/browser/components/metric-actions-panel/index.js
@@ -0,0 +1,90 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+var hg = require('mercury');
+var h = require('mercury').h;
+
+var Util = require('../../util');
+
+module.exports = {
+  render: render
+};
+
+var DEFAULT_NAMESPACE_ID = '4b20e0dc-cacf-11e5-87ec-42010af0020b';
+
+/** The main render function. */
+function render(state, selectedMetric, data) {
+  var panel = h('div.metric-actions-content', [
+      h('div.row', [
+        h('div.item-label', 'Service Name'),
+        h('div.item-value', selectedMetric.serviceName)
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Service Version'),
+        h('div.item-value', selectedMetric.metricData.ServiceVersion)
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Metric Type'),
+        h('div.item-value',
+          selectedMetric.metricData.ResultType.replace('resultType', ''))
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Metric Name'),
+        h('div.item-value', selectedMetric.metricData.MetricName)
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Current Value'),
+        h('div.item-value',
+          Util.formatValue(selectedMetric.metricData.CurrentValue))
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Logs'),
+        h('div.item-value', h('a', {
+          href: 'logs?p=' + selectedMetric.metricData.Project +
+              '&z=' + selectedMetric.metricData.Zone +
+              '&d=' + selectedMetric.metricData.PodName +
+              '&c=' + selectedMetric.metricData.MainContainer,
+          target: '_blank'
+        }, selectedMetric.metricData.MainContainer)),
+      ]),
+      h('div.space'),
+      h('div.row', [
+        h('div.item-label', 'Pod Name'),
+        h('div.item-value', h('a', {
+          href: 'https://app.google.stackdriver.com/gke/pod/1009941:vanadium:' +
+              DEFAULT_NAMESPACE_ID + ':' + selectedMetric.metricData.PodUID,
+          target: '_blank'
+        }, selectedMetric.metricData.Instance)),
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Pod Node'),
+        h('div.item-value', h('a', {
+          href: 'https://app.google.stackdriver.com/instances/' +
+              data.Instances[selectedMetric.metricData.PodNode],
+          target: '_blank'
+        }, selectedMetric.metricData.PodNode)),
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Pod Config'),
+        h('div.item-value', h('a', {
+          href: 'cfg?p=' + selectedMetric.metricData.Project +
+              '&z=' + selectedMetric.metricData.Zone +
+              '&d=' + selectedMetric.metricData.PodName,
+          target: '_blank'
+        }, 'cfg')),
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Pod Status'),
+        h('div.item-value', selectedMetric.metricData.PodStatus)
+      ]),
+      h('div.row', [
+        h('div.item-label', 'Zone'),
+        h('div.item-value', selectedMetric.metricData.Zone)
+      ]),
+      h('div.btn-close', {
+        'ev-click': hg.send(state.channels.closeMetricActionsPanel)
+      }, 'Close')
+  ]);
+  return h('div.metric-actions-container', panel);
+}
diff --git a/oncall/client/browser/components/page-header/index.js b/oncall/client/browser/components/page-header/index.js
index f1178b8..342c4e7 100644
--- a/oncall/client/browser/components/page-header/index.js
+++ b/oncall/client/browser/components/page-header/index.js
@@ -17,7 +17,6 @@
 var h = require('mercury').h;
 var dateformat = require('dateformat');
 
-var AppStateMgr = require('../../appstate-manager');
 var staleDataThresholdInSec = 900;
 
 module.exports = create;
@@ -26,8 +25,9 @@
 /** Constructor. */
 function create(data) {
   var state = hg.state({
-    // The timestamp when the current data was loaded from the backend server.
-    collectionTimestamp: hg.value(data.collectionTimestamp),
+    // The time period of the current data.
+    startTimestamp: hg.value(data.startTimestamp),
+    endTimestamp: hg.value(data.endTimestamp),
 
     // IDs of current oncalls.
     oncallIds: hg.array(data.oncallIds),
@@ -36,35 +36,12 @@
     loadingData: hg.value(data.loadingData),
 
     // Whether there is any error loading data.
-    hasLoadingFailure: hg.value(data.hasLoadingFailure),
-
-    channels: {
-      clickNavItem: clickNavItem
-    }
+    hasLoadingFailure: hg.value(data.hasLoadingFailure)
   });
 
   return state;
 }
 
-/** Callback when a navigation item is clicked. */
-function clickNavItem(state, data) {
-  if (data.level ==='global') {
-    AppStateMgr.setAppState({
-      'level': data.level,
-      'zoneLevelZone': '',
-      'zoneLevelType': '',
-      'instanceLevelInstance': '',
-      'instanceLevelZone': ''
-    });
-  } else if (data.level === 'zone') {
-    AppStateMgr.setAppState({
-      'level': data.level,
-      'instanceLevelInstance': '',
-      'instanceLevelZone': ''
-    });
-  }
-}
-
 /** The main render function. */
 function render(state) {
   // Oncalls' pictures.
@@ -82,13 +59,13 @@
   var timeClass = '.time';
   var infoClass = '.info';
   var staleData = false;
-  if (state.collectionTimestamp >= 0) {
-    var date = new Date(state.collectionTimestamp * 1000);
+  if (state.endTimestamp >= 0) {
+    var date = new Date(state.endTimestamp * 1000);
     strTime = dateformat(date);
 
     // Check stale data.
     var curTs = Math.round(new Date().getTime() / 1000.0);
-    if (curTs - state.collectionTimestamp > staleDataThresholdInSec) {
+    if (curTs - state.endTimestamp > staleDataThresholdInSec) {
       infoClass += '.stale-data';
       staleData = true;
     }
@@ -107,49 +84,15 @@
     timeClass += '.failure';
   }
 
-  // Current view level and navigation items.
-  var navTitle = '';
-  var navItems = [];
-  var level = AppStateMgr.getAppState('level');
-  var zoneLevelZone = AppStateMgr.getAppState('zoneLevelZone');
-  if (level === 'global') {
-    navTitle = 'Global Status';
-  } else if (level === 'zone') {
-    var zoneType = AppStateMgr.getAppState('zoneLevelType')
-        .startsWith('CloudService') ? 'Vanadium Services' : 'Nginx';
-    navTitle = zoneType + ' @ ' + zoneLevelZone;
-    navItems.push(h('div.navitems-container', [
-      h('div.navitem', {
-        'ev-click': hg.send(state.channels.clickNavItem, {level: 'global'})
-      }, 'GLOBAL ←')
-    ]));
-  } else if (level === 'instance') {
-    var instanceType = AppStateMgr.getAppState('instanceLevelInstance')
-        .startsWith('vanadium') ? 'Vanadium Services' : 'Nginx';
-    navTitle =
-        instanceType + ' @ ' + AppStateMgr.getAppState('instanceLevelInstance');
-    navItems.push(h('div.navitems-container', [
-      h('div.navitem', {
-        'ev-click': hg.send(state.channels.clickNavItem, {level: 'global'})
-      }, 'GLOBAL ←'),
-      h('div.navitem', {
-        'ev-click': hg.send(state.channels.clickNavItem,
-            {level: 'zone', zone: zoneLevelZone})
-      }, zoneLevelZone.toUpperCase() + ' ←')
-    ]));
-  }
-
-  navItems.push(h('div.navtitle', h('span', navTitle.toUpperCase())));
   return h('div.header', [
       h('div' + infoClass, [
         h('div.dashboard-title', [
           h('div#logo', ''),
-          h('div.title-and-time', [
-            h('div.title', 'Oncall Dashboard'),
-            h('div' + timeClass, strTime)
-          ])
         ]),
-        h('div.navtitle-container', navItems),
+        h('div.title-and-time', [
+          h('div.title', 'Vanadium Oncall Dashboard'),
+          h('div' + timeClass, strTime)
+        ]),
         h('div.pics', pics)
       ])
   ]);
diff --git a/oncall/client/browser/components/status-table/index.js b/oncall/client/browser/components/status-table/index.js
new file mode 100644
index 0000000..43246f9
--- /dev/null
+++ b/oncall/client/browser/components/status-table/index.js
@@ -0,0 +1,291 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+var hg = require('mercury');
+var h = require('mercury').h;
+var svg = require('virtual-dom/virtual-hyperscript/svg');
+
+var Consts = require('../../constants');
+var MouseMoveHandler = require('../../mousemove-handler.js');
+var Util = require('../../util');
+
+module.exports = create;
+module.exports.render = render;
+
+var tableRows = [
+  // Mounttable.
+  {
+    rowHeader: Consts.metricNames.MN_MOUNTTABLE,
+    columns: [
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
+        label: 'LATENCY',
+        metricName: Consts.metricNames.MN_MOUNTTABLE,
+        threshold: 2000
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_QPS,
+        label: 'QPS',
+        metricName: Consts.metricNames.MN_MOUNTTABLE
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_METADATA,
+        label: 'BUILD AGE (h)',
+        metricName: Consts.metricNames.MN_MOUNTTABLE
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_COUNTERS,
+        label: 'MOUNTED SERVERS',
+        metricName: Consts.metricNames.MN_MT_MOUNTED_SERVERS
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_COUNTERS,
+        label: 'NODES',
+        metricName: Consts.metricNames.MN_MT_NODES
+      },
+    ]
+  },
+  // Roled.
+  {
+    rowHeader: Consts.metricNames.MN_ROLE,
+    columns: [
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
+        label: 'LATENCY',
+        metricName: Consts.metricNames.MN_ROLE,
+        threshold: 2000
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_QPS,
+        label: 'QPS',
+        metricName: Consts.metricNames.MN_ROLE
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_METADATA,
+        label: 'BUILD AGE (h)',
+        metricName: Consts.metricNames.MN_ROLE
+      }
+    ]
+  },
+  // Proxy.
+  {
+    rowHeader: Consts.metricNames.MN_PROXY,
+    columns: [
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
+        label: 'LATENCY',
+        metricName: Consts.metricNames.MN_PROXY,
+        threshold: 2000
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_QPS,
+        label: 'QPS',
+        metricName: Consts.metricNames.MN_PROXY
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_METADATA,
+        label: 'BUILD AGE (h)',
+        metricName: Consts.metricNames.MN_PROXY
+      }
+    ]
+  },
+  // Identityd.
+  {
+    rowHeader: Consts.metricNames.MN_IDENTITY,
+    columns: [
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
+        label: 'LATENCY (MACAROON)',
+        metricName: Consts.metricNames.MN_MACAROON,
+        threshold: 2000
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
+        label: 'LATENCY (BINARY DISCHARGER)',
+        metricName: Consts.metricNames.MN_BINARY_DISCHARGER,
+        threshold: 2000
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
+        label: 'LATENCY (GOOGLE IDENTITY)',
+        metricName: Consts.metricNames.MN_GOOGLE_IDEN,
+        threshold: 2000
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_QPS,
+        label: 'QPS',
+        metricName: Consts.metricNames.MN_IDENTITY
+      },
+      {
+        dataKey: Consts.dataKeys.DK_SERVICE_METADATA,
+        label: 'BUILD AGE (h)',
+        metricName: Consts.metricNames.MN_IDENTITY
+      }
+    ]
+  }
+];
+
+/** Constructor. */
+function create(data) {
+  if (!data) {
+    return null;
+  }
+
+  return hg.state({
+    // Raw data.
+    data: hg.struct(data.data),
+
+    mouseOffsetFactor: hg.value(-1),
+    showMetricActionsPanel: hg.value(false),
+
+    channels: {
+      mouseMoveOnSparkline: mouseMoveOnSparkline,
+      mouseOutOfSparkline: mouseOutOfSparkline,
+    }
+  });
+}
+
+/** Callback for moving mouse on sparkline. */
+function mouseMoveOnSparkline(state, data) {
+  state.mouseOffsetFactor.set(data.f);
+}
+
+/** Callback for moving mouse out of sparkline. */
+function mouseOutOfSparkline(state) {
+  state.mouseOffsetFactor.set(-1);
+}
+
+/** The main render function. */
+function render(globalState, state) {
+  var data = state.data;
+
+  var rows = tableRows.map(function(rowData) {
+    var cols = rowData.columns.map(function(colData) {
+      // Create a column for a metric.
+      var colHeader = h('div.col-header', colData.label);
+      var metricsData = data[colData.dataKey][colData.metricName];
+      var sparkLines = metricsData.map(function(metricData) {
+        // 100 is the default logical width of any svg graphs.
+        var points = '0,100 100,100';
+        if (metricData && metricData.HistoryTimestamps) {
+          points = Util.genPolylinePoints(
+            metricData.HistoryTimestamps, metricData.HistoryValues,
+            data.MinTime, data.MaxTime,
+            metricData.MinValue, metricData.MaxValue);
+        }
+        var curValue = Util.formatValue(metricData.CurrentValue);
+        // Handle error when getting time series.
+        var hasErrors = metricData.ErrMsg !== '';
+        var extraColMetricClass = '';
+        if (hasErrors) {
+          curValue = '?';
+          extraColMetricClass = '.err';
+        }
+        // Handle current value over threshold.
+        var overThreshold = (
+            colData.threshold && metricData.CurrentValue >= colData.threshold);
+        var thresholdValue = -1;
+        if (overThreshold) {
+          extraColMetricClass = '.unhealthy';
+          thresholdValue = (colData.threshold-metricData.MinValue)/
+              (metricData.MaxValue-metricData.MinValue)*100.0;
+        }
+        // Handle stale data.
+        var tsLen = metricData.HistoryTimestamps.length;
+        if (tsLen > 0) {
+          var lastTimestamp = metricData.HistoryTimestamps[tsLen - 1];
+          if (data.MaxTime - lastTimestamp > 300) {
+            extraColMetricClass = '.stale';
+          }
+        } else {
+          extraColMetricClass = '.unhealthy';
+        }
+
+        // Mouse line.
+        var extraCurValueClass = '';
+        var mouseOffset = 100 * state.mouseOffsetFactor;
+        if (!hasErrors && mouseOffset >= 0) {
+          curValue = Util.formatValue(Util.interpolateValue(
+              metricData.CurrentValue, state.mouseOffsetFactor,
+              metricData.HistoryTimestamps, metricData.HistoryValues));
+          extraCurValueClass = '.history';
+        }
+
+        return h('div.col-metric' + extraColMetricClass, {
+          'title': hasErrors ?
+              metricData.ErrMsg : metricData.Instance + ', ' + metricData.Zone,
+          'ev-click': hg.send(globalState.channels.mouseClickOnMetric, {
+            metricData: metricData,
+            serviceName: rowData.rowHeader
+          })
+        }, [
+          h('div.highlight-overlay'),
+          renderMouseLine(mouseOffset),
+          h('div.sparkline', {
+            'ev-mousemove': new MouseMoveHandler(
+                state.channels.mouseMoveOnSparkline),
+            'ev-mouseout': hg.send(state.channels.mouseOutOfSparkline)
+          }, [
+            renderThreshold(thresholdValue),
+            renderSparkline(points)
+          ]),
+          h('div.cur-value' + extraCurValueClass, [curValue])
+        ]);
+      });
+      var items = [colHeader];
+      items = items.concat(sparkLines);
+      return h('div.col', items);
+    });
+    cols.unshift(h('div.row-header', Consts.getDisplayName(rowData.rowHeader)));
+    return h('div.row', cols);
+  });
+
+  return h('div.status-table', rows);
+}
+
+/**
+ * Renders sparkline for the given points.
+ * @param {string} points - A string in the form of "x1,y1 x2,y2 ...".
+ */
+function renderSparkline(points) {
+  return svg('svg', {
+    'class': 'content',
+    'viewBox': '0 0 100 100',
+    'preserveAspectRatio': 'none'
+  }, [
+    svg('polyline', {'points': points}),
+  ]);
+}
+
+/**
+ * Renders threshold line.
+ */
+function renderThreshold(value) {
+  return svg('svg', {
+    'class': 'threshold',
+    'viewBox': '0 0 100 100',
+    'preserveAspectRatio': 'none'
+  }, [
+    svg('path', {
+      'd': 'M 0 ' + value + ' L 100 ' + value,
+      'stroke-dasharray': '2,2'
+    }),
+  ]);
+}
+
+/**
+ * Renders mouse line at the given offset.
+ * @param {Number} mouseOffset - The logical offset for the mouse line.
+ */
+function renderMouseLine(mouseOffset) {
+  return svg('svg', {
+    'class': 'mouse-line',
+    'viewBox': '0 0 100 100',
+    'preserveAspectRatio': 'none'
+  }, [
+    svg('polyline', {
+      'points': mouseOffset + ',0 ' + mouseOffset + ',100'
+    })
+  ]);
+}
diff --git a/oncall/client/browser/components/summary-table/columns.js b/oncall/client/browser/components/summary-table/columns.js
deleted file mode 100644
index c58c3a1..0000000
--- a/oncall/client/browser/components/summary-table/columns.js
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * Columns of the summary table.
- */
-
-var hg = require('mercury');
-var h = require('mercury').h;
-var svg = require('virtual-dom/virtual-hyperscript/svg');
-
-var AppStateMgr = require('../../appstate-manager');
-var Consts = require('../../constants');
-var MouseMoveHandler = require('../../mousemove-handler.js');
-var Util = require('../../util');
-var summaryTableMetricNamesColumnComponent = require('./metricnames-column');
-
-module.exports = {
-  render: render
-};
-
-/** The main render function. */
-function render(state) {
-  var data = state.data.Zones;
-  var level = AppStateMgr.getAppState('level');
-
-  var cols = [hg.partial(summaryTableMetricNamesColumnComponent.render, state)];
-  if (level === 'global') {
-    cols = cols.concat(genCellsInGlobalLevel(state, data));
-  } else if (level === 'zone') {
-    cols = cols.concat(genCellsInZoneLevel(state, data));
-  }
-  return h('div.columns-container', cols);
-}
-
-/** Generates data cells in the "globel" level. */
-function genCellsInGlobalLevel(state, data) {
-  var aggType = AppStateMgr.getAppState('globalLevelAggType');
-  var cols = Consts.orderedZones.filter(function(zone) {
-    return data[zone];
-  }).map(function(zone) {
-    var aggData = data[zone][aggType];
-    var rows = [];
-    Consts.mainMetrics.forEach(function(curMetric, index) {
-      var dataKey = curMetric.dataKey;
-      var metricKey = curMetric.metricKey;
-      if (!Util.isEmptyObj(aggData[dataKey])) {
-        rows.push(renderCell(
-            state, aggData, aggData.Range.MinTime, aggData.Range.MaxTime,
-            dataKey, metricKey, zone, zone));
-      } else {
-        rows.push(renderFillerCell(state));
-      }
-      if (curMetric.addMinorDivider) {
-        rows.push(h('div.minor-divider'));
-      }
-      if (curMetric.addMajorDivider) {
-        rows.push(h('div.major-divider'));
-      }
-    });
-    return h('div.zone-col', rows);
-  });
-  return cols;
-}
-
-/** Generates data cells in the "zone" level. */
-function genCellsInZoneLevel(state, data) {
-  var zoneLevelZone = AppStateMgr.getAppState('zoneLevelZone');
-  var zoneLevelType = AppStateMgr.getAppState('zoneLevelType');
-
-  // Filter metrics and instances by zone level type (CloudService or Nginx).
-  var metrics = (zoneLevelType === 'CloudService' ?
-      Consts.cloudServiceMetrics.concat(Consts.cloudServiceGCEMetrics) :
-      Consts.nginxMetrics.concat(Consts.nginxGCEMetrics));
-  var instancePrefix = (zoneLevelType === 'CloudService' ?
-      'vanadium-' : 'nginx-');
-  var instanceNames = Object.keys(data[zoneLevelZone].Instances);
-  var cols = instanceNames.sort().filter(function(name) {
-    return name.startsWith(instancePrefix);
-  }).map(function(instance) {
-    var instanceData = data[zoneLevelZone].Instances[instance];
-    var rows = [];
-    // Calculate the min/max timestamps across all metrics.
-    var minTime = Number.MAX_VALUE;
-    var maxTime = 0;
-    metrics.forEach(function(curMetric) {
-      var dataKey = curMetric.dataKey;
-      var metricKey = curMetric.metricKey;
-      if (!Util.isEmptyObj(instanceData[dataKey])) {
-        var metricData = instanceData[dataKey][metricKey];
-        if (metricData) {
-          if (metricData.MinTime < minTime) {
-            minTime = metricData.MinTime;
-          }
-          if (metricData.MaxTime > maxTime) {
-            maxTime = metricData.MaxTime;
-          }
-        }
-      }
-    });
-    metrics.forEach(function(curMetric, index) {
-      var dataKey = curMetric.dataKey;
-      var metricKey = curMetric.metricKey;
-      if (!Util.isEmptyObj(instanceData[dataKey])) {
-        rows.push(renderCell(
-            state, instanceData, minTime, maxTime,
-            dataKey, metricKey, instance, zoneLevelZone));
-      } else {
-        rows.push(renderFillerCell(state));
-      }
-      if (curMetric.addMinorDivider) {
-        rows.push(h('div.minor-divider'));
-      }
-      if (curMetric.addMajorDivider) {
-        rows.push(h('div.major-divider'));
-      }
-    });
-    return h('div.zone-col', rows);
-  });
-  return cols;
-}
-
-/**
- * Renders a single table cell.
- * @param {hg.state} state - The component's state.
- * @param {Object} cellData - The data object for the cell.
- * @param {Number} minTime - The minimum time to render the sparkline for.
- * @param {Number} maxTime - The maximum time to render the sparkline for.
- * @param {string} dataKey - See comments of createMetric function in
- *     constants.js.
- * @param {string} metricKey - See comments of createMetric function in
- *     constants.js.
- * @param {string} column - The column name of the cell.
- * @param {string} zone - The zone name associated with the cell.
- */
-function renderCell(state, cellData, minTime, maxTime,
-    dataKey, metricKey, column, zone) {
-  var data = cellData[dataKey][metricKey];
-  var health = 'unhealthy';
-  if (data) {
-    health = (data.Healthy ? 'healthy' : 'unhealthy');
-  }
-
-  // 100 is the default logical width of any svg graphs.
-  var points = '0,100 100,100';
-  var mouseOffset = 100 * state.mouseMoveCellData.offsetFactor;
-  var mouseEventData = {column: column, dataKey: dataKey, metricKey: metricKey};
-  if (data && data.HistoryTimestamps) {
-    points = Util.genPolylinePoints(
-      data.HistoryTimestamps, data.HistoryValues,
-      minTime, maxTime, data.MinValue, data.MaxValue);
-  }
-
-  // Show mouse line based on the dataKey type (CloudService or Nginx).
-  var mouseMoveOnCloudServices =
-      state.mouseMoveCellData.dataKey.startsWith('CloudService');
-  var mouseMoveOnNginx = state.mouseMoveCellData.dataKey.startsWith('Nginx');
-  var showMouseLine = (column === state.mouseMoveCellData.column &&
-      ((dataKey.startsWith('CloudService') && mouseMoveOnCloudServices) ||
-       (dataKey.startsWith('Nginx') && mouseMoveOnNginx)));
-
-  var curValue = data ? data.CurrentValue : 0;
-  var curValueClass = 'value';
-  if (data && showMouseLine) {
-    curValue = Util.interpolateValue(
-        data.CurrentValue, state.mouseMoveCellData.offsetFactor,
-        data.HistoryTimestamps, data.HistoryValues);
-    curValueClass += '.history';
-  }
-  var formattedCurValue = data ? Util.formatValue(curValue) : '?';
-  var items = [
-    h('div.highlight-overlay'),
-    h('div.mouse-line', showMouseLine ? renderMouseLine(mouseOffset) : []),
-    h('div.sparkline', {
-      'ev-mousemove': new MouseMoveHandler(
-          state.channels.mouseMoveOnSparkline, mouseEventData),
-      'ev-mouseout': hg.send(state.channels.mouseOutOfSparkline)
-    }, renderSparkline(points)),
-    h('div.' + curValueClass, formattedCurValue)
-  ];
-
-  var cellClassName = 'zone-col-cell.cell.' + health;
-  return h('div.' + cellClassName, {
-    'ev-mouseout': hg.send(state.channels.mouseOutOfTableCell),
-    'ev-mouseover': hg.send(state.channels.mouseOverTableCell, mouseEventData),
-    'ev-click': hg.send(state.channels.clickCell, {
-      column: column,
-      dataKey: dataKey,
-      zone: zone
-    })
-  }, items);
-}
-
-/** Renders a empty filler table cell. */
-function renderFillerCell(state) {
-  return h('div.zone-col-cell-filler.cell', {
-    'ev-mouseout': hg.send(state.channels.mouseOutOfTableCell)
-  });
-}
-
-/**
- * Renders sparkline for the given points.
- * @param {string} points - A string in the form of "x1,y1 x2,y2 ...".
- */
-function renderSparkline(points) {
-  return svg('svg', {
-    'class': 'content',
-    'viewBox': '0 0 100 100',
-    'preserveAspectRatio': 'none'
-  }, [
-    svg('polyline', {'points': points}),
-  ]);
-}
-
-/**
- * Renders mouse line at the given offset.
- * @param {Number} mouseOffset - The logical offset for the mouse line.
- */
-function renderMouseLine(mouseOffset) {
-  return svg('svg', {
-    'class': 'mouse-line',
-    'viewBox': '0 0 100 100',
-    'preserveAspectRatio': 'none'
-  }, [
-    svg('polyline', {
-      'points': mouseOffset + ',0 ' + mouseOffset + ',100'
-    })
-  ]);
-}
diff --git a/oncall/client/browser/components/summary-table/header.js b/oncall/client/browser/components/summary-table/header.js
deleted file mode 100644
index cc5230d..0000000
--- a/oncall/client/browser/components/summary-table/header.js
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * The header row of the summary table.
- *
- * In the "global" view, it shows the aggregation switch and all the zones. In
- * the "zone" view, it shows all the instances for that zone.
- */
-
-var hg = require('mercury');
-var h = require('mercury').h;
-
-var Consts = require('../../constants');
-var AppStateMgr = require('../../appstate-manager');
-var Util = require('../../util');
-
-module.exports = {
-  render: render
-};
-
-/** The main render function. */
-function render(state) {
-  var data = state.data.Zones;
-  var level = AppStateMgr.getAppState('level');
-
-  // First column is the aggregation switch.
-  var cols = [renderGlobalLevelAggSwitch(state)];
-
-  // Generate the rest of the columns.
-  var headers = [];
-  if (level === 'global') {
-    // In the "global" level, add all the zones.
-    headers = Consts.orderedZones.filter(function(zone) {
-      return data[zone];
-    });
-  } else if (level === 'zone') {
-    // In the "zone" level, add instances based on the type.
-    var zoneLevelZone = AppStateMgr.getAppState('zoneLevelZone');
-    var zoneLevelType = AppStateMgr.getAppState('zoneLevelType');
-    var instancePrefix =
-        (zoneLevelType === 'CloudService' ? 'vanadium-' : 'nginx-');
-    // Filter instances by instancePrefix (determined by zoneLevelType).
-    headers = Object.keys(data[zoneLevelZone].Instances)
-        .sort().filter(function(name) {
-      return name.startsWith(instancePrefix);
-    });
-  }
-  cols = cols.concat(renderHeaders(state, headers));
-
-  return h('div.zone-names-row', cols);
-}
-
-/** Renders the aggregtion type switch. */
-function renderGlobalLevelAggSwitch(state) {
-  var aggType = AppStateMgr.getAppState('globalLevelAggType');
-  var level = AppStateMgr.getAppState('level');
-  if (level === 'global') {
-    // Only generate the switch (two links: average and max) in the "global"
-    // level to specify the aggregation method for all the cell data.
-    return h('div.view-type.zone-name-cell', [
-      h('div.average', {
-        className: aggType  === 'Average' ? 'selected' : '',
-        'ev-click': hg.send(state.channels.changeGlobalLevelAggType, 'Average')
-      }, 'average'),
-      h('div.max', {
-        className: aggType === 'Max' ? 'selected' : '',
-        'ev-click': hg.send(state.channels.changeGlobalLevelAggType, 'Max')
-      }, 'max')
-    ]);
-  } else {
-    // In the "zone" level, don't show the switch.
-    return h('div.view-type-dummy');
-  }
-}
-
-/**
- * Renders the given headers.
- * @param {hg.state} state
- * @param {Array<string>} headers
- */
-function renderHeaders(state, headers) {
-  var hoveredColumn = state.hoveredCellData.column;
-  return headers.map(function(header) {
-    var className = 'zone-name-cell';
-    if (header === hoveredColumn) {
-      className += '.highlight';
-    }
-    if (!checkMetricsHealthForHeader(state, header)) {
-      className += '.unhealthy';
-    }
-    return h('div.' + className, h('span', shortenHeaderLabel(header)));
-  });
-}
-
-/**
- * Checks whether all the metrics for the given header are healthy.
- * @param {hg.state} state
- * @param {string} header
- * @return {boolean}
- */
-function checkMetricsHealthForHeader(state, header) {
-  var data = state.data.Zones;
-  var aggType = AppStateMgr.getAppState('globalLevelAggType');
-  var level = AppStateMgr.getAppState('level');
-  var isHealthy = true;
-
-  // In different levels, we check different health data for a given header.
-  // The health data is already calculated and recorded in the data object
-  // loaded from the backend server.
-  var i, curMetric;
-  if (level === 'global') {
-    var zone = header;
-    var aggData = data[zone][aggType];
-    for (i = 0; i < Consts.mainMetrics.length; i++) {
-      curMetric = Consts.mainMetrics[i];
-      if (!Util.isEmptyObj(aggData[curMetric.dataKey])) {
-        var metricAggData = aggData[curMetric.dataKey][curMetric.metricKey];
-        if (!metricAggData || !metricAggData.Healthy) {
-          isHealthy = false;
-          break;
-        }
-      }
-    }
-  } else if (level === 'zone') {
-    var instance = header;
-    var zoneLevelZone = AppStateMgr.getAppState('zoneLevelZone');
-    var zoneLevelType = AppStateMgr.getAppState('zoneLevelType');
-    var metrics = (zoneLevelType === 'CloudService' ?
-        Consts.cloudServiceMetrics : Consts.nginxMetrics);
-    var instanceData = data[zoneLevelZone].Instances[instance];
-    for (i = 0; i < metrics.length; i++) {
-      curMetric = metrics[i];
-      if (!Util.isEmptyObj(instanceData[curMetric.dataKey])) {
-        var metricData = instanceData[curMetric.dataKey][curMetric.metricKey];
-        if (!metricData || !metricData.Healthy) {
-          isHealthy = false;
-          break;
-        }
-      }
-    }
-  }
-
-  return isHealthy;
-}
-
-/**
- * Shortens the header label by removing the common prefix.
- * @param {string} header
- * @return {string} The shortened header label.
- */
-function shortenHeaderLabel(header) {
-  if (header.startsWith('vanadium')) {
-    return header.replace('vanadium-', '');
-  }
-  if (header.startsWith('nginx')) {
-    return header.replace('nginx-', '');
-  }
-  return header;
-}
diff --git a/oncall/client/browser/components/summary-table/index.js b/oncall/client/browser/components/summary-table/index.js
deleted file mode 100644
index f2c8117..0000000
--- a/oncall/client/browser/components/summary-table/index.js
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * The summary table showing the data in "global" and "zone" level.
- *
- * In the "global" level, the table's columnes are zones with available data,
- * and its rows are metrics for vanadium services and nginx servers. Each table
- * cell (for zone Z and metric M) shows data aggregated across all the instances
- * in that zone for that metric.The aggregation method is either "average" or
- * "max", and can be selected at the top left corner of the summary table.
- *
- * Users can "drill down" to the "zone" level by clicking on any table cell.
- *
- * In the "zone" level, the table's columns are instances in that zone, and its
- * rows are metrics for EITHER vanadium services or nginx servers (depending on
- * which cell users click in the "global" level). In this level, users can drill
- * further down to the "instance" level by clicking on any table cell. The
- * instance view will be rendered by the "instance-view" component.
- */
-
-var hg = require('mercury');
-var h = require('mercury').h;
-
-var AppStateMgr = require('../../appstate-manager');
-var summaryTableHeaderComponent = require('./header');
-var summaryTableColumnsComponent = require('./columns');
-
-module.exports = create;
-module.exports.render = render;
-
-/** Constructor. */
-function create(data) {
-  if (!data) {
-    return null;
-  }
-
-  return hg.state({
-    // Raw data.
-    data: hg.struct(data.data),
-
-    // Keeps track of which cell the mouse cursor is hovering over.
-    //
-    // This will be used to highlight the hovered table cell as well as the
-    // corresponding column/row header cell.
-    hoveredCellData: hg.struct({
-      // Column name.
-      column: '',
-      // See "createMetric" function in constants.js.
-      dataKey: '',
-      // See "createMetric" function in constants.js.
-      metricKey: ''
-    }),
-
-    // Keeps track of the cell and relative location of the mouse cursor.
-    //
-    // This will be used to render a mouse line in all related table cells for
-    // easier data comparison.
-    mouseMoveCellData: hg.struct({
-      // Column name.
-      column: '',
-      // See "createMetric" function in constants.js.
-      dataKey: '',
-      // See "createMetric" function in constants.js.
-      offsetFactor: -1
-    }),
-
-    channels: {
-      changeGlobalLevelAggType: changeGlobalLevelAggType,
-      clickCell: clickCell,
-      mouseMoveOnSparkline: mouseMoveOnSparkline,
-      mouseOutOfSparkline: mouseOutOfSparkline,
-      mouseOverTableCell: mouseOverTableCell,
-      mouseOutOfTableCell: mouseOutOfTableCell
-    }
-  });
-}
-
-/**
- * Callback for changing the aggregation type in the "global" level.
- */
-function changeGlobalLevelAggType(state, newAggType) {
-  AppStateMgr.setAppState({'globalLevelAggType': newAggType});
-}
-
-/**
- * Callback for clicking a table cell.
- */
-function clickCell(state, data) {
-  var level = AppStateMgr.getAppState('level');
-  if (level === 'global') {
-    // Clicking a cell in the "global" level will go to the corresponding "zone"
-    // level.
-    var zoneLevelType =
-        data.dataKey.startsWith('CloudService') ? 'CloudService' : 'Nginx';
-    AppStateMgr.setAppState({
-      'level': 'zone',
-      'zoneLevelZone': data.column,
-      'zoneLevelType': zoneLevelType
-    });
-  } else if (level === 'zone') {
-    // Clicking a cell in the "zone" level will go to the corresponding
-    // "instance" level.
-    AppStateMgr.setAppState({
-      'level': 'instance',
-      'instanceLevelInstance': data.column,
-      'instanceLevelZone': data.zone,
-    });
-  }
-}
-
-/** Callback for moving mouse on sparkline. */
-function mouseMoveOnSparkline(state, data) {
-  state.mouseMoveCellData.set({
-    column: data.column,
-    dataKey: data.dataKey,
-    offsetFactor: data.f
-  });
-}
-
-/** Callback for moving mouse out of sparkline. */
-function mouseOutOfSparkline(state) {
-  state.mouseMoveCellData.set({
-    column: '',
-    dataKey: '',
-    offsetFactor: -1
-  });
-}
-
-/** Callback for moving mouse over a table cell. */
-function mouseOverTableCell(state, data) {
-  state.hoveredCellData.set({
-    column: data.column,
-    dataKey: data.dataKey,
-    metricKey: data.metricKey
-  });
-}
-
-/** Callback for moving mouse out of a table cell. */
-function mouseOutOfTableCell(state) {
-  state.hoveredCellData.set({
-    column: '',
-    dataKey: '',
-    metricKey: ''
-  });
-}
-
-/** The main render function. */
-function render(state) {
-  // Delegate rendering to sub-components.
-  return h('div.summary-table', [
-    hg.partial(summaryTableHeaderComponent.render, state),
-    hg.partial(summaryTableColumnsComponent.render, state)
-  ]);
-}
diff --git a/oncall/client/browser/components/summary-table/metricnames-column.js b/oncall/client/browser/components/summary-table/metricnames-column.js
deleted file mode 100644
index 0e0bf74..0000000
--- a/oncall/client/browser/components/summary-table/metricnames-column.js
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * Metric names column (the 1st column) of the summary table.
- */
-
-var h = require('mercury').h;
-
-var AppStateMgr = require('../../appstate-manager');
-var Consts = require('../../constants');
-var Util = require('../../util');
-
-module.exports = {
-  render: render
-};
-
-/** The main render function. */
-function render(state) {
-  var dataKey = state.hoveredCellData.dataKey;
-  var metricKey = state.hoveredCellData.metricKey;
-  var level = AppStateMgr.getAppState('level');
-
-  // In the "globel" level, we show all metrics.
-  // In the "zone" level, we only show either cloud services metrics or nginx
-  // metrics.
-  var metricNameRows = [];
-  var metrics = Consts.mainMetrics;
-  if (level === 'zone') {
-    var zoneLevelType = AppStateMgr.getAppState('zoneLevelType');
-    metrics = (zoneLevelType === 'CloudService' ?
-        Consts.cloudServiceMetrics.concat(Consts.cloudServiceGCEMetrics) :
-        Consts.nginxMetrics.concat(Consts.nginxGCEMetrics));
-  }
-
-  metrics.forEach(function(curMetric, index) {
-    var className = 'metric-name-cell.cell';
-    if (curMetric.dataKey === dataKey && curMetric.metricKey === metricKey) {
-      className += '.highlight';
-    }
-    if (!checkHealthForMetric(state, curMetric)) {
-      className += '.unhealthy';
-    }
-    // secontionLabel is the light grey label above each section of the table.
-    // For example: NGINX SERVER LOAD, NGINX GCE, etc.
-    if (curMetric.sectionLabel) {
-      var sectionLabelClassName = '.section-label';
-      if (curMetric.dataKey === dataKey) {
-        sectionLabelClassName += '.highlight-section';
-      }
-      metricNameRows.push(h('div.' + className, [
-        h('div' + sectionLabelClassName , h('span', curMetric.sectionLabel)),
-        h('div', h('span', curMetric.label))
-      ]));
-    } else {
-      metricNameRows.push(h('div.' + className, h('span', curMetric.label)));
-    }
-    if (curMetric.addMinorDivider) {
-      metricNameRows.push(h('div.minor-divider'));
-    }
-    if (curMetric.addMajorDivider) {
-      metricNameRows.push(h('div.major-divider'));
-    }
-  });
-  return h('div.metric-names-col', metricNameRows);
-}
-
-
-/**
- * Checks whether the given metric across all the zones/instances is healthy.
- * @param {hg.state} state
- * @param {Object} metric
- * @return {boolean}
- */
-function checkHealthForMetric(state, metric) {
-  var data = state.data.Zones;
-  var aggType = AppStateMgr.getAppState('globalLevelAggType');
-  var level = AppStateMgr.getAppState('level');
-  var isHealthy = true;
-
-  var i;
-  if (level === 'global') {
-    var availableZones = Consts.orderedZones.filter(function(zone) {
-      return data[zone];
-    });
-    for (i = 0; i < availableZones.length; i++) {
-      var zone = availableZones[i];
-      var aggData = data[zone][aggType];
-      if (!Util.isEmptyObj(aggData[metric.dataKey])) {
-        var metricAggData = aggData[metric.dataKey][metric.metricKey];
-        if (!metricAggData || !metricAggData.Healthy) {
-          isHealthy = false;
-          break;
-        }
-      }
-    }
-  } else if (level === 'zone') {
-    var zoneLevelZone = AppStateMgr.getAppState('zoneLevelZone');
-    var instances = Object.keys(data[zoneLevelZone].Instances).sort();
-    for (i = 0; i < instances.length; i++) {
-      var instance = instances[i];
-      var instanceData = data[zoneLevelZone].Instances[instance];
-      if (!Util.isEmptyObj(instanceData[metric.dataKey])) {
-        var metricData = instanceData[metric.dataKey][metric.metricKey];
-        if (!metricData || !metricData.Healthy) {
-          isHealthy = false;
-          break;
-        }
-      }
-    }
-  }
-
-  return isHealthy;
-}
diff --git a/oncall/client/browser/constants.js b/oncall/client/browser/constants.js
index db66902..6963893 100644
--- a/oncall/client/browser/constants.js
+++ b/oncall/client/browser/constants.js
@@ -16,6 +16,27 @@
   ASIA_EAST1_C: 'asia-east1-c',
 });
 
+/** Constants for all the metric names in the raw data. */
+var metricNames = Object.freeze({
+  MN_BINARY_DISCHARGER: 'binary discharger',
+  MN_GOOGLE_IDEN: 'google identity service',
+  MN_IDENTITY: 'identity service',
+  MN_MACAROON: 'macaroon service',
+  MN_MOUNTTABLE: 'mounttable',
+  MN_PROXY: 'proxy service',
+  MN_ROLE: 'role service',
+  MN_MT_MOUNTED_SERVERS: 'mounttable mounted servers',
+  MN_MT_NODES: 'mounttable nodes'
+});
+
+/** Constants for all the keys in raw data. */
+var dataKeys = Object.freeze({
+  DK_SERVICE_LATENCY: 'ServiceLatency',
+  DK_SERVICE_COUNTERS: 'ServiceCounters',
+  DK_SERVICE_QPS: 'ServiceQPS',
+  DK_SERVICE_METADATA: 'ServiceMetadata'
+});
+
 /** A map from long name strings to their shorter forms. */
 var displayNames = Object.freeze({
   'active-connections': 'ACTIVE CONN',
@@ -35,10 +56,11 @@
   'groups service latency': 'GROUPS',
   'healthCheckLatency': 'HEALTH LAT',
   'identityd': 'IDEN',
+  'identity service': 'IDENTITY',
   'macaroon service': 'MACAROON',
   'macaroon service latency': 'MACAROON',
   'memory-usage': 'RAM%',
-  'mounttable': 'MTB',
+  'mounttable': 'MOUNTTABLE',
   'mounttabled': 'MTB',
   'mounttable latency': 'MTB',
   'mounttable mounted servers': 'MTB SERVERS',
@@ -197,6 +219,8 @@
 
 module.exports = {
   zones: zones,
+  metricNames: metricNames,
+  dataKeys: dataKeys,
   orderedZones: [
     zones.US_CENTRAL1_C,
     zones.US_CENTRAL1_A,
diff --git a/oncall/client/browser/index.js b/oncall/client/browser/index.js
index 75ce280..8151bc9 100644
--- a/oncall/client/browser/index.js
+++ b/oncall/client/browser/index.js
@@ -7,11 +7,10 @@
 var request = require('superagent');
 var cookie = require('cookie');
 
-var AppStateMgr = require('./appstate-manager');
-var instanceViewComponent = require('./components/instance-view');
 var pageHeaderComponent = require('./components/page-header');
 var settingsPanelComponent = require('./components/settings');
-var summaryTableComponent = require('./components/summary-table');
+var statusTableComponent = require('./components/status-table');
+var metricActionsPanelComponent = require('./components/metric-actions-panel');
 
 /**
  * A variable to store the most update-to-date dashboard data.
@@ -20,9 +19,11 @@
  */
 var curData;
 
-// Initializes the app state manager and sets appStateChanged function as the
-// callback for app state changes.
-AppStateMgr.init(appStateChanged);
+/**
+ * A variable to keep track of current metric data shown in the
+ * metric actions panel.
+ */
+var selectedMetricData;
 
 // Ask mercury to listen to mousemove/mouseout/mouseover events.
 hg.Delegator().listenTo('mousemove');
@@ -36,35 +37,50 @@
 var state = hg.state({
   // The header panel at the top of the page.
   pageHeader: pageHeaderComponent({
-    collectionTimestamp: -1,
+    startTimestamp: -1,
+    endTimestamp: -1,
     oncallIds: ['_unknown', '_unknown'],
     loadingData: false,
     hasLoadingFailure: false
   }),
 
   components: hg.varhash({
-    // The summary table showing data on the "global" and "zone" level.
-    summaryTable: summaryTableComponent(null),
+    // The status table showing service status.
+    statusTable: statusTableComponent(null),
 
     // The view showing data on the "instance" level.
-    instanceView: instanceViewComponent(null),
+    // instanceView: instanceViewComponent(null),
   }),
 
   // Whether to show settings panel.
   showSettingsPanel: hg.value(false),
 
+  showMetricActionsPanel: hg.value(false),
+
   // Settings stored in cookies.
   settings: hg.varhash({
     darkTheme: hg.value(cookies.darkTheme === 'true')
   }),
 
   channels: {
+    mouseClickOnMetric: mouseClickOnMetric,
     changeTheme: changeTheme,
     clickOnSettingsGear: clickOnSettingsGear,
-    closeSettingsPanel: closeSettingsPanel
+    closeSettingsPanel: closeSettingsPanel,
+    closeMetricActionsPanel: closeMetricActionsPanel
   }
 });
 
+/** Callback for clicking on a metric. */
+function mouseClickOnMetric(state, data) {
+  selectedMetricData = data;
+  state.showMetricActionsPanel.set(true);
+}
+
+function closeMetricActionsPanel(state) {
+  state.showMetricActionsPanel.set(false);
+}
+
 /** Callback when user clicks on the settings gear. */
 function clickOnSettingsGear(state) {
   state.showSettingsPanel.set(true);
@@ -88,23 +104,26 @@
   var mainContent = [
     pageHeaderComponent.render(state.pageHeader),
   ];
-  if (state.components.summaryTable) {
+  if (state.components.statusTable) {
     mainContent.push(
-        h('div.main-container',
-          summaryTableComponent.render(state.components.summaryTable)));
+      h('div.main-container',
+          statusTableComponent.render(state, state.components.statusTable))
+    );
   }
-  if (state.components.instanceView) {
-    mainContent.push(
-        h('div.main-container',
-          instanceViewComponent.render(state.components.instanceView)));
-  }
-  mainContent.push(h('div.settings-gear', {
-    'ev-click': hg.send(state.channels.clickOnSettingsGear)
-  }));
+  mainContent.push(
+    h('div.settings-gear', {
+      'ev-click': hg.send(state.channels.clickOnSettingsGear)
+    })
+  );
   if (state.showSettingsPanel) {
     mainContent.push(hg.partial(settingsPanelComponent.render, state));
   }
 
+  if (state.showMetricActionsPanel) {
+    mainContent.push(hg.partial(metricActionsPanelComponent.render, state,
+          selectedMetricData, curData));
+  }
+
   var className = state.settings.darkTheme ? 'main.darkTheme' : 'main';
   return h('div.' + className, mainContent);
 };
@@ -121,7 +140,7 @@
       .accept('json')
       .timeout(30000)
       .end(function(err, res) {
-    if (!res.ok || err) {
+    if (!res || !res.ok || err) {
       state.pageHeader.hasLoadingFailure.set(true);
     } else {
       state.pageHeader.hasLoadingFailure.set(false);
@@ -137,58 +156,36 @@
 function processData(newData) {
   // Update components.
   curData = newData;
-  updateComponents(AppStateMgr.getCurState());
+  updateComponents();
 
   // Update the data loading indicator.
   state.pageHeader.loadingData.set(false);
 }
 
 /**
- * Callback function for app state changes.
- * @param {Object} curAppState - App's current state object.
- */
-function appStateChanged(curAppState) {
-  if (!state) {
-    return;
-  }
-  updateComponents(curAppState);
-}
-
-/**
  * Updates all page components.
- * @param {Object} curAppState - App's current state object.
  */
-function updateComponents(curAppState) {
+function updateComponents() {
   // Update page header.
-  state.pageHeader.collectionTimestamp.set(curData.CollectionTimestamp);
-  state.pageHeader.oncallIds.set(curData.OncallIDs.split(','));
+  state.pageHeader.endTimestamp.set(curData.MaxTime);
+  state.pageHeader.oncallIds.set(curData.Oncalls);
 
-  // Update summary table when the current view level is NOT "instance".
-  var summaryTableData = null;
-  if (curAppState.level !== 'instance') {
-    summaryTableData = summaryTableComponent({
-      data: curData
-    });
-  }
-  state.components.put('summaryTable', summaryTableData);
-
-  // Update instance view when the current view level is "instance".
-  var instanceViewData = null;
-  if (curAppState.level === 'instance') {
-    var instance = curAppState.instanceLevelInstance;
-    instanceViewData = instanceViewComponent({
-      collectionTimestamp: curData.CollectionTimestamp,
-      data: curData.Zones[curAppState.instanceLevelZone].Instances[instance],
-      appState: curAppState
-    });
-  }
-  state.components.put('instanceView' ,instanceViewData);
+  // Update status table.
+  var statusTableData = statusTableComponent({
+    data: curData
+  });
+  state.components.put('statusTable', statusTableData);
 }
 
 // Add an event handler for closing settings panel when esc key is pressed.
 document.onkeydown = function(evt) {
-  if (evt.keyCode === 27 && state.showSettingsPanel()) {
-    state.showSettingsPanel.set(false);
+  if (evt.keyCode === 27) {
+    if (state.showSettingsPanel()) {
+      state.showSettingsPanel.set(false);
+    }
+    if (state.showMetricActionsPanel()) {
+      state.showMetricActionsPanel.set(false);
+    }
   }
 };
 
diff --git a/oncall/client/stylesheets/components/data-table.css b/oncall/client/stylesheets/components/data-table.css
deleted file mode 100644
index 7495350..0000000
--- a/oncall/client/stylesheets/components/data-table.css
+++ /dev/null
@@ -1,60 +0,0 @@
-/* Copyright 2015 The Vanadium Authors. All rights reserved. */
-/* Use of this source code is governed by a BSD-style */
-/* license that can be found in the LICENSE file. */
-
-div.data-table {
-  margin-top: var(--subsection-margin-top);
-}
-
-
-div.data-table div.data-table-title {
-  inherits: div.group-title;
-  display: flex;
-  justify-content: space-between;
-  width: 100%;
-}
-
-div.data-table tr {
-  line-height: 14px;
-}
-
-div.data-table tr:first-child {
-  line-height: 18px;
-}
-
-div.data-table td {
-  padding-right: 12px;
-}
-
-div.data-table table {
-  color: var(--dark-green-grey);
-  font-size: 12px;
-  margin-top: 4px;
-}
-
-.darkTheme div.data-table table {
-  color: var(--light-green-grey);
-}
-
-.darkTheme div.data-table a {
-  color: LightBlue;
-}
-
-div.data-table tr:first-child td {
-  font-weight: var(--font-weight-medium);
-}
-
-div.data-table div.data-table-content {
-  padding-top: 2px;
-  font-size: 12px;
-  display: flex;
-}
-
-div.data-table div.type-header {
-  font-weight: var(--font-weight-medium);
-  line-height: 20px;
-}
-
-div.data-table div.column {
-  margin-right: 20px;
-}
diff --git a/oncall/client/stylesheets/components/full-graph.css b/oncall/client/stylesheets/components/full-graph.css
deleted file mode 100644
index 23f2575..0000000
--- a/oncall/client/stylesheets/components/full-graph.css
+++ /dev/null
@@ -1,149 +0,0 @@
-/* Copyright 2015 The Vanadium Authors. All rights reserved. */
-/* Use of this source code is governed by a BSD-style */
-/* license that can be found in the LICENSE file. */
-
-div.full-graph-container {
-}
-
-div.full-graph-container div.full-graph-title {
-  inherits: div.group-title;
-}
-
-div.full-graph-container div.metric-summary-container {
-  display: flex;
-}
-
-div.full-graph-container div.metric-summary-item {
-  flex-grow: 1;
-  padding-top: 4px;
-  position: relative;
-  width: 100%;
-  cursor: pointer;
-  margin-right: 2px;
-}
-
-div.full-graph-container div.metric-summary-item.hidden {
-  opacity: 0.15;
-}
-
-div.full-graph-container div.metric-summary-item.unhealthy {
-  background: var(--red-stripes-background-dimmed);
-}
-
-div.full-graph-container div.metric-summary-item:last-child {
-  margin-right: 0px !important;
-}
-
-div.full-graph-container div.metric-summary-title {
-  font-size: 12px;
-  color: var(--dark-green-grey);
-  line-height: 14px;
-  padding-top: 4px;
-}
-
-.darkTheme div.full-graph-container div.metric-summary-title {
-  color: var(--light-green-grey);
-}
-
-div.full-graph-container div.metric-summary-value {
-  font-size: 16px;
-  color: var(--cyan-800);
-  font-weight: var(--font-weight-medium);
-  line-height: 20px;
-  margin-bottom: 4px;
-}
-
-.darkTheme div.full-graph-container div.metric-summary-value {
-  color: var(--light-green);
-}
-
-div.full-graph-container div.metric-summary-value.unhealthy {
-  color: var(--dark-red);
-}
-
-.darkTheme div.full-graph-container div.metric-summary-value.unhealthy {
-  color: var(--light-red);
-}
-
-div.full-graph-container div.metric-summary-color {
-  height: 2px;
-}
-
-div.full-graph-container div.full-graph {
-  width: 100%;
-  height: var(--full-graph-height);
-  position: relative;
-}
-
-div.full-graph-container div.full-graph div.graph-y-axis {
-  width: var(--graph-number-div-width);
-}
-
-div.full-graph-container div.full-graph svg {
-  position: absolute;
-  top: 0px;
-  left: 0px;
-  height: 100%;
-  width: 100%;
-}
-
-div.full-graph-container div.full-graph svg polyline {
-  vector-effect: non-scaling-stroke;
-  fill: none;
-}
-
-div.full-graph-container div.full-graph svg.content polyline {
-  stroke: var(--light-grey);
-  stroke-width: 2;
-}
-
-div.full-graph-container svg.threshold-line {
-  shape-rendering: crispedges;
-}
-
-div.full-graph-container svg.threshold-line path {
-  vector-effect: non-scaling-stroke;
-  fill: none;
-  stroke: var(--dark-red);
-  stroke-width: 1;
-}
-
-div.full-graph-container div.full-graph div.threshold {
-  border-bottom: 2px dotted var(--dark-red);
-}
-
-div.full-graph-container div.full-graph svg.overlay polyline {
-  stroke: #CCC;
-  stroke-width: 1;
-}
-
-.darkTheme div.full-graph-container div.full-graph svg.overlay polyline {
-  stroke: #666;
-}
-
-div.full-graph-container svg.overlay {
-  shape-rendering: crispedges;
-}
-
-div.full-graph-container div.full-graph div.number {
- /* background-color: var(--light-grey);*/
-  width: var(--graph-number-div-width);
-  height: var(--full-graph-height);
-}
-
-div.full-graph-container div.time {
-  height: var(--graph-time-div-height);
-  border-top: 1px solid var(--light-grey);
-  justify-content: space-between;
-  display: flex;
-}
-
-.darkTheme div.full-graph-container div.time {
-  border-top: 1px solid #333;
-}
-
-div.full-graph-container div.time-label {
-  font-size: 10px;
-  color: var(--medium-grey);
-  padding: 0px 2px;
-}
diff --git a/oncall/client/stylesheets/components/header.css b/oncall/client/stylesheets/components/header.css
index 470c123..f16e3f1 100644
--- a/oncall/client/stylesheets/components/header.css
+++ b/oncall/client/stylesheets/components/header.css
@@ -29,6 +29,7 @@
 }
 
 div.header div.dashboard-title {
+  width: 120px;
   padding-left: 10px;
   display: flex;
 }
@@ -54,21 +55,19 @@
 div.header div.title-and-time {
   display: flex;
   flex-direction: column;
-  padding-top: 1px;
+  align-items: center;
 }
 
 div.header div.title {
-  font-size: 16px;
+  font-size: 18px;
   font-weight: var(--font-weight-medium);
-  line-height: 22px;
-  height: 22px;
+  padding-bottom: 4px;
 }
 
 div.header div.time {
   font-size: 10px;
   height: 12px;
   line-height: 12px;
-  padding-left: 1px;
 }
 
 div.header div.time.failure {
@@ -85,6 +84,8 @@
 div.header div.pics {
   display: flex;
   padding-right: 5px;
+  width: 120px;
+  justify-content: flex-end;
 }
 
 div.header div.pics img {
diff --git a/oncall/client/stylesheets/components/instance-view.css b/oncall/client/stylesheets/components/instance-view.css
deleted file mode 100644
index a65d674..0000000
--- a/oncall/client/stylesheets/components/instance-view.css
+++ /dev/null
@@ -1,8 +0,0 @@
-/* Copyright 2015 The Vanadium Authors. All rights reserved. */
-/* Use of this source code is governed by a BSD-style */
-/* license that can be found in the LICENSE file. */
-
-div.instance-view {
-  margin-top: 12px;
-  width: 640px;
-}
diff --git a/oncall/client/stylesheets/components/metric-actions-panel.css b/oncall/client/stylesheets/components/metric-actions-panel.css
new file mode 100644
index 0000000..5507a61
--- /dev/null
+++ b/oncall/client/stylesheets/components/metric-actions-panel.css
@@ -0,0 +1,59 @@
+/* Copyright 2015 The Vanadium Authors. All rights reserved. */
+/* Use of this source code is governed by a BSD-style */
+/* license that can be found in the LICENSE file. */
+
+div.metric-actions-container {
+  z-index: 999;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.2);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+div.metric-actions-container div.metric-actions-content {
+  background-color: white;
+  border: 1px black solid;
+  width: 440px;
+  padding: 12px;
+  padding-top: 16px;
+}
+
+div.metric-actions-container div.title {
+  font-weight: var(--font-weight-medium);
+  padding: 4px 0px 16px 0px;
+}
+
+div.metric-actions-container div.btn-close {
+  padding-top: 10px;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  color: #999;
+}
+
+div.metric-actions-container div.btn-close:hover {
+  color: #444;
+}
+
+div.metric-actions-container div.row {
+  display: flex;
+  flex-direction: row;
+}
+
+div.metric-actions-container div.space {
+  height: 14px;
+  width: 100%;
+}
+
+div.metric-actions-container div.item-label {
+  font-weight: var(--font-weight-medium);
+  width: 140px;
+  padding-right: 10px;
+}
+
+div.metric-actions-container div.item-link {
+  margin-left: 10px;
+}
+
diff --git a/oncall/client/stylesheets/components/metrics-group.css b/oncall/client/stylesheets/components/metrics-group.css
deleted file mode 100644
index 0b12cfd..0000000
--- a/oncall/client/stylesheets/components/metrics-group.css
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Copyright 2015 The Vanadium Authors. All rights reserved. */
-/* Use of this source code is governed by a BSD-style */
-/* license that can be found in the LICENSE file. */
-
-div.metrics-group {
-  margin-top: var(--subsection-margin-top);
-}
-
-div.metrics-group div.link-container {
-  display: flex;
-}
-
-div.metrics-group a {
-  text-decoration: none;
-  height: 100%;
-  color: var(--dark-green-grey);
-  padding: 0px 4px;
-  cursor: pointer;
-  font-size: 12px;
-  width: 30px;
-  text-align: center;
-}
-
-.darkTheme div.metrics-group a {
-  color: var(--light-green-grey);
-}
-
-div.metrics-group a:hover {
-  background-color: #DDD;
-}
-
-.darkTheme div.metrics-group a:hover {
-  background-color: var(--dark-highlight);
-}
-
-div.metrics-group-title-container {
-  inherits: div.group-title;
-  display: flex;
-  justify-content: space-between;
-  width: 100%;
-}
-
-div.metrics-group-title {
-  width: 100%;
-}
-
-div.metrics-group-row {
-  display: flex;
-}
-
-div.metrics-group-row div:last-child {
-  border-right: none !important;
-}
diff --git a/oncall/client/stylesheets/components/status-table.css b/oncall/client/stylesheets/components/status-table.css
new file mode 100644
index 0000000..3926cf8
--- /dev/null
+++ b/oncall/client/stylesheets/components/status-table.css
@@ -0,0 +1,154 @@
+/* Copyright 2016 The Vanadium Authors. All rights reserved. */
+/* Use of this source code is governed by a BSD-style */
+/* license that can be found in the LICENSE file. */
+
+div.status-table {
+  color: var(--dark-green-grey);
+  padding-top: 20px;
+  margin-right: 120px;
+}
+
+.darkTheme div.status-table {
+  color: var(--light-green-grey);
+}
+
+div.status-table .row {
+  display: flex;
+  flex-direction: row;
+  margin-bottom: 30px;
+}
+
+div.status-table .row-header {
+  width: 120px;
+  text-align: right;
+  padding-top: 15px;
+  padding-right: 10px;
+}
+
+div.status-table .col {
+  margin-left: 8px;
+}
+
+div.status-table .col-header {
+  font-size: 12px;
+  height: 21px;
+}
+
+div.status-table .col-metric {
+  position: relative;
+  padding: 6px 4px 6px 4px;
+  display: flex;
+  width: var(--col-metric-width);
+  height: var(--col-metric-height);
+  background: var(--cyan-800);
+  flex-direction: row;
+  cursor: pointer;
+}
+
+div.status-table .col-metric.err {
+  background-color: #ffbb55 !important;
+}
+
+div.status-table .col-metric.unhealthy {
+  background-color: #AA0000 !important;
+}
+
+div.status-table .col-metric.stale {
+  background-color: #ffbb55 !important;
+}
+
+.darkTheme div.status-table .col-metric {
+  background: DarkGreen;
+}
+
+div.status-table .col-metric .cur-value {
+  position: absolute;
+  left: 166px;
+  top: 6px;
+  color: white;
+  font-size: 14px;
+  height: 38px;
+  line-height: 38px;
+}
+
+div.status-table .col-metric div.cur-value.history {
+  color: #B2DADD !important;
+}
+
+.darkTheme div.status-table .col-metric div.cur-value.history {
+  color: #AAA !important;
+}
+
+div.status-table .col-metric.err .cur-value {
+  color: red !important;
+}
+
+div.status-table .col-metric div.sparkline {
+  position: absolute;
+  top: 6px;
+  left: 6px;
+  width: 156px;
+  height: 40px;
+}
+
+div.status-table .col-metric svg {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 156px;
+  height: 40px;
+}
+
+div.status-table .col-metric svg.mouse-line {
+  position: absolute;
+  top: 0px;
+  left: 6px;
+  width: 156px;
+  height: 50px;
+}
+
+div.status-table .col-metric svg.content {
+}
+
+div.status-table .col-metric svg.content polyline {
+  vector-effect: non-scaling-stroke;
+  fill: none;
+  stroke: white;
+  stroke-width: 0.8;
+}
+
+div.status-table .col-metric svg.threshold path {
+  vector-effect: non-scaling-stroke;
+  fill: none;
+  stroke: white;
+  stroke-width: 0.8;
+}
+
+div.status-table .col-metric svg.mouse-line {
+  shape-rendering: crispedges;
+}
+
+div.status-table .col-metric svg.mouse-line polyline {
+  vector-effect: non-scaling-stroke;
+  fill: none;
+  stroke: #339CA5;
+  stroke-width: 1;
+}
+
+.darkTheme div.status-table .col-metric svg.mouse-line polyline {
+  stroke: #338333;
+}
+
+div.status-table .col-metric div.highlight-overlay {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  top: 0px;
+  left: 0px;
+  background-color: white;
+  opacity: 0;
+}
+
+div.status-table .col-metric:hover div.highlight-overlay {
+  opacity: 0.1;
+}
diff --git a/oncall/client/stylesheets/components/summary-table.css b/oncall/client/stylesheets/components/summary-table.css
deleted file mode 100644
index 8b9aa36..0000000
--- a/oncall/client/stylesheets/components/summary-table.css
+++ /dev/null
@@ -1,297 +0,0 @@
-/* Copyright 2015 The Vanadium Authors. All rights reserved. */
-/* Use of this source code is governed by a BSD-style */
-/* license that can be found in the LICENSE file. */
-
-div.summary-table {
-  display: flex;
-  flex-direction: column;
-  font-size: 14px;
-  color: var(--dark-green-grey);
-  margin-top: 12px;
-  margin-bottom: 12px;
-  margin-right: var(--summary-table-metric-names-col-width);
-  width: 100%;
-}
-
-.darkTheme div.summary-table {
-  color: var(--light-green-grey);
-}
-
-div.summary-table .columns-container {
-  display: flex;
-  flex-grow: 1;
-  justify-content: center;
-}
-
-div.summary-table .subtable-title {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  transform: rotate(-90deg);
-  padding: 0px 6px;
-  width: 30px;
-  white-space: nowrap;
-}
-
-div.summary-table .cell {
-  flex-grow: 1;
-  min-height: 24px;
-  max-height: 50px;
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-}
-
-div.summary-table .cell.healthy {
-  background: var(--cyan-800);
-}
-
-.darkTheme div.summary-table .cell.healthy {
-  background: DarkGreen;
-}
-
-div.summary-table .cell.unhealthy {
-  background: #AA0000;
-}
-
-div.summary-table .metric-names-col {
-  display: flex;
-  flex-direction: column;
-  font-weight: var(--font-weight-medium);
-  text-align: right;
-  width: var(--summary-table-metric-names-col-width);
-  font-size: 15px;
-}
-
-div.summary-table .metric-name-cell {
-  padding-right: 10px;
-  position: relative;
-}
-
-div.summary-table .section-label {
-  position: absolute;
-  top: -19px;
-  left: var(--summary-table-metric-names-col-width);
-  width: 100%;
-  font-size: 12px;
-  font-weight: var(--font-weight-regular) !important;
-  color: #999;
-  text-align: left;
-}
-
-.darkTheme div.summary-table .section-label {
-  color: #666;
-}
-
-div.summary-table .section-label.highlight-section {
-  color: var(--dark-green-grey);
-}
-
-.darkTheme div.summary-table .section-label.highlight-section {
-  color: #999;
-}
-
-div.summary-table .metric-name-cell.unhealthy {
-  background-color: white;
-  color: #AA0000;
-}
-
-.darkTheme div.summary-table .metric-name-cell.unhealthy {
-  background-color: var(--dark-background);
-}
-
-div.summary-table .highlight {
-  background-color: var(--light-grey) !important;
-}
-
-.darkTheme div.summary-table .highlight {
-  background-color: var(--dark-highlight) !important;
-}
-
-div.summary-table .minor-divider {
-  min-height: 30px;
-  max-height: 30px;
-  flex-grow: 0;
-}
-
-div.summary-table .major-divider {
-  min-height: 30px;
-  max-height: 30px;
-  flex-grow: 0;
-}
-
-div.summary-table .dummy-divider {
-  min-height: 1px;
-  max-height: 1px;
-  flex-grow: 0;
-}
-
-div.summary-table .zone-names-row {
-  display: flex;
-  min-height: var(--summary-table-zone-row-height);
-  margin-bottom: 20px;
-  justify-content: center;
-}
-
-div.summary-table .zone-names-col {
-}
-
-div.summary-table .view-type {
-  display: flex;
-  justify-content: flex-end;
-  text-transform: none !important;
-  color: #AAA;
-  width: var(--summary-table-metric-names-col-width) !important;
-  padding-right: 10px;
-  font-size: 12px !important;
-  flex-grow: 0 !important;
-}
-
-div.summary-table .view-type div {
-  margin-left: 8px;
-  cursor: pointer;
-}
-
-div.summary-table .view-type div.selected {
-  color: var(--cyan-800) !important;
-  cursor: default !important;
-}
-
-.darkTheme div.summary-table .view-type div.selected {
-  color: var(--light-green) !important;
-}
-
-div.summary-table .view-type div:hover {
-  color: var(--dark-green-grey);
-}
-
-div.summary-table .view-type-dummy {
-  height: var(--summary-table-zone-row-height);
-  width: var(--summary-table-metric-names-col-width) !important;
-}
-
-div.summary-table .zone-name-cell {
-  height: var(--summary-table-zone-row-height);
-  line-height: var(--summary-table-zone-row-height);
-  text-transform: uppercase;
-  font-weight: var(--font-weight-medium);
-  min-width: var(--zone-column-min-width);
-  max-width: var(--zone-column-max-width);
-  text-align: center;
-  flex-grow: 1;
-  font-size: 16px;
-}
-
-div.summary-table .zone-name-cell.unhealthy {
-  background-color: white;
-  color: #AA0000;
-}
-
-.darkTheme div.summary-table .zone-name-cell.unhealthy {
-  background-color: var(--dark-background);
-}
-
-div.summary-table .zone-col {
-  display: flex;
-  flex-direction: column;
-  min-width: var(--zone-column-min-width);
-  max-width: var(--zone-column-max-width);
-  flex-grow: 1;
-}
-
-div.summary-table div.zone-col-cell {
-  position: relative;
-  cursor: pointer;
-}
-
-div.summary-table div.zone-col-cell div.highlight-overlay {
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  top: 0px;
-  left: 0px;
-  background-color: white;
-  opacity: 0;
-}
-
-div.summary-table div.zone-col-cell:hover div.highlight-overlay {
-  opacity: 0.1;
-}
-
-div.summary-table div.zone-col-cell div.value {
-  position: absolute;
-  height: 100%;
-  top: 0px;
-  right: 6px;
-  font-size: 18px;
-  color: white;
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  width: 48px;
-}
-
-div.summary-table div.zone-col-cell div.value.history {
-  color: #B2DADD !important;
-}
-
-.darkTheme div.summary-table div.zone-col-cell div.value.history {
-  color: #AAA !important;
-}
-
-div.summary-table div.zone-col-cell.unhealthy div.value.history {
-  color: #EECCCC !important;
-}
-
-div.summary-table div.zone-col-cell div.sparkline {
-  position: absolute;
-  top: 0px;
-  right: 60px;
-  left: 10px;
-  bottom: 0px;
-  display: flex;
-}
-
-div.summary-table div.zone-col-cell div.mouse-line {
-  position: absolute;
-  top: 0px;
-  right: 60px;
-  left: 10px;
-  bottom: 0px;
-  display: flex;
-}
-
-div.summary-table div.zone-col-cell svg.mouse-line {
-  shape-rendering: crispedges;
-}
-
-div.summary-table div.zone-col-cell svg.mouse-line polyline {
-  vector-effect: non-scaling-stroke;
-  fill: none;
-  stroke: #339CA5;
-  stroke-width: 1;
-}
-
-.darkTheme div.summary-table div.zone-col-cell svg.mouse-line polyline {
-  stroke: #338333;
-}
-
-div.summary-table div.zone-col-cell.unhealthy svg.mouse-line polyline {
-  stroke: #C44D4D !important;
-}
-
-div.summary-table div.zone-col-cell svg {
-  width: 100%;
-}
-
-div.summary-table div.zone-col-cell svg.content {
-  margin-top: 5px;
-  margin-bottom: 5px;
-}
-
-div.summary-table div.zone-col-cell svg.content polyline {
-  vector-effect: non-scaling-stroke;
-  fill: none;
-  stroke: white;
-  stroke-width: 0.8;
-}
diff --git a/oncall/client/stylesheets/index.css b/oncall/client/stylesheets/index.css
index cbef6db..10beca3 100644
--- a/oncall/client/stylesheets/index.css
+++ b/oncall/client/stylesheets/index.css
@@ -6,12 +6,9 @@
 @import "./reset.css";
 @import "./typography.css";
 @import "./common.css";
-@import "./components/data-table.css";
 @import "./components/header.css";
-@import "./components/full-graph.css";
-@import "./components/instance-view.css";
 @import "./components/main.css";
-@import "./components/metrics-group.css";
+@import "./components/metric-actions-panel.css";
 @import "./components/metric-item.css";
+@import "./components/status-table.css";
 @import "./components/settings.css";
-@import "./components/summary-table.css";
diff --git a/oncall/client/stylesheets/variables.css b/oncall/client/stylesheets/variables.css
index e0ea8f8..1edc3cd 100644
--- a/oncall/client/stylesheets/variables.css
+++ b/oncall/client/stylesheets/variables.css
@@ -49,7 +49,6 @@
     rgba(255, 0, 0, 0.3) 20px
   );
   --white-transparent: rgba(255, 255, 255, 0.4);
-  --blueish-white: #EBECEC;
   --dark-background: #222;
   --dark-highlight: #333;
 
@@ -64,24 +63,18 @@
   --section-shadow: rgba(0, 0, 0, 0.30) 0px 0px 4px 0px;
 
   /* Heights */
+  --col-metric-height: 50px;
   --header-height: 60px;
   --zone-title-height: 40px;
   --full-graph-height: 240px;
   --graph-time-div-height: 20px;
-  --metric-item-height: 58px;
+  --metric-item-height: 50px;
   --metric-item-title-height: 16px;
   --metric-item-value-height: 16px;
-  --gce-instance-name-height: 30px;
   --sparkline-height: 24px;
-  --group-title-height: 20px;
-  --subsection-margin-top: 36px;
-  --summary-table-zone-row-height: 30px;
 
   /* Width */
+  --col-metric-width: 206px;
   --graph-number-div-width: 30px;
-  --metric-item-width: 20%;
-  --zone-header-width: 160px;
-  --summary-table-metric-names-col-width: 150px;
-  --zone-column-min-width: 130px;
-  --zone-column-max-width: 240px;
+  --metric-item-width: 200px;
 }