1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
'use strict';
'require view';
'require dom';
'require poll';
'require uci';
'require rpc';
'require form';
var callInitAction, callUpnpGetStatus, callUpnpDeleteRule, handleDelRule;
callInitAction = rpc.declare({
object: 'luci',
method: 'setInitAction',
params: [ 'name', 'action' ],
expect: { result: false }
});
callUpnpGetStatus = rpc.declare({
object: 'luci.upnp',
method: 'get_status',
expect: { }
});
callUpnpDeleteRule = rpc.declare({
object: 'luci.upnp',
method: 'delete_rule',
params: [ 'token' ],
expect: { result : "OK" },
});
handleDelRule = function(num, ev) {
dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
ev.currentTarget.classList.add('spinning');
ev.currentTarget.disabled = true;
ev.currentTarget.blur();
callUpnpDeleteRule(num);
};
return view.extend({
load: function() {
return Promise.all([
callUpnpGetStatus(),
uci.load('upnpd')
]);
},
poll_status: function(nodes, data) {
var rules = Array.isArray(data[0].rules) ? data[0].rules : [];
var rows = rules.map(function(rule) {
return [
rule.host_hint || _('Unknown'),
rule.intaddr,
rule.intport,
rule.extport,
rule.proto,
rule.descr,
E('button', {
'class': 'btn cbi-button-remove',
'click': L.bind(handleDelRule, this, rule.num)
}, [ _('Delete') ])
];
});
cbi_update_table(nodes.querySelector('#upnp_status_table'), rows, E('em', _('There are no active port maps.')));
return;
},
render: function(data) {
var m, s, o;
var protocols = '%s & %s/%s'.format(
'<a href="https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol" target="_blank" rel="noreferrer"><abbr title="UPnP Internet Gateway Device (Control Protocol)">UPnP IGD</abbr></a>',
'<a href="https://en.wikipedia.org/wiki/Port_Control_Protocol" target="_blank" rel="noreferrer"><abbr title="Port Control Protocol">PCP</abbr></a>',
'<a href="https://en.wikipedia.org/wiki/NAT_Port_Mapping_Protocol" target="_blank" rel="noreferrer"><abbr title="NAT Port Mapping Protocol">NAT-PMP</abbr></a>');
m = new form.Map('upnpd', [_('UPnP IGD & PCP/NAT-PMP Service')],
_('The %s protocols allow clients on the local network to configure port maps/forwards on the router autonomously.',
'The %s (%s = UPnP IGD & PCP/NAT-PMP) protocols allow clients on the local network to configure port maps/forwards on the router autonomously.')
.format(protocols)
);
s = m.section(form.GridSection, '_active_rules');
s.render = L.bind(function(view, section_id) {
var table = E('table', { 'class': 'table cbi-section-table', 'id': 'upnp_status_table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Client Name')),
E('th', { 'class': 'th' }, _('Client Address')),
E('th', { 'class': 'th' }, _('Client Port')),
E('th', { 'class': 'th' }, _('External Port')),
E('th', { 'class': 'th' }, _('Protocol')),
E('th', { 'class': 'th' }, _('Description')),
E('th', { 'class': 'th cbi-section-actions' }, '')
])
]);
var rules = Array.isArray(data[0].rules) ? data[0].rules : [];
var rows = rules.map(function(rule) {
return [
rule.host_hint || _('Unknown'),
rule.intaddr,
rule.intport,
rule.extport,
rule.proto,
rule.descr,
E('button', {
'class': 'btn cbi-button-remove',
'click': L.bind(handleDelRule, this, rule.num)
}, [ _('Delete') ])
];
});
cbi_update_table(table, rows, E('em', _('There are no active port maps.')));
return E('div', { 'class': 'cbi-section cbi-tblsection' }, [
E('h3', _('Active Service Port Maps')), table ]);
}, o, this);
s = m.section(form.NamedSection, 'config', 'upnpd', _('Service Settings'));
s.addremove = false;
s.tab('setup', _('Service Setup'));
s.tab('advanced', _('Advanced Settings'));
o = s.taboption('setup', form.Flag, 'enabled', _('Start service'),
_('Start autonomous port mapping service'));
o.rmempty = false;
s.taboption('setup', form.Flag, 'enable_upnp', _('Enable UPnP IGD protocol')).default = '1';
s.taboption('setup', form.Flag, 'enable_natpmp', _('Enable PCP/NAT-PMP protocols')).default = '1';
o = s.taboption('setup', form.Flag, 'igdv1', _('UPnP IGDv1 compatibility mode'),
_('Advertise as IGDv1 (IPv4 only) device instead of IGDv2'));
o.default = '1';
o.rmempty = false;
o.depends('enable_upnp', '1');
o = s.taboption('setup', form.Value, 'download', _('Download speed'),
_('Report maximum download speed in kByte/s'));
o.depends('enable_upnp', '1');
o = s.taboption('setup', form.Value, 'upload', _('Upload speed'),
_('Report maximum upload speed in kByte/s'));
o.depends('enable_upnp', '1');
s.taboption('advanced', form.Flag, 'use_stun', _('Use %s', 'Use %s (%s = STUN)')
.format('<a href="https://en.wikipedia.org/wiki/STUN" target="_blank" rel="noreferrer"><abbr title="Session Traversal Utilities for NAT">STUN</abbr></a>'),
_('To detect the public IPv4 address for unrestricted full-cone/one-to-one NATs'));
o = s.taboption('advanced', form.Value, 'stun_host', _('STUN host'));
o.depends('use_stun', '1');
o.datatype = 'host';
o = s.taboption('advanced', form.Value, 'stun_port', _('STUN port'));
o.depends('use_stun', '1');
o.datatype = 'port';
o.placeholder = '3478';
o = s.taboption('advanced', form.Flag, 'secure_mode', _('Enable secure mode'),
_('Allow adding port maps for requesting IP addresses only'));
o.default = '1';
o.depends('enable_upnp', '1');
o = s.taboption('advanced', form.Value, 'notify_interval', _('Notify interval'),
_('A 900s interval will result in %s notifications with the minimum max-age of 1800s', 'A 900s interval will result in %s (%s = SSDP) notifications with the minimum max-age of 1800s')
.format('<abbr title="Simple Service Discovery Protocol">SSDP</abbr>'));
o.datatype = 'uinteger';
o.placeholder = '900';
o.depends('enable_upnp', '1');
o = s.taboption('advanced', form.Value, 'port', _('SOAP/HTTP port'));
o.datatype = 'port';
o.placeholder = '5000';
o.depends('enable_upnp', '1');
o = s.taboption('advanced', form.Value, 'presentation_url', _('Presentation URL'),
_('Report custom router web interface (presentation) URL'));
o.placeholder = 'http://192.168.1.1/';
o.depends('enable_upnp', '1');
o = s.taboption('advanced', form.Value, 'uuid', _('Device UUID'));
o.depends('enable_upnp', '1');
o = s.taboption('advanced', form.Value, 'model_number', _('Announced model number'));
o.depends('enable_upnp', '1');
o = s.taboption('advanced', form.Value, 'serial_number', _('Announced serial number'));
o.depends('enable_upnp', '1');
o = s.taboption('advanced', form.Flag, 'system_uptime', _('Report system instead of service uptime'));
o.default = '1';
o.depends('enable_upnp', '1');
s.taboption('advanced', form.Flag, 'log_output', _('Enable additional logging'),
_('Puts extra debugging information into the system log'));
o = s.taboption('advanced', form.Value, 'upnp_lease_file', _('Service lease file'));
o.placeholder = '/var/run/miniupnpd.leases';
s = m.section(form.GridSection, 'perm_rule', _('Service Access Control List'),
_('ACL specify which client addresses and ports can be mapped, IPv6 always allowed.'));
s.sortable = true;
s.anonymous = true;
s.addremove = true;
s.option(form.Value, 'comment', _('Comment'));
o = s.option(form.Value, 'int_addr', _('Client Address'));
o.datatype = 'ip4addr';
o.placeholder = '0.0.0.0/0';
o = s.option(form.Value, 'int_ports', _('Client Port'));
o.datatype = 'portrange';
o.placeholder = '1-65535';
o = s.option(form.Value, 'ext_ports', _('External Port'));
o.datatype = 'portrange';
o.placeholder = '1-65535';
o = s.option(form.ListValue, 'action', _('Action'));
o.value('allow', _('Allow'));
o.value('deny', _('Deny'));
return m.render().then(L.bind(function(m, nodes) {
poll.add(L.bind(function() {
return Promise.all([
callUpnpGetStatus()
]).then(L.bind(this.poll_status, this, nodes));
}, this), 5);
return nodes;
}, this, m));
}
});
|