Dashboard 2. Custom Logo in Title Bar

Hi,

I try to add a Custom logo in the Title bar (Header) (left side).

I use a template node with the following contents:

<template>
    <Teleport v-if="mounted" to="#app-bar-title">
        <img height="32px" :src="msg.payload"></img>
    </Teleport>
</template>
<script>
    export default {
        data() {
            return {
                mounted: false
            }
        },
        mounted() {
            this.mounted = true
        }
    }
</script>

This code has been found at: Template ui-template | Node-RED Dashboard 2.0

I inject an image of the size 32 x 32 pixels. The payload is send as a single Buffer Object to the template node.

The icon image is not shown in the Header, but the icon of an image is shown.
What could be wrong?

Regards

Alas I can't understand what you are saying here. Maybe a screen capture would clarify?

A bit of searching tells me the HTML syntax to embed a base64 encoded image is this

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
  //8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />

And when msg.payload contains data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4 //8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==, the :src="msg.payload" syntax does put a red dot in the dashboard title bar for me.

What does your msg.payload contain?

Hello @jbudd

Thank you for your response.

In the meantime, I made some progress (with the help of Gemini AI)

You asked:

Maybe a screen capture would clarify?

NodeREDDashboardlogointitle

As you can see, I now have a logo after the text "Dashboard"

Maybe it is not that nice, but okay, it is an image.

My first step , was a ui-template node, with the following contents:

<template>
    <Teleport v-if="mounted" to="#app-bar-title">
        <img height="32px" :src="msg.payload"></img>
    </Teleport>
</template>
<script>
    export default {
        data() {
            return {
                mounted: false
            }
        },
        mounted() {
            this.mounted = true
        }
    }
</script>

I injected the image, as msg.payload, containing a buffered object in this template node.
Perhaps it should have been "base64" encoded, but I did not test that.
It displayed the icon of a file, instead of the desired logo.

But,

With the help of Gemini AI, I got a new script for the ui-template node:

<template>
    <!-- Teleport hooks directly into the existing app bar title element -->
    <Teleport v-if="isMounted" to="#app-bar-title">
        <img src="/logo.png" class="app-bar-logo" alt="Logo" />
    </Teleport>
</template>

<script>
    export default {
    data() {
        return {
            isMounted: false
        }
    },
    mounted() {
        this.isMounted = true;
        
        this.$nextTick(() => {
            const titleEl = document.querySelector('#app-bar-title');
            if (titleEl) {
                titleEl.style.display = 'inline-flex';
                titleEl.style.alignItems = 'center';
                titleEl.style.flexDirection = 'row'; // Forces standard left-to-right order
                titleEl.style.gap = '12px';          // Spacing between logo and text
            }
        });
    }
}
</script>

<style>
    .app-bar-logo {
        width: 32px !important;
        height: 32px !important;
        min-width: 32px !important;
        min-height: 32px !important;
        object-fit: contain;
        /* Force this element to render first in the flex container */
        order: -1 !important;
}
</style>

This new script gave the result, as shown above, but the script is supposed the put the image in front of the text "Dashboard¨, which it does not.

Then Gemini suggested a more aggressive script, that should do it. But that does not work, either.

<template>
    <!-- Teleport still drops the image safely inside the app bar container -->
    <Teleport v-if="isMounted" to="#app-bar-title">
        <img id="ff-app-logo" src="/logo.png" class="app-bar-logo" alt="Logo" />
    </Teleport>
</template>

<script>
export default {
    data() {
        return {
            isMounted: false
        }
    },
    mounted() {
        this.isMounted = true;
        
        // Wait for Vue to fully render the element into the layout
        this.$nextTick(() => {
            const checkExist = setInterval(() => {
                const titleEl = document.querySelector('#app-bar-title');
                const logoEl = document.querySelector('#ff-app-logo');
                
                if (titleEl && logoEl) {
                    clearInterval(checkExist); // Stop searching once found
                    
                    // FORCE DOM RE-ORDER: Physically moves the logo to the absolute first position
                    titleEl.insertBefore(logoEl, titleEl.firstChild);
                    
                    // Apply structural layout adjustments to guarantee alignment
                    titleEl.style.setProperty('display', 'inline-flex', 'important');
                    titleEl.style.setProperty('align-items', 'center', 'important');
                    titleEl.style.setProperty('flex-direction', 'row', 'important');
                    titleEl.style.setProperty('gap', '12px', 'important');
                }
            }, 100); // Check every 100ms until the dashboard finishes drawing
        });
    }
}
</script>

<style>
/* Forces the layout constraints onto the image element */
.app-bar-logo {
    width: 32px !important;
    height: 32px !important;
    min-width: 32px !important;
    min-height: 32px !important;
    object-fit: contain !important;
    margin-right: 4px !important; /* Visual padding cushion */
}
</style>

A file logo.png has been copied into a folder in the .node-red folder.
Also the Settings.js file has been modified.

Perhaps. you see something, that is wrong, in order to get the logo in front of the text,?
If not I'm fine with the current solution.

Regards

Since one of your versions uses a static image, logo.png, you might find this approach usable.

I can get a static image to the right of the "show/hide tabs sidebar" button.

I didn't devote much effort to making it fit, I feel sure you could get a better result.
There may be undesirable side effects.

This is pure CSS in a CSS template, nothing to do with the teleport template.:

.v-toolbar__prepend {
    width: 100px;  /* make the div containing the button wider */ 
    background-image: url("/greendb.svg");  /* specify a background image, path is relative to httpStatic from settings.js. Leading / essential */
    background-position: right center; /* Move the background image to the right */
}

Hi @jbudd

I copied your CSS text into a ui-template and changed the greendb.svg into logo.png, as I have that file available in the folder as set by httpStatic in Settings.js file.
Unfortunately no image is shown.

Check a couple of things.

First try to access that file name by URL directly - keep the first part of the URL the same as the Dashboard URL and change everything after the first slash to be logo.png, e.g. http://localhost:1880/logo.png. If that isn't working, either the name or location is wrong and you will need to check your settings.js.

Also check your browser dev tools when accessing the Dashboard page to see if there are other errors.

Good evening Julian, @TotallyInformation,

Thanks for your response, although it is a little bit unclear for me, which of my posts, you refer to.

First try to access that file name by URL directly - keep the first part of the URL the same as the Dashboard URL and change everything after the first slash to be logo.png, e.g. http://localhost:1880/logo.png. If that isn't working, either the name or location is wrong and you will need to check your settings.js.

That is working, so the location, set in http static, is correct and also the file name is correct.

The issue was that the script in the FlowFuse documentation did not show the logo.png file.

The AI generated script does show the logo, as you can see in one of my previous posts. However that script should place the logo at the left side of the text "Dashboard", instead of the right side. At least, that is claimed by Gemini.

@jbudd suggested a simpler CSS solution, which did not work for me.

Regards,
Herman

Ah, OK, so you are past the display issue itself then. Sorry, I didn't spot that.

Unfortunately, I can't help with the D2 issue itself that much because I don't use D2 or D1 for anything meaningful.

However, I did spot this:


And so by adding a style for div#app-bar-title::before, that would add a pseudo element just before the app-bar-title - confusingly, it seems that the app-bar-title div is actually AFTER the title text. So adding something before it should put it in the correct place.

The before pseudo element allows you to add a background image in the way that jbudd described and you don't need any HTML at all, you can simply add some custom CSS styling to D2.

I did a quick, in-browser test with just text and it did work just fine.

I originally used a Raspberry Pi which was running Node-red 5 beta.
Now I have checked on another Pi running v4.1.8 and dashboard 2 v1.30.2.

This Pi did not have a value for httpStatic in settings.js so I added in
httpStatic: '/home/pi/.node-red/node-red-static';
and restarted Node-red.

I went to https://icons8.com/icons/set/small and downloaded a small .png file into that directory.

Using the above code with width 100px, this image was not visible, but when I increased the width to 200px it is. No idea why it didn't show up at 100px.

This is an export of the CSS template.

[
    {
        "id": "40d4732c511368f8",
        "type": "ui-template",
        "z": "7f20ad1a9174e987",
        "group": "",
        "page": "",
        "ui": "a3e614cd9df4ae2e",
        "name": "",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": ".v-toolbar__prepend {\nwidth: 200px; /* make the div containing the button wider */\nbackground-image: url(\"/dog.png\"); /* specify a background image, path is relative to httpStatic from settings.js.\nLeading / essential */\nbackground-position: right center; /* Move the background image to the right */\n}",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "site:style",
        "className": "",
        "x": 380,
        "y": 400,
        "wires": [
            []
        ]
    },
    {
        "id": "a3e614cd9df4ae2e",
        "type": "ui-base",
        "name": "My Dashboard",
        "path": "/dashboard",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control"
        ],
        "showPathInSidebar": false,
        "navigationStyle": "default",
        "titleBarStyle": "default"
    },
    {
        "id": "e6ad19c61af3e744",
        "type": "global-config",
        "env": [],
        "modules": {
            "@flowfuse/node-red-dashboard": "1.30.2"
        }
    }
]

Using pseudo elements I only managed to superimpose my logo on top of the button, so I went to background images.
Not sure which is better coding style.

Hi Julian,

Thanks for your respose to this issue.

Unfortunately, I can't help with the D2 issue itself that much because I don't use D2 or D1 for anything meaningful

It would have surprised me, if the creater of UI-Builder used DB1 or DB2. :joy:

I think, that I leave it for the moment and focus on other interesting things.

Regards

Hi @jbudd,

I will look to your CSS tomorrow and report back

Regards