feat: карточки сервисов на дашборде + секция Hosts в правилах + скрытие вкладки Conditions для Hosts

Дашборд:
- Добавлены карточки статуса Zapret, Zapret2, ByeDpi на панель мониторинга
- Показывают статус (работает/остановлен), количество перезапусков, предупреждения
- Баг: API возвращает boolean а не число — исправлено сравнение

Секция Hosts:
- Перенесены опции hosts из глобальных настроек в отдельный тип секции (action=hosts)
- Опции: пользовательские DNS записи, удалённые списки хостов, автообновление, интервал
- Миграция: автоматический перенос глобальных настроек hosts в секцию
- Вкладка «Условия» скрыта для секций типа Hosts (hosts записи применяются глобально)
- Возвращена кнопка «Import Hosts File» с модальным окном загрузки из файла
- Убраны неиспользуемые опции из settings.js (~280 строк)

Локализация:
- Переведены 6 новых строк для превью импорта hosts
This commit is contained in:
Dushnilin
2026-08-06 14:16:18 +03:00
parent 488bbfefd5
commit 7125e1a48e
23 changed files with 1222 additions and 793 deletions
@@ -103,6 +103,7 @@ const PRIORITY_UC = LIB_DIR + "/singbox/priority.uc";
const DNS_FAILOVER_UC = LIB_DIR + "/singbox/dns_failover.uc";
const SUBSCRIPTION_CACHE_UC = LIB_DIR + "/subscription/cache.uc";
const UPDATES_UC = LIB_DIR + "/components/updates.uc";
const HOSTS_UC = LIB_DIR + "/components/hosts.uc";
const STATE_UC = LIB_DIR + "/service/state.uc";
const RELOAD_UC = LIB_DIR + "/service/reload.uc";
const UI_UC = LIB_DIR + "/service/ui.uc";
@@ -747,6 +748,8 @@ function start_main() {
if (status != 0)
return status;
module_background(HOSTS_UC, [ "list-update" ]);
status = module_status(SINGBOX_UC, [ "configure-service" ]);
if (status != 0)
return status;
@@ -1048,6 +1051,7 @@ function parse_reload_plan(output) {
changed_byedpi_runtime: 0,
changed_cron: 0,
changed_list: 0,
changed_hosts_list: 0,
needs_sing_box_reload: 0,
needs_nft_rebuild: 0,
needs_zapret_restart: 0,
@@ -1057,6 +1061,7 @@ function parse_reload_plan(output) {
needs_dnsmasq_restore: 0,
needs_cron_refresh: 0,
needs_list_update: 0,
needs_hosts_update: 0,
has_work: 0
};
@@ -1091,6 +1096,7 @@ function reload_actions_summary(plan) {
actions = append_reload_action(actions, plan.needs_dnsmasq_configure || plan.needs_dnsmasq_restore, "dnsmasq");
actions = append_reload_action(actions, plan.needs_cron_refresh, "scheduled jobs");
actions = append_reload_action(actions, plan.needs_list_update, "remote lists");
actions = append_reload_action(actions, plan.needs_hosts_update, "hosts lists");
return actions;
}
@@ -1270,6 +1276,9 @@ function reload(reason) {
if (plan.needs_byedpi_restart == 1)
module_success(BYEDPI_UC, [ "stop-runtime" ]);
if (plan.needs_hosts_update == 1)
module_success(HOSTS_UC, [ "list-update" ]);
if (plan.needs_nft_rebuild == 1) {
log_message("Rebuilding nftables rules", "info");
status = nft_rebuild_runtime();
+12 -2
View File
@@ -65,6 +65,7 @@ function emit_reload_plan(previous, current, context) {
zapret2_runtime: current.zapret2_runtime != previous.zapret2_runtime,
byedpi_runtime: current.byedpi_runtime != previous.byedpi_runtime,
list: current.list != previous.list,
hosts_list: current.hosts_list != previous.hosts_list,
cron: current.cron != previous.cron
};
@@ -77,7 +78,8 @@ function emit_reload_plan(previous, current, context) {
dnsmasq_configure: false,
dnsmasq_restore: false,
cron_refresh: false,
list_update: false
list_update: false,
hosts_update: false
};
if (changed.sing_box)
@@ -120,6 +122,11 @@ function emit_reload_plan(previous, current, context) {
if (changed.list && context.has_list_update_sources)
needs.list_update = true;
if (changed.hosts_list) {
needs.hosts_update = true;
needs.sing_box_reload = true;
}
if (needs.nft_rebuild && context.has_nft_list_update_sources)
needs.list_update = true;
@@ -147,7 +154,8 @@ function emit_reload_plan(previous, current, context) {
needs.dnsmasq_configure ||
needs.dnsmasq_restore ||
needs.cron_refresh ||
needs.list_update;
needs.list_update ||
needs.hosts_update;
emit("urltest_new_enabled_sections", new_urltest_enabled_sections(
previous.urltest_sections,
@@ -166,6 +174,7 @@ function emit_reload_plan(previous, current, context) {
emit_bool("changed_byedpi_runtime", changed.byedpi_runtime);
emit_bool("changed_cron", changed.cron);
emit_bool("changed_list", changed.list);
emit_bool("changed_hosts_list", changed.hosts_list);
emit_bool("needs_sing_box_reload", needs.sing_box_reload);
emit_bool("needs_nft_rebuild", needs.nft_rebuild);
@@ -176,6 +185,7 @@ function emit_reload_plan(previous, current, context) {
emit_bool("needs_dnsmasq_restore", needs.dnsmasq_restore);
emit_bool("needs_cron_refresh", needs.cron_refresh);
emit_bool("needs_list_update", needs.list_update);
emit_bool("needs_hosts_update", needs.hosts_update);
emit_bool("has_work", has_work);
}
+54 -2
View File
@@ -167,6 +167,7 @@ const RELOAD_STATE_FIELDS = [
"zapret2_runtime_signature",
"byedpi_runtime_signature",
"list_signature",
"hosts_list_signature",
"cron_signature",
"urltest_enabled_sections",
"dont_touch_dhcp"
@@ -715,6 +716,8 @@ function rule_has_list_update_source(enabled, action, community_lists, remote_do
return false;
if (as_string(action) == "dns")
return list_has_remote_references(domain_ip_lists);
if (as_string(action) == "hosts")
return false;
return (
has_community_subnet_list(community_lists) ||
@@ -726,7 +729,7 @@ function rule_has_list_update_source(enabled, action, community_lists, remote_do
}
function rule_has_nft_list_update_source(enabled, action, community_lists, remote_subnet_lists, rule_set_with_subnets, domain_ip_lists) {
if (!arg_bool(enabled) || as_string(action) == "dns")
if (!arg_bool(enabled) || as_string(action) == "dns" || as_string(action) == "hosts")
return false;
return (
@@ -917,7 +920,7 @@ function nft_runtime_signature_body(settings, sections) {
let action = option(section, "action", "");
body = signature_add_value(body, "rule." + name + ".action", action);
if (action == "dns")
if (action == "dns" || action == "hosts")
continue;
body = signature_add_value(body, "rule." + name + ".ip_cidr", section_rule_condition_csv(section, "ip_cidr", "subnets"));
body = signature_add_value(body, "rule." + name + ".source_ip_cidr", section_rule_condition_csv(section, "source_ip_cidr", "subnets"));
@@ -1130,6 +1133,14 @@ function append_list_update_signature_body(body, section) {
body = signature_add_value(body, "lists." + name + ".domain_ip_lists", option(section, "domain_ip_lists", ""));
return body;
}
if (action == "hosts") {
body = signature_add_value(body, "lists." + name + ".hosts_list_urls", option(section, "hosts_list_urls", ""));
body = signature_add_value(body, "lists." + name + ".hosts_list_disabled", option(section, "hosts_list_disabled", ""));
body = signature_add_value(body, "lists." + name + ".dns_hosts", option(section, "dns_hosts", ""));
body = signature_add_value(body, "lists." + name + ".hosts_list_auto_update", option(section, "hosts_list_auto_update", ""));
body = signature_add_value(body, "lists." + name + ".hosts_list_update_interval", option(section, "hosts_list_update_interval", ""));
return body;
}
body = signature_add_value(body, "lists." + name + ".ports", section_rule_ports_csv(section));
body = signature_add_value(body, "lists." + name + ".community_subnet_lists", rule_config.filter_community_subnet_lists_value(connections.community_lists_value(section)));
@@ -1150,6 +1161,24 @@ function list_update_signature_body(sections) {
return body;
}
function hosts_list_signature_body(sections) {
let body = "";
for (let section in sections) {
section = object_or_empty(section);
if (option(section, "action", "") != "hosts")
continue;
if (!bool_option(section, "enabled", true))
continue;
let name = section_name(section);
body = signature_add_value(body, "hosts." + name + ".hosts_list_urls", option(section, "hosts_list_urls", ""));
body = signature_add_value(body, "hosts." + name + ".hosts_list_disabled", option(section, "hosts_list_disabled", ""));
body = signature_add_value(body, "hosts." + name + ".dns_hosts", option(section, "dns_hosts", ""));
body = signature_add_value(body, "hosts." + name + ".hosts_list_auto_update", option(section, "hosts_list_auto_update", "0"));
body = signature_add_value(body, "hosts." + name + ".hosts_list_update_interval", option(section, "hosts_list_update_interval", ""));
}
return body;
}
function cron_signature_body(settings, sections) {
let body = signature_add_value("", "settings.update_interval", settings_update_interval(settings));
body = signature_add_value(body, "settings.component_update_check_interval", settings_component_update_check_interval(settings));
@@ -1167,6 +1196,17 @@ function cron_signature_body(settings, sections) {
body = signature_add_value(body, "subscription." + name + ".subscription_update_interval", section_subscription_update_interval(section));
}
for (let section in sections) {
section = object_or_empty(section);
if (option(section, "action", "") != "hosts")
continue;
if (!bool_option(section, "enabled", true))
continue;
let name = section_name(section);
body = signature_add_value(body, "hosts." + name + ".hosts_list_auto_update", option(section, "hosts_list_auto_update", "0"));
body = signature_add_value(body, "hosts." + name + ".hosts_list_update_interval", option(section, "hosts_list_update_interval", ""));
}
return body;
}
@@ -1305,6 +1345,11 @@ function append_sing_box_rule_signature_body(body, section, sections) {
if (bool_option(section, "dns_detour_enabled", false))
body = signature_add_value(body, prefix + ".dns_detour_section", option(section, "dns_detour_section", ""));
}
else if (action == "hosts") {
body = signature_add_value(body, prefix + ".hosts_list_urls", option(section, "hosts_list_urls", ""));
body = signature_add_value(body, prefix + ".hosts_list_disabled", option(section, "hosts_list_disabled", ""));
body = signature_add_value(body, prefix + ".dns_hosts", option(section, "dns_hosts", ""));
}
body = signature_add_value(body, prefix + ".domain", section_rule_condition_csv(section, "domain", "domains"));
body = signature_add_value(body, prefix + ".domain_suffix", section_rule_condition_csv(section, "domain_suffix", "domains"));
@@ -1599,6 +1644,7 @@ function reload_state_values_from_sources(format, settings, sections, servers, d
zapret2_runtime_signature: signature_hash(zapret2_runtime_signature_body(sections)),
byedpi_runtime_signature: signature_hash(byedpi_runtime_signature_body(sections)),
list_signature: signature_hash(list_update_signature_body(sections)),
hosts_list_signature: signature_hash(hosts_list_signature_body(sections)),
cron_signature: signature_hash(cron_signature_body(settings, sections)),
urltest_enabled_sections: urltest_enabled_sections_value(sections),
dont_touch_dhcp: dont_touch_dhcp_value(settings)
@@ -2018,6 +2064,12 @@ else if (mode == "list-update-signature-fixture") {
let data = fixture_data(ARGV[1]);
exit(print_signature_hash(list_update_signature_body(fixture_section_list(data))) ? 0 : 1);
}
else if (mode == "hosts-list-signature")
exit(print_signature_hash(hosts_list_signature_body(uci_sections("section"))) ? 0 : 1);
else if (mode == "hosts-list-signature-fixture") {
let data = fixture_data(ARGV[1]);
exit(print_signature_hash(hosts_list_signature_body(fixture_section_list(data))) ? 0 : 1);
}
else if (mode == "cron-signature")
exit(print_signature_hash(cron_signature_body(uci_settings(), uci_sections("section"))) ? 0 : 1);
else if (mode == "cron-signature-fixture") {