326 lines
14 KiB
JavaScript
326 lines
14 KiB
JavaScript
(function () {
|
||
"use strict";
|
||
|
||
const REG = [
|
||
{
|
||
num: "7271UV",
|
||
type: "SIP",
|
||
station: "ГТС Алматы",
|
||
city: "Алматы",
|
||
desc: "Оконечный номер экстренных служб (пример схемы)",
|
||
reserve: "Есть: SIP Trunk 7272222222 (принадлежит ГТС Алматы)",
|
||
},
|
||
{
|
||
num: "727103",
|
||
type: "BRW SIP",
|
||
station: "ГТС Алматы",
|
||
city: "Алматы",
|
||
desc: "Номер пересчёта принадлежит BRW SIP 7273130333",
|
||
reserve: "Есть: SIP Trunk 7272222222 (принадлежит ГТС Алматы)",
|
||
},
|
||
{
|
||
num: "7272222222",
|
||
type: "ТДМ",
|
||
station: "ГТС Алматы",
|
||
city: "Алматы",
|
||
desc: "Резервный номер в SIP Trunk, принадлежит ГТС Алматы",
|
||
reserve: "Резерв для 7271UV",
|
||
},
|
||
];
|
||
|
||
const OWNERS = [
|
||
{ name: "ГТС Алматы (собственный номер станции)", short: "ГТС Алматы", match: function (d) { return d.owner === "own"; } },
|
||
{ name: "BRW SIP", short: "BRW SIP", match: function (d) { return d.owner === "brw-sip"; } },
|
||
{ name: "АТС", short: "АТС", match: function (d) { return d.owner === "pbx"; } },
|
||
{ name: "АТС зоны", short: "АТС зоны", match: function (d) { return d.owner === "zone"; } },
|
||
];
|
||
|
||
function normalize(v) {
|
||
return String(v == null ? "" : v).replace(/\s+/g, "").toUpperCase();
|
||
}
|
||
|
||
function findEntry(num) {
|
||
const n = normalize(num);
|
||
if (!n) return null;
|
||
return REG.find(function (e) { return normalize(e.num) === n; }) || null;
|
||
}
|
||
|
||
function derive(raw) {
|
||
let n = normalize(raw);
|
||
n = n.replace(/^(?:[8]\+?7|0)/, "");
|
||
if (!/^[0-9A-Z]+$/.test(n)) return null;
|
||
const digits = n.replace(/[^0-9]/g, "");
|
||
const city = digits.startsWith("727") ? "Алматы" : "неизвестен";
|
||
|
||
const entry = findEntry(raw);
|
||
let owner, reserve, viaStation, isReserve = false;
|
||
if (entry) {
|
||
owner = ownerByType(entry.type);
|
||
reserve = entry.reserve;
|
||
viaStation = entry.station;
|
||
isReserve = /резерв/i.test(entry.desc);
|
||
} else {
|
||
owner = digits.length >= 8 ? "pbx" : "zone";
|
||
reserve = "Не определён (номер не в реестре)";
|
||
viaStation = "неизвестна";
|
||
}
|
||
|
||
const owners = OWNERS.filter(function (o) { return o.match({ owner: owner }); });
|
||
const ownerName = owners.length ? owners[0].name : "станция-владелец";
|
||
const endLabel = entry ? "Оконечный номер " + entry.num : "Оконечный номер";
|
||
|
||
const mobile = [
|
||
{ t: "Мобильный оператор" },
|
||
{ t: "SSW/NGN транзит" },
|
||
{ t: "SSW/NGN рег." },
|
||
{ t: endLabel, active: true },
|
||
];
|
||
const fixed = [
|
||
{ t: "Станция владельца номера абонента" },
|
||
{ t: "SSW/NGN рег." + (viaStation ? " (" + viaStation + ")" : "") },
|
||
{ t: endLabel, active: true },
|
||
];
|
||
if (owner === "brw-sip") {
|
||
fixed.splice(2, 0, { t: "Номер пересчёта → BRW SIP", active: true });
|
||
}
|
||
if (reserve && !/не определён/i.test(reserve) && !isReserve) {
|
||
const rnum = reserve.replace(/^Есть[:\s]*/, "").split(/[\s(]/)[0];
|
||
mobile.push({ t: "Резерв", sub: rnum + " · авт. при недоступности SIP" });
|
||
fixed.push({ t: "Резерв", sub: rnum + " · авт. при недоступности SIP" });
|
||
}
|
||
|
||
return { entry: entry, city: city, owner: owner, ownerName: ownerName, reserve: reserve, viaStation: viaStation, mobile: mobile, fixed: fixed };
|
||
}
|
||
|
||
function ownerByType(type) {
|
||
const t = normalize(type);
|
||
if (t.indexOf("BRW") === 0) return "brw-sip";
|
||
if (t === "SIP" || t === "GPON") return "pbx";
|
||
if (t === "ТДМ") return "own";
|
||
return "zone";
|
||
}
|
||
|
||
function renderChain(id, nodes) {
|
||
const el = document.getElementById(id);
|
||
el.innerHTML = "";
|
||
nodes.forEach(function (node, i) {
|
||
const d = document.createElement("div");
|
||
d.className = "chain-node" + (node.active ? " active" : "");
|
||
const s = document.createElement("strong");
|
||
s.textContent = node.t;
|
||
d.appendChild(s);
|
||
if (node.sub) {
|
||
const sp = document.createElement("span");
|
||
sp.textContent = node.sub;
|
||
d.appendChild(sp);
|
||
}
|
||
el.appendChild(d);
|
||
if (i < nodes.length - 1) {
|
||
const a = document.createElement("span");
|
||
a.className = "chain-arrow";
|
||
a.setAttribute("aria-hidden", "true");
|
||
a.innerHTML = "→";
|
||
el.appendChild(a);
|
||
}
|
||
});
|
||
}
|
||
|
||
function chip(text, tone) {
|
||
const s = document.createElement("span");
|
||
s.className = "kt-ai-chip";
|
||
if (tone) s.setAttribute("data-tone", tone);
|
||
s.textContent = text;
|
||
return s;
|
||
}
|
||
|
||
function show(num) {
|
||
const d = derive(num);
|
||
const detail = document.getElementById("resDetail");
|
||
if (!d) {
|
||
detail.style.display = "none";
|
||
const empty = '<div class="chain-node"><strong>Проверьте номер</strong><span>Введите ABC21UV</span></div>';
|
||
document.getElementById("chain1").innerHTML = empty;
|
||
document.getElementById("chain2").innerHTML = empty;
|
||
document.getElementById("r1src").textContent = "";
|
||
document.getElementById("r2src").textContent = "";
|
||
return;
|
||
}
|
||
|
||
detail.style.display = "block";
|
||
document.getElementById("rdNum").textContent = d.entry ? d.entry.num : num.trim().toUpperCase();
|
||
const chips = document.getElementById("rdChips");
|
||
chips.innerHTML = "";
|
||
if (d.entry) {
|
||
chips.appendChild(chip(d.entry.type, "blue"));
|
||
chips.appendChild(chip(d.entry.station, "gray"));
|
||
chips.appendChild(chip("Город: " + d.entry.city, "gray"));
|
||
} else {
|
||
chips.appendChild(chip("Город: " + d.city, "gray"));
|
||
}
|
||
document.getElementById("rdMeta").textContent = d.entry ? d.entry.desc : "Номер не найден в реестре — показан базовый маршрут.";
|
||
document.getElementById("rdOwn").textContent = "Владелец (по пересчёту): " + d.ownerName;
|
||
const r = document.getElementById("rdReserve");
|
||
if (/не определён/i.test(d.reserve)) {
|
||
r.style.display = "none";
|
||
} else {
|
||
r.style.display = "block";
|
||
r.textContent = "Резерв: " + d.reserve;
|
||
}
|
||
|
||
document.getElementById("r1src").textContent = "Мобильный оператор → SSW/NGN транзит → SSW/NGN рег. → " + (d.entry ? d.entry.num : "оконечный номер");
|
||
document.getElementById("r2src").textContent = "Станция владельца номера → SSW/NGN рег. → " + (d.entry ? d.entry.num : "оконечный номер");
|
||
renderChain("chain1", d.mobile);
|
||
renderChain("chain2", d.fixed);
|
||
renderScheme(d.entry ? d.entry.num : num);
|
||
}
|
||
|
||
function renderRegister() {
|
||
const list = document.getElementById("regList");
|
||
list.innerHTML = "";
|
||
REG.forEach(function (e) {
|
||
const row = document.createElement("div");
|
||
row.className = "kt-ai-card reg-row";
|
||
const top = document.createElement("div");
|
||
top.className = "reg-row-top";
|
||
const num = document.createElement("span");
|
||
num.className = "reg-row-num";
|
||
num.textContent = e.num;
|
||
top.appendChild(num);
|
||
top.appendChild(chip(e.type, "blue"));
|
||
top.appendChild(chip(e.station, "gray"));
|
||
top.appendChild(chip("Резерв: есть", "green"));
|
||
row.appendChild(top);
|
||
const meta = document.createElement("div");
|
||
meta.className = "reg-row-meta";
|
||
meta.textContent = e.desc + " · Город: " + e.city + " · Формат набора: ABC21UV";
|
||
row.appendChild(meta);
|
||
const res = document.createElement("div");
|
||
res.className = "reg-row-reserve";
|
||
res.textContent = "Резерв: " + e.reserve;
|
||
row.appendChild(res);
|
||
list.appendChild(row);
|
||
});
|
||
document.getElementById("regCounter").textContent = "Каждый номер: тип (ТДМ / GPON / SIP), станция владения, формат набора ABC21UV и резерв. В реестре: " + REG.length + ".";
|
||
}
|
||
|
||
function renderScheme(focusNum) {
|
||
const NS = "http://www.w3.org/2000/svg";
|
||
const svg = document.createElementNS(NS, "svg");
|
||
svg.setAttribute("viewBox", "0 0 1100 460");
|
||
svg.setAttribute("role", "img");
|
||
svg.setAttribute("aria-label", "Схема маршрутизации вызовов экстренных служб");
|
||
|
||
const entry = findEntry(focusNum) || REG[0] || null;
|
||
const endNum = entry ? entry.num : "—";
|
||
const hasBrw = entry ? normalize(entry.type).indexOf("BRW") === 0 : false;
|
||
|
||
function rect(x, y, w, h, rx, fill, stroke, sw) {
|
||
const r = document.createElementNS(NS, "rect");
|
||
r.setAttribute("x", x); r.setAttribute("y", y);
|
||
r.setAttribute("width", w); r.setAttribute("height", h);
|
||
r.setAttribute("rx", rx);
|
||
if (fill) r.setAttribute("fill", fill);
|
||
if (stroke) r.setAttribute("stroke", stroke);
|
||
r.setAttribute("stroke-width", sw || 1);
|
||
return r;
|
||
}
|
||
function txt(x, y, s, size, fill, weight) {
|
||
const t = document.createElementNS(NS, "text");
|
||
t.setAttribute("x", x); t.setAttribute("y", y);
|
||
t.setAttribute("text-anchor", "middle");
|
||
t.setAttribute("style", "font-size:" + size + "px; fill:" + fill + ";" + (weight ? " font-weight:" + weight + ";" : ""));
|
||
t.textContent = s;
|
||
return t;
|
||
}
|
||
function edge(x1, y1, x2, y2, dashed, label, ldy) {
|
||
const l = document.createElementNS(NS, "line");
|
||
l.setAttribute("x1", x1); l.setAttribute("y1", y1);
|
||
l.setAttribute("x2", x2); l.setAttribute("y2", y2);
|
||
l.setAttribute("stroke", dashed ? "var(--kt-ai-fg-muted)" : "var(--vibe-accent)");
|
||
l.setAttribute("stroke-width", "1.6");
|
||
if (dashed) l.setAttribute("stroke-dasharray", "5 4");
|
||
l.setAttribute("marker-end", "url(#arrow" + (dashed ? "M" : "A") + ")");
|
||
l.setAttribute("opacity", "0.85");
|
||
svg.appendChild(l);
|
||
if (label) {
|
||
const t = document.createElementNS(NS, "text");
|
||
t.setAttribute("x", (x1 + x2) / 2); t.setAttribute("y", ((y1 + y2) / 2) + (ldy || -7));
|
||
t.setAttribute("text-anchor", "middle");
|
||
t.setAttribute("style", "font-size:10px; fill:var(--kt-ai-fg-muted)");
|
||
t.textContent = label;
|
||
svg.appendChild(t);
|
||
}
|
||
}
|
||
|
||
const defs = document.createElementNS(NS, "defs");
|
||
defs.innerHTML = '<marker id="arrowA" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="var(--vibe-accent)"></path></marker><marker id="arrowM" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="var(--kt-ai-fg-muted)"></path></marker>';
|
||
svg.appendChild(defs);
|
||
|
||
edge(215, 60, 390, 170);
|
||
edge(385, 60, 530, 170);
|
||
edge(560, 204, 620, 266);
|
||
if (hasBrw) {
|
||
edge(695, 300, 656, 334);
|
||
}
|
||
edge(620, 300, 350, 335, true, "резерв при недоступности SIP", -7);
|
||
|
||
const boxFill = "var(--kt-ai-card-bg)";
|
||
const bStroke = "var(--kt-ai-border)";
|
||
|
||
// Мобильные операторы
|
||
svg.appendChild(rect(50, 32, 165, 56, 10, boxFill, bStroke));
|
||
svg.appendChild(txt(132, 56, "Мобильные", 14, "var(--kt-ai-fg)", 600));
|
||
svg.appendChild(txt(132, 73, "операторы", 14, "var(--kt-ai-fg)", 600));
|
||
// ГТС
|
||
svg.appendChild(rect(220, 32, 165, 56, 10, boxFill, bStroke));
|
||
svg.appendChild(txt(302, 63, "ГТС", 15, "var(--kt-ai-fg)", 600));
|
||
// SSW/NGN транзит
|
||
svg.appendChild(rect(390, 170, 170, 56, 10, boxFill, bStroke));
|
||
svg.appendChild(txt(475, 193, "SSW/NGN транзит", 14, "var(--kt-ai-fg)", 600));
|
||
svg.appendChild(txt(475, 210, "транзитная шлюзовая система", 11, "var(--kt-ai-fg-muted)"));
|
||
// SSW/NGN рег.
|
||
svg.appendChild(rect(550, 266, 170, 56, 10, boxFill, bStroke));
|
||
svg.appendChild(txt(635, 289, "SSW/NGN рег.", 14, "var(--kt-ai-fg)", 600));
|
||
svg.appendChild(txt(635, 306, "региональная маршрутизация", 11, "var(--kt-ai-fg-muted)"));
|
||
// Резерв
|
||
svg.appendChild(rect(200, 335, 150, 56, 10, "var(--kt-ai-bg)", bStroke));
|
||
svg.appendChild(txt(275, 358, "SIP Trunk (резерв)", 12, "var(--kt-ai-fg)", 600));
|
||
svg.appendChild(txt(275, 375, "7272222222 · ГТС Алматы", 11, "var(--kt-ai-fg-muted)"));
|
||
// Оконечный номер
|
||
svg.appendChild(rect(700, 335, 190, 56, 10, "var(--kt-ai-bg)", hasBrw ? "var(--vibe-accent)" : bStroke, hasBrw ? 2 : 1));
|
||
svg.appendChild(txt(795, 358, "Оконечный номер", 13, "var(--kt-ai-fg)", 600));
|
||
svg.appendChild(txt(795, 375, endNum, 12, "var(--vibe-accent)", 600));
|
||
// BRW SIP
|
||
if (hasBrw) {
|
||
svg.appendChild(rect(450, 335, 205, 56, 10, boxFill, bStroke));
|
||
svg.appendChild(txt(552, 358, "BRW SIP", 14, "var(--kt-ai-fg)", 600));
|
||
svg.appendChild(txt(552, 375, "7273130333", 12, "var(--kt-ai-fg-muted)", 600));
|
||
const l1 = document.createElementNS(NS, "line");
|
||
l1.setAttribute("x1", 655); l1.setAttribute("y1", 363);
|
||
l1.setAttribute("x2", 695); l1.setAttribute("y2", 363);
|
||
l1.setAttribute("stroke", "var(--vibe-accent)");
|
||
l1.setAttribute("stroke-width", "1.6");
|
||
l1.setAttribute("marker-end", "url(#arrowA)");
|
||
l1.setAttribute("opacity", "0.85");
|
||
svg.appendChild(l1);
|
||
}
|
||
|
||
const el = document.getElementById("schemeSvg");
|
||
el.innerHTML = "";
|
||
el.appendChild(svg);
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
renderRegister();
|
||
renderScheme("727103");
|
||
const input = document.getElementById("num");
|
||
const btn = document.getElementById("findBtn");
|
||
btn.addEventListener("click", function () { show(input.value); });
|
||
input.addEventListener("keydown", function (ev) {
|
||
if (ev.key === "Enter") show(input.value);
|
||
});
|
||
input.addEventListener("input", function () { show(input.value); });
|
||
show("7271UV");
|
||
});
|
||
})();
|