feat(cert-manager): Zertifikatspaket hochladen, erkennen und in jedem Format herunterladen
Neuer erster Reiter Übersicht: mehrere Dateien oder die ZIP vom Aussteller auf einmal ablegen. Der Server erkennt jedes Teil (Server-, Zwischen-, Stammzertifikat, privater Schlüssel, CSR, auch aus PFX/P7B), fasst Duplikate zusammen, ordnet Schlüssel/CSR dem Zertifikat zu und baut die Kette. Unter jedem Teil stehen Download-Knöpfe für alle passenden Formate (crt, cer, Fullchain, p7b, pfx mit Schlüssel und Kette; key PKCS#8/PKCS#1/ DER; csr PEM/DER). Geschützte PFX lassen sich mit Passwort entsperren. Neue Endpunkte POST analyze (Dateien/ZIP, Begrenzung Anzahl und Größe vor dem Entpacken) und POST export (JSON, zustandslos). Modultexte siezen jetzt durchgehend; Gültigkeit in UTC wie im Zertifikat. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,11 @@ import { InspectTab } from './components/InspectTab';
|
||||
import { SplitTab } from './components/SplitTab';
|
||||
import { MergeTab } from './components/MergeTab';
|
||||
import { ConvertTab } from './components/ConvertTab';
|
||||
import { OverviewTab } from './components/OverviewTab';
|
||||
|
||||
type TabId = 'inspect' | 'split' | 'merge' | 'convert';
|
||||
type TabId = 'overview' | 'inspect' | 'split' | 'merge' | 'convert';
|
||||
|
||||
const TABS: TabId[] = ['inspect', 'split', 'merge', 'convert'];
|
||||
const TABS: TabId[] = ['overview', 'inspect', 'split', 'merge', 'convert'];
|
||||
|
||||
/** Returns true if the selected file is a PFX/P12 (requires decryption password) */
|
||||
function isPfxFile(file: File | null): boolean {
|
||||
@@ -23,6 +24,7 @@ function isPfxFile(file: File | null): boolean {
|
||||
|
||||
/**
|
||||
* CertManagerPage — tab-based shell for certificate operations.
|
||||
* quick-261001-l4q: erster Reiter „Übersicht“ fuer ganze Zertifikatspakete.
|
||||
* Layout per UI-SPEC: max-w-4xl, shared input card, tab nav, tab content card.
|
||||
* T-09-02: password lives in local React state only; never logged or placed in URLs.
|
||||
* T-09-04: all API calls via postForm() which sends credentials:'include'.
|
||||
@@ -34,7 +36,7 @@ function isPfxFile(file: File | null): boolean {
|
||||
export default function CertManagerPage() {
|
||||
const t = useTranslations('certManager');
|
||||
|
||||
const [activeTab, setActiveTab] = useState<TabId>('inspect');
|
||||
const [activeTab, setActiveTab] = useState<TabId>('overview');
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [pemText, setPemText] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
@@ -75,17 +77,14 @@ export default function CertManagerPage() {
|
||||
|
||||
const renderActiveTab = () => {
|
||||
switch (activeTab) {
|
||||
case 'overview':
|
||||
return <OverviewTab />;
|
||||
case 'inspect':
|
||||
return <InspectTab file={file} pemText={pemText} password={password} />;
|
||||
case 'split':
|
||||
return <SplitTab file={file} pemText={pemText} password={password} />;
|
||||
case 'merge':
|
||||
return (
|
||||
<MergeTab
|
||||
password={password}
|
||||
onOutputFormatChange={setMergeOutputFormat}
|
||||
/>
|
||||
);
|
||||
return <MergeTab password={password} onOutputFormatChange={setMergeOutputFormat} />;
|
||||
case 'convert':
|
||||
return (
|
||||
<ConvertTab
|
||||
@@ -102,42 +101,8 @@ export default function CertManagerPage() {
|
||||
<div className="mx-auto max-w-4xl space-y-6 p-3 sm:p-6">
|
||||
<PageHeader moduleSlug="cert-manager" title={t('title')} description={t('description')} />
|
||||
|
||||
{/* Shared input card */}
|
||||
<div className="rounded-lg bg-card dark:border dark:border-border p-6 shadow-sm space-y-4">
|
||||
{/* DropZone */}
|
||||
<DropZone
|
||||
onFile={handleFile}
|
||||
accept=".pem,.crt,.cer,.der,.pfx,.p12,.p7b,.p7c"
|
||||
currentFile={file}
|
||||
onClear={handleClearFile}
|
||||
/>
|
||||
|
||||
{/* ODER divider */}
|
||||
<div className="flex items-center gap-3">
|
||||
<hr className="flex-1 border-border" />
|
||||
<span className="text-xs text-muted-foreground">{t('or')}</span>
|
||||
<hr className="flex-1 border-border" />
|
||||
</div>
|
||||
|
||||
{/* PEM textarea */}
|
||||
<textarea
|
||||
value={pemText}
|
||||
onChange={(e) => handlePemChange(e.target.value)}
|
||||
placeholder={t('paste.placeholder')}
|
||||
rows={4}
|
||||
className="w-full rounded border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring resize-y"
|
||||
/>
|
||||
|
||||
{/* Conditional password field (T-09-02) */}
|
||||
<PasswordField
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
show={showPassword}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tab navigation */}
|
||||
<nav className="border-b border-border flex gap-6">
|
||||
<nav className="flex gap-6 overflow-x-auto border-b border-border">
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
@@ -154,6 +119,39 @@ export default function CertManagerPage() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Shared input card — nur fuer die Einzeldatei-Werkzeuge; die Uebersicht
|
||||
(quick-261001-l4q) hat ihre eigene Mehrfach-Ablage. */}
|
||||
{activeTab !== 'overview' && (
|
||||
<div className="rounded-lg bg-card dark:border dark:border-border p-6 shadow-sm space-y-4">
|
||||
{/* DropZone */}
|
||||
<DropZone
|
||||
onFile={handleFile}
|
||||
accept=".pem,.crt,.cer,.der,.pfx,.p12,.p7b,.p7c"
|
||||
currentFile={file}
|
||||
onClear={handleClearFile}
|
||||
/>
|
||||
|
||||
{/* ODER divider */}
|
||||
<div className="flex items-center gap-3">
|
||||
<hr className="flex-1 border-border" />
|
||||
<span className="text-xs text-muted-foreground">{t('or')}</span>
|
||||
<hr className="flex-1 border-border" />
|
||||
</div>
|
||||
|
||||
{/* PEM textarea */}
|
||||
<textarea
|
||||
value={pemText}
|
||||
onChange={(e) => handlePemChange(e.target.value)}
|
||||
placeholder={t('paste.placeholder')}
|
||||
rows={4}
|
||||
className="w-full rounded border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring resize-y"
|
||||
/>
|
||||
|
||||
{/* Conditional password field (T-09-02) */}
|
||||
<PasswordField value={password} onChange={setPassword} show={showPassword} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tab content card */}
|
||||
<div className="rounded-lg bg-card dark:border dark:border-border p-6 shadow-sm">
|
||||
{renderActiveTab()}
|
||||
|
||||
Reference in New Issue
Block a user