Skip to content

Commit 7b0ee69

Browse files
committed
luci-app-adblock-fast: update to 1.2.2-r6
Update luci-app-adblock-fast from 1.2.1-r3 to 1.2.2-r6. This rewrites the rpcd backend from a 519-line shell script to a 452-line native ucode module, adds two new RPC methods for cron management, reorganizes the web UI with a dedicated scheduling tab and a collapsible service details section, moves schedule persistence from UCI to crontab, and adds the AGPL-3.0-or-later LICENSE file. Signed-off-by: Stan Grishin <stangri@melmac.ca> --- - **8 files changed**, +1,765 / -851 lines (net +914) - **1 commit**: `a18c61a` — `luci-app-adblock-fast: update to 1.2.2-r6` --- - `+rpcd-mod-ucode` — Native ucode rpcd module loader - `+jsonfilter` — No longer needed; ucode handles JSON natively - Version bumped from `1.2.1-r3` to `1.2.2-r6` - URL updated from `github.com/stangri/...` to `github.com/mossdef-org/...` --- The rpcd script moves from: ``` /usr/libexec/rpcd/luci.adblock-fast (519 lines, shell) ``` to: ``` /usr/share/rpcd/ucode/luci.adblock-fast (452 lines, ucode) ``` | Aspect | Old (Shell) | New (ucode) | |-----------------|-------------------------|--------------------| | Language | POSIX `/bin/sh` | ucode bytecode | | JSON handling | `json_init/add/dump` | Native objects | | UCI access | `config_load/get` subpr | `cursor()` API | | ubus access | `jsonfilter` pipelines | `connect()` API | | Business logic | Duplicated in script | Imports shared lib | | Method decl | `case` switch block | Declarative schema | | Performance | Fork/exec per operation | In-process calls | The new script imports the shared business logic module directly: ```javascript import adb from '/lib/adblock-fast/adblock-fast.uc'; ``` Query methods (`getInitStatus`, `getPlatformSupport`, `getFileUrlFilesizes`, etc.) now delegate to the shared `adb` library rather than re-implementing the logic. **Retained (7 methods, same interface):** | Method | Type | Purpose | |-----------------------|--------|--------------------| | `getFileUrlFilesizes` | Query | URL sizes from cfg | | `getInitList` | Query | Enabled/running | | `getInitStatus` | Query | Full service state | | `getPlatformSupport` | Query | Resolver/tool info | | `setInitAction` | Action | Start/stop/enable | | `getCronStatus` | Query | Cron diagnostics | | `syncCron` | Action | Update cron sched | **Added (2 new methods):** | Method | Type | Purpose | |----------------|--------|----------------------| | `getCronEntry` | Query | Get raw cron line | | `setCronEntry` | Action | Set/replace cron line| The cron subsystem now tracks three entry states: - **`active`** — Enabled and scheduled - **`suspended`** — Disabled but recoverable - **`disabled`** — Fully off `getCronStatus` returns new diagnostic fields: - `cron_line_multi` — Multiple entries detected - `cron_line_parse_ok` — Expression validity - `cron_line_state` — One of: `active`, `suspended`, `disabled`, `multi`, `unsupported`, `missing` - `entry` — Raw matched cron line Bumped from **11** to **13**, reflecting the new methods and enhanced `getCronStatus` response shape. Two new methods added to the rpcd ACL file (`luci-app-adblock-fast.json`): - `getCronEntry` (read section) - `setCronEntry` (write section) --- **LuciCompat** bumped from `11` to `13`. **Data fetching refactored:** - Removed separate `getServiceInfo()` ubus call - `initStatus` response now provides all data (package compat, errors, warnings) in one call - `ubus` object built from `initData` properties instead of separate service instance query **New "Service Details" section:** - Blocking stats, DNS backend info, compressed cache status, force-DNS ports, and donation link moved from inline status text to a separate collapsible `detailsDiv` - Main status area now shows only the essential state label and cache info when stopped **Cron warning logic rewritten:** - Warnings only displayed when service is both enabled and running - New `warningCronEntryMismatch` warning for suspended or unparseable cron entries - Suggests "Resync Cron" action when `cronSyncNeeded` is detected - Pre-existing checks for missing/disabled cron daemon preserved but now gated behind `showCronWarnings` flag **Cron sync flow rewritten:** 1. Fetches current entry via `getCronEntry()` 2. Strips comment markers and suspended/disabled tags from the entry 3. Writes cleaned entry via `setCronEntry()` 4. Reloads page only on success **New exports:** `getCronEntry`, `setCronEntry` **New helper: `parseCronEntry(cronEntry)`** - Parses raw cron line into form field values - Detects schedule mode from cron pattern: - `every_n_hours` — `*/N * * * *` - `every_n_days` — `M H */N * *` - `monthly` — `M H D * *` - `weekly` — `M H * * D` - `daily` — `M H * * *` (default) - Returns config object with: `auto_update_enabled`, `auto_update_mode`, `auto_update_hour`, `auto_update_minute`, `auto_update_weekday`, `auto_update_monthday`, `auto_update_every_ndays`, `auto_update_every_nhours` - Falls back to defaults for unparseable entries **New helper: `generateCronEntry(config)`** - Inverse of `parseCronEntry` - Converts form config object back to cron syntax - Returns empty string when auto-update disabled - Output format: `M H DOM * DOW /etc/init.d/adblock-fast dl` `# adblock-fast-auto` **Data loading consolidated:** - Removed separate `getFileUrlFilesizes()` and `getPlatformSupport()` calls - Now fetches `getInitStatus()` + `getCronStatus()` - Sizes and platform data extracted from the unified `initStatus` response **New "List Updates Schedule" tab:** - All scheduling options moved from `tab_advanced` to dedicated `tab_schedule` - Options: `auto_update_enabled`, `auto_update_mode`, `auto_update_every_ndays`, `auto_update_every_nhours`, `auto_update_weekday`, `auto_update_monthday`, `auto_update_hour`, `auto_update_minute` - Each option's `cfgvalue()` overridden to read from parsed cron config instead of UCI - `config_update_enabled` remains on `tab_advanced` **Schedule persistence moved to crontab:** - Old: scheduling fields saved to UCI config, then `syncCron` called after `uci-applied` event to generate cron entry from config - New: `handleSave()` collects form values, calls `generateCronEntry()`, writes directly via `setCronEntry()`, then removes scheduling fields from UCI before saving remaining config - Result: schedule lives in crontab, survives config resets **`handleSaveApply` simplified:** - Old: chained `handleSave` → listener for `uci-applied` → `syncCron` → page reload - New: `handleSave()` → `ui.changes.apply()` - Cron already updated during save, no separate sync step needed **Instance handling fix:** - `dnsmasq_instance` and `smartdns_instance` write overrides now wrap values in arrays - Ensures instances stored as UCI lists, not scalar strings --- **2 new strings added:** - `"List Updates Schedule"` — New tab header - `"Service Details"` — New status section header **0 strings removed, 0 strings reworded.** All other changes are source line number updates from the JavaScript refactoring. Existing translations remain valid; translators only need to handle the 2 new entries. --- Adds the full AGPL-3.0-or-later license text (661 lines), matching the `PKG_LICENSE` field already declared in the Makefile. --- - `rpcdCompat` bumped from `11` to `13` - `LuciCompat` bumped from `11` to `13` - Requires `rpcd-mod-ucode` (replaces `jsonfilter`) - Requires companion `adblock-fast` package >=1.2.2 (for the shared `/lib/adblock-fast/adblock-fast.uc` library imported by the rpcd ucode module) - All existing RPC methods preserved; 2 new ones added - All existing UI functionality preserved; scheduling options reorganized into dedicated tab Signed-off-by: Stan Grishin <stangri@melmac.ca>
1 parent 8da730b commit 7b0ee69

File tree

9 files changed

+1838
-931
lines changed

9 files changed

+1838
-931
lines changed

applications/luci-app-adblock-fast/LICENSE

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

applications/luci-app-adblock-fast/Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ include $(TOPDIR)/rules.mk
66
PKG_NAME:=luci-app-adblock-fast
77
PKG_LICENSE:=AGPL-3.0-or-later
88
PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
9-
PKG_VERSION:=1.2.1
10-
PKG_RELEASE:=3
9+
PKG_VERSION:=1.2.2
10+
PKG_RELEASE:=6
1111

1212
LUCI_TITLE:=AdBlock-Fast Web UI
13-
LUCI_URL:=https://github.com/stangri/luci-app-adblock-fast/
13+
LUCI_URL:=https://github.com/mossdef-org/luci-app-adblock-fast/
1414
LUCI_DESCRIPTION:=Provides Web UI for adblock-fast service.
15-
LUCI_DEPENDS:=+luci-base +adblock-fast +jsonfilter
15+
LUCI_DEPENDS:=+luci-base +adblock-fast +rpcd-mod-ucode
1616

1717
define Package/$(PKG_NAME)/config
1818
# shown in make menuconfig <Help>

applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js

Lines changed: 172 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var pkg = {
1212
return "adblock-fast";
1313
},
1414
get LuciCompat() {
15-
return 11;
15+
return 13;
1616
},
1717
get ReadmeCompat() {
1818
return "";
@@ -199,6 +199,19 @@ var getCronStatus = rpc.declare({
199199
params: ["name"],
200200
});
201201

202+
var getCronEntry = rpc.declare({
203+
object: "luci." + pkg.Name,
204+
method: "getCronEntry",
205+
params: ["name"],
206+
});
207+
208+
var setCronEntry = rpc.declare({
209+
object: "luci." + pkg.Name,
210+
method: "setCronEntry",
211+
params: ["name", "entry"],
212+
expect: { result: false },
213+
});
214+
202215
var getPlatformSupport = rpc.declare({
203216
object: "luci." + pkg.Name,
204217
method: "getPlatformSupport",
@@ -302,35 +315,38 @@ var status = baseclass.extend({
302315
render: function () {
303316
return Promise.all([
304317
L.resolveDefault(getInitStatus(pkg.Name), {}),
305-
L.resolveDefault(getServiceInfo(pkg.Name, true), {}),
306318
L.resolveDefault(getCronStatus(pkg.Name), {}),
307-
]).then(function ([initStatus, ubusInfo, cronStatus]) {
319+
]).then(function ([initStatus, cronStatus]) {
320+
var initData = initStatus?.[pkg.Name] || {};
308321
var reply = {
309-
status: initStatus?.[pkg.Name] || {
310-
enabled: false,
311-
status: null,
312-
packageCompat: 0,
313-
rpcdCompat: 0,
314-
running: null,
315-
version: null,
316-
errors: [],
317-
warnings: [],
318-
force_dns_active: null,
319-
force_dns_ports: [],
320-
entries: null,
321-
dns: null,
322-
outputFile: null,
323-
outputCache: null,
324-
outputGzip: null,
325-
outputFileExists: null,
326-
outputCacheExists: null,
327-
outputGzipExists: null,
328-
leds: [],
329-
},
330-
ubus: ubusInfo?.[pkg.Name]?.instances?.main?.data || {
331-
packageCompat: 0,
332-
errors: [],
333-
warnings: [],
322+
status:
323+
initData.enabled !== undefined
324+
? initData
325+
: {
326+
enabled: false,
327+
status: null,
328+
packageCompat: 0,
329+
rpcdCompat: 0,
330+
running: null,
331+
version: null,
332+
errors: [],
333+
warnings: [],
334+
force_dns_active: null,
335+
force_dns_ports: [],
336+
entries: null,
337+
dns: null,
338+
outputFile: null,
339+
outputCache: null,
340+
outputGzip: null,
341+
outputFileExists: null,
342+
outputCacheExists: null,
343+
outputGzipExists: null,
344+
leds: [],
345+
},
346+
ubus: {
347+
packageCompat: initData.packageCompat || 0,
348+
errors: initData.errors ? [...initData.errors] : [],
349+
warnings: initData.warnings ? [...initData.warnings] : [],
334350
},
335351
cron: cronStatus?.[pkg.Name] || {
336352
auto_update_enabled: false,
@@ -340,6 +356,7 @@ var status = baseclass.extend({
340356
cron_running: false,
341357
cron_line_present: false,
342358
cron_line_match: false,
359+
cron_line_state: "none",
343360
},
344361
};
345362

@@ -364,22 +381,43 @@ var status = baseclass.extend({
364381
});
365382
}
366383
var cronSyncNeeded = false;
367-
if (reply.cron.auto_update_enabled) {
384+
var showCronWarnings =
385+
reply.status.enabled &&
386+
reply.status.running &&
387+
(reply.cron.auto_update_enabled ||
388+
reply.cron.cron_line_state === "suspended");
389+
if (showCronWarnings) {
368390
var enableCronCmd =
369391
"<code>/etc/init.d/cron enable && /etc/init.d/cron start</code>";
370392
var resyncLabel = "<code>" + _("Resync Cron") + "</code>";
371-
if (reply.status.enabled && reply.status.running) {
372-
if (!reply.cron.cron_init || !reply.cron.cron_bin) {
373-
reply.ubus.warnings.push({
374-
code: "warningCronMissing",
375-
info: enableCronCmd,
376-
});
377-
} else if (!reply.cron.cron_enabled || !reply.cron.cron_running) {
378-
reply.ubus.warnings.push({
379-
code: "warningCronDisabled",
380-
info: enableCronCmd,
381-
});
382-
}
393+
if (!reply.cron.cron_init || !reply.cron.cron_bin) {
394+
reply.ubus.warnings.push({
395+
code: "warningCronMissing",
396+
info: enableCronCmd,
397+
});
398+
} else if (!reply.cron.cron_enabled || !reply.cron.cron_running) {
399+
reply.ubus.warnings.push({
400+
code: "warningCronDisabled",
401+
info: enableCronCmd,
402+
});
403+
}
404+
if (reply.cron.cron_line_state === "suspended") {
405+
reply.ubus.warnings.push({
406+
code: "warningCronEntryMismatch",
407+
info: resyncLabel,
408+
});
409+
cronSyncNeeded = true;
410+
} else if (
411+
reply.cron.auto_update_enabled &&
412+
(reply.cron.cron_line_state === "unsupported" ||
413+
reply.cron.cron_line_state === "multi")
414+
) {
415+
reply.ubus.warnings.push({
416+
code: "warningCronEntryMismatch",
417+
info: resyncLabel,
418+
});
419+
cronSyncNeeded = true;
420+
} else if (reply.cron.auto_update_enabled) {
383421
if (!reply.cron.cron_line_present) {
384422
reply.ubus.warnings.push({
385423
code: "warningCronEntryMissing",
@@ -410,31 +448,6 @@ var status = baseclass.extend({
410448
switch (reply.status.status) {
411449
case "statusSuccess":
412450
text += pkg.statusTable[reply.status.status] + ".";
413-
text +=
414-
"<br />" +
415-
_("Blocking %s domains (with %s).").format(
416-
reply.status.entries,
417-
reply.status.dns,
418-
);
419-
if (reply.status.outputGzipExists) {
420-
text += "<br />" + _("Compressed cache file created.");
421-
}
422-
if (reply.status.force_dns_active) {
423-
text += "<br />" + _("Force DNS ports:");
424-
reply.status.force_dns_ports.forEach((element) => {
425-
text += " " + element;
426-
});
427-
text += ".";
428-
}
429-
text +=
430-
"<br />" +
431-
"<br />" +
432-
_(
433-
"Please %sdonate%s to support development of this project.",
434-
).format(
435-
"<a href='" + pkg.DonateURL + "' target='_blank'>",
436-
"</a>",
437-
);
438451
break;
439452
case "statusStopped":
440453
if (reply.status.enabled) {
@@ -446,11 +459,6 @@ var status = baseclass.extend({
446459
_("Disabled") +
447460
").";
448461
}
449-
if (reply.status.outputCacheExists) {
450-
text += "<br />" + _("Cache file found.");
451-
} else if (reply.status.outputGzipExists) {
452-
text += "<br />" + _("Compressed cache file found.");
453-
}
454462
break;
455463
case "statusRestarting":
456464
case "statusForceReloading":
@@ -465,21 +473,77 @@ var status = baseclass.extend({
465473
} else {
466474
text = _("Not installed or not found");
467475
}
468-
var statusText = E("div", { class: "cbi-value-description" }, text);
476+
var statusText = E("div", {}, text);
469477
var statusField = E("div", { class: "cbi-value-field" }, statusText);
470478
var statusDiv = E("div", { class: "cbi-value" }, [
471479
statusTitle,
472480
statusField,
473481
]);
474482

483+
var detailsDiv = [];
484+
if (reply.status.version) {
485+
var detailsText = "";
486+
if (reply.status.status === "statusSuccess") {
487+
detailsText += _("Blocking %s domains (with %s).").format(
488+
reply.status.entries,
489+
reply.status.dns,
490+
);
491+
if (reply.status.outputGzipExists) {
492+
detailsText += "<br />" + _("Compressed cache file created.");
493+
}
494+
if (reply.status.force_dns_active) {
495+
detailsText += "<br />" + _("Force DNS ports:");
496+
reply.status.force_dns_ports.forEach((element) => {
497+
detailsText += " " + element;
498+
});
499+
detailsText += ".";
500+
}
501+
}
502+
if (reply.status.status === "statusStopped") {
503+
if (reply.status.outputCacheExists) {
504+
detailsText += _("Cache file found.");
505+
} else if (reply.status.outputGzipExists) {
506+
detailsText += _("Compressed cache file found.");
507+
}
508+
}
509+
if (detailsText) {
510+
var detailsTitle = E(
511+
"label",
512+
{ class: "cbi-value-title" },
513+
_("Service Details"),
514+
);
515+
var detailsDescr = E(
516+
"div",
517+
{ class: "cbi-value-description" },
518+
_(
519+
"Please %sdonate%s to support development of this project.",
520+
).format(
521+
"<a href='" + pkg.DonateURL + "' target='_blank'>",
522+
"</a>",
523+
),
524+
);
525+
var detailsContent = E("div", {}, detailsText);
526+
var detailsField = E("div", { class: "cbi-value-field" }, [
527+
detailsContent,
528+
E("br"),
529+
E("br"),
530+
detailsDescr,
531+
]);
532+
detailsDiv = E("div", { class: "cbi-value" }, [
533+
detailsTitle,
534+
detailsField,
535+
]);
536+
}
537+
}
538+
475539
var warningsDiv = [];
476540
if (reply.ubus.warnings && reply.ubus.warnings.length) {
477541
var warningsTitle = E(
478542
"label",
479543
{ class: "cbi-value-title" },
480544
_("Service Warnings"),
481545
);
482-
var text = "";
546+
text = "";
483547
reply.ubus.warnings.forEach((element) => {
484548
if (element.code && pkg.warningTable[element.code]) {
485549
text += pkg.formatMessage(
@@ -509,7 +573,7 @@ var status = baseclass.extend({
509573
{ class: "cbi-value-title" },
510574
_("Service Errors"),
511575
);
512-
var text = "";
576+
text = "";
513577
reply.ubus.errors.forEach((element) => {
514578
if (element.code && pkg.errorTable[element.code]) {
515579
text += pkg.formatMessage(
@@ -586,19 +650,38 @@ var status = baseclass.extend({
586650
ui.showModal(null, [
587651
E("p", { class: "spinning" }, _("Syncing cron schedule")),
588652
]);
589-
return syncCron(pkg.Name, "apply").then(
590-
function (result) {
591-
ui.hideModal();
592-
location.reload();
593-
},
594-
function (error) {
595-
ui.hideModal();
596-
ui.addNotification(
597-
null,
598-
E("p", {}, _("Failed to sync cron schedule")),
653+
return L.resolveDefault(getCronEntry(pkg.Name), {})
654+
.then(function (response) {
655+
var entry =
656+
(response?.[pkg.Name] && response[pkg.Name].entry) || "";
657+
if (!entry) {
658+
return Promise.reject(new Error("No cron entry"));
659+
}
660+
entry = entry.replace(/^\s*#\s*/, "");
661+
entry = entry.replace(
662+
/adblock-fast-auto-(suspended|disabled)/g,
663+
"adblock-fast-auto",
599664
);
600-
},
601-
);
665+
return L.resolveDefault(setCronEntry(pkg.Name, entry), {
666+
result: false,
667+
});
668+
})
669+
.then(
670+
function (result) {
671+
if (!result || result.result === false) {
672+
throw new Error("Failed to update cron schedule");
673+
}
674+
ui.hideModal();
675+
location.reload();
676+
},
677+
function (error) {
678+
ui.hideModal();
679+
ui.addNotification(
680+
null,
681+
E("p", {}, _("Failed to sync cron schedule")),
682+
);
683+
},
684+
);
602685
},
603686
},
604687
_("Resync Cron"),
@@ -749,6 +832,7 @@ var status = baseclass.extend({
749832
return E("div", {}, [
750833
header,
751834
statusDiv,
835+
detailsDiv,
752836
warningsDiv,
753837
errorsDiv,
754838
buttonsDiv,
@@ -773,6 +857,8 @@ return L.Class.extend({
773857
getFileUrlFilesizes: getFileUrlFilesizes,
774858
syncCron: syncCron,
775859
getCronStatus: getCronStatus,
860+
getCronEntry: getCronEntry,
861+
setCronEntry: setCronEntry,
776862
getPlatformSupport: getPlatformSupport,
777863
getServiceInfo: getServiceInfo,
778864
});

0 commit comments

Comments
 (0)