Files
Tools/frontend/src/components/CronExplainerTool.jsx
T

158 lines
6.0 KiB
React

import { useState } from 'react';
import axios from '../services/api';
const sectionBox = {
marginTop: '14px',
padding: '14px',
background: 'var(--surface-2)',
border: '1px solid var(--border)',
borderRadius: '12px',
};
const EXAMPLES = [
{ label: '* * * * *', value: '* * * * *' },
{ label: '0 9 * * 1-5', value: '0 9 * * 1-5' },
{ label: '0 0 1 * *', value: '0 0 1 * *' },
{ label: '*/15 * * * *', value: '*/15 * * * *' },
];
const FIELD_LABELS = [
{ key: 'minute', label: 'Minute' },
{ key: 'hour', label: 'Stunde' },
{ key: 'day', label: 'Tag' },
{ key: 'month', label: 'Monat' },
{ key: 'weekday', label: 'Wochentag' },
];
function CronExplainerTool() {
const [expression, setExpression] = useState('');
const [result, setResult] = useState(null);
const [error, setError] = useState('');
const explain = async (expr) => {
const val = expr !== undefined ? expr : expression;
setError('');
setResult(null);
if (!val.trim()) {
setError('Bitte Cron-Ausdruck eingeben.');
return;
}
try {
const res = await axios.post('/api/cron/explain', { expression: val });
setResult(res.data);
} catch (err) {
setError(err.response?.data?.error || err.response?.data?.message || 'Ungültiger Cron-Ausdruck');
}
};
const useExample = (val) => {
setExpression(val);
setError('');
setResult(null);
explain(val);
};
const formatDate = (iso) => {
const d = new Date(iso);
return d.toLocaleString('de-DE', {
weekday: 'short', day: '2-digit', month: '2-digit',
year: 'numeric', hour: '2-digit', minute: '2-digit',
});
};
return (
<div className="main-content">
<h2>Cron Erklärer</h2>
<p style={{ color: 'var(--muted)', marginBottom: '16px', fontSize: '0.95rem' }}>
Cron-Ausdrücke (5 Felder) analysieren und auf Deutsch erklären.
</p>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginBottom: '10px' }}>
{EXAMPLES.map(({ label, value }) => (
<button
key={value}
className="ghost"
onClick={() => useExample(value)}
style={{ fontFamily: 'monospace', fontSize: '0.85rem' }}
>
{label}
</button>
))}
</div>
<div style={{ display: 'flex', gap: '8px' }}>
<input
type="text"
value={expression}
onChange={(e) => { setExpression(e.target.value); setResult(null); setError(''); }}
placeholder="z.B. 0 9 * * 1-5"
style={{ fontFamily: 'monospace', flex: 1 }}
onKeyDown={(e) => e.key === 'Enter' && explain()}
/>
<button onClick={() => explain()} style={{ flexShrink: 0 }}>Erklären</button>
</div>
{error && <p className="error" style={{ marginTop: '12px' }}>{error}</p>}
{result && (
<>
<div style={{ ...sectionBox, borderColor: 'var(--accent)' }}>
<p style={{ color: 'var(--muted)', fontSize: '0.8rem', marginBottom: '4px', fontWeight: 600 }}>Erklärung</p>
<p style={{ color: 'var(--text)', fontWeight: 600, fontSize: '1.05rem' }}>{result.explanation}</p>
</div>
<div style={sectionBox}>
<p style={{ color: 'var(--muted)', fontSize: '0.8rem', marginBottom: '10px', fontWeight: 600 }}>Felder</p>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
<th style={{ textAlign: 'left', padding: '6px 10px', color: 'var(--muted)', fontSize: '0.8rem', fontWeight: 600, borderBottom: '1px solid var(--border)' }}>Feld</th>
<th style={{ textAlign: 'left', padding: '6px 10px', color: 'var(--muted)', fontSize: '0.8rem', fontWeight: 600, borderBottom: '1px solid var(--border)' }}>Ausdruck</th>
<th style={{ textAlign: 'left', padding: '6px 10px', color: 'var(--muted)', fontSize: '0.8rem', fontWeight: 600, borderBottom: '1px solid var(--border)' }}>Bedeutung</th>
</tr>
</thead>
<tbody>
{FIELD_LABELS.map(({ key, label }, i) => (
<tr key={key} style={{ background: i % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.02)' }}>
<td style={{ padding: '8px 10px', color: 'var(--accent)', fontWeight: 600, fontSize: '0.9rem' }}>{label}</td>
<td style={{ padding: '8px 10px', fontFamily: 'monospace', color: 'var(--text)', fontSize: '0.9rem' }}>
{expression.split(/\s+/)[i] || '—'}
</td>
<td style={{ padding: '8px 10px', color: 'var(--text)', fontSize: '0.9rem' }}>
{result.fields[key]}
</td>
</tr>
))}
</tbody>
</table>
</div>
{result.next_runs && result.next_runs.length > 0 && (
<div style={sectionBox}>
<p style={{ color: 'var(--muted)', fontSize: '0.8rem', marginBottom: '10px', fontWeight: 600 }}>Nächste 5 Ausführungen</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
{result.next_runs.map((run, i) => (
<div key={run} style={{
display: 'flex', alignItems: 'center', gap: '10px',
padding: '8px 12px',
background: i === 0 ? 'rgba(34,211,238,0.06)' : 'transparent',
borderRadius: '8px',
border: i === 0 ? '1px solid rgba(34,211,238,0.2)' : '1px solid transparent',
}}>
<span style={{ color: 'var(--muted)', width: '20px', fontSize: '0.85rem' }}>#{i + 1}</span>
<span style={{ fontFamily: 'monospace', color: i === 0 ? 'var(--accent)' : 'var(--text)', fontSize: '0.9rem' }}>
{formatDate(run)}
</span>
</div>
))}
</div>
</div>
)}
</>
)}
</div>
);
}
export default CronExplainerTool;