diff --git a/packages/local-sync/src/local-sync-dashboard/public/images/close.png b/packages/local-sync/images/close.png similarity index 100% rename from packages/local-sync/src/local-sync-dashboard/public/images/close.png rename to packages/local-sync/images/close.png diff --git a/packages/local-sync/src/local-sync-dashboard/public/images/dropdown.png b/packages/local-sync/images/dropdown.png similarity index 100% rename from packages/local-sync/src/local-sync-dashboard/public/images/dropdown.png rename to packages/local-sync/images/dropdown.png diff --git a/packages/local-sync/main.es6 b/packages/local-sync/main.es6 index bd555b299..14da6c9e0 100644 --- a/packages/local-sync/main.es6 +++ b/packages/local-sync/main.es6 @@ -1,9 +1,14 @@ +/* eslint global-require: 0 */ +import {ComponentRegistry} from 'nylas-exports' import {createLogger} from './src/shared/logger' export function activate() { global.Logger = createLogger('local-sync') - require('./src/local-api/app.js'); - require('./src/local-sync-worker/app.js'); + require('./src/local-api/app'); + require('./src/local-sync-worker/app'); + + const Root = require('./src/local-sync-dashboard/root').default; + ComponentRegistry.register(Root, {role: 'Developer:LocalSyncUI'}); } export function deactivate() { diff --git a/packages/local-sync/src/local-sync-dashboard/app.js b/packages/local-sync/src/local-sync-dashboard/app.js deleted file mode 100644 index d9719213d..000000000 --- a/packages/local-sync/src/local-sync-dashboard/app.js +++ /dev/null @@ -1,49 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const Inert = require('inert'); -const Hapi = require('hapi'); -const HapiWebSocket = require('hapi-plugin-websocket'); - -const server = new Hapi.Server(); -server.connection({ port: process.env.PORT }); - -const attach = (directory) => { - const routesDir = path.join(__dirname, directory) - fs.readdirSync(routesDir).forEach((filename) => { - if (filename.endsWith('.js')) { - const routeFactory = require(path.join(routesDir, filename)); - routeFactory(server); - } - }); -} - -server.register([HapiWebSocket, Inert], () => { - attach('./routes/') - - server.route({ - method: 'GET', - path: '/ping', - config: { - auth: false, - }, - handler: (request, reply) => { - global.Logger.info("---> Ping!") - reply("pong") - }, - }); - - server.route({ - method: 'GET', - path: '/{param*}', - handler: { - directory: { - path: require('path').join(__dirname, 'public'), - }, - }, - }); - - server.start((startErr) => { - if (startErr) { throw startErr; } - global.Logger.info({uri: server.info.uri}, 'Dashboard running'); - }); -}); diff --git a/packages/local-sync/src/local-sync-dashboard/public/js/dropdown.jsx b/packages/local-sync/src/local-sync-dashboard/dropdown.jsx similarity index 94% rename from packages/local-sync/src/local-sync-dashboard/public/js/dropdown.jsx rename to packages/local-sync/src/local-sync-dashboard/dropdown.jsx index c09f78d64..31a8f7a19 100644 --- a/packages/local-sync/src/local-sync-dashboard/public/js/dropdown.jsx +++ b/packages/local-sync/src/local-sync-dashboard/dropdown.jsx @@ -1,6 +1,6 @@ -const React = window.React; +import {React} from 'nylas-exports'; -class Dropdown extends React.Component { +export default class Dropdown extends React.Component { constructor(props) { super(props); this.state = { @@ -67,5 +67,3 @@ Dropdown.propTypes = { defaultOption: React.PropTypes.string, onSelect: React.PropTypes.func, } - -window.Dropdown = Dropdown; diff --git a/packages/local-sync/src/local-sync-dashboard/public/js/elapsed-time.jsx b/packages/local-sync/src/local-sync-dashboard/elapsed-time.jsx similarity index 83% rename from packages/local-sync/src/local-sync-dashboard/public/js/elapsed-time.jsx rename to packages/local-sync/src/local-sync-dashboard/elapsed-time.jsx index ecad76b01..c8b2b2a4d 100644 --- a/packages/local-sync/src/local-sync-dashboard/public/js/elapsed-time.jsx +++ b/packages/local-sync/src/local-sync-dashboard/elapsed-time.jsx @@ -1,12 +1,11 @@ -const React = window.React; -const ReactDOM = window.ReactDOM; +import {React, ReactDOM} from 'nylas-exports'; setInterval(() => { const event = new Event('tick'); window.dispatchEvent(event); }, 1000); -class ElapsedTime extends React.Component { +export default class ElapsedTime extends React.Component { constructor(props) { super(props); this.state = { @@ -36,5 +35,3 @@ ElapsedTime.propTypes = { refTimestamp: React.PropTypes.number, // milliseconds formatTime: React.PropTypes.func, } - -window.ElapsedTime = ElapsedTime; diff --git a/packages/local-sync/src/local-sync-dashboard/public/js/modal.jsx b/packages/local-sync/src/local-sync-dashboard/modal.jsx similarity index 96% rename from packages/local-sync/src/local-sync-dashboard/public/js/modal.jsx rename to packages/local-sync/src/local-sync-dashboard/modal.jsx index bcc8587f2..b8c350454 100644 --- a/packages/local-sync/src/local-sync-dashboard/public/js/modal.jsx +++ b/packages/local-sync/src/local-sync-dashboard/modal.jsx @@ -1,6 +1,6 @@ -const React = window.React; +import {React} from 'nylas-exports'; -class Modal extends React.Component { +export default class Modal extends React.Component { constructor(props) { super(props); this.state = { @@ -101,5 +101,3 @@ Modal.propTypes = { onClose: React.PropTypes.func, actionElems: React.PropTypes.arrayOf(React.PropTypes.object), } - -window.Modal = Modal; diff --git a/packages/local-sync/src/local-sync-dashboard/newrelic.js b/packages/local-sync/src/local-sync-dashboard/newrelic.js deleted file mode 100644 index 97cd18e90..000000000 --- a/packages/local-sync/src/local-sync-dashboard/newrelic.js +++ /dev/null @@ -1,21 +0,0 @@ -const {NODE_ENV} = process.env -/** - * New Relic agent configuration. - * - * See lib/config.defaults.js in the agent distribution for a more complete - * description of configuration variables and their potential values. - */ -exports.config = { - /** - * Array of application names. - */ - app_name: [`k2-dash-${NODE_ENV}`], - logging: { - /** - * Level at which to log. 'trace' is most useful to New Relic when diagnosing - * issues with the agent, 'info' and higher will impose the least overhead on - * production applications. - */ - level: 'info', - }, -} diff --git a/packages/local-sync/src/local-sync-dashboard/public/css/app.css b/packages/local-sync/src/local-sync-dashboard/public/css/app.css deleted file mode 100644 index c77a55c1c..000000000 --- a/packages/local-sync/src/local-sync-dashboard/public/css/app.css +++ /dev/null @@ -1,261 +0,0 @@ -body { - background-image: url(http://news.nationalgeographic.com/content/dam/news/2015/12/13/BookTalk%20K2/01BookTalkK2.jpg); - background-image: -moz-linear-gradient(top, rgba(232, 244, 250, 0.2), rgba(231, 231, 233, 1)), url(http://news.nationalgeographic.com/content/dam/news/2015/12/13/BookTalk%20K2/01BookTalkK2.jpg); - background-image: -webkit-linear-gradient(top, rgba(232, 244, 250, 0.2), rgba(231, 231, 233, 1)), url(http://news.nationalgeographic.com/content/dam/news/2015/12/13/BookTalk%20K2/01BookTalkK2.jpg); - background-size: 100vw auto; - background-attachment: fixed; - font-family: Roboto, sans-serif; - font-size: 11px; -} - -h2 { - padding-top: 10px; - text-align: center; -} - -pre { - margin: 0; -} - -#accounts-wrapper { - position: relative; -} - -.account { - position: absolute; - border-radius: 5px; - width: 240px; - height: 450px; - background-color: rgb(255, 255, 255); - padding: 15px; - margin: 5px; - overflow: hidden; -} - -.account h3 { - font-size: 13px; - margin: 0; - padding: 0; -} - -.account .section { - font-size: 12px; - padding: 10px 0; - text-align: center; -} - -.account.errored { - color: #a94442; - border-radius: 4px; - background-color: rgb(231, 195, 195); -} - -.error-link { - font-weight: bold; -} - -.error-link:hover { - cursor: pointer; - color: #702726; -} - -#open-all-sync { - color: #ffffff; - padding-left: 5px; -} - -.right-action { - float: right; - margin-top: 10px; -} - -.action-link { - color: rgba(16, 83, 161, 0.88); - text-decoration: underline; - cursor: pointer; - margin: 5px 0; -} - -.action-link.cancel { - margin-top: 10px; -} - -.sync-policy textarea { - width: 100%; - height: 200px; - white-space: pre; -} - -.modal { - background-color: white; - width: 50%; - margin: 10vh auto; - padding: 20px; - max-height: calc(80vh - 40px); /* minus padding */ - overflow: auto; -} - -.modal-bg { - position: fixed; - width: 100%; - height: 100%; - left: 0; - top: 0; - background-color: rgba(0, 0, 0, 0.3); - z-index: 10; -} - -.modal-close-wrapper { - position: relative; - height: 0; - width: 0; - float: right; - top: -10px; -} - -.modal-close { - position: absolute; - cursor: pointer; - font-size: 14px; - font-weight: bold; - background: url('../images/close.png') center center no-repeat; - background-size: 12px auto; - height: 12px; - width: 12px; - top: 12px; - right: 12px; -} - -.sync-graph { - margin-top: 3px; -} - -.stats b { - display: inline-block; - margin-top: 5px; - margin-bottom: 1px; -} - -#syncback-request-details { - font-size: 15px; - color: black; -} - -#syncback-request-details .counts { - margin: 10px; -} - -#syncback-request-details span { - margin: 10px; -} - -#syncback-request-details table { - width: 100%; - border: solid black 1px; - box-shadow: 1px 1px #333333; - margin: 10px 0; - border-collapse: collapse; -} - -#syncback-request-details tr:nth-child(even) { - background-color: #F1F1F1; -} - -#syncback-request-details tr:not(:first-child):hover { - background-color: #C9C9C9; -} - -#syncback-request-details td, #syncback-request-details th { - text-align: center; - padding: 10px 5px; - border: solid black 1px; -} - -.dropdown-arrow { - margin: 0 5px; - height: 7px; - vertical-align: middle; -} - -.dropdown-options { - border: solid black 1px; - position: absolute; - background-color: white; - text-align: left; - display: inline; -} - -.dropdown-option { - position: relative; - padding: 0px 2px; -} - -.dropdown-option:hover { - background-color: rgb(114, 163, 255); -} - -.dropdown-selected { - display: inline; -} - -.dropdown-wrapper { - display: inline; - cursor: pointer; - font-weight: normal; -} - -.mini-account::after { - display: inline-block; - position: relative; - height: 100%; - width: 100%; - background-color: #666666; - content: ""; - z-index: -1; -} - -.mini-account { - background-color: rgb(0, 255, 157); - display: inline-block; - width: 10px; - height: 10px; -} - -.mini-account.errored { - background-color: rgb(255, 38, 0); -} - -.process-loads { - display: inline-block; - padding: 15px; - width: 250px; - margin: 15px 0; - background-color: white; -} - -.process-loads .section { - text-decoration: underline; - margin-bottom: 10px; - font-size: 12px; -} - -.sum-accounts { - border-top: solid black 1px; - margin-top: 5px; - padding-top: 5px; -} - -.account-filter { - padding-left: 5px; -} - -.process-group { - display: inline-block; - margin: 10px; - max-width: 250px; - vertical-align: top; -} - -#group-by-process { - vertical-align: middle; -} diff --git a/packages/local-sync/src/local-sync-dashboard/public/favicon.png b/packages/local-sync/src/local-sync-dashboard/public/favicon.png deleted file mode 100644 index d595d6d01..000000000 Binary files a/packages/local-sync/src/local-sync-dashboard/public/favicon.png and /dev/null differ diff --git a/packages/local-sync/src/local-sync-dashboard/public/index.html b/packages/local-sync/src/local-sync-dashboard/public/index.html deleted file mode 100644 index 888e40cfb..000000000 --- a/packages/local-sync/src/local-sync-dashboard/public/index.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - K2 Dashboard - - -

K2 Dashboard

-
- - diff --git a/packages/local-sync/src/local-sync-dashboard/public/js/account-filter.jsx b/packages/local-sync/src/local-sync-dashboard/public/js/account-filter.jsx deleted file mode 100644 index 4c13b2462..000000000 --- a/packages/local-sync/src/local-sync-dashboard/public/js/account-filter.jsx +++ /dev/null @@ -1,26 +0,0 @@ -const React = window.React; - -function AccountFilter(props) { - return ( -
- Display: -
- ) -} - -AccountFilter.propTypes = { - onChange: React.PropTypes.func, - id: React.PropTypes.string, -} - -AccountFilter.states = { - all: "all", - errored: "errored", - notErrored: "not-errored", -}; - -window.AccountFilter = AccountFilter; diff --git a/packages/local-sync/src/local-sync-dashboard/public/js/app.jsx b/packages/local-sync/src/local-sync-dashboard/public/js/app.jsx deleted file mode 100644 index 1b03ab9f6..000000000 --- a/packages/local-sync/src/local-sync-dashboard/public/js/app.jsx +++ /dev/null @@ -1,333 +0,0 @@ -/* eslint react/react-in-jsx-scope: 0*/ -/* eslint no-console: 0*/ - -const React = window.React; -const ReactDOM = window.ReactDOM; -const _ = window._; -const { - SyncPolicy, - SetAllSyncPolicies, - AccountFilter, - SyncGraph, - SyncbackRequestDetails, - ElapsedTime, - Modal, - MiniAccount, - ProcessLoads, -} = window; - -function calcAcctPosition(count) { - const width = 280; - const height = 490; - const marginTop = 0; - const marginSide = 0; - - const acctsPerRow = Math.floor((window.innerWidth - 2 * marginSide) / width); - const row = Math.floor(count / acctsPerRow) - const col = count - (row * acctsPerRow); - const top = marginTop + (row * height); - const left = marginSide + (width * col); - - return {left: left, top: top}; -} - -function formatSyncTimes(timestamp) { - return timestamp / 1000; -} - -class Account extends React.Component { - constructor(props) { - super(props); - this.state = { - accountId: props.account.id, - version: null, - } - } - - shouldComponentUpdate(nextProps) { - return nextProps.account.version !== this.props.account.version || - nextProps.active !== this.props.active || - nextProps.assignment !== this.props.assignment || - nextProps.count !== this.props.count; - } - - clearError() { - const req = new XMLHttpRequest(); - const url = `${window.location.protocol}/accounts/${this.state.accountId}/clear-sync-error`; - req.open("PUT", url, true); - req.onreadystatechange = () => { - if (req.readyState === XMLHttpRequest.DONE) { - if (req.status === 200) { - // Would setState here, but external updates currently refresh the account - } else { - console.error(req.responseText); - } - } - } - req.send(); - } - - renderPolicyOrError() { - const account = this.props.account; - if (account.sync_error != null) { - return this.renderError(); - } - return ( - - ); - } - - renderError() { - const {message, stack} = this.props.account.sync_error - return ( -
-
Error
- -
{JSON.stringify(stack, null, 2)}
-
-
this.clearError()}>Clear Error
-
- ) - } - - render() { - const {account, assignment, active} = this.props; - const errorClass = account.sync_error ? ' errored' : '' - - const numStoredSyncs = account.last_sync_completions.length; - const oldestSync = account.last_sync_completions[numStoredSyncs - 1]; - const newestSync = account.last_sync_completions[0]; - const avgBetweenSyncs = (newestSync - oldestSync) / (1000 * numStoredSyncs); - - let firstSyncDuration = "Incomplete"; - if (account.first_sync_completion) { - firstSyncDuration = (new Date(account.first_sync_completion) - new Date(account.created_at)) / 1000; - } - - const position = calcAcctPosition(this.props.count); - - return ( -
-

{account.email_address} [{account.id}] {active ? '🌕' : '🌑'}

- {assignment} - -
- First Sync Duration (sec): -
{firstSyncDuration}
- Average Time Between Syncs (sec): -
{avgBetweenSyncs}
- Time Since Last Sync (sec): -
-            
-          
- Recent Syncs: - -
- {this.renderPolicyOrError()} -
- ); - } -} - -Account.propTypes = { - account: React.PropTypes.object, - active: React.PropTypes.bool, - assignment: React.PropTypes.string, - count: React.PropTypes.number, -} - -class Root extends React.Component { - - constructor() { - super(); - this.state = { - accounts: {}, - assignments: {}, - activeAccountIds: [], - visibleAccounts: AccountFilter.states.all, - groupByProcess: false, - }; - } - - componentDidMount() { - let url = null; - if (window.location.protocol === "https:") { - url = `wss://${window.location.host}/websocket`; - } else { - url = `ws://${window.location.host}/websocket`; - } - this.websocket = new WebSocket(url); - this.websocket.onopen = () => { - this.websocket.send("Message to send"); - }; - this.websocket.onmessage = (evt) => { - try { - const msg = JSON.parse(evt.data); - if (msg.cmd === 'UPDATE') { - this.onReceivedUpdate(msg.payload); - } - } catch (err) { - console.error(err); - } - }; - this.websocket.onclose = () => { - window.location.reload(); - }; - } - - onReceivedUpdate(update) { - const accounts = Object.assign({}, this.state.accounts); - for (const account of update.updatedAccounts) { - if (accounts[account.id]) { - account.version = accounts[account.id].version + 1; - } else { - account.version = 0; - } - accounts[account.id] = account; - } - - this.setState({ - assignments: update.assignments || this.state.assignments, - activeAccountIds: update.activeAccountIds || this.state.activeAccountIds, - accounts: accounts, - processLoads: update.processLoads, - }) - } - - onFilter() { - this.setState({visibleAccounts: document.getElementById('account-filter').value}); - } - - onGroupChange() { - this.setState({ - groupByProcess: document.getElementById('group-by-process').checked, - }); - } - - render() { - let ids = Object.keys(this.state.accounts); - - switch (this.state.visibleAccounts) { - case AccountFilter.states.errored: - ids = ids.filter((id) => this.state.accounts[id].sync_error) - break; - case AccountFilter.states.notErrored: - ids = ids.filter((id) => !this.state.accounts[id].sync_error) - break; - default: - break; - } - - let content; - if (this.props.collapsed) { - const groupByProcess = ( -
- this.onGroupChange()} - /> - Group Accounts By Process -
- ) - - if (this.state.groupByProcess) { - const accountsById = _.groupBy(this.state.accounts, 'id'); - const processes = []; - - for (const processName of Object.keys(this.state.processLoads)) { - const accounts = [] - - for (const accountId of this.state.processLoads[processName]) { - const account = accountsById[accountId][0]; - accounts.push(( - - )) - } - processes.push(( -
- {accounts} -
- )) - } - content = ( -
- {groupByProcess} -
- {processes} -
-
- ) - } else { - content = ( -
- {groupByProcess} -
- { - ids.sort((a, b) => a / 1 - b / 1).map((id) => - - ) - } -
-
- ) - } - } else { - let count = 0; - content = ( -
- { - ids.sort((a, b) => a / 1 - b / 1).map((id) => - - ) - } -
- ) - } - - return ( -
- - this.onFilter.call(this)} /> - parseInt(id, 10))} /> - {content} -
- ) - } -} - -Root.propTypes = { - collapsed: React.PropTypes.bool, -} - -let collapsed = false; -const collapsedStr = "collapsed"; -const index = window.location.search.indexOf(collapsedStr); -if (index >= 0) { - const value = window.location.search.substring(index + collapsedStr.length + 1); - if (value.startsWith("true")) { - collapsed = true; - } -} - -ReactDOM.render( - , - document.getElementById('root') -); diff --git a/packages/local-sync/src/local-sync-dashboard/public/js/mini-account.jsx b/packages/local-sync/src/local-sync-dashboard/public/js/mini-account.jsx deleted file mode 100644 index aba4b4d22..000000000 --- a/packages/local-sync/src/local-sync-dashboard/public/js/mini-account.jsx +++ /dev/null @@ -1,40 +0,0 @@ -const React = window.React; - -class MiniAccount extends React.Component { - - calculateColor() { - // in milliseconds - const grayAfter = 1000 * 60 * 10; // 10 minutes - const elapsedTime = Date.now() - this.props.account.last_sync_completions[0]; - let opacity = 0; - if (elapsedTime < grayAfter) { - opacity = 1.0 - elapsedTime / grayAfter; - } - - return `rgba(0, 255, 157, ${opacity})`; - } - - render() { - let errorClass; - const style = {}; - if (this.props.account.sync_error) { - errorClass = 'errored'; - } else { - errorClass = ''; - style.backgroundColor = this.calculateColor(); - } - - return ( -
- ) - } -} - -MiniAccount.propTypes = { - account: React.PropTypes.object, -}; - -window.MiniAccount = MiniAccount; diff --git a/packages/local-sync/src/local-sync-dashboard/public/js/process-loads.jsx b/packages/local-sync/src/local-sync-dashboard/public/js/process-loads.jsx deleted file mode 100644 index e8917fd52..000000000 --- a/packages/local-sync/src/local-sync-dashboard/public/js/process-loads.jsx +++ /dev/null @@ -1,37 +0,0 @@ -const React = window.React; - -function ProcessLoads(props) { - let entries; - let sumElem; - if (props.loads == null || Object.keys(props.loads).length === 0) { - entries = "No Data"; - sumElem = ""; - } else { - entries = []; - let sum = 0; - for (const processName of Object.keys(props.loads).sort()) { - const count = props.loads[processName].length; - sum += count; - entries.push( -
- {processName}: {count} accounts -
- ); - } - sumElem =
Total Accounts: {sum}
- } - - return ( -
-
Process Loads
- {entries} - {sumElem} -
- ) -} - -ProcessLoads.propTypes = { - loads: React.PropTypes.object, -} - -window.ProcessLoads = ProcessLoads; diff --git a/packages/local-sync/src/local-sync-dashboard/public/js/react-dom.js b/packages/local-sync/src/local-sync-dashboard/public/js/react-dom.js deleted file mode 100644 index 1cf5496b5..000000000 --- a/packages/local-sync/src/local-sync-dashboard/public/js/react-dom.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * ReactDOM v15.1.0 - * - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ -// Based off https://github.com/ForbesLindesay/umd/blob/master/template.js -;(function(f) { - // CommonJS - if (typeof exports === "object" && typeof module !== "undefined") { - module.exports = f(require('react')); - - // RequireJS - } else if (typeof define === "function" && define.amd) { - define(['react'], f); - - //