mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 10:12:41 +01:00
When viewing a live build log, trap users in a small personal hell where nothing but slavish devotion to the log exists
Summary: Depends on D19152. Ref T13088. This adds live log tailing. It is probably not the final version of this feature because it prevents escape once you begin tailing a log. Test Plan: Used `bin/harbormaster write-log --rate ...` to write a log slowly. Viewed it in the web UI. Clicked "Follow Log". Followed the log until the write finished, a lifetime later. Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13088 Differential Revision: https://secure.phabricator.com/D19153
This commit is contained in:
parent
21ddfe442e
commit
f114b2dd7d
4 changed files with 125 additions and 20 deletions
|
@ -78,7 +78,7 @@ return array(
|
||||||
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
|
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
|
||||||
'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948',
|
'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948',
|
||||||
'rsrc/css/application/flag/flag.css' => 'bba8f811',
|
'rsrc/css/application/flag/flag.css' => 'bba8f811',
|
||||||
'rsrc/css/application/harbormaster/harbormaster.css' => 'c7e29d9e',
|
'rsrc/css/application/harbormaster/harbormaster.css' => '5dd4c2de',
|
||||||
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
|
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
|
||||||
'rsrc/css/application/herald/herald.css' => 'cd8d0134',
|
'rsrc/css/application/herald/herald.css' => 'cd8d0134',
|
||||||
'rsrc/css/application/maniphest/report.css' => '9b9580b7',
|
'rsrc/css/application/maniphest/report.css' => '9b9580b7',
|
||||||
|
@ -416,7 +416,7 @@ return array(
|
||||||
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
|
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
|
||||||
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
|
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
|
||||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
|
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
|
||||||
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'be6974cc',
|
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '796a8803',
|
||||||
'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e',
|
'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e',
|
||||||
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
|
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
|
||||||
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
|
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
|
||||||
|
@ -579,7 +579,7 @@ return array(
|
||||||
'font-fontawesome' => 'e838e088',
|
'font-fontawesome' => 'e838e088',
|
||||||
'font-lato' => 'c7ccd872',
|
'font-lato' => 'c7ccd872',
|
||||||
'global-drag-and-drop-css' => 'b556a948',
|
'global-drag-and-drop-css' => 'b556a948',
|
||||||
'harbormaster-css' => 'c7e29d9e',
|
'harbormaster-css' => '5dd4c2de',
|
||||||
'herald-css' => 'cd8d0134',
|
'herald-css' => 'cd8d0134',
|
||||||
'herald-rule-editor' => 'dca75c0e',
|
'herald-rule-editor' => 'dca75c0e',
|
||||||
'herald-test-css' => 'a52e323e',
|
'herald-test-css' => 'a52e323e',
|
||||||
|
@ -636,7 +636,7 @@ return array(
|
||||||
'javelin-behavior-event-all-day' => 'b41537c9',
|
'javelin-behavior-event-all-day' => 'b41537c9',
|
||||||
'javelin-behavior-fancy-datepicker' => 'ecf4e799',
|
'javelin-behavior-fancy-datepicker' => 'ecf4e799',
|
||||||
'javelin-behavior-global-drag-and-drop' => '960f6a39',
|
'javelin-behavior-global-drag-and-drop' => '960f6a39',
|
||||||
'javelin-behavior-harbormaster-log' => 'be6974cc',
|
'javelin-behavior-harbormaster-log' => '796a8803',
|
||||||
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
|
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
|
||||||
'javelin-behavior-high-security-warning' => 'a464fe03',
|
'javelin-behavior-high-security-warning' => 'a464fe03',
|
||||||
'javelin-behavior-history-install' => '7ee2b591',
|
'javelin-behavior-history-install' => '7ee2b591',
|
||||||
|
@ -1535,6 +1535,9 @@ return array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-quicksand',
|
'javelin-quicksand',
|
||||||
),
|
),
|
||||||
|
'796a8803' => array(
|
||||||
|
'javelin-behavior',
|
||||||
|
),
|
||||||
'7a68dda3' => array(
|
'7a68dda3' => array(
|
||||||
'owners-path-editor',
|
'owners-path-editor',
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
|
@ -1889,9 +1892,6 @@ return array(
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
'javelin-request',
|
'javelin-request',
|
||||||
),
|
),
|
||||||
'be6974cc' => array(
|
|
||||||
'javelin-behavior',
|
|
||||||
),
|
|
||||||
'bea6e7f4' => array(
|
'bea6e7f4' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
|
|
@ -22,14 +22,14 @@ final class HarbormasterBuildLogRenderController
|
||||||
if ($head_lines === null) {
|
if ($head_lines === null) {
|
||||||
$head_lines = 8;
|
$head_lines = 8;
|
||||||
}
|
}
|
||||||
$head_lines = min($head_lines, 100);
|
$head_lines = min($head_lines, 1024);
|
||||||
$head_lines = max($head_lines, 0);
|
$head_lines = max($head_lines, 0);
|
||||||
|
|
||||||
$tail_lines = $request->getInt('tail');
|
$tail_lines = $request->getInt('tail');
|
||||||
if ($tail_lines === null) {
|
if ($tail_lines === null) {
|
||||||
$tail_lines = 16;
|
$tail_lines = 16;
|
||||||
}
|
}
|
||||||
$tail_lines = min($tail_lines, 100);
|
$tail_lines = min($tail_lines, 1024);
|
||||||
$tail_lines = max($tail_lines, 0);
|
$tail_lines = max($tail_lines, 0);
|
||||||
|
|
||||||
$head_offset = $request->getInt('headOffset');
|
$head_offset = $request->getInt('headOffset');
|
||||||
|
@ -301,7 +301,6 @@ final class HarbormasterBuildLogRenderController
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
foreach ($views as $view) {
|
foreach ($views as $view) {
|
||||||
if ($spacer) {
|
if ($spacer) {
|
||||||
$spacer['tail'] = $view['viewOffset'];
|
$spacer['tail'] = $view['viewOffset'];
|
||||||
|
@ -360,6 +359,22 @@ final class HarbormasterBuildLogRenderController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($log->getLive()) {
|
||||||
|
$last_view = last($views);
|
||||||
|
$last_line = last($last_view['viewData']);
|
||||||
|
if ($last_line) {
|
||||||
|
$last_offset = $last_line['offset'];
|
||||||
|
} else {
|
||||||
|
$last_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$last_tail = $last_view['viewOffset'] + $last_view['viewLength'];
|
||||||
|
$show_live = ($last_tail === $log_size);
|
||||||
|
if ($show_live) {
|
||||||
|
$rows[] = $this->renderLiveRow($last_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$table = phutil_tag(
|
$table = phutil_tag(
|
||||||
'table',
|
'table',
|
||||||
array(
|
array(
|
||||||
|
@ -650,25 +665,66 @@ final class HarbormasterBuildLogRenderController
|
||||||
),
|
),
|
||||||
$expand_down),
|
$expand_down),
|
||||||
);
|
);
|
||||||
$expand_row = phutil_tag('tr', array(), $expand_cells);
|
|
||||||
$expand_table = phutil_tag(
|
return $this->renderActionTable($expand_cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderLiveRow($log_size) {
|
||||||
|
$icon_down = id(new PHUIIconView())
|
||||||
|
->setIcon('fa-chevron-down');
|
||||||
|
|
||||||
|
$follow = javelin_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'sigil' => 'harbormaster-log-expand harbormaster-log-live',
|
||||||
|
'meta' => array(
|
||||||
|
'headOffset' => $log_size,
|
||||||
|
'head' => 0,
|
||||||
|
'tail' => 1024,
|
||||||
|
'live' => true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
$icon_down,
|
||||||
|
' ',
|
||||||
|
pht('Follow Log'),
|
||||||
|
' ',
|
||||||
|
$icon_down,
|
||||||
|
));
|
||||||
|
|
||||||
|
$expand_cells = array(
|
||||||
|
phutil_tag(
|
||||||
|
'td',
|
||||||
|
array(
|
||||||
|
'class' => 'harbormaster-log-follow',
|
||||||
|
),
|
||||||
|
$follow),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->renderActionTable($expand_cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderActionTable(array $action_cells) {
|
||||||
|
$action_row = phutil_tag('tr', array(), $action_cells);
|
||||||
|
|
||||||
|
$action_table = phutil_tag(
|
||||||
'table',
|
'table',
|
||||||
array(
|
array(
|
||||||
'class' => 'harbormaster-log-expand-table',
|
'class' => 'harbormaster-log-expand-table',
|
||||||
),
|
),
|
||||||
$expand_row);
|
$action_row);
|
||||||
|
|
||||||
$cells = array(
|
$format_cells = array(
|
||||||
phutil_tag('th', array()),
|
phutil_tag('th', array()),
|
||||||
phutil_tag(
|
phutil_tag(
|
||||||
'td',
|
'td',
|
||||||
array(
|
array(
|
||||||
'class' => 'harbormaster-log-expand-cell',
|
'class' => 'harbormaster-log-expand-cell',
|
||||||
),
|
),
|
||||||
$expand_table),
|
$action_table),
|
||||||
);
|
);
|
||||||
|
|
||||||
return phutil_tag('tr', array(), $cells);
|
return phutil_tag('tr', array(), $format_cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,6 @@
|
||||||
'Helvetica Neue', Helvetica, Arial, sans-serif;
|
'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.harbormaster-log-expand-up {
|
.harbormaster-log-expand-up {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
@ -107,6 +106,14 @@
|
||||||
margin: 0 0 0px 4px;
|
margin: 0 0 0px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.harbormaster-log-follow {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.harbormaster-log-follow .phui-icon-view {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.harbormaster-log-expand-mid {
|
.harbormaster-log-expand-mid {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
JX.behavior('harbormaster-log', function(config) {
|
JX.behavior('harbormaster-log', function(config) {
|
||||||
var contentNode = JX.$(config.contentNodeID);
|
var contentNode = JX.$(config.contentNodeID);
|
||||||
|
var following = false;
|
||||||
|
|
||||||
JX.DOM.listen(contentNode, 'click', 'harbormaster-log-expand', function(e) {
|
JX.DOM.listen(contentNode, 'click', 'harbormaster-log-expand', function(e) {
|
||||||
if (!e.isNormalClick()) {
|
if (!e.isNormalClick()) {
|
||||||
|
@ -13,23 +14,64 @@ JX.behavior('harbormaster-log', function(config) {
|
||||||
|
|
||||||
e.kill();
|
e.kill();
|
||||||
|
|
||||||
var row = e.getNode('tag:tr');
|
expand(e.getTarget());
|
||||||
|
});
|
||||||
|
|
||||||
|
function expand(node) {
|
||||||
|
var row = JX.DOM.findAbove(node, 'tr');
|
||||||
row = JX.DOM.findAbove(row, 'tr');
|
row = JX.DOM.findAbove(row, 'tr');
|
||||||
|
|
||||||
var data = e.getNodeData('harbormaster-log-expand');
|
var data = JX.Stratcom.getData(node);
|
||||||
|
|
||||||
var uri = new JX.URI(config.renderURI)
|
var uri = new JX.URI(config.renderURI)
|
||||||
.addQueryParams(data);
|
.addQueryParams(data);
|
||||||
|
|
||||||
|
if (data.live) {
|
||||||
|
following = true;
|
||||||
|
}
|
||||||
|
|
||||||
var request = new JX.Request(uri, function(r) {
|
var request = new JX.Request(uri, function(r) {
|
||||||
var result = JX.$H(r.markup).getNode();
|
var result = JX.$H(r.markup).getNode();
|
||||||
var rows = [].slice.apply(result.firstChild.childNodes);
|
var rows = [].slice.apply(result.firstChild.childNodes);
|
||||||
|
|
||||||
|
// If we're following the bottom of the log, the result always includes
|
||||||
|
// the last line from the previous render. Throw it away, then add the
|
||||||
|
// new data.
|
||||||
|
if (data.live && row.previousSibling) {
|
||||||
|
JX.DOM.remove(row.previousSibling);
|
||||||
|
}
|
||||||
|
|
||||||
JX.DOM.replace(row, rows);
|
JX.DOM.replace(row, rows);
|
||||||
|
|
||||||
|
if (data.live) {
|
||||||
|
// If this was a live follow, scroll the new data into view. This is
|
||||||
|
// probably intensely annoying in practice but seems cool for now.
|
||||||
|
var last_row = rows[rows.length - 1];
|
||||||
|
var tail_pos = JX.$V(last_row).y + JX.Vector.getDim(last_row).y;
|
||||||
|
var view_y = JX.Vector.getViewport().y;
|
||||||
|
JX.DOM.scrollToPosition(null, (tail_pos - view_y) + 32);
|
||||||
|
|
||||||
|
setTimeout(follow, 500);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
request.send();
|
request.send();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function follow() {
|
||||||
|
if (!following) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var live;
|
||||||
|
try {
|
||||||
|
live = JX.DOM.find(contentNode, 'a', 'harbormaster-log-live');
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expand(live);
|
||||||
|
}
|
||||||
|
|
||||||
function onresponse(r) {
|
function onresponse(r) {
|
||||||
JX.DOM.alterClass(contentNode, 'harbormaster-log-view-loading', false);
|
JX.DOM.alterClass(contentNode, 'harbormaster-log-view-loading', false);
|
||||||
|
|
Loading…
Reference in a new issue