refactor(decnet_web/Webhooks): extract FormRow + SecretModal

This commit is contained in:
2026-05-09 06:01:47 -04:00
parent 432057f44a
commit 1ac64d2ae2
3 changed files with 179 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
import React from 'react';
import { Save, X } from '../../icons';
import type { FormState, SimpleEvent } from './types';
interface Props {
title: string;
form: FormState;
setForm: React.Dispatch<React.SetStateAction<FormState>>;
onSave: (e: React.FormEvent) => void;
onCancel: () => void;
saving: boolean;
isEdit: boolean;
onToggleSimple: (n: SimpleEvent) => void;
}
const FormRow: React.FC<Props> = ({
title, form, setForm, onSave, onCancel, saving, isEdit, onToggleSimple,
}) => (
<tr className="wh-form-row">
<td colSpan={7}>
<form className="wh-form-grid" onSubmit={onSave}>
<label className="wh-form-title">{title}</label>
<label>NAME</label>
<input
type="text"
value={form.name}
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
placeholder="shuffle-prod"
required
maxLength={64}
/>
<label>URL</label>
<input
type="url"
value={form.url}
onChange={(e) => setForm((f) => ({ ...f, url: e.target.value }))}
placeholder="https://shuffle.example.com/api/v1/hooks/webhook_xxx"
required
/>
<label>
SECRET {isEdit && <span className="wh-form-hint">(blank = keep existing)</span>}
</label>
<input
type="password"
value={form.secret}
onChange={(e) => setForm((f) => ({ ...f, secret: e.target.value }))}
placeholder={isEdit ? '—' : 'leave blank to auto-generate'}
minLength={16}
maxLength={256}
/>
<label>SIMPLE EVENTS</label>
<div className="wh-checkbox-group">
{(['AttackerDetail', 'DeckyStatus', 'SystemStatus'] as const).map((name) => (
<label key={name}>
<input
type="checkbox"
checked={form.simple_events.includes(name)}
onChange={() => onToggleSimple(name)}
/>
{name}
</label>
))}
</div>
<label>
ADVANCED PATTERNS
<br />
<span className="wh-form-hint">(one per line, NATS-style)</span>
</label>
<textarea
value={form.topic_patterns}
onChange={(e) => setForm((f) => ({ ...f, topic_patterns: e.target.value }))}
placeholder={'attacker.>\ndecky.*.state'}
/>
<label>ENABLED</label>
<div className="wh-checkbox-group">
<label>
<input
type="checkbox"
checked={form.enabled}
onChange={(e) => setForm((f) => ({ ...f, enabled: e.target.checked }))}
/>
Receive events
</label>
</div>
<div className="wh-form-buttons">
<button type="button" className="btn ghost" onClick={onCancel} disabled={saving}>
<X size={12} /> CANCEL
</button>
<button type="submit" className="btn violet" disabled={saving}>
<Save size={12} /> {saving ? 'SAVING…' : isEdit ? 'SAVE CHANGES' : 'CREATE'}
</button>
</div>
</form>
</td>
</tr>
);
export default FormRow;

View File

@@ -0,0 +1,28 @@
/**
* @vitest-environment jsdom
*/
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import SecretModal from './SecretModal';
describe('SecretModal', () => {
it('renders the secret and fires onClose for DONE', () => {
const onClose = vi.fn();
render(<SecretModal name="shuffle" secret="abc123def456ghi7" onClose={onClose} />);
expect(screen.getByText('abc123def456ghi7')).toBeInTheDocument();
expect(screen.getByText(/SHUFFLE/)).toBeInTheDocument();
fireEvent.click(screen.getByText('DONE'));
expect(onClose).toHaveBeenCalledTimes(1);
});
it('closes when backdrop is clicked but not when the inner modal is clicked', () => {
const onClose = vi.fn();
const { container } = render(
<SecretModal name="x" secret="s" onClose={onClose} />,
);
fireEvent.click(container.querySelector('.wh-secret-modal-backdrop')!);
expect(onClose).toHaveBeenCalledTimes(1);
fireEvent.click(container.querySelector('.wh-secret-modal')!);
expect(onClose).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,46 @@
import React, { useState } from 'react';
import { AlertTriangle, Check, Copy } from '../../icons';
interface Props {
name: string;
secret: string;
onClose: () => void;
}
const SecretModal: React.FC<Props> = ({ name, secret, onClose }) => {
const [copied, setCopied] = useState(false);
const copy = async () => {
try {
await navigator.clipboard.writeText(secret);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch {
/* no-op — browsers without clipboard perms will just see no feedback */
}
};
return (
<div
className="wh-secret-modal-backdrop"
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
>
<div className="wh-secret-modal">
<h3>WEBHOOK SECRET · {name.toUpperCase()}</h3>
<div className="wh-secret-warn">
<AlertTriangle size={14} />
<span>COPY THIS NOW IT WILL NOT BE SHOWN AGAIN. THE HMAC ON EVERY DELIVERY IS SIGNED WITH THIS VALUE.</span>
</div>
<div className="wh-secret-value">{secret}</div>
<div className="wh-secret-actions">
<button className="btn ghost" onClick={copy}>
<Copy size={12} /> {copied ? 'COPIED' : 'COPY'}
</button>
<button className="btn violet" onClick={onClose}>
<Check size={12} /> DONE
</button>
</div>
</div>
</div>
);
};
export default SecretModal;