+
Base64 Encoder / Decoder
+
{ setInput(e.target.value); setResult(''); setError(''); }}
+ placeholder="Text eingeben"
+ />
+
+
+ {error &&
{error}
}
+ {result && (
+
+
+ {result}
+
+
+
+ )}
+
+ );
+}
+
+export default Base64Tool;
diff --git a/frontend/src/components/HasherTool.jsx b/frontend/src/components/HasherTool.jsx
new file mode 100644
index 0000000..e32c226
--- /dev/null
+++ b/frontend/src/components/HasherTool.jsx
@@ -0,0 +1,68 @@
+import { useState } from 'react';
+import axios from '../services/api';
+
+const resultBox = {
+ marginTop: '12px',
+ padding: '12px 14px',
+ background: 'var(--surface-2)',
+ border: '1px solid var(--border)',
+ borderRadius: '12px',
+ display: 'flex',
+ alignItems: 'center',
+ gap: '10px',
+ justifyContent: 'space-between',
+};
+
+function HasherTool() {
+ const [input, setInput] = useState('');
+ const [algo, setAlgo] = useState('sha256');
+ const [result, setResult] = useState('');
+ const [copied, setCopied] = useState(false);
+
+ const handleHash = async () => {
+ try {
+ const res = await axios.post(`/api/hash/${algo}`, { text: input });
+ setResult(res.data[algo]);
+ } catch {
+ alert('Fehler beim Hashen');
+ }
+ };
+
+ const copy = () => {
+ navigator.clipboard.writeText(result);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ };
+
+ return (
+
+
SHA256 / bcrypt Hasher
+
+ Hinweis: bcrypt ist sicher für Passwörter – SHA256 für Datenintegrität.
+
+
{ setInput(e.target.value); setResult(''); }}
+ placeholder="Text eingeben"
+ />
+
+
+ {result && (
+
+
+ {result}
+
+
+
+ )}
+
+ );
+}
+
+export default HasherTool;
diff --git a/frontend/src/components/JwtDecoderTool.jsx b/frontend/src/components/JwtDecoderTool.jsx
new file mode 100644
index 0000000..71643c2
--- /dev/null
+++ b/frontend/src/components/JwtDecoderTool.jsx
@@ -0,0 +1,69 @@
+import { useState } from 'react';
+import axios from '../services/api';
+
+const sectionBox = {
+ marginTop: '12px',
+ padding: '14px',
+ background: 'var(--surface-2)',
+ border: '1px solid var(--border)',
+ borderRadius: '12px',
+};
+
+function JwtDecoderTool() {
+ const [input, setInput] = useState('');
+ const [decoded, setDecoded] = useState(null);
+ const [error, setError] = useState('');
+
+ const handleDecode = async () => {
+ setError('');
+ setDecoded(null);
+ try {
+ const res = await axios.post('/api/jwt/decode', { token: input });
+ setDecoded(res.data);
+ } catch (err) {
+ setError(err.response?.data?.message || 'Ungültiger JWT');
+ }
+ };
+
+ return (
+
+
Passwort-Generator
+
+
+
setLength(Number(e.target.value))}
+ style={{ accentColor: 'var(--accent)', padding: 0, border: 'none', background: 'transparent', marginBottom: '12px' }}
+ />
+
+
+ {[
+ { label: 'Großbuchstaben', value: uppercase, set: setUppercase },
+ { label: 'Kleinbuchstaben', value: lowercase, set: setLowercase },
+ { label: 'Zahlen', value: numbers, set: setNumbers },
+ { label: 'Sonderzeichen', value: symbols, set: setSymbols },
+ ].map(({ label, value, set }) => (
+
+ ))}
+
+
+
+ Stärke:
+ {strength.label}
+
+
+
+ {error &&
{error}
}
+ {result && (
+
+
+ {result}
+
+
+
+ )}
+
+ );
+}
+
+export default PasswordGenTool;
diff --git a/frontend/src/components/TextDiffTool.jsx b/frontend/src/components/TextDiffTool.jsx
new file mode 100644
index 0000000..6436fa4
--- /dev/null
+++ b/frontend/src/components/TextDiffTool.jsx
@@ -0,0 +1,84 @@
+import { useState } from 'react';
+import axios from '../services/api';
+
+const diffColors = {
+ added: { bg: 'rgba(34, 197, 94, 0.12)', text: '#22c55e', prefix: '+ ' },
+ removed: { bg: 'rgba(239, 68, 68, 0.12)', text: '#ef4444', prefix: '- ' },
+ unchanged: { bg: 'transparent', text: 'var(--muted)', prefix: ' ' },
+};
+
+function TextDiffTool() {
+ const [text1, setText1] = useState('');
+ const [text2, setText2] = useState('');
+ const [diff, setDiff] = useState(null);
+
+ const compare = async () => {
+ try {
+ const res = await axios.post('/api/text/diff', { text1, text2 });
+ setDiff(res.data.diff);
+ } catch {
+ alert('Fehler beim Vergleich');
+ }
+ };
+
+ return (
+
+
Text Diff
+
+
+
+
+ {diff !== null && (
+
+ {diff.length === 0 ? (
+
Keine Unterschiede gefunden.
+ ) : (
+ diff.map((line, i) => {
+ const c = diffColors[line.type] || diffColors.unchanged;
+ return (
+
+ {c.prefix}{line.text}
+
+ );
+ })
+ )}
+
+ )}
+
+ );
+}
+
+export default TextDiffTool;
diff --git a/frontend/src/components/TimestampTool.jsx b/frontend/src/components/TimestampTool.jsx
new file mode 100644
index 0000000..b9e04b6
--- /dev/null
+++ b/frontend/src/components/TimestampTool.jsx
@@ -0,0 +1,101 @@
+import { useState, useEffect } from 'react';
+import axios from '../services/api';
+
+const resultBox = {
+ marginTop: '12px',
+ padding: '12px 14px',
+ background: 'var(--surface-2)',
+ border: '1px solid var(--border)',
+ borderRadius: '12px',
+ display: 'flex',
+ alignItems: 'center',
+ gap: '10px',
+ justifyContent: 'space-between',
+};
+
+function TimestampTool() {
+ const [value, setValue] = useState('');
+ const [direction, setDirection] = useState('unix_to_date');
+ const [result, setResult] = useState(null);
+ const [copied, setCopied] = useState(false);
+ const [error, setError] = useState('');
+ const [now, setNow] = useState(Math.floor(Date.now() / 1000));
+
+ useEffect(() => {
+ const interval = setInterval(() => setNow(Math.floor(Date.now() / 1000)), 1000);
+ return () => clearInterval(interval);
+ }, []);
+
+ const convert = async () => {
+ setError('');
+ setResult(null);
+ try {
+ const res = await axios.post('/api/timestamp/convert', { value, direction });
+ setResult(res.data);
+ } catch (err) {
+ setError(err.response?.data?.message || 'Ungültiger Wert');
+ }
+ };
+
+ const resultText = result
+ ? direction === 'unix_to_date' ? result.utc : String(result.unix)
+ : '';
+
+ const copy = () => {
+ navigator.clipboard.writeText(resultText);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ };
+
+ return (
+
+
Timestamp Converter
+
+
+ Aktueller Unix Timestamp:
+ {now}
+
+
+
+ {[
+ { key: 'unix_to_date', label: 'Unix → Datum' },
+ { key: 'date_to_unix', label: 'Datum → Unix' },
+ ].map(({ key, label }) => (
+
+ ))}
+
+
+
{ setValue(e.target.value); setResult(null); setError(''); }}
+ placeholder={direction === 'unix_to_date' ? 'z.B. 1700000000' : 'z.B. 2024-01-15 oder 15.01.2024'}
+ />
+
+ {error &&
{error}
}
+ {result && (
+
+
+ {direction === 'unix_to_date' ? (
+ {result.utc}
+ ) : (
+ {result.unix}
+ )}
+
+
+
+ )}
+
+ );
+}
+
+export default TimestampTool;
diff --git a/frontend/src/components/ToolOverview.jsx b/frontend/src/components/ToolOverview.jsx
index 06bffbc..17f2ad2 100644
--- a/frontend/src/components/ToolOverview.jsx
+++ b/frontend/src/components/ToolOverview.jsx
@@ -2,9 +2,18 @@ import { useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import axios from '../services/api';
+const TOOLS = [
+ { icon: '🔒', path: '/tools/md5', title: 'MD5 Hasher', desc: 'MD5-Hash berechnen (nur zur Analyse)' },
+ { icon: '#️⃣', path: '/tools/hasher', title: 'SHA256 / bcrypt', desc: 'Sichere Hash-Algorithmen für Strings' },
+ { icon: '📦', path: '/tools/base64', title: 'Base64', desc: 'Text kodieren und dekodieren' },
+ { icon: '🔑', path: '/tools/jwt', title: 'JWT Decoder', desc: 'JWT Token analysieren (ohne Signaturprüfung)' },
+ { icon: '🔐', path: '/tools/password', title: 'Passwort-Generator', desc: 'Sichere Passwörter generieren' },
+ { icon: '🕐', path: '/tools/timestamp', title: 'Timestamp Converter', desc: 'Unix Timestamp in Datum umrechnen' },
+ { icon: '📝', path: '/tools/textdiff', title: 'Text Diff', desc: 'Zwei Texte vergleichen' },
+];
+
function ToolOverview() {
const navigate = useNavigate();
- const role = localStorage.getItem('role');
const [websites, setWebsites] = useState([]);
const [loadingWebsites, setLoadingWebsites] = useState(true);
@@ -13,7 +22,7 @@ function ToolOverview() {
try {
const res = await axios.get('/api/websites');
setWebsites(res.data);
- } catch (e) {
+ } catch {
setWebsites([]);
} finally {
setLoadingWebsites(false);
@@ -27,9 +36,24 @@ function ToolOverview() {
Lade Links...
) : websites.length === 0 ? (