Add 6 new tools: Hasher, Base64, JWT Decoder, Password Gen, Timestamp, Text Diff

- backend/tools/hasher.py: POST /api/hash/sha256 and /api/hash/bcrypt (bcrypt added to requirements)
- backend/tools/base64tool.py: POST /api/base64/encode and /api/base64/decode
- backend/tools/jwtdecoder.py: POST /api/jwt/decode (signature verification disabled)
- backend/tools/passwordgen.py: POST /api/password/generate with charset and length options
- backend/tools/timestamp.py: POST /api/timestamp/convert (unix<->date, ISO 8601 + German format)
- backend/tools/textdiff.py: POST /api/text/diff returning structured added/removed/unchanged lines
- All blueprints registered in app.py and tools/__init__.py
- React components with copy button, dark/light mode support via CSS variables
- ToolOverview rebuilt as card grid; App.jsx routes added for all tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nirodan
2026-04-24 14:28:18 +02:00
parent 80ec5eca7b
commit 7f9c5c874a
17 changed files with 788 additions and 6 deletions
+108
View File
@@ -0,0 +1,108 @@
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 getStrength(length, charsets) {
const score = (charsets >= 3 ? 2 : charsets >= 2 ? 1 : 0) + (length >= 16 ? 2 : length >= 12 ? 1 : 0);
if (score >= 4) return { label: 'Stark', color: '#22c55e' };
if (score >= 2) return { label: 'Mittel', color: '#f59e0b' };
return { label: 'Schwach', color: '#ef4444' };
}
function PasswordGenTool() {
const [length, setLength] = useState(16);
const [uppercase, setUppercase] = useState(true);
const [lowercase, setLowercase] = useState(true);
const [numbers, setNumbers] = useState(true);
const [symbols, setSymbols] = useState(false);
const [result, setResult] = useState('');
const [copied, setCopied] = useState(false);
const [error, setError] = useState('');
const charsets = [uppercase, lowercase, numbers, symbols].filter(Boolean).length;
const strength = getStrength(length, charsets);
const generate = async () => {
setError('');
try {
const res = await axios.post('/api/password/generate', { length, uppercase, lowercase, numbers, symbols });
setResult(res.data.password);
} catch (err) {
setError(err.response?.data?.message || 'Fehler beim Generieren');
}
};
const copy = () => {
navigator.clipboard.writeText(result);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
};
return (
<div className="main-content">
<h2>Passwort-Generator</h2>
<label style={{ display: 'block', color: 'var(--muted)', fontWeight: 600, marginBottom: '6px' }}>
Länge: {length}
</label>
<input
type="range"
min={8}
max={64}
value={length}
onChange={(e) => setLength(Number(e.target.value))}
style={{ accentColor: 'var(--accent)', padding: 0, border: 'none', background: 'transparent', marginBottom: '12px' }}
/>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px', margin: '8px 0 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 }) => (
<label key={label} style={{ display: 'flex', alignItems: 'center', gap: '6px', cursor: 'pointer', color: 'var(--text)', fontWeight: 500 }}>
<input
type="checkbox"
checked={value}
onChange={(e) => set(e.target.checked)}
style={{ width: 'auto', accentColor: 'var(--accent)' }}
/>
{label}
</label>
))}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '4px' }}>
<span className="muted" style={{ fontSize: '0.875rem' }}>Stärke:</span>
<span style={{ fontWeight: 700, color: strength.color }}>{strength.label}</span>
</div>
<button onClick={generate}>Generieren</button>
{error && <p className="error">{error}</p>}
{result && (
<div style={resultBox}>
<span style={{ wordBreak: 'break-all', color: 'var(--text)', fontFamily: 'monospace', fontSize: '0.95rem' }}>
{result}
</span>
<button className="ghost" onClick={copy} style={{ flexShrink: 0, margin: 0 }}>
{copied ? 'Kopiert!' : 'Kopieren'}
</button>
</div>
)}
</div>
);
}
export default PasswordGenTool;