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
38 changes: 36 additions & 2 deletions ui/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
<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 @click="reloadApp">
Reload App
</v-btn>
</div>
<div v-else-if="error.type === 'no internet'" style="border: none" class="nrdb-placeholder">
<v-btn rounded @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 @@ -43,7 +64,8 @@ export default {
},
computed: {
...mapState('ui', ['dashboards', 'pages', 'widgets']),
...mapState('setup', ['setup']),
...mapState('setup', ['setup', 'error']),

status: function () {
if (this.dashboards) {
const dashboards = Object.keys(this.dashboards)
Expand Down Expand Up @@ -223,6 +245,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('ndrb-theme-default', JSON.stringify(payload.themes[key]))
break
}
}
})
},
methods: {
Expand All @@ -233,6 +264,9 @@ export default {
this.$router.push({
name
})
},
reloadApp () {
location.reload()
}
}
}
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.
61 changes: 50 additions & 11 deletions ui/src/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,30 @@ 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('ndrb-theme-default')
if (cachedTheme) {
return JSON.parse(cachedTheme)
}
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 +109,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 +269,37 @@ 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)
function handleOnline () {
// remove the online event listener and reload the page
window.removeEventListener('online', handleOnline)
location.reload()
}

let error = {}
if (navigator.onLine) {
error = { error: err, type: 'server unreachable', message: 'There was an error loading the Dashboard.' }
// Add timer to reload the page every 20 seconds
setInterval(() => {
location.reload()
}, 20000)
} else {
// handle general errors here
console.error('An error occurred:', err)
error = { error: err, 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
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')
})
6 changes: 5 additions & 1 deletion ui/src/store/setup.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// 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) {
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