Luci Lua programming web interface help/doc?

So I am trying to modify/add some feature to the existing Luci interface. I am using this version of OpenWRT:

And specifically, I am trying to modify the firewall section of Luci. I've added a .js file (domainfilter.js) which I originally copy the forwards.js (in the /feeds/luci/applications/luci-app-firewall/htdocs/luci-static/resource/view/firewall/ folder) and take out alot of things I don't need there. I am VERY NEW to modifying openwrt code and not too familiar with Lua (I assume this is what it is using) programming language.

My first question is that is there ANY detail documentation as for thing such as declaring components such as grid,textbox, check box. I mean I figured out quite a bit from messing around witht he forwards.js. But just wondering if there is a more "complete" documentation which detail all avaliable component and their properties? Also the "events"?

So if you look a the code below. Where can I program the "event" which trigger by user pressing the "save and apply" button? What I would like to do is to be able to modify external "dnsmasq.conf" file under the /etc/config folder of the router. I am trying to create a place where user can build a "black/white list" to do dns filtering. Meaning only allow/block certain url lookup (so that user can or can not go certain web sites).

Any help will be GREATLY appreciated.

Thank you!

'use strict';
'require view';
'require ui';
'require rpc';
'require uci';
'require form';
'require firewall as fwmodel';
'require tools.firewall as fwtool';
'require tools.widgets as widgets';

function rule_proto_txt(s, ctHelpers) {
	var family = (uci.get('firewall', s, 'family') || '').toLowerCase().replace(/^(?:any|\*)$/, '');
	var dip = uci.get('firewall', s, 'dest_ip') || ''
	var proto = L.toArray(uci.get('firewall', s, 'proto')).filter(function(p) {
		return (p != '*' && p != 'any' && p != 'all');
	}).map(function(p) {
		var pr = fwtool.lookupProto(p);
		return {
			num:   pr[0],
			name:  pr[1],
			types: (pr[0] == 1 || pr[0] == 58) ? L.toArray(uci.get('firewall', s, 'icmp_type')) : null
		};
	});

	m = String(uci.get('firewall', s, 'helper') || '').match(/^(!\s*)?(\S+)$/);
	var h = m ? {
		val:  m[0].toUpperCase(),
		inv:  m[1],
		name: (ctHelpers.filter(function(ctH) { return ctH.name.toLowerCase() == m[2].toLowerCase() })[0] || {}).description
	} : null;

	m = String(uci.get('firewall', s, 'mark')).match(/^(!\s*)?(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);
	var f = m ? {
		val:  m[0].toUpperCase().replace(/X/g, 'x'),
		inv:  m[1],
		num:  '0x%02X'.format(+m[2]),
		mask: m[3] ? '0x%02X'.format(+m[3]) : null
	} : null;

	return fwtool.fmt(_('Incoming %{ipv6?%{ipv4?<var>IPv4</var> and <var>IPv6</var>:<var>IPv6</var>}:<var>IPv4</var>}%{proto?, protocol %{proto#%{next?, }%{item.types?<var class="cbi-tooltip-container">%{item.name}<span class="cbi-tooltip">ICMP with types %{item.types#%{next?, }<var>%{item}</var>}</span></var>:<var>%{item.name}</var>}}}%{mark?, mark <var%{mark.inv? data-tooltip="Match fwmarks except %{mark.num}%{mark.mask? with mask %{mark.mask}}.":%{mark.mask? data-tooltip="Mask fwmark value with %{mark.mask} before compare."}}>%{mark.val}</var>}%{helper?, helper %{helper.inv?<var data-tooltip="Match any helper except &quot;%{helper.name}&quot;">%{helper.val}</var>:<var data-tooltip="%{helper.name}">%{helper.val}</var>}}'), {
		ipv4: ((!family && (dip.indexOf(':') == -1)) || family == 'ipv4'),
		ipv6: ((!family && (dip.indexOf(':') != -1)) || family == 'ipv6'),
		proto: proto,
		helper: h,
		mark:   f
	});
}

function rule_src_txt(s, hosts) {
	var z = uci.get('firewall', s, 'src');

	return fwtool.fmt(_('From %{src}%{src_ip?, IP %{src_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{src_port?, port %{src_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}%{src_mac?, MAC %{src_mac#%{next?, }<var%{item.inv? data-tooltip="Match MACs except %{item.val}%{item.hint.name? a.k.a. %{item.hint.name}}.":%{item.hint.name? data-tooltip="%{item.hint.name}"}}>%{item.ival}</var>}}'), {
		src: E('span', { 'class': 'zonebadge', 'style': fwmodel.getZoneColorStyle(z) }, [(z == '*') ? E('em', _('any zone')) : (z ? E('strong', z) : E('em', _('this device')))]),
		src_ip: fwtool.map_invert(uci.get('firewall', s, 'src_ip'), 'toLowerCase'),
		src_mac: fwtool.map_invert(uci.get('firewall', s, 'src_mac'), 'toUpperCase').map(function(v) { return Object.assign(v, { hint: hosts[v.val] }) }),
		src_port: fwtool.map_invert(uci.get('firewall', s, 'src_port'))
	});
}

function rule_dest_txt(s) {
	return fwtool.fmt(_('To %{dest}%{dest_ip?, IP %{dest_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{dest_port?, port %{dest_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}'), {
		dest: E('span', { 'class': 'zonebadge', 'style': fwmodel.getZoneColorStyle(null) }, [E('em', _('this device'))]),
		dest_ip: fwtool.map_invert(uci.get('firewall', s, 'src_dip'), 'toLowerCase'),
		dest_port: fwtool.map_invert(uci.get('firewall', s, 'src_dport'))
	});
}

function rule_limit_txt(s) {
	var m = String(uci.get('firewall', s, 'limit')).match(/^(\d+)\/([smhd])\w*$/i),
	    l = m ? {
			num:   +m[1],
			unit:  ({ s: _('second'), m: _('minute'), h: _('hour'), d: _('day') })[m[2]],
			burst: uci.get('firewall', s, 'limit_burst')
		} : null;

	if (!l)
		return '';

	return fwtool.fmt(_('Limit matching to <var>%{limit.num}</var> packets per <var>%{limit.unit}</var>%{limit.burst? burst <var>%{limit.burst}</var>}'), { limit: l });
}

function rule_target_txt(s) {
	var z = uci.get('firewall', s, 'dest');

	return fwtool.fmt(_('<var data-tooltip="DNAT">Forward</var> to %{dest}%{dest_ip? IP <var>%{dest_ip}</var>}%{dest_port? port <var>%{dest_port}</var>}'), {
		dest: E('span', { 'class': 'zonebadge', 'style': 'background-color:' + fwmodel.getColorForName((z && z != '*') ? z : null) }, [(z == '*') ? E('em', _('any zone')) : (z ? E('strong', z) : E('em', _('this device')))]),
		dest_ip: (uci.get('firewall', s, 'dest_ip') || '').toLowerCase(),
		dest_port: uci.get('firewall', s, 'dest_port')
	});
}

function validate_opt_family(m, section_id, opt) {
	var dopt = m.section.getOption('dest_ip'),
	    fmopt = m.section.getOption('family');

	if (!dopt.isValid(section_id) && opt != 'dest_ip')
		return true;
	if (!fmopt.isValid(section_id) && opt != 'family')
		return true;

	var dip = dopt.formvalue(section_id) || '',
	    fm = fmopt.formvalue(section_id) || '';

	if (fm == '' || (fm == 'ipv6' && (dip.indexOf(':') != -1 || dip == '')) || (fm == 'ipv4' && dip.indexOf(':') == -1))
		return true;

	return _('Address family, Internal IP address must match');
}

return view.extend({
	callHostHints: rpc.declare({
		object: 'luci-rpc',
		method: 'getHostHints',
		expect: { '': {} }
	}),

	callConntrackHelpers: rpc.declare({
		object: 'luci',
		method: 'getConntrackHelpers',
		expect: { result: [] }
	}),

	callNetworkDevices: rpc.declare({
		object: 'luci-rpc',
		method: 'getNetworkDevices',
		expect: { '': {} }
	}),

	load: function() {
		return Promise.all([
			this.callHostHints(),
			this.callConntrackHelpers(),
			this.callNetworkDevices(),
			uci.load('firewall')
		]);
	},

	render: function(data) {
		if (fwtool.checkLegacySNAT())
			return fwtool.renderMigration();
		else
			return this.renderForwards(data);
	},

	renderForwards: function(data) {
		var hosts = data[0],
		    ctHelpers = data[1],
		    devs = data[2],
		    m, s, o;
		var fw4 = L.hasSystemFeature('firewall4');

		m = new form.Map('firewall', _('Firewall - Domain Filtering'),
			_('Domain Filtering for traffic going out of WAN'));

		s = m.section(form.TypedSection, 'domainfiltering', _('General Settings'));
		s.anonymous = true;
		s.addremove = false;

		o = s.option(form.Flag, 'enable', _('Enable'));

		var p = [
			s.option(form.ListValue, 'input', _('Input'))
		];

		for (var i = 0; i < p.length; i++) {
			p[i].value('blacklist', _('blacklist'));
			p[i].value('whitelist', _('whitelist'));
		}
		
		s = m.section(form.GridSection, 'domainfiltering', _('Domain Filtering'));
		s.addremove = true;
		s.anonymous = true;
		s.sortable  = true;

		s.tab('general', _('General Settings'));

		o = s.taboption('general', form.Value, 'name', _('Name'));
		o.placeholder = _('Description');

		o = s.taboption('general', form.Value, 'domain', _('Domain'));
		o.placeholder = _('domain url');

		return m.render();
	}
});

Your post subject says Lua, but you're not actually using lua, that's confusing.

You shouldn't place non-uci-compatible files in /etc/config/ as it may result in uci errors when running any uci command.

Why not use the DNS Forwardings part of Network->DHCP & DNS->General Settings page which already allows setting domain names to be allowed/blocked?

If I was able to find information via Google, I wouldn't be asking here. Here is ALWAYS my LAST RESORT as I understand people here would say this. I am not lazy, I tried to my time as much as possible. I know OpenWRT is a opensource thus resource are limited and information are kind of "scattered" (to me at least). The only kind information I found were

I am looking to learn or see documentaiton about exactly how to start modifing the LUCI source code so that I can do customize thing I want in the firmware

I've also tried to find some deatil documenation as how to use LUCI interface to do what you said in the DHCP/DNS page. But I wasn't able to get it to work:

I've also tried the information here:

https://openwrt.org/docs/guide-user/base-system/dhcp_configuration

image

Which again, it didn't seem to work.

Thus I found the only solution I was able to get to work is to do the following:

First edit /etc/dnsmasq.conf:

Add line (this means to not resolve any DNS)
no-resolv

And then to white list domain add: (this means to use 127.0.0.1 (local itself) as the dns server for domain yahoo.com )
server=/yahoo.com/127.0.0.1

And to avoid user use their own DNS server in the device DNS (when they not use get dns server automatically, such as they program the dns server themselves to use 8.8.8.8)

uci -q delete firewall.dns_int
uci set firewall.dns_int="redirect"
uci set firewall.dns_int.name="Intercept-DNS"
uci set firewall.dns_int.src="lan"
uci set firewall.dns_int.src_dport="53"
uci set firewall.dns_int.proto="tcp udp"
uci set firewall.dns_int.family="any"
uci set firewall.dns_int.target="DNAT"
uci commit firewall
service firewall restart

And that seems to work good where it will also block people trying to do their own DNS (such as 8.8.8.8) to bypass the system.

So I was going to do a custom page where I can modify the dnsmasq.conf file and send these commands via the LUCI GUI.