luci-mod-system: implement plugin UI architecture

include some example plugins also.
JS files provide UI to configure behaviour of plugins
which typically live in

/usr/share/ucode/luci/plugins/<class>/<type>

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
Paul Donald
2026-02-04 21:15:32 +01:00
parent a5bedae648
commit 617f364333
12 changed files with 431 additions and 2 deletions

View File

@@ -0,0 +1,19 @@
#
# Copyright (C) 2026
#
# SPDX-License-Identifier: Apache-2.0
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Plugins - HTTP Headers examples and HTTP 2FA UI example
LUCI_DEPENDS:=+luci-base +luci-mod-system
LUCI_TYPE:=plugin
PKG_LICENSE:=Apache-2.0
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,44 @@
'use strict';
'require baseclass';
'require form';
/*
class, type, name and id are used to build a reference for the uci config. E.g.
config http_headers '0aef1fa8f9a045bdaf51a35ce99eb5c5'
option name 'X-Foobar'
...
*/
return baseclass.extend({
class: 'http',
class_i18n: _('HTTP'),
type: 'headers',
type_i18n: _('Headers'),
name: 'X-Foobar', // to make visual ID in UCI config easy
id: '0aef1fa8f9a045bdaf51a35ce99eb5c5', // cat /proc/sys/kernel/random/uuid | tr -d -
title: _('X-Foobar Example Plugin'),
description: _('This plugin sets an X-Foobar HTTP header.'),
addFormOptions(s) {
let o;
o = s.option(form.Flag, 'enabled', _('Enabled'));
o = s.option(form.Value, 'foo', _('Foo'));
o.default = 'foo';
o.depends('enabled', '1');
o = s.option(form.Value, 'bar', _('Bar'));
o.default = '4000';
o.depends('enabled', '1');
},
configSummary(section) {
return _('I am class %s, type %s, name %s, bar: %d').format(this.class_i18n, this.type_i18n, this.name, section.bar || 1000);
}
});

View File

@@ -0,0 +1,44 @@
'use strict';
'require baseclass';
'require form';
/*
class, type, name and id are used to build a reference for the uci config. E.g.
config http_headers '263fe72d7e834fa99a82639ed0d9e3bd'
option name 'X-Example'
...
*/
return baseclass.extend({
class: 'http',
class_i18n: _('HTTP'),
type: 'headers',
type_i18n: _('Headers'),
name: 'X-Example', // to make visual ID in UCI config easy
id: '263fe72d7e834fa99a82639ed0d9e3bd', // cat /proc/sys/kernel/random/uuid | tr -d -
title: _('X-Example Example Plugin'),
description: _('This plugin sets an X-Example HTTP header.'),
addFormOptions(s) {
let o;
o = s.option(form.Flag, 'enabled', _('Enabled'));
o = s.option(form.Value, 'foo', _('Foo'));
o.default = 'foo';
o.depends('enabled', '1');
o = s.option(form.Value, 'bar', _('Bar'));
o.default = '3000';
o.depends('enabled', '1');
},
configSummary(section) {
return _('I am class %s, type %s, name %s, bar: %d').format(this.class_i18n, this.type_i18n, this.name, section.bar || 1000);
}
});

View File

@@ -0,0 +1,44 @@
'use strict';
'require baseclass';
'require form';
/*
class, type, name and id are used to build a reference for the uci config. E.g.
config foo_bar '3ed2ee077c4941f8ab394106fd95ad9d'
option name 'Chonki Boi'
...
*/
return baseclass.extend({
class: 'foo',
class_i18n: _('FOO'),
type: 'bar',
type_i18n: _('Bar'),
name: 'Chonki Boi', // to make visual ID in UCI config easy
id: '3ed2ee077c4941f8ab394106fd95ad9d', // cat /proc/sys/kernel/random/uuid | tr -d -
title: _('Chonki Boi Example Plugin'),
description: _('This plugin does nothing. It is just a UI example.'),
addFormOptions(s) {
let o;
o = s.option(form.Flag, 'enabled', _('Enabled'));
o = s.option(form.Value, 'foo', _('Foo'));
o.default = 'chonkk value';
o.depends('enabled', '1');
o = s.option(form.Value, 'bar', _('Bar'));
o.default = '1000';
o.depends('enabled', '1');
},
configSummary(section) {
return _('I am class %s, type %s, name %s, bar: %d').format(this.class_i18n, this.type_i18n, this.name, section.bar || 1000);
}
});

View File

@@ -0,0 +1,44 @@
'use strict';
'require baseclass';
'require form';
/*
class, type, name and id are used to build a reference for the uci config. E.g.
config http_auth '6c4b5551b62b4bc8a3053fb519d71d5f'
option name '2FA'
...
*/
return baseclass.extend({
class: 'http',
class_i18n: _('HTTP'),
type: 'auth',
type_i18n: _('Auth'),
name: '2FA', // to make visual ID in UCI config easy
id: '6c4b5551b62b4bc8a3053fb519d71d5f', // cat /proc/sys/kernel/random/uuid | tr -d -
title: _('2FA Example Plugin'),
description: _('This plugin does nothing. It is just a UI example.'),
addFormOptions(s) {
let o;
o = s.option(form.Flag, 'enabled', _('Enabled'));
o = s.option(form.Value, 'foo', _('Foo'));
o.default = '2FA value';
o.depends('enabled', '1');
o = s.option(form.Value, 'bar', _('Bar'));
o.default = '2000';
o.depends('enabled', '1');
},
configSummary(section) {
return _('I am class %s, type %s, name %s, bar: %d').format(this.class_i18n, this.type_i18n, this.name, section.bar || 1000);
}
});

View File

@@ -0,0 +1,31 @@
// Copyright 2026
// SPDX-License-Identifier: Apache-2.0
/*
The plugin filename shall be the 32 character uuid in its JS config front-end.
This allows parsing plugins against user-defined configuration. User retains
all control over whether a plugin is active or not.
*/
'use strict';
import { cursor } from 'uci';
/*
The ucode plugin portion shall return a default action which returns a value
and type of value appropriate for its usage class and type. For http.headers,
it shall return a string array[] with header_name, header_value, without any
\r or \n.
*/
function default_action(...args) {
const uci = cursor();
const str = uci.get('luci_plugins', args[0], 'bar') || '4000';
const value = sprintf('%s; %s', str, ...args);
// do stuff
// should produce: x-foobar: 4000; 0aef1fa8f9a045bdaf51a35ce99eb5c5
return ['X-Foobar', value];
};
return default_action;

View File

@@ -0,0 +1,31 @@
// Copyright 2026
// SPDX-License-Identifier: Apache-2.0
/*
The plugin filename shall be the 32 character uuid in its JS config front-end.
This allows parsing plugins against user-defined configuration. User retains
all control over whether a plugin is active or not.
*/
'use strict';
import { cursor } from 'uci';
/*
The ucode plugin portion shall return a default action which returns a value
and type of value appropriate for its usage class and type. For http.headers,
it shall return a string array[] with header_name, header_value, without any
\r or \n.
*/
function default_action(...args) {
const uci = cursor();
const str = uci.get('luci_plugins', args[0], 'foo') || 'foo';
const value = sprintf('%s; %s', str, ...args);
// do stuff
// should produce: x-example: foo; 263fe72d7e834fa99a82639ed0d9e3bd
return ['X-Example', value];
};
return default_action;