Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow App Launch When Offline or Unable to Connect to Node-RED #1455

Merged
merged 13 commits into from
Nov 12, 2024
49 changes: 48 additions & 1 deletion ui/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
<template>
<v-app>
<div v-if="loading" class="nrdb-splash-loading">
<div v-if="error" class="nrdb-placeholder-container">
<div class="nrdb-placeholder">
<img src="./assets/logo.png">
<h1>Node-RED Dashboard 2.0</h1>
<img src="./assets/disconnected.png">
<!-- eslint-disable-next-line vue/no-v-html -->
<p :class="'status-warning'" v-html="error.message" />
<br>
<h4>What you can try:</h4>
<div v-if="error.type === 'server unreachable'" style="border: none" class="nrdb-placeholder">
<v-btn rounded class="nrdb-placeholder-btns" @click="reloadApp">
Reload App
</v-btn>
<br>
<v-btn rounded class="nrdb-placeholder-btns" @click="contactAdmin">
Contact Admin
</v-btn>
</div>
<div v-else-if="error.type === 'no internet'" style="border: none" class="nrdb-placeholder">
<v-btn rounded class="nrdb-placeholder-btns" @click="reloadApp">
Reload App
</v-btn>
</div>
</div>
</div>
<div v-else-if="loading" class="nrdb-splash-loading">
<DashboardLoading />
Loading...
</div>
Expand Down Expand Up @@ -44,6 +69,8 @@ export default {
computed: {
...mapState('ui', ['dashboards', 'pages', 'widgets']),
...mapState('setup', ['setup']),
...mapState('setup', ['error']),
cgjgh marked this conversation as resolved.
Show resolved Hide resolved

status: function () {
if (this.dashboards) {
const dashboards = Object.keys(this.dashboards)
Expand Down Expand Up @@ -223,6 +250,15 @@ export default {
this.$store.commit('ui/groups', payload.groups)
this.$store.commit('ui/widgets', payload.widgets)
this.$store.commit('ui/themes', payload.themes)

for (const key in payload.themes) {
// check if "Default Theme" theme exists
if (payload.themes[key].name === 'Default Theme') {
// store the default theme in local storage for use when disconnected from Node-RED
localStorage.setItem('defaultNRDBTheme', JSON.stringify(payload.themes[key]))
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
break
}
}
})
},
methods: {
Expand All @@ -233,6 +269,17 @@ export default {
this.$router.push({
name
})
},
reloadApp () {
location.reload()
},
contactAdmin () {
const email = ''
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
const subject = 'Node-RED Dashboard Error'
const body = 'Insert issue here...'
const mailtoLink = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`
window.open(mailtoLink, '_blank')
console.log('Mail app opening...')
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down
Binary file added ui/src/assets/disconnected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 58 additions & 11 deletions ui/src/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,32 @@ import './stylesheets/common.css'
import store from './store/index.mjs'
import { useDataTracker } from './widgets/data-tracker.mjs' // eslint-disable-line import/order

// set a base theme on which we will add our custom NR-defined theme
// Retrieve the "Default" theme from cache
function retrieveDefaultThemeFromCache () {
const cachedTheme = localStorage.getItem('defaultNRDBTheme')
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
if (cachedTheme) {
console.log('Found cached theme in localStorage')
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
return JSON.parse(cachedTheme)
}
console.log('No cached theme found in localStorage')
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
return null
}

const defaultTheme = retrieveDefaultThemeFromCache()

// set a base theme on which we will add our custom NR-defined theme (initially set to the default theme if exists in cache)
const theme = {
dark: false,
colors: {
background: '#fff',
'navigation-background': '#ffffff',
'group-background': '#ffffff',
primary: '#0000ff',
background: defaultTheme ? defaultTheme.colors.bgPage : '#fff',
'navigation-background': defaultTheme ? defaultTheme.colors.surface : '#ffffff',
'group-background': defaultTheme ? defaultTheme.colors.groupBg : '#ffffff',
'group-outline': defaultTheme ? defaultTheme.colors.groupOutline : '#d1d1d1',
primary: defaultTheme ? defaultTheme.colors.primary : '#0094CE',
accent: '#ff6b99',
secondary: '#26ff8c',
success: '#a5d64c',
surface: '#ffffff',
surface: defaultTheme ? defaultTheme.colors.surface : '#ffffff',
info: '#ff53d0',
warning: '#ff8e00',
error: '#ff5252'
Expand Down Expand Up @@ -97,7 +111,7 @@ fetch('_setup')
return
case !response.ok:
console.error('Failed to fetch setup data:', response)
return
throw new Error('Failed to fetch setup data:', response)
case host.origin !== new URL(response.url).origin: {
console.log('Following redirect:', response.url)
const url = new URL(response.url)
Expand Down Expand Up @@ -257,10 +271,43 @@ fetch('_setup')
app.mount('#app')
})
.catch((err) => {
joepavitt marked this conversation as resolved.
Show resolved Hide resolved
if (err instanceof TypeError && err.message === 'Failed to fetch') {
forcePageReload(err)
console.error('An error occurred:', err)

cgjgh marked this conversation as resolved.
Show resolved Hide resolved
function handleOnline () {
console.log('Back online, reloading page...')
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
// remove the online event listener and reload the page
window.removeEventListener('online', handleOnline)
location.reload()
}

let error = {}
if (navigator.onLine) {
error = { type: 'server unreachable', message: 'There was an error loading the Dashboard.' }
// Add timer to reload the page every 20 seconds
setInterval(() => {
console.log('Reloading page...')
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
location.reload()
}, 20000)
} else {
// handle general errors here
console.error('An error occurred:', err)
error = { type: 'no internet', message: 'Your device appears to be offline.' }
// Add event listener
window.addEventListener('online', handleOnline)
}

store.commit('setup/setError', error) // pass the error to the Vuex store

// load minimal VueJS app to display error message and options to user
window.Vue = Vue
window.vuex = vuex
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
const app = Vue.createApp(App)
.use(store)
.use(vuetify)
.use(router)

const head = createHead()
app.use(head)
app.mixin(VueHeadMixin)

// mount the VueJS app into <div id="app"></div> in /ui/public/index.html
app.mount('#app')
})
7 changes: 6 additions & 1 deletion ui/src/store/setup.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// initial state
const state = () => ({
setup: null
setup: null,
error: null
})

const mutations = {
set (state, setup) {
console.log('setup', setup)
state.setup = setup
},
setError (state, error) {
console.log('error', error)
cgjgh marked this conversation as resolved.
Show resolved Hide resolved
state.error = error
}
}

Expand Down
13 changes: 7 additions & 6 deletions ui/src/stylesheets/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ main {
display: flex;
align-items: center;
justify-content: center;
background-color: #eee;
background-color: rgb(var(--v-theme-background, 238, 238, 238)); /* Fallback to light gray (#eee) */
}

.nrdb-placeholder {
Expand All @@ -92,12 +92,13 @@ main {
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;
margin: 1rem;
border-radius: var(--group-border-radius, 6px);
border: 1px solid rgb(var(--v-theme-group-outline, 209, 209, 209)); /* Fallback to light gray (#d1d1d1) */
color: rgb(var(--v-theme-on-group-background, 34, 34, 34)); /* Fallback to dark gray (#222) */
background-color: rgb(var(--v-theme-group-background, 255, 255, 255)); /* Fallback to white (#fff) */
}

.nrdb-placeholder img {
Expand All @@ -119,7 +120,7 @@ main {

.nrdb-placeholder .status-duplicates {
font-weight: 400;
color: #222;
color: rgb(var(--v-theme-on-surface, 34, 34, 34)); /* Fallback to dark gray (#222) */
margin-top: 0.5rem;
}

Expand Down
Loading