Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15331967
D19141.id45849.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
26 KB
Referenced Files
None
Subscribers
None
D19141.id45849.diff
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -78,7 +78,7 @@
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948',
'rsrc/css/application/flag/flag.css' => 'bba8f811',
- 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4',
+ 'rsrc/css/application/harbormaster/harbormaster.css' => 'fecac64f',
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
'rsrc/css/application/herald/herald.css' => 'cd8d0134',
'rsrc/css/application/maniphest/report.css' => '9b9580b7',
@@ -416,6 +416,7 @@
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
+ 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '0844f3c1',
'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e',
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
@@ -578,7 +579,7 @@
'font-fontawesome' => 'e838e088',
'font-lato' => 'c7ccd872',
'global-drag-and-drop-css' => 'b556a948',
- 'harbormaster-css' => 'f491c9f4',
+ 'harbormaster-css' => 'fecac64f',
'herald-css' => 'cd8d0134',
'herald-rule-editor' => 'dca75c0e',
'herald-test-css' => 'a52e323e',
@@ -635,6 +636,7 @@
'javelin-behavior-event-all-day' => 'b41537c9',
'javelin-behavior-fancy-datepicker' => 'ecf4e799',
'javelin-behavior-global-drag-and-drop' => '960f6a39',
+ 'javelin-behavior-harbormaster-log' => '0844f3c1',
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
'javelin-behavior-high-security-warning' => 'a464fe03',
'javelin-behavior-history-install' => '7ee2b591',
@@ -960,6 +962,9 @@
'javelin-stratcom',
'javelin-workflow',
),
+ '0844f3c1' => array(
+ 'javelin-behavior',
+ ),
'08f4ccc3' => array(
'phui-oi-list-view-css',
),
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1230,6 +1230,7 @@
'HarbormasterBuildLogDownloadController' => 'applications/harbormaster/controller/HarbormasterBuildLogDownloadController.php',
'HarbormasterBuildLogPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildLogPHIDType.php',
'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php',
+ 'HarbormasterBuildLogRenderController' => 'applications/harbormaster/controller/HarbormasterBuildLogRenderController.php',
'HarbormasterBuildLogTestCase' => 'applications/harbormaster/__tests__/HarbormasterBuildLogTestCase.php',
'HarbormasterBuildLogView' => 'applications/harbormaster/view/HarbormasterBuildLogView.php',
'HarbormasterBuildLogViewController' => 'applications/harbormaster/controller/HarbormasterBuildLogViewController.php',
@@ -6519,6 +6520,7 @@
'HarbormasterBuildLogDownloadController' => 'HarbormasterController',
'HarbormasterBuildLogPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildLogRenderController' => 'HarbormasterController',
'HarbormasterBuildLogTestCase' => 'PhabricatorTestCase',
'HarbormasterBuildLogView' => 'AphrontView',
'HarbormasterBuildLogViewController' => 'HarbormasterController',
diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
--- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
+++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
@@ -97,7 +97,10 @@
'buildkite/' => 'HarbormasterBuildkiteHookController',
),
'log/' => array(
- 'view/(?P<id>\d+)/' => 'HarbormasterBuildLogViewController',
+ 'view/(?P<id>\d+)/(?:\$(?P<lines>\d+(?:-\d+)?))?'
+ => 'HarbormasterBuildLogViewController',
+ 'render/(?P<id>\d+)/(?:\$(?P<lines>\d+(?:-\d+)?))?'
+ => 'HarbormasterBuildLogRenderController',
'download/(?P<id>\d+)/' => 'HarbormasterBuildLogDownloadController',
),
),
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php b/src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php
@@ -0,0 +1,562 @@
+<?php
+
+final class HarbormasterBuildLogRenderController
+ extends HarbormasterController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $id = $request->getURIData('id');
+
+ $log = id(new HarbormasterBuildLogQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->executeOne();
+ if (!$log) {
+ return new Aphront404Response();
+ }
+
+ $log_size = $this->getTotalByteLength($log);
+
+ $head_lines = $request->getInt('head');
+ if ($head_lines === null) {
+ $head_lines = 8;
+ }
+ $head_lines = min($head_lines, 100);
+ $head_lines = max($head_lines, 0);
+
+ $tail_lines = $request->getInt('tail');
+ if ($tail_lines === null) {
+ $tail_lines = 16;
+ }
+ $tail_lines = min($tail_lines, 100);
+ $tail_lines = max($tail_lines, 0);
+
+ $head_offset = $request->getInt('headOffset');
+ if ($head_offset === null) {
+ $head_offset = 0;
+ }
+
+ $tail_offset = $request->getInt('tailOffset');
+ if ($tail_offset === null) {
+ $tail_offset = $log_size;
+ }
+
+ // Figure out which ranges we're actually going to read. We'll read either
+ // one range (either just at the head, or just at the tail) or two ranges
+ // (one at the head and one at the tail).
+
+ // This gets a little bit tricky because: the ranges may overlap; we just
+ // want to do one big read if there is only a little bit of text left
+ // between the ranges; we may not know where the tail range ends; and we
+ // can only read forward from line map markers, not from any arbitrary
+ // position in the file.
+
+ $bytes_per_line = 140;
+ $body_lines = 8;
+
+ $views = array();
+ if ($head_lines > 0) {
+ $views[] = array(
+ 'offset' => $head_offset,
+ 'lines' => $head_lines,
+ 'direction' => 1,
+ );
+ }
+
+ if ($tail_lines > 0) {
+ $views[] = array(
+ 'offset' => $tail_offset,
+ 'lines' => $tail_lines,
+ 'direction' => -1,
+ );
+ }
+
+ $reads = $views;
+ foreach ($reads as $key => $read) {
+ $offset = $read['offset'];
+
+ $lines = $read['lines'];
+
+ $read_length = 0;
+ $read_length += ($lines * $bytes_per_line);
+ $read_length += ($body_lines * $bytes_per_line);
+
+ $direction = $read['direction'];
+ if ($direction < 0) {
+ $offset -= $read_length;
+ if ($offset < 0) {
+ $offset = 0;
+ $read_length = $log_size;
+ }
+ }
+
+ $position = $log->getReadPosition($offset);
+ list($position_offset, $position_line) = $position;
+ $read_length += ($offset - $position_offset);
+
+ $reads[$key]['fetchOffset'] = $position_offset;
+ $reads[$key]['fetchLength'] = $read_length;
+ $reads[$key]['fetchLine'] = $position_line;
+ }
+
+ $reads = $this->mergeOverlappingReads($reads);
+
+ foreach ($reads as $key => $read) {
+ $data = $log->loadData($read['fetchOffset'], $read['fetchLength']);
+
+ $offset = $read['fetchOffset'];
+ $line = $read['fetchLine'];
+ $lines = $this->getLines($data);
+ $line_data = array();
+ foreach ($lines as $line_text) {
+ $length = strlen($line_text);
+ $line_data[] = array(
+ 'offset' => $offset,
+ 'length' => $length,
+ 'line' => $line,
+ 'data' => $line_text,
+ );
+ $line += 1;
+ $offset += $length;
+ }
+
+ $reads[$key]['data'] = $data;
+ $reads[$key]['lines'] = $line_data;
+ }
+
+ foreach ($views as $view_key => $view) {
+ $anchor_byte = $view['offset'];
+
+ $data_key = null;
+ foreach ($reads as $read_key => $read) {
+ $s = $read['fetchOffset'];
+ $e = $s + $read['fetchLength'];
+
+ if (($s <= $anchor_byte) && ($e >= $anchor_byte)) {
+ $data_key = $read_key;
+ break;
+ }
+ }
+
+ if ($data_key === null) {
+ throw new Exception(
+ pht('Unable to find fetch!'));
+ }
+
+ $anchor_key = null;
+ foreach ($reads[$data_key]['lines'] as $line_key => $line) {
+ $s = $line['offset'];
+ $e = $s + $line['length'];
+ if (($s <= $anchor_byte) && ($e >= $anchor_byte)) {
+ $anchor_key = $line_key;
+ break;
+ }
+ }
+
+ if ($anchor_key === null) {
+ throw new Exception(
+ pht(
+ 'Unable to find lines.'));
+ }
+
+ if ($direction > 0) {
+ $slice_offset = $line_key;
+ } else {
+ $slice_offset = max(0, $line_key - ($view['lines'] - 1));
+ }
+ $slice_length = $view['lines'];
+
+ $views[$view_key] += array(
+ 'sliceKey' => $data_key,
+ 'sliceOffset' => $slice_offset,
+ 'sliceLength' => $slice_length,
+ );
+ }
+
+ foreach ($views as $view_key => $view) {
+ $slice_key = $view['sliceKey'];
+ $lines = array_slice(
+ $reads[$slice_key]['lines'],
+ $view['sliceOffset'],
+ $view['sliceLength']);
+
+ $data_offset = null;
+ $data_length = null;
+ foreach ($lines as $line) {
+ if ($data_offset === null) {
+ $data_offset = $line['offset'];
+ }
+ $data_length += $line['length'];
+ }
+
+ // If the view cursor starts in the middle of a line, we're going to
+ // strip part of the line.
+ $direction = $view['direction'];
+ if ($direction > 0) {
+ $view_offset = $view['offset'];
+ $view_length = $data_length;
+ if ($data_offset < $view_offset) {
+ $trim = ($view_offset - $data_offset);
+ $view_length -= $trim;
+ }
+ } else {
+ $view_offset = $data_offset;
+ $view_length = $data_length;
+ if ($data_offset + $data_length > $view['offset']) {
+ $view_length -= (($data_offset + $data_length) - $view['offset']);
+ }
+ }
+
+ $views[$view_key] += array(
+ 'viewOffset' => $view_offset,
+ 'viewLength' => $view_length,
+ );
+ }
+
+ $views = $this->mergeOverlappingViews($views);
+
+ foreach ($views as $view_key => $view) {
+ $slice_key = $view['sliceKey'];
+ $lines = array_slice(
+ $reads[$slice_key]['lines'],
+ $view['sliceOffset'],
+ $view['sliceLength']);
+
+ $view_offset = $view['viewOffset'];
+ foreach ($lines as $line_key => $line) {
+ $line_offset = $line['offset'];
+
+ if ($line_offset >= $view_offset) {
+ break;
+ }
+
+ $trim = ($view_offset - $line_offset);
+ $line_data = substr($line['data'], $trim);
+ if (!strlen($line_data)) {
+ unset($lines[$line_key]);
+ continue;
+ }
+
+ $lines[$line_key]['data'] = $line_data;
+ $lines[$line_key]['length'] = strlen($line_data);
+ $lines[$line_key]['offset'] += $trim;
+ break;
+ }
+
+ $view_end = $view['viewOffset'] + $view['viewLength'];
+ foreach ($lines as $line_key => $line) {
+ $line_end = $line['offset'] + $line['length'];
+ if ($line_end <= $view_end) {
+ break;
+ }
+
+ $trim = ($line_end - $view_end);
+ $line_data = substr($line['data'], -$trim);
+ if (!strlen($line_data)) {
+ unset($lines[$line_key]);
+ continue;
+ }
+
+ $lines[$line_key]['data'] = $line_data;
+ $lines[$line_key]['length'] = strlen($line_data);
+ }
+
+ $views[$view_key]['viewData'] = $lines;
+ }
+
+ $spacer = null;
+ $render = array();
+ foreach ($views as $view) {
+ if ($spacer) {
+ $spacer['tail'] = $view['viewOffset'];
+ $render[] = $spacer;
+ }
+
+ $render[] = $view;
+
+ $spacer = array(
+ 'spacer' => true,
+ 'head' => ($view['viewOffset'] + $view['viewLength']),
+ );
+ }
+
+ $uri = $log->getURI();
+ $highlight_range = $request->getURIData('lines');
+
+ $rows = array();
+ foreach ($render as $range) {
+ if (isset($range['spacer'])) {
+ $rows[] = phutil_tag(
+ 'tr',
+ array(),
+ array(
+ phutil_tag(
+ 'th',
+ array(),
+ null),
+ phutil_tag(
+ 'td',
+ array(),
+ array(
+ javelin_tag(
+ 'a',
+ array(
+ 'sigil' => 'harbormaster-log-expand',
+ 'meta' => array(
+ 'headOffset' => $range['head'],
+ 'tailOffset' => $range['tail'],
+ 'head' => 4,
+ ),
+ ),
+ 'Show Up ^^^^'),
+ '... '.($range['tail'] - $range['head']).' bytes ...',
+ javelin_tag(
+ 'a',
+ array(
+ 'sigil' => 'harbormaster-log-expand',
+ 'meta' => array(
+ 'headOffset' => $range['head'],
+ 'tailOffset' => $range['tail'],
+ 'tail' => 4,
+ ),
+ ),
+ 'Show Down VVVV'),
+ )),
+ ));
+ continue;
+ }
+
+ $lines = $range['viewData'];
+ foreach ($lines as $line) {
+ $display_line = ($line['line'] + 1);
+ $display_text = ($line['data']);
+
+ $display_line = phutil_tag(
+ 'a',
+ array(
+ 'href' => $uri.'$'.$display_line,
+ ),
+ $display_line);
+
+ $line_cell = phutil_tag('th', array(), $display_line);
+ $text_cell = phutil_tag('td', array(), $display_text);
+
+ $rows[] = phutil_tag(
+ 'tr',
+ array(),
+ array(
+ $line_cell,
+ $text_cell,
+ ));
+ }
+ }
+
+ $table = phutil_tag(
+ 'table',
+ array(
+ 'class' => 'harbormaster-log-table PhabricatorMonospaced',
+ ),
+ $rows);
+
+ // When this is a normal AJAX request, return the rendered log fragment
+ // in an AJAX payload.
+ if ($request->isAjax()) {
+ return id(new AphrontAjaxResponse())
+ ->setContent(
+ array(
+ 'markup' => hsprintf('%s', $table),
+ ));
+ }
+
+ // If the page is being accessed as a standalone page, present a
+ // readable version of the fragment for debugging.
+
+ require_celerity_resource('harbormaster-css');
+
+ $header = pht('Standalone Log Fragment');
+
+ $render_view = id(new PHUIObjectBoxView())
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+ ->setHeaderText($header)
+ ->appendChild($table);
+
+ $page_view = id(new PHUITwoColumnView())
+ ->setFooter($render_view);
+
+ $crumbs = $this->buildApplicationCrumbs()
+ ->addTextCrumb(pht('Build Log %d', $log->getID()), $log->getURI())
+ ->addTextCrumb(pht('Fragment'))
+ ->setBorder(true);
+
+ return $this->newPage()
+ ->setTitle(
+ array(
+ pht('Build Log %d', $log->getID()),
+ pht('Standalone Fragment'),
+ ))
+ ->setCrumbs($crumbs)
+ ->appendChild($page_view);
+ }
+
+ private function getTotalByteLength(HarbormasterBuildLog $log) {
+ $total_bytes = $log->getByteLength();
+ if ($total_bytes) {
+ return (int)$total_bytes;
+ }
+
+ // TODO: Remove this after enough time has passed for installs to run
+ // log rebuilds or decide they don't care about older logs.
+
+ // Older logs don't have this data denormalized onto the log record unless
+ // an administrator has run `bin/harbormaster rebuild-log --all` or
+ // similar. Try to figure it out by summing up the size of each chunk.
+
+ // Note that the log may also be legitimately empty and have actual size
+ // zero.
+ $chunk = new HarbormasterBuildLogChunk();
+ $conn = $chunk->establishConnection('r');
+
+ $row = queryfx_one(
+ $conn,
+ 'SELECT SUM(size) total FROM %T WHERE logID = %d',
+ $chunk->getTableName(),
+ $log->getID());
+
+ return (int)$row['total'];
+ }
+
+ private function getLines($data) {
+ $parts = preg_split("/(\r\n|\r|\n)/", $data, 0, PREG_SPLIT_DELIM_CAPTURE);
+
+ if (last($parts) === '') {
+ array_pop($parts);
+ }
+
+ $lines = array();
+ for ($ii = 0; $ii < count($parts); $ii += 2) {
+ $line = $parts[$ii];
+ if (isset($parts[$ii + 1])) {
+ $line .= $parts[$ii + 1];
+ }
+ $lines[] = $line;
+ }
+
+ return $lines;
+ }
+
+
+ private function mergeOverlappingReads(array $reads) {
+ // Find planned reads which will overlap and merge them into a single
+ // larger read.
+
+ $uk = array_keys($reads);
+ $vk = array_keys($reads);
+
+ foreach ($uk as $ukey) {
+ foreach ($vk as $vkey) {
+ // Don't merge a range into itself, even though they do technically
+ // overlap.
+ if ($ukey === $vkey) {
+ continue;
+ }
+
+ $uread = idx($reads, $ukey);
+ if ($uread === null) {
+ continue;
+ }
+
+ $vread = idx($reads, $vkey);
+ if ($vread === null) {
+ continue;
+ }
+
+ $us = $uread['fetchOffset'];
+ $ue = $us + $uread['fetchLength'];
+
+ $vs = $vread['fetchOffset'];
+ $ve = $vs + $vread['fetchLength'];
+
+ if (($vs > $ue) || ($ve < $us)) {
+ continue;
+ }
+
+ $min = min($us, $vs);
+ $max = max($ue, $ve);
+
+ $reads[$ukey]['fetchOffset'] = $min;
+ $reads[$ukey]['fetchLength'] = ($max - $min);
+ $reads[$ukey]['fetchLine'] = min(
+ $uread['fetchLine'],
+ $vread['fetchLine']);
+
+ unset($reads[$vkey]);
+ }
+ }
+
+ return $reads;
+ }
+
+ private function mergeOverlappingViews(array $views) {
+ $uk = array_keys($views);
+ $vk = array_keys($views);
+
+ $body_lines = 8;
+ $body_bytes = ($body_lines * 140);
+
+ foreach ($uk as $ukey) {
+ foreach ($vk as $vkey) {
+ if ($ukey === $vkey) {
+ continue;
+ }
+
+ $uview = idx($views, $ukey);
+ if ($uview === null) {
+ continue;
+ }
+
+ $vview = idx($views, $vkey);
+ if ($vview === null) {
+ continue;
+ }
+
+ // If these views don't use the same line data, don't try to
+ // merge them.
+ if ($uview['sliceKey'] != $vview['sliceKey']) {
+ continue;
+ }
+
+ // If these views are overlapping or separated by only a few bytes,
+ // merge them into a single view.
+ $us = $uview['viewOffset'];
+ $ue = $us + $uview['viewLength'];
+
+ $vs = $vview['viewOffset'];
+ $ve = $vs + $vview['viewLength'];
+
+ $uss = $uview['sliceOffset'];
+ $use = $uss + $uview['sliceLength'];
+
+ $vss = $vview['sliceOffset'];
+ $vse = $vss + $vview['sliceLength'];
+
+ if ($ue <= $vs) {
+ if (($ue + $body_bytes) >= $vs) {
+ if (($use + $body_lines) >= $vss) {
+ $views[$ukey] = array(
+ 'sliceLength' => ($vse - $uss),
+ 'viewLength' => ($ve - $us),
+ ) + $views[$ukey];
+
+ unset($views[$vkey]);
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ return $views;
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildLogViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildLogViewController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildLogViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildLogViewController.php
@@ -4,8 +4,7 @@
extends HarbormasterController {
public function handleRequest(AphrontRequest $request) {
- $request = $this->getRequest();
- $viewer = $request->getUser();
+ $viewer = $this->getViewer();
$id = $request->getURIData('id');
@@ -21,7 +20,8 @@
$log_view = id(new HarbormasterBuildLogView())
->setViewer($viewer)
- ->setBuildLog($log);
+ ->setBuildLog($log)
+ ->setHighlightedLineRange($request->getURIData('lines'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Build Logs'))
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
--- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
@@ -129,6 +129,30 @@
$this->getID());
}
+ public function loadData($offset, $length) {
+ return substr($this->getLogText(), $offset, $length);
+ }
+
+ public function getReadPosition($read_offset) {
+ $position = array(0, 0);
+
+ $map = $this->getLineMap();
+ if (!$map) {
+ throw new Exception(pht('No line map.'));
+ }
+
+ list($map) = $map;
+ foreach ($map as $marker) {
+ list($offset, $count) = $marker;
+ if ($offset > $read_offset) {
+ break;
+ }
+ $position = $marker;
+ }
+
+ return $position;
+ }
+
public function getLogText() {
// TODO: Remove this method since it won't scale for big logs.
@@ -148,6 +172,15 @@
return "/harbormaster/log/view/{$id}/";
}
+ public function getRenderURI($lines) {
+ if (strlen($lines)) {
+ $lines = '$'.$lines;
+ }
+
+ $id = $this->getID();
+ return "/harbormaster/log/render/{$id}/{$lines}";
+ }
+
/* -( Chunks )------------------------------------------------------------- */
diff --git a/src/applications/harbormaster/view/HarbormasterBuildLogView.php b/src/applications/harbormaster/view/HarbormasterBuildLogView.php
--- a/src/applications/harbormaster/view/HarbormasterBuildLogView.php
+++ b/src/applications/harbormaster/view/HarbormasterBuildLogView.php
@@ -3,6 +3,7 @@
final class HarbormasterBuildLogView extends AphrontView {
private $log;
+ private $highlightedLineRange;
public function setBuildLog(HarbormasterBuildLog $log) {
$this->log = $log;
@@ -13,6 +14,15 @@
return $this->log;
}
+ public function setHighlightedLineRange($range) {
+ $this->highlightedLineRange = $range;
+ return $this;
+ }
+
+ public function getHighlightedLineRange() {
+ return $this->highlightedLineRange;
+ }
+
public function render() {
$viewer = $this->getViewer();
$log = $this->getBuildLog();
@@ -34,10 +44,28 @@
$header->addActionLink($download_button);
+ $content_id = celerity_generate_unique_node_id();
+ $content_div = javelin_tag(
+ 'div',
+ array(
+ 'id' => $content_id,
+ 'class' => 'harbormaster-log-view-loading',
+ ),
+ pht('Loading...'));
+
+ require_celerity_resource('harbormaster-css');
+
+ Javelin::initBehavior(
+ 'harbormaster-log',
+ array(
+ 'contentNodeID' => $content_id,
+ 'renderURI' => $log->getRenderURI($this->getHighlightedLineRange()),
+ ));
+
$box_view = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeader($header)
- ->appendChild('...');
+ ->appendChild($content_div);
return $box_view;
}
diff --git a/webroot/rsrc/css/application/harbormaster/harbormaster.css b/webroot/rsrc/css/application/harbormaster/harbormaster.css
--- a/webroot/rsrc/css/application/harbormaster/harbormaster.css
+++ b/webroot/rsrc/css/application/harbormaster/harbormaster.css
@@ -30,3 +30,41 @@
text-overflow: ellipsis;
color: {$lightgreytext};
}
+
+.harbormaster-log-view-loading {
+ padding: 8px;
+ text-align: center;
+ color: {$lightgreytext};
+}
+
+.harbormaster-log-table th {
+ background-color: {$paste.highlight};
+ border-right: 1px solid {$paste.border};
+
+ -moz-user-select: -moz-none;
+ -khtml-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.harbormaster-log-table th a {
+ display: block;
+ color: {$darkbluetext};
+ text-align: right;
+ padding: 2px 6px 1px 12px;
+}
+
+.harbormaster-log-table th a:hover {
+ background: {$paste.border};
+}
+
+.harbormaster-log-table td {
+ white-space: pre-wrap;
+ padding: 2px 8px 1px;
+ width: 100%;
+}
+
+.harbormaster-log-table tr.harbormaster-log-highlighted td {
+ background: {$paste.highlight};
+}
diff --git a/webroot/rsrc/js/application/harbormaster/behavior-harbormaster-log.js b/webroot/rsrc/js/application/harbormaster/behavior-harbormaster-log.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/harbormaster/behavior-harbormaster-log.js
@@ -0,0 +1,43 @@
+/**
+ * @provides javelin-behavior-harbormaster-log
+ * @requires javelin-behavior
+ */
+
+JX.behavior('harbormaster-log', function(config) {
+ var contentNode = JX.$(config.contentNodeID);
+
+ JX.DOM.listen(contentNode, 'click', 'harbormaster-log-expand', function(e) {
+ if (!e.isNormalClick()) {
+ return;
+ }
+
+ e.kill();
+
+ var row = e.getNode('tag:tr');
+ var data = e.getNodeData('harbormaster-log-expand');
+
+ var uri = new JX.URI(config.renderURI)
+ .addQueryParams(data);
+
+ var request = new JX.Request(uri, function(r) {
+ var result = JX.$H(r.markup).getNode();
+ var rows = JX.DOM.scry(result, 'tr');
+
+ JX.DOM.replace(row, rows);
+ });
+
+ request.send();
+ });
+
+ function onresponse(r) {
+ JX.DOM.alterClass(contentNode, 'harbormaster-log-view-loading', false);
+
+ JX.DOM.setContent(contentNode, JX.$H(r.markup));
+ }
+
+ var uri = new JX.URI(config.renderURI);
+
+ new JX.Request(uri, onresponse)
+ .send();
+
+});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 8, 3:36 PM (2 w, 8 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7383016
Default Alt Text
D19141.id45849.diff (26 KB)
Attached To
Mode
D19141: Sort of make Harbormaster build logs page properly
Attached
Detach File
Event Timeline
Log In to Comment