diff --git a/nodes/config/ui_page.js b/nodes/config/ui_page.js index e8dc63d99..3dca563f0 100644 --- a/nodes/config/ui_page.js +++ b/nodes/config/ui_page.js @@ -21,12 +21,18 @@ module.exports = function (RED) { node.register = function (group, widgetNode, widgetConfig, widgetEvents) { const ui = RED.nodes.getNode(config.ui) const page = config - ui.register(page, group, widgetNode, widgetConfig, widgetEvents) + if (ui) { + ui.register(page, group, widgetNode, widgetConfig, widgetEvents) + } else { + node.error(`Error registering Widget - ${widgetNode.name || widgetNode.id}. No parent ui-base node found for ui-page node: ${(page.name || page.id)}`) + } } node.deregister = function (group, widgetNode) { const ui = RED.nodes.getNode(config.ui) const page = config - ui.deregister(page, group, widgetNode) + if (ui) { + ui.deregister(page, group, widgetNode) + } } } RED.nodes.registerType('ui-page', UIPageNode) diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico index df36fcfb7..14bf2fb78 100644 Binary files a/ui/public/favicon.ico and b/ui/public/favicon.ico differ diff --git a/ui/src/App.vue b/ui/src/App.vue index ae3597a61..2fb278040 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -1,6 +1,14 @@ @@ -17,7 +25,84 @@ export default { name: 'App', inject: ['$socket'], computed: { - ...mapState('ui', ['dashboards', 'pages', 'widgets']) + ...mapState('ui', ['dashboards', 'pages', 'widgets']), + status: function () { + if (this.dashboards) { + const dashboards = Object.keys(this.dashboards) + if (!dashboards || dashboards.length === 0) { + return { + type: 'info', + msg: 'Please add some Dashboard 2.0 nodes to your flow and re-deploy.' + } + } else if (dashboards.length > 1) { + return { + type: 'warning', + msg: 'We currently do not support multiple ui-base nodes in a single flow.

Please remove all but one (in the "config" menu on the right-side of Node-RED) and re-deploy.

' + } + } else { + const pages = Object.values(this.pages) + console.log(dashboards) + console.log(pages) + let msg = null + for (let i = 0; i < pages.length; i++) { + const page = pages[i] + console.log(page) + if (!dashboards.includes(page.ui)) { + // Catch instances of multiple Dashboards, or pages not bound to a Dashboard we know about + msg = { + type: 'warning', + msg: 'You have at least one ui-page that is mapped to a ui-base that we can\'t find. We currently do not support multiple ui-base nodes in a single flow, so can only import one at a time.

Please remove all but one (in the "config" menu on the right-side of Node-RED) and re-deploy.

' + } + break + } + } + if (msg) { + return msg + } + } + // check pages overlapping with URL + if (this.pages) { + const endpoints = {} + const duplicates = {} + Object.values(this.pages).forEach(page => { + const route = this.dashboards[page.ui].path + page.path + if (endpoints[route]) { + if (!duplicates[route]) { + // our first instance of a duplicate + duplicates[route] = { + pages: [endpoints[route]], + route + } + } + duplicates[route].pages.push(page) + } else { + endpoints[route] = page + } + }) + if (Object.keys(duplicates).length > 0) { + let msg = 'Warning: You have multiple pages configured with the same URL.' + let list = '
' + Object.values(duplicates).forEach((d) => { + d.pages.forEach((p) => { + list += `
${p.name} (path: ${p.path})
` + }) + }) + list += '
' + msg += list + return { + type: 'warning', + msg + } + } + } + return null + } else { + return { + type: 'info', + msg: 'Please add and configure your first Dashboard 2.0 node to get started.' + } + } + } }, created () { this.$socket.on('ui-config', (topic, payload) => { @@ -25,23 +110,26 @@ export default { // loop over pages, add them to vue router Object.values(payload.pages).forEach(page => { - const route = payload.dashboards[page.ui].path + page.path - const routeName = 'Page:' + page.name - console.log('adding route', route) - this.$router?.addRoute({ - path: route, - name: routeName, - component: layouts[page.layout], - meta: { - title: page.name, // the page name - id: page.id, // the pages id - dashboard: page.ui // the dashboard id - to simplify determining which dashboard we're on + // check that the page's bound UI is also in our config + if (payload.dashboards[page.ui]) { + console.log('adding route for page', page) + const route = payload.dashboards[page.ui].path + page.path + const routeName = 'Page:' + page.name + this.$router?.addRoute({ + path: route, + name: routeName, + component: layouts[page.layout], + meta: { + title: page.name, // the page name + id: page.id, // the pages id + dashboard: page.ui // the dashboard id - to simplify determining which dashboard we're on + } + }) + // store data on the "page" object so it's easy for us to map in the navigation drawer + page.route = { + path: route, + name: routeName } - }) - // store data on the "page" object so it's easy for us to map in the navigation drawer - page.route = { - path: route, - name: routeName } }) diff --git a/ui/src/assets/logo.png b/ui/src/assets/logo.png index f3d2503fc..cc75e13ff 100644 Binary files a/ui/src/assets/logo.png and b/ui/src/assets/logo.png differ diff --git a/ui/src/main.js b/ui/src/main.js index 65a7cad41..0ba6f8a8c 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -19,7 +19,7 @@ import * as directives from 'vuetify/directives' const theme = { dark: false, colors: { - background: '#0000ff', + background: '#fff', 'group-background': '#ffffff', primary: '#0000ff', accent: '#ff6b99', diff --git a/ui/src/store/ui.js b/ui/src/store/ui.js index c8901d5a5..166342373 100644 --- a/ui/src/store/ui.js +++ b/ui/src/store/ui.js @@ -4,6 +4,7 @@ // initial state const state = () => ({ + dashboards: null, pages: null, groups: null, themes: null, @@ -12,6 +13,9 @@ const state = () => ({ // getters const getters = { + dashboards (state) { + return state.dashboards + }, pages (state) { return state.pages }, @@ -74,6 +78,12 @@ const getters = { } const mutations = { + dashboards (state, dashboards) { + state.dashboards = { + ...state.dashboards, + ...dashboards + } + }, pages (state, pages) { state.pages = pages }, diff --git a/ui/src/stylesheets/common.css b/ui/src/stylesheets/common.css index d2e20df04..56a3d4f74 100644 --- a/ui/src/stylesheets/common.css +++ b/ui/src/stylesheets/common.css @@ -29,6 +29,55 @@ main { padding: var(--nrdb-main-padding); } +/** +* Placeholder +*/ + +.nrdb-placeholder-container { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: #eee; +} + +.nrdb-placeholder { + max-width: 480px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #222; + text-align: center; + padding: 1.5rem 2rem; + border-radius: 6px; + border: 1px solid #d1d1d1; + background-color: #fff; +} + +.nrdb-placeholder img { + width: 150px; +} + +.nrdb-placeholder p { + margin: 0.5rem 0 0; +} + +.nrdb-placeholder .status-info { + font-weight: 600; +} + +.nrdb-placeholder .status-warning { + color: #bb2020; + font-weight: 600; +} + +.nrdb-placeholder .status-duplicates { + font-weight: 400; + color: #222; + margin-top: 0.5rem; +} + /** * Common Widget Styling */