node-red-dashboard icon indicating copy to clipboard operation
node-red-dashboard copied to clipboard

The "Layout Editor" button for Dashboard Pages (when clicked) redirects to the wrong URL if altered `RED.settings.httpNodeRoot`

Open f0lg0 opened this issue 1 month ago • 3 comments

Current Behavior

The onclick event when opening the layout editor for a given UI page in nodes/config/ui_base.html

async function showDashboardWysiwygEditor (pageId, baseId) {
    // call to httpadmin endpoint requesting layout editor mode for this group
    const windowUrl = new URL(window.location.href)
    const base = RED.nodes.node(baseId)
    const dashboardBasePath = (base.path || 'dashboard').replace(/\/$/, '').replace(/^\/+/, '')
    const authTokens = RED.settings.get('auth-tokens') || {}
    const headers = {}
    if (authTokens?.access_token) {
        headers.Authorization = `${authTokens.token_type || 'Bearer'} ${authTokens.access_token}`
    }

    // promisify the ajax call so we can await it & return false after opening the popup
    const ajax = () => {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: `${dashboardBasePath}/api/v1/${baseId}/edit/${pageId}`, // e.g. /dashboard/api/v1/123/edit/456
                type: 'PATCH',
                headers,
                data: {
                    mode: 'edit',
                    dashboard: baseId,
                    page: pageId
                },
                success: function (data) {
                    // construct the url to open the layout editor
                    const _url = new URL(`${dashboardBasePath}/${data.path}`.replace(/\/\//g, '/'), windowUrl.origin)
                    _url.searchParams.set('edit-key', data.editKey)
                    const editorPath = windowUrl.pathname?.replace(/\/$/, '').replace(/^\/+/, '') || ''
                    if (editorPath) {
                        _url.searchParams.set('editor-path', editorPath)
                    }
                    resolve(_url)
                },
                error: function (err) {
                    reject(err)
                }
            })
        })
    }
    try {
        const url = await ajax()
        const target = `ff-dashboard-${baseId}` // try to reuse the same window per base
        const newWindow = window.open(url, target)
        // if we have an access_token then send it to the new window once opened
        // This becomes necessary if the user has gotten logged out since opening the editor.
        // Also, this squares up any token mismatch due to iframes/Same-Origin Policy etc.
        if (authTokens?.access_token && newWindow) {
            // Wait 1s then send the local access_token
            // (it is unlikely user will have done any editing and be wanting to deploy within 1s!)
            setTimeout(() => {
                newWindow.postMessage({
                    type: 'authorization',
                    access_token: authTokens.access_token,
                    token_type: authTokens.token_type,
                    expires_in: authTokens.expires_in
                }, url.origin)
            }, 1000)
        }
    } catch (err) {
        console.error('layout mode error', err)
        RED.notify(RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.error.layoutEditor'), 'error')
    }
}

Crafts an URL without considering the RED.settings.httpNodeRoot that can be altered by the user when setting up Nodered

// construct the url to open the layout editor
const _url = new URL(`${dashboardBasePath}/${data.path}`.replace(/\/\//g, '/'), windowUrl.origin)
_url.searchParams.set('edit-key', data.editKey)
const editorPath = windowUrl.pathname?.replace(/\/$/, '').replace(/^\/+/, '') || ''
if (editorPath) {
    _url.searchParams.set('editor-path', editorPath)
}
resolve(_url)

resulting in a wrong redirect causing a 404.

In other words, if Nodered starts at http://localhost:8000/custompath/ instead of simply http://localhost:8000/, clicking on the Layout Editor button results in a redirect to http://localhost:8000/dashboard/page1?edit... causing a 404 instead of http://localhost:8000/custompath/dashboard/page1?edit....

This behavior can be seen by simply deploying any UI node such as a Button.

Expected Behavior

I expect the Layout Editor button to redirect me to the layout editor page listening under the path that considers the RED.settings.httpNodeRoot prefix.

For example, if Nodered starts at http://localhost:8000/custompath/ instead of simply http://localhost:8000/, clicking on the Layout Editor button should redirect me http://localhost:8000/custompath/dashboard/page1?edit... instead of http://localhost:8000/dashboard/page1?edit....

This could be fixed by considering the RED.settings.httpNodeRoot prefix here:

const _url = new URL(`${RED.settings.httpNodeRoot}/${dashboardBasePath}/${data.path}`.replace(/\/\//g, '/'), windowUrl.origin)

instead of

const _url = new URL(`${dashboardBasePath}/${data.path}`.replace(/\/\//g, '/'), windowUrl.origin)

Steps To Reproduce

Alter Nodered settings and set a custom prefix on RED.settings.httpNodeRoot, then deploy any UI node such as a Button and proceed to click on the "Layout editor" button on the Dashboard 2.0 sidebar under the given page, usually labeled as Page 1 if no other configs have been changed. You should encounter a 404 error.

For referece instead, the "Open Dashboard" button works correctly.

Environment

  • Dashboard version: 1.29.0
  • Node-RED version: 4.0.9
  • Node.js version: 22.16.0
  • npm version: 10.9.2
  • Platform/OS: Ubuntu 24.04.3
  • Browser: Firefox

Have you provided an initial effort estimate for this issue?

I am not a FlowFuse team member

f0lg0 avatar Nov 07 '25 08:11 f0lg0

This has annoyed me for ages but I had no idea where to start looking. Your fix works for me.

prpr19xx avatar Nov 10 '25 02:11 prpr19xx

@f0lg0 are you able to turn your suggested fix into a PR we can review and get merged?

knolleary avatar Nov 12 '25 14:11 knolleary

@f0lg0 are you able to turn your suggested fix into a PR we can review and get merged?

Done https://github.com/FlowFuse/node-red-dashboard/pull/1929

f0lg0 avatar Nov 13 '25 13:11 f0lg0

This is probably the same bug as #1687 and possibly #1557.

prpr19xx avatar Nov 17 '25 01:11 prpr19xx