PageSourceSearch

https://catellix.com/static/js/recent-pages.js?v=20260417-recent-ui

js catellix.com collected 2026-09-24 12:57:06 UTC 11,254 bytes, 277 lines download raw bytes

1/**
2 * Histórico local das últimas páginas visitadas (atalhos no menu lateral do dashboard).
3 * Persistência: localStorage. Carregado de forma assíncrona a partir de csrf.js.
4 */
5(function () {
6    'use strict';
7
8    var STORAGE_KEY = 'catellix_nav_recent_v1';
9    var MAX = 5;
10
11    var SKIP_PREFIXES = ['/static/', '/api/', '/docs', '/openapi'];
12    var SKIP_PATHS = {
13        '/login': 1,
14        '/register': 1,
15        '/logout': 1,
16        '/reset_password': 1,
17        '/set_new_password': 1,
18        '/confirm_email': 1,
19        '/comece-gratis': 1
20    };
21
22    /** Rotas conhecidas: chave = pathname + query normalizado */
23    var ROUTES = {
24        '/dashboard': { label: 'Painel', icon: '📊', module: 'dashboard' },
25        '/dashboard?tab=alerts': { label: 'Alertas', icon: '🔔', module: 'dashboard_alerts' },
26        '/dashboard?tab=impact': { label: 'Impacto', icon: '💰', module: 'dashboard_impact' },
27        '/dashban': { label: 'DashBan', icon: '📝', module: 'dashban' },
28        '/custom-dashboards': { label: 'Meus painéis', icon: '+', module: 'custom_dashboards' },
29        '/network-maps': { label: 'Mapas', icon: '🗺', module: 'network_maps' },
30        '/perfil': { label: 'Perfil', icon: '👤', module: 'perfil' },
31        '/settings': { label: 'Definições', icon: '⚙', module: 'settings' },
32        '/cad_hosts': { label: 'Hosts', icon: '⊞', module: 'cad_hosts' },
33        '/ipam': { label: 'IPAM', icon: '📡', module: 'ipam' },
34        '/pops': { label: 'POPs', icon: '🏢', module: 'pops' },
35        '/backup': { label: 'Backup', icon: '◫', module: 'backup' },
36        '/tasks': { label: 'Tarefas', icon: '☑', module: 'tasks' },
37        '/pop-monitoring': { label: 'Câmaras', icon: '📹', module: 'pop_monitoring' },
38        '/servidores': { label: 'Servidores', icon: '🖥', module: 'servidores' },
39        '/pc-notebook': { label: 'Endpoints', icon: '💻', module: 'desktop_lite' },
40        '/looking-glass': { label: 'LG', icon: '🔍', module: 'looking_glass' },
41        '/bgp': { label: 'BGP', icon: '⊡', module: 'bgp' },
42        '/ospf': { label: 'OSPF', icon: '◇', module: 'ospf' },
43        '/mpls': { label: 'MPLS', icon: '⬡', module: 'mpls' },
44        '/layer2': { label: 'Layer 2', icon: '▦', module: 'layer2' },
45        '/tools': { label: 'Ferramentas', icon: '🔧', module: 'tools' },
46        '/analise-rede': { label: 'Latência', icon: '📊', module: 'analise_rede' },
47        '/reports': { label: 'Relatórios', icon: '📄', module: 'reports' },
48        '/flow': { label: 'Flow', icon: '〰', module: 'flow' },
49        '/gmud': { label: 'Mudanças', icon: '📋', module: 'gmud' },
50        '/wifi-analyser': { label: 'Wi‑Fi', icon: '📶', module: 'dashboard' },
51        '/detector': { label: 'Detector', icon: '📡', module: null },
52        '/constructor': { label: 'Constructor', icon: '✦', module: 'constructor' },
53        '/fasttask': { label: 'Fast Task', icon: '⌘', module: null },
54        '/analise_hosts': { label: 'Análise de Hosts', icon: '📊', module: 'analise_rede' },
55        '/manager': { label: 'ERP', icon: '⚡', module: null },
56        '/manager/users-licenses': { label: 'Manager – Licenças Monitoring', icon: '🔐', module: null },
57        '/admin/childrenllix': { label: 'Manager – Kids licenças', icon: '📱', module: null },
58        '/admin/cupons': { label: 'Cupons', icon: '🎁', module: 'admin_cupons' },
59        '/admin/vendas-whatsapp': { label: 'WhatsApp', icon: '📱', module: 'admin_vendas_whatsapp' },
60        '/admin/security-audit': { label: 'Auditoria', icon: '🛡', module: null },
61        '/revenda/dashboard': { label: 'Painel Empresa', icon: '🏢', module: null }
62    };
63
64    function normalizeUrlPathSearch(pathname, search) {
65        try {
66            var p = pathname || '/';
67            if (p.length > 1 && p.charAt(p.length - 1) === '/') p = p.slice(0, -1);
68            var raw = (search && search.charAt(0) === '?') ? search.slice(1) : (search || '');
69            var sp = new URLSearchParams(raw);
70            var keys = Array.from(sp.keys()).sort();
71            var q = new URLSearchParams();
72            keys.forEach(function (k) {
73                var vals = sp.getAll(k).slice().sort();
74                vals.forEach(function (v) { q.append(k, v); });
75            });
76            var qs = q.toString();
77            return p + (qs ? '?' + qs : '');
78        } catch (e) {
79            return (pathname || '/') + (search || '');
80        }
81    }
82
83    function normalizeStoredUrl(u) {
84        if (!u || typeof u !== 'string') return '';
85        try {
86            if (u.charAt(0) === '/') {
87                var q = u.indexOf('?');
88                if (q < 0) return normalizeUrlPathSearch(u, '');
89                return normalizeUrlPathSearch(u.slice(0, q), u.slice(q));
90            }
91            var parsed = new URL(u, window.location.origin);
92            return normalizeUrlPathSearch(parsed.pathname, parsed.search);
93        } catch (e2) {
94            return u;
95        }
96    }
97
98    function shouldSkipPath(pathNorm) {
99        var low = (pathNorm.split('?')[0] || '').toLowerCase();
100        if (SKIP_PATHS[low]) return true;
101        for (var i = 0; i < SKIP_PREFIXES.length; i++) {
102            if (low.indexOf(SKIP_PREFIXES[i]) === 0) return true;
103        }
104        return false;
105    }
106
107    function humanizePath(pathNorm) {
108        var base = pathNorm.split('?')[0] || '/';
109        if (base === '/') return 'Início';
110        var parts = base.replace(/^\/+/, '').split('/');
111        var last = parts[parts.length - 1] || base;
112        return last.replace(/[-_]+/g, ' ').replace(/\b\w/g, function (c) { return c.toUpperCase(); });
113    }
114
115    function getMeta(pathNorm) {
116        if (ROUTES[pathNorm]) return ROUTES[pathNorm];
117        var q = pathNorm.indexOf('?');
118        if (q > 0) {
119            var bare = pathNorm.slice(0, q);
120            if (ROUTES[bare]) return ROUTES[bare];
121        }
122        return { label: humanizePath(pathNorm), icon: '🔗', module: null };
123    }
124
125    function readList() {
126        try {
127            var raw = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
128            return Array.isArray(raw) ? raw : [];
129        } catch (e) {
130            return [];
131        }
132    }
133
134    function writeList(arr) {
135        try {
136            localStorage.setItem(STORAGE_KEY, JSON.stringify(arr.slice(0, MAX)));
137        } catch (e) {}
138    }
139
140    function recordVisit() {
141        var pathNorm = normalizeUrlPathSearch(window.location.pathname, window.location.search);
142        if (!pathNorm || shouldSkipPath(pathNorm)) return;
143
144        var meta = getMeta(pathNorm);
145        var entry = {
146            url: pathNorm,
147            label: meta.label,
148            icon: meta.icon,
149            module: meta.module
150        };
151
152        var list = readList();
153        var next = [entry];
154        for (var i = 0; i < list.length; i++) {
155            var it = list[i];
156            if (!it || !it.url) continue;
157            if (normalizeStoredUrl(it.url) === pathNorm) continue;
158            next.push(it);
159            if (next.length >= MAX) break;
160        }
161        writeList(next);
162    }
163
164    var MANAGER_RESTRICTED_PREFIXES = ['/manager', '/admin/security-audit', '/admin/cupons', '/admin/vendas-whatsapp', '/admin/childrenllix', '/admin/provedores', '/admin/escolas', '/admin/prospectos'];
165
166    function isManagerRestrictedPath(url) {
167        var path = (url || '').split('?')[0].toLowerCase();
168        for (var i = 0; i < MANAGER_RESTRICTED_PREFIXES.length; i++) {
169            if (path === MANAGER_RESTRICTED_PREFIXES[i] || path.indexOf(MANAGER_RESTRICTED_PREFIXES[i] + '/') === 0) return true;
170        }
171        return false;
172    }
173
174    function renderNavShortcuts() {
175        var box = document.getElementById('navRecentShortcuts');
176        if (!box) return;
177
178        while (box.firstChild) box.removeChild(box.firstChild);
179
180        var cur = normalizeUrlPathSearch(window.location.pathname, window.location.search);
181        var raw = readList();
182        var seen = {};
183        var shown = 0;
184
185        var isManagerErp = false;
186        try {
187            var u = JSON.parse(localStorage.getItem('user') || '{}');
188            isManagerErp = ((u.email || '').trim().toLowerCase() === '[email protected]');
189        } catch (e) {}
190
191        for (var i = 0; i < raw.length && shown < MAX; i++) {
192            var it = raw[i];
193            if (!it || !it.url) continue;
194            var nu = normalizeStoredUrl(it.url);
195            if (!nu || nu === cur) continue;
196            if (seen[nu]) continue;
197            if (!isManagerErp && isManagerRestrictedPath(nu)) continue;
198            seen[nu] = true;
199
200            var a = document.createElement('a');
201            a.href = nu;
202            a.className = 'nav-recent-link';
203            a.setAttribute('role', 'menuitem');
204            if (it.module) a.setAttribute('data-module-id', it.module);
205
206            var icon = document.createElement('span');
207            icon.className = 'nav-icon';
208            icon.setAttribute('aria-hidden', 'true');
209            icon.textContent = it.icon || '🔗';
210
211            var span = document.createElement('span');
212            span.textContent = it.label || humanizePath(nu);
213
214            a.appendChild(icon);
215            a.appendChild(span);
216            box.appendChild(a);
217            shown++;
218        }
219
220        var isWeb = false;
221        try { isWeb = window.__catellixEnabledWebModulesIsWeb === true; } catch (e1) {}
222        var enabled = [];
223        try { enabled = window.__catellixEnabledWebModules || []; } catch (e2) {}
224        if (isWeb && enabled.length) {
225            box.querySelectorAll('a[data-module-id]').forEach(function (link) {
226                var mid = link.getAttribute('data-module-id');
227                if (!mid) return;
228                link.style.display = enabled.indexOf(mid) >= 0 ? '' : 'none';
229            });
230        }
231
232        var block = document.getElementById('navRecentBlock');
233        if (block) {
234            var anyVis = false;
235            for (var k = 0; k < box.children.length; k++) {
236                if (box.children[k].style.display !== 'none') { anyVis = true; break; }
237            }
238            block.style.display = (box.childElementCount && anyVis) ? '' : 'none';
239        }
240    }
241
242    function bindLogoutClear() {
243        try {
244            document.querySelectorAll('a[href="/logout"]').forEach(function (a) {
245                a.addEventListener('click', function () {
246                    try { localStorage.removeItem(STORAGE_KEY); } catch (e) {}
247                });
248            });
249        } catch (e) {}
250    }
251
252    function run() {
253        recordVisit();
254        bindLogoutClear();
255
256        function tryRender() {
257            if (document.getElementById('navRecentShortcuts')) {
258                renderNavShortcuts();
259                return;
260            }
261            if (document.readyState === 'loading') {
262                document.addEventListener('DOMContentLoaded', tryRender);
263                return;
264            }
265            setTimeout(tryRender, 50);
266        }
267        tryRender();
268    }
269
270    window.CatellixRecentPages = {
271        record: recordVisit,
272        render: renderNavShortcuts,
273        normalize: normalizeStoredUrl
274    };
275
276    run();
277})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.