Skip to content

Commit 9c6bc10

Browse files
committed
fix: connect 3 orphan tools (base-converter, bandwidth, ip-reference), replace Geo-IP mock with ip-api.com, remove Bootstrap remnants from main.js, update README badge to 32 passing
1 parent 961943a commit 9c6bc10

7 files changed

Lines changed: 158 additions & 114 deletions

File tree

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
44
[![Status](https://img.shields.io/badge/Status-Stable-green)](https://github.com/Medalcode/NetOpsToolkit)
55
[![Deploy](https://img.shields.io/badge/Deploy-Netlify-00C7B7)](https://netops-toolkit.netlify.app)
6-
[![Tests](https://img.shields.io/badge/Tests-20%20passing-success)](https://github.com/Medalcode/NetOpsToolkit)
6+
[![Tests](https://img.shields.io/badge/Tests-32%20passing-success)](https://github.com/Medalcode/NetOpsToolkit)
77
[![CI](https://github.com/Medalcode/NetOpsToolkit/actions/workflows/ci.yml/badge.svg)](https://github.com/Medalcode/NetOpsToolkit/actions)
8-
[![Netlify Status](https://api.netlify.com/api/v1/badges/PUT-YOUR-BADGE-HERE/deploy-status)](https://app.netlify.com/sites/YOUR-SITE/deploys)
98

109
> **"La Navaja Suiza para Ingenieros de Red"**
1110
>
@@ -21,7 +20,7 @@
2120
-**PWA Ready** - Instalable como aplicación
2221
-**Internationalization** - Soporte nativo Español 🇪🇸 / Inglés 🇺🇸
2322
-**Secure** - CSP headers, XSS prevention
24-
-**Tested** - 29 tests unitarios pasando (Validators + VLSM Logic)
23+
-**Tested** - 32 tests unitarios pasando (Validators + VLSM Logic)
2524
-**Open Source** - MIT License
2625

2726
## 🎯 Herramientas Disponibles
@@ -36,6 +35,9 @@
3635

3736
### 🔧 Utilities
3837

38+
- **Base Converter** - Conversión entre sistemas numéricos (bin/oct/dec/hex)
39+
- **Bandwidth Calculator** - Cálculo de ancho de banda y throughput
40+
- **IP Reference** - Referencia rápida de clases IP, rangos y máscaras
3941
- **Port Reference** - Catálogo de puertos TCP/UDP
4042
- **OUI Lookup** - Identificación de fabricantes por MAC
4143
- **Config Generator** - Plantillas Cisco, Mikrotik, Juniper
@@ -112,7 +114,10 @@ npm run test:coverage
112114

113115
**Estado actual**: 3 test suites, 32 tests pasando (añadidos tests para `dns-core` el 2026-01-27)
114116

115-
### Cambios Recientes (v4.0.0 - 2026-02-25)
117+
### Cambios Recientes (v4.0.0 - 2026-05-17)
118+
- **3 herramientas rescatadas**: Base Converter, Bandwidth Calculator e IP Reference estaban huérfanas (registradas en JS pero sin HTML). Se inyecta su HTML al contenedor dinámicamente.
119+
- **Geo-IP con datos reales**: Reemplazada la función mock por llamadas a la API pública `ip-api.com`.
120+
- **Adiós Bootstrap**: Eliminada dependencia de Bootstrap CSS/JS. Todo el estilo usa Tailwind/cyber.
116121
- **Refactorización Lean**: Eliminación de archivos legados (`index_legacy.html`, `js/` raíz) y consolidación de lógica dispersa.
117122
- **Estructura de Capas**: Separación clara entre `core/`, `platform/` y `ui/`.
118123
- **Skills Consilidadas**: Implementación de Super-Skills paramétricas (`identity-service`, `net-analysis-engine`).

index.html

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,33 @@ <h3 class="text-lg font-bold text-white mb-2" data-i18n="keygen.title">Key Gener
178178
<h3 class="text-lg font-bold text-white mb-2" data-i18n="ports.title">Port Reference</h3>
179179
<p class="text-slate-400 text-sm" data-i18n="ports.desc">Searchable database of common TCP/UDP ports.</p>
180180
</div>
181+
182+
<!-- Base Converter (formerly orphaned) -->
183+
<div class="bg-surface-dark cyber-border rounded p-6 hover:border-primary transition-colors cursor-pointer group card-hover" onclick="switchToolView('tool-hex')">
184+
<div class="text-emerald-500 mb-4 p-3 bg-emerald-500/10 rounded inline-block group-hover:bg-emerald-500 group-hover:text-white transition-colors">
185+
<span class="material-symbols-outlined">conversion_path</span>
186+
</div>
187+
<h3 class="text-lg font-bold text-white mb-2" data-i18n="hex.title">Base Converter</h3>
188+
<p class="text-slate-400 text-sm" data-i18n="hex.desc">Convert between decimal, binary, and hexadecimal.</p>
189+
</div>
190+
191+
<!-- Bandwidth Calculator (formerly orphaned) -->
192+
<div class="bg-surface-dark cyber-border rounded p-6 hover:border-primary transition-colors cursor-pointer group card-hover" onclick="switchToolView('tool-bw')">
193+
<div class="text-violet-500 mb-4 p-3 bg-violet-500/10 rounded inline-block group-hover:bg-violet-500 group-hover:text-white transition-colors">
194+
<span class="material-symbols-outlined">speed</span>
195+
</div>
196+
<h3 class="text-lg font-bold text-white mb-2" data-i18n="bw.title">Bandwidth Calc</h3>
197+
<p class="text-slate-400 text-sm" data-i18n="bw.desc">Calculate transfer time from file size and speed.</p>
198+
</div>
199+
200+
<!-- IP Reference (formerly orphaned) -->
201+
<div class="bg-surface-dark cyber-border rounded p-6 hover:border-primary transition-colors cursor-pointer group card-hover" onclick="switchToolView('tool-ip-ref')">
202+
<div class="text-amber-500 mb-4 p-3 bg-amber-500/10 rounded inline-block group-hover:bg-amber-500 group-hover:text-white transition-colors">
203+
<span class="material-symbols-outlined">info</span>
204+
</div>
205+
<h3 class="text-lg font-bold text-white mb-2" data-i18n="ipref.title">IP Reference</h3>
206+
<p class="text-slate-400 text-sm" data-i18n="ipref.desc">Classful IP ranges, private blocks, and special addresses.</p>
207+
</div>
181208
</div>
182209

183210
<!-- === DYNAMIC TOOL CONTAINER (Where individual tools load) === -->

netlify/functions/geo-ip.js

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
1-
/**
2-
* GeoIP Function (Serverless)
3-
* Acts as a secure proxy to get client IP and Geo data.
4-
*/
5-
exports.handler = async (event, context) => {
6-
// Netlify provides client IP in headers
7-
const clientIp = event.headers['x-nf-client-connection-ip'] || event.headers['client-ip'] || '127.0.0.1';
8-
9-
// Simulated Geo Data (In prod, you'd call MaxMind or similar backend service)
10-
// We simulate it here to avoid external API dependencies that break or require keys for this demo.
11-
// But purely getting the IP from the request headers is a true "Backend" task.
12-
13-
const mockGeo = {
14-
ip: clientIp,
15-
city: "Unknown City",
16-
region: "Region",
17-
country: "Internet",
18-
org: "ISP"
19-
};
1+
exports.handler = async (event) => {
2+
const clientIp = event.headers['x-nf-client-connection-ip']
3+
|| event.headers['client-ip']
4+
|| event.headers['x-forwarded-for']?.split(',')[0].trim()
5+
|| '127.0.0.1';
206

21-
return {
22-
statusCode: 200,
23-
headers: {
24-
"Content-Type": "application/json",
25-
"Access-Control-Allow-Origin": "*" // Allow CORS
26-
},
27-
body: JSON.stringify(mockGeo)
28-
};
7+
try {
8+
const res = await fetch(`http://ip-api.com/json/${clientIp}?fields=query,city,region,country,org`);
9+
if (!res.ok) throw new Error(`ip-api returned ${res.status}`);
10+
const data = await res.json();
11+
return {
12+
statusCode: 200,
13+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
14+
body: JSON.stringify({
15+
ip: data.query || clientIp,
16+
city: data.city || "Unknown",
17+
region: data.region || "",
18+
country: data.country || "",
19+
org: data.org || "Unknown",
20+
}),
21+
};
22+
} catch (err) {
23+
return {
24+
statusCode: 200,
25+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
26+
body: JSON.stringify({
27+
ip: clientIp,
28+
city: "Unavailable",
29+
region: "",
30+
country: "",
31+
org: "",
32+
}),
33+
};
34+
}
2935
};

src/ui/components/bandwidth.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,39 @@
1-
export function initBandwidthTool() {
1+
export function initBandwidthTool(container) {
2+
container.innerHTML = `
3+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
4+
<div class="space-y-4">
5+
<div>
6+
<label class="block text-[10px] font-bold text-slate-500 uppercase mb-1">File Size</label>
7+
<div class="flex gap-2">
8+
<input id="bw-size" type="number" placeholder="100" class="flex-1 bg-black border border-border-dark rounded px-3 py-2 text-sm mono-data text-white placeholder-slate-700 focus:border-primary transition-colors">
9+
<select id="bw-size-unit" class="bg-black border border-border-dark rounded px-3 py-2 text-sm mono-data text-white cursor-pointer">
10+
<option value="1">Bytes</option>
11+
<option value="1024" selected>KB</option>
12+
<option value="1048576">MB</option>
13+
<option value="1073741824">GB</option>
14+
</select>
15+
</div>
16+
</div>
17+
<div>
18+
<label class="block text-[10px] font-bold text-slate-500 uppercase mb-1">Transfer Speed</label>
19+
<div class="flex gap-2">
20+
<input id="bw-speed" type="number" placeholder="50" class="flex-1 bg-black border border-border-dark rounded px-3 py-2 text-sm mono-data text-white placeholder-slate-700 focus:border-primary transition-colors">
21+
<select id="bw-speed-unit" class="bg-black border border-border-dark rounded px-3 py-2 text-sm mono-data text-white cursor-pointer">
22+
<option value="1">bps</option>
23+
<option value="1000">Kbps</option>
24+
<option value="1000000" selected>Mbps</option>
25+
<option value="1000000000">Gbps</option>
26+
</select>
27+
</div>
28+
</div>
29+
<button id="btn-bw-calc" class="bg-primary hover:bg-primary/80 text-white text-xs font-bold uppercase tracking-widest px-4 py-2 rounded transition-colors">Calculate</button>
30+
</div>
31+
<div id="bw-results" class="bg-surface-dark cyber-border rounded p-4 text-slate-400 text-sm">
32+
Enter a file size and speed to calculate transfer time.
33+
</div>
34+
</div>
35+
`;
36+
237
const sizeInput = document.getElementById("bw-size");
338
const sizeUnit = document.getElementById("bw-size-unit");
439
const speedInput = document.getElementById("bw-speed");

src/ui/components/base-converter.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,30 @@
33
* @module converter
44
*/
55

6-
export function initConverter() {
6+
export function initConverter(container) {
7+
container.innerHTML = `
8+
<div class="space-y-4">
9+
<div>
10+
<label class="block text-[10px] font-bold text-slate-500 uppercase mb-1">Enter a number (with or without prefix):</label>
11+
<input id="converter-input" type="text" placeholder="e.g. 255, 0xFF, 0b11111111" class="w-full bg-black border border-border-dark rounded px-3 py-2 text-sm mono-data text-white placeholder-slate-700 focus:border-primary transition-colors">
12+
</div>
13+
<div class="grid grid-cols-3 gap-4">
14+
<div class="bg-surface-dark cyber-border rounded p-4">
15+
<div class="text-[10px] text-slate-500 uppercase font-bold mb-1">Decimal</div>
16+
<div id="res-dec" class="text-2xl font-bold text-white mono-data">—</div>
17+
</div>
18+
<div class="bg-surface-dark cyber-border rounded p-4">
19+
<div class="text-[10px] text-slate-500 uppercase font-bold mb-1">Binary</div>
20+
<div id="res-bin" class="text-2xl font-bold text-white mono-data break-all">—</div>
21+
</div>
22+
<div class="bg-surface-dark cyber-border rounded p-4">
23+
<div class="text-[10px] text-slate-500 uppercase font-bold mb-1">Hexadecimal</div>
24+
<div id="res-hex" class="text-2xl font-bold text-white mono-data">—</div>
25+
</div>
26+
</div>
27+
</div>
28+
`;
29+
730
const input = document.getElementById("converter-input");
831
if (!input) return;
932

src/ui/components/ip_reference.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
export function initIpRefTool() {
2-
const container = document.getElementById("ip-ref-content");
3-
if (!container) return; // Guard clause
1+
export function initIpRefTool(container) {
2+
container.innerHTML = '<div id="ip-ref-content"></div>';
3+
const content = document.getElementById("ip-ref-content");
4+
if (!content) return;
45

56
// Function to generate HTML for a table
67
const createTable = (title, columns, data) => `
@@ -48,7 +49,7 @@ export function initIpRefTool() {
4849
ipReferenceData.special
4950
);
5051

51-
container.innerHTML = classesHTML + privateHTML + specialHTML;
52+
content.innerHTML = classesHTML + privateHTML + specialHTML;
5253
}
5354

5455
export const ipReferenceData = {

src/ui/main.js

Lines changed: 24 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ async function init() {
7272

7373
// 1. Theme (Immediate visual stability)
7474
initTheme();
75-
updateBootstrapTheme(getEffectiveTheme()); // Sync Bootstrap
7675
console.log("✅ Theme System");
7776

7877
// 2. Internationalization
@@ -100,10 +99,10 @@ async function init() {
10099
// Fallback: try to show error on screen if possible
101100
const main = document.querySelector("main");
102101
if (main) {
103-
main.innerHTML = `<div class="alert alert-danger m-4">
104-
<h4>System Error</h4>
105-
<p>Failed to initialize application: ${e.message}</p>
106-
<button class="btn btn-outline-danger" onclick="location.reload()">Reload</button>
102+
main.innerHTML = `<div class="bg-red-500/10 border border-red-500/30 rounded p-6 m-4 text-red-400">
103+
<h4 class="text-lg font-bold mb-2">System Error</h4>
104+
<p class="text-sm mb-3">Failed to initialize application: ${e.message}</p>
105+
<button class="bg-red-500 hover:bg-red-600 text-white text-xs font-bold px-4 py-2 rounded transition-colors" onclick="location.reload()">Reload</button>
107106
</div>`;
108107
}
109108
}
@@ -427,98 +426,46 @@ function runVLSMCalculation() {
427426
* Global Actions (Buttons)
428427
*/
429428
function setupGlobalActions() {
430-
const container = document.querySelector(".global-actions");
431-
if (!container) return;
432-
433-
container.innerHTML = "";
434-
435429
// --- History System Integration ---
436-
// Create the panel and overlay (hidden by default)
437430
const { panel, overlay, content } = createHistoryPanel(
438-
// On Load Item
439431
item => {
440-
// Restore inputs
441432
const ipEl = document.getElementById("vlsm-ip");
442433
const hostsEl = document.getElementById("vlsm-hosts");
443434
if (ipEl) ipEl.value = item.network;
444435
if (hostsEl) hostsEl.value = item.hosts;
445-
446-
// Go to dashboard/VLSM view
447436
showView("view-vlsm", "VLSM CALCULATOR");
448437
panel.classList.remove("open");
449438
overlay.classList.remove("active");
450439
},
451-
// On Delete Item
452-
id => {
453-
removeFromHistory(id);
454-
refreshHistory();
455-
},
456-
// On Clear All
457-
() => {
458-
clearHistory();
459-
refreshHistory();
460-
}
440+
id => { removeFromHistory(id); refreshHistory(); },
441+
() => { clearHistory(); refreshHistory(); }
461442
);
462443

463-
// Refresh function helper
464444
function refreshHistory() {
465-
updateHistoryPanel(
466-
content,
467-
getHistory(),
468-
getHistoryStats(),
469-
() => {
470-
/* Load handled in createHistoryPanel closure above */
471-
},
472-
id => {
473-
removeFromHistory(id);
474-
refreshHistory();
475-
},
476-
() => {
477-
clearHistory();
478-
refreshHistory();
479-
}
445+
updateHistoryPanel(content, getHistory(), getHistoryStats(),
446+
() => {},
447+
id => { removeFromHistory(id); refreshHistory(); },
448+
() => { clearHistory(); refreshHistory(); }
480449
);
481450
}
482451

483-
// Append Panel & Overlay to Body (outside header)
484452
document.body.appendChild(overlay);
485453
document.body.appendChild(panel);
486-
// Add History Button to Header
487-
const historyBtn = document.createElement("button");
488-
historyBtn.className = "btn btn-outline-info btn-sm me-2";
489-
historyBtn.innerHTML = "<i class=\"fas fa-history\"></i> Historial";
490-
historyBtn.onclick = () => {
491-
refreshHistory(); // Update data before showing
492-
panel.classList.add("open");
493-
overlay.classList.add("active");
494-
};
495-
container.appendChild(historyBtn);
496-
// ----------------------------------
497-
498-
// Theme Button
499-
const themeBtn = createThemeToggle();
500-
themeBtn.className = "btn btn-outline-secondary btn-sm me-2";
501-
themeBtn.addEventListener("click", () => {
502-
setTimeout(() => updateBootstrapTheme(getEffectiveTheme()), 50);
503-
});
504-
container.appendChild(themeBtn);
505-
506-
// Lang Button
507-
const langBtn = document.createElement("button");
508-
langBtn.className = "btn btn-outline-secondary btn-sm";
509-
langBtn.innerHTML = document.documentElement.lang === "es" ? "🇺🇸 EN" : "🇪🇸 ES";
510-
langBtn.onclick = () => switchAppLanguage(langBtn);
511-
container.appendChild(langBtn);
512-
}
513454

514-
function switchAppLanguage(btn) {
515-
const newLang = document.documentElement.lang === "es" ? "en" : "es";
516-
setLanguage(newLang);
517-
btn.innerHTML = newLang === "es" ? "🇺🇸 EN" : "🇪🇸 ES";
518-
}
519-
520-
function updateBootstrapTheme(theme) {
521-
document.documentElement.setAttribute("data-bs-theme", theme === "dark" ? "dark" : "light");
455+
// Add History button to the header next to the tools buttons
456+
const headerRight = document.querySelector("header .flex.items-center.gap-4:last-child");
457+
if (headerRight) {
458+
const historyBtn = document.createElement("button");
459+
historyBtn.className = "px-3 py-1.5 text-[10px] font-bold uppercase tracking-widest rounded bg-surface-dark text-slate-400 border border-border-dark hover:text-white hover:border-primary transition-all";
460+
historyBtn.innerHTML = '<span class="material-symbols-outlined !text-sm" style="vertical-align: middle;">history</span>';
461+
historyBtn.title = "History";
462+
historyBtn.onclick = () => {
463+
refreshHistory();
464+
panel.classList.add("open");
465+
overlay.classList.add("active");
466+
};
467+
headerRight.insertBefore(historyBtn, headerRight.firstChild);
468+
}
522469
}
523470

524471
// Boot

0 commit comments

Comments
 (0)