| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| var hg = require('mercury'); |
| var h = require('mercury').h; |
| var request = require('superagent'); |
| var cookie = require('cookie'); |
| |
| var AppStateMgr = require('./appstate-manager'); |
| var instanceViewComponent = require('./components/instance-view'); |
| var pageHeaderComponent = require('./components/page-header'); |
| var settingsPanelComponent = require('./components/settings'); |
| var summaryTableComponent = require('./components/summary-table'); |
| |
| /** |
| * A variable to store the most update-to-date dashboard data. |
| * |
| * We automatically retrive data from backend server every minute. |
| */ |
| var curData; |
| |
| // Initializes the app state manager and sets appStateChanged function as the |
| // callback for app state changes. |
| AppStateMgr.init(appStateChanged); |
| |
| // Ask mercury to listen to mousemove/mouseout/mouseover events. |
| hg.Delegator().listenTo('mousemove'); |
| hg.Delegator().listenTo('mouseout'); |
| hg.Delegator().listenTo('mouseover'); |
| |
| // Parse cookie. |
| var cookies = cookie.parse(document.cookie); |
| |
| /** Top level state. */ |
| var state = hg.state({ |
| // The header panel at the top of the page. |
| pageHeader: pageHeaderComponent({ |
| collectionTimestamp: -1, |
| oncallIds: ['_unknown', '_unknown'], |
| loadingData: false, |
| hasLoadingFailure: false |
| }), |
| |
| components: hg.varhash({ |
| // The summary table showing data on the "global" and "zone" level. |
| summaryTable: summaryTableComponent(null), |
| |
| // The view showing data on the "instance" level. |
| instanceView: instanceViewComponent(null), |
| }), |
| |
| // Whether to show settings panel. |
| showSettingsPanel: hg.value(false), |
| |
| // Settings stored in cookies. |
| settings: hg.varhash({ |
| darkTheme: hg.value(cookies.darkTheme === 'true') |
| }), |
| |
| channels: { |
| changeTheme: changeTheme, |
| clickOnSettingsGear: clickOnSettingsGear, |
| closeSettingsPanel: closeSettingsPanel |
| } |
| }); |
| |
| /** Callback when user clicks on the settings gear. */ |
| function clickOnSettingsGear(state) { |
| state.showSettingsPanel.set(true); |
| } |
| |
| /** Callback when user closes the settings panel. */ |
| function closeSettingsPanel(state) { |
| state.showSettingsPanel.set(false); |
| } |
| |
| /** Callback when user changes theme. */ |
| function changeTheme(state, data) { |
| document.cookie = cookie.serialize('darkTheme', data.darkTheme, { |
| maxAge: '315360000' // ten years. |
| }); |
| state.settings.darkTheme.set(data.darkTheme); |
| } |
| |
| /** The main render function. */ |
| var render = function(state) { |
| var mainContent = [ |
| pageHeaderComponent.render(state.pageHeader), |
| ]; |
| if (state.components.summaryTable) { |
| mainContent.push( |
| h('div.main-container', |
| summaryTableComponent.render(state.components.summaryTable))); |
| } |
| if (state.components.instanceView) { |
| mainContent.push( |
| h('div.main-container', |
| instanceViewComponent.render(state.components.instanceView))); |
| } |
| mainContent.push(h('div.settings-gear', { |
| 'ev-click': hg.send(state.channels.clickOnSettingsGear) |
| })); |
| if (state.showSettingsPanel) { |
| mainContent.push(hg.partial(settingsPanelComponent.render, state)); |
| } |
| |
| var className = state.settings.darkTheme ? 'main.darkTheme' : 'main'; |
| return h('div.' + className, mainContent); |
| }; |
| |
| /** Loads dashboard data from backend server. */ |
| function loadData() { |
| // Update data loading indicator. |
| state.pageHeader.loadingData.set(true); |
| |
| // Get json dashboard data from "/data" endpoint. |
| var dataEndpoint = window.location.origin + window.location.pathname + 'data'; |
| request |
| .get(dataEndpoint) |
| .accept('json') |
| .timeout(30000) |
| .end(function(err, res) { |
| if (!res.ok || err) { |
| state.pageHeader.hasLoadingFailure.set(true); |
| } else { |
| state.pageHeader.hasLoadingFailure.set(false); |
| processData(res.body); |
| } |
| }); |
| } |
| |
| /** |
| * Callback function of loadData. |
| * @param {Object} newData - Newly loaded data in json format. |
| */ |
| function processData(newData) { |
| // Update components. |
| curData = newData; |
| updateComponents(AppStateMgr.getCurState()); |
| |
| // Update the data loading indicator. |
| state.pageHeader.loadingData.set(false); |
| } |
| |
| /** |
| * Callback function for app state changes. |
| * @param {Object} curAppState - App's current state object. |
| */ |
| function appStateChanged(curAppState) { |
| if (!state) { |
| return; |
| } |
| updateComponents(curAppState); |
| } |
| |
| /** |
| * Updates all page components. |
| * @param {Object} curAppState - App's current state object. |
| */ |
| function updateComponents(curAppState) { |
| // Update page header. |
| state.pageHeader.collectionTimestamp.set(curData.CollectionTimestamp); |
| state.pageHeader.oncallIds.set(curData.OncallIDs.split(',')); |
| |
| // Update summary table when the current view level is NOT "instance". |
| var summaryTableData = null; |
| if (curAppState.level !== 'instance') { |
| summaryTableData = summaryTableComponent({ |
| data: curData |
| }); |
| } |
| state.components.put('summaryTable', summaryTableData); |
| |
| // Update instance view when the current view level is "instance". |
| var instanceViewData = null; |
| if (curAppState.level === 'instance') { |
| var instance = curAppState.instanceLevelInstance; |
| instanceViewData = instanceViewComponent({ |
| collectionTimestamp: curData.CollectionTimestamp, |
| data: curData.Zones[curAppState.instanceLevelZone].Instances[instance], |
| appState: curAppState |
| }); |
| } |
| state.components.put('instanceView' ,instanceViewData); |
| } |
| |
| // Add an event handler for closing settings panel when esc key is pressed. |
| document.onkeydown = function(evt) { |
| if (evt.keyCode === 27 && state.showSettingsPanel()) { |
| state.showSettingsPanel.set(false); |
| } |
| }; |
| |
| hg.app(document.body, state, render); |
| loadData(); |
| setInterval(loadData, 60000); |