DevTools: Text Diff und Netzwerk-Rechner (Geschwindigkeit, IP/Subnetz, CIDR) hinzugefügt
This commit is contained in:
@@ -939,6 +939,411 @@ function KeyGen({ toast }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────── Text Diff ──
|
||||||
|
|
||||||
|
function diffLines(a, b) {
|
||||||
|
const aLines = a.split('\n');
|
||||||
|
const bLines = b.split('\n');
|
||||||
|
// Myers diff (simple LCS-based)
|
||||||
|
const m = aLines.length, n = bLines.length;
|
||||||
|
const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));
|
||||||
|
for (let i = m-1; i >= 0; i--)
|
||||||
|
for (let j = n-1; j >= 0; j--)
|
||||||
|
dp[i][j] = aLines[i] === bLines[j] ? dp[i+1][j+1]+1 : Math.max(dp[i+1][j], dp[i][j+1]);
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
let i = 0, j = 0;
|
||||||
|
while (i < m || j < n) {
|
||||||
|
if (i < m && j < n && aLines[i] === bLines[j]) {
|
||||||
|
result.push({ type: 'same', text: aLines[i] }); i++; j++;
|
||||||
|
} else if (j < n && (i >= m || dp[i][j+1] >= dp[i+1][j])) {
|
||||||
|
result.push({ type: 'add', text: bLines[j] }); j++;
|
||||||
|
} else {
|
||||||
|
result.push({ type: 'del', text: aLines[i] }); i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TextDiff({ mobile }) {
|
||||||
|
const [left, setLeft] = useState('');
|
||||||
|
const [right, setRight] = useState('');
|
||||||
|
const [diff, setDiff] = useState(null);
|
||||||
|
|
||||||
|
function compare() {
|
||||||
|
if (!left.trim() && !right.trim()) return;
|
||||||
|
setDiff(diffLines(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() { setLeft(''); setRight(''); setDiff(null); }
|
||||||
|
|
||||||
|
function loadFile(e, setter) {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = ev => { setter(ev.target.result); setDiff(null); };
|
||||||
|
reader.readAsText(file);
|
||||||
|
e.target.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const added = diff ? diff.filter(d=>d.type==='add').length : 0;
|
||||||
|
const removed = diff ? diff.filter(d=>d.type==='del').length : 0;
|
||||||
|
const same = diff ? diff.filter(d=>d.type==='same').length : 0;
|
||||||
|
|
||||||
|
const ta = { ...S.inp, fontSize:12, fontFamily:"'Courier New',monospace", width:'100%',
|
||||||
|
boxSizing:'border-box', minHeight:180, resize:'vertical', lineHeight:1.6 };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{display:'grid', gridTemplateColumns: mobile?'1fr':'1fr 1fr', gap:12, marginBottom:12}}>
|
||||||
|
{[['left','Text A (Original)', left, setLeft], ['right','Text B (Neu)', right, setRight]].map(([side, label, val, setter])=>(
|
||||||
|
<div key={side}>
|
||||||
|
<div style={{display:'flex', alignItems:'center', gap:8, marginBottom:6}}>
|
||||||
|
<span style={{color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, flex:1}}>{label}</span>
|
||||||
|
<label style={{...S.btn('#4ecdc4',false), fontSize:10, padding:'3px 10px', cursor:'pointer'}}>
|
||||||
|
📂 .txt laden
|
||||||
|
<input type="file" accept=".txt,.md,.log,.csv,.json,.xml,.yaml,.yml,.js,.ts,.jsx,.tsx,.py,.php,.sh,.env"
|
||||||
|
style={{display:'none'}} onChange={e=>loadFile(e,setter)}/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<textarea style={ta} value={val} placeholder="Text hier einfügen…"
|
||||||
|
onChange={e=>{ setter(e.target.value); setDiff(null); }}/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{display:'flex', gap:8, marginBottom:16, flexWrap:'wrap'}}>
|
||||||
|
<button style={{...S.btn('#4ecdc4',true), fontSize:12, padding:'8px 20px'}}
|
||||||
|
onClick={compare}>Vergleichen</button>
|
||||||
|
{!!(left||right||diff) && (
|
||||||
|
<button style={{...S.btn('#ff6b9d',false), fontSize:11, padding:'8px 16px'}}
|
||||||
|
onClick={clear}>✕ Leeren</button>
|
||||||
|
)}
|
||||||
|
{!!diff && (
|
||||||
|
<div style={{display:'flex', gap:10, alignItems:'center', flexWrap:'wrap'}}>
|
||||||
|
<span style={{background:'rgba(255,107,157,0.15)', color:'#ff6b9d', fontFamily:'monospace',
|
||||||
|
fontSize:11, padding:'4px 10px', borderRadius:6}}>− {removed} entfernt</span>
|
||||||
|
<span style={{background:'rgba(78,205,196,0.15)', color:'#4ecdc4', fontFamily:'monospace',
|
||||||
|
fontSize:11, padding:'4px 10px', borderRadius:6}}>+ {added} hinzugefügt</span>
|
||||||
|
<span style={{background:'rgba(255,255,255,0.05)', color:'rgba(255,255,255,0.45)', fontFamily:'monospace',
|
||||||
|
fontSize:11, padding:'4px 10px', borderRadius:6}}>= {same} gleich</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!!diff && (
|
||||||
|
<div style={{borderRadius:9, border:'1px solid rgba(255,255,255,0.08)',
|
||||||
|
overflow:'auto', maxHeight:480, background:'rgba(0,0,0,0.2)'}}>
|
||||||
|
<table style={{width:'100%', borderCollapse:'collapse', fontFamily:"'Courier New',monospace", fontSize:12}}>
|
||||||
|
<tbody>
|
||||||
|
{diff.map((d, i) => (
|
||||||
|
<tr key={i} style={{
|
||||||
|
background: d.type==='add' ? 'rgba(78,205,196,0.1)' : d.type==='del' ? 'rgba(255,107,157,0.1)' : 'transparent',
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.03)'}}>
|
||||||
|
<td style={{width:24, textAlign:'center', padding:'2px 6px', userSelect:'none',
|
||||||
|
color: d.type==='add'?'#4ecdc4': d.type==='del'?'#ff6b9d':'rgba(255,255,255,0.2)',
|
||||||
|
fontWeight:700, fontSize:14, borderRight:'1px solid rgba(255,255,255,0.05)'}}>
|
||||||
|
{d.type==='add'?'+': d.type==='del'?'−':''}
|
||||||
|
</td>
|
||||||
|
<td style={{padding:'3px 10px', whiteSpace:'pre-wrap', wordBreak:'break-all',
|
||||||
|
color: d.type==='add'?'#a8f0eb': d.type==='del'?'#ffb3cc':'rgba(255,255,255,0.65)'}}>
|
||||||
|
{d.text || ' '}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!diff && (
|
||||||
|
<div style={{color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:11,
|
||||||
|
padding:'20px 0', textAlign:'center'}}>
|
||||||
|
Texte eingeben oder Dateien laden → Vergleichen klicken
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────── Netzwerk ──
|
||||||
|
|
||||||
|
function NetzwerkRechner({ mobile }) {
|
||||||
|
const [nTab, setNTab] = useState('speed');
|
||||||
|
|
||||||
|
// ── Geschwindigkeit ──
|
||||||
|
const [speedVal, setSpeedVal] = useState('');
|
||||||
|
const [speedUnit, setSpeedUnit] = useState('mbps');
|
||||||
|
|
||||||
|
const SPEED_UNITS = [
|
||||||
|
{ id:'bps', label:'bit/s', toBps: 1 },
|
||||||
|
{ id:'kbps', label:'kbit/s', toBps: 1e3 },
|
||||||
|
{ id:'mbps', label:'Mbit/s', toBps: 1e6 },
|
||||||
|
{ id:'gbps', label:'Gbit/s', toBps: 1e9 },
|
||||||
|
{ id:'bBps', label:'B/s', toBps: 8 },
|
||||||
|
{ id:'kBps', label:'kB/s', toBps: 8e3 },
|
||||||
|
{ id:'mBps', label:'MB/s', toBps: 8e6 },
|
||||||
|
{ id:'gBps', label:'GB/s', toBps: 8e9 },
|
||||||
|
];
|
||||||
|
|
||||||
|
function fmtSpeed(bps, u) {
|
||||||
|
const v = bps / u.toBps;
|
||||||
|
if (v >= 1000) return v.toLocaleString('de-DE', {maximumFractionDigits:2}) + ' ' + u.label;
|
||||||
|
return parseFloat(v.toPrecision(5)).toLocaleString('de-DE') + ' ' + u.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
const speedBps = parseFloat(speedVal) * (SPEED_UNITS.find(u=>u.id===speedUnit)?.toBps||1e6);
|
||||||
|
const speedValid = !isNaN(speedBps) && speedVal !== '';
|
||||||
|
|
||||||
|
// Download-Zeit
|
||||||
|
function dlTime(bps, sizeBytes) {
|
||||||
|
const s = (sizeBytes * 8) / bps;
|
||||||
|
if (s < 60) return `${s.toFixed(1)} Sek.`;
|
||||||
|
if (s < 3600) return `${(s/60).toFixed(1)} Min.`;
|
||||||
|
return `${(s/3600).toFixed(2)} Std.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── IP / Subnetz ──
|
||||||
|
const [ipInput, setIpInput] = useState('');
|
||||||
|
const [subnetResult, setSubnetResult] = useState(null);
|
||||||
|
const [subnetErr, setSubnetErr] = useState('');
|
||||||
|
|
||||||
|
function parseIP(str) {
|
||||||
|
const parts = str.trim().split('.').map(Number);
|
||||||
|
if (parts.length !== 4 || parts.some(p=>isNaN(p)||p<0||p>255)) return null;
|
||||||
|
return parts.reduce((acc,p)=>(acc<<8)|p, 0) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcSubnet(input) {
|
||||||
|
setIpInput(input);
|
||||||
|
setSubnetErr('');
|
||||||
|
setSubnetResult(null);
|
||||||
|
if (!input.trim()) return;
|
||||||
|
let ip, prefix;
|
||||||
|
|
||||||
|
if (input.includes('/')) {
|
||||||
|
const [ipPart, pfx] = input.split('/');
|
||||||
|
ip = parseIP(ipPart);
|
||||||
|
prefix = parseInt(pfx);
|
||||||
|
if (ip === null || isNaN(prefix) || prefix < 0 || prefix > 32) { setSubnetErr('Ungültige CIDR-Notation'); return; }
|
||||||
|
} else {
|
||||||
|
ip = parseIP(input);
|
||||||
|
if (ip === null) { setSubnetErr('Ungültige IP-Adresse'); return; }
|
||||||
|
prefix = 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mask = prefix === 0 ? 0 : (~0 << (32 - prefix)) >>> 0;
|
||||||
|
const net = (ip & mask) >>> 0;
|
||||||
|
const bcast = (net | (~mask >>> 0)) >>> 0;
|
||||||
|
const hosts = prefix >= 31 ? (prefix===32?1:2) : Math.pow(2, 32-prefix) - 2;
|
||||||
|
const first = prefix >= 31 ? net : (net + 1) >>> 0;
|
||||||
|
const last = prefix >= 31 ? bcast : (bcast - 1) >>> 0;
|
||||||
|
|
||||||
|
function i2ip(n) { return [(n>>>24)&0xff,(n>>>16)&0xff,(n>>>8)&0xff,n&0xff].join('.'); }
|
||||||
|
function i2mask(n) { return i2ip(n); }
|
||||||
|
|
||||||
|
// Klasse
|
||||||
|
const first8 = (ip>>>24)&0xff;
|
||||||
|
const klasse = first8 < 128?'A': first8<192?'B': first8<224?'C': first8<240?'D (Multicast)':'E (Reserviert)';
|
||||||
|
const privat = (first8===10)||(first8===172&&((ip>>>16)&0xff)>=16&&((ip>>>16)&0xff)<=31)||
|
||||||
|
(first8===192&&((ip>>>16)&0xff)===168);
|
||||||
|
|
||||||
|
setSubnetResult({
|
||||||
|
ip: i2ip(ip), cidr: `${i2ip(net)}/${prefix}`,
|
||||||
|
mask: i2mask(mask), maskBin: mask.toString(2).padStart(32,'0').match(/.{8}/g).join('.'),
|
||||||
|
net: i2ip(net), bcast: i2ip(bcast),
|
||||||
|
first: i2ip(first), last: i2ip(last),
|
||||||
|
hosts: hosts.toLocaleString('de-DE'),
|
||||||
|
prefix, klasse, privat,
|
||||||
|
wildcard: i2ip(~mask>>>0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const CIDR_TABLE = [
|
||||||
|
{pfx:'/8', mask:'255.0.0.0', hosts:'16.777.214', bsp:'Klasse A – Großnetz'},
|
||||||
|
{pfx:'/16',mask:'255.255.0.0',hosts:'65.534',bsp:'Klasse B – Mittelnetz'},
|
||||||
|
{pfx:'/24',mask:'255.255.255.0',hosts:'254',bsp:'Typisches Heimnetz'},
|
||||||
|
{pfx:'/25',mask:'255.255.255.128',hosts:'126',bsp:'Hälfte eines /24'},
|
||||||
|
{pfx:'/26',mask:'255.255.255.192',hosts:'62',bsp:'Viertel eines /24'},
|
||||||
|
{pfx:'/27',mask:'255.255.255.224',hosts:'30',bsp:'Kleines Subnetz'},
|
||||||
|
{pfx:'/28',mask:'255.255.255.240',hosts:'14',bsp:'VLAN, kleine Abteilung'},
|
||||||
|
{pfx:'/29',mask:'255.255.255.248',hosts:'6',bsp:'Point-to-Point + Reserve'},
|
||||||
|
{pfx:'/30',mask:'255.255.255.252',hosts:'2',bsp:'Point-to-Point Link'},
|
||||||
|
{pfx:'/32',mask:'255.255.255.255',hosts:'1',bsp:'Einzelner Host / Loopback'},
|
||||||
|
];
|
||||||
|
|
||||||
|
const nTabs = [{id:'speed',label:'⚡ Geschwindigkeit'},{id:'subnet',label:'🌐 IP / Subnetz'},{id:'cidr',label:'📋 CIDR-Tabelle'}];
|
||||||
|
const lbl = {color:'rgba(255,255,255,0.55)',fontFamily:'monospace',fontSize:10,marginBottom:4};
|
||||||
|
const inp = {...S.inp, fontSize:13, padding:'7px 9px', width:'100%', boxSizing:'border-box'};
|
||||||
|
|
||||||
|
function NRow({label,value,muted,mono}) {
|
||||||
|
return (
|
||||||
|
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',
|
||||||
|
padding:'6px 10px',borderRadius:6,background:'rgba(255,255,255,0.03)',marginBottom:3}}>
|
||||||
|
<span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11}}>{label}</span>
|
||||||
|
<code style={{color:muted?'rgba(255,255,255,0.45)':'#4ecdc4',
|
||||||
|
fontFamily:mono===false?'inherit':"'Courier New',monospace",fontSize:12,fontWeight:700,
|
||||||
|
textAlign:'right',maxWidth:'60%',wordBreak:'break-all'}}>{value}</code>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{display:'flex',gap:6,marginBottom:18,flexWrap:'wrap'}}>
|
||||||
|
{nTabs.map(t=>(
|
||||||
|
<button key={t.id} onClick={()=>setNTab(t.id)} style={{
|
||||||
|
padding:'6px 14px',borderRadius:20,fontFamily:'monospace',fontSize:11,cursor:'pointer',
|
||||||
|
background:nTab===t.id?'#4ecdc4':'rgba(255,255,255,0.05)',
|
||||||
|
color:nTab===t.id?'#0d0d0f':'rgba(255,255,255,0.65)',
|
||||||
|
border:nTab===t.id?'none':'1px solid rgba(255,255,255,0.12)',
|
||||||
|
fontWeight:nTab===t.id?700:400}}>{t.label}</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{nTab==='speed' && (
|
||||||
|
<div>
|
||||||
|
<div style={{display:'grid',gridTemplateColumns:mobile?'1fr':'2fr 1fr',gap:12,marginBottom:16}}>
|
||||||
|
<div>
|
||||||
|
<div style={lbl}>Wert eingeben</div>
|
||||||
|
<input style={inp} type="number" min="0" placeholder="z.B. 100"
|
||||||
|
value={speedVal} onChange={e=>setSpeedVal(e.target.value)}/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={lbl}>Einheit</div>
|
||||||
|
<select style={{...inp,cursor:'pointer'}} value={speedUnit} onChange={e=>setSpeedUnit(e.target.value)}>
|
||||||
|
<optgroup label="Bit-basiert">
|
||||||
|
<option value="bps">bit/s</option>
|
||||||
|
<option value="kbps">kbit/s</option>
|
||||||
|
<option value="mbps">Mbit/s</option>
|
||||||
|
<option value="gbps">Gbit/s</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Byte-basiert">
|
||||||
|
<option value="bBps">B/s</option>
|
||||||
|
<option value="kBps">kB/s</option>
|
||||||
|
<option value="mBps">MB/s</option>
|
||||||
|
<option value="gBps">GB/s</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{speedValid && (
|
||||||
|
<>
|
||||||
|
<div style={{marginBottom:10}}>
|
||||||
|
{SPEED_UNITS.map(u=>(
|
||||||
|
<NRow key={u.id} label={u.label} value={fmtSpeed(speedBps,u)} muted={u.id===speedUnit}/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{marginTop:14,padding:'10px 12px',borderRadius:8,
|
||||||
|
background:'rgba(255,255,255,0.03)',border:'1px solid rgba(255,255,255,0.07)'}}>
|
||||||
|
<div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:9,marginBottom:8}}>DOWNLOAD-ZEITEN BEI DIESER GESCHWINDIGKEIT</div>
|
||||||
|
{[['700 MB CD',700e6],['4,7 GB DVD',4.7e9],['25 GB Blu-ray',25e9],['50 GB Spiel',50e9],['100 GB',100e9]].map(([lbl,b])=>(
|
||||||
|
<NRow key={lbl} label={lbl} value={dlTime(speedBps,b)} muted/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{marginTop:10,padding:'8px 12px',borderRadius:7,
|
||||||
|
background:'rgba(255,255,255,0.02)',border:'1px solid rgba(255,255,255,0.06)'}}>
|
||||||
|
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,lineHeight:1.8}}>
|
||||||
|
💡 Faustregel: Mbit/s ÷ 8 = MB/s · Internetanbieter werben mit Mbit/s, Dateimanager zeigt MB/s
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!speedValid && (
|
||||||
|
<div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:11,padding:'10px 0'}}>
|
||||||
|
Geschwindigkeit eingeben → alle Einheiten werden berechnet
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nTab==='subnet' && (
|
||||||
|
<div>
|
||||||
|
<div style={{marginBottom:16}}>
|
||||||
|
<div style={lbl}>IP-Adresse oder CIDR (z.B. 192.168.1.100/24)</div>
|
||||||
|
<div style={{display:'flex',gap:8,flexWrap:'wrap'}}>
|
||||||
|
<input style={{...inp,flex:1,minWidth:200}} placeholder="192.168.1.100/24"
|
||||||
|
value={ipInput} onChange={e=>calcSubnet(e.target.value)}/>
|
||||||
|
{!!ipInput && <button style={{...S.btn('#ff6b9d',false),fontSize:11,padding:'7px 14px'}}
|
||||||
|
onClick={()=>{setIpInput('');setSubnetResult(null);setSubnetErr('');}}>✕</button>}
|
||||||
|
</div>
|
||||||
|
{!!subnetErr && <div style={{color:'#ff6b9d',fontFamily:'monospace',fontSize:11,marginTop:6}}>{subnetErr}</div>}
|
||||||
|
</div>
|
||||||
|
{subnetResult && (
|
||||||
|
<div>
|
||||||
|
<NRow label="IP-Adresse" value={subnetResult.ip}/>
|
||||||
|
<NRow label="CIDR-Notation" value={subnetResult.cidr}/>
|
||||||
|
<NRow label="Subnetzmaske" value={subnetResult.mask}/>
|
||||||
|
<NRow label="Maske binär" value={subnetResult.maskBin}/>
|
||||||
|
<NRow label="Wildcard-Maske" value={subnetResult.wildcard} muted/>
|
||||||
|
<NRow label="Netzadresse" value={subnetResult.net}/>
|
||||||
|
<NRow label="Broadcast" value={subnetResult.bcast}/>
|
||||||
|
<NRow label="Erster Host" value={subnetResult.first}/>
|
||||||
|
<NRow label="Letzter Host" value={subnetResult.last}/>
|
||||||
|
<NRow label="Nutzbare Hosts" value={subnetResult.hosts}/>
|
||||||
|
<NRow label="IP-Klasse" value={subnetResult.klasse} muted/>
|
||||||
|
<NRow label="Privater Bereich" value={subnetResult.privat?'✓ Ja (RFC 1918)':'✗ Öffentlich'} muted/>
|
||||||
|
<div style={{marginTop:10,padding:'8px 12px',borderRadius:7,
|
||||||
|
background:'rgba(255,255,255,0.02)',border:'1px solid rgba(255,255,255,0.06)'}}>
|
||||||
|
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,lineHeight:1.8}}>
|
||||||
|
💡 Subnetzmaske: 1er-Bits = Netzanteil, 0er-Bits = Hostanteil ·
|
||||||
|
Broadcast = letzte Adresse · Netz = erste Adresse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!subnetResult && !subnetErr && (
|
||||||
|
<div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:11,padding:'10px 0'}}>
|
||||||
|
IP eingeben (mit oder ohne /Präfix) → Subnetz wird berechnet · Standard: /24
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{nTab==='cidr' && (
|
||||||
|
<div>
|
||||||
|
<div style={{overflowX:'auto'}}>
|
||||||
|
<table style={{width:'100%',borderCollapse:'collapse',fontFamily:'monospace',fontSize:12}}>
|
||||||
|
<thead>
|
||||||
|
<tr style={{borderBottom:'1px solid rgba(255,255,255,0.1)'}}>
|
||||||
|
{['Präfix','Subnetzmaske','Nutzbare Hosts','Typischer Einsatz'].map(h=>(
|
||||||
|
<th key={h} style={{padding:'7px 10px',textAlign:'left',color:'rgba(255,255,255,0.45)',fontSize:10,fontWeight:600}}>{h}</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{CIDR_TABLE.map((r,i)=>(
|
||||||
|
<tr key={r.pfx} style={{background:i%2?'rgba(255,255,255,0.02)':'transparent',
|
||||||
|
borderBottom:'1px solid rgba(255,255,255,0.04)'}}>
|
||||||
|
<td style={{padding:'7px 10px',color:'#4ecdc4',fontWeight:700}}>{r.pfx}</td>
|
||||||
|
<td style={{padding:'7px 10px',color:'rgba(255,255,255,0.75)'}}>{r.mask}</td>
|
||||||
|
<td style={{padding:'7px 10px',color:'rgba(255,255,255,0.75)',textAlign:'right'}}>{r.hosts}</td>
|
||||||
|
<td style={{padding:'7px 10px',color:'rgba(255,255,255,0.5)',fontSize:11}}>{r.bsp}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div style={{marginTop:14,padding:'10px 12px',borderRadius:8,
|
||||||
|
background:'rgba(255,255,255,0.03)',border:'1px solid rgba(255,255,255,0.07)'}}>
|
||||||
|
<div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:9,marginBottom:6}}>PRIVATE ADRESSBEREICHE (RFC 1918)</div>
|
||||||
|
{[
|
||||||
|
['10.0.0.0/8','10.0.0.0 – 10.255.255.255','Klasse A, große Firmennetzwerke'],
|
||||||
|
['172.16.0.0/12','172.16.0.0 – 172.31.255.255','Klasse B, mittlere Netzwerke'],
|
||||||
|
['192.168.0.0/16','192.168.0.0 – 192.168.255.255','Klasse C, Heimnetzwerke'],
|
||||||
|
].map(([cidr,range,desc])=>(
|
||||||
|
<div key={cidr} style={{display:'flex',gap:12,padding:'4px 0',borderBottom:'1px solid rgba(255,255,255,0.04)',flexWrap:'wrap'}}>
|
||||||
|
<code style={{color:'#4ecdc4',minWidth:120}}>{cidr}</code>
|
||||||
|
<span style={{color:'rgba(255,255,255,0.6)',minWidth:200}}>{range}</span>
|
||||||
|
<span style={{color:'rgba(255,255,255,0.4)',fontSize:11}}>{desc}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const TOOL_DEFS = [
|
const TOOL_DEFS = [
|
||||||
{id:'cron', label:'⏱ Crontab', desc:'Crontab erklären und erstellen', ready:true,
|
{id:'cron', label:'⏱ Crontab', desc:'Crontab erklären und erstellen', ready:true,
|
||||||
tabs:[{id:'explain',label:'Erklären'},{id:'build',label:'Erstellen'}],
|
tabs:[{id:'explain',label:'Erklären'},{id:'build',label:'Erstellen'}],
|
||||||
@@ -946,7 +1351,8 @@ const TOOL_DEFS = [
|
|||||||
{id:'elektro', label:'⚡ Elektro', desc:'Ohm, Leistung, Kosten, Widerstände', ready:true, component:ElektroRechner},
|
{id:'elektro', label:'⚡ Elektro', desc:'Ohm, Leistung, Kosten, Widerstände', ready:true, component:ElektroRechner},
|
||||||
{id:'json', label:'{ } JSON', desc:'JSON formatieren', ready:false},
|
{id:'json', label:'{ } JSON', desc:'JSON formatieren', ready:false},
|
||||||
{id:'regex', label:'🔍 Regex', desc:'Regex testen', ready:false},
|
{id:'regex', label:'🔍 Regex', desc:'Regex testen', ready:false},
|
||||||
{id:'diff', label:'± Text Diff', desc:'Texte vergleichen', ready:false},
|
{id:'diff', label:'± Text Diff', desc:'Zwei Texte vergleichen', ready:true, component:TextDiff},
|
||||||
|
{id:'netzwerk', label:'🌐 Netzwerk', desc:'IP, Subnetz, Geschwindigkeit, CIDR', ready:true, component:NetzwerkRechner},
|
||||||
{id:'datetime', label:'📅 Datetime', desc:'Zeiten umrechnen · Geburtstag · Differenz', ready:true, component:DatetimeRechner},
|
{id:'datetime', label:'📅 Datetime', desc:'Zeiten umrechnen · Geburtstag · Differenz', ready:true, component:DatetimeRechner},
|
||||||
{id:'keygen', label:'🔐 Key-Gen', desc:'Sichere Keys & Secrets generieren', ready:true, component:KeyGen},
|
{id:'keygen', label:'🔐 Key-Gen', desc:'Sichere Keys & Secrets generieren', ready:true, component:KeyGen},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user