| Index: appengine/swarming/elements/res/imp/taskpage/task-page.html
|
| diff --git a/appengine/swarming/elements/res/imp/taskpage/task-page.html b/appengine/swarming/elements/res/imp/taskpage/task-page.html
|
| index bee2aa92c6088bef0aef8f0324822554e9f7eeb7..d5cee63f12742ff6a2f36f64712003cd86dc91be 100644
|
| --- a/appengine/swarming/elements/res/imp/taskpage/task-page.html
|
| +++ b/appengine/swarming/elements/res/imp/taskpage/task-page.html
|
| @@ -24,23 +24,80 @@
|
| None.
|
| -->
|
|
|
| +<link rel="import" href="/res/imp/bower_components/iron-icon/iron-icon.html">
|
| +<link rel="import" href="/res/imp/bower_components/iron-icons/iron-icons.html">
|
| +<link rel="import" href="/res/imp/bower_components/paper-button/paper-button.html">
|
| +<link rel="import" href="/res/imp/bower_components/paper-dialog/paper-dialog.html">
|
| +<link rel="import" href="/res/imp/bower_components/paper-input/paper-input.html">
|
| +<link rel="import" href="/res/imp/bower_components/paper-tabs/paper-tabs.html">
|
| <link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
|
|
|
| <link rel="import" href="/res/imp/common/common-behavior.html">
|
| +<link rel="import" href="/res/imp/common/single-page-style.html">
|
| <link rel="import" href="/res/imp/common/swarming-app.html">
|
| +<link rel="import" href="/res/imp/common/task-behavior.html">
|
| <link rel="import" href="/res/imp/common/url-param.html">
|
|
|
| <link rel="import" href="task-page-data.html">
|
|
|
| <dom-module id="task-page">
|
| <template>
|
| - <style include="iron-flex iron-flex-alignment iron-positioning swarming-app-style">
|
| + <style include="iron-flex iron-flex-alignment swarming-app-style single-page-style task-style">
|
| + .milo {
|
| + width: calc(100% - 11px);
|
| + /** We don't control the milo site and it's on a different domain than
|
| + us, so there's no good way to avoid scrolling other than tell the iframe
|
| + it is really tall.*/
|
| + height: 2000px;
|
| + }
|
|
|
| + .left {
|
| + min-width: 550px;
|
| + }
|
| + .right {
|
| + min-width: 500px;
|
| + margin-top: 8px;
|
| + }
|
| +
|
| + .expand {
|
| + min-width: 3em;
|
| + vertical-align: middle;
|
| + padding: .5em;
|
| + }
|
| +
|
| + .code {
|
| + font-family: monospace;
|
| + }
|
| +
|
| + .stdout {
|
| + white-space: pre-line;
|
| + padding: 2px;
|
| + }
|
| +
|
| + .refresh_input {
|
| + padding: 0 5px;
|
| + }
|
| +
|
| + .tabbed {
|
| + border: 3px solid #1F78B4;
|
| + margin-left: 5px;
|
| + min-height: 80vh;
|
| + }
|
| </style>
|
|
|
| <url-param name="id"
|
| value="{{task_id}}">
|
| </url-param>
|
| + <url-param name="request_detail"
|
| + value="{{_request_detail}}">
|
| + </url-param>
|
| + <url-param name="show_raw"
|
| + value="{{_show_raw}}">
|
| + </url-param>
|
| + <url-param name="refresh"
|
| + value="{{_refresh}}"
|
| + default_value="10">
|
| + </url-param>
|
|
|
| <swarming-app
|
| client_id="[[client_id]]"
|
| @@ -64,7 +121,312 @@
|
| stdout="{{_stdout}}">
|
| </task-page-data>
|
|
|
| - <h1>Task Page Stub</h1>
|
| + <div class="horizontal layout wrap">
|
| + <div class="left flex">
|
| + <div class="horizontal layout">
|
| + <paper-input class="id_input" label="Task id" value="{{task_id}}"></paper-input>
|
| + <button on-click="_refresh">
|
| + <iron-icon class="refresh" icon="icons:refresh"></iron-icon>
|
| + </button>
|
| + <button on-click="_retry"><span>Retry</span></button>
|
| + <button on-click="_cancel">Cancel</button>
|
| + </div>
|
| + <table>
|
| + <tr>
|
| + <td>Name</td>
|
| + <td>[[_request.name]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>State</td>
|
| + <td class$="[[_stateClass(_result)]]">[[_state(_result)]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Created</td>
|
| + <td>[[_request.human_created_ts]]</td>
|
| + </tr>
|
| + <template is="dom-if" if="[[_wasPickedUp(_result)]]">
|
| + <tr>
|
| + <td>Started</td>
|
| + <td>[[_result.human_started_ts]]</td>
|
| + </tr>
|
| + </template>
|
| + <template is="dom-if" if="[[_wasNotPickedUp(_result)]]">
|
| + <tr>
|
| + <td>Expires</td>
|
| + <td>[[_expires(_request)]]</td>
|
| + </tr>
|
| + </template>
|
| + <template is="dom-if" if="[[_result.human_completed_ts]]">
|
| + <tr>
|
| + <td>Completed</td>
|
| + <td>[[_result.human_completed_ts]]</td>
|
| + </tr>
|
| + </template>
|
| + <template is="dom-if" if="[[_result.human_abandoned_ts]]">
|
| + <tr>
|
| + <td>Abandoned</td>
|
| + <td>[[_result.human_abandoned_ts]]</td>
|
| + </tr>
|
| + </template>
|
| + <tr>
|
| + <td>Last Updated</td>
|
| + <td>[[_result.human_modified_ts]]</td>
|
| + </tr>
|
| + <template is="dom-if" if="[[_result.deduped_from]]">
|
| + <tr>
|
| + <td><b>Deduped from</b></td>
|
| + <td>
|
| + <a href$="[[_taskLink(_result.deduped_from)]]">
|
| + [[_result.deduped_from]]
|
| + </a>
|
| + </td>
|
| + </tr>
|
| + </template>
|
| + <tr>
|
| + <td>Pending Time</td>
|
| + <td>[[_pending(_result)]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Duration</td>
|
| + <td>[[_result.human_duration]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Priority</td>
|
| + <td>[[_request.priority]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>User</td>
|
| + <td>[[_request.user]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Authenticated</td>
|
| + <td>[[_request.authenticated]]</td>
|
| + </tr>
|
| + <template is="dom-if" if="[[_request.service_account]]">
|
| + <tr>
|
| + <td>Service Account</td>
|
| + <td>[[_request.service_account]]</td>
|
| + </tr>
|
| + </template>
|
| + <template is="dom-if" if="[[_request.properties.secret_bytes]]">
|
| + <tr>
|
| + <td>Secret Bytes</td>
|
| + <td>[[_request.properties.secret_bytes]]</td>
|
| + </tr>
|
| + </template>
|
| + <template is="dom-if" if="[[_request.parent_task_id]]">
|
| + <tr>
|
| + <td>Parent Task</td>
|
| + <td>
|
| + <a href$="[[_taskLink(_request.parent_task_id)]]">[[_request.parent_task_id]]</a>
|
| + </td>
|
| + </tr>
|
| + </template>
|
| + <tr>
|
| + <td rowspan$="[[_rowspan(_request.properties.dimensions)]]">Requested Dimensions</td>
|
| + </tr>
|
| + <template is="dom-repeat" items="{{_request.properties.dimensions}}" as="dimension">
|
| + <tr>
|
| + <td><b>[[dimension.key]]:</b> [[dimension.value]]</td>
|
| + </tr>
|
| + </template>
|
| + <tr>
|
| + <td>Isolated Inputs</td>
|
| + <td>
|
| + <a href$="[[_isolateLink(_request.properties.inputs_ref)]]">
|
| + [[_request.properties.inputs_ref.isolated]]
|
| + </a>
|
| + </td>
|
| + </tr>
|
| + <template is="dom-if" if="[[_not(_request_detail)]]">
|
| + <tr>
|
| + <td>More Details</td>
|
| + <td>
|
| + <button on-click="_toggleDetails">
|
| + <iron-icon icon="icons:add-circle-outline"></iron-icon>
|
| + </button>
|
| + </td>
|
| + </tr>
|
| + </template>
|
| + <template is="dom-if" if="[[_request_detail]]">
|
| + <tr>
|
| + <td>Hide Details</td>
|
| + <td>
|
| + <button on-click="_toggleDetails">
|
| + <iron-icon icon="icons:remove-circle-outline"></iron-icon>
|
| + </button>
|
| + </td>
|
| + </tr>
|
| + </template>
|
| + <template is="dom-if" if="[[_request_detail]]">
|
| + <tr>
|
| + <td>Extra Args</td>
|
| + <td class="code">[[_extraArgs(_request)]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td rowspan$="[[_rowspan(_request.tags)]]">Tags</td>
|
| + </tr>
|
| + <template is="dom-repeat" items="{{_request.tags}}" as="tag">
|
| + <tr>
|
| + <td>[[tag]]</td>
|
| + </tr>
|
| + </template>
|
| +
|
| + <tr>
|
| + <td>Execution timeout</td>
|
| + <td>[[_humanDuration(_request.properties.execution_timeout_secs)]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>I/O timeout</td>
|
| + <td>[[_humanDuration(_request.properties.io_timeout_secs)]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Grace period</td>
|
| + <td>[[_humanDuration(_request.properties.grace_period_secs)]]</td>
|
| + </tr>
|
| +
|
| + <tr>
|
| + <td>CIPD server</td>
|
| + <td>
|
| + <a href$="[[_request.properties.cipd_input.server]]">
|
| + [[_request.properties.cipd_input.server]]
|
| + </a>
|
| + </td>
|
| + </tr>
|
| + <tr>
|
| + <td>CIPD version</td>
|
| + <td>[[_request.properties.cipd_input.client_package.version]]</td>
|
| + </tr>
|
| + <template is="dom-if" if="[[_wasPickedUp(_result)]]">
|
| + <tr>
|
| + <td>CIPD package name</td>
|
| + <td>[[_result.cipd_pins.client_package.package_name]]</td>
|
| + </tr>
|
| + </template>
|
| +
|
| + <tr hidden$="[[_not(_request.properties.cipd_input)]]">
|
| + <td rowspan$="[[_cipdRowspan(_request,_result)]]">CIPD packages</td>
|
| + </tr>
|
| + <template is="dom-repeat" items="[[_cipdPackages(_request,_result)]]" as="cipd">
|
| + <tr>
|
| + <td>[[cipd.path]]/</td>
|
| + </tr>
|
| + <tr>
|
| + <td><b>Requested:</b>[[cipd.requested]]</td>
|
| + </tr>
|
| + <tr hidden$="[[_wasNotPickedUp(_result)]]">
|
| + <td><b>Actual:</b>[[cipd.actual]]</td>
|
| + </tr>
|
| + </template>
|
| + </template>
|
| + </table>
|
| +
|
| + <div class="title">Task Execution</div>
|
| + <template is="dom-if" if="[[_wasPickedUp(_result)]]">
|
| + <table>
|
| + <tr>
|
| + <td>Bot assigned to task</td>
|
| + <td><a href$="[[_botLink(_result.bot_id)]]">[[_result.bot_id]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td rowspan$="[[_rowspan(_result.bot_dimensions)]]">Bot Dimensions</td>
|
| + </tr>
|
| + <template is="dom-repeat" items="[[_result.bot_dimensions]]" as="dimension">
|
| + <tr>
|
| + <td><b>[[dimension.key]]:</b> [[_join(dimension.value," | ")]]</td>
|
| + </tr>
|
| + </template>
|
| +
|
| + <tr>
|
| + <td>Exit code</td>
|
| + <td>[[_result.exit_code]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Try number</td>
|
| + <td>[[_result.try_number]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Failure</td>
|
| + <td class$="[[_failureClass(_result.failure)]]">[[_result.failure]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Internal Failure</td>
|
| + <td class$="[[_internalClass(_result.internal_failure)]]">[[_result.internal_failure]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Isolated Outputs</td>
|
| + <td>
|
| + <a href$="[[_isolateLink(_result.outputs_ref)]]">
|
| + [[_result.outputs_ref.isolated]]
|
| + </a>
|
| + </td>
|
| + </tr>
|
| + <tr>
|
| + <td>Bot version</td>
|
| + <td>[[_result.bot_version]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Server version</td>
|
| + <td>[[_result.server_versions]]</td>
|
| + </tr>
|
| + </table>
|
| + </template>
|
| + <template is="dom-if" if="[[_wasNotPickedUp(_result)]]">
|
| + This space left blank until a bot is assigned to the task.
|
| + </template>
|
| +
|
| + <template is="dom-if" if="[[_result.performance_stats]]">
|
| + <div class="title">Performance Stats</div>
|
| + <table>
|
| + <tr>
|
| + <td title="This includes time taken to download inputs, isolate outputs, and setup CIPD">Total Overhead</td>
|
| + <td>[[_humanDuration(_result.performance_stats.bot_overhead)]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Downloading Inputs From Isolate</td>
|
| + <td>[[_humanDuration(_result.performance_stats.isolated_download.duration)]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Uploading Outputs To Isolate</td>
|
| + <td>[[_humanDuration(_result.performance_stats.isolated_upload.duration)]]</td>
|
| + </tr>
|
| + <tr>
|
| + <td>Initial bot cache</td>
|
| + <td>[[_result.performance_stats.isolated_download.initial_number_items]] items;
|
| + [[_bytes(_result.performance_stats.isolated_download.initial_size)]]</td>
|
| + </tr>
|
| + </table>
|
| + </template>
|
| + </div>
|
| +
|
| + <div class="flex right">
|
| + <div class="horizontal layout">
|
| + <div class="tabs">
|
| + <paper-tabs selected="{{_show_raw}}" no-bar>
|
| + <paper-tab disabled$="[[_noMilo(_request)]]">Milo Output</paper-tab>
|
| + <paper-tab>Raw Output</paper-tab>
|
| + </paper-tabs>
|
| + </div>
|
| +
|
| + <paper-input
|
| + class="refresh_input"
|
| + label="Refresh Interval (seconds)"
|
| + value="{{_refresh}}"
|
| + auto-validate
|
| + min="1"
|
| + max="1000"
|
| + pattern="[0-9]+">
|
| + </paper-input>
|
| + </div>
|
| +
|
| + <template is="dom-if" if="[[_supportsMilo(_request,_show_raw)]]">
|
| + <iframe id="miloFrame" class="milo tabbed" src$="[[_getMiloLink(milo_prefix,task_id)]]"></iframe>
|
| + </template>
|
| + <template is="dom-if" if="[[_show_raw]]">
|
| + <div class="code stdout tabbed">[[_stdout]]</div>
|
| + </template>
|
| + </div>
|
| + </div>
|
| </div>
|
|
|
| </swarming-app>
|
| @@ -77,6 +439,7 @@
|
|
|
| behaviors: [
|
| SwarmingBehaviors.CommonBehavior,
|
| + SwarmingBehaviors.TaskBehavior,
|
| ],
|
|
|
| properties: {
|
| @@ -86,6 +449,176 @@
|
| client_id: {
|
| type: String,
|
| },
|
| + milo_prefix: {
|
| + type: String,
|
| + },
|
| +
|
| + _request: {
|
| + type: Object,
|
| + observer: "_requestUpdated"
|
| + },
|
| + _request_detail: {
|
| + type: Boolean,
|
| + }
|
| + },
|
| +
|
| + _bytes: function(sizeInBytes) {
|
| + return sk.human.bytes(sizeInBytes);
|
| + },
|
| +
|
| + _cipdRowspan: function(request, result) {
|
| + if (!request || !request.properties || !request.properties.cipd_input) {
|
| + return 0;
|
| + }
|
| + // We always need to at least double the number of packages because we
|
| + // show the path and then the requested. If the actual package info
|
| + // is available, we triple the number of packages to account for that.
|
| + var rowSpan = (request.properties.cipd_input.packages || []).length;
|
| + if (result && result.cipd_pins && result.cipd_pins.packages) {
|
| + rowSpan *= 3;
|
| + } else {
|
| + rowSpan *= 2;
|
| + }
|
| + // Add one because rowSpan counts from 1.
|
| + return rowSpan + 1;
|
| + },
|
| +
|
| + _cipdPackages: function(request, result) {
|
| + if (!request || !request.properties || !request.properties.cipd_input) {
|
| + return [];
|
| + }
|
| + var packages = request.properties.cipd_input.packages || [];
|
| + var actual = (result && result.cipd_pins && result.cipd_pins.packages) || [];
|
| + packages.forEach(function(p) {
|
| + p.requested = p.package_name + ":" + p.version;
|
| + actual.forEach(function(c) {
|
| + if (c.path === p.path) {
|
| + p.actual = c.package_name + ":" + c.version;
|
| + }
|
| + });
|
| + });
|
| + return packages;
|
| + },
|
| +
|
| + _expires: function(request) {
|
| + var delta = parseInt(request.expiration_secs);
|
| + if (delta) {
|
| + return sk.human.localeTime(new Date(request.created_ts.getTime() + delta * 1000));
|
| + }
|
| + // Fall back to something
|
| + return request.expiration_secs + " seconds from created time";
|
| + },
|
| +
|
| + _extraArgs: function(request) {
|
| + if (!request || !request.properties) {
|
| + return "";
|
| + }
|
| + var args = request.properties.extra_args || [];
|
| + return args.join(" ");
|
| + },
|
| +
|
| + _failureClass: function(failure) {
|
| + if (failure) {
|
| + return "failed_task";
|
| + }
|
| + return "";
|
| + },
|
| +
|
| + _getMiloLink: function(prefix,id) {
|
| + if (!prefix) {
|
| + return undefined;
|
| + }
|
| + return prefix + id;
|
| + },
|
| +
|
| + _internalClass: function(failure) {
|
| + if (failure) {
|
| + return "exception";
|
| + }
|
| + return "";
|
| + },
|
| +
|
| + _isolateLink: function(ref) {
|
| + if (!ref || !ref.isolatedserver) {
|
| + return undefined;
|
| + }
|
| + return ref.isolatedserver + "/browse?namespace="+ref.namespace +
|
| + "&hash=" + ref.isolated;
|
| + },
|
| +
|
| + _join: function(arr, s) {
|
| + arr = arr || [];
|
| + return arr.join(s);
|
| + },
|
| +
|
| + _noMilo: function(result) {
|
| + return !this._tag(result, "allow_milo");
|
| + },
|
| +
|
| + _pending: function(result) {
|
| + if (!result.created_ts) {
|
| + return "";
|
| + }
|
| + var end = result.started_ts || result.abandoned_ts || new Date();
|
| + // In the case of deduplicated tasks, started_ts comes before the task.
|
| + if (end <= result.created_ts) {
|
| + return "0s";
|
| + }
|
| + return this._timeDiffExact(result.created_ts, end);
|
| + },
|
| +
|
| + _requestUpdated: function(request) {
|
| + if (this._noMilo(request)) {
|
| + this.set("_show_raw", 1);
|
| + }
|
| + },
|
| +
|
| + _rowspan: function(dims) {
|
| + dims = dims || [];
|
| + return dims.length + 1;
|
| + },
|
| +
|
| + _supportsMilo: function(request, showRaw) {
|
| + return !showRaw && request && this._tag(request, "allow_milo");
|
| + },
|
| +
|
| + _state: function(result) {
|
| + if (!result) {
|
| + return "";
|
| + }
|
| + if (result.state === this.COMPLETED) {
|
| + if (result.failure) {
|
| + return this.COMPLETED_FAILURE;
|
| + }
|
| + if (result.try_number === "0") {
|
| + return this.COMPLETED_DEDUPED;
|
| + }
|
| + return this.COMPLETED_SUCCESS;
|
| + }
|
| + return result.state;
|
| + },
|
| +
|
| + _stateClass: function(result) {
|
| + return this.stateClass(this._state(result));
|
| + },
|
| +
|
| + _toggleDetails: function() {
|
| + this.set("_request_detail", !this._request_detail);
|
| + },
|
| +
|
| + _tag: function(result, col) {
|
| + if (!result || !result.tagMap) {
|
| + return undefined;
|
| + }
|
| + return result.tagMap[col];
|
| + },
|
| +
|
| + _wasPickedUp: function(result) {
|
| + return result && result.state !== this.PENDING && result.state !== this.CANCELED && result.state != this.EXPIRED;
|
| + },
|
| +
|
| + _wasNotPickedUp: function(result) {
|
| + return result && !this._wasPickedUp(result);
|
| },
|
| });
|
| })();
|
|
|