diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fadafe5e4e..3b35f33608 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -271,12 +271,16 @@ phutil_register_library_map(array( 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog', 'PhabricatorController' => 'applications/base/controller/base', 'PhabricatorDaemon' => 'infrastructure/daemon/base', + 'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/combined', 'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/console', 'PhabricatorDaemonControl' => 'infrastructure/daemon/control', 'PhabricatorDaemonController' => 'applications/daemon/controller/base', 'PhabricatorDaemonDAO' => 'infrastructure/daemon/storage/base', 'PhabricatorDaemonLog' => 'infrastructure/daemon/storage/log', 'PhabricatorDaemonLogEvent' => 'infrastructure/daemon/storage/event', + 'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/daemonlogevents', + 'PhabricatorDaemonLogListController' => 'applications/daemon/controller/loglist', + 'PhabricatorDaemonLogListView' => 'applications/daemon/view/daemonloglist', 'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/logview', 'PhabricatorDaemonReference' => 'infrastructure/daemon/control/reference', 'PhabricatorDaemonTimelineConsoleController' => 'applications/daemon/controller/timeline', @@ -675,11 +679,15 @@ phutil_register_library_map(array( 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorController' => 'AphrontController', 'PhabricatorDaemon' => 'PhutilDaemon', + 'PhabricatorDaemonCombinedLogController' => 'PhabricatorDaemonController', 'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonController' => 'PhabricatorController', 'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO', 'PhabricatorDaemonLog' => 'PhabricatorDaemonDAO', 'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO', + 'PhabricatorDaemonLogEventsView' => 'AphrontView', + 'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController', + 'PhabricatorDaemonLogListView' => 'AphrontView', 'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController', 'PhabricatorDaemonTimelineConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonTimelineEventController' => 'PhabricatorDaemonController', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index c4d008cd02..873927f4bf 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -231,6 +231,8 @@ class AphrontDefaultApplicationConfiguration '/daemon/' => array( 'task/(?P\d+)/$' => 'PhabricatorWorkerTaskDetailController', 'log/' => array( + '$' => 'PhabricatorDaemonLogListController', + 'combined/$' => 'PhabricatorDaemonCombinedLogController', '(?P\d+)/$' => 'PhabricatorDaemonLogViewController', ), 'timeline/$' => 'PhabricatorDaemonTimelineConsoleController', diff --git a/src/applications/daemon/controller/combined/PhabricatorDaemonCombinedLogController.php b/src/applications/daemon/controller/combined/PhabricatorDaemonCombinedLogController.php new file mode 100644 index 0000000000..1b237b23da --- /dev/null +++ b/src/applications/daemon/controller/combined/PhabricatorDaemonCombinedLogController.php @@ -0,0 +1,54 @@ +getRequest(); + + $pager = new AphrontPagerView(); + $pager->setOffset($request->getInt('page')); + $pager->setPageSize(1000); + + $events = id(new PhabricatorDaemonLogEvent())->loadAllWhere( + '1 = 1 ORDER BY id DESC LIMIT %d, %d', + $pager->getOffset(), + $pager->getPageSize() + 1); + + $events = $pager->sliceResults($events); + $pager->setURI($request->getRequestURI(), 'page'); + + $event_view = new PhabricatorDaemonLogEventsView(); + $event_view->setEvents($events); + $event_view->setCombinedLog(true); + + $log_panel = new AphrontPanelView(); + $log_panel->setHeader('Combined Daemon Logs'); + $log_panel->appendChild($event_view); + $log_panel->appendChild($pager); + + return $this->buildStandardPageResponse( + $log_panel, + array( + 'title' => 'Combined Daemon Log', + )); + } + +} diff --git a/src/applications/daemon/controller/combined/__init__.php b/src/applications/daemon/controller/combined/__init__.php new file mode 100644 index 0000000000..8c01081ee8 --- /dev/null +++ b/src/applications/daemon/controller/combined/__init__.php @@ -0,0 +1,18 @@ +loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT 15'); - $rows = array(); - foreach ($logs as $log) { - $epoch = $log->getDateCreated(); - - $rows[] = array( - phutil_escape_html($log->getDaemon()), - phutil_escape_html($log->getHost()), - $log->getPID(), - date('M j, Y', $epoch), - date('g:i A', $epoch), - phutil_render_tag( - 'a', - array( - 'href' => '/daemon/log/'.$log->getID().'/', - 'class' => 'button small grey', - ), - 'View Log'), - ); - } - - $daemon_table = new AphrontTableView($rows); - $daemon_table->setHeaders( - array( - 'Daemon', - 'Host', - 'PID', - 'Date', - 'Time', - 'View', - )); - $daemon_table->setColumnClasses( - array( - 'wide wrap', - '', - '', - '', - 'right', - 'action', - )); + $daemon_table = new PhabricatorDaemonLogListView(); + $daemon_table->setDaemonLogs($logs); $daemon_panel = new AphrontPanelView(); - $daemon_panel->setHeader('Recently Launched Daemons'); + $daemon_panel->setHeader( + 'Recently Launched Daemons'. + ' · '. + phutil_render_tag( + 'a', + array( + 'href' => '/daemon/log/', + ), + 'View All Daemons'). + ' · '. + phutil_render_tag( + 'a', + array( + 'href' => '/daemon/log/combined/', + ), + 'View Combined Log')); $daemon_panel->appendChild($daemon_table); $tasks = id(new PhabricatorWorkerTask())->loadAllWhere( diff --git a/src/applications/daemon/controller/console/__init__.php b/src/applications/daemon/controller/console/__init__.php index 6f34be9665..a1b99f9d14 100644 --- a/src/applications/daemon/controller/console/__init__.php +++ b/src/applications/daemon/controller/console/__init__.php @@ -7,6 +7,7 @@ phutil_require_module('phabricator', 'applications/daemon/controller/base'); +phutil_require_module('phabricator', 'applications/daemon/view/daemonloglist'); phutil_require_module('phabricator', 'infrastructure/daemon/storage/log'); phutil_require_module('phabricator', 'infrastructure/daemon/timeline/storage/cursor'); phutil_require_module('phabricator', 'infrastructure/daemon/workers/storage/task'); diff --git a/src/applications/daemon/controller/loglist/PhabricatorDaemonLogListController.php b/src/applications/daemon/controller/loglist/PhabricatorDaemonLogListController.php new file mode 100644 index 0000000000..1d44639026 --- /dev/null +++ b/src/applications/daemon/controller/loglist/PhabricatorDaemonLogListController.php @@ -0,0 +1,50 @@ +getRequest(); + + $pager = new AphrontPagerView(); + $pager->setOffset($request->getInt('page')); + + $logs = id(new PhabricatorDaemonLog())->loadAllWhere( + '1 = 1 ORDER BY id DESC LIMIT %d, %d', + $pager->getOffset(), + $pager->getPageSize() + 1); + + $logs = $pager->sliceResults($logs); + $pager->setURI($request->getRequestURI(), 'page'); + + $daemon_table = new PhabricatorDaemonLogListView(); + $daemon_table->setDaemonLogs($logs); + + $daemon_panel = new AphrontPanelView(); + $daemon_panel->setHeader('Launched Daemons'); + $daemon_panel->appendChild($daemon_table); + $daemon_panel->appendChild($pager); + + return $this->buildStandardPageResponse( + $daemon_panel, + array( + 'title' => 'All Daemons', + )); + } + +} diff --git a/src/applications/daemon/controller/loglist/__init__.php b/src/applications/daemon/controller/loglist/__init__.php new file mode 100644 index 0000000000..407b072e8b --- /dev/null +++ b/src/applications/daemon/controller/loglist/__init__.php @@ -0,0 +1,18 @@ +loadAllWhere( - 'logID = %d ORDER BY id DESC LIMIT 200', + 'logID = %d ORDER BY id DESC LIMIT 1000', $log->getID()); $content = array(); @@ -72,44 +72,19 @@ class PhabricatorDaemonLogViewController extends PhabricatorDaemonController { $content[] = $panel; - $rows = array(); - foreach ($events as $event) { - $rows[] = array( - phutil_escape_html($event->getLogType()), - date('M j, Y', $event->getEpoch()), - date('g:i:s A', $event->getEpoch()), - str_replace("\n", '
', phutil_escape_html($event->getMessage())), - ); - } - - $log_table = new AphrontTableView($rows); - $log_table->setHeaders( - array( - 'Type', - 'Date', - 'Time', - 'Message', - )); - $log_table->setColumnClasses( - array( - '', - '', - 'right', - 'wide wrap', - )); + $event_view = new PhabricatorDaemonLogEventsView(); + $event_view->setEvents($events); $log_panel = new AphrontPanelView(); $log_panel->setHeader('Daemon Logs'); - $log_panel->appendChild($log_table); + $log_panel->appendChild($event_view); $content[] = $log_panel; - - return $this->buildStandardPageResponse( $content, array( - 'title' => 'Log', + 'title' => 'Daemon Log', )); } diff --git a/src/applications/daemon/controller/logview/__init__.php b/src/applications/daemon/controller/logview/__init__.php index b2cbe255d2..8ddc17864a 100644 --- a/src/applications/daemon/controller/logview/__init__.php +++ b/src/applications/daemon/controller/logview/__init__.php @@ -8,15 +8,14 @@ phutil_require_module('phabricator', 'aphront/response/404'); phutil_require_module('phabricator', 'applications/daemon/controller/base'); +phutil_require_module('phabricator', 'applications/daemon/view/daemonlogevents'); phutil_require_module('phabricator', 'infrastructure/daemon/storage/event'); phutil_require_module('phabricator', 'infrastructure/daemon/storage/log'); -phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/control/static'); phutil_require_module('phabricator', 'view/form/control/textarea'); phutil_require_module('phabricator', 'view/layout/panel'); -phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/daemon/view/daemonlogevents/PhabricatorDaemonLogEventsView.php b/src/applications/daemon/view/daemonlogevents/PhabricatorDaemonLogEventsView.php new file mode 100644 index 0000000000..a8c0a72814 --- /dev/null +++ b/src/applications/daemon/view/daemonlogevents/PhabricatorDaemonLogEventsView.php @@ -0,0 +1,83 @@ +events = $events; + } + + public function setCombinedLog($is_combined) { + $this->combinedLog = $is_combined; + } + + public function render() { + $rows = array(); + + foreach ($this->events as $event) { + $row = array( + phutil_escape_html($event->getLogType()), + date('M j, Y', $event->getEpoch()), + date('g:i:s A', $event->getEpoch()), + str_replace("\n", '
', phutil_escape_html($event->getMessage())), + ); + + if ($this->combinedLog) { + array_unshift( + $row, + phutil_render_tag( + 'a', + array( + 'href' => '/daemon/log/'.$event->getLogID().'/', + ), + phutil_escape_html('Daemon '.$event->getLogID()))); + } + + $rows[] = $row; + } + + $classes = array( + '', + '', + 'right', + 'wide wrap', + ); + + $headers = array( + 'Type', + 'Date', + 'Time', + 'Message', + ); + + if ($this->combinedLog) { + array_unshift($classes, 'pri'); + array_unshift($headers, 'Daemon'); + } + + $log_table = new AphrontTableView($rows); + $log_table->setHeaders($headers); + $log_table->setColumnClasses($classes); + + return $log_table->render(); + } + +} diff --git a/src/applications/daemon/view/daemonlogevents/__init__.php b/src/applications/daemon/view/daemonlogevents/__init__.php new file mode 100644 index 0000000000..29eecc7ec3 --- /dev/null +++ b/src/applications/daemon/view/daemonlogevents/__init__.php @@ -0,0 +1,15 @@ +daemonLogs = $daemon_logs; + } + + public function render() { + $rows = array(); + + foreach ($this->daemonLogs as $log) { + $epoch = $log->getDateCreated(); + + if ($log->getHost() == php_uname('n')) { + + // This will probably fail since apache can't signal the process, but + // we can check the error code to figure out if the process exists. + $is_running = posix_kill($log->getPID(), 0); + if (posix_get_last_error() == 1) { + // "Operation Not Permitted", indicates that the PID exists. If it + // doesn't, we'll get an error 3 ("No such process") instead. + $is_running = true; + } + + if ($is_running) { + $running = phutil_render_tag( + 'span', + array( + 'style' => 'color: #00cc00', + 'title' => 'Running', + ), + '•'); + } else { + $running = phutil_render_tag( + 'span', + array( + 'style' => 'color: #cc0000', + 'title' => 'Not running', + ), + '•'); + } + } else { + $running = phutil_render_tag( + 'span', + array( + 'style' => 'color: #888888', + 'title' => 'Not on this host', + ), + '?'); + } + + $rows[] = array( + $running, + phutil_escape_html($log->getDaemon()), + phutil_escape_html($log->getHost()), + $log->getPID(), + date('M j, Y', $epoch), + date('g:i A', $epoch), + phutil_render_tag( + 'a', + array( + 'href' => '/daemon/log/'.$log->getID().'/', + 'class' => 'button small grey', + ), + 'View Log'), + ); + } + + $daemon_table = new AphrontTableView($rows); + $daemon_table->setHeaders( + array( + '', + 'Daemon', + 'Host', + 'PID', + 'Date', + 'Time', + 'View', + )); + $daemon_table->setColumnClasses( + array( + '', + 'wide wrap', + '', + '', + '', + 'right', + 'action', + )); + + return $daemon_table->render(); + } + +} diff --git a/src/applications/daemon/view/daemonloglist/__init__.php b/src/applications/daemon/view/daemonloglist/__init__.php new file mode 100644 index 0000000000..3be004735f --- /dev/null +++ b/src/applications/daemon/view/daemonloglist/__init__.php @@ -0,0 +1,15 @@ +getOffset(), $pager->getPageSize() + 1); - if (count($files) > $pager->getPageSize()) { - $files = array_slice($files, 0, $pager->getPageSize(), true); - $pager->setHasMorePages(true); - } - + $files = $pager->sliceResults($files); $pager->setURI($request->getRequestURI(), 'page'); $rows = array(); diff --git a/src/view/control/pager/AphrontPagerView.php b/src/view/control/pager/AphrontPagerView.php index 6b526975c4..0429b8ca13 100644 --- a/src/view/control/pager/AphrontPagerView.php +++ b/src/view/control/pager/AphrontPagerView.php @@ -80,6 +80,32 @@ final class AphrontPagerView extends AphrontView { return $this->count !== null; } + /** + * A common paging strategy is to select one extra record and use that to + * indicate that there's an additional page (this doesn't give you a + * complete page count but is often faster than counting the total number + * of items). This method will take a result array, slice it down to the + * page size if necessary, and call setHasMorePages() if there are more than + * one page of results. + * + * $results = queryfx_all( + * $conn, + * 'SELECT ... LIMIT %d, %d', + * $pager->getOffset(), + * $pager->getPageSize() + 1); + * $results = $pager->sliceResults($results); + * + * @param list Result array. + * @return list One page of results. + */ + public function sliceResults(array $results) { + if (count($results) > $this->getPageSize()) { + $results = array_slice($results, 0, $this->getPageSize(), true); + $this->setHasMorePages(true); + } + return $results; + } + public function render() { if (!$this->uri) { throw new Exception(