diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,8 +10,8 @@ 'conpherence.pkg.css' => '437d3b5a', 'conpherence.pkg.js' => '281b1a73', 'core.pkg.css' => 'b2ad82f4', - 'core.pkg.js' => 'cb50c410', - 'darkconsole.pkg.js' => 'a2faee86', + 'core.pkg.js' => '1cedf416', + 'darkconsole.pkg.js' => '31272f61', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', 'diffusion.pkg.css' => '91c5d3a6', @@ -20,7 +20,7 @@ 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', - 'rsrc/css/aphront/dark-console.css' => 'e7c6e44d', + 'rsrc/css/aphront/dark-console.css' => '53798a6d', 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', @@ -361,7 +361,7 @@ 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => 'ce5f793f', + 'rsrc/js/application/aphlict/Aphlict.js' => '7cacce98', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'd82b1ff9', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', @@ -522,7 +522,7 @@ 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/darkconsole/DarkLog.js' => 'c8e1ffe3', 'rsrc/js/core/darkconsole/DarkMessage.js' => 'c48cccdd', - 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '698614f9', + 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '2a228a94', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', @@ -538,7 +538,7 @@ 'symbols' => array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', - 'aphront-dark-console-css' => 'e7c6e44d', + 'aphront-dark-console-css' => '53798a6d', 'aphront-dialog-view-css' => '685c7e2d', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', @@ -583,7 +583,7 @@ 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => 'ce5f793f', + 'javelin-aphlict' => '7cacce98', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', 'javelin-behavior-aphlict-listen' => 'd82b1ff9', @@ -605,7 +605,7 @@ 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', - 'javelin-behavior-dark-console' => '698614f9', + 'javelin-behavior-dark-console' => '2a228a94', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => '408bf173', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', @@ -1089,6 +1089,16 @@ 'javelin-install', 'javelin-util', ), + '2a228a94' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-util', + 'javelin-dom', + 'javelin-request', + 'phabricator-keyboard-shortcut', + 'phabricator-darklog', + 'phabricator-darkmessage', + ), '2b8de964' => array( 'javelin-install', 'javelin-util', @@ -1381,16 +1391,6 @@ '6882e80a' => array( 'javelin-dom', ), - '698614f9' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-util', - 'javelin-dom', - 'javelin-request', - 'phabricator-keyboard-shortcut', - 'phabricator-darklog', - 'phabricator-darkmessage', - ), '69adf288' => array( 'javelin-install', ), @@ -1482,6 +1482,13 @@ 'owners-path-editor', 'javelin-behavior', ), + '7cacce98' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), '7cbe244b' => array( 'javelin-install', 'javelin-util', @@ -2014,13 +2021,6 @@ 'cd2b9b77' => array( 'phui-oi-list-view-css', ), - 'ce5f793f' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php --- a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php +++ b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php @@ -23,6 +23,7 @@ )); $reconnect_label = pht('Reconnect'); + $replay_label = pht('Replay'); $buttons = phutil_tag( 'div', @@ -40,11 +41,22 @@ 'action' => 'reconnect', 'label' => $reconnect_label, )), + id(new PHUIButtonView()) + ->setIcon('fa-backward') + ->setColor(PHUIButtonView::GREY) + ->setText($replay_label) + ->addSigil('dark-console-realtime-action') + ->setMetadata( + array( + 'action' => 'replay', + 'label' => $replay_label, + )), )); return phutil_tag( 'div', array( + 'class' => 'dark-console-realtime', ), array( $buttons, diff --git a/support/aphlict/server/aphlict_server.js b/support/aphlict/server/aphlict_server.js --- a/support/aphlict/server/aphlict_server.js +++ b/support/aphlict/server/aphlict_server.js @@ -197,3 +197,8 @@ admin_server.setClientServers(aphlict_clients); admin_server.setPeerList(peer_list); } + +for (ii = 0; ii < aphlict_clients.length; ii++) { + var client_server = aphlict_clients[ii]; + client_server.setAdminServers(aphlict_admins); +} diff --git a/support/aphlict/server/lib/AphlictClientServer.js b/support/aphlict/server/lib/AphlictClientServer.js --- a/support/aphlict/server/lib/AphlictClientServer.js +++ b/support/aphlict/server/lib/AphlictClientServer.js @@ -16,10 +16,12 @@ this._server = server; this._lists = {}; + this._adminServers = []; }, properties: { logger: null, + adminServers: null }, members: { @@ -33,6 +35,20 @@ return this._lists[instance]; }, + getHistory: function(age) { + var results = []; + + var servers = this.getAdminServers(); + for (var ii = 0; ii < servers.length; ii++) { + var messages = servers[ii].getHistory(age); + for (var jj = 0; jj < messages.length; jj++) { + results.push(messages[jj]); + } + } + + return results; + }, + log: function() { var logger = this.getLogger(); if (!logger) { @@ -117,6 +133,26 @@ listener.unsubscribe(message.data); break; + case 'replay': + var age = message.data.age || 60000; + var min_age = (new Date().getTime() - age); + + var old_messages = self.getHistory(min_age); + for (var ii = 0; ii < old_messages.length; ii++) { + var old_message = old_messages[ii]; + + if (!listener.isSubscribedToAny(old_message.subscribers)) { + continue; + } + + try { + listener.writeMessage(old_message); + } catch (error) { + break; + } + } + break; + default: log( 'Unrecognized command "%s".', diff --git a/webroot/rsrc/css/aphront/dark-console.css b/webroot/rsrc/css/aphront/dark-console.css --- a/webroot/rsrc/css/aphront/dark-console.css +++ b/webroot/rsrc/css/aphront/dark-console.css @@ -231,3 +231,7 @@ padding: 8px; margin: 2px; } + +.dark-console-realtime .button { + margin-right: 8px; +} diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -40,6 +40,7 @@ _socket: null, _subscriptions: null, _status: null, + _isReconnect: false, start: function() { JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead)); @@ -94,10 +95,31 @@ }, _open: function() { + // If this is a reconnect, ask the server to replay recent messages + // after other tabs have had a chance to subscribe. Do this before we + // broadcast that the connection status is now open. + if (this._isReconnect) { + setTimeout(JX.bind(this, this._reconnect), 100); + } + this._broadcastStatus('open'); JX.Leader.broadcast(null, {type: 'aphlict.getsubscribers'}); }, + _reconnect: function() { + this.replay(); + + JX.Leader.broadcast(null, {type: 'aphlict.reconnect', data: null}); + }, + + replay: function() { + var replay = { + age: 60000 + }; + + JX.Leader.broadcast(null, {type: 'aphlict.replay', data: replay}); + }, + _close: function() { this._broadcastStatus('closed'); }, @@ -131,10 +153,13 @@ case 'aphlict.subscribe': if (is_leader) { - this._write({ - command: 'subscribe', - data: message.data - }); + this._writeCommand('subscribe', message.data); + } + break; + + case 'aphlict.replay': + if (is_leader) { + this._writeCommand('replay', message.data); } break; @@ -147,11 +172,27 @@ _setStatus: function(status) { this._status = status; + + // If we've ever seen an open connection, any new connection we make + // is a reconnect and should replay history. + if (status == 'open') { + this._isReconnect = true; + } + this.invoke('didChangeStatus'); }, _write: function(message) { this._socket.send(JX.JSON.stringify(message)); + }, + + _writeCommand: function(command, message) { + var frame = { + command: command, + data: message + }; + + return this._write(frame); } }, diff --git a/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js --- a/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js +++ b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js @@ -392,6 +392,9 @@ ws.reconnect(); } break; + case 'replay': + JX.Aphlict.getInstance().replay(); + break; } });