feat: add Guest Mode isolation with quotas and fix Telegram bot language switching

This commit is contained in:
Dushnilin
2026-09-08 09:10:00 +03:00
parent 203bb9b433
commit d07e2c599f
11 changed files with 1149 additions and 52 deletions
@@ -1297,9 +1297,183 @@ function createParentalContent(section) {
o.rmempty = false;
}
// ─── Guest Mode Section ───────────────────────────────────────────────────────
function createGuestModeContent(section) {
// Enabled switch
let o = section.option(form.Flag, "enabled", _("Enable Guest Mode"));
o.default = "0";
o.rmempty = false;
// Policy Mode: selected vs inverted
o = section.option(
form.ListValue,
"mode",
_("Policy Mode"),
_(
"Selected: specified devices become guests. Inverted: all devices on the network become guests EXCEPT the trusted ones (Zero-Trust).",
),
);
o.default = "selected";
o.value("selected", _("Selected Devices are Guests (Explicit list)"));
o.value("inverted", _("All Devices are Guests EXCEPT Trusted (Zero-Trust)"));
// Guest devices (for selected mode)
o = section.option(
form.DynamicList,
"guest_devices",
_("Guest Devices (MAC / IP)"),
_(
"Devices that are isolated as guests. Enter IP or MAC addresses, or pick from active LAN devices.",
),
);
o.rmempty = true;
o.placeholder = "192.168.1.150 or AA:BB:CC:DD:EE:FF";
o.validate = validateDevice;
o.depends("mode", "selected");
o.renderWidget = function (sectionId, optionIndex, cfgvalue) {
return local_devices.createLocalDeviceDynamicListWidget(
this,
sectionId,
cfgvalue,
);
};
// Trusted devices (for inverted mode)
o = section.option(
form.DynamicList,
"trusted_devices",
_("Trusted Master Devices (MAC / IP)"),
_(
"Master and family devices with full LAN and router access. Any device NOT in this list will be treated as a guest.",
),
);
o.rmempty = true;
o.placeholder = "192.168.1.100 or AA:BB:CC:DD:EE:FF";
o.validate = validateDevice;
o.depends("mode", "inverted");
o.renderWidget = function (sectionId, optionIndex, cfgvalue) {
return local_devices.createLocalDeviceDynamicListWidget(
this,
sectionId,
cfgvalue,
);
};
// LAN Isolation
o = section.option(
form.Flag,
"isolate_lan",
_("Isolate Local LAN"),
_(
"Prevent guest devices from accessing other local network devices (RFC1918 and IPv6 local subnets).",
),
);
o.default = "1";
o.rmempty = false;
// Block Router Admin
o = section.option(
form.Flag,
"block_router_admin",
_("Block Router Administration"),
_(
"Prevent guest devices from accessing router management services (LuCI Web UI, SSH, Clash API). DHCP and DNS remain accessible.",
),
);
o.default = "1";
o.rmempty = false;
// Daily Time Quota (minutes)
o = section.option(
form.Value,
"daily_time_limit",
_("Daily Time Limit (minutes)"),
_(
"Maximum allowed online time per guest device per day. When reached, access is blocked until midnight. Enter 0 for unlimited.",
),
);
o.default = "0";
o.placeholder = "0";
o.rmempty = true;
o.validate = function (_sectionId, value) {
if (value === "" || value === null || value === undefined) return true;
const n = Number(value);
if (!Number.isInteger(n) || n < 0 || n > 1440)
return _("Enter a whole number of minutes between 0 and 1440");
return true;
};
// Daily Traffic Quota (MB)
o = section.option(
form.Value,
"daily_traffic_limit",
_("Daily Traffic Limit (MB)"),
_(
"Maximum allowed download and upload traffic per guest device per day in megabytes. Enter 0 for unlimited.",
),
);
o.default = "0";
o.placeholder = "0";
o.rmempty = true;
o.validate = function (_sectionId, value) {
if (value === "" || value === null || value === undefined) return true;
const n = Number(value);
if (!Number.isInteger(n) || n < 0)
return _("Enter a positive whole number in megabytes");
return true;
};
// Schedule Active Window (start_time, end_time)
o = section.option(
form.Value,
"start_time",
_("Allowed Start Time"),
_("Start of allowed daily access window in HH:MM format (24-hour clock)."),
);
o.placeholder = "08:00";
o.rmempty = true;
o.validate = validateTime;
o = section.option(
form.Value,
"end_time",
_("Allowed End Time"),
_("End of allowed daily access window in HH:MM format (24-hour clock)."),
);
o.placeholder = "22:00";
o.rmempty = true;
o.validate = validateTime;
// Days of week
o = section.option(
form.MultiValue,
"days",
_("Allowed Days of Week"),
_("Select days when guest access is permitted. Leave empty for all days."),
);
o.rmempty = true;
Object.entries(DAY_LABELS).forEach(([val, label]) => {
o.value(val, label);
});
// Telegram notification
o = section.option(
form.Flag,
"notify",
_("Telegram Alerts"),
_(
"Send Telegram notification when a guest device hits its daily time or traffic quota.",
),
);
o.default = "1";
o.rmempty = false;
}
const EntryPoint = {
createProfileContent,
createParentalContent,
createGuestModeContent,
};
return baseclass.extend(EntryPoint);
@@ -1015,6 +1015,13 @@ function createMenuTabsOrderWidget(option, section_id) {
defaultVisible: false,
fixed: false,
},
{
id: "guest_mode",
label: _("Guest Mode"),
desc: _("Guest network isolation and device quotas"),
defaultVisible: true,
fixed: false,
},
{
id: "monitoring",
label: _("Monitoring"),
@@ -443,6 +443,7 @@ const EntryPoint = {
"server",
"profile",
"schedule",
"guest_mode",
"monitoring",
"diagnostic",
"updates",
@@ -575,6 +576,21 @@ const EntryPoint = {
parental.createParentalContent(s);
return s;
},
guest_mode: () => {
const s = tachyonMap.section(
form.NamedSection,
"guest_mode",
"guest_mode",
_("Guest Mode"),
_(
"Guest mode isolates guest devices from local LAN and router administration, while allowing internet access with daily quotas and schedules.",
),
);
s.anonymous = false;
s.addremove = false;
parental.createGuestModeContent(s);
return s;
},
monitoring: () => {
const s = tachyonMap.section(
form.TypedSection,