Creating a new luci page and calling uci values

I need to create a new page under the system tab. I have copied
/www/luci-static/resources/view/system/system.js to /www/luci-static/resources/view/system/foo.js

In foo,js I have

'use strict';
'require view';
'require poll';
'require ui';
'require uci';
'require rpc';
'require form';
'require tools.widgets as widgets';
var callInitList, callInitAction, callGetLocaltime, callSetLocaltime, CBILocalTime;
callInitList = rpc.declare({
    object: 'luci',
    method: 'getInitList',
    params: ['name'],
    expect: {
        '': {}
    },
    filter: function(res) {
        for (var k in res)
            return +res[k].enabled;
        return null;
    }
});
callInitAction = rpc.declare({
    object: 'luci',
    method: 'setInitAction',
    params: ['name', 'action'],
    expect: {
        result: false
    }
});
callGetLocaltime = rpc.declare({
    object: 'system',
    method: 'info',
    expect: {
        localtime: 0
    }
});
callSetLocaltime = rpc.declare({
    object: 'luci',
    method: 'setLocaltime',
    params: ['localtime'],
    expect: {
        result: 0
    }
});

function formatTime(epoch) {
    var date = new Date(epoch * 1000);
    return '%04d-%02d-%02d %02d:%02d:%02d'.format(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
}
CBILocalTime = form.DummyValue.extend({
    renderWidget: function(section_id, option_id, cfgvalue) {
        return E([], [E('input', {
            'id': 'localtime',
            'type': 'text',
            'readonly': true,
            'value': formatTime(cfgvalue)
        }), E('br'), E('span', {
            'class': 'control-group'
        }, [E('button', {
            'class': 'cbi-button cbi-button-apply',
            'click': ui.createHandlerFn(this, function() {
                return callSetLocaltime(Math.floor(Date.now() / 1000));
            }),
            'disabled': (this.readonly != null) ? this.readonly : this.map.readonly
        }, _('Sync with browser')), ' ', this.ntpd_support ? E('button', {
            'class': 'cbi-button cbi-button-apply',
            'click': ui.createHandlerFn(this, function() {
                return callInitAction('sysntpd', 'restart');
            }),
            'disabled': (this.readonly != null) ? this.readonly : this.map.readonly
        }, _('Sync with NTP-Server')) : ''])]);
    },
});
return view.extend({
    load: function() {
        return Promise.all([callInitList('sysntpd'), callGetLocaltime(), uci.load('luci'), uci.load('system'), uci.load('foo')]);
    },
    render: function(rpc_replies) {
        var ntpd_enabled = rpc_replies[0],
            localtime = rpc_replies[2],
            m, s, o;
        m = new form.Map('system', _('Foo'), _(''));
        m.chain('luci');
        s = m.section(form.TypedSection, 'system', _(''));
        s.anonymous = true;
        s.addremove = false;
//        s.tab('general', _('General Settings'));
//        s.tab('logging', _('Logging'));
//        s.tab('timesync', _('Time Synchronization'));
//        s.tab('language', _('Language and Style'));

            o = s.option(form.Flag, 'enabled', _(''));
            o.rmempty = false;
            o.ucisection = 'ntp';
            o.default = o.disabled;
            o.write = function(section_id, value) {
                ntpd_enabled = +value;
                if (ntpd_enabled && !uci.get('system', 'ntp')) {
                    uci.add('system', 'timeserver', 'ntp');
                    uci.set('system', 'ntp', 'server', default_servers);
                }
                if (!ntpd_enabled)
                    uci.set('system', 'ntp', 'enabled', 0);
                else
                    uci.unset('system', 'ntp', 'enabled');
                return callInitAction('sysntpd', 'enable');
            };
            o.load = function(section_id) {
                return (ntpd_enabled == 1 && uci.get('system', 'ntp') != null && uci.get('system', 'ntp', 'enabled') != 0) ? '1' : '0';
            };

        o = s.option(form.Value,'macaddress',_('Mac Address'));
        o.datatype='macaddress';

        o.optional = true;
        o.value('UTC');
            o.uciconfig = 'luci';
        o.ucisection = 'main';
        o.ucioption = 'lang';
        o.value('auto');
        var l = Object.assign({
                en: 'English'
            }, uci.get('luci', 'languages')),
            k = Object.keys(l).sort();
        for (var i = 0; i < k.length; i++)
            if (k[i].charAt(0) != '.')
                o.value(uci.get('luci', 'themes', k[i]), k[i]);
        if (L.hasSystemFeature('sysntpd')) {
            var default_servers = ['0.openwrt.pool.ntp.org', '1.openwrt.pool.ntp.org', '2.openwrt.pool.ntp.org', '3.openwrt.pool.ntp.org'];
//            o = s.taboption('timesync', widgets.NetworkSelect, 'interface', _('Bind NTP server'), _('Provide the NTP server to the selected interface or, if unspecified, to all interfaces'));
            o.ucisection = 'ntp';
            o.depends('enable_server', '1');
            o.multiple = false;
            o.nocreate = true;
            o.optional = true;
//            o = s.taboption('timesync', form.DynamicList, 'server', _('NTP server candidates'));
            o.datatype = 'host(0)';
            o.ucisection = 'ntp';
            o.depends('enabled', '1');
            o.load = function(section_id) {
                return uci.get('system', 'ntp', 'server');
            };
        }
        return m.render().then(function(mapEl) {
            poll.add(function() {
                return callGetLocaltime().then(function(t) {
                    mapEl.querySelector('#localtime').value = formatTime(t);
                });
            });
            return mapEl;
        });
    }
});

What i would like is for

        o = s.option(form.Value,'macaddress',_('Mac Address'));
        o.datatype='macaddress';

To display the uci value of
foo.device.mac

How do i do this?

Also out of my sample page which bits can be removed so it's clean as i don't need the standard system stuff like ntp.

When you create a map, create it for "foo", not "system", when you create a named section, create it for "device", not "system" and then your option should be for "mac", not "macaddress".

Don't forget to edit the ACL file to grant access to the "foo" uci config.

Do you mean this line

m = new form.Map('system', _('Foo'), _(''));

if so what should it read?

Yes i've previously added acl for foo.

In your case it should be m = new form.Map('foo', _('Foo'), _(''));.

Well, I don't think you need the third parameter if the description is empty and unless you will also be providing translations to different languages or sending a PR for your package to the OpenWrt luci repository, it should actually be m = new form.Map('foo', 'Foo' );

made that change now the page only displays

This section contains no values yet

So I have

    `m = new form.Map('foo', 'Foo' );`

and

    o = s.option(form.Value,'device',_('Mac Address'));
    o.datatype='mac';

(and different combinations of) but it still doesn't work, any idea where i'm going wrong?

Ok using the below the mac value now shows but the mac address box appears 4 times on the page instead of once?

'use strict';
'require view';
'require poll';
'require ui';
'require uci';
'require rpc';
'require form';
'require tools.widgets as widgets';
var callInitList, callInitAction, callGetLocaltime, callSetLocaltime, CBILocalTime;
callInitList = rpc.declare({
    object: 'luci',
    method: 'getInitList',
    params: ['name'],
    expect: {
        '': {}
    },
    filter: function(res) {
        for (var k in res)
            return +res[k].enabled;
        return null;
    }
});
callInitAction = rpc.declare({
    object: 'luci',
    method: 'setInitAction',
    params: ['name', 'action'],
    expect: {
        result: false
    }
});
callGetLocaltime = rpc.declare({
    object: 'system',
    method: 'info',
    expect: {
        localtime: 0
    }
});
callSetLocaltime = rpc.declare({
    object: 'luci',
    method: 'setLocaltime',
    params: ['localtime'],
    expect: {
        result: 0
    }
});

return view.extend({
    load: function() {
        return Promise.all([uci.load('luci'), uci.load('foo')]);
    },
    render: function(rpc_replies) {
        var ntpd_enabled = rpc_replies[0],
            localtime = rpc_replies[2],
            m, s, o;
        m = new form.Map('foo', _('Foo'),);
        m.chain('luci');
        s = m.section(form.TypedSection, 'foo');
        s.anonymous = true;
        s.addremove = false;
//        s.tab('general', _('General Settings'));
//        s.tab('logging', _('Logging'));
//        s.tab('timesync', _('Time Synchronization'));
//        s.tab('language', _('Language and Style'));

            o = s.option(form.Flag, 'enabled', _(''));
            o.rmempty = false;
            o.ucisection = 'ntp';
            o.default = o.disabled;
            o.write = function(section_id, value) {
                ntpd_enabled = +value;
                if (ntpd_enabled && !uci.get('system', 'ntp')) {
                    uci.add('system', 'timeserver', 'ntp');
                    uci.set('system', 'ntp', 'server', default_servers);
                }
                if (!ntpd_enabled)
                    uci.set('system', 'ntp', 'enabled', 0);
                else
                    uci.unset('system', 'ntp', 'enabled');
                return callInitAction('sysntpd', 'enable');
            };
            o.load = function(section_id) {
                return (ntpd_enabled == 1 && uci.get('system', 'ntp') != null && uci.get('system', 'ntp', 'enabled') != 0) ? '1' : '0';
            };

        o = s.option(form.Value,'mac',_('Device MAC Address'));
        o.datatype='device';

        o.optional = true;
        o.value('UTC');
            o.uciconfig = 'luci';
        o.ucisection = 'main';
        o.ucioption = 'lang';
        o.value('auto');
        var l = Object.assign({
                en: 'English'
            }, uci.get('luci', 'languages')),
            k = Object.keys(l).sort();
        for (var i = 0; i < k.length; i++)
            if (k[i].charAt(0) != '.')
                o.value(uci.get('luci', 'themes', k[i]), k[i]);
        if (L.hasSystemFeature('sysntpd')) {
            var default_servers = ['0.openwrt.pool.ntp.org', '1.openwrt.pool.ntp.org', '2.openwrt.pool.ntp.org', '3.openwrt.pool.ntp.org'];
//            o = s.taboption('timesync', widgets.NetworkSelect, 'interface', _('Bind NTP server'), _('Provide the NTP server to the selected interface or, if unspecified, to all interfaces'));
            o.ucisection = 'ntp';
            o.depends('enable_server', '1');
            o.multiple = false;
            o.nocreate = true;
            o.optional = true;
//            o = s.taboption('timesync', form.DynamicList, 'server', _('NTP server candidates'));
            o.datatype = 'host(0)';
            o.ucisection = 'ntp';
            o.depends('enabled', '1');
            o.load = function(section_id) {
                return uci.get('system', 'ntp', 'server');
            };
        }
        return m.render().then(function(mapEl) {
            poll.add(function() {
                return callGetLocaltime().then(function(t) {
                    mapEl.querySelector('#localtime').value = formatTime(t);
                });
            });
            return mapEl;
        });
    }
});

You didn't follow below:

I believe there's a luci-app-template in the repo, based on your attempts/questions, you might be much better off starting with that than trying to adapt a somewhat complex luci app to your needs.

You need to create a Luci App.
See the example app which has a readme with explanation: