Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cypress/fixtures/flows/dashboard-forms.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"info": "",
"env": []
},
{
{
"id": "dashboard-ui-base",
"type": "ui-base",
"name": "UI Name",
Expand Down Expand Up @@ -128,7 +128,7 @@
"label": "",
"order": 1,
"width": "3",
"height": "1",
"height": "2",
"options": [
{
"label": "Name",
Expand Down
54 changes: 28 additions & 26 deletions cypress/tests/widgets/form.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ describe('Node-RED Dashboard 2.0 - Forms', () => {
})
it('permits users to set default values via msg.payload', () => {
// check that the form is empty
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-name0"]').find('input[type="text"]').should('have.value', '')
cy.clickAndWait(cy.get('button').contains('Set Defaults'))
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-name0"]').find('input[type="text"]').should('have.value', 'Overridden Default Name')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-name0"] input[type="text"]').should('have.value', '')
cy.clickAndWait(cy.get('button').contains('Set Defaults'), 200)
cy.clickAndWait(cy.get('button').contains('Set Defaults'), 200)
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-name0"] input[type="text"]').should('have.value', 'Overridden Default Name')
})

it('can have their content defined by msg.ui_update.options', () => {
Expand All @@ -52,17 +53,17 @@ describe('Node-RED Dashboard 2.0 - Forms', () => {
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-dob"]').should('not.exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-tob"]').should('not.exist')

cy.clickAndWait(cy.get('button').contains('Override Form Options'))
cy.clickAndWait(cy.get('button').contains('Override Form Options'), 200)

cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-name"]').find('input[type="text"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-multiline"]').find('textarea').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-password"]').find('input[type="password"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-email"]').find('input[type="email"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-age"]').find('input[type="number"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-newsletter"]').find('input[type="checkbox"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-notifications"]').find('input[type="checkbox"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-dob"]').find('input[type="date"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-tob"]').find('input[type="time"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-name"] input[type="text"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-multiline"] textarea').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-password"] input[type="password"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-email"] input[type="email"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-age"] input[type="number"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-newsletter"] input[type="checkbox"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-notifications"] input[type="checkbox"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-dob"] input[type="date"]').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-form-dynamic').find('[data-form="form-row-tob"] input[type="time"]').should('exist')
})

const payloadElId = '#nrdb-ui-widget-74cbdedad6183c40'
Expand All @@ -71,13 +72,13 @@ describe('Node-RED Dashboard 2.0 - Forms', () => {
it('Delivers topic from msg.topic', () => {
const formElId = '#nrdb-ui-widget-3cd8df20415c4c04'
// wait for the input to be actionable
cy.get(formElId).find('[data-form="form-row-name1"]').find('input[type="text"]').should('not.be.disabled')
cy.get(formElId).find('[data-form="form-row-name1"] input[type="text"]').should('not.be.disabled')
// enter a value into the text input field
cy.get(formElId).find('[data-form="form-row-name1"]').find('input[type="text"]').clear()
cy.get(formElId).find('[data-form="form-row-name1"]').find('input[type="text"]').should('have.value', '')
cy.get(formElId).find('[data-form="form-row-name1"]').find('input[type="text"]').type('payload for msg.topic test')
cy.get(formElId).find('[data-form="form-row-name1"] input[type="text"]').clear()
cy.get(formElId).find('[data-form="form-row-name1"] input[type="text"]').should('have.value', '')
cy.get(formElId).find('[data-form="form-row-name1"] input[type="text"]').type('payload for msg.topic test')
// submit the form
cy.clickAndWait(cy.get(formElId).find('[data-action="form-submit"]'))
cy.clickAndWait(cy.get(formElId).find('[data-action="form-submit"]'), 200)
// check the output for the topic
cy.get(payloadElId).find('.nrdb-ui-text-value').contains('{"name1":"payload for msg.topic test"}')
cy.get(topicElId).find('.nrdb-ui-text-value').contains('topic from msg.topic')
Expand All @@ -86,13 +87,14 @@ describe('Node-RED Dashboard 2.0 - Forms', () => {
it('Delivers topic from flow.f1', () => {
const formElId = '#nrdb-ui-widget-ddb5a30c677e5e0b'
// wait for the input to be actionable
cy.get(formElId).find('[data-form="form-row-name2"]').find('input[type="text"]').should('not.be.disabled')
cy.get(formElId).find('[data-form="form-row-name2"] input[type="text"]').should('not.be.disabled')
// enter a value into the text input field
cy.get(formElId).find('[data-form="form-row-name2"]').find('input[type="text"]').clear()
cy.get(formElId).find('[data-form="form-row-name2"]').find('input[type="text"]').should('have.value', '')
cy.get(formElId).find('[data-form="form-row-name2"]').find('input[type="text"]').type('flow.f1 test')
cy.get(formElId).find('[data-form="form-row-name2"] input[type="text"]').clear()
cy.get(formElId).find('[data-form="form-row-name2"] input[type="text"]').should('have.value', '')
cy.get(formElId).find('[data-form="form-row-name2"] input[type="text"]').should('not.be.disabled')
cy.get(formElId).find('[data-form="form-row-name2"] input[type="text"]').type('flow.f1 test')
// submit the form
cy.clickAndWait(cy.get(formElId).find('[data-action="form-submit"]'))
cy.clickAndWait(cy.get(formElId).find('[data-action="form-submit"]'), 200)
// check the output for the topic
cy.get(payloadElId).find('.nrdb-ui-text-value').contains('{"name2":"flow.f1 test"}')
cy.get(topicElId).find('.nrdb-ui-text-value').contains('topic from flow.f1')
Expand All @@ -101,11 +103,11 @@ describe('Node-RED Dashboard 2.0 - Forms', () => {
it('Delivers topic from global.g1', () => {
const formElId = '#nrdb-ui-widget-cf0774a3c2e9edd4'
// wait for the input to be actionable
cy.get(formElId).find('[data-form="form-row-name3"]').find('input[type="text"]').should('not.be.disabled')
cy.get(formElId).find('[data-form="form-row-name3"] input[type="text"]').should('not.be.disabled')
// enter a value into the text input field
cy.get(formElId).find('[data-form="form-row-name3"]').find('input[type="text"]').type('global.g1 test')
cy.get(formElId).find('[data-form="form-row-name3"] input[type="text"]').type('global.g1 test')
// submit the form
cy.clickAndWait(cy.get(formElId).find('[data-action="form-submit"]'))
cy.clickAndWait(cy.get(formElId).find('[data-action="form-submit"]'), 200)
// check the output for the topic
cy.get(payloadElId).find('.nrdb-ui-text-value').contains('{"name3":"global.g1 test"}')
cy.get(topicElId).find('.nrdb-ui-text-value').contains('topic from global.g1')
Expand Down
5 changes: 3 additions & 2 deletions nodes/config/ui_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ module.exports = function (RED) {
// - Redirects (301) URLs ending with a trailing slash to their non-trailing equivalent.
// - Preserves the query string.
// - Collapses any redundant slashes (e.g., "//" becomes "/").
uiShared.app.use((req, res, next) => {
uiShared.app.use(config.path + '/', (req, res, next) => {
Comment thread
Steve-Mcl marked this conversation as resolved.
// Skip if not a GET or HEAD request
if (!(req.method === 'GET' || req.method === 'HEAD')) {
next()
Expand All @@ -153,7 +153,8 @@ module.exports = function (RED) {
if (req.path.slice(-1) === '/' && req.path.length > 1) {
const query = req.url.slice(req.path.length) // get the query string (if any)
const safePath = req.path.slice(0, -1).replace(/\/+/g, '/') // remove trailing slash and make any double slashes single slashes
res.redirect(301, safePath + query) // redirect to the safe path
const redirectUrl = config.path + safePath + query // construct the new URL
res.redirect(301, redirectUrl) // redirect to the new URL
} else {
next()
}
Expand Down
20 changes: 15 additions & 5 deletions ui/src/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@ precacheAndRoute(self.__WB_MANIFEST)
cleanupOutdatedCaches()

/** @type {RegExp[] | undefined} */
let allowlist
// in dev mode, we disable precaching to avoid caching issues
if (import.meta.env.DEV) { allowlist = [/^\/$/] }
const denylist = []

// to allow work offline
// in dev mode, do not precache anything
if (import.meta.env.DEV) {
// don't precache anything
console.log('Development mode, not pre-caching anything')
denylist.push(/.*/)
} else {
// don't precache anything where the urls pathname ends with a slash (including times when the url has a query string)
// this permits the request to be handled by the server which will do a redirect as required
const configPath = self.location.pathname.split('/')[1]
denylist.push(new RegExp(`/${configPath}/[^?]*/(\\?.*)*$`))
}

// to allow work offline for allowed routes only
registerRoute(new NavigationRoute(
createHandlerBoundToURL('index.html'),
{ allowlist }
{ denylist }
))

self.skipWaiting()
Expand Down