From ab18172510bbb9cb8f02af6bf862e374517cf3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Thu, 13 Aug 2026 11:40:54 -0600 Subject: [PATCH 1/5] fix[frontend](integrations): standarized collector colorscheme --- frontend/src/features/integrations/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/integrations/constants.ts b/frontend/src/features/integrations/constants.ts index 314cf95e6..b46ebb4ea 100644 --- a/frontend/src/features/integrations/constants.ts +++ b/frontend/src/features/integrations/constants.ts @@ -30,7 +30,7 @@ export const INGEST_META: Record = { agent: { label: 'Agent', pill: 'bg-sky-500/15 text-sky-600 ring-sky-500/25 dark:text-sky-300' }, forwarder: { label: 'Collector', pill: 'bg-violet-500/15 text-violet-600 ring-violet-500/25 dark:text-violet-300' }, plugin: { label: 'Plugin', pill: 'bg-amber-500/15 text-amber-600 ring-amber-500/25 dark:text-amber-300' }, - collector: { label: 'Collector', pill: 'bg-fuchsia-500/15 text-fuchsia-600 ring-fuchsia-500/25 dark:text-fuchsia-300' }, + collector: { label: 'Collector', pill: 'bg-violet-500/15 text-violet-600 ring-violet-500/25 dark:text-violet-300' }, } export function ingestMeta(t?: string): { label: string; pill: string } { From f7d49ff3c9e53b6ad48402ad093afaef64e3cd2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Thu, 13 Aug 2026 12:04:50 -0600 Subject: [PATCH 2/5] fix[frontend](integrations):added missing translations --- .../setup/collector/RemoteEnablePanel.tsx | 4 +- frontend/src/shared/i18n/locales/de.json | 43 ++++++++++++++++++- frontend/src/shared/i18n/locales/es.json | 43 ++++++++++++++++++- frontend/src/shared/i18n/locales/fr.json | 41 ++++++++++++++++++ frontend/src/shared/i18n/locales/it.json | 41 ++++++++++++++++++ frontend/src/shared/i18n/locales/pt.json | 43 ++++++++++++++++++- frontend/src/shared/i18n/locales/ru.json | 41 ++++++++++++++++++ 7 files changed, 251 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx b/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx index 2d5461798..04a5eaf19 100644 --- a/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx +++ b/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx @@ -287,11 +287,11 @@ export function RemoteEnablePanel({ {forwarders.isLoading ? (
- {t(`${ROOT}.loadingForwarders`)} + {t(`${ROOT}.loadingCollectors`)}
) : allForwarders.length === 0 ? (

- {t(`${ROOT}.noForwarders`)} + {t(`${ROOT}.noCollectors`)}

) : (
diff --git a/frontend/src/shared/i18n/locales/de.json b/frontend/src/shared/i18n/locales/de.json index 085384a1b..7eb9f53d4 100644 --- a/frontend/src/shared/i18n/locales/de.json +++ b/frontend/src/shared/i18n/locales/de.json @@ -3330,6 +3330,47 @@ "bodyHttp": "Senden Sie Ihre Ereignisse als JSON per HTTP POST an {{endpoint}}. Jeder Anfrage-Body ist ein JSON-Ereignis:", "httpBindNote": "Der HTTP/HTTPS-Listener bindet standardmäßig an 127.0.0.1 (den Collector-Host) — senden Sie daher von einer auf diesem Host laufenden App aus, oder schalten Sie einen Reverse-Proxy vor. Für entfernte Quellen syslog (UDP/TCP/TLS) verwenden." } + }, + "remoteEnable": { + "title": "Diese Integration remote aktivieren", + "body": "Wählen Sie einen Collector, wählen Sie das Protokoll und den Port, aktivieren Sie dann die Integration — kein SSH erforderlich.", + "noPermission": "Sie haben keine Berechtigung, Remote-Integrationen zu verwalten. Bitten Sie einen Administrator, die Berechtigung integrations.write zu erteilen.", + "loadingCollectors": "Collector werden geladen…", + "noCollectors": "Es ist noch kein Collector registriert. Installieren Sie einen oben, um Integrationen remote zu aktivieren.", + "forwarderOffline": "Dieser Collector ist offline und kann derzeit keine Konfigurationsänderungen empfangen.", + "legendOnline": "{{count}} online", + "legendOffline": "{{count}} offline", + "legendTotal": "{{count}} insgesamt", + "forwarderLabel": "Collector", + "protoLabel": "Protokoll", + "portLabel": "Port", + "currentStateLoading": "Aktuelle Konfiguration wird geprüft…", + "currentlyEnabled": "Aktuell: aktiviert · {{proto}} · Port {{port}}", + "currentlyDisabled": "Aktuell: deaktiviert · {{proto}} · Port {{port}}", + "authLabel": "Authentifizierung", + "authNone": "Keine", + "authBearer": "Bearer-Token", + "authHmac": "HMAC-Signatur", + "pathLabel": "Pfad", + "signatureHeaderLabel": "Signature-Header", + "certTitle": "TLS-Zertifikat", + "certLoading": "Zertifikatstatus wird geprüft…", + "certLoaded": "Ein gültiges Zertifikat ist bereits auf diesem Collector geladen.", + "certMissing": "Es ist noch kein Zertifikat geladen — laden Sie eines hoch, um dieses Protokoll zu aktivieren.", + "certInvalid": "Das geladene Zertifikat ist ungültig: {{error}}", + "certPemLabel": "Zertifikat", + "keyPemLabel": "Privater Schlüssel", + "caPemLabel": "CA-Zertifikat", + "uploadCertsButton": "Zertifikat hochladen", + "certUploadSuccess": "Zertifikat hochgeladen.", + "certUploadError": "Fehler beim Hochladen des Zertifikats.", + "certReadError": "Dateien konnten nicht gelesen werden.", + "enableButton": "Integration aktivieren", + "disableButton": "Integration deaktivieren", + "enableSuccess": "Integration aktiviert.", + "disableSuccess": "Integration deaktiviert.", + "enableError": "Fehler beim Aktualisieren der Integration.", + "secretWarning": "Speichern Sie dieses Token sicher — es wird danach nicht mehr angezeigt." } }, "codeBlock": { @@ -5504,4 +5545,4 @@ "noneFailed": "Keine Fehler." } } -} \ No newline at end of file +} diff --git a/frontend/src/shared/i18n/locales/es.json b/frontend/src/shared/i18n/locales/es.json index 6526a4b80..9f1224529 100644 --- a/frontend/src/shared/i18n/locales/es.json +++ b/frontend/src/shared/i18n/locales/es.json @@ -3292,6 +3292,47 @@ "bodyHttp": "Envía tus eventos como JSON mediante HTTP POST a {{endpoint}}. El cuerpo de cada solicitud es un evento JSON:", "httpBindNote": "El listener HTTP/HTTPS se enlaza a 127.0.0.1 (el host del Collector) de forma predeterminada, así que envía desde una app que se ejecute en ese host — o usa un reverse proxy como intermediario. Para fuentes remotas, usa syslog (UDP/TCP/TLS)." } + }, + "remoteEnable": { + "title": "Habilitar esta integración remotamente", + "body": "Elige un Collector, selecciona el protocolo y puerto, luego habilita la integración — sin necesidad de SSH.", + "noPermission": "No tienes permiso para gestionar integraciones remotas. Pide a un administrador que otorgue el permiso integrations.write.", + "loadingCollectors": "Cargando Collectors…", + "noCollectors": "Aún no hay Collector registrado. Instala uno arriba para habilitar integraciones remotamente.", + "forwarderOffline": "Este Collector está sin conexión y no puede recibir cambios de configuración en este momento.", + "legendOnline": "{{count}} en línea", + "legendOffline": "{{count}} sin conexión", + "legendTotal": "{{count}} total", + "forwarderLabel": "Collector", + "protoLabel": "Protocolo", + "portLabel": "Puerto", + "currentStateLoading": "Comprobando configuración actual…", + "currentlyEnabled": "Actual: habilitado · {{proto}} · puerto {{port}}", + "currentlyDisabled": "Actual: deshabilitado · {{proto}} · puerto {{port}}", + "authLabel": "Autenticación", + "authNone": "Ninguno", + "authBearer": "Token portador", + "authHmac": "Firma HMAC", + "pathLabel": "Ruta", + "signatureHeaderLabel": "Encabezado de firma", + "certTitle": "Certificado TLS", + "certLoading": "Comprobando estado del certificado…", + "certLoaded": "Un certificado válido ya está cargado en este Collector.", + "certMissing": "No hay certificado cargado aún — carga uno para habilitar este protocolo.", + "certInvalid": "El certificado cargado no es válido: {{error}}", + "certPemLabel": "Certificado", + "keyPemLabel": "Clave privada", + "caPemLabel": "Certificado CA", + "uploadCertsButton": "Cargar certificado", + "certUploadSuccess": "Certificado cargado.", + "certUploadError": "No se pudo cargar el certificado.", + "certReadError": "No se pudieron leer los archivos seleccionados.", + "enableButton": "Habilitar integración", + "disableButton": "Deshabilitar integración", + "enableSuccess": "Integración habilitada.", + "disableSuccess": "Integración deshabilitada.", + "enableError": "No se pudo actualizar la integración.", + "secretWarning": "Almacena este token de forma segura — no se mostrará nuevamente." } }, "codeBlock": { @@ -5466,4 +5507,4 @@ "noneFailed": "Sin fallos." } } -} \ No newline at end of file +} diff --git a/frontend/src/shared/i18n/locales/fr.json b/frontend/src/shared/i18n/locales/fr.json index 441bc865a..38d3a0376 100644 --- a/frontend/src/shared/i18n/locales/fr.json +++ b/frontend/src/shared/i18n/locales/fr.json @@ -3330,6 +3330,47 @@ "bodyHttp": "Envoyez vos événements au format JSON via HTTP POST vers {{endpoint}}. Chaque corps de requête est un événement JSON :", "httpBindNote": "L'écouteur HTTP/HTTPS est lié à 127.0.0.1 (l'hôte Collector) par défaut ; envoyez donc les requêtes depuis une application s'exécutant sur cet hôte, ou placez un proxy inverse devant. Pour les sources distantes, utilisez syslog (UDP/TCP/TLS)." } + }, + "remoteEnable": { + "title": "Activer cette intégration à distance", + "body": "Choisissez un Collector, sélectionnez le protocole et le port, puis activez l'intégration — aucun SSH requis.", + "noPermission": "Vous n'avez pas la permission de gérer les intégrations distantes. Demandez à un administrateur d'accorder la permission integrations.write.", + "loadingCollectors": "Chargement des Collectors…", + "noCollectors": "Aucun Collector n'est enregistré pour le moment. Installez-en un ci-dessus pour activer les intégrations à distance.", + "forwarderOffline": "Ce Collector est hors ligne et ne peut pas recevoir de modifications de configuration en ce moment.", + "legendOnline": "{{count}} en ligne", + "legendOffline": "{{count}} hors ligne", + "legendTotal": "{{count}} total", + "forwarderLabel": "Collector", + "protoLabel": "Protocole", + "portLabel": "Port", + "currentStateLoading": "Vérification de la configuration actuelle…", + "currentlyEnabled": "Actuellement : activé · {{proto}} · port {{port}}", + "currentlyDisabled": "Actuellement : désactivé · {{proto}} · port {{port}}", + "authLabel": "Authentification", + "authNone": "Aucune", + "authBearer": "Jeton Bearer", + "authHmac": "Signature HMAC", + "pathLabel": "Chemin", + "signatureHeaderLabel": "En-tête de signature", + "certTitle": "Certificat TLS", + "certLoading": "Vérification du statut du certificat…", + "certLoaded": "Un certificat valide est déjà chargé sur ce Collector.", + "certMissing": "Aucun certificat chargé pour le moment — téléchargez-en un pour activer ce protocole.", + "certInvalid": "Le certificat chargé est invalide : {{error}}", + "certPemLabel": "Certificat", + "keyPemLabel": "Clé privée", + "caPemLabel": "Certificat CA", + "uploadCertsButton": "Télécharger le certificat", + "certUploadSuccess": "Certificat téléchargé.", + "certUploadError": "Échec du téléchargement du certificat.", + "certReadError": "Impossible de lire le(s) fichier(s) sélectionné(s).", + "enableButton": "Activer l'intégration", + "disableButton": "Désactiver l'intégration", + "enableSuccess": "Intégration activée.", + "disableSuccess": "Intégration désactivée.", + "enableError": "Échec de la mise à jour de l'intégration.", + "secretWarning": "Stockez ce jeton en toute sécurité — il ne sera pas affiché à nouveau." } }, "codeBlock": { diff --git a/frontend/src/shared/i18n/locales/it.json b/frontend/src/shared/i18n/locales/it.json index f3f8ee051..1a08184ac 100644 --- a/frontend/src/shared/i18n/locales/it.json +++ b/frontend/src/shared/i18n/locales/it.json @@ -3330,6 +3330,47 @@ "bodyHttp": "Invia i tuoi eventi come JSON via HTTP POST a {{endpoint}}. Il corpo di ogni richiesta è un evento JSON:", "httpBindNote": "Il listener HTTP/HTTPS si collega a 127.0.0.1 (l'host del Collector) per impostazione predefinita, quindi invia dall'app in esecuzione su quell'host — oppure usalo con un reverse proxy. Per sorgenti remote, usa syslog (UDP/TCP/TLS)." } + }, + "remoteEnable": { + "title": "Abilita questa integrazione da remoto", + "body": "Scegli un Collector, scegli il protocollo e la porta, quindi abilita l'integrazione — non è necessario SSH.", + "noPermission": "Non hai il permesso di gestire le integrazioni remote. Chiedi a un amministratore di concedere il permesso integrations.write.", + "loadingCollectors": "Caricamento Collectors…", + "noCollectors": "Nessun Collector è ancora registrato. Installane uno sopra per abilitare le integrazioni da remoto.", + "forwarderOffline": "Questo Collector è offline e non può ricevere modifiche di configurazione in questo momento.", + "legendOnline": "{{count}} online", + "legendOffline": "{{count}} offline", + "legendTotal": "{{count}} totali", + "forwarderLabel": "Collector", + "protoLabel": "Protocollo", + "portLabel": "Porta", + "currentStateLoading": "Verifica della configurazione attuale…", + "currentlyEnabled": "Attualmente: abilitato · {{proto}} · porta {{port}}", + "currentlyDisabled": "Attualmente: disabilitato · {{proto}} · porta {{port}}", + "authLabel": "Autenticazione", + "authNone": "Nessuna", + "authBearer": "Bearer token", + "authHmac": "Firma HMAC", + "pathLabel": "Percorso", + "signatureHeaderLabel": "Header di firma", + "certTitle": "Certificato TLS", + "certLoading": "Verifica dello stato del certificato…", + "certLoaded": "Un certificato valido è già caricato su questo Collector.", + "certMissing": "Nessun certificato caricato ancora — caricane uno per abilitare questo protocollo.", + "certInvalid": "Il certificato caricato non è valido: {{error}}", + "certPemLabel": "Certificato", + "keyPemLabel": "Chiave privata", + "caPemLabel": "Certificato CA", + "uploadCertsButton": "Carica certificato", + "certUploadSuccess": "Certificato caricato.", + "certUploadError": "Caricamento del certificato non riuscito.", + "certReadError": "Non è stato possibile leggere il file o i file selezionati.", + "enableButton": "Abilita integrazione", + "disableButton": "Disabilita integrazione", + "enableSuccess": "Integrazione abilitata.", + "disableSuccess": "Integrazione disabilitata.", + "enableError": "Aggiornamento dell'integrazione non riuscito.", + "secretWarning": "Archivia questo token in modo sicuro — non verrà visualizzato di nuovo." } }, "codeBlock": { diff --git a/frontend/src/shared/i18n/locales/pt.json b/frontend/src/shared/i18n/locales/pt.json index 4f8cecc2b..f84f73af9 100644 --- a/frontend/src/shared/i18n/locales/pt.json +++ b/frontend/src/shared/i18n/locales/pt.json @@ -3292,6 +3292,47 @@ "bodyHttp": "Envie seus eventos como JSON via HTTP POST para {{endpoint}}. O corpo de cada requisição é um evento JSON:", "httpBindNote": "O listener HTTP/HTTPS vincula-se a 127.0.0.1 (o host do Collector) por padrão, portanto envie requisições de um app em execução nesse host — ou coloque um proxy reverso à frente. Para fontes remotas, use syslog (UDP/TCP/TLS)." } + }, + "remoteEnable": { + "title": "Ativar esta integração remotamente", + "body": "Escolha um Collector, selecione o protocolo e a porta, depois ative a integração — sem necessidade de SSH.", + "noPermission": "Você não tem permissão para gerenciar integrações remotas. Peça a um administrador para conceder a permissão integrations.write.", + "loadingCollectors": "Carregando Collectors…", + "noCollectors": "Nenhum Collector foi registrado ainda. Instale um acima para ativar integrações remotamente.", + "forwarderOffline": "Este Collector está offline e não pode receber alterações de configuração agora.", + "legendOnline": "{{count}} online", + "legendOffline": "{{count}} offline", + "legendTotal": "{{count}} total", + "forwarderLabel": "Collector", + "protoLabel": "Protocolo", + "portLabel": "Porta", + "currentStateLoading": "Verificando configuração atual…", + "currentlyEnabled": "Atualmente: ativada · {{proto}} · porta {{port}}", + "currentlyDisabled": "Atualmente: desativada · {{proto}} · porta {{port}}", + "authLabel": "Autenticação", + "authNone": "Nenhuma", + "authBearer": "Token Bearer", + "authHmac": "Assinatura HMAC", + "pathLabel": "Caminho", + "signatureHeaderLabel": "Cabeçalho de assinatura", + "certTitle": "Certificado TLS", + "certLoading": "Verificando status do certificado…", + "certLoaded": "Um certificado válido já está carregado neste Collector.", + "certMissing": "Nenhum certificado carregado ainda — envie um para ativar este protocolo.", + "certInvalid": "O certificado carregado é inválido: {{error}}", + "certPemLabel": "Certificado", + "keyPemLabel": "Chave privada", + "caPemLabel": "Certificado CA", + "uploadCertsButton": "Enviar certificado", + "certUploadSuccess": "Certificado enviado.", + "certUploadError": "Falha ao enviar o certificado.", + "certReadError": "Não foi possível ler o(s) arquivo(s) selecionado(s).", + "enableButton": "Ativar integração", + "disableButton": "Desativar integração", + "enableSuccess": "Integração ativada.", + "disableSuccess": "Integração desativada.", + "enableError": "Falha ao atualizar a integração.", + "secretWarning": "Armazene este token com segurança — não será exibido novamente." } }, "codeBlock": { @@ -5466,4 +5507,4 @@ "noneFailed": "Sem falhas." } } -} \ No newline at end of file +} diff --git a/frontend/src/shared/i18n/locales/ru.json b/frontend/src/shared/i18n/locales/ru.json index c397ed363..6cca77678 100644 --- a/frontend/src/shared/i18n/locales/ru.json +++ b/frontend/src/shared/i18n/locales/ru.json @@ -3212,6 +3212,47 @@ "bodyHttp": "Отправляйте события в формате JSON через HTTP POST на {{endpoint}}. Тело каждого запроса — одно событие JSON:", "httpBindNote": "По умолчанию слушатель HTTP/HTTPS привязывается к 127.0.0.1 (хосту Collector), поэтому отправляйте запросы из приложения, работающего на этом хосте, или используйте обратный прокси. Для удалённых источников используйте syslog (UDP/TCP/TLS)." } + }, + "remoteEnable": { + "title": "Включить эту интеграцию удалённо", + "body": "Выберите Collector, протокол и порт, затем включите интеграцию — SSH не требуется.", + "noPermission": "У вас нет прав на управление удалёнными интеграциями. Попросите администратора предоставить разрешение integrations.write.", + "loadingCollectors": "Загрузка Collectors…", + "noCollectors": "Ни один Collector ещё не зарегистрирован. Установите один выше, чтобы включить интеграции удалённо.", + "forwarderOffline": "Этот Collector находится в режиме офлайн и сейчас не может получать изменения конфигурации.", + "legendOnline": "{{count}} в сети", + "legendOffline": "{{count}} офлайн", + "legendTotal": "{{count}} всего", + "forwarderLabel": "Collector", + "protoLabel": "Протокол", + "portLabel": "Порт", + "currentStateLoading": "Проверка текущей конфигурации…", + "currentlyEnabled": "Сейчас: включено · {{proto}} · порт {{port}}", + "currentlyDisabled": "Сейчас: отключено · {{proto}} · порт {{port}}", + "authLabel": "Аутентификация", + "authNone": "Нет", + "authBearer": "Bearer-токен", + "authHmac": "HMAC-подпись", + "pathLabel": "Путь", + "signatureHeaderLabel": "Заголовок подписи", + "certTitle": "TLS-сертификат", + "certLoading": "Проверка статуса сертификата…", + "certLoaded": "Действительный сертификат уже загружен на этот Collector.", + "certMissing": "Сертификат ещё не загружен — загрузите один, чтобы включить этот протокол.", + "certInvalid": "Загруженный сертификат недействителен: {{error}}", + "certPemLabel": "Сертификат", + "keyPemLabel": "Закрытый ключ", + "caPemLabel": "Сертификат ЦА", + "uploadCertsButton": "Загрузить сертификат", + "certUploadSuccess": "Сертификат загружен.", + "certUploadError": "Не удалось загрузить сертификат.", + "certReadError": "Не удалось прочитать выбранный файл(ы).", + "enableButton": "Включить интеграцию", + "disableButton": "Отключить интеграцию", + "enableSuccess": "Интеграция включена.", + "disableSuccess": "Интеграция отключена.", + "enableError": "Не удалось обновить интеграцию.", + "secretWarning": "Сохраните этот токен в безопасном месте — он больше не будет показан." } }, "codeBlock": { From 8abc53e3125f88206efdc2d6aebcfed6fcba481c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Thu, 13 Aug 2026 13:06:01 -0600 Subject: [PATCH 3/5] fix[frontend](integrations):added send to master option on collector --- .../setup/collector/ForwarderGuide.tsx | 67 ++++++-- .../setup/collector/RemoteEnablePanel.tsx | 144 +++++++++++------- .../setup/collector/collectors/aix.tsx | 2 +- .../setup/collector/collectors/cisco.tsx | 2 +- .../collector/collectors/ciscoFirepower.tsx | 2 +- .../collector/collectors/ciscoMeraki.tsx | 2 +- .../collector/collectors/ciscoSwitch.tsx | 2 +- .../collector/collectors/deceptivebytes.tsx | 2 +- .../setup/collector/collectors/fortigate.tsx | 2 +- .../setup/collector/collectors/fortiweb.tsx | 2 +- .../setup/collector/collectors/github.tsx | 2 +- .../setup/collector/collectors/mikrotik.tsx | 2 +- .../setup/collector/collectors/netflow.tsx | 2 +- .../setup/collector/collectors/oracle.tsx | 2 +- .../setup/collector/collectors/paloalto.tsx | 2 +- .../setup/collector/collectors/pfsense.tsx | 2 +- .../setup/collector/collectors/sonicwall.tsx | 2 +- .../setup/collector/collectors/sophosxg.tsx | 2 +- .../setup/collector/collectors/suricata.tsx | 2 +- .../setup/collector/collectors/vmware.tsx | 2 +- frontend/src/shared/i18n/locales/de.json | 6 +- frontend/src/shared/i18n/locales/en.json | 8 +- frontend/src/shared/i18n/locales/es.json | 6 +- frontend/src/shared/i18n/locales/fr.json | 8 +- frontend/src/shared/i18n/locales/it.json | 8 +- frontend/src/shared/i18n/locales/pt.json | 6 +- frontend/src/shared/i18n/locales/ru.json | 8 +- 27 files changed, 200 insertions(+), 97 deletions(-) diff --git a/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx b/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx index d97355240..0d1d61035 100644 --- a/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx +++ b/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState, type ReactNode } from 'react' +import { useMemo, useRef, useState, type ReactNode } from 'react' import { useTranslation } from 'react-i18next' import { ChevronDown, Forward, Server, ShieldCheck } from 'lucide-react' import { cn } from '@/shared/lib/utils' @@ -6,7 +6,6 @@ import { Section } from '@/features/integrations/components/ui/Section' import { CodeBlock } from '@/features/integrations/components/ui/CodeBlock' import { useConectionKey } from '@/features/integrations/hooks/useConnectionKey' import { FlowNode, FlowEdge } from '@/shared/components/ui/flow-diagram' -import { useCollectorIntegration } from '@/features/integrations/hooks/useCollectorIntegration' import { RemoteEnablePanel, type RemoteEnableSelection } from './RemoteEnablePanel' import { availableProtosFor, defaultPortFor, type Proto } from './protoCatalog' @@ -115,15 +114,8 @@ function ManualCommandSection({ sourceType, selection }: { sourceType: string; s // ── Forwarder install section ───────────────────────────────────────────────── -export function ForwarderInstall({ source }: { source: string }) { - const { t } = useTranslation() - const { key } = useConectionKey() - const host = forwarderHost() - - // Token is always shown as ••••••• in the UI; the real value is copied on click. - // Falls back to "*******" if the key hasn't loaded or the request failed. - const token = key.data?.connectionKey ?? '*'.repeat(7) - const installCmd = `sudo bash -c " +function buildForwarderInstallCmd(host: string, token: string): string { + return `sudo bash -c " apt update -y && apt install wget -y && \\ mkdir -p /opt/utmstack-forwarder && \\ wget --no-check-certificate -P /opt/utmstack-forwarder \\ @@ -131,6 +123,13 @@ export function ForwarderInstall({ source }: { source: string }) { chmod 755 /opt/utmstack-forwarder/utmstack_forwarder && \\ /opt/utmstack-forwarder/utmstack_forwarder install ${host} ${token} yes "` +} + +export function ForwarderInstall({ source }: { source: string }) { + const { t } = useTranslation() + const { key } = useConectionKey() + const token = key.data?.connectionKey ?? '*'.repeat(7) + const installCmd = buildForwarderInstallCmd(forwarderHost(), token) return (
@@ -160,14 +159,19 @@ interface ForwarderGuideProps { export function ForwarderGuide({ source, port, sourceType, defaultProto, children }: ForwarderGuideProps) { const { t } = useTranslation() - const { forwarders } = useCollectorIntegration() - const hasOnlineForwarder = (forwarders.data ?? []).some((f) => f.status === 'online') const availableProtos = useMemo(() => availableProtosFor(sourceType), [sourceType]) const initialProto = defaultProto ?? availableProtos[0] const [selection, setSelection] = useState(() => ({ proto: initialProto, port: defaultPortFor(sourceType, initialProto) || port, })) + const [installOpen, setInstallOpen] = useState(false) + const installRef = useRef(null) + + const handleAddCollector = () => { + setInstallOpen(true) + setTimeout(() => installRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50) + } return (
@@ -179,25 +183,56 @@ export function ForwarderGuide({ source, port, sourceType, defaultProto, childre

- {!forwarders.isLoading && !hasOnlineForwarder && } - {/* Vendor-specific steps (device-side config). */} {children} +
+ setInstallOpen((o) => !o)} /> +
) } +function ForwarderInstallSection({ source, open, onToggle }: { source: string; open: boolean; onToggle: () => void }) { + const { t } = useTranslation() + const { key } = useConectionKey() + + const token = key.data?.connectionKey ?? '*'.repeat(7) + const installCmd = buildForwarderInstallCmd(forwarderHost(), token) + + return ( +
+ + {open && ( +
+

{t(`${SHARED}.install.body`)}

+ +

+ {t(`${SHARED}.install.reuse`, { source })} +

+
+ )} +
+ ) +} + export function ForwarderUninstallSection() { const { t } = useTranslation() const [open, setOpen] = useState(false) diff --git a/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx b/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx index 04a5eaf19..0124079ff 100644 --- a/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx +++ b/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx @@ -1,16 +1,20 @@ import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' import { toast } from 'sonner' -import { Check, ChevronDown, Copy, Loader2, ShieldAlert, ShieldCheck, Upload } from 'lucide-react' +import { Check, ChevronDown, Copy, KeySquare, Loader2, Plus, ShieldAlert, ShieldCheck, Upload } from 'lucide-react' import { cn } from '@/shared/lib/utils' import { Button } from '@/shared/components/ui/button' import { Section } from '@/features/integrations/components/ui/Section' import { useAuth } from '@/features/auth/services/auth.context' +import { useBilling } from '@/features/billing/services/billing.context' import { useCollectorIntegration } from '@/features/integrations/hooks/useCollectorIntegration' import type { ForwarderCollector } from '@/features/integrations/types' import { defaultPortFor, httpDefaultsFor, type HttpAuth, type Proto } from './protoCatalog' const ROOT = 'integrations.setup.remoteEnable' +const MASTER_ID = -1 +const ADD_COLLECTOR_ID = -2 export interface RemoteEnableSelection { proto: Proto @@ -23,6 +27,7 @@ interface RemoteEnablePanelProps { defaultProto?: Proto step?: number onSelectionChange?: (selection: RemoteEnableSelection) => void + onRequestAddCollector?: () => void } async function fileToBase64(file: File): Promise { @@ -72,8 +77,8 @@ function ForwarderPicker({ className="flex h-9 w-full items-center justify-between gap-2 rounded-md border border-border bg-background px-3 text-sm text-foreground disabled:opacity-70" > - {selected && } - {selected ? `${selected.hostname} (${selected.ip})` : ''} + {selected && selected.ip && } + {selected ? (selected.ip ? `${selected.hostname} (${selected.ip})` : selected.hostname) : ''} @@ -89,10 +94,9 @@ function ForwarderPicker({ }} className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm hover:bg-muted" > - - - {f.hostname} ({f.ip}) - + {f.ip && } + {f.id === ADD_COLLECTOR_ID && } + {f.ip ? `${f.hostname} (${f.ip})` : f.hostname} ))} @@ -130,10 +134,14 @@ export function RemoteEnablePanel({ defaultProto, step, onSelectionChange, + onRequestAddCollector, }: RemoteEnablePanelProps) { const { t } = useTranslation() + const navigate = useNavigate() const { hasPermission } = useAuth() const canManage = hasPermission('integrations.write') + const { license } = useBilling() + const canUseMaster = license?.edition === 'enterprise' || !!license?.mssp const { forwarders, setDataType, setCertificates, tlsStatus, dataTypeConfig } = useCollectorIntegration() @@ -161,13 +169,30 @@ export function RemoteEnablePanel({ const [keyPem, setKeyPem] = useState(null) const [caPem, setCaPem] = useState(null) + const isMaster = collectorId === MASTER_ID + const masterOption: ForwarderCollector = { + id: MASTER_ID, + hostname: t(`${ROOT}.sendToMaster`), + ip: '', + version: '', + status: 'online', + } + const addCollectorOption: ForwarderCollector = { + id: ADD_COLLECTOR_ID, + hostname: t(`${ROOT}.addCollector`), + ip: '', + version: '', + status: 'online', + } + const pickerOptions = [masterOption, ...allForwarders, addCollectorOption] const selectedForwarder = allForwarders.find((f) => f.id === collectorId) ?? null - const isSelectedForwarderOffline = selectedForwarder != null && selectedForwarder.status !== 'online' + const isSelectedForwarderOffline = !isMaster && selectedForwarder != null && selectedForwarder.status !== 'online' useEffect(() => { - if (collectorId != null && allForwarders.some((f) => f.id === collectorId)) return - const preferred = allForwarders.find((f) => f.status === 'online') ?? allForwarders[0] - setCollectorId(preferred?.id ?? null) + if (collectorId == null) return + if (collectorId === MASTER_ID) return + if (allForwarders.some((f) => f.id === collectorId)) return + setCollectorId(null) }, [allForwarders, collectorId]) useEffect(() => { @@ -177,10 +202,10 @@ export function RemoteEnablePanel({ const isHttp = proto === 'http' || proto === 'https' const needsCerts = proto === 'tls' || proto === 'https' - const tlsStatusQuery = tlsStatus(needsCerts ? collectorId : null) + const tlsStatusQuery = tlsStatus(needsCerts && !isMaster ? collectorId : null) - const currentConfig = dataTypeConfig(collectorId, dataType) - const isSyncingConfig = collectorId != null && currentConfig.isLoading + const currentConfig = dataTypeConfig(isMaster ? null : collectorId, dataType) + const isSyncingConfig = !isMaster && collectorId != null && currentConfig.isLoading const isLiveOnSelectedForwarder = !!(currentConfig.data?.configured && currentConfig.data?.enabled) const isConfigLocked = isSyncingConfig || isLiveOnSelectedForwarder @@ -284,29 +309,34 @@ export function RemoteEnablePanel({

{t(`${ROOT}.body`)}

- {forwarders.isLoading ? ( -
- - {t(`${ROOT}.loadingCollectors`)} -
- ) : allForwarders.length === 0 ? ( -

- {t(`${ROOT}.noCollectors`)} -

- ) : ( -
-
+
+ {!forwarders.isLoading && allForwarders.length === 0 && !isMaster && ( +

+ {t(`${ROOT}.noCollectors`)} +

+ )} +
- + {!isMaster && ( + + )}
+ {isMaster && ( + + )} + + {!isMaster && (<> {isSyncingConfig ? (
@@ -524,8 +564,8 @@ export function RemoteEnablePanel({ {t(`${ROOT}.disableButton`)}
+ )}
- )}
) } diff --git a/frontend/src/features/integrations/components/setup/collector/collectors/aix.tsx b/frontend/src/features/integrations/components/setup/collector/collectors/aix.tsx index 38985e495..c6383e0b4 100644 --- a/frontend/src/features/integrations/components/setup/collector/collectors/aix.tsx +++ b/frontend/src/features/integrations/components/setup/collector/collectors/aix.tsx @@ -12,7 +12,7 @@ function AixGuide({ module: _module }: { module: Integration }) { return ( -
+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

{t(`${ROOT}.certWarning.body`)}

-
+

{t(`${ROOT}.step2.body`)}

-
+

-

+

:${PORT}` return ( -

+

{t(`${ROOT}.step2.body`)}

{t(`${ROOT}.step2.editBody`)}

diff --git a/frontend/src/features/integrations/components/setup/collector/collectors/paloalto.tsx b/frontend/src/features/integrations/components/setup/collector/collectors/paloalto.tsx index 1200970c3..ac404985e 100644 --- a/frontend/src/features/integrations/components/setup/collector/collectors/paloalto.tsx +++ b/frontend/src/features/integrations/components/setup/collector/collectors/paloalto.tsx @@ -13,7 +13,7 @@ function PaloAltoGuide({ module: _module }: { module: Integration }) { return ( -
+

-

+

-

+

-

+

-

+

-

+

Date: Thu, 13 Aug 2026 14:52:17 -0600 Subject: [PATCH 4/5] fix[frontend](integrations): added api key selectors on collector integrations --- .../features/api-keys/hooks/useApiKeysList.ts | 12 ++++ .../components/setup/CustomSetup.tsx | 2 +- .../setup/collector/ApiKeyPicker.tsx | 66 +++++++++++++++++++ .../setup/collector/ForwarderGuide.tsx | 17 +++++ .../setup/collector/RemoteEnablePanel.tsx | 41 ++++++++++-- 5 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 frontend/src/features/api-keys/hooks/useApiKeysList.ts create mode 100644 frontend/src/features/integrations/components/setup/collector/ApiKeyPicker.tsx diff --git a/frontend/src/features/api-keys/hooks/useApiKeysList.ts b/frontend/src/features/api-keys/hooks/useApiKeysList.ts new file mode 100644 index 000000000..2576e8769 --- /dev/null +++ b/frontend/src/features/api-keys/hooks/useApiKeysList.ts @@ -0,0 +1,12 @@ +import { useQuery } from '@tanstack/react-query' +import { apiKeysHttpService } from '../services/api-keys-http.service' + +export const API_KEYS_LIST_QUERY_KEY = ['api-keys', 'list'] as const + +export function useApiKeysList() { + return useQuery({ + queryKey: API_KEYS_LIST_QUERY_KEY, + queryFn: () => apiKeysHttpService.list(1, 200), + staleTime: 30_000, + }) +} diff --git a/frontend/src/features/integrations/components/setup/CustomSetup.tsx b/frontend/src/features/integrations/components/setup/CustomSetup.tsx index 921ee83e9..4a961c9f4 100644 --- a/frontend/src/features/integrations/components/setup/CustomSetup.tsx +++ b/frontend/src/features/integrations/components/setup/CustomSetup.tsx @@ -37,7 +37,7 @@ export function CustomSetup({ integration }: { integration: Integration }) { // slug if the catalog row has no explicit data type. const name = integration.dataType || integration.moduleName?.toLowerCase() || 'my-integration' - const [selection, setSelection] = useState({ proto: 'udp', port: '7100' }) + const [selection, setSelection] = useState({ proto: 'udp', port: '7100', isMaster: false, apiKey: null }) const { proto, port } = selection const isHttp = proto === 'http' || proto === 'https' diff --git a/frontend/src/features/integrations/components/setup/collector/ApiKeyPicker.tsx b/frontend/src/features/integrations/components/setup/collector/ApiKeyPicker.tsx new file mode 100644 index 000000000..b2afa7bba --- /dev/null +++ b/frontend/src/features/integrations/components/setup/collector/ApiKeyPicker.tsx @@ -0,0 +1,66 @@ +import { useEffect, useRef, useState } from 'react' +import { ChevronDown, Plus } from 'lucide-react' +import type { ApiKey } from '@/features/api-keys/types/api-key.types' + +export interface ApiKeyPickerProps { + keys: ApiKey[] + value: number | null + onChange: (id: number) => void + onAddNew: () => void + addLabel: string + placeholder: string + emptyLabel: string + disabled?: boolean +} + +export function ApiKeyPicker({ keys, value, onChange, onAddNew, addLabel, placeholder, emptyLabel, disabled }: ApiKeyPickerProps) { + const [open, setOpen] = useState(false) + const ref = useRef(null) + const selected = keys.find((k) => k.id === value) ?? null + + useEffect(() => { + if (!open) return + const onDoc = (e: MouseEvent) => ref.current && !ref.current.contains(e.target as Node) && setOpen(false) + document.addEventListener('mousedown', onDoc) + return () => document.removeEventListener('mousedown', onDoc) + }, [open]) + + return ( +

+ + {open && ( +
+ {keys.length === 0 && ( + {emptyLabel} + )} + {keys.map((k) => ( + + ))} + +
+ )} +
+ ) +} diff --git a/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx b/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx index 0d1d61035..c84120742 100644 --- a/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx +++ b/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx @@ -65,6 +65,19 @@ function FlowDiagram({ source, port }: { source: string; port: string }) { ) } +// ── Master API key note ─────────────────────────────────────────────────────── + +function MasterApiKeyNote({ apiKey }: { apiKey: { id: number; name: string } }) { + const { t } = useTranslation() + return ( +
+

{t(`${SHARED}.masterHeader.body`, { name: apiKey.name })}

+ {/* ponytail: secret is generated once server-side; user must paste it themselves */} + +
+ ) +} + // ── Manual (CLI) command — collapsible, reactive to the RemoteEnablePanel ──── // selection above it. Replaces what used to be a fixed "Optional — Enable TLS // encryption" block: the command shown here always matches whatever @@ -164,6 +177,8 @@ export function ForwarderGuide({ source, port, sourceType, defaultProto, childre const [selection, setSelection] = useState(() => ({ proto: initialProto, port: defaultPortFor(sourceType, initialProto) || port, + isMaster: false, + apiKey: null, })) const [installOpen, setInstallOpen] = useState(false) const installRef = useRef(null) @@ -192,6 +207,8 @@ export function ForwarderGuide({ source, port, sourceType, defaultProto, childre onRequestAddCollector={handleAddCollector} /> + {selection.isMaster && selection.apiKey && } + {/* Vendor-specific steps (device-side config). */} {children} diff --git a/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx b/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx index 0124079ff..3447fd3d2 100644 --- a/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx +++ b/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { toast } from 'sonner' -import { Check, ChevronDown, Copy, KeySquare, Loader2, Plus, ShieldAlert, ShieldCheck, Upload } from 'lucide-react' +import { Check, ChevronDown, Copy, Loader2, Plus, ShieldAlert, ShieldCheck, Upload } from 'lucide-react' import { cn } from '@/shared/lib/utils' import { Button } from '@/shared/components/ui/button' import { Section } from '@/features/integrations/components/ui/Section' @@ -11,6 +11,8 @@ import { useBilling } from '@/features/billing/services/billing.context' import { useCollectorIntegration } from '@/features/integrations/hooks/useCollectorIntegration' import type { ForwarderCollector } from '@/features/integrations/types' import { defaultPortFor, httpDefaultsFor, type HttpAuth, type Proto } from './protoCatalog' +import { useApiKeysList } from '@/features/api-keys/hooks/useApiKeysList' +import { ApiKeyPicker } from './ApiKeyPicker' const ROOT = 'integrations.setup.remoteEnable' const MASTER_ID = -1 @@ -19,6 +21,8 @@ const ADD_COLLECTOR_ID = -2 export interface RemoteEnableSelection { proto: Proto port: string + isMaster: boolean + apiKey: { id: number; name: string } | null } interface RemoteEnablePanelProps { @@ -144,11 +148,13 @@ export function RemoteEnablePanel({ const canUseMaster = license?.edition === 'enterprise' || !!license?.mssp const { forwarders, setDataType, setCertificates, tlsStatus, dataTypeConfig } = useCollectorIntegration() + const apiKeys = useApiKeysList() const allForwarders = forwarders.data ?? [] const initialProto = defaultProto ?? availableProtos[0] const [collectorId, setCollectorId] = useState(null) + const [apiKeyId, setApiKeyId] = useState(null) const [proto, setProto] = useState(initialProto) const [port, setPort] = useState(() => defaultPortFor(dataType, initialProto)) const httpDefaults = httpDefaultsFor(dataType) @@ -196,8 +202,18 @@ export function RemoteEnablePanel({ }, [allForwarders, collectorId]) useEffect(() => { - onSelectionChange?.({ proto, port }) - }, [proto, port, onSelectionChange]) + if (collectorId !== MASTER_ID) setApiKeyId(null) + }, [collectorId]) + + useEffect(() => { + const resolvedKey = apiKeys.data?.data.find((k) => k.id === apiKeyId) ?? null + onSelectionChange?.({ + proto, + port, + isMaster, + apiKey: resolvedKey ? { id: resolvedKey.id, name: resolvedKey.name } : null, + }) + }, [proto, port, isMaster, apiKeyId, apiKeys.data, onSelectionChange]) const isHttp = proto === 'http' || proto === 'https' const needsCerts = proto === 'tls' || proto === 'https' @@ -373,10 +389,21 @@ export function RemoteEnablePanel({ {isMaster && ( - + )} {!isMaster && (<> From facb1101cfd736955396d305860bc9e2b8c3c9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Thu, 13 Aug 2026 15:08:51 -0600 Subject: [PATCH 5/5] fix[frontend](integrations): added sent to master command section (step 2 ) --- .../setup/collector/ForwarderGuide.tsx | 35 +++++++------ .../setup/collector/RemoteEnablePanel.tsx | 50 ++++++++++++------- frontend/src/shared/i18n/locales/de.json | 10 +++- frontend/src/shared/i18n/locales/en.json | 10 +++- frontend/src/shared/i18n/locales/es.json | 10 +++- frontend/src/shared/i18n/locales/fr.json | 10 +++- frontend/src/shared/i18n/locales/it.json | 10 +++- frontend/src/shared/i18n/locales/pt.json | 10 +++- frontend/src/shared/i18n/locales/ru.json | 10 +++- 9 files changed, 117 insertions(+), 38 deletions(-) diff --git a/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx b/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx index c84120742..9ac66dee4 100644 --- a/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx +++ b/frontend/src/features/integrations/components/setup/collector/ForwarderGuide.tsx @@ -65,15 +65,20 @@ function FlowDiagram({ source, port }: { source: string; port: string }) { ) } -// ── Master API key note ─────────────────────────────────────────────────────── +// ── Master command (POST endpoint + auth header) ───────────────────────────── -function MasterApiKeyNote({ apiKey }: { apiKey: { id: number; name: string } }) { +function MasterCommandSection({ selection }: { selection: RemoteEnableSelection }) { const { t } = useTranslation() + if (!selection.apiKey) return null + const host = forwarderHost() + // ponytail: secret is generated once server-side; user must paste it themselves + const cmd = `POST ${selection.proto}://${host}:8080/v1/logs +Authorization: Bearer +Content-Type: application/json` return ( -
-

{t(`${SHARED}.masterHeader.body`, { name: apiKey.name })}

- {/* ponytail: secret is generated once server-side; user must paste it themselves */} - +
+

{t(`${SHARED}.masterHeader.body`, { name: selection.apiKey.name })}

+
) } @@ -207,16 +212,18 @@ export function ForwarderGuide({ source, port, sourceType, defaultProto, childre onRequestAddCollector={handleAddCollector} /> - {selection.isMaster && selection.apiKey && } + {selection.isMaster && selection.apiKey && } - {/* Vendor-specific steps (device-side config). */} - {children} + {/* Vendor-specific steps (device-side config). Hidden in master mode — it targets the forwarder, not master. */} + {!selection.isMaster && children} -
- setInstallOpen((o) => !o)} /> -
- - + {!selection.isMaster && ( +
+ setInstallOpen((o) => !o)} /> +
+ )} + {!selection.isMaster && } + {!selection.isMaster && } ) } diff --git a/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx b/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx index 3447fd3d2..2a228d1f0 100644 --- a/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx +++ b/frontend/src/features/integrations/components/setup/collector/RemoteEnablePanel.tsx @@ -155,6 +155,7 @@ export function RemoteEnablePanel({ const initialProto = defaultProto ?? availableProtos[0] const [collectorId, setCollectorId] = useState(null) const [apiKeyId, setApiKeyId] = useState(null) + const [masterProto, setMasterProto] = useState<'http' | 'https'>('https') const [proto, setProto] = useState(initialProto) const [port, setPort] = useState(() => defaultPortFor(dataType, initialProto)) const httpDefaults = httpDefaultsFor(dataType) @@ -208,12 +209,12 @@ export function RemoteEnablePanel({ useEffect(() => { const resolvedKey = apiKeys.data?.data.find((k) => k.id === apiKeyId) ?? null onSelectionChange?.({ - proto, + proto: isMaster ? masterProto : proto, port, isMaster, apiKey: resolvedKey ? { id: resolvedKey.id, name: resolvedKey.name } : null, }) - }, [proto, port, isMaster, apiKeyId, apiKeys.data, onSelectionChange]) + }, [proto, masterProto, port, isMaster, apiKeyId, apiKeys.data, onSelectionChange]) const isHttp = proto === 'http' || proto === 'https' const needsCerts = proto === 'tls' || proto === 'https' @@ -389,21 +390,36 @@ export function RemoteEnablePanel({ {isMaster && ( - +
+ + +
)} {!isMaster && (<> diff --git a/frontend/src/shared/i18n/locales/de.json b/frontend/src/shared/i18n/locales/de.json index bb95a58b9..24e427da3 100644 --- a/frontend/src/shared/i18n/locales/de.json +++ b/frontend/src/shared/i18n/locales/de.json @@ -2736,6 +2736,10 @@ "uninstall": { "title": "Optional – Collector deinstallieren", "body": "Führen Sie diesen Befehl auf dem Host aus, auf dem der Collector installiert ist, um den Dienst zu beenden und alle zugehörigen Dateien zu entfernen." + }, + "masterHeader": { + "title": "Autorisierungs-Header", + "body": "Fügen Sie diesen Authorization-Header jeder Anfrage hinzu, die Sie an den Master senden. Das Geheimnis für \"{{name}}\" wird nur einmal bei der Erstellung des API-Schlüssels angezeigt — kopieren Sie es dann und fügen Sie es anstelle des Platzhalters unten ein." } }, "aix": { @@ -3374,7 +3378,11 @@ "sendToMaster": "An Master senden", "enterpriseFeature": "Dies ist eine Enterprise-Funktion.", "addCollector": "Collector hinzufügen", - "addApiKey": "API-Schlüssel hinzufügen" + "addApiKey": "API-Schlüssel hinzufügen", + "apiKeyLabel": "API-Schlüssel", + "apiKeyPlaceholder": "API-Schlüssel auswählen", + "apiKeyAddNew": "API-Schlüssel hinzufügen…", + "apiKeyNone": "Noch keine API-Schlüssel vorhanden" } }, "codeBlock": { diff --git a/frontend/src/shared/i18n/locales/en.json b/frontend/src/shared/i18n/locales/en.json index cea2d1525..9bb76200c 100644 --- a/frontend/src/shared/i18n/locales/en.json +++ b/frontend/src/shared/i18n/locales/en.json @@ -2895,6 +2895,10 @@ "intro": "Prefer the terminal? SSH into the Collector host and run the command below — it matches whatever protocol and port you have selected above.", "certStep": "1 · Load your certificate and key (once per Collector)", "enableStep": "2 · Enable the integration" + }, + "masterHeader": { + "title": "Authorization header", + "body": "Include this Authorization header on every request you send to Master. The secret for \"{{name}}\" is only revealed once when the API key is generated — copy it then and paste it in place of the placeholder below." } }, "aix": { @@ -3475,7 +3479,11 @@ "sendToMaster": "Send to Master", "enterpriseFeature": "This is an Enterprise feature.", "addCollector": "Add Collector", - "addApiKey": "Add API key" + "addApiKey": "Add API key", + "apiKeyLabel": "API key", + "apiKeyPlaceholder": "Select an API key", + "apiKeyAddNew": "Add API key…", + "apiKeyNone": "No API keys yet" } }, "codeBlock": { diff --git a/frontend/src/shared/i18n/locales/es.json b/frontend/src/shared/i18n/locales/es.json index 021bfba00..c1126d49b 100644 --- a/frontend/src/shared/i18n/locales/es.json +++ b/frontend/src/shared/i18n/locales/es.json @@ -1838,6 +1838,10 @@ "uninstall": { "title": "Opcional — Desinstalar el Collector", "body": "Ejecuta este comando en el host donde está instalado el Collector para detener el servicio y eliminar todos sus archivos." + }, + "masterHeader": { + "title": "Cabecera de autorización", + "body": "Incluye esta cabecera Authorization en cada solicitud que envíes al Master. El secreto de \"{{name}}\" solo se muestra una vez al generar la clave API — cópialo entonces y pégalo en lugar del marcador de posición de abajo." } }, "cisco": { @@ -3336,7 +3340,11 @@ "sendToMaster": "Enviar al Master", "enterpriseFeature": "Esta es una función Enterprise.", "addCollector": "Añadir Collector", - "addApiKey": "Añadir clave API" + "addApiKey": "Añadir clave API", + "apiKeyLabel": "Clave API", + "apiKeyPlaceholder": "Selecciona una clave API", + "apiKeyAddNew": "Añadir clave API…", + "apiKeyNone": "Todavía no hay claves API" } }, "codeBlock": { diff --git a/frontend/src/shared/i18n/locales/fr.json b/frontend/src/shared/i18n/locales/fr.json index 08d6caa8d..4cc0730e1 100644 --- a/frontend/src/shared/i18n/locales/fr.json +++ b/frontend/src/shared/i18n/locales/fr.json @@ -2736,6 +2736,10 @@ "uninstall": { "title": "Optionnel — Désinstaller le Collector", "body": "Exécutez cette commande sur l'hôte où le Collector est installé pour arrêter le service et supprimer tous ses fichiers." + }, + "masterHeader": { + "title": "En-tête d'autorisation", + "body": "Incluez cet en-tête Authorization dans chaque requête envoyée au Master. Le secret de \"{{name}}\" n'est révélé qu'une seule fois à la génération de la clé API — copiez-le à ce moment et collez-le à la place du placeholder ci-dessous." } }, "aix": { @@ -3374,7 +3378,11 @@ "sendToMaster": "Envoyer au Master", "enterpriseFeature": "Ceci est une fonctionnalité Enterprise.", "addCollector": "Ajouter un Collector", - "addApiKey": "Ajouter une clé API" + "addApiKey": "Ajouter une clé API", + "apiKeyLabel": "Clé API", + "apiKeyPlaceholder": "Sélectionner une clé API", + "apiKeyAddNew": "Ajouter une clé API…", + "apiKeyNone": "Aucune clé API pour l'instant" } }, "codeBlock": { diff --git a/frontend/src/shared/i18n/locales/it.json b/frontend/src/shared/i18n/locales/it.json index ff1d4e9b2..66fb0b191 100644 --- a/frontend/src/shared/i18n/locales/it.json +++ b/frontend/src/shared/i18n/locales/it.json @@ -2736,6 +2736,10 @@ "uninstall": { "title": "Facoltativo — Disinstalla il Collector", "body": "Esegui questo comando sull'host in cui è installato il Collector per interrompere il servizio e rimuovere tutti i suoi file." + }, + "masterHeader": { + "title": "Intestazione di autorizzazione", + "body": "Includi questa intestazione Authorization in ogni richiesta inviata al Master. Il segreto di \"{{name}}\" viene rivelato una sola volta al momento della generazione della chiave API — copialo allora e incollalo al posto del segnaposto qui sotto." } }, "aix": { @@ -3374,7 +3378,11 @@ "sendToMaster": "Invia al Master", "enterpriseFeature": "Questa è una funzionalità Enterprise.", "addCollector": "Aggiungi Collector", - "addApiKey": "Aggiungi chiave API" + "addApiKey": "Aggiungi chiave API", + "apiKeyLabel": "Chiave API", + "apiKeyPlaceholder": "Seleziona una chiave API", + "apiKeyAddNew": "Aggiungi chiave API…", + "apiKeyNone": "Nessuna chiave API ancora" } }, "codeBlock": { diff --git a/frontend/src/shared/i18n/locales/pt.json b/frontend/src/shared/i18n/locales/pt.json index d777c2711..13732266e 100644 --- a/frontend/src/shared/i18n/locales/pt.json +++ b/frontend/src/shared/i18n/locales/pt.json @@ -1838,6 +1838,10 @@ "uninstall": { "title": "Opcional — Desinstalar o Collector", "body": "Execute este comando no host onde o Collector está instalado para parar o serviço e remover todos os seus arquivos." + }, + "masterHeader": { + "title": "Cabeçalho de autorização", + "body": "Inclua este cabeçalho Authorization em cada requisição enviada ao Master. O segredo de \"{{name}}\" é revelado apenas uma vez ao gerar a chave de API — copie-o naquele momento e cole no lugar do marcador abaixo." } }, "cisco": { @@ -3336,7 +3340,11 @@ "sendToMaster": "Enviar para o Master", "enterpriseFeature": "Este é um recurso Enterprise.", "addCollector": "Adicionar Collector", - "addApiKey": "Adicionar chave de API" + "addApiKey": "Adicionar chave de API", + "apiKeyLabel": "Chave de API", + "apiKeyPlaceholder": "Selecionar uma chave de API", + "apiKeyAddNew": "Adicionar chave de API…", + "apiKeyNone": "Nenhuma chave de API ainda" } }, "codeBlock": { diff --git a/frontend/src/shared/i18n/locales/ru.json b/frontend/src/shared/i18n/locales/ru.json index 0aa9d8d70..aef29e634 100644 --- a/frontend/src/shared/i18n/locales/ru.json +++ b/frontend/src/shared/i18n/locales/ru.json @@ -2618,6 +2618,10 @@ "uninstall": { "title": "Дополнительно — Удалить Collector", "body": "Выполните эту команду на хосте, где установлен Collector, чтобы остановить службу и удалить все её файлы." + }, + "masterHeader": { + "title": "Заголовок авторизации", + "body": "Включайте этот заголовок Authorization в каждый запрос, отправляемый на Master. Секрет ключа «{{name}}» отображается только один раз при его создании — скопируйте его тогда и вставьте вместо заполнителя ниже." } }, "aix": { @@ -3256,7 +3260,11 @@ "sendToMaster": "Отправить на Master", "enterpriseFeature": "Это функция Enterprise.", "addCollector": "Добавить Collector", - "addApiKey": "Добавить API-ключ" + "addApiKey": "Добавить API-ключ", + "apiKeyLabel": "API-ключ", + "apiKeyPlaceholder": "Выберите API-ключ", + "apiKeyAddNew": "Добавить API-ключ…", + "apiKeyNone": "Нет ни одного API-ключа" } }, "codeBlock": {