1 // Copyright 2022 Stan Grishin <stangri@melmac.ca>
2 // This code wouldn't have been possible without help from [@vsviridov](https://github.com/vsviridov)
14 return "https://docs.openwrt.melmac.net/" + pkg
.Name
+ "/";
18 var getGateways
= rpc
.declare({
19 object
: "luci." + pkg
.Name
,
20 method
: "getGateways",
24 var getInitList
= rpc
.declare({
25 object
: "luci." + pkg
.Name
,
26 method
: "getInitList",
30 var getInitStatus
= rpc
.declare({
31 object
: "luci." + pkg
.Name
,
32 method
: "getInitStatus",
36 var getInterfaces
= rpc
.declare({
37 object
: "luci." + pkg
.Name
,
38 method
: "getInterfaces",
42 var getPlatformSupport
= rpc
.declare({
43 object
: "luci." + pkg
.Name
,
44 method
: "getPlatformSupport",
48 var _setInitAction
= rpc
.declare({
49 object
: "luci." + pkg
.Name
,
50 method
: "setInitAction",
51 params
: ["name", "action"],
52 expect
: { result
: false },
57 on: function (event
, callback
) {
58 var pair
= { event
: event
, callback
: callback
};
59 this.listeners
.push(pair
);
60 return function unsubscribe() {
61 this.listeners
= this.listeners
.filter(function (listener
) {
62 return listener
!== pair
;
66 emit: function (event
, data
) {
67 this.listeners
.forEach(function (listener
) {
68 if (listener
.event
=== event
) {
69 listener
.callback(data
);
73 getInitList: function (name
) {
74 getInitList(name
).then(
76 this.emit("getInitList", result
);
80 getInitStatus: function (name
) {
81 getInitStatus(name
).then(
83 this.emit("getInitStatus", result
);
87 getGateways: function (name
) {
88 getGateways(name
).then(
90 this.emit("getGateways", result
);
94 getPlatformSupport: function (name
) {
95 getPlatformSupport(name
).then(
97 this.emit("getPlatformSupport", result
);
101 getInterfaces: function (name
) {
102 getInterfaces(name
).then(
104 this.emit("getInterfaces", result
);
108 setInitAction: function (name
, action
) {
109 _setInitAction(name
, action
).then(
111 this.emit("setInitAction", result
);
117 var status
= baseclass
.extend({
118 render: function () {
120 L
.resolveDefault(getInitStatus(pkg
.Name
), {}),
121 // L.resolveDefault(getGateways(pkg.Name), {}),
122 ]).then(function (data
) {
123 // var replyStatus = data[0];
124 // var replyGateways = data[1];
128 if (data
[0] && data
[0][pkg
.Name
]) {
129 reply
= data
[0][pkg
.Name
];
134 running_iptables
: null,
136 running_nft_file
: null,
144 var header
= E("h2", {}, _("Policy Based Routing - Status"));
147 { class: "cbi-value-title" },
151 text
= _("Version %s").format(reply
.version
) + " - ";
153 text
+= _("Running");
154 if (reply
.running_iptables
) {
155 text
+= " (" + _("iptables mode") + ").";
156 } else if (reply
.running_nft_file
) {
157 text
+= " (" + _("fw4 nft file mode") + ").";
158 } else if (reply
.running_nft
) {
159 text
+= " (" + _("nft mode") + ").";
165 text
+= _("Stopped.");
167 text
+= _("Stopped (Disabled).");
171 text
= _("Not installed or not found");
173 var statusText
= E("div", {}, text
);
174 var statusField
= E("div", { class: "cbi-value-field" }, statusText
);
175 var statusDiv
= E("div", { class: "cbi-value" }, [
180 var gatewaysDiv
= [];
181 if (reply
.gateways
) {
182 var gatewaysTitle
= E(
184 { class: "cbi-value-title" },
185 _("Service Gateways")
188 "The %s indicates default gateway. See the %sREADME%s for details."
190 "<strong>✓</strong>",
191 '<a href="' + pkg
.URL
+ '#AWordAboutDefaultRouting" target="_blank">',
194 var gatewaysDescr
= E("div", { class: "cbi-value-description" }, text
);
195 var gatewaysText
= E("div", {}, reply
.gateways
);
196 var gatewaysField
= E("div", { class: "cbi-value-field" }, [
200 gatewaysDiv
= E("div", { class: "cbi-value" }, [
206 var warningsDiv
= [];
207 if (reply
.warnings
&& reply
.warnings
.length
) {
208 var textLabelsTable
= {
209 warningResolverNotSupported
: _(
210 "Resolver set (%s) is not supported on this system."
211 ).format(L
.uci
.get(pkg
.Name
, "config", "resolver_set")),
212 warningAGHVersionTooLow
: _(
213 "Installed AdGuardHome (%s) doesn't support 'ipset_file' option."
215 warningPolicyProcessCMD
: _("%s"),
216 warningTorUnsetParams
: _(
217 "Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'"
219 warningTorUnsetProto
: _(
220 "Please unset 'proto' or set 'proto' to 'all' for policy '%s'"
222 warningTorUnsetChainIpt
: _(
223 "Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'"
225 warningTorUnsetChainNft
: _(
226 "Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'"
228 warningInvalidOVPNConfig
: _(
229 "Invalid OpenVPN config for %s interface"
231 warningOutdatedWebUIApp
: _(
232 "The WebUI application is outdated (version %s), please update it"
234 warningBadNftCallsInUserFile
: _(
235 "Incompatible nft calls detected in user include file, disabling fw4 nft file support."
237 warningDnsmasqInstanceNoConfdir
: _(
238 "Dnsmasq instance (%s) targeted in settings, but it doesn't have its own confdir."
241 var warningsTitle
= E(
243 { class: "cbi-value-title" },
244 _("Service Warnings")
247 reply
.warnings
.forEach((element
) => {
248 if (element
.id
&& textLabelsTable
[element
.id
]) {
249 if (element
.id
!== "warningPolicyProcessCMD") {
251 (textLabelsTable
[element
.id
] + ".").format(
256 text
+= _("Unknown warning") + "<br />";
259 var warningsText
= E("div", {}, text
);
260 var warningsField
= E(
262 { class: "cbi-value-field" },
265 warningsDiv
= E("div", { class: "cbi-value" }, [
272 if (reply
.errors
&& reply
.errors
.length
) {
273 var textLabelsTable
= {
274 errorConfigValidation
: _("Config (%s) validation failure").format(
275 "/etc/config/" + pkg
.Name
277 errorNoIpFull
: _("%s binary cannot be found").format("ip-full"),
278 errorNoIptables
: _("%s binary cannot be found").format("iptables"),
280 "Resolver set support (%s) requires ipset, but ipset binary cannot be found"
281 ).format(L
.uci
.get(pkg
.Name
, "config", "resolver_set")),
283 "Resolver set support (%s) requires nftables, but nft binary cannot be found"
284 ).format(L
.uci
.get(pkg
.Name
, "config", "resolver_set")),
285 errorResolverNotSupported
: _(
286 "Resolver set (%s) is not supported on this system"
287 ).format(L
.uci
.get(pkg
.Name
, "config", "resolver_set")),
288 errorServiceDisabled
: _(
289 "The %s service is currently disabled"
291 errorNoWanGateway
: _(
292 "The %s service failed to discover WAN gateway"
294 errorNoWanInterface
: _(
295 "The %s inteface not found, you need to set the 'pbr.config.procd_wan_interface' option"
297 errorNoWanInterfaceHint
: _(
298 "Refer to https://docs.openwrt.melmac.net/pbr/#procd_wan_interface"
300 errorIpsetNameTooLong
: _(
301 "The ipset name '%s' is longer than allowed 31 characters"
303 errorNftsetNameTooLong
: _(
304 "The nft set name '%s' is longer than allowed 255 characters"
306 errorUnexpectedExit
: _(
307 "Unexpected exit or service termination: '%s'"
309 errorPolicyNoSrcDest
: _(
310 "Policy '%s' has no source/destination parameters"
312 errorPolicyNoInterface
: _("Policy '%s' has no assigned interface"),
313 errorPolicyUnknownInterface
: _(
314 "Policy '%s' has an unknown interface"
316 errorPolicyProcessCMD
: _("%s"),
317 errorFailedSetup
: _("Failed to set up '%s'"),
318 errorFailedReload
: _("Failed to reload '%s'"),
319 errorUserFileNotFound
: _("Custom user file '%s' not found or empty"),
320 errorUserFileSyntax
: _("Syntax error in custom user file '%s'"),
321 errorUserFileRunning
: _("Error running custom user file '%s'"),
322 errorUserFileNoCurl
: _(
323 "Use of 'curl' is detected in custom user file '%s', but 'curl' isn't installed"
325 errorNoGateways
: _("Failed to set up any gateway"),
326 errorResolver
: _("Resolver '%s'"),
327 errorPolicyProcessNoIpv6
: _(
328 "Skipping IPv6 policy '%s' as IPv6 support is disabled"
330 errorPolicyProcessUnknownFwmark
: _(
331 "Unknown packet mark for interface '%s'"
333 errorPolicyProcessMismatchFamily
: _(
334 "Mismatched IP family between in policy '%s'"
336 errorPolicyProcessUnknownProtocol
: _(
337 "Unknown protocol in policy '%s'"
339 errorPolicyProcessInsertionFailed
: _(
340 "Insertion failed for both IPv4 and IPv6 for policy '%s'"
342 errorPolicyProcessInsertionFailedIpv4
: _(
343 "Insertion failed for IPv4 for policy '%s'"
345 errorInterfaceRoutingEmptyValues
: _(
346 "Received empty tid/mark or interface name when setting up routing"
348 errorFailedToResolve
: _("Failed to resolve '%s'"),
349 errorInvalidOVPNConfig
: _(
350 "Invalid OpenVPN config for '%s' interface"
352 errorNftFileInstall
: _("Failed to install fw4 nft file '%s'"),
353 errorNoDownloadWithSecureReload
: _(
354 "Policy '%s' refers to URL which can't be downloaded in 'secure_reload' mode!"
356 errorDownloadUrlNoHttps
: _(
357 "Failed to download '%s', HTTPS is not supported!"
359 errorDownloadUrl
: _("Failed to download '%s'!"),
360 errorFileSchemaRequiresCurl
: _(
361 "The file:// schema requires curl, but it's not detected on this system!"
363 errorTryFailed
: _("Command failed: %s"),
367 { class: "cbi-value-title" },
371 reply
.errors
.forEach((element
) => {
372 if (element
.id
&& textLabelsTable
[element
.id
]) {
373 if (element
.id
!== "errorPolicyProcessCMD") {
375 (textLabelsTable
[element
.id
] + "!").format(
380 text
+= _("Unknown error!") + "<br />";
383 text
+= _("Errors encountered, please check the %sREADME%s!").format(
384 '<a href="' + pkg
.URL
+ '" target="_blank">',
387 var errorsText
= E("div", {}, text
);
388 var errorsField
= E("div", { class: "cbi-value-field" }, errorsText
);
389 errorsDiv
= E("div", { class: "cbi-value" }, [
395 var btn_gap
= E("span", {}, "  ");
396 var btn_gap_long
= E(
399 "        "
405 class: "btn cbi-button cbi-button-apply",
407 click: function (ev
) {
411 { class: "spinning" },
412 _("Starting %s service").format(pkg
.Name
)
415 return RPC
.setInitAction(pkg
.Name
, "start");
424 class: "btn cbi-button cbi-button-apply",
426 click: function (ev
) {
430 { class: "spinning" },
431 _("Restarting %s service").format(pkg
.Name
)
434 return RPC
.setInitAction(pkg
.Name
, "restart");
443 class: "btn cbi-button cbi-button-reset",
445 click: function (ev
) {
449 { class: "spinning" },
450 _("Stopping %s service").format(pkg
.Name
)
453 return RPC
.setInitAction(pkg
.Name
, "stop");
462 class: "btn cbi-button cbi-button-apply",
464 click: function (ev
) {
468 { class: "spinning" },
469 _("Enabling %s service").format(pkg
.Name
)
472 return RPC
.setInitAction(pkg
.Name
, "enable");
481 class: "btn cbi-button cbi-button-reset",
483 click: function (ev
) {
487 { class: "spinning" },
488 _("Disabling %s service").format(pkg
.Name
)
491 return RPC
.setInitAction(pkg
.Name
, "disable");
498 btn_enable
.disabled
= true;
499 btn_disable
.disabled
= false;
501 btn_start
.disabled
= true;
502 btn_action
.disabled
= false;
503 btn_stop
.disabled
= false;
505 btn_start
.disabled
= false;
506 btn_action
.disabled
= true;
507 btn_stop
.disabled
= true;
510 btn_start
.disabled
= true;
511 btn_action
.disabled
= true;
512 btn_stop
.disabled
= true;
513 btn_enable
.disabled
= false;
514 btn_disable
.disabled
= true;
517 var buttonsTitle
= E(
519 { class: "cbi-value-title" },
522 var buttonsText
= E("div", {}, [
533 var buttonsField
= E("div", { class: "cbi-value-field" }, buttonsText
);
534 var buttonsDiv
= reply
.version
535 ? E("div", { class: "cbi-value" }, [buttonsTitle
, buttonsField
])
537 return E("div", {}, [
549 RPC
.on("setInitAction", function (reply
) {
554 return L
.Class
.extend({
556 getInterfaces
: getInterfaces
,
557 getPlatformSupport
: getPlatformSupport
,