Status-> System log, new text should be at the top, not bottom

Here is what I mean

See discussion at:

I tested that "reversed syslog" on my own system, but as I had already used OpenWrt so long, it seemed wrong to have log as new lines on top.

2 Likes

Thanks !

I pulled the code from the syslog.js on my system, but it was just one continuous and unreadable line of text.

I asked chatgpt to properly format and indent the code, it returned the following

'use strict';
'require view';
'require fs';
'require ui';

return view.extend({
    load: function() {
        return Promise.all([
            L.resolveDefault(fs.stat('/sbin/logread'), null),
            L.resolveDefault(fs.stat('/usr/sbin/logread'), null)
        ]).then(function(stat) {
            var logger = stat[0] ? stat[0].path : stat[1] ? stat[1].path : null;
            return fs.exec_direct(logger, ['-e', '^']).catch(function(err) {
                ui.addNotification(
                    null,
                    E('p', {}, _('Unable to load log data: ' + err.message))
                );
                return '';
            });
        });
    },
    render: function(logdata) {
        var loglines = logdata.trim().split(/\n/);
        return E([], [
            E('h2', {}, [_('System Log')]),
            E('div', {'id': 'content_syslog'}, [
                E('textarea', {
                    'id': 'syslog',
                    'style': 'font-size:12px',
                    'readonly': 'readonly',
                    'wrap': 'off',
                    'rows': loglines.length + 1
                }, [loglines.join('\n')])
            ])
        ]);
    },
    handleSaveApply: null,
    handleSave: null,
    handleReset: null
});

According to that post, it would be a matter of changing the code

fs.exec_direct('/sbin/logread'

to

loglines.join('\n') => loglines.reverse().join('\n')

Unfortunately, the orignal line no longer exists in the current incarnation of syslog.js

I think that you can see the format from my previous WNDR3700 community build's (Build for WNDR3700v1/v2 / WNDR3800 (discontinued)) download site:

...-luci.patch shows the changes:

I wanted to show only the 50 last lines, and the code goes like shown below:

I created a seaprate menu item and page for that, so the code is a bit more complex, but the crucial part is:
loglines.join('\n') change to
loglines.slice(-50).reverse().join('\n')
or just
loglines.reverse().join('\n')

key extract:

render: function(logdata) {
+		var loglines = logdata.trim().split(/\n/);
+
+		return E([], [
+			E('h2', {}, [ _('Last 50 lines of System Log (recent on top)') ]),
+			E('div', { 'id': 'content_syslog' }, [
+				E('textarea', {
+					'id': 'syslog',
+					'style': 'font-size:12px',
+					'readonly': 'readonly',
+					'wrap': 'off',
+					'rows': 52
+				}, [ loglines.slice(-50).reverse().join('\n') ])
+			])
+		]);
+	},

full code:

diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/view/status/recentlog.js b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/recentlog.js
new file mode 100644
index 000000000..219034a4b
--- /dev/null
+++ b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/recentlog.js
@@ -0,0 +1,33 @@
+'use strict';
+'require fs';
+'require ui';
+
+return L.view.extend({
+	load: function() {
+		return fs.exec_direct('/sbin/logread', [ '-e', '^' ]).catch(function(err) {
+			ui.addNotification(null, E('p', {}, _('Unable to load log data: ' + err.message)));
+			return '';
+		});
+	},
+
+	render: function(logdata) {
+		var loglines = logdata.trim().split(/\n/);
+
+		return E([], [
+			E('h2', {}, [ _('Last 50 lines of System Log (recent on top)') ]),
+			E('div', { 'id': 'content_syslog' }, [
+				E('textarea', {
+					'id': 'syslog',
+					'style': 'font-size:12px',
+					'readonly': 'readonly',
+					'wrap': 'off',
+					'rows': 52
+				}, [ loglines.slice(-50).reverse().join('\n') ])
+			])
+		]);
+	},
+
+	handleSaveApply: null,
+	handleSave: null,
+	handleReset: null
+});
diff --git a/modules/luci-mod-status/root/usr/share/luci/menu.d/luci-mod-status.json b/modules/luci-mod-status/root/usr/share/luci/menu.d/luci-mod-status.json
index e726c56b2..3c9fe48c5 100644
--- a/modules/luci-mod-status/root/usr/share/luci/menu.d/luci-mod-status.json
+++ b/modules/luci-mod-status/root/usr/share/luci/menu.d/luci-mod-status.json
@@ -35,9 +35,18 @@
 		}
 	},
 
+	"admin/status/recentlog": {
+		"title": "Last 50 lines in log",
+		"order": 4,
+		"action": {
+			"type": "view",
+			"path": "status/recentlog"
+		}
+	},
+
 	"admin/status/logs": {
 		"title": "System Log",
-		"order": 4,
+		"order": 5,
 		"action": {
 			"type": "alias",
 			"path": "admin/status/logs/syslog"
@@ -67,7 +76,7 @@
 
 	"admin/status/processes": {
 		"title": "Processes",
-		"order": 6,
+		"order": 7,
 		"action": {
 			"type": "view",
 			"path": "status/processes"
@@ -92,7 +101,7 @@
 
 	"admin/status/realtime": {
 		"title": "Realtime Graphs",
-		"order": 7,
+		"order": 8,
 		"action": {
 			"type": "alias",
 			"path": "admin/status/realtime/load"

It does exists there. At least in master and 22.03 and 21.02 and 19.07

An alternative may be to scroll to the bottom of the page on page loading...

On every system logs are always from top to bottom and people would expect the same

1 Like

I confirm the code below works as discussed
Version
LuCI openwrt-22.03 branch (git-22.361.69894-438c598)https://github.com/openwrt/luci
OpenWrt 22.03.3 r20028-43d71ad93e
luci git-20.074.84698-ead5e81

'use strict';
'require view';
'require fs';
'require ui';

return view.extend({
    load: function() {
        return Promise.all([
            L.resolveDefault(fs.stat('/sbin/logread'), null),
            L.resolveDefault(fs.stat('/usr/sbin/logread'), null)
        ]).then(function(stat) {
            var logger = stat[0] ? stat[0].path : stat[1] ? stat[1].path : null;
            return fs.exec_direct(logger, ['-e', '^']).catch(function(err) {
                ui.addNotification(
                    null,
                    E('p', {}, _('Unable to load log data: ' + err.message))
                );
                return '';
            });
        });
    },
    render: function(logdata) {
        var loglines = logdata.trim().split(/\n/);
        return E([], [
            E('h2', {}, [_('System Log')]),
            E('div', {'id': 'content_syslog'}, [
                E('textarea', {
                    'id': 'syslog',
                    'style': 'font-size:12px',
                    'readonly': 'readonly',
                    'wrap': 'off',
                    'rows': loglines.length + 1
                }, [loglines.reverse().join('\n')])
            ])
        ]);
    },
    handleSaveApply: null,
    handleSave: null,
    handleReset: null
});

So it seems the code literally flips the entries...which as noted:

I agree. Changing something that's standard and expected everywere else (e.g. even the same software but via the command line, from which the web GUI gets the data) - is a good source for/cause of confusion (and may mess up other's scripts).

I know it would confuse me, and the only refrence would be this one obscure thread and one PR.

Lastly given the log is rolling, inverting the entries may falsely imply the logs run from "the beginning".

I actually did run into that in a TP-Link OEM firmware, and that originally prompted me to try reversing.

But I did that as the quick "show last 50 lines" page, not for the whole log.

1 Like

I understand how it would be different from what you are used to.

However having the end of the syslog visible as soon as you open the page would be really great.

Now every time you load this page you must first scroll to the bottom

I understand why you clip the syslog to 50 lines to achieve this, but that's a problem if you're hunting a problem that occurred more than about 2 hours ago.

I just loaded my syslog page and I also scrolled to the bottom out of habit, not realizing what I need is right there at the top !

I would add a checked checkbox at the top of the interface with label "reverse chronological order" to make it easy for anyone to switch back and forth, and also as a reminder that they don't have to scroll down anymore.

I would be nice to also have this for the kernel log page.

astly, and this could be another feature request, but this page should live update as new entries appear in the log file, similair to "tail -f" so as to make page reloads unnecessary during long term monitoring.

I'd say it took me about maybe a week to get used to having the syslog in reverse but now I really dig it.

No more scrolling! :partying_face:

certainly a selective order and respective auto refresh with filter option would be nice but one would expect in case you need to read syslog you do have the skill to SSH and use logread too? :wink:

I second that suggestion! Add html code to automatically scroll down

1 Like

Hi,

Recently updated from this old version to the newest.

So that means updating this script to eliminate performing the useless scroll down action everytime you hit F5.

Someone added a "scroll to bottom" button, but you have to click every single time, which, I simply don't have the time and patience for that sort of menial labour.

So here is the current state of the code for syslog.js in 23.05.5

'use strict';'require view';'require fs';'require ui';return view.extend({load:function(){return Promise.all([L.resolveDefault(fs.stat('/sbin/logread'),null),L.resolveDefault(fs.stat('/usr/sbin/logread'),null)]).then(function(stat){var logger=stat[0]?stat[0].path:stat[1]?stat[1].path:null;return fs.exec_direct(logger,['-e','^']).catch(function(err){ui.addNotification(null,E('p',{},_('Unable to load log data: '+err.message)));return'';});});},render:function(logdata){var loglines=logdata.trim().split(/\n/);var scrollDownButton=E('button',{'id':'scrollDownButton','class':'cbi-button cbi-button-neutral'},_('Scroll to tail','scroll to bottom (the tail) of the log file'));scrollDownButton.addEventListener('click',function(){scrollUpButton.scrollIntoView();});var scrollUpButton=E('button',{'id':'scrollUpButton','class':'cbi-button cbi-button-neutral'},_('Scroll to head','scroll to top (the head) of the log file'));scrollUpButton.addEventListener('click',function(){scrollDownButton.scrollIntoView();});return E([],[E('h2',{},[_('System Log')]),E('div',{'id':'content_syslog'},[E('div',{'style':'padding-bottom: 20px'},[scrollDownButton]),E('textarea',{'id':'syslog','style':'font-size:12px','readonly':'readonly','wrap':'off','rows':loglines.length+1},[loglines.join('\n')]),E('div',{'style':'padding-bottom: 20px'},[scrollUpButton])])]);},handleSaveApply:null,handleSave:

reformatted

'use strict';

// Import necessary modules
'require view';
'require fs';
'require ui';

// Extend the view
return view.extend({
    // Load method to fetch log data
    load: function() {
        // Check if logread binary exists in either of the paths
        return Promise.all([
            L.resolveDefault(fs.stat('/sbin/logread'), null),
            L.resolveDefault(fs.stat('/usr/sbin/logread'), null)
        ]).then(function(stat) {
            // Determine the path of the logread binary
            var logger = stat[0] ? stat[0].path : (stat[1] ? stat[1].path : null);

            // If logger path is found, execute logread command and fetch log data
            return fs.exec_direct(logger, ['-e', '^'])
                .catch(function(err) {
                    // If an error occurs, display a notification
                    ui.addNotification(
                        null,
                        E('p', {}, _('Unable to load log data: ' + err.message))
                    );
                    return ''; // Return an empty string if log data cannot be loaded
                });
        });
    },

    // Render method to display the log data
    render: function(logdata) {
        // Split the log data into individual lines
        var loglines = logdata.trim().split(/\n/);

        // Create "Scroll to tail" button
        var scrollDownButton = E(
            'button',
            {
                id: 'scrollDownButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to tail', 'Scroll to bottom (the tail) of the log file')
        );

        // Add click event to "Scroll to tail" button
        scrollDownButton.addEventListener('click', function() {
            scrollUpButton.scrollIntoView(); // Scroll to the top button
        });

        // Create "Scroll to head" button
        var scrollUpButton = E(
            'button',
            {
                id: 'scrollUpButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to head', 'Scroll to top (the head) of the log file')
        );

        // Add click event to "Scroll to head" button
        scrollUpButton.addEventListener('click', function() {
            scrollDownButton.scrollIntoView(); // Scroll to the bottom button
        });

        // Return the rendered content as a DOM structure
        return E([], [
            // Header for the log section
            E('h2', {}, [_('System Log')]),

            // Container for the system log
            E('div', { id: 'content_syslog' }, [
                // Button to scroll to the bottom of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollDownButton]),

                // Text area to display the log data
                E('textarea', {
                    id: 'syslog',
                    style: 'font-size:12px',
                    readonly: 'readonly',
                    wrap: 'off',
                    rows: loglines.length + 1
                }, [loglines.join('\n')]),

                // Button to scroll to the top of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollUpButton])
            ])
        ]);
    },

    // Placeholder for handleSaveApply method (not implemented)
    handleSaveApply: null,

    // Placeholder for handleSave method (not implemented)
    handleSave: null
});

Now I see 3 solutions

  1. Invert the log order so that the newest is at the top

  2. Auto-scroll to the end on page reloads

  3. Auto-update the textarea so you don't have to page reload constantly

  4. all 3 features with toggle buttons for each at the top (where are we going to store the state of this though ?)

Here is the attempt at "invert text area"

'use strict';

// Import necessary modules
'require view';
'require fs';
'require ui';

// Extend the view
return view.extend({
    // Load method to fetch log data
    load: function() {
        // Check if logread binary exists in either of the paths
        return Promise.all([
            L.resolveDefault(fs.stat('/sbin/logread'), null),
            L.resolveDefault(fs.stat('/usr/sbin/logread'), null)
        ]).then(function(stat) {
            // Determine the path of the logread binary
            var logger = stat[0] ? stat[0].path : (stat[1] ? stat[1].path : null);

            // If logger path is found, execute logread command and fetch log data
            return fs.exec_direct(logger, ['-e', '^'])
                .catch(function(err) {
                    // If an error occurs, display a notification
                    ui.addNotification(
                        null,
                        E('p', {}, _('Unable to load log data: ' + err.message))
                    );
                    return ''; // Return an empty string if log data cannot be loaded
                });
        });
    },

    // Render method to display the log data
    render: function(logdata) {
        // Split the log data into individual lines and reverse the order
        var loglines = logdata.trim().split(/\n/).reverse();

        // Create "Scroll to tail" button
        var scrollDownButton = E(
            'button',
            {
                id: 'scrollDownButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to tail', 'Scroll to bottom (the tail) of the log file')
        );

        // Add click event to "Scroll to tail" button
        scrollDownButton.addEventListener('click', function() {
            scrollUpButton.scrollIntoView(); // Scroll to the top button
        });

        // Create "Scroll to head" button
        var scrollUpButton = E(
            'button',
            {
                id: 'scrollUpButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to head', 'Scroll to top (the head) of the log file')
        );

        // Add click event to "Scroll to head" button
        scrollUpButton.addEventListener('click', function() {
            scrollDownButton.scrollIntoView(); // Scroll to the bottom button
        });

        // Return the rendered content as a DOM structure
        return E([], [
            // Header for the log section
            E('h2', {}, [_('System Log')]),

            // Container for the system log
            E('div', { id: 'content_syslog' }, [
                // Button to scroll to the bottom of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollDownButton]),

                // Text area to display the log data
                E('textarea', {
                    id: 'syslog',
                    style: 'font-size:12px',
                    readonly: 'readonly',
                    wrap: 'off',
                    rows: loglines.length + 1
                }, [loglines.join('\n')]),

                // Button to scroll to the top of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollUpButton])
            ])
        ]);
    },

    // Placeholder for handleSaveApply method (not implemented)
    handleSaveApply: null,

    // Placeholder for handleSave method (not implemented)
    handleSave: null
});

Here is the attempt at "auto-scroll down"

'use strict';

// Import necessary modules
'require view';
'require fs';
'require ui';

// Extend the view
return view.extend({
    // Load method to fetch log data
    load: function () {
        // Check if logread binary exists in either of the paths
        return Promise.all([
            L.resolveDefault(fs.stat('/sbin/logread'), null),
            L.resolveDefault(fs.stat('/usr/sbin/logread'), null)
        ]).then(function (stat) {
            // Determine the path of the logread binary
            var logger = stat[0] ? stat[0].path : (stat[1] ? stat[1].path : null);

            // If logger path is found, execute logread command and fetch log data
            return fs.exec_direct(logger, ['-e', '^']).catch(function (err) {
                // If an error occurs, display a notification
                ui.addNotification(
                    null,
                    E('p', {}, _('Unable to load log data: ' + err.message))
                );
                return ''; // Return an empty string if log data cannot be loaded
            });
        });
    },

    // Render method to display the log data
    render: function (logdata) {
        // Split the log data into individual lines
        var loglines = logdata.trim().split(/\n/);

        // Create "Scroll to tail" button
        var scrollDownButton = E(
            'button',
            {
                id: 'scrollDownButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to tail', 'Scroll to bottom (the tail) of the log file')
        );

        // Add click event to "Scroll to tail" button
        scrollDownButton.addEventListener('click', function () {
            scrollUpButton.scrollIntoView(); // Scroll to the top button
        });

        // Create "Scroll to head" button
        var scrollUpButton = E(
            'button',
            {
                id: 'scrollUpButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to head', 'Scroll to top (the head) of the log file')
        );

        // Add click event to "Scroll to head" button
        scrollUpButton.addEventListener('click', function () {
            scrollDownButton.scrollIntoView(); // Scroll to the bottom button
        });

        // Create the textarea for the log content
        var textarea = E('textarea', {
            id: 'syslog',
            style: 'font-size:12px',
            readonly: 'readonly',
            wrap: 'off',
            rows: loglines.length + 1
        }, [loglines.join('\n')]);

        // ** Auto-scroll to the bottom of the textarea **
        setTimeout(() => {
            textarea.scrollTop = textarea.scrollHeight; // Scroll to the bottom
        }, 0); // Use a timeout to ensure it happens after rendering

        // Return the rendered content as a DOM structure
        return E([], [
            // Header for the log section
            E('h2', {}, [_('System Log')]),

            // Container for the system log
            E('div', { id: 'content_syslog' }, [
                // Button to scroll to the bottom of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollDownButton]),

                // Text area to display the log data
                textarea,

                // Button to scroll to the top of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollUpButton])
            ])
        ]);
    },

    // Placeholder for handleSaveApply method (not implemented)
    handleSaveApply: null,

    // Placeholder for handleSave method (not implemented)
    handleSave: null
});

Here is the attempt at "auto update content every 2 seconds"

'use strict';

// Import necessary modules
'require view';
'require fs';
'require ui';

// Extend the view
return view.extend({
    // Load method to fetch log data
    load: function () {
        // Check if logread binary exists in either of the paths
        return Promise.all([
            L.resolveDefault(fs.stat('/sbin/logread'), null),
            L.resolveDefault(fs.stat('/usr/sbin/logread'), null)
        ]).then(function (stat) {
            // Determine the path of the logread binary
            var logger = stat[0] ? stat[0].path : (stat[1] ? stat[1].path : null);
            return logger;
        });
    },

    // Render method to display the log data
    render: function (logger) {
        // Create "Scroll to tail" button
        var scrollDownButton = E(
            'button',
            {
                id: 'scrollDownButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to tail', 'Scroll to bottom (the tail) of the log file')
        );

        // Create "Scroll to head" button
        var scrollUpButton = E(
            'button',
            {
                id: 'scrollUpButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to head', 'Scroll to top (the head) of the log file')
        );

        // Create the textarea for the log content
        var textarea = E('textarea', {
            id: 'syslog',
            style: 'font-size:12px',
            readonly: 'readonly',
            wrap: 'off'
        });

        // ** Auto-scroll to the bottom of the textarea **
        var autoScrollToBottom = function () {
            textarea.scrollTop = textarea.scrollHeight;
        };

        // Add click event to "Scroll to tail" button
        scrollDownButton.addEventListener('click', function () {
            autoScrollToBottom();
        });

        // Add click event to "Scroll to head" button
        scrollUpButton.addEventListener('click', function () {
            textarea.scrollTop = 0; // Scroll to the top
        });

        // Function to fetch and update log data every 2 seconds
        var updateLogData = function () {
            fs.exec_direct(logger, ['-e', '^'])
                .then(function (logdata) {
                    // Update the textarea with the new log data
                    var loglines = logdata.trim().split(/\n/);
                    textarea.value = loglines.join('\n');

                    // Auto-scroll to the bottom after updating
                    autoScrollToBottom();
                })
                .catch(function (err) {
                    // If an error occurs, display a notification
                    ui.addNotification(
                        null,
                        E('p', {}, _('Unable to load log data: ' + err.message))
                    );
                });
        };

        // Start fetching log data immediately and then every 2 seconds
        updateLogData(); // Initial fetch
        setInterval(updateLogData, 2000); // Fetch every 2 seconds

        // Return the rendered content as a DOM structure
        return E([], [
            // Header for the log section
            E('h2', {}, [_('System Log')]),

            // Container for the system log
            E('div', { id: 'content_syslog' }, [
                // Button to scroll to the bottom of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollDownButton]),

                // Text area to display the log data
                textarea,

                // Button to scroll to the top of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollUpButton])
            ])
        ]);
    },

    // Placeholder for handleSaveApply method (not implemented)
    handleSaveApply: null,

    // Placeholder for handleSave method (not implemented)
    handleSave: null
});

I tried testing those by modifying the file
/www/luci-static/resources/view/status/syslog.js
And none of them had any effect.
I tried restarting uhttpd, still no effect

I then deleted the syslog,js file entirely, and the syslog still displays as usual...

Do these files do anything ?

Here is a version, which contains the invert, autoscroll and autoupdate features

These 3 features are controlled by 3 checkboxes, placed to the right of the scroll to tail button
By default invert and autoupdate and enabled

This code is untested, as no matter what I do to the syslog.js file, luci syslog does not change ?!

'use strict';

// Import necessary modules
'require view';
'require fs';
'require ui';

// Extend the view
return view.extend({
    // Load method to fetch log data
    load: function () {
        // Check if logread binary exists in either of the paths
        return Promise.all([
            L.resolveDefault(fs.stat('/sbin/logread'), null),
            L.resolveDefault(fs.stat('/usr/sbin/logread'), null)
        ]).then(function (stat) {
            // Determine the path of the logread binary
            var logger = stat[0] ? stat[0].path : (stat[1] ? stat[1].path : null);
            return logger;
        });
    },

    // Render method to display the log data
    render: function (logger) {
        // Initial state for the checkboxes
        let isInverted = true; // Default: invert is enabled
        let isAutoscroll = false; // Default: autoscroll is disabled
        let isAutoupdate = true; // Default: autoupdate is enabled

        // Create the textarea for the log content
        var textarea = E('textarea', {
            id: 'syslog',
            style: 'font-size:12px',
            readonly: 'readonly',
            wrap: 'off'
        });

        // Function to fetch and update the log data
        var updateLogData = function () {
            fs.exec_direct(logger, ['-e', '^'])
                .then(function (logdata) {
                    // Split log data into lines
                    var loglines = logdata.trim().split(/\n/);

                    // Apply "invert" logic based on checkbox
                    if (isInverted) {
                        loglines = loglines.reverse();
                    }

                    // Update textarea content
                    textarea.value = loglines.join('\n');

                    // Auto-scroll to the bottom if "autoscroll" is enabled
                    if (isAutoscroll) {
                        textarea.scrollTop = textarea.scrollHeight;
                    }
                })
                .catch(function (err) {
                    // If an error occurs, display a notification
                    ui.addNotification(
                        null,
                        E('p', {}, _('Unable to load log data: ' + err.message))
                    );
                });
        };

        // Start fetching log data if "autoupdate" is enabled
        if (isAutoupdate) {
            updateLogData(); // Initial fetch
            setInterval(function () {
                if (isAutoupdate) {
                    updateLogData(); // Periodic updates
                }
            }, 2000); // Fetch every 2 seconds
        }

        // Create the checkboxes
        var invertCheckbox = E('input', {
            type: 'checkbox',
            checked: 'checked', // Default: checked
            id: 'invertCheckbox'
        });
        var autoscrollCheckbox = E('input', {
            type: 'checkbox',
            id: 'autoscrollCheckbox'
        });
        var autoupdateCheckbox = E('input', {
            type: 'checkbox',
            checked: 'checked', // Default: checked
            id: 'autoupdateCheckbox'
        });

        // Create labels for the checkboxes
        var invertLabel = E('label', { for: 'invertCheckbox' }, _('Invert'));
        var autoscrollLabel = E('label', { for: 'autoscrollCheckbox' }, _('Autoscroll'));
        var autoupdateLabel = E('label', { for: 'autoupdateCheckbox' }, _('Autoupdate'));

        // Add event listeners for the checkboxes
        invertCheckbox.addEventListener('change', function () {
            isInverted = invertCheckbox.checked;
            updateLogData(); // Update log data immediately when toggled
        });

        autoscrollCheckbox.addEventListener('change', function () {
            isAutoscroll = autoscrollCheckbox.checked;
        });

        autoupdateCheckbox.addEventListener('change', function () {
            isAutoupdate = autoupdateCheckbox.checked;
        });

        // Create "Scroll to tail" button
        var scrollDownButton = E(
            'button',
            {
                id: 'scrollDownButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to tail', 'Scroll to bottom (the tail) of the log file')
        );

        // Add click event to "Scroll to tail" button
        scrollDownButton.addEventListener('click', function () {
            textarea.scrollTop = textarea.scrollHeight; // Scroll to the bottom
        });

        // Create "Scroll to head" button
        var scrollUpButton = E(
            'button',
            {
                id: 'scrollUpButton',
                class: 'cbi-button cbi-button-neutral'
            },
            _('Scroll to head', 'Scroll to top (the head) of the log file')
        );

        // Add click event to "Scroll to head" button
        scrollUpButton.addEventListener('click', function () {
            textarea.scrollTop = 0; // Scroll to the top
        });

        // Return the rendered content as a DOM structure
        return E([], [
            // Header for the log section
            E('h2', {}, [_('System Log')]),

            // Container for the system log
            E('div', { id: 'content_syslog' }, [
                // Button and checkboxes row
                E('div', { style: 'padding-bottom: 20px; display: flex; align-items: center;' }, [
                    scrollDownButton,
                    E('div', { style: 'margin-left: 20px;' }, [
                        invertCheckbox,
                        invertLabel,
                        E('span', { style: 'margin-left: 10px;' }, []), // Add spacing between checkboxes
                        autoscrollCheckbox,
                        autoscrollLabel,
                        E('span', { style: 'margin-left: 10px;' }, []),
                        autoupdateCheckbox,
                        autoupdateLabel
                    ])
                ]),

                // Text area to display the log data
                textarea,

                // Button to scroll to the top of the log
                E('div', { style: 'padding-bottom: 20px' }, [scrollUpButton])
            ])
        ]);
    },

    // Placeholder for handleSaveApply method (not implemented)
    handleSaveApply: null,

    // Placeholder for handleSave method (not implemented)
    handleSave: null
});

I think reversing the syslog as suggested will be a bit odd, but the ability to remove the time stamp and or highlight txt or modify colors for specific hardware or software would be cool.
hide everything but vpn stuff that sort of thing would be cool.

The code seems to work OK, but the dialogue box created needs to have an initial size set.

Couldn't you just add a toggle button top right that stores it's state in the browser? And then both sides are happy? No need for custom patches.

which controls what?