mirror of
https://github.com/openwrt/luci.git
synced 2026-04-15 10:51:51 +00:00
luci-app-adguardhome: add new app
Add LuCI UI for AdGuard Home configuration. If AdGuard Home service is running, restart it automatically when configuration is applied. Signed-off-by: George Sapkin <george@sapk.in>
This commit is contained in:
committed by
Paul Donald
parent
c335f3affc
commit
c923488dda
16
applications/luci-app-adguardhome/Makefile
Normal file
16
applications/luci-app-adguardhome/Makefile
Normal file
@@ -0,0 +1,16 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_NAME:=luci-app-adguardhome
|
||||
LUCI_MAINTAINER:=George Sapkin <george@sapk.in>
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
|
||||
LUCI_TITLE:=LuCI support for AdGuard Home
|
||||
LUCI_DEPENDS:=+adguardhome +luci-base
|
||||
LUCI_EXTRA_DEPENDS:=adguardhome (>=0.107.73-r3)
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,261 @@
|
||||
'use strict';
|
||||
|
||||
'require dom';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
'require view';
|
||||
|
||||
const DEFAULT_CONFIG_FILE = '/etc/adguardhome/adguardhome.yaml';
|
||||
const DEFAULT_WORK_DIR = '/var/lib/adguardhome';
|
||||
const DEFAULT_USER = 'adguardhome';
|
||||
const DEFAULT_GROUP = DEFAULT_USER;
|
||||
|
||||
const DEFAULT_GOGC = '0';
|
||||
const DEFAULT_GOMAXPROCS = '0';
|
||||
const DEFAULT_GOMEMLIMIT = '0';
|
||||
|
||||
const PATH_REGEX = new RegExp('^/etc(/[^/]+)?/?$');
|
||||
|
||||
const POLL_INTERVAL = 5;
|
||||
|
||||
const RUNNING_SPAN = `<span style="color: var(--success-color-high); font-weight: bold">${_('Running')}</span>`;
|
||||
const NOT_RUNNING_SPAN = `<span style="color: var(--error-color-high); font-weight: bold">${_('Not running')}</span>`;
|
||||
|
||||
const STORAGE_KEY = 'luci-app-adguardhome';
|
||||
|
||||
function getServiceInfo(name) {
|
||||
const fn = rpc.declare({
|
||||
object: 'service',
|
||||
method: 'list',
|
||||
params: ['name'],
|
||||
expect: { [name]: { instances: { [name]: {} }}},
|
||||
});
|
||||
return () => fn(name);
|
||||
}
|
||||
|
||||
const getAGHServiceInfo = getServiceInfo('adguardhome');
|
||||
|
||||
async function getStatus() {
|
||||
try {
|
||||
const res = await getAGHServiceInfo();
|
||||
const isRunning = res?.instances?.adguardhome?.running;
|
||||
return isRunning ?? false;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusValue(isRunning) {
|
||||
return isRunning ? RUNNING_SPAN : NOT_RUNNING_SPAN;
|
||||
}
|
||||
|
||||
async function getVersion() {
|
||||
try {
|
||||
const res = await fs.exec('/usr/bin/AdGuardHome', ['--version']);
|
||||
const version = res.stdout
|
||||
? (res.stdout.match(/version\s+(.*)/) || [null, res.stdout.trim()])[1]
|
||||
: '';
|
||||
return version;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return 'unknown version';
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatus(node) {
|
||||
const output = node?.querySelector('output');
|
||||
return output
|
||||
? async () => {
|
||||
const isRunning = await getStatus();
|
||||
dom.content(output, getStatusValue(isRunning));
|
||||
}
|
||||
: () => {};
|
||||
}
|
||||
|
||||
function validateConfigFile(_unused, value) {
|
||||
if (value == null || value === '') {
|
||||
return true;
|
||||
}
|
||||
if (!value.startsWith('/')) {
|
||||
return _('Path must be absolute.');
|
||||
}
|
||||
if (value.endsWith('/')) {
|
||||
return _('Path must not end with a slash.');
|
||||
}
|
||||
if (PATH_REGEX.test(value)) {
|
||||
return _('Configuration file must be stored in its own directory, and not in \'/etc\'.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateWorkDir(_unused, value) {
|
||||
if (value == null || value === '') {
|
||||
return true;
|
||||
}
|
||||
if (!value.startsWith('/')) {
|
||||
return _('Path must be absolute.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
getStatus(),
|
||||
getVersion(),
|
||||
]);
|
||||
},
|
||||
|
||||
async render([isRunning, version]) {
|
||||
const map = new form.Map('adguardhome', _('AdGuard Home'));
|
||||
|
||||
const statusSect = map.section(form.TypedSection, 'status');
|
||||
statusSect.anonymous = true;
|
||||
statusSect.cfgsections = () => ['status_section'];
|
||||
|
||||
const versionOpt = statusSect.option(form.DummyValue, '_version', _('Version'));
|
||||
versionOpt.cfgvalue = () => version;
|
||||
|
||||
const statusOpt = statusSect.option(form.DummyValue, '_status', _('Service Status'));
|
||||
statusOpt.rawhtml = true;
|
||||
statusOpt.cfgvalue = () => getStatusValue(isRunning);
|
||||
|
||||
const mainSect = map.section(form.TypedSection, 'adguardhome');
|
||||
mainSect.anonymous = true;
|
||||
|
||||
mainSect.tab('general', _('General Settings'));
|
||||
mainSect.tab(
|
||||
'jail',
|
||||
_('File System Access'),
|
||||
_('Files and directories that AdGuard Home should have read-only or read-write access to.'),
|
||||
);
|
||||
mainSect.tab(
|
||||
'advanced',
|
||||
_('Advanced Settings'),
|
||||
_('Go environment variables that tune garbage collector and memory management.') +
|
||||
' ' + _('Modify at your own risk.'),
|
||||
);
|
||||
|
||||
const configFileOpt = mainSect.taboption(
|
||||
'general',
|
||||
form.Value,
|
||||
'config_file',
|
||||
_('Configuration file'),
|
||||
_('Configuration file must be stored in its own directory, and not in \'/etc\'.') +
|
||||
'<br />' + _('Parent directory will be owned by the service user.') +
|
||||
'<br />' + _('If empty, defaults to') + ` '${DEFAULT_CONFIG_FILE}'.`,
|
||||
);
|
||||
configFileOpt.placeholder = DEFAULT_CONFIG_FILE;
|
||||
configFileOpt.validate = validateConfigFile;
|
||||
|
||||
const workDirOpt = mainSect.taboption(
|
||||
'general',
|
||||
form.Value,
|
||||
'work_dir',
|
||||
_('Working directory'),
|
||||
_('Directory where filters, logs, and statistics are stored.') +
|
||||
'<br />' + _('Will be owned by the service user.') +
|
||||
'<br />' + _('If empty, defaults to') + ` '${DEFAULT_WORK_DIR}'.`,
|
||||
);
|
||||
workDirOpt.placeholder = DEFAULT_WORK_DIR;
|
||||
workDirOpt.validate = validateWorkDir;
|
||||
|
||||
const userOpt = mainSect.taboption(
|
||||
'general',
|
||||
form.Value,
|
||||
'user',
|
||||
_('Service user'),
|
||||
_('User the service runs under.') + ' ' + _('If empty, defaults to') +
|
||||
` '${DEFAULT_USER}'.`,
|
||||
);
|
||||
userOpt.placeholder = DEFAULT_USER;
|
||||
|
||||
const groupOpt = mainSect.taboption(
|
||||
'general',
|
||||
form.Value,
|
||||
'group',
|
||||
_('Service group'),
|
||||
_('Group the service runs under.') + ' ' + _('If empty, defaults to') +
|
||||
` '${DEFAULT_GROUP}'.`,
|
||||
|
||||
);
|
||||
groupOpt.placeholder = DEFAULT_GROUP;
|
||||
|
||||
const verboseOpt = mainSect.taboption(
|
||||
'general',
|
||||
form.Flag,
|
||||
'verbose',
|
||||
_('Verbose logging'),
|
||||
);
|
||||
verboseOpt.default = '0';
|
||||
|
||||
const advSettingsOpt = mainSect.taboption(
|
||||
'general',
|
||||
form.Flag,
|
||||
'advanced_settings',
|
||||
_('Advanced Settings'),
|
||||
);
|
||||
advSettingsOpt.default = '0';
|
||||
advSettingsOpt.rmempty = false;
|
||||
advSettingsOpt.load = () => sessionStorage.getItem(STORAGE_KEY) || '0';
|
||||
advSettingsOpt.remove = () => {};
|
||||
advSettingsOpt.write = (_, value) => sessionStorage.setItem(STORAGE_KEY, value);
|
||||
|
||||
mainSect.taboption('jail', form.DynamicList, 'jail_mount', _('Read-only access'));
|
||||
mainSect.taboption('jail', form.DynamicList, 'jail_mount_rw', _('Read-write access'));
|
||||
|
||||
const gcOpt = mainSect.taboption(
|
||||
'advanced',
|
||||
form.Value,
|
||||
'gc',
|
||||
'GOGC',
|
||||
_('Tunes the garbage collector\'s aggressiveness by setting the percentage of heap ' +
|
||||
'growth allowed before the next collection cycle triggers.') + '<br />' +
|
||||
_('If empty, defaults to') + ' ' + _('unset and 100') + '.',
|
||||
'<a href="https://go.dev/doc/gc-guide#GOGC" target="_blank">https://go.dev/doc/gc-guide#GOGC</a>'
|
||||
);
|
||||
gcOpt.datatype = 'uinteger';
|
||||
gcOpt.depends('advanced_settings', '1');
|
||||
gcOpt.placeholder = DEFAULT_GOGC;
|
||||
gcOpt.retain = true;
|
||||
|
||||
const maxProcsOpt = mainSect.taboption(
|
||||
'advanced',
|
||||
form.Value,
|
||||
'maxprocs',
|
||||
'GOMAXPROCS',
|
||||
_('The maximum number of operating system threads that can execute user-level Go code' +
|
||||
' simultaneously.') + '<br />' +
|
||||
_('If empty, defaults to') + ' ' + _('unset and matching the number of CPUs') + '.',
|
||||
);
|
||||
maxProcsOpt.datatype = 'uinteger';
|
||||
maxProcsOpt.depends('advanced_settings', '1');
|
||||
maxProcsOpt.placeholder = DEFAULT_GOMAXPROCS;
|
||||
maxProcsOpt.retain = true;
|
||||
|
||||
const memLimitOpt = mainSect.taboption(
|
||||
'advanced',
|
||||
form.Value,
|
||||
'memlimit',
|
||||
'GOMEMLIMIT',
|
||||
_('A soft memory cap for the Go runtime, allowing the garbage collector to run more ' +
|
||||
'frequently as usage approaches the limit to prevent Out-of-Memory (OOM) kills.') +
|
||||
'<br />' +
|
||||
_('If empty, defaults to') + ' ' + _('unset') + '.',
|
||||
);
|
||||
memLimitOpt.datatype = 'uinteger';
|
||||
memLimitOpt.depends('advanced_settings', '1');
|
||||
memLimitOpt.placeholder = DEFAULT_GOMEMLIMIT;
|
||||
memLimitOpt.retain = true;
|
||||
|
||||
const rendered = await map.render();
|
||||
|
||||
const statusNode = map.findElement('data-field', statusOpt.cbid('status_section'));
|
||||
poll.add(updateStatus(statusNode), POLL_INTERVAL);
|
||||
|
||||
return rendered;
|
||||
},
|
||||
});
|
||||
159
applications/luci-app-adguardhome/po/templates/adguardhome.pot
Normal file
159
applications/luci-app-adguardhome/po/templates/adguardhome.pot
Normal file
@@ -0,0 +1,159 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244
|
||||
msgid ""
|
||||
"A soft memory cap for the Go runtime, allowing the garbage collector to run "
|
||||
"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) "
|
||||
"kills."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113
|
||||
#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3
|
||||
msgid "AdGuard Home"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199
|
||||
msgid "Advanced Settings"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146
|
||||
msgid "Configuration file"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147
|
||||
msgid ""
|
||||
"Configuration file must be stored in its own directory, and not in '/etc'."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159
|
||||
msgid "Directory where filters, logs, and statistics are stored."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132
|
||||
msgid "File System Access"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133
|
||||
msgid ""
|
||||
"Files and directories that AdGuard Home should have read-only or read-write "
|
||||
"access to."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129
|
||||
msgid "General Settings"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138
|
||||
msgid ""
|
||||
"Go environment variables that tune garbage collector and memory management."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3
|
||||
msgid "Grant permissions for the AdGuard Home LuCI app"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181
|
||||
msgid "Group the service runs under."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247
|
||||
msgid "If empty, defaults to"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139
|
||||
msgid "Modify at your own risk."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24
|
||||
msgid "Not running"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148
|
||||
msgid "Parent directory will be owned by the service user."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99
|
||||
msgid "Path must be absolute."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86
|
||||
msgid "Path must not end with a slash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207
|
||||
msgid "Read-only access"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208
|
||||
msgid "Read-write access"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122
|
||||
msgid "Service Status"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180
|
||||
msgid "Service group"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170
|
||||
msgid "Service user"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230
|
||||
msgid ""
|
||||
"The maximum number of operating system threads that can execute user-level "
|
||||
"Go code simultaneously."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215
|
||||
msgid ""
|
||||
"Tunes the garbage collector's aggressiveness by setting the percentage of "
|
||||
"heap growth allowed before the next collection cycle triggers."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171
|
||||
msgid "User the service runs under."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191
|
||||
msgid "Verbose logging"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119
|
||||
msgid "Version"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160
|
||||
msgid "Will be owned by the service user."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158
|
||||
msgid "Working directory"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247
|
||||
msgid "unset"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217
|
||||
msgid "unset and 100"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232
|
||||
msgid "unset and matching the number of CPUs"
|
||||
msgstr ""
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"admin/services/adguardhome": {
|
||||
"title": "AdGuard Home",
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "adguardhome/config"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-adguardhome" ],
|
||||
"uci": {
|
||||
"adguardhome": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"luci-app-adguardhome": {
|
||||
"description": "Grant permissions for the AdGuard Home LuCI app",
|
||||
"read": {
|
||||
"file": {
|
||||
"/usr/bin/AdGuardHome --version": [ "exec" ]
|
||||
},
|
||||
"ubus": {
|
||||
"service": [ "list" ]
|
||||
},
|
||||
"uci": [ "adguardhome" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "adguardhome" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"config": "adguardhome",
|
||||
"init": "adguardhome"
|
||||
}
|
||||
Reference in New Issue
Block a user