From a83a7076e88d22ddcad52e76b1015a9e877679d3 Mon Sep 17 00:00:00 2001 From: morsh Date: Thu, 27 Apr 2017 18:49:07 -0700 Subject: [PATCH] enabling multi dashboards API+client --- server/routes/api.js | 144 +++++++++++++++++- src/actions/ConfigurationsActions.ts | 27 +++- .../ConfigDashboard/ConfigDashboard.tsx | 2 +- src/components/Navbar/index.tsx | 45 ++++-- src/pages/Config.tsx | 2 +- src/pages/Dashboard.tsx | 2 +- src/routes.tsx | 1 + src/stores/ConfigurationsStore.ts | 30 +++- 8 files changed, 229 insertions(+), 24 deletions(-) diff --git a/server/routes/api.js b/server/routes/api.js index 00a60d4..515b898 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -44,7 +44,132 @@ router.post('/dashboard.js', (req, res) => { }) }); -function isAuthoeizedToSetup(req) { + +router.get('/dashboards', (req, res) => { + + let privateDashboard = path.join(__dirname, '..', 'dashboards'); + let preconfDashboard = path.join(__dirname, '..', 'dashboards', 'preconfigured'); + + let script = ''; + let files = fs.readdirSync(privateDashboard); + if (files && files.length) { + files.forEach((fileName) => { + let filePath = path.join(privateDashboard, fileName); + let stats = fs.statSync(filePath); + if (stats.isFile()) { + let content = fs.readFileSync(filePath, 'utf8'); + + // Ensuing this dashboard is loaded into the dashboards array on the page + script += ` + (function (window) { + var dashboard = (function () { + ${content} + })(); + window.dashboardDefinitions = window.dashboardDefinitions || []; + window.dashboardDefinitions.push(dashboard); + })(window); + `; + } + }); + } + + let templates = fs.readdirSync(preconfDashboard); + if (templates && templates.length) { + templates.forEach((fileName) => { + let filePath = path.join(preconfDashboard, fileName); + let stats = fs.statSync(filePath); + if (stats.isFile()) { + let content = fs.readFileSync(filePath, 'utf8'); + + // Ensuing this dashboard is loaded into the dashboards array on the page + script += ` + (function (window) { + var dashboardTemplate = (function () { + ${content} + })(); + window.dashboardTemplates = window.dashboardTemplates || []; + window.dashboardTemplates.push(dashboardTemplate); + })(window); + `; + } + }); + } + + res.send(script); +}); + +router.get('/dashboards/:id', (req, res) => { + + let dashboardId = req.params.id; + let privateDashboard = path.join(__dirname, '..', 'dashboards'); + let preconfDashboard = path.join(__dirname, '..', 'dashboards', 'preconfigured'); + + let script = ''; + let files = fs.readdirSync(privateDashboard) || []; + + // Make sure the array only contains files + files = files.filter(fileName => fs.statSync(path.join(privateDashboard, fileName)).isFile()); + + if (files.length) { + + let dashboardFile = null; + let dashboardIndex = parseInt(dashboardId); + if (!isNaN(dashboardIndex) && files.length > dashboardIndex) { + dashboardFile = files[dashboardIndex]; + } + + if (!dashboardFile) { + files.forEach(fileName => { + let filePath = path.join(privateDashboard, fileName); + + let stats = fs.statSync(filePath); + if (stats.isFile()) { + let dashboard = getJSONFromScript(filePath); + if (dashboard.url && dashboard.url.toLowerCase() === dashboardId.toLowerCase()) { + dashboardFile = fileName; + } + } + }) + } + + if (dashboardFile) { + let filePath = path.join(privateDashboard, dashboardFile); + let stats = fs.statSync(filePath); + if (stats.isFile()) { + let content = fs.readFileSync(filePath, 'utf8'); + + // Ensuing this dashboard is loaded into the dashboards array on the page + script += ` + (function (window) { + var dashboard = (function () { + ${content} + })(); + window.dashboard = dashboard || null; + })(window); + `; + } + } + } + + res.send(script); +}); + +router.post('/dashboards/:id', (req, res) => { + let dashboardName = req.params.dashboard; + var content = (req.body && req.body.script) || ''; + console.dir(content); + + fs.writeFile(path.join(__dirname, '..', 'dashboards', 'dashboard.private.js'), content, err => { + if (err) { + console.error(err); + return res.end(err); + } + + res.end(content); + }) +}); + +function isAuthorizedToSetup(req) { if (!fs.existsSync(privateSetupPath)) { return true; } let configString = fs.readFileSync(privateSetupPath, 'utf8'); @@ -61,7 +186,7 @@ function isAuthoeizedToSetup(req) { router.get('/setup', (req, res) => { - if (!isAuthoeizedToSetup(req)) { + if (!isAuthorizedToSetup(req)) { return res.send({ error: new Error('User is not authorized to setup') }); } @@ -76,7 +201,7 @@ router.get('/setup', (req, res) => { router.post('/setup', (req, res) => { - if (!isAuthoeizedToSetup(req)) { + if (!isAuthorizedToSetup(req)) { return res.send({ error: new Error('User is not authorized to setup') }); } @@ -95,4 +220,17 @@ router.post('/setup', (req, res) => { module.exports = { router +} + +function getJSONFromScript(filePath) { + if (!fs.existsSync(filePath)) { return {}; } + + let jsonScript = {}; + let stats = fs.statSync(filePath); + if (stats.isFile()) { + let content = fs.readFileSync(filePath, 'utf8'); + eval('jsonScript = (function () { ' + content + ' })();'); + } + + return jsonScript; } \ No newline at end of file diff --git a/src/actions/ConfigurationsActions.ts b/src/actions/ConfigurationsActions.ts index 50e00c7..d6da5ca 100644 --- a/src/actions/ConfigurationsActions.ts +++ b/src/actions/ConfigurationsActions.ts @@ -3,6 +3,7 @@ import * as request from 'xhr-request'; interface IConfigurationsActions { loadConfiguration(): any; + loadDashboard(id: string): any; saveConfiguration(dashboard: IDashboardConfig): any; failure(error: any): void; } @@ -14,17 +15,33 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc loadConfiguration() { - return (dispatcher: (dashboard: IDashboardConfig) => void) => { + return (dispatcher: (result: { dashboards: IDashboardConfig[], templates: IDashboardConfig[] }) => void) => { - this.getScript('/api/dashboard.js', () => { - let dashboards: IDashboardConfig[] = (window as any)['dashboards']; + this.getScript('/api/dashboards', () => { + let dashboards: IDashboardConfig[] = (window as any)['dashboardDefinitions']; + let templates: IDashboardConfig[] = (window as any)['dashboardTemplates']; if (!dashboards || !dashboards.length) { return this.failure(new Error('Could not load configuration')); } - let dashboard = dashboards[0]; - return dispatcher(dashboard); + return dispatcher({ dashboards, templates }); + }); + }; + } + + loadDashboard(id: string) { + + return (dispatcher: (result: { dashboard: IDashboardConfig }) => void) => { + + this.getScript('/api/dashboards/' + id, () => { + let dashboard: IDashboardConfig = (window as any)['dashboard']; + + if (!dashboard) { + return this.failure(new Error('Could not load configuration for dashboard ' + id)); + } + + return dispatcher({ dashboard }); }); }; } diff --git a/src/components/ConfigDashboard/ConfigDashboard.tsx b/src/components/ConfigDashboard/ConfigDashboard.tsx index 403243e..6657513 100644 --- a/src/components/ConfigDashboard/ConfigDashboard.tsx +++ b/src/components/ConfigDashboard/ConfigDashboard.tsx @@ -35,7 +35,7 @@ export default class ConfigDashboard extends React.Component { this.setState(state); }); AccountActions.updateAccount(); + + ConfigurationsStore.listen((state) => { + this.setState({ + dashboards: state.dashboards + }); + }); } render() { + let { dashboards } = this.state; let { children, title } = this.props; let pathname = '/'; try { pathname = window.location.pathname; } catch (e) { } @@ -81,17 +90,6 @@ export default class Navbar extends React.Component { dashboard} - tileClassName="md-list-tile--mini" - primaryText={'Dashboard'} - /> - ), - ( - settings} @@ -101,6 +99,30 @@ export default class Navbar extends React.Component { ) ]; + (dashboards || []).forEach((dashboard, index) => { + let name = dashboard.name || null; + let url = '/dashboard/' + (dashboard.url || index.toString()); + let active = pathname === url; + if (!title && active && name) { + title = name; + } + + navigationItems.push( + ( + {dashboard.icon || 'dashboard'}} + tileClassName="md-list-tile--mini" + primaryText={name || 'Dashboard'} + /> + ) + ) + }); + + let toolbarActions = this.state.account ? : @@ -129,6 +151,7 @@ export default class Navbar extends React.Component { break; default: + title = 'Ibex Dashboard'; break; } diff --git a/src/pages/Config.tsx b/src/pages/Config.tsx index d1a9740..01f483b 100644 --- a/src/pages/Config.tsx +++ b/src/pages/Config.tsx @@ -22,7 +22,7 @@ export default class Config extends React.Component { constructor(props: any) { super(props); - ConfigurationsActions.loadConfiguration(); + //ConfigurationsActions.loadConfiguration(); } componentDidMount() { diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index f9504af..b84c935 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -23,7 +23,7 @@ export default class Dashboard extends React.Component { constructor(props: any) { super(props); - ConfigurationsActions.loadConfiguration(); + // ConfigurationsActions.loadConfiguration(); } componentDidMount() { diff --git a/src/routes.tsx b/src/routes.tsx index bb78861..f5948fd 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -16,6 +16,7 @@ export default ( + diff --git a/src/stores/ConfigurationsStore.ts b/src/stores/ConfigurationsStore.ts index a381d60..60bc0f8 100644 --- a/src/stores/ConfigurationsStore.ts +++ b/src/stores/ConfigurationsStore.ts @@ -7,6 +7,8 @@ import configurationActions from '../actions/ConfigurationsActions'; interface IConfigurationsStoreState { dashboard: IDashboardConfig; + dashboards: IDashboardConfig[]; + templates: IDashboardConfig[]; connections: IDictionary; connectionsMissing: boolean; loaded: boolean; @@ -15,6 +17,8 @@ interface IConfigurationsStoreState { class ConfigurationsStore extends AbstractStoreModel implements IConfigurationsStoreState { dashboard: IDashboardConfig; + dashboards: IDashboardConfig[]; + templates: IDashboardConfig[]; connections: IDictionary; connectionsMissing: boolean; loaded: boolean; @@ -23,16 +27,38 @@ class ConfigurationsStore extends AbstractStoreModel super(); this.dashboard = null; + this.dashboards = null; + this.templates = null; this.connections = {}; this.connectionsMissing = false; this.loaded = false; this.bindListeners({ - loadConfiguration: configurationActions.loadConfiguration + loadConfiguration: configurationActions.loadConfiguration, + loadDashboard: configurationActions.loadDashboard }); + + configurationActions.loadConfiguration(); + + let pathname = window.location.pathname; + if (pathname === '/dashboard') { + configurationActions.loadDashboard("0"); + } + + if (pathname.startsWith('/dashboard/')) { + let dashboardId = pathname.substring('/dashboard/'.length); + configurationActions.loadDashboard(dashboardId); + } } - loadConfiguration(dashboard: IDashboardConfig) { + loadConfiguration(result: { dashboards: IDashboardConfig[], templates: IDashboardConfig[] }) { + let { dashboards,templates } = result; + this.dashboards = dashboards; + this.templates = templates; + } + + loadDashboard(result: { dashboard: IDashboardConfig }) { + let { dashboard } = result; this.dashboard = dashboard; if (this.dashboard && !this.loaded) {