Webhookの編集をモーダル化

This commit is contained in:
CyberRex
2026-05-27 09:17:36 +09:00
parent e89f7b4cf3
commit 2a4050d442
2 changed files with 81 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
# CertRemind 開発進捗
最終更新: 2026-05-25
最終更新: 2026-05-27
## 現在の実装状況
@@ -244,7 +244,7 @@ pnpm monitor:worker
- 確認ダイアログ付き履歴削除
- 通知方法管理画面
- Webhook 登録
- Webhook 編集
- モーダルでの Webhook 編集
- 確認ダイアログ付き Webhook 削除
- ブラウザ Push 通知の許可状態表示
- 現在のブラウザの Push 登録状態表示

View File

@@ -1,4 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { ArrowLeft, BellOff, BellRing, Link, Pencil, Plus, Trash2 } from 'lucide-react';
import { request } from '../api/client.js';
import { ConfirmDialog } from '../components/ConfirmDialog.jsx';
@@ -56,7 +57,9 @@ export function NotificationMethodsView({ onBack }) {
const [vapidPublicKey, setVapidPublicKey] = useState('');
const [currentPushStatus, setCurrentPushStatus] = useState('unchecked');
const [form, setForm] = useState({ alias: '', url: '' });
const [editingId, setEditingId] = useState('');
const [editingWebhook, setEditingWebhook] = useState(null);
const [editForm, setEditForm] = useState({ alias: '', url: '' });
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [permission, setPermission] = useState(
typeof Notification === 'undefined' ? 'unsupported' : Notification.permission,
);
@@ -121,19 +124,12 @@ export function NotificationMethodsView({ onBack }) {
setBusy(true);
try {
validateWebhookForm(form);
const endpoint = editingId
? `/api/notification-methods/webhooks/${editingId}`
: '/api/notification-methods/webhooks';
await request(endpoint, {
method: editingId ? 'PATCH' : 'POST',
await request('/api/notification-methods/webhooks', {
method: 'POST',
body: JSON.stringify({ alias: form.alias.trim(), url: form.url.trim() }),
});
setForm({ alias: '', url: '' });
setEditingId('');
showToast({
type: 'success',
message: editingId ? 'Webhookを更新しました' : 'Webhookを登録しました',
});
showToast({ type: 'success', message: 'Webhookを登録しました' });
await loadMethods();
} catch (err) {
showToast({ type: 'error', message: err.message });
@@ -143,8 +139,38 @@ export function NotificationMethodsView({ onBack }) {
}
function startEdit(webhook) {
setEditingId(webhook.notificationMethodId);
setForm({ alias: webhook.alias, url: webhook.url });
setEditingWebhook(webhook);
setEditForm({ alias: webhook.alias, url: webhook.url });
setEditDialogOpen(true);
}
function handleEditDialogOpenChange(open) {
setEditDialogOpen(open);
if (!open) {
setEditingWebhook(null);
setEditForm({ alias: '', url: '' });
}
}
async function submitWebhookEdit(event) {
event.preventDefault();
if (!editingWebhook) return;
setBusy(true);
try {
validateWebhookForm(editForm);
await request(`/api/notification-methods/webhooks/${editingWebhook.notificationMethodId}`, {
method: 'PATCH',
body: JSON.stringify({ alias: editForm.alias.trim(), url: editForm.url.trim() }),
});
showToast({ type: 'success', message: 'Webhookを更新しました' });
handleEditDialogOpenChange(false);
await loadMethods();
} catch (err) {
showToast({ type: 'error', message: err.message });
} finally {
setBusy(false);
}
}
async function deleteWebhook(methodId) {
@@ -273,11 +299,50 @@ export function NotificationMethodsView({ onBack }) {
</Field>
<button className="primary" disabled={busy}>
<Plus aria-hidden="true" size={18} />
{editingId ? '更新' : '登録'}
登録
</button>
</div>
</form>
<Dialog.Root open={editDialogOpen} onOpenChange={handleEditDialogOpenChange}>
<Dialog.Portal>
<Dialog.Overlay className="dialog-overlay" />
<Dialog.Content className="dialog-content">
<Dialog.Title className="dialog-title">Webhookを編集</Dialog.Title>
<form className="dialog-form" onSubmit={submitWebhookEdit}>
<Field label="エイリアス名">
<input
value={editForm.alias}
onChange={(event) => setEditForm({ ...editForm, alias: event.target.value })}
placeholder="Slack 通知"
maxLength="120"
required
/>
</Field>
<Field label="URL">
<input
value={editForm.url}
onChange={(event) => setEditForm({ ...editForm, url: event.target.value })}
placeholder="https://hooks.slack.com/services/..."
maxLength="2048"
required
/>
</Field>
<div className="dialog-actions">
<Dialog.Close asChild>
<button className="secondary" type="button">
キャンセル
</button>
</Dialog.Close>
<button className="primary" disabled={busy || !editingWebhook}>
更新
</button>
</div>
</form>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
<section className="panel">
<h2>登録済みWebhook</h2>
<div className="method-list">