Name the PSTN modes as extensions of the original tiers
The behaviour was right but the vocabulary wasn't: the previous commit replaced full/restricted/internal with a new none/open/out/in/both naming, when the ask was to extend the existing tier dropdown rather than supplant it. The dropdown now reads: full, restricted (both ways), restricted incoming, restricted outgoing, internal. The stored values match — internal, restricted, full, restricted-in, restricted-out — so the original three keep their own names in the config file and migrating a legacy install is now near-identity for them (tier=full becomes restrict=full, tier=restricted becomes restrict=restricted, which is what that tier already meant). restricted incoming = dials anywhere, only whitelisted numbers get through. restricted outgoing = anyone can call in, may only dial the whitelist. Named in parallel rather than as "full incoming", so the two sit next to each other in the list without needing the parenthetical to tell them apart. No behavioural change: the derived tier_out/allowed_out/tier_in/allowed_in the dialplan reads are unchanged, so the dialplan itself is untouched. Verified: legacy migration mapping the original tiers to their same-named modes; both new modes round-tripping through the dashboard and compiling to the right derived keys (restricted-in sets allowed_in only, restricted-out sets allowed_out only); whitelist disabled on full and internal; and the group-ring helper still ringing restricted-incoming only for a whitelisted caller while restricted-outgoing rings for everyone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NAddJGE1G6eGaPzmScG5Vh
This commit is contained in:
+25
-22
@@ -3,8 +3,10 @@
|
|||||||
# US-only outbound (NANP dialplan
|
# US-only outbound (NANP dialplan
|
||||||
# restriction), independent outbound/inbound concurrent-call caps, a
|
# restriction), independent outbound/inbound concurrent-call caps, a
|
||||||
# per-extension permission model built on ONE whitelist plus a mode saying
|
# per-extension permission model built on ONE whitelist plus a mode saying
|
||||||
# which direction(s) it applies to (none / unrestricted / restrict outbound /
|
# which direction(s) it applies to — the original full / restricted /
|
||||||
# restrict inbound / restrict both), a configurable inbound ring-group,
|
# internal tiers, plus restricted-in (whitelist gates incoming, dials
|
||||||
|
# anywhere) and restricted-out (whitelist gates outgoing, anyone can call
|
||||||
|
# in) — a configurable inbound ring-group,
|
||||||
# IP-authenticated trunk (no SIP password stored), ntfy alerts on
|
# IP-authenticated trunk (no SIP password stored), ntfy alerts on
|
||||||
# denied/rejected calls, and a periodic spend/volume check.
|
# denied/rejected calls, and a periodic spend/volume check.
|
||||||
#
|
#
|
||||||
@@ -784,32 +786,32 @@ _pstn_migrate_permissions_split() {
|
|||||||
function flush_section() {
|
function flush_section() {
|
||||||
if (!have) return
|
if (!have) return
|
||||||
if (header != "") print header
|
if (header != "") print header
|
||||||
mode = "none"
|
mode = "internal"
|
||||||
if (tier == "full") mode = "open"
|
if (tier == "full") mode = "full"
|
||||||
else if (tier == "restricted") mode = "both"
|
else if (tier == "restricted") mode = "restricted"
|
||||||
# An install that predates the split has no tier_out; one made
|
# An install that predates the split has no tier_out; one made
|
||||||
# between the split and this change may, so honour it if present.
|
# between the split and this change may, so honour it if present.
|
||||||
if (tier_out != "" || tier_in != "") {
|
if (tier_out != "" || tier_in != "") {
|
||||||
o = (tier_out != "" ? tier_out : tier)
|
o = (tier_out != "" ? tier_out : tier)
|
||||||
i = (tier_in != "" ? tier_in : tier)
|
i = (tier_in != "" ? tier_in : tier)
|
||||||
if (o == "full" && i == "full") mode = "open"
|
if (o == "full" && i == "full") mode = "full"
|
||||||
else if (o == "restricted" && i == "restricted") mode = "both"
|
else if (o == "restricted" && i == "restricted") mode = "restricted"
|
||||||
else if (o == "restricted") mode = "out"
|
else if (o == "restricted") mode = "restricted-out"
|
||||||
else if (i == "restricted") mode = "in"
|
else if (i == "restricted") mode = "restricted-in"
|
||||||
else mode = "none"
|
else mode = "internal"
|
||||||
}
|
}
|
||||||
print "restrict=" mode
|
print "restrict=" mode
|
||||||
if (nums != "") print "allowed_numbers=" nums
|
if (nums != "") print "allowed_numbers=" nums
|
||||||
if (mode == "open") { print "tier_out=full"; print "tier_in=full"; print "tier=full" }
|
if (mode == "full") { print "tier_out=full"; print "tier_in=full"; print "tier=full" }
|
||||||
else if (mode == "out") {
|
else if (mode == "restricted-out") {
|
||||||
print "tier_out=restricted"; print "allowed_out=" nums
|
print "tier_out=restricted"; print "allowed_out=" nums
|
||||||
print "tier_in=full"; print "tier=restricted"
|
print "tier_in=full"; print "tier=restricted"
|
||||||
}
|
}
|
||||||
else if (mode == "in") {
|
else if (mode == "restricted-in") {
|
||||||
print "tier_out=full"; print "tier_in=restricted"
|
print "tier_out=full"; print "tier_in=restricted"
|
||||||
print "allowed_in=" nums; print "tier=full"
|
print "allowed_in=" nums; print "tier=full"
|
||||||
}
|
}
|
||||||
else if (mode == "both") {
|
else if (mode == "restricted") {
|
||||||
print "tier_out=restricted"; print "allowed_out=" nums
|
print "tier_out=restricted"; print "allowed_out=" nums
|
||||||
print "tier_in=restricted"; print "allowed_in=" nums
|
print "tier_in=restricted"; print "allowed_in=" nums
|
||||||
print "tier=restricted"
|
print "tier=restricted"
|
||||||
@@ -887,12 +889,13 @@ _pstn_write_permissions_file() {
|
|||||||
local _written_exts=""
|
local _written_exts=""
|
||||||
{
|
{
|
||||||
echo "; PSTN permissions. Each extension has ONE whitelist and a 'restrict' mode"
|
echo "; PSTN permissions. Each extension has ONE whitelist and a 'restrict' mode"
|
||||||
echo "; saying which direction(s) that whitelist applies to:"
|
echo "; saying which direction(s) that whitelist applies to. The first three are"
|
||||||
echo "; none - no PSTN at all (internal extension calling still works)"
|
echo "; the original tiers, unchanged; the last two are the new half-restrictions:"
|
||||||
echo "; open - unrestricted both ways"
|
echo "; full - no whitelist, calls both ways"
|
||||||
echo "; out - may only DIAL numbers on the whitelist; anyone may call in"
|
echo "; restricted - the whitelist applies BOTH ways"
|
||||||
echo "; in - may only be CALLED BY numbers on the whitelist; may dial anywhere"
|
echo "; internal - no PSTN at all (internal extension calling still works)"
|
||||||
echo "; both - the whitelist applies in both directions"
|
echo "; restricted-in - whitelist gates INCOMING only; may dial anywhere"
|
||||||
|
echo "; restricted-out - whitelist gates OUTGOING only; anyone may call in"
|
||||||
echo "; 'allowed_numbers' is that whitelist: pipe-separated 11-digit numbers, used"
|
echo "; 'allowed_numbers' is that whitelist: pipe-separated 11-digit numbers, used"
|
||||||
echo "; directly as a REGEX() alternation (see this file's own comments on why"
|
echo "; directly as a REGEX() alternation (see this file's own comments on why"
|
||||||
echo "; untrusted call data is always the string being tested, never the pattern)."
|
echo "; untrusted call data is always the string being tested, never the pattern)."
|
||||||
@@ -927,7 +930,7 @@ _pstn_write_permissions_file() {
|
|||||||
local _ext
|
local _ext
|
||||||
for _ext in $FULL_EXTS; do
|
for _ext in $FULL_EXTS; do
|
||||||
echo "[$_ext]"
|
echo "[$_ext]"
|
||||||
echo "restrict=open"
|
echo "restrict=full"
|
||||||
echo "tier_out=full"
|
echo "tier_out=full"
|
||||||
echo "tier_in=full"
|
echo "tier_in=full"
|
||||||
echo "tier=full"
|
echo "tier=full"
|
||||||
@@ -940,7 +943,7 @@ _pstn_write_permissions_file() {
|
|||||||
_ext="$1"; local _nums="$2"
|
_ext="$1"; local _nums="$2"
|
||||||
shift 2
|
shift 2
|
||||||
echo "[$_ext]"
|
echo "[$_ext]"
|
||||||
echo "restrict=both"
|
echo "restrict=restricted"
|
||||||
echo "allowed_numbers=${_nums}"
|
echo "allowed_numbers=${_nums}"
|
||||||
echo "tier_out=restricted"
|
echo "tier_out=restricted"
|
||||||
echo "allowed_out=${_nums}"
|
echo "allowed_out=${_nums}"
|
||||||
|
|||||||
@@ -1144,8 +1144,9 @@ def _write_ini_cp(path, header, cp):
|
|||||||
PERMISSIONS_HEADER = (
|
PERMISSIONS_HEADER = (
|
||||||
"; PSTN permissions. Each extension has ONE whitelist (allowed_numbers)\n"
|
"; PSTN permissions. Each extension has ONE whitelist (allowed_numbers)\n"
|
||||||
"; and a 'restrict' mode saying which direction(s) it applies to:\n"
|
"; and a 'restrict' mode saying which direction(s) it applies to:\n"
|
||||||
"; none / open / out (may only dial the list) / in (may only be called\n"
|
"; full / restricted (both ways) / internal - the original tiers - plus\n"
|
||||||
"; by the list) / both.\n"
|
"; restricted-in (whitelist gates incoming only) and restricted-out\n"
|
||||||
|
"; (whitelist gates outgoing only).\n"
|
||||||
"; restrict + allowed_numbers are the authored pair; tier_out/allowed_out\n"
|
"; restrict + allowed_numbers are the authored pair; tier_out/allowed_out\n"
|
||||||
"; and tier_in/allowed_in are DERIVED from them and are what the dialplan\n"
|
"; and tier_in/allowed_in are DERIVED from them and are what the dialplan\n"
|
||||||
"; reads; 'tier' is a rollback mirror for a pre-split pstn-trunk.sh.\n"
|
"; reads; 'tier' is a rollback mirror for a pre-split pstn-trunk.sh.\n"
|
||||||
@@ -1187,7 +1188,7 @@ def _read_permissions_cp():
|
|||||||
return cp
|
return cp
|
||||||
|
|
||||||
|
|
||||||
RESTRICT_RE = re.compile(r"^(none|open|out|in|both)$")
|
RESTRICT_RE = re.compile(r"^(internal|restricted|full|restricted-in|restricted-out)$")
|
||||||
|
|
||||||
|
|
||||||
def _derive_restrict(tier_out, tier_in):
|
def _derive_restrict(tier_out, tier_in):
|
||||||
@@ -1197,14 +1198,14 @@ def _derive_restrict(tier_out, tier_in):
|
|||||||
the more restrictive reading: anything that isn't clearly open in a
|
the more restrictive reading: anything that isn't clearly open in a
|
||||||
direction is treated as restricted or none, never widened."""
|
direction is treated as restricted or none, never widened."""
|
||||||
if tier_out == "full" and tier_in == "full":
|
if tier_out == "full" and tier_in == "full":
|
||||||
return "open"
|
return "full"
|
||||||
if tier_out == "restricted" and tier_in == "restricted":
|
if tier_out == "restricted" and tier_in == "restricted":
|
||||||
return "both"
|
return "restricted"
|
||||||
if tier_out == "restricted":
|
if tier_out == "restricted":
|
||||||
return "out"
|
return "restricted-out"
|
||||||
if tier_in == "restricted":
|
if tier_in == "restricted":
|
||||||
return "in"
|
return "restricted-in"
|
||||||
return "none"
|
return "internal"
|
||||||
|
|
||||||
|
|
||||||
def get_all_permissions():
|
def get_all_permissions():
|
||||||
@@ -1212,8 +1213,9 @@ def get_all_permissions():
|
|||||||
with a non-default record.
|
with a non-default record.
|
||||||
|
|
||||||
Each extension has ONE whitelist and a mode saying which direction(s) it
|
Each extension has ONE whitelist and a mode saying which direction(s) it
|
||||||
applies to: none (no PSTN), open (unrestricted), out (may only dial the
|
applies to: the original internal / restricted / full, plus restricted-in
|
||||||
list), in (may only be called by the list), both. That authored pair is
|
(whitelist gates incoming, dials anywhere) and restricted-out (whitelist
|
||||||
|
gates outgoing, anyone may call in). That authored pair is
|
||||||
what this returns and what the UI edits; the dialplan reads the derived
|
what this returns and what the UI edits; the dialplan reads the derived
|
||||||
tier_out/allowed_out/tier_in/allowed_in that write_permission keeps in
|
tier_out/allowed_out/tier_in/allowed_in that write_permission keeps in
|
||||||
step with it.
|
step with it.
|
||||||
@@ -1254,12 +1256,15 @@ def get_all_permissions():
|
|||||||
# Which per-direction tiers each authored mode compiles down to. The dialplan
|
# Which per-direction tiers each authored mode compiles down to. The dialplan
|
||||||
# only ever reads the compiled keys; this table is the single place the
|
# only ever reads the compiled keys; this table is the single place the
|
||||||
# mapping is defined.
|
# mapping is defined.
|
||||||
|
# The first three are the original tiers, unchanged. restricted-in and
|
||||||
|
# restricted-out are the halves: the whitelist gates one direction while the
|
||||||
|
# other stays wide open.
|
||||||
_RESTRICT_TIERS = {
|
_RESTRICT_TIERS = {
|
||||||
"none": ("internal", "internal"),
|
"internal": ("internal", "internal"),
|
||||||
"open": ("full", "full"),
|
"full": ("full", "full"),
|
||||||
"out": ("restricted", "full"),
|
"restricted": ("restricted", "restricted"),
|
||||||
"in": ("full", "restricted"),
|
"restricted-in": ("full", "restricted"),
|
||||||
"both": ("restricted", "restricted"),
|
"restricted-out": ("restricted", "full"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1276,7 +1281,8 @@ def write_permission(ext, restrict, numbers_raw, messaging_enabled=False):
|
|||||||
|
|
||||||
One list, not two: the whitelist is "the numbers this extension deals
|
One list, not two: the whitelist is "the numbers this extension deals
|
||||||
with", and the mode says whether that constrains dialling out, being
|
with", and the mode says whether that constrains dialling out, being
|
||||||
called, or both. Modes are none / open / out / in / both.
|
called, or both. Modes are internal / restricted / full (the original
|
||||||
|
tiers) plus restricted-in and restricted-out.
|
||||||
|
|
||||||
Writes three layers, all derived from those two authored values:
|
Writes three layers, all derived from those two authored values:
|
||||||
restrict, allowed_numbers what a human edits (and what this reads back)
|
restrict, allowed_numbers what a human edits (and what this reads back)
|
||||||
@@ -1308,7 +1314,7 @@ def write_permission(ext, restrict, numbers_raw, messaging_enabled=False):
|
|||||||
tier_out, tier_in = _RESTRICT_TIERS[restrict]
|
tier_out, tier_in = _RESTRICT_TIERS[restrict]
|
||||||
|
|
||||||
cp = _read_permissions_cp()
|
cp = _read_permissions_cp()
|
||||||
if restrict == "none":
|
if restrict == "internal":
|
||||||
# Clear the PSTN keys only, never the whole section — messaging and
|
# Clear the PSTN keys only, never the whole section — messaging and
|
||||||
# personal_did are independent and must survive. (Removing the
|
# personal_did are independent and must survive. (Removing the
|
||||||
# section here was a real, confirmed bug in an earlier version.)
|
# section here was a real, confirmed bug in an earlier version.)
|
||||||
@@ -1321,7 +1327,7 @@ def write_permission(ext, restrict, numbers_raw, messaging_enabled=False):
|
|||||||
if not cp.has_section(ext):
|
if not cp.has_section(ext):
|
||||||
cp.add_section(ext)
|
cp.add_section(ext)
|
||||||
cp.set(ext, "restrict", restrict)
|
cp.set(ext, "restrict", restrict)
|
||||||
_set_or_clear(cp, ext, "allowed_numbers", numbers if restrict != "open" else "")
|
_set_or_clear(cp, ext, "allowed_numbers", numbers if restrict != "full" else "")
|
||||||
cp.set(ext, "tier_out", tier_out)
|
cp.set(ext, "tier_out", tier_out)
|
||||||
cp.set(ext, "tier_in", tier_in)
|
cp.set(ext, "tier_in", tier_in)
|
||||||
_set_or_clear(cp, ext, "allowed_out", numbers if tier_out == "restricted" else "")
|
_set_or_clear(cp, ext, "allowed_out", numbers if tier_out == "restricted" else "")
|
||||||
@@ -1336,8 +1342,8 @@ def write_permission(ext, restrict, numbers_raw, messaging_enabled=False):
|
|||||||
cp.remove_option(ext, "messaging")
|
cp.remove_option(ext, "messaging")
|
||||||
|
|
||||||
# Drop the section entirely once nothing is left in it — only reachable
|
# Drop the section entirely once nothing is left in it — only reachable
|
||||||
# when the mode is none, messaging is off, and no personal_did was ever
|
# when the mode is internal, messaging is off, and no personal_did was
|
||||||
# assigned.
|
# ever assigned.
|
||||||
if cp.has_section(ext) and not cp.options(ext):
|
if cp.has_section(ext) and not cp.options(ext):
|
||||||
cp.remove_section(ext)
|
cp.remove_section(ext)
|
||||||
|
|
||||||
@@ -1345,7 +1351,7 @@ def write_permission(ext, restrict, numbers_raw, messaging_enabled=False):
|
|||||||
if not ok:
|
if not ok:
|
||||||
return False, err
|
return False, err
|
||||||
|
|
||||||
if restrict != "none" and restrict != "open" and not clean:
|
if restrict not in ("internal", "full") and not clean:
|
||||||
return True, ("Saved, but the whitelist is EMPTY — with this mode that means no PSTN "
|
return True, ("Saved, but the whitelist is EMPTY — with this mode that means no PSTN "
|
||||||
"number is permitted in the restricted direction yet.")
|
"number is permitted in the restricted direction yet.")
|
||||||
return True, "Saved"
|
return True, "Saved"
|
||||||
@@ -2693,11 +2699,11 @@ INDEX_HTML = """<!doctype html>
|
|||||||
<p class="muted"><b>Name</b> and <b>Category</b> are editable in place — click, type, then Save. Adding an extension generates a random password and reloads PJSIP + rebuilds the dialplan automatically; the password is shown once, at the top of this card.</p>
|
<p class="muted"><b>Name</b> and <b>Category</b> are editable in place — click, type, then Save. Adding an extension generates a random password and reloads PJSIP + rebuilds the dialplan automatically; the password is shown once, at the top of this card.</p>
|
||||||
<p class="muted pstn-only"><b>PSTN</b> sets how the outside phone network reaches this extension, and the <b>Whitelist</b> beside it is the one list of numbers that mode applies to:</p>
|
<p class="muted pstn-only"><b>PSTN</b> sets how the outside phone network reaches this extension, and the <b>Whitelist</b> beside it is the one list of numbers that mode applies to:</p>
|
||||||
<ul class="muted pstn-only" style="margin:0 0 var(--sp-2); padding-left:1.2rem">
|
<ul class="muted pstn-only" style="margin:0 0 var(--sp-2); padding-left:1.2rem">
|
||||||
<li><b>No PSTN</b> — outside calls neither in nor out.</li>
|
<li><b>full</b> — dial anyone, anyone can call. No whitelist.</li>
|
||||||
<li><b>Unrestricted</b> — dial anyone, anyone can call.</li>
|
<li><b>restricted (both ways)</b> — the whitelist applies to outgoing <i>and</i> incoming.</li>
|
||||||
<li><b>Restrict outbound</b> — may only dial the whitelist; anyone can call in.</li>
|
<li><b>restricted incoming</b> — dials anywhere; only whitelisted numbers can call in.</li>
|
||||||
<li><b>Restrict inbound</b> — may dial anywhere; only the whitelist can call in.</li>
|
<li><b>restricted outgoing</b> — anyone can call in; may only dial the whitelist.</li>
|
||||||
<li><b>Restrict both</b> — the whitelist applies in both directions.</li>
|
<li><b>internal</b> — no PSTN at all, either direction.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="muted pstn-only">Internal extension-to-extension calling and ring groups are never gated by any of this. Changes are usually live on the next call; if one doesn't seem to take effect, use "Commit changes" above.</p>
|
<p class="muted pstn-only">Internal extension-to-extension calling and ring groups are never gated by any of this. Changes are usually live on the next call; if one doesn't seem to take effect, use "Commit changes" above.</p>
|
||||||
<p class="muted"><b>Messaging</b> — Asterisk's native SIP texting between extensions: no carrier SMS, no PSTN, no cost, and no dependency on a PSTN trunk at all (which is why this column is here even with no trunk installed). Independent of the calling tier. Enforced live by a dedicated dialplan context — see <code>services/asterisk.sh</code>'s README, including its caveat that the sender-extraction logic still needs real-traffic confirmation. If this box predates that wiring, rerun <code>sudo ./setup.sh asterisk</code>.</p>
|
<p class="muted"><b>Messaging</b> — Asterisk's native SIP texting between extensions: no carrier SMS, no PSTN, no cost, and no dependency on a PSTN trunk at all (which is why this column is here even with no trunk installed). Independent of the calling tier. Enforced live by a dedicated dialplan context — see <code>services/asterisk.sh</code>'s README, including its caveat that the sender-extraction logic still needs real-traffic confirmation. If this box predates that wiring, rerun <code>sudo ./setup.sh asterisk</code>.</p>
|
||||||
@@ -3224,15 +3230,19 @@ async function loadExtensions() {
|
|||||||
let extRows = [];
|
let extRows = [];
|
||||||
let extSort = { key: null, dir: 1 };
|
let extSort = { key: null, dir: 1 };
|
||||||
|
|
||||||
// Sort the PSTN column by how much reach the mode grants, not alphabetically
|
// Sort by how much reach the mode grants, not alphabetically — "full" would
|
||||||
// — "open" would otherwise land between "both" and "in".
|
// otherwise land between "internal" and "restricted".
|
||||||
const RESTRICT_ORDER = { none: 0, both: 1, in: 2, out: 3, open: 4 };
|
const RESTRICT_ORDER = {
|
||||||
|
"internal": 0, "restricted": 1, "restricted-in": 2, "restricted-out": 3, "full": 4,
|
||||||
|
};
|
||||||
|
// Order as offered in the dropdown: the original three tiers still read the
|
||||||
|
// same, with the two half-restrictions slotted in between.
|
||||||
const RESTRICT_LABELS = [
|
const RESTRICT_LABELS = [
|
||||||
["none", "No PSTN"],
|
["full", "full"],
|
||||||
["open", "Unrestricted"],
|
["restricted", "restricted (both ways)"],
|
||||||
["out", "Restrict outbound"],
|
["restricted-in", "restricted incoming"],
|
||||||
["in", "Restrict inbound"],
|
["restricted-out", "restricted outgoing"],
|
||||||
["both", "Restrict both"],
|
["internal", "internal"],
|
||||||
];
|
];
|
||||||
|
|
||||||
function extSortValue(e, key) {
|
function extSortValue(e, key) {
|
||||||
@@ -3255,7 +3265,7 @@ function restrictSelect(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The whitelist is meaningless for "no PSTN" and "unrestricted".
|
// The whitelist is meaningless for "no PSTN" and "unrestricted".
|
||||||
function restrictUsesList(mode) { return mode === "out" || mode === "in" || mode === "both"; }
|
function restrictUsesList(mode) { return mode.indexOf("restricted") === 0; }
|
||||||
|
|
||||||
function renderExtensions() {
|
function renderExtensions() {
|
||||||
const tbody = document.querySelector("#ext-table tbody");
|
const tbody = document.querySelector("#ext-table tbody");
|
||||||
@@ -3935,7 +3945,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
perms = get_all_permissions()
|
perms = get_all_permissions()
|
||||||
extensions = []
|
extensions = []
|
||||||
for e in list_extensions():
|
for e in list_extensions():
|
||||||
p = perms.get(e["ext"], {"restrict": "none", "allowed_numbers": "", "messaging": False})
|
p = perms.get(e["ext"], {"restrict": "internal", "allowed_numbers": "", "messaging": False})
|
||||||
extensions.append({"ext": e["ext"], "name": e["name"],
|
extensions.append({"ext": e["ext"], "name": e["name"],
|
||||||
"restrict": p["restrict"],
|
"restrict": p["restrict"],
|
||||||
"allowed_numbers": p["allowed_numbers"],
|
"allowed_numbers": p["allowed_numbers"],
|
||||||
|
|||||||
Reference in New Issue
Block a user