feat(webhook): Phase 4 — admin-ui /webhooks page
Routes: - /webhooks (layout) → /webhooks/index (list) + /webhooks/$id (detail) - nav link добавлен в __root.tsx между Outbox и Language switch List page (webhooks.index.tsx): - Table: name | URL | scope filter (colored dots) | dictionaries | status - ScopeChips reuse SCOPE_DOT из lib/scope-style.ts (consistency с dictionaries scope coloring) - Create dialog: name, URL, scope filter (MultiSelect), dictionary & event type (CSV inputs), description. Client-side validation (URL scheme, name required) дублирует server-side @Pattern. - Secret reveal modal после create: warning + plaintext + copy button. Single-time view — после закрытия secret не получить, только rotate. Detail page (webhooks.$id.tsx): - Subscription metadata grid (URL, status, filters, masked secret, createdBy + timestamp) - Action buttons: rotate-secret + delete (с confirmation) - Delivery history table: outboxEventId | status badge (delivered/ retrying/pending/dlq) | attempts | last attempt | HTTP code | error - Pagination 50/page - Status badges: delivered=success, retrying=warning, pending=neutral, dlq=error API client (queries + mutations): - 4 queries: subscriptions list, subscription by id, deliveries page, DLQ page (last unused в UI пока, но готов для admin DLQ tab) - 4 mutations: create, update, delete, rotateSecret. Все invalidate правильные query keys. i18n: 50 ключей × 2 локали (ru-RU + en-US) под webhooks.*
This commit is contained in:
@@ -16,6 +16,54 @@ i18n
|
||||
'nav.dictionaries': 'Справочники',
|
||||
'nav.audit': 'Аудит',
|
||||
'nav.outbox': 'Outbox',
|
||||
'nav.webhooks': 'Webhooks',
|
||||
'webhooks.title': 'Webhook subscriptions',
|
||||
'webhooks.subtitle': 'HTTP-доставка событий не-Kafka подписчикам',
|
||||
'webhooks.empty': 'Нет подписок. Создайте первую через кнопку справа.',
|
||||
'webhooks.active': 'Активна',
|
||||
'webhooks.inactive': 'Отключена',
|
||||
'webhooks.allScopes': 'все scope',
|
||||
'webhooks.allDicts': 'все справочники',
|
||||
'webhooks.allEvents': 'все события',
|
||||
'webhooks.notFound': 'Подписка не найдена',
|
||||
'webhooks.col.name': 'Имя',
|
||||
'webhooks.col.url': 'URL',
|
||||
'webhooks.col.scopes': 'Scope filter',
|
||||
'webhooks.col.dictionaries': 'Справочники',
|
||||
'webhooks.col.status': 'Статус',
|
||||
'webhooks.action.create': 'Создать',
|
||||
'webhooks.action.delete': 'Удалить',
|
||||
'webhooks.action.rotateSecret': 'Сменить secret',
|
||||
'webhooks.confirmDelete': 'Удалить подписку «{{name}}»? Все pending deliveries удалятся каскадом.',
|
||||
'webhooks.confirmRotate': 'Сменить HMAC secret? Старый сразу перестанет работать — receiver надо обновить заранее.',
|
||||
'webhooks.field.name': 'Имя подписки',
|
||||
'webhooks.field.url': 'Webhook URL',
|
||||
'webhooks.field.urlHint': 'https:// или http://. Private CIDR (10/8, 192.168/16, 127/8) запрещены SSRF guard',
|
||||
'webhooks.field.scopeFilter': 'Scope filter',
|
||||
'webhooks.field.scopeFilterHint': 'Пусто = все scope',
|
||||
'webhooks.field.dictionaryFilter': 'Справочники (CSV)',
|
||||
'webhooks.field.dictionaryFilterHint': 'Через запятую: spacecraft, satellite_type. Пусто = все',
|
||||
'webhooks.field.eventTypeFilter': 'Типы событий (CSV)',
|
||||
'webhooks.field.eventTypeFilterHint': 'record.created, record.updated, record.closed. Пусто = все',
|
||||
'webhooks.field.description': 'Описание',
|
||||
'webhooks.field.createdBy': 'Создатель',
|
||||
'webhooks.error.nameUrlRequired': 'Имя и URL обязательны',
|
||||
'webhooks.error.urlScheme': 'URL должен начинаться с http:// или https://',
|
||||
'webhooks.secret.title': 'HMAC secret для «{{name}}»',
|
||||
'webhooks.secret.warningTitle': 'Скопируй сейчас!',
|
||||
'webhooks.secret.warning': 'Этот secret показывается ОДИН раз. Дальше будет замаскирован. Передай receiver чтобы он мог проверять X-Ordinis-Signature header.',
|
||||
'webhooks.secret.rotatedTitle': 'Secret обновлён',
|
||||
'webhooks.secret.rotatedWarning': 'Новый secret. Старый перестал работать. Обнови receiver.',
|
||||
'webhooks.secret.copy': 'Копировать',
|
||||
'webhooks.secret.copied': 'Скопировано',
|
||||
'webhooks.secret.acknowledge': 'Сохранил',
|
||||
'webhooks.deliveries.title': 'История доставок',
|
||||
'webhooks.deliveries.empty': 'Доставок ещё не было',
|
||||
'webhooks.deliveries.col.eventId': 'Event ID',
|
||||
'webhooks.deliveries.col.attempts': 'Попыток',
|
||||
'webhooks.deliveries.col.lastAttempt': 'Последняя попытка',
|
||||
'webhooks.deliveries.col.lastStatusCode': 'HTTP статус',
|
||||
'webhooks.deliveries.col.lastError': 'Последняя ошибка',
|
||||
'audit.title': 'Журнал аудита',
|
||||
'audit.subtitle': 'Юридически значимый лог изменений НСИ',
|
||||
'audit.filter.dictionary': 'Справочник',
|
||||
@@ -192,6 +240,54 @@ i18n
|
||||
'nav.dictionaries': 'Dictionaries',
|
||||
'nav.audit': 'Audit',
|
||||
'nav.outbox': 'Outbox',
|
||||
'nav.webhooks': 'Webhooks',
|
||||
'webhooks.title': 'Webhook subscriptions',
|
||||
'webhooks.subtitle': 'HTTP delivery of events to non-Kafka consumers',
|
||||
'webhooks.empty': 'No subscriptions yet. Create one via the button on the right.',
|
||||
'webhooks.active': 'Active',
|
||||
'webhooks.inactive': 'Disabled',
|
||||
'webhooks.allScopes': 'all scopes',
|
||||
'webhooks.allDicts': 'all dictionaries',
|
||||
'webhooks.allEvents': 'all events',
|
||||
'webhooks.notFound': 'Subscription not found',
|
||||
'webhooks.col.name': 'Name',
|
||||
'webhooks.col.url': 'URL',
|
||||
'webhooks.col.scopes': 'Scope filter',
|
||||
'webhooks.col.dictionaries': 'Dictionaries',
|
||||
'webhooks.col.status': 'Status',
|
||||
'webhooks.action.create': 'Create',
|
||||
'webhooks.action.delete': 'Delete',
|
||||
'webhooks.action.rotateSecret': 'Rotate secret',
|
||||
'webhooks.confirmDelete': 'Delete subscription "{{name}}"? All pending deliveries will be cascade-deleted.',
|
||||
'webhooks.confirmRotate': 'Rotate HMAC secret? The old one stops working immediately — update the receiver first.',
|
||||
'webhooks.field.name': 'Subscription name',
|
||||
'webhooks.field.url': 'Webhook URL',
|
||||
'webhooks.field.urlHint': 'https:// or http://. Private CIDRs (10/8, 192.168/16, 127/8) blocked by SSRF guard',
|
||||
'webhooks.field.scopeFilter': 'Scope filter',
|
||||
'webhooks.field.scopeFilterHint': 'Empty = all scopes',
|
||||
'webhooks.field.dictionaryFilter': 'Dictionaries (CSV)',
|
||||
'webhooks.field.dictionaryFilterHint': 'Comma-separated: spacecraft, satellite_type. Empty = all',
|
||||
'webhooks.field.eventTypeFilter': 'Event types (CSV)',
|
||||
'webhooks.field.eventTypeFilterHint': 'record.created, record.updated, record.closed. Empty = all',
|
||||
'webhooks.field.description': 'Description',
|
||||
'webhooks.field.createdBy': 'Created by',
|
||||
'webhooks.error.nameUrlRequired': 'Name and URL are required',
|
||||
'webhooks.error.urlScheme': 'URL must start with http:// or https://',
|
||||
'webhooks.secret.title': 'HMAC secret for "{{name}}"',
|
||||
'webhooks.secret.warningTitle': 'Copy now!',
|
||||
'webhooks.secret.warning': 'This secret is shown ONCE. It will be masked afterwards. Pass it to the receiver so it can verify the X-Ordinis-Signature header.',
|
||||
'webhooks.secret.rotatedTitle': 'Secret rotated',
|
||||
'webhooks.secret.rotatedWarning': 'New secret. Old one stopped working. Update the receiver.',
|
||||
'webhooks.secret.copy': 'Copy',
|
||||
'webhooks.secret.copied': 'Copied',
|
||||
'webhooks.secret.acknowledge': 'Saved it',
|
||||
'webhooks.deliveries.title': 'Delivery history',
|
||||
'webhooks.deliveries.empty': 'No deliveries yet',
|
||||
'webhooks.deliveries.col.eventId': 'Event ID',
|
||||
'webhooks.deliveries.col.attempts': 'Attempts',
|
||||
'webhooks.deliveries.col.lastAttempt': 'Last attempt',
|
||||
'webhooks.deliveries.col.lastStatusCode': 'HTTP status',
|
||||
'webhooks.deliveries.col.lastError': 'Last error',
|
||||
'audit.title': 'Audit log',
|
||||
'audit.subtitle': 'Legally significant log of NSI changes',
|
||||
'audit.filter.dictionary': 'Dictionary',
|
||||
|
||||
Reference in New Issue
Block a user