[Oblt Onboarding] Remove Custom Logs flow (#216053)

Closes https://github.com/elastic/kibana/issues/208025

This change deleted the "Stream log files" onboarding flow which is now
replaced by the Auto Detect flow.

| Before | After |
| --- | --- |
| ![CleanShot 2025-03-27 at 14 55
55@2x](https://github.com/user-attachments/assets/46a90769-8b3d-495f-b600-9a8f24964761)
| ![CleanShot 2025-03-27 at 14 56
28@2x](https://github.com/user-attachments/assets/e800cab8-cfd2-48e8-8d1a-8a41c037d532)
|


Changes made:
* Deleted UI components responsible for rendering the Custom Logs flow
* Deleted the definition for a custom card in the onboarding search
results
* Deleted API endpoints and supporting files used only by the Custom
Logs flow
* `/internal/observability_onboarding/logs/setup/environment` endpoint
was still used by the OTel Host flow, so it was moved to a dedicated
OTel route and pathname changed to
`/internal/observability_onboarding/otel_host/setup`
* Functionality of the `/internal/observability_onboarding/otel/api_key`
endpoint was merged into the above mentioned OTel route, so UI has to
make a single API request to get all the necessary information from the
server
* Deleted Scout UI tests for the Custom Logs flow
* Deleted API integration tests for the deleted endpoints
* API tests that we previously testing
`/internal/observability_onboarding/logs/flow` were converted to test
`/internal/observability_onboarding/flow'` used by the Auto Detect flow

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Mykola Harmash 2025-04-16 11:01:18 +02:00 committed by GitHub
parent ff8f7333c2
commit fc686f8a6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 97 additions and 2934 deletions

View file

@ -31679,15 +31679,6 @@
"xpack.monitoring.updateLicenseButtonLabel": "Mettre à jour la licence",
"xpack.monitoring.updateLicenseTitle": "Mettre à jour votre licence",
"xpack.monitoring.useAvailableLicenseDescription": "Si vous avez déjà une nouvelle licence, chargez-la maintenant.",
"xpack.observability_onboarding.apiKeyBanner.created": "Clé dAPI créée.",
"xpack.observability_onboarding.apiKeyBanner.created.description": "Noubliez pas de stocker cette information en lieu sûr. Elle ne sera plus affichée lorsque vous continuerez.",
"xpack.observability_onboarding.apiKeyBanner.failed": "Échec de la création de clé dAPI.",
"xpack.observability_onboarding.apiKeyBanner.failed.description": "Un problème est survenu : {message}",
"xpack.observability_onboarding.apiKeyBanner.field.copyButton": "Copier dans le presse-papiers",
"xpack.observability_onboarding.apiKeyBanner.field.label": "Clé d'API",
"xpack.observability_onboarding.apiKeyBanner.loading": "Création dune clé dAPI",
"xpack.observability_onboarding.apiKeyBanner.noPermissions": "Lutilisateur ne dispose pas dautorisations pour créer une clé dAPI.",
"xpack.observability_onboarding.apiKeyBanner.noPermissions.description": "Les privilèges de cluster requis sont {requiredClusterPrivileges} et les privilèges d'index requis sont {requiredIndexPrivileges} pour les index {indices}. Veuillez ajouter tous les privilèges requis au rôle de l'utilisateur authentifié.",
"xpack.observability_onboarding.asyncLoadFailureCallout.buttonContent": "Réessayer",
"xpack.observability_onboarding.asyncLoadFailureCallout.copy": "Certains éléments requis n'ont pas pu être chargés.",
"xpack.observability_onboarding.asyncLoadFailureCallout.title": "Échec de chargement",
@ -31712,28 +31703,8 @@
"xpack.observability_onboarding.autoDetectPanel.visualizeYourDataLabel": "Visualiser vos données",
"xpack.observability_onboarding.autoDetectPanel.yourDataIsReadyToExploreLabel": "Vos données sont prêtes à lexploration !",
"xpack.observability_onboarding.breadcrumbs.onboarding": "Intégration",
"xpack.observability_onboarding.configureLogs.advancedSettings": "Paramètres avancés",
"xpack.observability_onboarding.configureLogs.customConfig": "Configurations personnalisées",
"xpack.observability_onboarding.configureLogs.customConfig.helper": "Ajoutez des options de configuration YAML à la configuration de votre agent. Faites attention, car l'utilisation de cette fonctionnalité peut endommager votre fichier de configuration. {learnMoreLink}",
"xpack.observability_onboarding.configureLogs.description": "Configurer les entrées",
"xpack.observability_onboarding.configureLogs.learnMore": "En savoir plus",
"xpack.observability_onboarding.configureLogs.logFile.addRow": "Ajouter une ligne",
"xpack.observability_onboarding.configureLogs.logFile.helperText": "Vous pouvez utiliser un chemin d'accès ou un modèle de fichier.",
"xpack.observability_onboarding.configureLogs.logFile.path": "Chemin de fichier log",
"xpack.observability_onboarding.configureLogs.logFile.placeholder": "Exemple : /var/log/application.*",
"xpack.observability_onboarding.configureLogs.namespace": "Espace de nom",
"xpack.observability_onboarding.configureLogs.namespace.helper": "Ce paramètre modifie le nom du flux de données de l'intégration. {learnMoreLink}",
"xpack.observability_onboarding.configureLogs.namespace.placeholder": "Espace de nom",
"xpack.observability_onboarding.configureLogs.namespace.tooltip": "Fournir un espace de nom pour personnaliser le groupement de vos logs. Valeur par défaut de l'espace de nom par défaut.",
"xpack.observability_onboarding.configureLogs.serviceName": "Nom de service",
"xpack.observability_onboarding.configureLogs.serviceName.helper": "Nom du service à partir duquel vos données sont collectées.",
"xpack.observability_onboarding.configureLogs.serviceName.placeholder": "Nommer votre service",
"xpack.observability_onboarding.configureLogs.serviceName.tooltip": "Fournir un nom de service permet aux services distribués qui tournent sur plusieurs hôtes de mettre en corrélation les instances liées.",
"xpack.observability_onboarding.configureLogsContent.euiButtonIcon.deleteLabel": "Supprimer",
"xpack.observability_onboarding.configureLogsContent.stepModal.collectCustomLogsLabel": "Collectez des logs personnalisés",
"xpack.observability_onboarding.copyToClipboardButton.copyToClipboardButtonLabel": "Copier dans le presse-papiers",
"xpack.observability_onboarding.createStackInAWSConsole.createFirehoseStreamInAWSConsoleButtonLabel": "Créer un flux Firehose dans AWS",
"xpack.observability_onboarding.customLogs.installShipper.title": "Installer l'agent de transfert pour collecter les logs",
"xpack.observability_onboarding.dataIngestStatus.findAllPremadeAssetsTextLabel": "Trouvez toutes les ressources prêtes à l'emploi {viewAllAssetsLink}",
"xpack.observability_onboarding.dataIngestStatus.troubleshootingLinkText": "Ouvrir la documentation",
"xpack.observability_onboarding.dataIngestStatus.troubleshootingTextLabel": "Vous trouverez plus d'informations et des solutions de résolution des problèmes dans notre documentation. {troubleshootingLink}",
@ -31800,27 +31771,18 @@
"xpack.observability_onboarding.insight.feedbackButtons.negative": "Non",
"xpack.observability_onboarding.insight.feedbackButtons.positive": "Oui",
"xpack.observability_onboarding.insight.feedbackButtons.title": "Cela a-t-il été utile ?",
"xpack.observability_onboarding.inspect.h3.pathLabel": "Chemin",
"xpack.observability_onboarding.inspect.h3.stateLabel": "État",
"xpack.observability_onboarding.inspect.h3.usageLabel": "Utilisation",
"xpack.observability_onboarding.inspect.stepPanel.inspectWizardLabel": "Inspecter l'assistant",
"xpack.observability_onboarding.installElasticAgent.configStep.auto.description": "La configuration agent ci-dessous sera téléchargée par le script d'installation et écrite dans ({configPath}). Ceci écrasera toute configuration agent existante.",
"xpack.observability_onboarding.installElasticAgent.configStep.downloadConfigButton": "Téléchargez le fichier de configuration",
"xpack.observability_onboarding.installElasticAgent.configStep.manual.description": "Ajoutez la configuration suivante à {configPath} sur l'hôte où vous avez installé Elastic Agent.",
"xpack.observability_onboarding.installElasticAgent.configStep.yamlCodeBlockdescription": "Configuration YAML d'Elastic Agent",
"xpack.observability_onboarding.installElasticAgent.configureStep.title": "Configurer Elastic Agent",
"xpack.observability_onboarding.installElasticAgent.description": "Pour collecter les données depuis votre système et les intégrer à Elastic, vous devez d'abord installer un outil de transfert sur la machine qui génère les logs. Dans ce cas, l'outil de transfert est un agent développé par Elastic.",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig": "Télécharger automatiquement la configuration de l'agent",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig.overwriteWarning": "Le téléchargement automatique de la configuration de l'agent écrase toute configuration d'agent existante chez votre hôte.",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig.tooltip": "Activer pour ajouter une chaîne au bloc de code suivant pour télécharger la configuration standard de l'agent sur votre hôte pendant l'installation. Désactiver pour configurer manuellement l'agent à la prochaine étape.",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform": "Choisissez une plateforme",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.linux": "Linux",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.macOS": "MacOS",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.windows": "Windows",
"xpack.observability_onboarding.installElasticAgent.installStep.description": "Sélectionnez votre plateforme et exécutez la commande install dans votre terminal pour enregistrer, puis démarrez Elastic Agent. Faites ceci pour chaque hôte. Vérifiez {hostRequirementsLink} avant l'installation.",
"xpack.observability_onboarding.installElasticAgent.installStep.hostRequirements": "exigences d'hébergement et autres options d'installation",
"xpack.observability_onboarding.installElasticAgent.installStep.title": "Installation d'Elastic Agent",
"xpack.observability_onboarding.installElasticAgent.integrationSuccessCallout.title": "Intégration {integrationName} installée.",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.completedTitle": "La configuration Elastic Agent est écrite dans {configPath}",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.incompleteTitle": "Configurer l'agent",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.loadingTitle": "Téléchargement de la configuration Elastic Agent",
@ -31836,9 +31798,6 @@
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.completedTitle": "Connecté à Elastic Agent",
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.incompleteTitle": "Se connecter à Elastic Agent",
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.loadingTitle": "Connexion à Elastic Agent",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.completedTitle": "Les logs sont en cours de transfert.",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.incompleteTitle": "Transfert des logs dans Elastic Observability",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.loadingTitle": "En attente du transfert des logs…",
"xpack.observability_onboarding.installElasticAgent.troubleshooting": "Résolution des problèmes",
"xpack.observability_onboarding.installIntegration.error.unauthorized": "Le privilège Kibana {requiredKibanaPrivileges} requis est manquant. Veuillez ajouter le privilège requis au rôle de l'utilisateur authentifié.",
"xpack.observability_onboarding.installOtelCollector.configStep.copyCommand": "Copier dans le presse-papiers",
@ -31898,7 +31857,6 @@
"xpack.observability_onboarding.packageList.uploadFileTitle": "Charger un fichier",
"xpack.observability_onboarding.progressCallout.li.otherLabel": "Autres (les logs non pris en charge seront stockés dans un index Firehose générique).",
"xpack.observability_onboarding.progressCallout.strong.allServicesWeCanLabel": "Tous les services que nous pouvons détecter",
"xpack.observability_onboarding.steps.exploreLogs": "Explorer les logs",
"xpack.observability_onboarding.useCustomCardsForCategory.apmDescription": "Collectez des traces distribuées à partir de vos applications avec Elastic APM",
"xpack.observability_onboarding.useCustomCardsForCategory.apmOtelDescription": "Collectez des traces distribuées avec OpenTelemetry",
"xpack.observability_onboarding.useCustomCardsForCategory.apmOtelTitle": "OpenTelemetry",

View file

@ -31658,15 +31658,6 @@
"xpack.monitoring.updateLicenseButtonLabel": "ライセンスを更新",
"xpack.monitoring.updateLicenseTitle": "ライセンスの更新",
"xpack.monitoring.useAvailableLicenseDescription": "すでに新しいライセンスがある場合は、今すぐアップロードしてください。",
"xpack.observability_onboarding.apiKeyBanner.created": "APIキーが作成されました。",
"xpack.observability_onboarding.apiKeyBanner.created.description": "必ずこの情報を安全な場所に保管してください。続行すると、表示されなくなります。",
"xpack.observability_onboarding.apiKeyBanner.failed": "APIキーを作成できませんでした。",
"xpack.observability_onboarding.apiKeyBanner.failed.description": "問題が発生しました:{message}",
"xpack.observability_onboarding.apiKeyBanner.field.copyButton": "クリップボードにコピー",
"xpack.observability_onboarding.apiKeyBanner.field.label": "APIキー",
"xpack.observability_onboarding.apiKeyBanner.loading": "APIキーを作成中",
"xpack.observability_onboarding.apiKeyBanner.noPermissions": "ユーザーにはAPIキーを作成する権限がありません。",
"xpack.observability_onboarding.apiKeyBanner.noPermissions.description": "必要なクラスター特権は{requiredClusterPrivileges}です。インデックス{indices}で必要なインデックス特権は{requiredIndexPrivileges}です。認証されたユーザーのロールにすべての必要な特権を追加してください。",
"xpack.observability_onboarding.asyncLoadFailureCallout.buttonContent": "再試行",
"xpack.observability_onboarding.asyncLoadFailureCallout.copy": "一部の必要な要素を読み込めませんでした。",
"xpack.observability_onboarding.asyncLoadFailureCallout.title": "読み込み失敗",
@ -31691,28 +31682,8 @@
"xpack.observability_onboarding.autoDetectPanel.visualizeYourDataLabel": "データを可視化する",
"xpack.observability_onboarding.autoDetectPanel.yourDataIsReadyToExploreLabel": "データを探索できます!",
"xpack.observability_onboarding.breadcrumbs.onboarding": "オンボーディング",
"xpack.observability_onboarding.configureLogs.advancedSettings": "高度な設定",
"xpack.observability_onboarding.configureLogs.customConfig": "カスタム構成",
"xpack.observability_onboarding.configureLogs.customConfig.helper": "エージェント構成にYAML構成オプションを追加します。この機能を使用すると、構成ファイルが破損する可能性があるため、注意してください。{learnMoreLink}",
"xpack.observability_onboarding.configureLogs.description": "入力を構成",
"xpack.observability_onboarding.configureLogs.learnMore": "詳細",
"xpack.observability_onboarding.configureLogs.logFile.addRow": "行の追加",
"xpack.observability_onboarding.configureLogs.logFile.helperText": "ファイルパスまたはパターンを使用できます。",
"xpack.observability_onboarding.configureLogs.logFile.path": "ログファイルパス",
"xpack.observability_onboarding.configureLogs.logFile.placeholder": "例:/var/log/application.*",
"xpack.observability_onboarding.configureLogs.namespace": "名前空間",
"xpack.observability_onboarding.configureLogs.namespace.helper": "この設定により、統合のデータストリームの名前が変更されます。{learnMoreLink}",
"xpack.observability_onboarding.configureLogs.namespace.placeholder": "名前空間",
"xpack.observability_onboarding.configureLogs.namespace.tooltip": "名前空間を指定して、ログのグループ化をカスタマイズします。デフォルト名前空間を設定します。",
"xpack.observability_onboarding.configureLogs.serviceName": "サービス名",
"xpack.observability_onboarding.configureLogs.serviceName.helper": "データが収集されるサービスの名前を設定します。",
"xpack.observability_onboarding.configureLogs.serviceName.placeholder": "サービス名を指定",
"xpack.observability_onboarding.configureLogs.serviceName.tooltip": "複数のホスト上で実行される分散サービスが関連するインスタンスを関連付けることができるように、サービス名を指定します。",
"xpack.observability_onboarding.configureLogsContent.euiButtonIcon.deleteLabel": "削除",
"xpack.observability_onboarding.configureLogsContent.stepModal.collectCustomLogsLabel": "カスタムログを収集",
"xpack.observability_onboarding.copyToClipboardButton.copyToClipboardButtonLabel": "クリップボードにコピー",
"xpack.observability_onboarding.createStackInAWSConsole.createFirehoseStreamInAWSConsoleButtonLabel": "AWSでFirehose Streamを作成",
"xpack.observability_onboarding.customLogs.installShipper.title": "ログを収集するためのシッパーをインストール",
"xpack.observability_onboarding.dataIngestStatus.findAllPremadeAssetsTextLabel": "{viewAllAssetsLink}ですぐに使えるすべての組み込みのアセットをご覧ください",
"xpack.observability_onboarding.dataIngestStatus.troubleshootingLinkText": "ドキュメントを開く",
"xpack.observability_onboarding.dataIngestStatus.troubleshootingTextLabel": "詳細とトラブルシューティングの解決策については、ドキュメントをご覧ください。{troubleshootingLink}",
@ -31779,27 +31750,18 @@
"xpack.observability_onboarding.insight.feedbackButtons.negative": "いいえ",
"xpack.observability_onboarding.insight.feedbackButtons.positive": "はい",
"xpack.observability_onboarding.insight.feedbackButtons.title": "役に立ちましたか。",
"xpack.observability_onboarding.inspect.h3.pathLabel": "パス",
"xpack.observability_onboarding.inspect.h3.stateLabel": "ステータス",
"xpack.observability_onboarding.inspect.h3.usageLabel": "使用方法",
"xpack.observability_onboarding.inspect.stepPanel.inspectWizardLabel": "検査ウィザード",
"xpack.observability_onboarding.installElasticAgent.configStep.auto.description": "次のエージェント構成が、インストールスクリプトによってダウンロードされ、({configPath})に書き込まれます。これにより、既存のエージェント構成がすべて上書きされます。",
"xpack.observability_onboarding.installElasticAgent.configStep.downloadConfigButton": "構成ファイルのダウンロード",
"xpack.observability_onboarding.installElasticAgent.configStep.manual.description": "Elasticエージェントをインストールしたホストの{configPath}に以下の構成を追加します。",
"xpack.observability_onboarding.installElasticAgent.configStep.yamlCodeBlockdescription": "Elasticエージェントyaml構成",
"xpack.observability_onboarding.installElasticAgent.configureStep.title": "Elasticエージェントの構成",
"xpack.observability_onboarding.installElasticAgent.description": "システムからデータを収集してElasticにストリーミングするには、まずログを生成するコンピューターでシッピングツールをインストールする必要があります。この場合、シッピングツールはElasticが開発したエージェントです。",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig": "エージェント構成を自動的にダウンロード",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig.overwriteWarning": "エージェント構成を自動的にダウンロードすると、ホスト上の既存のエージェント構成が上書きされます。",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig.tooltip": "オンにすると、インストール中にエージェントの標準構成をホストにダウンロードする以下のコードブロックに文字列が追加されます。オフにすると、次のステップでエージェントを手動で構成することができます。",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform": "プラットフォームを選択",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.linux": "Linux",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.macOS": "MacOS",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.windows": "Windows",
"xpack.observability_onboarding.installElasticAgent.installStep.description": "プラットフォームを選択し、ターミナルでinstallコマンドを実行してElasticエージェントを登録、起動します。各ホストでこの手順を実行します。インストール前に{hostRequirementsLink}を確認してください。",
"xpack.observability_onboarding.installElasticAgent.installStep.hostRequirements": "ホスト要件とその他のインストールオプション",
"xpack.observability_onboarding.installElasticAgent.installStep.title": "Elasticエージェントをインストール",
"xpack.observability_onboarding.installElasticAgent.integrationSuccessCallout.title": "{integrationName}統合がインストールされました。",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.completedTitle": "Elasticエージェント構成が{configPath}に書き込まれました",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.incompleteTitle": "エージェントの構成",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.loadingTitle": "Elasticエージェント構成をダウンロード中",
@ -31815,9 +31777,6 @@
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.completedTitle": "Elasticエージェントに接続しました",
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.incompleteTitle": "Elasticエージェントに接続",
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.loadingTitle": "Elasticエージェントに接続中",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.completedTitle": "ログを送信中です。",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.incompleteTitle": "Elasticオブザーバビリティにログを送信",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.loadingTitle": "ログの送信を待機中...",
"xpack.observability_onboarding.installElasticAgent.troubleshooting": "トラブルシューティング",
"xpack.observability_onboarding.installIntegration.error.unauthorized": "必要なkibana権限{requiredKibanaPrivileges}がありません。認証されたユーザーのロールに必要な権限を追加してください。",
"xpack.observability_onboarding.installOtelCollector.configStep.copyCommand": "クリップボードにコピー",
@ -31877,7 +31836,6 @@
"xpack.observability_onboarding.packageList.uploadFileTitle": "ファイルをアップロード",
"xpack.observability_onboarding.progressCallout.li.otherLabel": "その他サポートされていないログは、一般的なFirehoseインデックスに格納されます。",
"xpack.observability_onboarding.progressCallout.strong.allServicesWeCanLabel": "検出可能なすべてのサービス",
"xpack.observability_onboarding.steps.exploreLogs": "ログを探索",
"xpack.observability_onboarding.useCustomCardsForCategory.apmDescription": "Elastic APMを使用してアプリケーションから分散トレースを収集",
"xpack.observability_onboarding.useCustomCardsForCategory.apmOtelDescription": "OpenTelemetryで分散トレースを収集",
"xpack.observability_onboarding.useCustomCardsForCategory.apmOtelTitle": "OpenTelemetry",

View file

@ -31713,15 +31713,6 @@
"xpack.monitoring.updateLicenseButtonLabel": "更新许可证",
"xpack.monitoring.updateLicenseTitle": "更新您的许可证",
"xpack.monitoring.useAvailableLicenseDescription": "如果您已经持有新的许可证,请立即上传。",
"xpack.observability_onboarding.apiKeyBanner.created": "已创建 API 密钥。",
"xpack.observability_onboarding.apiKeyBanner.created.description": "记得将此信息存储在安全位置。在您继续后,将不再显示该信息。",
"xpack.observability_onboarding.apiKeyBanner.failed": "无法创建 API 密钥。",
"xpack.observability_onboarding.apiKeyBanner.failed.description": "出现问题:{message}",
"xpack.observability_onboarding.apiKeyBanner.field.copyButton": "复制到剪贴板",
"xpack.observability_onboarding.apiKeyBanner.field.label": "API 密钥",
"xpack.observability_onboarding.apiKeyBanner.loading": "正在创建 API 密钥",
"xpack.observability_onboarding.apiKeyBanner.noPermissions": "用户无权创建 API 密钥。",
"xpack.observability_onboarding.apiKeyBanner.noPermissions.description": "索引 {indices} 的所需集群权限为 {requiredClusterPrivileges},所需索引权限为 {requiredIndexPrivileges},请为已通过身份验证的用户角色添加所有所需权限。",
"xpack.observability_onboarding.asyncLoadFailureCallout.buttonContent": "重试",
"xpack.observability_onboarding.asyncLoadFailureCallout.copy": "无法加载某些必需元素。",
"xpack.observability_onboarding.asyncLoadFailureCallout.title": "加载失败",
@ -31746,28 +31737,8 @@
"xpack.observability_onboarding.autoDetectPanel.visualizeYourDataLabel": "可视化数据",
"xpack.observability_onboarding.autoDetectPanel.yourDataIsReadyToExploreLabel": "数据已准确就绪,可供浏览!",
"xpack.observability_onboarding.breadcrumbs.onboarding": "载入",
"xpack.observability_onboarding.configureLogs.advancedSettings": "高级设置",
"xpack.observability_onboarding.configureLogs.customConfig": "定制配置",
"xpack.observability_onboarding.configureLogs.customConfig.helper": "将 YAML 配置选项添加到代理配置。请务必谨慎使用此功能,因为它可能会破坏配置文件。{learnMoreLink}",
"xpack.observability_onboarding.configureLogs.description": "配置输入",
"xpack.observability_onboarding.configureLogs.learnMore": "了解详情",
"xpack.observability_onboarding.configureLogs.logFile.addRow": "添加行",
"xpack.observability_onboarding.configureLogs.logFile.helperText": "可以使用文件路径或模式。",
"xpack.observability_onboarding.configureLogs.logFile.path": "日志文件路径",
"xpack.observability_onboarding.configureLogs.logFile.placeholder": "示例:/var/log/application.*",
"xpack.observability_onboarding.configureLogs.namespace": "命名空间",
"xpack.observability_onboarding.configureLogs.namespace.helper": "此设置将更改集成的数据流的名称。{learnMoreLink}",
"xpack.observability_onboarding.configureLogs.namespace.placeholder": "命名空间",
"xpack.observability_onboarding.configureLogs.namespace.tooltip": "提供命名空间以定制日志分组。默认采用默认命名空间。",
"xpack.observability_onboarding.configureLogs.serviceName": "服务名称",
"xpack.observability_onboarding.configureLogs.serviceName.helper": "命名将从中收集数据的服务。",
"xpack.observability_onboarding.configureLogs.serviceName.placeholder": "为服务提供名称",
"xpack.observability_onboarding.configureLogs.serviceName.tooltip": "提供服务名称以便在多个主机上运行的分布式服务关联相关实例。",
"xpack.observability_onboarding.configureLogsContent.euiButtonIcon.deleteLabel": "删除",
"xpack.observability_onboarding.configureLogsContent.stepModal.collectCustomLogsLabel": "收集定制日志",
"xpack.observability_onboarding.copyToClipboardButton.copyToClipboardButtonLabel": "复制到剪贴板",
"xpack.observability_onboarding.createStackInAWSConsole.createFirehoseStreamInAWSConsoleButtonLabel": "在 AWS 中创建 Firehose 流",
"xpack.observability_onboarding.customLogs.installShipper.title": "安装采集器以收集日志",
"xpack.observability_onboarding.dataIngestStatus.findAllPremadeAssetsTextLabel": "查找所有可随时使用 {viewAllAssetsLink} 的现成资产",
"xpack.observability_onboarding.dataIngestStatus.troubleshootingLinkText": "打开文档",
"xpack.observability_onboarding.dataIngestStatus.troubleshootingTextLabel": "在我们的文档中查找更多详情和故障排除解决方案。{troubleshootingLink}",
@ -31834,27 +31805,18 @@
"xpack.observability_onboarding.insight.feedbackButtons.negative": "否",
"xpack.observability_onboarding.insight.feedbackButtons.positive": "是",
"xpack.observability_onboarding.insight.feedbackButtons.title": "这是否有帮助?",
"xpack.observability_onboarding.inspect.h3.pathLabel": "路径",
"xpack.observability_onboarding.inspect.h3.stateLabel": "状态",
"xpack.observability_onboarding.inspect.h3.usageLabel": "用法",
"xpack.observability_onboarding.inspect.stepPanel.inspectWizardLabel": "检查向导",
"xpack.observability_onboarding.installElasticAgent.configStep.auto.description": "以下代理配置将由安装脚本下载并写入到 ({configPath})。这会覆盖任何现有代理配置。",
"xpack.observability_onboarding.installElasticAgent.configStep.downloadConfigButton": "下载配置文件",
"xpack.observability_onboarding.installElasticAgent.configStep.manual.description": "将以下配置添加到已安装 Elastic 代理的主机上的{configPath}。",
"xpack.observability_onboarding.installElasticAgent.configStep.yamlCodeBlockdescription": "Elastic 代理 yaml 配置",
"xpack.observability_onboarding.installElasticAgent.configureStep.title": "配置 Elastic 代理",
"xpack.observability_onboarding.installElasticAgent.description": "要从您的系统收集数据并将其流式传输到 Elastic您首先需要在生成日志的机器上安装传输工具。在此情况下传输工具为由 Elastic 开发的代理。",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig": "自动下载代理的配置",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig.overwriteWarning": "自动下载代理配置将覆盖您主机上的任何现有代理配置。",
"xpack.observability_onboarding.installElasticAgent.installStep.autoDownloadConfig.tooltip": "打开可将一个字符串添加到以下代码块,该代码会在安装期间将代理的标准配置下载到您的主机上。关闭可在下一步中手动配置代理。",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform": "选择平台",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.linux": "Linux",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.macOS": "MacOS",
"xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.windows": "Windows",
"xpack.observability_onboarding.installElasticAgent.installStep.description": "选择平台并在终端中运行安装命令,以注册并启动 Elastic 代理。对每台主机执行此操作。请在安装之前复查{hostRequirementsLink}。",
"xpack.observability_onboarding.installElasticAgent.installStep.hostRequirements": "主机要求和其他安装选项",
"xpack.observability_onboarding.installElasticAgent.installStep.title": "安装 Elastic 代理",
"xpack.observability_onboarding.installElasticAgent.integrationSuccessCallout.title": "已安装 {integrationName} 集成。",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.completedTitle": "Elastic 代理配置已写入到 {configPath}",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.incompleteTitle": "配置代理",
"xpack.observability_onboarding.installElasticAgent.progress.eaConfig.loadingTitle": "正在下载 Elastic 代理配置",
@ -31870,9 +31832,6 @@
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.completedTitle": "已连接到 Elastic 代理",
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.incompleteTitle": "连接到 Elastic 代理",
"xpack.observability_onboarding.installElasticAgent.progress.eaStatus.loadingTitle": "正在连接 Elastic 代理",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.completedTitle": "正在传输日志!",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.incompleteTitle": "传输日志到 Elastic Observability",
"xpack.observability_onboarding.installElasticAgent.progress.logsIngest.loadingTitle": "等待传输日志......",
"xpack.observability_onboarding.installElasticAgent.troubleshooting": "故障排除",
"xpack.observability_onboarding.installIntegration.error.unauthorized": "缺失所需的 Kibana 权限 {requiredKibanaPrivileges},请将所需权限添加到已通过身份验证的用户的角色。",
"xpack.observability_onboarding.installOtelCollector.configStep.copyCommand": "复制到剪贴板",
@ -31932,7 +31891,6 @@
"xpack.observability_onboarding.packageList.uploadFileTitle": "上传文件",
"xpack.observability_onboarding.progressCallout.li.otherLabel": "其他(不受支持的日志将存储在常规 Firehose 索引中)。",
"xpack.observability_onboarding.progressCallout.strong.allServicesWeCanLabel": "我们可以检测的所有服务",
"xpack.observability_onboarding.steps.exploreLogs": "浏览日志",
"xpack.observability_onboarding.useCustomCardsForCategory.apmDescription": "使用 Elastic APM 从您的应用程序中收集分布式跟踪",
"xpack.observability_onboarding.useCustomCardsForCategory.apmOtelDescription": "使用 OpenTelemetry 收集分布式跟踪",
"xpack.observability_onboarding.useCustomCardsForCategory.apmOtelTitle": "OpenTelemetry",

View file

@ -1,96 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`generateCustomLogsYml should return a basic yml configuration 1`] = `
"outputs:
default:
type: elasticsearch
hosts:
- http://localhost:9200
api_key: elastic:changeme
inputs:
- id: my-logs-id
type: logfile
data_stream:
namespace: default
streams:
- id: logs-onboarding-my-dataset
data_stream:
dataset: my-dataset
paths:
- /my-service.logs
"
`;
exports[`generateCustomLogsYml should return a yml configuration with customConfigurations 1`] = `
"outputs:
default:
type: elasticsearch
hosts:
- http://localhost:9200
api_key: elastic:changeme
inputs:
- id: my-logs-id
type: logfile
data_stream:
namespace: default
streams:
- id: logs-onboarding-my-dataset
data_stream:
dataset: my-dataset
paths:
- /my-service.logs
agent.retry:
enabled: true
retriesCount: 3
agent.monitoring:
metrics: false
"
`;
exports[`generateCustomLogsYml should return a yml configuration with multiple logFilePaths 1`] = `
"outputs:
default:
type: elasticsearch
hosts:
- http://localhost:9200
api_key: elastic:changeme
inputs:
- id: my-logs-id
type: logfile
data_stream:
namespace: default
streams:
- id: logs-onboarding-my-dataset
data_stream:
dataset: my-dataset
paths:
- /my-service-1.logs
- /my-service-2.logs
"
`;
exports[`generateCustomLogsYml should return a yml configuration with service name 1`] = `
"outputs:
default:
type: elasticsearch
hosts:
- http://localhost:9200
api_key: elastic:changeme
inputs:
- id: my-logs-id
type: logfile
data_stream:
namespace: default
streams:
- id: logs-onboarding-my-dataset
data_stream:
dataset: my-dataset
paths:
- /my-service.logs
processors:
- add_fields:
target: service
fields:
name: my-service
"
`;

View file

@ -1,63 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { dump } from 'js-yaml';
import { generateCustomLogsYml } from './generate_custom_logs_yml';
const baseMockConfig = {
datasetName: 'my-dataset',
namespace: 'default',
logFilePaths: ['/my-service.logs'],
apiKey: 'elastic:changeme',
esHost: ['http://localhost:9200'],
logfileId: 'my-logs-id',
};
describe('generateCustomLogsYml', () => {
it('should return a basic yml configuration', () => {
const result = generateCustomLogsYml(baseMockConfig);
expect(result).toMatchSnapshot();
});
it('should return a yml configuration with multiple logFilePaths', () => {
const mockConfig = {
...baseMockConfig,
logFilePaths: ['/my-service-1.logs', '/my-service-2.logs'],
};
const result = generateCustomLogsYml(mockConfig);
expect(result).toMatchSnapshot();
});
it('should return a yml configuration with service name', () => {
const mockConfig = {
...baseMockConfig,
serviceName: 'my-service',
};
const result = generateCustomLogsYml(mockConfig);
expect(result).toMatchSnapshot();
});
it('should return a yml configuration with customConfigurations', () => {
const mockConfig = {
...baseMockConfig,
customConfigurations: dump({
['agent.retry']: {
enabled: true,
retriesCount: 3,
},
['agent.monitoring']: {
metrics: false,
},
}),
};
const result = generateCustomLogsYml(mockConfig);
expect(result).toMatchSnapshot();
});
});

View file

@ -1,72 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { dump, load } from 'js-yaml';
export const generateCustomLogsYml = ({
datasetName = '',
serviceName,
namespace = '',
customConfigurations,
logFilePaths = [],
apiKey,
esHost,
logfileId,
}: {
datasetName?: string;
serviceName?: string;
namespace?: string;
customConfigurations?: string;
logFilePaths?: string[];
apiKey: string;
esHost: string[];
logfileId: string;
}) => {
const customConfigYaml = load(customConfigurations ?? '');
const processors = [
{
add_fields: {
target: 'service',
fields: {
name: serviceName,
},
},
},
];
return dump({
...{
outputs: {
default: {
type: 'elasticsearch',
hosts: esHost,
api_key: apiKey,
},
},
inputs: [
{
id: logfileId,
type: 'logfile',
data_stream: {
namespace,
},
streams: [
{
id: `logs-onboarding-${datasetName}`,
data_stream: {
dataset: datasetName,
},
paths: logFilePaths,
...(serviceName ? { processors } : {}),
},
],
},
],
},
...customConfigYaml,
});
};

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './custom_logs/generate_custom_logs_yml';

View file

@ -1,15 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export type EaInstallProgressStepId =
| 'ea-download'
| 'ea-extract'
| 'ea-install'
| 'ea-status'
| 'ea-config';
export type LogsFlowProgressStepId = EaInstallProgressStepId | 'logs-ingest';

View file

@ -35,7 +35,7 @@ export const OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT: ObservabilityOnboardingTe
type: 'keyword',
_meta: {
description:
"The current onboarding flow type user is going through (e.g. 'autoDetect', 'logFiles', 'kubernetes'). If not present, user is on the landing screen.",
"The current onboarding flow type user is going through (e.g. 'autoDetect', 'kubernetes'). If not present, user is on the landing screen.",
},
},
flow_id: {

View file

@ -12,7 +12,6 @@ import { useLocation } from 'react-router-dom-v5-compat';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import {
AutoDetectPage,
CustomLogsPage,
KubernetesPage,
LandingPage,
OtelLogsPage,
@ -44,9 +43,6 @@ export function ObservabilityOnboardingFlow() {
<Route path="/auto-detect">
<AutoDetectPage />
</Route>
<Route path="/customLogs">
<CustomLogsPage />
</Route>
<Route path="/kubernetes">
<KubernetesPage />
</Route>

View file

@ -39,7 +39,6 @@ export function useCustomCards(
history,
`/otel-kubernetes/${location.search}`
);
const { href: customLogsUrl } = reactRouterNavigate(history, `/customLogs/${location.search}`);
const { href: firehoseUrl } = reactRouterNavigate(history, `/firehose/${location.search}`);
const apmUrl = `${getUrlForApp?.('apm')}/${isServerless ? 'onboarding' : 'tutorial'}`;
@ -393,23 +392,6 @@ export function useCustomCards(
integration: '',
isCollectionCard: false,
},
{
id: 'custom-logs',
type: 'virtual',
title: 'Stream log files',
description: 'Stream any logs into Elastic in a simple way and explore their data',
name: 'custom-logs-virtual',
categories: ['observability'],
icons: [
{
type: 'eui',
src: 'filebeatApp',
},
],
url: customLogsUrl,
version: '',
integration: '',
},
/**
* The new Firehose card should only be visible on Cloud
* as Firehose integration requires additional proxy,

View file

@ -1,18 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { PageTemplate } from './template';
import { BackButton } from '../shared/back_button';
import { CustomLogsPanel } from '../quickstart_flows/custom_logs';
export const CustomLogsPage = () => (
<PageTemplate>
<BackButton />
<CustomLogsPanel />
</PageTemplate>
);

View file

@ -6,7 +6,6 @@
*/
export { AutoDetectPage } from './auto_detect';
export { CustomLogsPage } from './custom_logs';
export { KubernetesPage } from './kubernetes';
export { OtelKubernetesPage } from './otel_kubernetes';
export { LandingPage } from './landing';

View file

@ -1,162 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
EuiButtonIcon,
EuiCallOut,
EuiCopy,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
} from '@elastic/eui';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import { i18n } from '@kbn/i18n';
import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public';
import React from 'react';
import { APIReturnType } from '../../../services/rest/create_call_api';
type ApiKeyPayload = APIReturnType<'POST /internal/observability_onboarding/logs/flow'>;
export type HasPrivileges = boolean;
export function ApiKeyBanner({
hasPrivileges = true,
status,
payload,
error,
}: {
hasPrivileges?: boolean;
status: FETCH_STATUS;
payload?: Partial<ApiKeyPayload>;
error?: IHttpFetchError<ResponseErrorBody>;
}) {
const loadingCallout = (
<EuiCallOut
title={
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
<EuiFlexItem>
{i18n.translate('xpack.observability_onboarding.apiKeyBanner.loading', {
defaultMessage: 'Creating API Key',
})}
</EuiFlexItem>
</EuiFlexGroup>
}
color="primary"
data-test-subj="obltOnboardingLogsCreatingApiKey"
/>
);
const apiKeySuccessCallout = (
<EuiCallOut
title={i18n.translate('xpack.observability_onboarding.apiKeyBanner.created', {
defaultMessage: 'API Key created.',
})}
color="success"
iconType="check"
data-test-subj="obltOnboardingLogsApiKeyCreated"
>
<p>
{i18n.translate('xpack.observability_onboarding.apiKeyBanner.created.description', {
defaultMessage:
'Remember to store this information in a safe place. It wont be displayed anymore after you continue.',
})}
</p>
<EuiFieldText
data-test-subj="apmAgentKeyCallOutFieldText"
readOnly
value={payload?.apiKeyEncoded}
aria-label={i18n.translate('xpack.observability_onboarding.apiKeyBanner.field.label', {
defaultMessage: 'Api Key',
})}
append={
<EuiCopy textToCopy={payload?.apiKeyEncoded ?? ''}>
{(copy) => (
<EuiButtonIcon
data-test-subj="observabilityOnboardingApiKeySuccessCalloutButton"
iconType="copyClipboard"
onClick={copy}
color="primary"
css={{
'> svg.euiIcon': {
borderRadius: '0 !important',
},
}}
aria-label={i18n.translate(
'xpack.observability_onboarding.apiKeyBanner.field.copyButton',
{
defaultMessage: 'Copy to clipboard',
}
)}
/>
)}
</EuiCopy>
}
/>
</EuiCallOut>
);
const apiKeyFailureCallout = (
<EuiCallOut
title={i18n.translate('xpack.observability_onboarding.apiKeyBanner.failed', {
defaultMessage: 'Failed to create API key.',
})}
color="danger"
iconType="error"
data-test-subj="obltOnboardingLogsApiKeyCreationFailed"
>
<p>
{i18n.translate('xpack.observability_onboarding.apiKeyBanner.failed.description', {
defaultMessage: 'Something went wrong: {message}',
values: {
message: error?.body?.message,
},
})}
</p>
</EuiCallOut>
);
const noPermissionsCallout = (
<EuiCallOut
title={i18n.translate('xpack.observability_onboarding.apiKeyBanner.noPermissions', {
defaultMessage: 'User does not have permissions to create API key.',
})}
color="warning"
iconType="warning"
data-test-subj="obltOnboardingLogsApiKeyCreationNoPrivileges"
>
<p>
{i18n.translate('xpack.observability_onboarding.apiKeyBanner.noPermissions.description', {
defaultMessage:
'Required cluster privileges are {requiredClusterPrivileges} and required index privileges are {requiredIndexPrivileges} for indices {indices}, please add all required privileges to the role of the authenticated user.',
values: {
requiredClusterPrivileges: "['monitor', 'manage_own_api_key']",
requiredIndexPrivileges: "['auto_configure', 'create_doc']",
indices: "['logs-*-*', 'metrics-*-*']",
},
})}
</p>
</EuiCallOut>
);
if (!hasPrivileges) {
return noPermissionsCallout;
}
if (status === FETCH_STATUS.SUCCESS) {
return apiKeySuccessCallout;
}
if (status === FETCH_STATUS.FAILURE) {
return apiKeyFailureCallout;
}
return loadingCallout;
}

View file

@ -1,402 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
EuiAccordion,
EuiButtonEmpty,
EuiButtonIcon,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiForm,
EuiFormRow,
EuiHorizontalRule,
EuiIconTip,
EuiLink,
EuiSpacer,
EuiTextArea,
useEuiFontSize,
useEuiTheme,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useCallback, useState } from 'react';
import {
ConnectedCustomIntegrationsButton,
ConnectedCustomIntegrationsForm,
useConsumerCustomIntegrations,
CustomIntegrationsProvider,
Callbacks,
} from '@kbn/custom-integrations';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useWizard } from '.';
import { OptionalFormRow } from '../shared/optional_form_row';
import { StepModal } from '../shared/step_panel';
import { getFilename } from './get_filename';
const customIntegrationsTestSubjects = {
create: {
integrationName: 'obltOnboardingCustomLogsIntegrationsName',
datasetName: 'obltOnboardingCustomLogsDatasetName',
errorCallout: {
callout: 'obltOnboardingCustomIntegrationErrorCallout',
},
},
button: 'obltOnboardingCustomLogsContinue',
};
export function ConfigureLogs() {
const {
services: { http },
} = useKibana();
const { goToStep, setState, getState } = useWizard();
const { integrationName, datasetName, lastCreatedIntegrationOptions } = getState();
const onIntegrationCreation: Callbacks['onIntegrationCreation'] = (integrationOptions) => {
const { integrationName: createdIntegrationName, datasets: createdDatasets } =
integrationOptions;
setState((state) => ({
...state,
integrationName: createdIntegrationName,
datasetName: createdDatasets[0].name,
lastCreatedIntegrationOptions: integrationOptions,
}));
goToStep('installElasticAgent');
};
return (
<CustomIntegrationsProvider
services={{ http }}
onIntegrationCreation={onIntegrationCreation}
initialState={{
mode: 'create',
context: {
options: {
deletePrevious: true,
resetOnCreation: false,
errorOnFailedCleanup: false,
},
...(integrationName !== undefined && datasetName !== undefined
? {
fields: {
integrationName,
datasets: [{ name: datasetName, type: 'logs' as const }],
},
}
: {}),
previouslyCreatedIntegration: lastCreatedIntegrationOptions,
},
}}
>
<ConfigureLogsContent />
</CustomIntegrationsProvider>
);
}
export function ConfigureLogsContent() {
const {
dispatchableEvents: { updateCreateFields },
} = useConsumerCustomIntegrations();
const { euiTheme } = useEuiTheme();
const xsFontSize = useEuiFontSize('xs').fontSize;
const { getState, setState } = useWizard();
const wizardState = getState();
const [serviceName, setServiceName] = useState(wizardState.serviceName);
const [logFilePaths, setLogFilePaths] = useState(wizardState.logFilePaths);
const [namespace, setNamespace] = useState(wizardState.namespace);
const [customConfigurations, setCustomConfigurations] = useState(
wizardState.customConfigurations
);
const logFilePathNotConfigured = logFilePaths.every((filepath) => !filepath);
const onContinue = useCallback(() => {
setState((state) => ({
...state,
serviceName,
logFilePaths: logFilePaths.filter((filepath) => !!filepath),
namespace,
customConfigurations,
}));
}, [customConfigurations, logFilePaths, namespace, serviceName, setState]);
function addLogFilePath() {
setLogFilePaths((prev) => [...prev, '']);
}
function removeLogFilePath(index: number) {
setLogFilePaths((prev) => prev.filter((_, i) => i !== index));
}
function onLogFilePathChanges(index: number, event: React.FormEvent<HTMLInputElement>) {
const filepath = event.currentTarget?.value;
setLogFilePaths((prev) => prev.map((path, i) => (i === index ? filepath : path)));
if (index === 0) {
if (updateCreateFields) {
updateCreateFields({
integrationName: getFilename(filepath).toLowerCase(),
datasets: [
{
name: getFilename(filepath).toLowerCase(),
type: 'logs' as const,
},
],
});
}
}
}
return (
<StepModal
title={i18n.translate(
'xpack.observability_onboarding.configureLogsContent.stepModal.collectCustomLogsLabel',
{ defaultMessage: 'Collect custom logs' }
)}
panelFooter={[
<ConnectedCustomIntegrationsButton
isDisabled={logFilePathNotConfigured || !namespace}
onClick={onContinue}
testSubj={customIntegrationsTestSubjects.button}
/>,
]}
>
<EuiForm fullWidth>
<EuiText color="subdued">
<p>
{i18n.translate('xpack.observability_onboarding.configureLogs.description', {
defaultMessage: 'Configure inputs',
})}
</p>
</EuiText>
<EuiSpacer size="l" />
<EuiFormRow
label={i18n.translate('xpack.observability_onboarding.configureLogs.logFile.path', {
defaultMessage: 'Log file path',
})}
helpText={i18n.translate(
'xpack.observability_onboarding.configureLogs.logFile.helperText',
{
defaultMessage: 'You can use a file path or pattern.',
}
)}
>
<>
{logFilePaths.map((filepath, index) => (
<div key={index} data-test-subj={`obltOnboardingLogFilePath-${index}`}>
{index > 0 && <EuiSpacer size="s" />}
<EuiFlexGroup alignItems="center" gutterSize="xs">
<EuiFlexItem>
<EuiFieldText
data-test-subj="observabilityOnboardingConfigureLogsFieldText"
placeholder={i18n.translate(
'xpack.observability_onboarding.configureLogs.logFile.placeholder',
{
defaultMessage: 'Example: /var/log/application.*',
}
)}
value={filepath}
onChange={(ev) => onLogFilePathChanges(index, ev)}
/>
</EuiFlexItem>
{index > 0 && (
<EuiFlexItem grow={false}>
<EuiButtonIcon
iconType="trash"
aria-label={i18n.translate(
'xpack.observability_onboarding.configureLogsContent.euiButtonIcon.deleteLabel',
{ defaultMessage: 'Delete' }
)}
onClick={() => removeLogFilePath(index)}
data-test-subj={`obltOnboardingLogFilePathDelete-${index}`}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
</div>
))}
</>
</EuiFormRow>
<EuiSpacer size="s" />
<EuiFlexGroup alignItems="flexStart" direction="column" gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconType="plusInCircle"
onClick={addLogFilePath}
data-test-subj="obltOnboardingCustomLogsAddFilePath"
>
{i18n.translate('xpack.observability_onboarding.configureLogs.logFile.addRow', {
defaultMessage: 'Add row',
})}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<OptionalFormRow
label={
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>
{i18n.translate('xpack.observability_onboarding.configureLogs.serviceName', {
defaultMessage: 'Service name',
})}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip
content={i18n.translate(
'xpack.observability_onboarding.configureLogs.serviceName.tooltip',
{
defaultMessage:
'Provide a service name to allow for distributed services running on multiple hosts to correlate the related instances.',
}
)}
position="right"
/>
</EuiFlexItem>
</EuiFlexGroup>
}
helpText={
<FormattedMessage
id="xpack.observability_onboarding.configureLogs.serviceName.helper"
defaultMessage="Name the service your data is collected from."
/>
}
>
<EuiFieldText
placeholder={i18n.translate(
'xpack.observability_onboarding.configureLogs.serviceName.placeholder',
{
defaultMessage: 'Give your service a name',
}
)}
value={serviceName}
onChange={(event) => setServiceName(event.target.value)}
data-test-subj="obltOnboardingCustomLogsServiceName"
/>
</OptionalFormRow>
<EuiHorizontalRule margin="m" />
<EuiAccordion
id="advancedSettingsAccordion"
css={{
'.euiAccordion__buttonContent': {
color: euiTheme.colors.textPrimary,
fontSize: xsFontSize,
},
'.euiAccordion__arrow svg': {
stroke: euiTheme.colors.primary,
width: euiTheme.size.m,
height: euiTheme.size.m,
},
}}
buttonContent={i18n.translate(
'xpack.observability_onboarding.configureLogs.advancedSettings',
{
defaultMessage: 'Advanced settings',
}
)}
data-test-subj="obltOnboardingCustomLogsAdvancedSettings"
>
<EuiSpacer size="l" />
<EuiFormRow
label={
<EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>
{i18n.translate('xpack.observability_onboarding.configureLogs.namespace', {
defaultMessage: 'Namespace',
})}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip
content={i18n.translate(
'xpack.observability_onboarding.configureLogs.namespace.tooltip',
{
defaultMessage:
'Provide a namespace to customize the grouping of your logs. Defaults to the default namespace.',
}
)}
position="right"
/>
</EuiFlexItem>
</EuiFlexGroup>
}
helpText={
<FormattedMessage
id="xpack.observability_onboarding.configureLogs.namespace.helper"
defaultMessage="This setting changes the name of the integration's data stream. {learnMoreLink}"
values={{
learnMoreLink: (
<EuiLink
data-test-subj="observabilityOnboardingConfigureLogsLearnMoreLink"
external
target="_blank"
href={
'https://www.elastic.co/guide/en/fleet/current/data-streams.html#data-streams-naming-scheme'
}
>
{i18n.translate('xpack.observability_onboarding.configureLogs.learnMore', {
defaultMessage: 'Learn more',
})}
</EuiLink>
),
}}
/>
}
>
<EuiFieldText
placeholder={i18n.translate(
'xpack.observability_onboarding.configureLogs.namespace.placeholder',
{
defaultMessage: 'Namespace',
}
)}
value={namespace}
onChange={(event) => setNamespace(event.target.value)}
data-test-subj="obltOnboardingCustomLogsNamespace"
/>
</EuiFormRow>
<EuiSpacer size="l" />
<OptionalFormRow
label={i18n.translate('xpack.observability_onboarding.configureLogs.customConfig', {
defaultMessage: 'Custom configurations',
})}
helpText={
<FormattedMessage
id="xpack.observability_onboarding.configureLogs.customConfig.helper"
defaultMessage="Add YAML configuration options to your agent configuration. Be careful using this feature as it can break your configuration file. {learnMoreLink}"
values={{
learnMoreLink: (
<EuiLink
data-test-subj="observabilityOnboardingConfigureLogsLearnMoreLink"
external
target="_blank"
href={
'https://www.elastic.co/guide/en/beats/filebeat/current/multiline-examples.html'
}
>
{i18n.translate('xpack.observability_onboarding.configureLogs.learnMore', {
defaultMessage: 'Learn more',
})}
</EuiLink>
),
}}
/>
}
>
<EuiTextArea
value={customConfigurations}
onChange={(event) => setCustomConfigurations(event.target.value)}
data-test-subj="obltOnboardingCustomLogsCustomConfig"
/>
</OptionalFormRow>
</EuiAccordion>
<EuiSpacer size="l" />
<ConnectedCustomIntegrationsForm testSubjects={customIntegrationsTestSubjects} />
</EuiForm>
</StepModal>
);
}

View file

@ -1,27 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getFilename } from './get_filename';
describe('Observability onboarding - get_filename', () => {
it.each([
['test', '/logs-onboarding/test.log'],
['test', '/logs-onboarding/test.log'],
['test', 'test.log'],
['test', '/logs-onboarding/long-path/test.log'],
['test', '/logs-onboarding/test.20240223.log'],
['test', 'test'],
['', ''],
['test', '\\logs-onboarding\\test.log'],
['test', "/logs-on'boarding/test.log"],
['te_st', "/logs-on'boarding/te'st.log"],
['test_123', '/logs-onboarding/test 123.log'],
['t_e_s_t_1_2_3_', '/logs-onboarding/t-e%s*t#1@2!3$.log'],
])('should return "%s" for filename "%s"', (expectedFilename: string, filePath: string) => {
expect(getFilename(filePath)).toBe(expectedFilename);
});
});

View file

@ -1,19 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const getFilename = (path?: string) => {
if (!path) {
return '';
}
const filenameWithExt = path.replace(/^.*[\\\/](?!\d*$)/g, '');
const filenameParts = filenameWithExt.split('.');
return replaceSpecialChars(filenameParts[0]);
};
export const replaceSpecialChars = (filename: string) => filename.replaceAll(/[^a-zA-Z0-9_]/g, '_');

View file

@ -1,87 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiPanel } from '@elastic/eui';
import { CustomIntegrationOptions } from '@kbn/custom-integrations';
import { i18n } from '@kbn/i18n';
import { Route } from '@kbn/shared-ux-router';
import { createWizardContext, Step } from '../../../context/create_wizard_context';
import { ConfigureLogs } from './configure_logs';
import { Inspect } from './inspect';
import { InstallElasticAgent } from './install_elastic_agent';
import { FeedbackButtons } from '../shared/feedback_buttons';
interface WizardState {
integrationName?: string;
lastCreatedIntegrationOptions?: CustomIntegrationOptions;
datasetName?: string;
serviceName: string;
logFilePaths: string[];
namespace: string;
customConfigurations: string;
logsType?: 'system' | 'sys' | 'http-endpoint' | 'opentelemetry' | 'log-file' | 'service';
uploadType?: 'log-file' | 'api-key';
elasticAgentPlatform: 'linux-tar' | 'macos' | 'windows' | 'deb' | 'rpm';
autoDownloadConfig: boolean;
apiKeyEncoded: string;
onboardingId: string;
}
const initialState: WizardState = {
integrationName: undefined,
datasetName: undefined,
serviceName: '',
logFilePaths: [''],
namespace: 'default',
customConfigurations: '',
elasticAgentPlatform: 'linux-tar',
autoDownloadConfig: false,
apiKeyEncoded: '',
onboardingId: '',
};
export type CustomLogsSteps = 'configureLogs' | 'installElasticAgent' | 'inspect';
const steps: Record<CustomLogsSteps, Step> = {
configureLogs: { component: ConfigureLogs },
installElasticAgent: {
component: InstallElasticAgent,
title: i18n.translate('xpack.observability_onboarding.customLogs.installShipper.title', {
defaultMessage: 'Install shipper to collect logs',
}),
},
inspect: { component: Inspect },
};
const {
Provider,
useWizard,
routes: customLogsRoutes,
} = createWizardContext({
initialState,
initialStep: 'configureLogs',
steps,
basePath: '/customLogs',
});
export { Provider, useWizard, customLogsRoutes };
export const CustomLogsPanel: React.FC = () => {
return (
<Provider>
<EuiPanel hasBorder>
{Object.keys(customLogsRoutes).map((key) => {
const path = key as keyof typeof customLogsRoutes;
const { handler, exact } = customLogsRoutes[path];
return <Route key={path} path={path} exact={exact} component={handler} />;
})}
<FeedbackButtons flow="custom-logs" />
</EuiPanel>
</Provider>
);
};

View file

@ -1,54 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { EuiTitle, EuiSpacer } from '@elastic/eui';
import { StepModal } from '../shared/step_panel';
import { useWizard } from '.';
import { BackButton } from '../../shared/back_button';
export function Inspect() {
const { getState, getPath, getUsage } = useWizard();
return (
<StepModal
title={i18n.translate('xpack.observability_onboarding.inspect.stepPanel.inspectWizardLabel', {
defaultMessage: 'Inspect wizard',
})}
panelFooter={[<BackButton />]}
>
<EuiTitle size="s">
<h3>
{i18n.translate('xpack.observability_onboarding.inspect.h3.stateLabel', {
defaultMessage: 'State',
})}
</h3>
</EuiTitle>
<pre>{JSON.stringify(getState(), null, 4)}</pre>
<EuiSpacer size="m" />
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.observability_onboarding.inspect.h3.pathLabel"
defaultMessage="Path"
/>
</h3>
</EuiTitle>
<pre>{JSON.stringify(getPath(), null, 4)}</pre>
<EuiSpacer size="m" />
<EuiTitle size="s">
<h3>
{i18n.translate('xpack.observability_onboarding.inspect.h3.usageLabel', {
defaultMessage: 'Usage',
})}
</h3>
</EuiTitle>
<pre>{JSON.stringify(getUsage(), null, 4)}</pre>
</StepModal>
);
}

View file

@ -1,376 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiButton, EuiCallOut, EuiHorizontalRule, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { default as React, useCallback, useEffect, useState } from 'react';
import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common';
import { OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT } from '../../../../common/telemetry_events';
import { ObservabilityOnboardingPluginSetupDeps } from '../../../plugin';
import { useWizard } from '.';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import {
ElasticAgentPlatform,
getElasticAgentSetupCommand,
} from '../shared/get_elastic_agent_setup_command';
import {
InstallElasticAgentSteps,
ProgressStepId,
EuiStepStatus,
} from '../shared/install_elastic_agent_steps';
import { StepModal } from '../shared/step_panel';
import { ApiKeyBanner } from './api_key_banner';
import { WindowsInstallStep } from '../shared/windows_install_step';
import { TroubleshootingLink } from '../shared/troubleshooting_link';
const defaultDatasetName = '';
export function InstallElasticAgent() {
const {
services: { share, analytics },
} = useKibana<ObservabilityOnboardingPluginSetupDeps>();
const [dataReceivedTelemetrySent, setDataReceivedTelemetrySent] = useState(false);
const logsLocator = share.url.locators.get<LogsLocatorParams>(LOGS_LOCATOR_ID);
const { getState, setState } = useWizard();
const wizardState = getState();
const { integrationName: integration, datasetName: dataset, autoDownloadConfig } = wizardState;
const [elasticAgentPlatform, setElasticAgentPlatform] =
useState<ElasticAgentPlatform>('linux-tar');
const enforcedDatasetName =
(integration === dataset ? dataset : `${integration}.${dataset}`) ?? defaultDatasetName;
async function onContinue() {
await logsLocator!.navigate({
dataViewSpec: {
title: `logs-${enforcedDatasetName}-*`,
timeFieldName: '@timestamp',
},
});
}
function onAutoDownloadConfig() {
setState((state) => ({
...state,
autoDownloadConfig: !state.autoDownloadConfig,
}));
}
const { data: monitoringRole, status: monitoringRoleStatus } = useFetcher((callApi) => {
if (!hasAlreadySavedFlow(getState())) {
return callApi('GET /internal/observability_onboarding/logs/setup/privileges');
}
// FIXME: Dario could not find a reasonable fix for getState()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const { data: setup } = useFetcher((callApi) => {
return callApi('GET /internal/observability_onboarding/logs/setup/environment');
}, []);
const {
data: installShipperSetup,
status: installShipperSetupStatus,
error,
} = useFetcher(
(callApi) => {
const { datasetName, serviceName, namespace, customConfigurations, logFilePaths } =
getState();
if (!hasAlreadySavedFlow(getState()) && monitoringRole?.hasPrivileges && datasetName) {
return callApi('POST /internal/observability_onboarding/logs/flow', {
params: {
body: {
name: datasetName,
type: 'logFiles',
state: {
datasetName,
serviceName,
namespace,
customConfigurations,
logFilePaths,
},
},
},
});
}
},
// FIXME: Dario could not find a reasonable fix for getState()
// eslint-disable-next-line react-hooks/exhaustive-deps
[monitoringRole?.hasPrivileges]
);
const { status: saveOnboardingStateDataStatus } = useFetcher((callApi) => {
const {
onboardingId,
datasetName,
serviceName,
namespace,
customConfigurations,
logFilePaths,
} = getState();
if (onboardingId) {
return callApi('PUT /internal/observability_onboarding/flow/{onboardingId}', {
params: {
path: { onboardingId },
body: {
state: {
datasetName,
serviceName,
namespace,
customConfigurations,
logFilePaths,
},
},
},
});
}
// FIXME: Dario could not find a reasonable fix for getState()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const { apiKeyEncoded, onboardingId } = installShipperSetup ?? getState();
const succesfullySavedOnboardingState = saveOnboardingStateDataStatus === FETCH_STATUS.SUCCESS;
const { data: yamlConfig = '', status: yamlConfigStatus } = useFetcher(
(callApi) => {
if (apiKeyEncoded && onboardingId) {
return callApi('GET /internal/observability_onboarding/elastic_agent/config', {
headers: { authorization: `ApiKey ${apiKeyEncoded}` },
params: { query: { onboardingId } },
});
}
},
// FIXME: Dario could not find a reasonable fix for succesfullySavedOnboardingState
// eslint-disable-next-line react-hooks/exhaustive-deps
[apiKeyEncoded, onboardingId, succesfullySavedOnboardingState]
);
useEffect(() => {
setState((state) => ({ ...state, onboardingId, apiKeyEncoded }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [onboardingId, apiKeyEncoded]);
const {
data: progressData,
status: progressStatus,
refetch: refetchProgress,
} = useFetcher(
(callApi) => {
if (onboardingId) {
return callApi('GET /internal/observability_onboarding/flow/{onboardingId}/progress', {
params: { path: { onboardingId } },
});
}
},
[onboardingId]
);
const progressSucceded = progressStatus === FETCH_STATUS.SUCCESS;
useEffect(() => {
if (progressSucceded) {
setTimeout(() => {
refetchProgress();
}, 2000);
}
}, [progressSucceded, refetchProgress]);
const getCheckLogsStep = useCallback(() => {
const progress = progressData?.progress;
if (progress) {
const stepStatus = progress?.['logs-ingest']?.status as EuiStepStatus;
const title =
stepStatus === 'loading'
? CHECK_LOGS_LABELS.loading
: stepStatus === 'complete'
? CHECK_LOGS_LABELS.completed
: CHECK_LOGS_LABELS.incomplete;
return {
title,
status: stepStatus,
'data-test-subj': 'obltOnboardingCheckLogsStep',
};
}
return {
title: CHECK_LOGS_LABELS.incomplete,
status: 'incomplete' as const,
};
}, [progressData?.progress]);
const isInstallStarted = progressData?.progress['ea-download'] !== undefined;
const isInstallCompleted = progressData?.progress?.['ea-status']?.status === 'complete';
const autoDownloadConfigStatus = (progressData?.progress?.['ea-config']?.status ??
'incomplete') as EuiStepStatus;
const isIngestCompleted = progressData?.progress?.['logs-ingest']?.status === 'complete';
useEffect(() => {
if (isIngestCompleted && !dataReceivedTelemetrySent) {
setDataReceivedTelemetrySent(true);
analytics?.reportEvent(OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT.eventType, {
flow_type: 'logFiles',
flow_id: onboardingId,
step: 'logs-ingest',
step_status: 'complete',
});
}
}, [analytics, dataReceivedTelemetrySent, isIngestCompleted, onboardingId]);
return (
<StepModal
panelFooter={[
<EuiButton
data-test-subj="obltOnboardingExploreLogs"
color="primary"
fill
iconType="magnifyWithPlus"
onClick={onContinue}
>
{i18n.translate('xpack.observability_onboarding.steps.exploreLogs', {
defaultMessage: 'Explore logs',
})}
</EuiButton>,
]}
>
<EuiText color="subdued">
<p>
{i18n.translate('xpack.observability_onboarding.installElasticAgent.description', {
defaultMessage:
'To collect the data from your system and stream it to Elastic, you first need to install a shipping tool on the machine generating the logs. In this case, the shipping tool is an agent developed by Elastic.',
})}
</p>
</EuiText>
<EuiSpacer size="m" />
{integration && (
<>
<EuiCallOut
title={i18n.translate(
'xpack.observability_onboarding.installElasticAgent.integrationSuccessCallout.title',
{
defaultMessage: '{integrationName} integration installed.',
values: {
integrationName: integration,
},
}
)}
color="success"
iconType="check"
data-test-subj="obltOnboardingCustomIntegrationInstalled"
/>
<EuiSpacer size="m" />
</>
)}
{apiKeyEncoded && onboardingId ? (
<ApiKeyBanner
payload={{ apiKeyEncoded, onboardingId }}
hasPrivileges
status={FETCH_STATUS.SUCCESS}
/>
) : (
monitoringRoleStatus !== FETCH_STATUS.NOT_INITIATED &&
monitoringRoleStatus !== FETCH_STATUS.LOADING && (
<ApiKeyBanner
payload={installShipperSetup}
hasPrivileges={monitoringRole?.hasPrivileges}
status={installShipperSetupStatus}
error={error}
/>
)
)}
<EuiSpacer size="m" />
<InstallElasticAgentSteps
installAgentPlatformOptions={[
{
label: i18n.translate(
'xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.linux',
{ defaultMessage: 'Linux' }
),
id: 'linux-tar',
},
{
label: i18n.translate(
'xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.macOS',
{ defaultMessage: 'MacOS' }
),
id: 'macos',
},
{
label: i18n.translate(
'xpack.observability_onboarding.installElasticAgent.installStep.choosePlatform.windows',
{ defaultMessage: 'Windows' }
),
id: 'windows',
disableSteps: true,
children: (
<WindowsInstallStep docsLink="https://www.elastic.co/guide/en/observability/current/logs-stream.html" />
),
},
]}
onSelectPlatform={(id) => setElasticAgentPlatform(id)}
selectedPlatform={elasticAgentPlatform}
installAgentCommand={getElasticAgentSetupCommand({
elasticAgentPlatform,
apiKeyEncoded,
apiEndpoint: setup?.apiEndpoint,
scriptDownloadUrl: setup?.scriptDownloadUrl,
elasticAgentVersion: setup?.elasticAgentVersionInfo.agentVersion,
autoDownloadConfig,
onboardingId,
})}
autoDownloadConfig={autoDownloadConfig}
onToggleAutoDownloadConfig={onAutoDownloadConfig}
installAgentStatus={
installShipperSetupStatus === FETCH_STATUS.LOADING
? 'loading'
: isInstallCompleted
? 'complete'
: 'current'
}
showInstallProgressSteps={isInstallStarted}
installProgressSteps={
(progressData?.progress ?? {}) as Partial<
Record<ProgressStepId, { status: EuiStepStatus; message?: string }>
>
}
configureAgentStatus={
yamlConfigStatus === FETCH_STATUS.LOADING ? 'loading' : autoDownloadConfigStatus
}
configureAgentYaml={yamlConfig}
appendedSteps={[getCheckLogsStep()]}
/>
<EuiHorizontalRule />
<TroubleshootingLink />
</StepModal>
);
}
type WizardState = ReturnType<ReturnType<typeof useWizard>['getState']>;
function hasAlreadySavedFlow({ apiKeyEncoded, onboardingId }: WizardState) {
return Boolean(apiKeyEncoded && onboardingId);
}
const CHECK_LOGS_LABELS = {
incomplete: i18n.translate(
'xpack.observability_onboarding.installElasticAgent.progress.logsIngest.incompleteTitle',
{ defaultMessage: 'Ship logs to Elastic Observability' }
),
loading: i18n.translate(
'xpack.observability_onboarding.installElasticAgent.progress.logsIngest.loadingTitle',
{ defaultMessage: 'Waiting for logs to be shipped...' }
),
completed: i18n.translate(
'xpack.observability_onboarding.installElasticAgent.progress.logsIngest.completedTitle',
{ defaultMessage: 'Logs are being shipped!' }
),
};

View file

@ -59,34 +59,32 @@ export const OtelLogsPanel: React.FC = () => {
} = useKibana<ObservabilityOnboardingAppServices>();
const {
data: apiKeyData,
data: setupData,
error,
refetch,
} = useFetcher(
(callApi) => {
return callApi('POST /internal/observability_onboarding/otel/api_key', {});
return callApi('POST /internal/observability_onboarding/otel_host/setup');
},
[],
{ showToastOnError: false }
);
const { data: setup } = useFetcher((callApi) => {
return callApi('GET /internal/observability_onboarding/logs/setup/environment');
}, []);
useEffect(() => {
if (apiKeyData && setup) {
if (setupData) {
onPageReady({
meta: {
description: `[ttfmp_onboarding] Requests to get the environment and to generate API key succeeded and the flow's UI has rendered`,
description: `[ttfmp_onboarding] Requests to setup the flow succeeded and the flow's UI has rendered`,
},
});
}
}, [apiKeyData, onPageReady, setup]);
}, [onPageReady, setupData]);
const ingestEndpointUrl = isServerless ? setup?.managedOtlpServiceUrl : setup?.elasticsearchUrl;
const ingestEndpointUrl = isServerless
? setupData?.managedOtlpServiceUrl
: setupData?.elasticsearchUrl;
const AGENT_CDN_BASE_URL = 'artifacts.elastic.co/downloads/beats/elastic-agent';
const agentVersion = setup?.elasticAgentVersionInfo.agentVersion ?? '';
const agentVersion = setupData?.elasticAgentVersionInfo.agentVersion ?? '';
const urlEncodedAgentVersion = encodeURIComponent(agentVersion);
const logsLocator = share.url.locators.get<LogsLocatorParams>(LOGS_LOCATOR_ID);
@ -117,7 +115,7 @@ export const OtelLogsPanel: React.FC = () => {
curl --output elastic-distro-${agentVersion}-linux-$arch.tar.gz --url https://${AGENT_CDN_BASE_URL}/elastic-agent-${urlEncodedAgentVersion}-linux-$arch.tar.gz --proto '=https' --tlsv1.2 -fL && mkdir -p elastic-distro-${agentVersion}-linux-$arch && tar -xvf elastic-distro-${agentVersion}-linux-$arch.tar.gz -C "elastic-distro-${agentVersion}-linux-$arch" --strip-components=1 && cd elastic-distro-${agentVersion}-linux-$arch
rm ./otel.yml && cp ${sampleConfigurationPath} ./otel.yml && mkdir -p ./data/otelcol && sed -i 's#\\\${env:STORAGE_DIR}#'"$PWD"/data/otelcol'#g' ./otel.yml && sed -i 's#\\\${env:${elasticEndpointVarName}}#${ingestEndpointUrl}#g' ./otel.yml && sed -i 's/\\\${env:ELASTIC_API_KEY}/${apiKeyData?.apiKeyEncoded}/g' ./otel.yml`,
rm ./otel.yml && cp ${sampleConfigurationPath} ./otel.yml && mkdir -p ./data/otelcol && sed -i 's#\\\${env:STORAGE_DIR}#'"$PWD"/data/otelcol'#g' ./otel.yml && sed -i 's#\\\${env:${elasticEndpointVarName}}#${ingestEndpointUrl}#g' ./otel.yml && sed -i 's/\\\${env:ELASTIC_API_KEY}/${setupData?.apiKeyEncoded}/g' ./otel.yml`,
start: 'sudo ./otelcol --config otel.yml',
type: 'copy',
},
@ -129,7 +127,7 @@ rm ./otel.yml && cp ${sampleConfigurationPath} ./otel.yml && mkdir -p ./data/ote
curl --output elastic-distro-${agentVersion}-darwin-$arch.tar.gz --url https://${AGENT_CDN_BASE_URL}/elastic-agent-${urlEncodedAgentVersion}-darwin-$arch.tar.gz --proto '=https' --tlsv1.2 -fL && mkdir -p "elastic-distro-${agentVersion}-darwin-$arch" && tar -xvf elastic-distro-${agentVersion}-darwin-$arch.tar.gz -C "elastic-distro-${agentVersion}-darwin-$arch" --strip-components=1 && cd elastic-distro-${agentVersion}-darwin-$arch
rm ./otel.yml && cp ${sampleConfigurationPath} ./otel.yml && mkdir -p ./data/otelcol && sed -i '' 's#\\\${env:STORAGE_DIR}#'"$PWD"/data/otelcol'#g' ./otel.yml && sed -i '' 's#\\\${env:${elasticEndpointVarName}}#${ingestEndpointUrl}#g' ./otel.yml && sed -i '' 's/\\\${env:ELASTIC_API_KEY}/${apiKeyData?.apiKeyEncoded}/g' ./otel.yml`,
rm ./otel.yml && cp ${sampleConfigurationPath} ./otel.yml && mkdir -p ./data/otelcol && sed -i '' 's#\\\${env:STORAGE_DIR}#'"$PWD"/data/otelcol'#g' ./otel.yml && sed -i '' 's#\\\${env:${elasticEndpointVarName}}#${ingestEndpointUrl}#g' ./otel.yml && sed -i '' 's/\\\${env:ELASTIC_API_KEY}/${setupData?.apiKeyEncoded}/g' ./otel.yml`,
start: './otelcol --config otel.yml',
type: 'copy',
},
@ -172,9 +170,9 @@ rm ./otel.yml && cp ${sampleConfigurationPath} ./otel.yml && mkdir -p ./data/ote
}}
/>
{(!setup || !apiKeyData) && <EuiSkeletonText lines={6} />}
{!setupData && <EuiSkeletonText lines={6} />}
{setup && apiKeyData && (
{setupData && (
<>
<EuiText>
<p>{selectedContent.firstStepTitle}</p>

View file

@ -1,179 +0,0 @@
#!/bin/bash
set -u
fail() {
printf "%s\n" "$@" >&2
exit 1
}
# require bash
if [ -z "${BASH_VERSION:-}" ]; then
fail "bash required"
fi
if [ $# -lt 4 ]; then
fail "usage: $0 api_key api_endpoint agent_version onboarding_id [autoDownloadConfig=1]"
fi
API_KEY_ENCODED=$1
API_ENDPOINT=$2
ELASTIC_AGENT_VERSION=$3
ONBOARDING_ID=$4
AUTO_DOWNLOAD_CONFIG=${5:-}
# require curl
[ $(builtin type -P curl) ] || fail "curl required"
# check OS
OS="$(uname)"
ARCH="$(uname -m)"
os=linux
arch=x86_64
cfg=/opt/Elastic/Agent/elastic-agent.yml
if [ "${OS}" == "Linux" ]; then
if [ "${ARCH}" == "aarch64" ]; then
arch=arm64
fi
elif [ "${OS}" == "Darwin" ]; then
os=darwin
if [ "${ARCH}" == "arm64" ]; then
arch=aarch64
fi
cfg=/Library/Elastic/Agent/elastic-agent.yml
else
fail "this script is only supported on linux and macOS"
fi
artifact=elastic-agent-${ELASTIC_AGENT_VERSION}-${os}-${arch}
# https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.8.2-darwin-x86_64.tar.gz
# https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.8.2-darwin-aarch64.tar.gz
# https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.8.2-linux-x86_64.tar.gz
# https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.8.2-linux-arm64.tar.gz
updateStepProgress() {
local STEPNAME="$1"
local STATUS="$2" # "incomplete" | "complete" | "disabled" | "loading" | "warning" | "danger" | "current"
local MESSAGE=${3:-}
local PAYLOAD=${4:-}
local data=""
if [ -z "$PAYLOAD" ]; then
data="{\"status\":\"${STATUS}\", \"message\":\"${MESSAGE}\"}"
else
data="{\"status\":\"${STATUS}\", \"message\":\"${MESSAGE}\", \"payload\":${PAYLOAD}}"
fi
curl --request POST \
--url "${API_ENDPOINT}/flow/${ONBOARDING_ID}/step/${STEPNAME}" \
--header "Authorization: ApiKey ${API_KEY_ENCODED}" \
--header "Content-Type: application/json" \
--header "kbn-xsrf: true" \
--header "x-elastic-internal-origin: Kibana" \
--data "$data" \
--output /dev/null \
--no-progress-meter
}
ELASTIC_AGENT_DOWNLOAD_URL="https://artifacts.elastic.co/downloads/beats/elastic-agent/${artifact}.tar.gz"
echo "Downloading Elastic Agent archive from ${ELASTIC_AGENT_DOWNLOAD_URL}"
updateStepProgress "ea-download" "loading"
curl -L -O $ELASTIC_AGENT_DOWNLOAD_URL --fail
if [ "$?" -eq 0 ]; then
echo "Downloaded Elastic Agent"
updateStepProgress "ea-download" "complete"
else
updateStepProgress "ea-download" "danger" "Failed to download Elastic Agent, see script output for error."
fail "Failed to download Elastic Agent"
fi
echo "Extracting Elastic Agent"
updateStepProgress "ea-extract" "loading"
tar -xzf ${artifact}.tar.gz
if [ "$?" -eq 0 ]; then
echo "Elastic Agent extracted"
updateStepProgress "ea-extract" "complete"
else
updateStepProgress "ea-extract" "danger" "Failed to extract Elastic Agent, see script output for error."
fail "Failed to extract Elastic Agent"
fi
echo "Installing Elastic Agent"
updateStepProgress "ea-install" "loading"
cd ${artifact}
./elastic-agent install -f
if [ "$?" -eq 0 ]; then
echo "Elastic Agent installed"
updateStepProgress "ea-install" "complete"
else
updateStepProgress "ea-install" "danger" "Failed to install Elastic Agent, see script output for error."
fail "Failed to install Elastic Agent"
fi
waitForElasticAgentStatus() {
local MAX_RETRIES=10
local i=0
echo -n "."
elastic-agent status >/dev/null
local ELASTIC_AGENT_STATUS_EXIT_CODE="$?"
while [ "$ELASTIC_AGENT_STATUS_EXIT_CODE" -ne 0 ] && [ $i -le $MAX_RETRIES ]; do
sleep 1
echo -n "."
elastic-agent status >/dev/null
ELASTIC_AGENT_STATUS_EXIT_CODE="$?"
((i++))
done
echo ""
return $ELASTIC_AGENT_STATUS_EXIT_CODE
}
echo "Checking Elastic Agent status"
updateStepProgress "ea-status" "loading"
waitForElasticAgentStatus
if [ "$?" -ne 0 ]; then
updateStepProgress "ea-status" "warning" "Unable to determine agent status"
fi
# https://www.elastic.co/guide/en/fleet/current/elastic-agent-cmd-options.html#elastic-agent-status-command
ELASTIC_AGENT_STATES=(STARTING CONFIGURING HEALTHY DEGRADED FAILED STOPPING UPGRADING ROLLBACK)
# Get elastic-agent status in json format | removing extra states in the json | finding "state":value | removing , | removing "state": | trimming the result
ELASTIC_AGENT_STATE="$(elastic-agent status --output json | sed -n '/components/q;p' | grep state | sed 's/\(.*\),/\1 /' | sed 's/"state": //' | sed 's/[[:space:]]//g')"
# Get elastic-agent status in json format | removing extra states in the json | finding "message":value | removing , | removing "message": | trimming the result | removing ""
ELASTIC_AGENT_MESSAGE="$(elastic-agent status --output json | sed -n '/components/q;p' | grep message | sed 's/\(.*\),/\1 /' | sed 's/"message": //' | sed 's/[[:space:]]//g' | sed 's/\"//g')"
# Get elastic-agent status in json format | removing extra ids in the json | finding "id":value | removing , | removing "id": | trimming the result | removing ""
ELASTIC_AGENT_ID="$(elastic-agent status --output json | sed -n '/components/q;p' | grep \"id\" | sed 's/\(.*\),/\1 /' | sed 's/"id": //' | sed 's/[[:space:]]//g' | sed 's/\"//g')"
if [ "${ELASTIC_AGENT_STATE}" = "2" ] && [ "${ELASTIC_AGENT_MESSAGE}" = "Running" ]; then
echo "Elastic Agent running (id: ${ELASTIC_AGENT_ID})"
echo "Download and save configuration to ${cfg}"
updateStepProgress "ea-status" "complete" "" "{\"agentId\": \"${ELASTIC_AGENT_ID}\"}"
else
updateStepProgress "ea-status" "warning" "Expected agent status HEALTHY / Running but got ${ELASTIC_AGENT_STATES[ELASTIC_AGENT_STATE]} / ${ELASTIC_AGENT_MESSAGE}"
fi
downloadElasticAgentConfig() {
echo "Downloading elastic-agent.yml"
updateStepProgress "ea-config" "loading"
curl --request GET \
--url "${API_ENDPOINT}/elastic_agent/config?onboardingId=${ONBOARDING_ID}" \
--header "Authorization: ApiKey ${API_KEY_ENCODED}" \
--header "Content-Type: application/json" \
--header "kbn-xsrf: true" \
--header "x-elastic-internal-origin: Kibana" \
--no-progress-meter \
--output ${cfg}
if [ "$?" -eq 0 ]; then
echo "Downloaded elastic-agent.yml"
updateStepProgress "ea-config" "complete"
else
updateStepProgress "ea-config" "warning" "Failed to write elastic-agent.yml on host automatically, try manually setting the configuration"
fail "Failed to download elastic-agent.yml"
fi
}
if [ "${AUTO_DOWNLOAD_CONFIG}" == "autoDownloadConfig=1" ]; then
downloadElasticAgentConfig
echo "Done with standalone Elastic Agent setup. Look for streaming logs to arrive in Kibana"
else
echo "Done with standalone Elastic Agent setup. Make sure to add your configuration to ${cfg}, then look for streaming logs to arrive in Kibana"
fi

View file

@ -1,64 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as t from 'io-ts';
import { v4 as uuidv4 } from 'uuid';
import { generateCustomLogsYml } from '../../../common/elastic_agent_logs';
import { getAuthenticationAPIKey } from '../../lib/get_authentication_api_key';
import { getFallbackESUrl } from '../../lib/get_fallback_urls';
import { getObservabilityOnboardingFlow } from '../../lib/state';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
const generateConfig = createObservabilityOnboardingServerRoute({
endpoint: 'GET /internal/observability_onboarding/elastic_agent/config',
params: t.type({
query: t.type({ onboardingId: t.string }),
}),
security: {
authz: {
enabled: false,
reason: 'Authorization is checked by the Saved Object client',
},
},
async handler(resources): Promise<string> {
const {
params: {
query: { onboardingId },
},
core,
plugins,
request,
services: { esLegacyConfigService },
} = resources;
const authApiKey = getAuthenticationAPIKey(request);
const coreStart = await core.start();
const savedObjectsClient = coreStart.savedObjects.getScopedClient(request);
const elasticsearchUrl = plugins.cloud?.setup?.elasticsearchUrl
? [plugins.cloud?.setup?.elasticsearchUrl]
: await getFallbackESUrl(esLegacyConfigService);
const savedState = await getObservabilityOnboardingFlow({
savedObjectsClient,
savedObjectId: onboardingId,
});
const yaml = generateCustomLogsYml({
...savedState?.state,
apiKey: authApiKey ? `${authApiKey?.apiKeyId}:${authApiKey?.apiKey}` : '$API_KEY',
esHost: elasticsearchUrl,
logfileId: `custom-logs-${uuidv4()}`,
});
return yaml;
},
});
export const elasticAgentRouteRepository = {
...generateConfig,
};

View file

@ -18,7 +18,6 @@ import { transformOutputToFullPolicyOutput } from '@kbn/fleet-plugin/server/serv
import { OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT } from '../../../common/telemetry_events';
import { getObservabilityOnboardingFlow, saveObservabilityOnboardingFlow } from '../../lib/state';
import type { SavedObservabilityOnboardingFlow } from '../../saved_objects/observability_onboarding_status';
import { ObservabilityOnboardingFlow } from '../../saved_objects/observability_onboarding_status';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getHasLogs } from './get_has_logs';
import { getKibanaUrl } from '../../lib/get_fallback_urls';
@ -29,46 +28,6 @@ import { createInstallApiKey } from '../../lib/api_key/create_install_api_key';
import { hasLogMonitoringPrivileges } from '../../lib/api_key/has_log_monitoring_privileges';
import { makeTar, type Entry } from './make_tar';
const updateOnboardingFlowRoute = createObservabilityOnboardingServerRoute({
endpoint: 'PUT /internal/observability_onboarding/flow/{onboardingId}',
security: {
authz: {
enabled: false,
reason: 'Authorization is checked by the Saved Object client',
},
},
params: t.type({
path: t.type({
onboardingId: t.string,
}),
body: t.partial({
state: t.record(t.string, t.unknown),
}),
}),
async handler(resources): Promise<{ onboardingId: string }> {
const {
params: {
path: { onboardingId },
body: { state },
},
core,
request,
} = resources;
const coreStart = await core.start();
const savedObjectsClient = coreStart.savedObjects.getScopedClient(request);
const { id } = await saveObservabilityOnboardingFlow({
savedObjectsClient,
savedObjectId: onboardingId,
observabilityOnboardingState: {
type: 'logFiles',
state,
progress: {},
} as ObservabilityOnboardingFlow,
});
return { onboardingId: id };
},
});
const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({
endpoint: 'POST /internal/observability_onboarding/flow/{id}/step/{name}',
security: {
@ -210,10 +169,6 @@ const getProgressRoute = createObservabilityOnboardingServerRoute({
* Elastic agent.
*
* If the user does not have all necessary privileges a 403 Forbidden response is returned.
*
* This endpoint differs from the existing `POST /internal/observability_onboarding/logs/flow`
* endpoint in that it caters for the auto-detect flow where integrations are detected and installed
* on the host system, rather than in the Kibana UI.
*/
const createFlowRoute = createObservabilityOnboardingServerRoute({
endpoint: 'POST /internal/observability_onboarding/flow',
@ -674,7 +629,6 @@ function generateAgentConfigTar(output: Output, installedIntegrations: Installed
export const flowRouteRepository = {
...createFlowRoute,
...updateOnboardingFlowRoute,
...stepProgressUpdateRoute,
...getProgressRoute,
...integrationsInstallRoute,

View file

@ -5,19 +5,17 @@
* 2.0.
*/
import type { EndpointOf, ServerRouteRepository } from '@kbn/server-route-repository';
import { elasticAgentRouteRepository } from './elastic_agent/route';
import { flowRouteRepository } from './flow/route';
import { kubernetesOnboardingRouteRepository } from './kubernetes/route';
import { logsOnboardingRouteRepository } from './logs/route';
import { firehoseOnboardingRouteRepository } from './firehose/route';
import { otelHostOnboardingRouteRepository } from './otel_host/route';
function getTypedObservabilityOnboardingServerRouteRepository() {
const repository = {
...flowRouteRepository,
...logsOnboardingRouteRepository,
...elasticAgentRouteRepository,
...kubernetesOnboardingRouteRepository,
...firehoseOnboardingRouteRepository,
...otelHostOnboardingRouteRepository,
};
return repository;

View file

@ -1,176 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import * as t from 'io-ts';
import Boom from '@hapi/boom';
import { ElasticAgentVersionInfo } from '../../../common/types';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getFallbackESUrl } from '../../lib/get_fallback_urls';
import { getKibanaUrl } from '../../lib/get_fallback_urls';
import { getAgentVersionInfo } from '../../lib/get_agent_version';
import { saveObservabilityOnboardingFlow } from '../../lib/state';
import { createShipperApiKey } from '../../lib/api_key/create_shipper_api_key';
import { ObservabilityOnboardingFlow } from '../../saved_objects/observability_onboarding_status';
import { hasLogMonitoringPrivileges } from '../../lib/api_key/has_log_monitoring_privileges';
import { createManagedOtlpServiceApiKey } from '../../lib/api_key/create_managed_otlp_service_api_key';
import { getManagedOtlpServiceUrl } from '../../lib/get_managed_otlp_service_url';
const logMonitoringPrivilegesRoute = createObservabilityOnboardingServerRoute({
endpoint: 'GET /internal/observability_onboarding/logs/setup/privileges',
security: {
authz: {
enabled: false,
reason: 'This route has custom authorization logic using Elasticsearch client',
},
},
handler: async (resources): Promise<{ hasPrivileges: boolean }> => {
const { context } = resources;
const {
elasticsearch: { client },
} = await context.core;
const hasPrivileges = await hasLogMonitoringPrivileges(client.asCurrentUser);
return { hasPrivileges };
},
});
const installShipperSetupRoute = createObservabilityOnboardingServerRoute({
endpoint: 'GET /internal/observability_onboarding/logs/setup/environment',
security: {
authz: {
enabled: false,
reason: "This route only reads cluster's metadata and does not require authorization",
},
},
async handler(resources): Promise<{
apiEndpoint: string;
scriptDownloadUrl: string;
elasticAgentVersionInfo: ElasticAgentVersionInfo;
elasticsearchUrl: string[];
managedOtlpServiceUrl: string;
}> {
const {
core,
plugins,
kibanaVersion,
services: { esLegacyConfigService },
} = resources;
const fleetPluginStart = await plugins.fleet.start();
const elasticAgentVersionInfo = await getAgentVersionInfo(fleetPluginStart, kibanaVersion);
const kibanaUrl = getKibanaUrl(core.setup, plugins.cloud?.setup);
const scriptDownloadUrl = new URL(
core.setup.http.staticAssets.getPluginAssetHref('standalone_agent_setup.sh'),
kibanaUrl
).toString();
const apiEndpoint = new URL(`${kibanaUrl}/internal/observability_onboarding`).toString();
const elasticsearchUrl = plugins.cloud?.setup?.elasticsearchUrl
? [plugins.cloud?.setup?.elasticsearchUrl]
: await getFallbackESUrl(esLegacyConfigService);
return {
apiEndpoint,
elasticsearchUrl,
scriptDownloadUrl,
elasticAgentVersionInfo,
managedOtlpServiceUrl: await getManagedOtlpServiceUrl(resources),
};
},
});
const createAPIKeyRoute = createObservabilityOnboardingServerRoute({
endpoint: 'POST /internal/observability_onboarding/otel/api_key',
security: {
authz: {
enabled: false,
reason: 'This route has custom authorization logic using Elasticsearch client',
},
},
params: t.type({}),
async handler({ context, config }): Promise<{ apiKeyEncoded: string }> {
const {
elasticsearch: { client },
} = await context.core;
const hasPrivileges = await hasLogMonitoringPrivileges(client.asCurrentUser);
if (!hasPrivileges) {
throw Boom.forbidden('Insufficient permissions to create shipper API key');
}
const timestamp = new Date().toISOString();
const { encoded: apiKeyEncoded } = config.serverless.enabled
? await createManagedOtlpServiceApiKey(client.asCurrentUser, `ingest-otel-host-${timestamp}`)
: await createShipperApiKey(client.asCurrentUser, `otel-logs-${timestamp}`);
return { apiKeyEncoded };
},
});
const createFlowRoute = createObservabilityOnboardingServerRoute({
endpoint: 'POST /internal/observability_onboarding/logs/flow',
security: {
authz: {
enabled: false,
reason: 'Authorization is checked by the Saved Object client and Elasticsearch client',
},
},
params: t.type({
body: t.intersection([
t.type({
name: t.string,
}),
t.type({
type: t.literal('logFiles'),
}),
t.partial({
state: t.record(t.string, t.unknown),
}),
]),
}),
async handler(resources): Promise<{ apiKeyEncoded: string; onboardingId: string }> {
const {
context,
params: {
body: { name, type, state },
},
core,
request,
} = resources;
const coreStart = await core.start();
const {
elasticsearch: { client },
} = await context.core;
const { encoded: apiKeyEncoded } = await createShipperApiKey(
client.asCurrentUser,
`standalone_agent_logs_onboarding_${name}`
);
const savedObjectsClient = coreStart.savedObjects.getScopedClient(request);
const { id } = await saveObservabilityOnboardingFlow({
savedObjectsClient,
observabilityOnboardingState: {
type,
state: state as ObservabilityOnboardingFlow['state'],
progress: {},
},
});
return { apiKeyEncoded, onboardingId: id };
},
});
export const logsOnboardingRouteRepository = {
...logMonitoringPrivilegesRoute,
...installShipperSetupRoute,
...createFlowRoute,
...createAPIKeyRoute,
};

View file

@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import Boom from '@hapi/boom';
import { ElasticAgentVersionInfo } from '../../../common/types';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getFallbackESUrl } from '../../lib/get_fallback_urls';
import { getAgentVersionInfo } from '../../lib/get_agent_version';
import { createShipperApiKey } from '../../lib/api_key/create_shipper_api_key';
import { hasLogMonitoringPrivileges } from '../../lib/api_key/has_log_monitoring_privileges';
import { createManagedOtlpServiceApiKey } from '../../lib/api_key/create_managed_otlp_service_api_key';
import { getManagedOtlpServiceUrl } from '../../lib/get_managed_otlp_service_url';
const setupFlowRoute = createObservabilityOnboardingServerRoute({
endpoint: 'POST /internal/observability_onboarding/otel_host/setup',
security: {
authz: {
enabled: false,
reason: 'This route has custom authorization logic using Elasticsearch client',
},
},
async handler(resources): Promise<{
elasticAgentVersionInfo: ElasticAgentVersionInfo;
elasticsearchUrl: string;
apiKeyEncoded: string;
managedOtlpServiceUrl: string;
}> {
const {
context,
config,
plugins,
kibanaVersion,
services: { esLegacyConfigService },
} = resources;
const {
elasticsearch: { client },
} = await context.core;
const hasPrivileges = await hasLogMonitoringPrivileges(client.asCurrentUser);
if (!hasPrivileges) {
throw Boom.forbidden('Insufficient permissions to create API key');
}
const fleetPluginStart = await plugins.fleet.start();
const elasticAgentVersionInfo = await getAgentVersionInfo(fleetPluginStart, kibanaVersion);
const elasticsearchUrlList = plugins.cloud?.setup?.elasticsearchUrl
? [plugins.cloud?.setup?.elasticsearchUrl]
: await getFallbackESUrl(esLegacyConfigService);
const timestamp = new Date().toISOString();
const { encoded: apiKeyEncoded } = config.serverless.enabled
? await createManagedOtlpServiceApiKey(client.asCurrentUser, `ingest-otel-host-${timestamp}`)
: await createShipperApiKey(client.asCurrentUser, `otel-host-${timestamp}`);
return {
elasticsearchUrl: elasticsearchUrlList.length > 0 ? elasticsearchUrlList[0] : '',
elasticAgentVersionInfo,
apiKeyEncoded,
managedOtlpServiceUrl: await getManagedOtlpServiceUrl(resources),
};
},
});
export const otelHostOnboardingRouteRepository = {
...setupFlowRoute,
};

View file

@ -21,7 +21,7 @@ export interface LogFilesState {
type ObservabilityOnboardingFlowState = LogFilesState | undefined;
type ObservabilityOnboardingType = 'logFiles' | 'autoDetect' | 'kubernetes';
type ObservabilityOnboardingType = 'autoDetect';
export interface ObservabilityOnboardingFlow {
type: ObservabilityOnboardingType;

View file

@ -29,7 +29,6 @@
"@kbn/security-plugin",
"@kbn/std",
"@kbn/use-tracked-promise",
"@kbn/custom-integrations",
"@kbn/share-plugin",
"@kbn/deeplinks-observability",
"@kbn/fleet-plugin",

View file

@ -1,11 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const FIELD_VALIDATION = {
INTEGRATION_NAME_LOWERCASE: 'An integration name should be lowercase.',
DATASET_NAME_LOWERCASE: 'A dataset name should be lowercase.',
};

View file

@ -54,5 +54,3 @@ export const test = base.extend<ExtendedScoutTestFixtures, ExtendedScoutWorkerFi
});
export const generateIntegrationName = (name: string) => `${name}_${uuidv4().slice(0, 5)}`;
export * as assertionMessages from './assertion_messages';

View file

@ -1,83 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect } from '@kbn/scout-oblt';
import { generateIntegrationName, test } from '../../fixtures';
test.describe(
'Observability Onboarding - Custom Integration',
{ tag: ['@ess', '@svlOblt'] },
() => {
const integrationName = generateIntegrationName('mylogs');
const logsFilePath = `${integrationName}.log`;
test.beforeEach(async ({ browserAuth, pageObjects: { customLogs } }) => {
await browserAuth.loginAsAdmin();
await customLogs.goto();
});
test.afterEach(async ({ apiServices }) => {
await apiServices.fleet.integration.delete(integrationName);
});
test('should be installed, show API Key and correct instructions', async ({
pageObjects: { customLogs },
page,
}) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await expect(customLogs.integrationNameInput).toHaveValue(integrationName);
await expect(customLogs.datasetNameInput).toHaveValue(integrationName);
await expect(customLogs.serviceNameInput).toHaveValue('');
await customLogs.continueButton.click();
await expect(
customLogs.customIntegrationInstalledCallout,
`'mylogs integration installed' should be displayed`
).toBeVisible();
await expect(
customLogs.apiKeyCreatedCallout,
`'API Key created' should be displayed`
).toBeVisible();
// validate default 'Install the Elastic Agent' instructions
await expect(page.testSubj.locator('linux-tar')).toHaveAttribute('aria-pressed', 'true');
await expect(customLogs.autoDownloadConfigurationToggle).not.toBeChecked();
await expect(customLogs.installCodeSnippet).toBeVisible();
await customLogs.selectPlatform('macos');
await expect(customLogs.autoDownloadConfigurationToggle).not.toBeChecked();
await expect(customLogs.installCodeSnippet).toBeVisible();
await customLogs.selectPlatform('windows');
await expect(customLogs.autoDownloadConfigurationToggle).toBeDisabled();
await expect(customLogs.windowsInstallElasticAgentDocLink).toBeVisible();
await expect(customLogs.installCodeSnippet).toBeHidden();
await expect(
customLogs.configureElasticAgentStep.getByText('Step 2 is disabled')
).toBeVisible();
});
test('should update instructions when automatic config download toggled', async ({
pageObjects: { customLogs },
}) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
await customLogs.autoDownloadConfigurationToggle.click();
await expect(customLogs.autoDownloadConfigurationCallout).toBeVisible();
await expect(customLogs.installCodeSnippet).toContainText('autoDownloadConfig=1');
await customLogs.selectPlatform('macos');
await expect(customLogs.autoDownloadConfigurationCallout).toBeVisible();
await expect(customLogs.installCodeSnippet).toContainText('autoDownloadConfig=1');
await customLogs.autoDownloadConfigurationToggle.click();
await expect(customLogs.installCodeSnippet).not.toContainText('autoDownloadConfig=1');
});
}
);

View file

@ -1,107 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect } from '@kbn/scout-oblt';
import { assertionMessages, generateIntegrationName, test } from '../../fixtures';
test.describe(
'Observability Onboarding - Custom logs configuration',
{ tag: ['@ess', '@svlOblt'] },
() => {
const logsFilePath = `${generateIntegrationName('mylogs')}.log`;
test.beforeEach(async ({ browserAuth, pageObjects: { customLogs } }) => {
await browserAuth.loginAsAdmin();
await customLogs.goto();
});
test('should navigate to the onboarding home page when the back button clicked', async ({
pageObjects: { customLogs },
page,
}) => {
await customLogs.clickBackButton();
expect(page.url()).toContain('/app/observabilityOnboarding');
});
test(`should allow multiple entries for Log File Path`, async ({
pageObjects: { customLogs },
}) => {
await expect(
customLogs.getLogFilePathInputField(0),
'Log File Path should be empty'
).toHaveValue('');
await expect(
customLogs.continueButton,
'Continue button should be disabled when Log File Path is not set'
).toBeDisabled();
await customLogs.addLogFilePathButton.click();
await expect(customLogs.logFilePathList).toHaveCount(2);
await customLogs.logFilePathDeleteButton(1).click();
await expect(customLogs.logFilePathList).toHaveCount(1);
});
test(`should allow updating Advanced Settings`, async ({ pageObjects: { customLogs } }) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await expect(customLogs.advancedSettingsContent).toBeHidden();
await customLogs.clickAdvancedSettingsButton();
await expect(
customLogs.advancedSettingsContent,
'Advanced Settings should be opened'
).toBeVisible();
await expect(customLogs.namespaceInput).toHaveValue('default');
await customLogs.namespaceInput.fill('');
await expect(
customLogs.continueButton,
'Continue button should be disabled when Namespace is empty'
).toBeDisabled();
await customLogs.namespaceInput.fill('default');
await expect(customLogs.customConfigInput).toHaveValue('');
await expect(customLogs.continueButton).toBeEnabled();
await customLogs.clickAdvancedSettingsButton();
await expect(
customLogs.advancedSettingsContent,
'Advanced Settings should be closed'
).toBeHidden();
});
test('should validate Integration Name field', async ({
pageObjects: { customLogs },
page,
}) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.integrationNameInput.fill('');
await expect(customLogs.continueButton).toBeDisabled();
await customLogs.integrationNameInput.fill('hello$world');
await expect(customLogs.integrationNameInput).toHaveValue('hello_world');
await customLogs.integrationNameInput.fill('H3llowOrld');
await expect(
page.getByText(assertionMessages.FIELD_VALIDATION.INTEGRATION_NAME_LOWERCASE)
).toBeVisible();
});
test('should validate DataSet Name field', async ({ pageObjects: { customLogs }, page }) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.datasetNameInput.fill('');
await expect(customLogs.continueButton).toBeDisabled();
await customLogs.datasetNameInput.fill('hello$world');
await expect(customLogs.datasetNameInput).toHaveValue('hello_world');
await customLogs.datasetNameInput.fill('H3llowOrld');
await expect(
page.getByText(assertionMessages.FIELD_VALIDATION.DATASET_NAME_LOWERCASE)
).toBeVisible();
});
}
);

View file

@ -1,109 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect } from '@kbn/scout-oblt';
import { generateIntegrationName, test } from '../../fixtures';
test.describe(
'Observability Onboarding - Custom Integration Error',
{ tag: ['@ess', '@svlOblt'] },
() => {
const integrationName = generateIntegrationName('mylogs');
const logsFilePath = `${integrationName}.log`;
test.afterEach(async ({ apiServices }) => {
await apiServices.fleet.integration.delete(integrationName);
});
test('should be displayed when user has no previleges', async ({
browserAuth,
pageObjects: { customLogs },
}) => {
await browserAuth.loginAsPrivilegedUser();
await customLogs.goto();
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
await expect(
customLogs.customIntegrationInstalledCallout,
`'mylogs integration installed' should be displayed`
).toBeVisible();
await expect(
customLogs.apiKeyPrivilegesErrorCallout,
`'User does not have permissions to create API key' should be displayed`
).toBeVisible();
});
test('should be displayed when API Key creation failed', async ({
browserAuth,
pageObjects: { customLogs },
page,
}) => {
await page.route('**/observability_onboarding/logs/flow', (route) => {
route.fulfill({
status: 500,
body: JSON.stringify({
message: 'Internal error',
}),
});
});
await browserAuth.loginAsAdmin();
await customLogs.goto();
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
await expect(
customLogs.customIntegrationInstalledCallout,
`'mylogs integration installed' should be displayed`
).toBeVisible();
await expect(
customLogs.apiKeyCreateErrorCallout,
`'Failed to create API Key' should be displayed`
).toBeVisible();
});
test('should be displayed when integration failed', async ({
browserAuth,
pageObjects: { customLogs },
page,
kbnUrl,
}) => {
await page.route(kbnUrl.get('/api/fleet/epm/custom_integrations'), (route) => {
route.fulfill({
status: 500,
json: {
message: 'Internal error',
},
});
});
await browserAuth.loginAsAdmin();
await customLogs.goto();
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
await expect(customLogs.customIntegrationErrorCallout).toBeVisible();
});
test('should be displayed when integration with the same name exists', async ({
browserAuth,
pageObjects: { customLogs },
apiServices,
page,
}) => {
await apiServices.fleet.integration.install(integrationName);
await browserAuth.loginAsAdmin();
await customLogs.goto();
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
const errorMessage = `Failed to create the integration as an installation with the name ${integrationName} already exists.`;
await expect(page.getByText(errorMessage)).toBeVisible();
});
}
);

View file

@ -1,249 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect, Locator } from '@kbn/scout-oblt';
import { generateIntegrationName, test } from '../../fixtures';
const checkAgentStatusUpdated = async (locator: Locator, status: string) =>
expect(locator.getByText(status)).toBeVisible();
test.describe('Observability Onboarding - Elastic Agent', { tag: ['@ess', '@svlOblt'] }, () => {
const integrationName = generateIntegrationName('mylogs');
const logsFilePath = `${integrationName}.log`;
let onboardingId: string;
test.beforeEach(async ({ browserAuth, pageObjects, page }) => {
await page.route('**/observability_onboarding/logs/flow', async (route) => {
const response = await route.fetch();
const body = await response.json();
onboardingId = body.onboardingId;
await route.fulfill({ response });
});
// login and create custom integration
await browserAuth.loginAsAdmin();
await pageObjects.customLogs.goto();
await pageObjects.customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await pageObjects.customLogs.continueButton.click();
await pageObjects.customLogs.apiKeyCreatedCallout.waitFor({ state: 'visible' });
});
test.afterEach(async ({ apiServices }) => {
await apiServices.fleet.integration.delete(integrationName);
});
test('should be installed sucessfully', async ({ pageObjects: { customLogs }, apiServices }) => {
// downloading agent
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-download',
'loading'
);
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('loading'),
'Downloading Elastic Agent'
);
// downloading agent failed
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-download',
'danger'
);
await expect(customLogs.getStepStatusLocator('danger'), 'Download Elastic Agent').toBeVisible();
// downloading agent completed
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent downloaded'
);
// extracting agent
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'loading'
);
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('loading'),
'Extracting Elastic Agent'
);
// extracting agent completed
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'complete'
);
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent extracted'
);
// installing agent failed
await apiServices.onboarding.updateInstallationStepStatus(onboardingId, 'ea-install', 'danger');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('danger'),
'Install Elastic Agent'
);
// installing agent completed
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-install',
'complete'
);
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent installed'
);
// agent connecting
await apiServices.onboarding.updateInstallationStepStatus(onboardingId, 'ea-status', 'loading');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('loading'),
'Connecting to the Elastic Agent'
);
await expect(customLogs.getCheckLogsStepLocator('incomplete')).toBeVisible();
await expect(customLogs.checkLogsStepMessage).toHaveText('Ship logs to Elastic Observability');
// agent connected
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-status',
'complete',
{
agentId: 'test-agent-id',
}
);
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Connected to the Elastic Agent'
);
});
test('should be configured sucessfully for Linux and wait for logs', async ({
pageObjects: { customLogs },
apiServices,
}) => {
// install and connect agent for linux
await customLogs.autoDownloadConfigurationToggle.click();
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'complete'
);
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-install',
'complete'
);
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-status',
'complete',
{
agentId: 'test-agent-id',
}
);
await apiServices.onboarding.updateInstallationStepStatus(onboardingId, 'ea-config', 'loading');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('loading'),
'Downloading Elastic Agent config'
);
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-config',
'complete'
);
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent config written to /opt/Elastic/Agent/elastic-agent.yml'
);
await expect(customLogs.getCheckLogsStepLocator('loading')).toBeVisible();
await expect(customLogs.checkLogsStepMessage).toHaveText('Waiting for logs to be shipped...');
});
test('should be configured sucessfully for MacOS and wait for logs', async ({
pageObjects: { customLogs },
apiServices,
}) => {
// install and connect agent for macos
await customLogs.selectPlatform('macos');
await customLogs.autoDownloadConfigurationToggle.click();
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'complete'
);
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-install',
'complete'
);
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-status',
'complete',
{
agentId: 'test-agent-id',
}
);
await apiServices.onboarding.updateInstallationStepStatus(
onboardingId,
'ea-config',
'complete'
);
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent config written to /Library/Elastic/Agent/elastic-agent.yml'
);
await expect(customLogs.getCheckLogsStepLocator('loading')).toBeVisible();
await expect(customLogs.checkLogsStepMessage).toHaveText('Waiting for logs to be shipped...');
});
test('should ship logs to Elastic', async ({ page, pageObjects: { customLogs } }) => {
await page.route('**/progress', (route) => {
route.fulfill({
status: 200,
body: JSON.stringify({
progress: {
'ea-download': { status: 'complete' },
'ea-extract': { status: 'complete' },
'ea-install': { status: 'complete' },
'ea-status': { status: 'complete' },
'ea-config': { status: 'complete' },
'logs-ingest': { status: 'complete' },
},
}),
});
});
await expect(customLogs.getCheckLogsStepLocator('complete')).toBeVisible();
await expect(customLogs.checkLogsStepMessage).toHaveText('Logs are being shipped!');
await customLogs.exploreLogsButton.click();
await expect(page).toHaveURL(/\/app\/discover/);
});
});

View file

@ -1,85 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE } from '@kbn/observability-onboarding-plugin/server/saved_objects/observability_onboarding_status';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { SupertestWithRoleScopeType } from '../../../services';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const roleScopedSupertest = getService('roleScopedSupertest');
let viewerClient: SupertestWithRoleScopeType;
let adminClient: SupertestWithRoleScopeType;
describe('Creating onboarding logs flow', () => {
before(async () => {
viewerClient = await roleScopedSupertest.getSupertestWithRoleScope('viewer', {
withInternalHeaders: true,
useCookieHeader: true,
});
adminClient = await roleScopedSupertest.getSupertestWithRoleScope('admin', {
withInternalHeaders: true,
useCookieHeader: true,
});
});
it('fails with a 500 error when missing privileges', async () => {
const response = await viewerClient
.post('/internal/observability_onboarding/logs/flow')
.send({
type: 'logFiles',
name: 'name',
state: {},
});
expect(response.statusCode).to.be(500);
expect(response.body.message).to.contain('unauthorized');
});
it('returns a flow id and apiKey encoded', async () => {
const state = {
datasetName: 'my-dataset',
serviceName: 'my-service',
namespace: 'my-namespace',
logFilePaths: ['my-service-logs.log'],
};
const response = await adminClient.post('/internal/observability_onboarding/logs/flow').send({
type: 'logFiles',
name: 'name',
state,
});
expect(response.statusCode).to.be(200);
expect(response.body.apiKeyEncoded).to.not.empty();
expect(response.body.onboardingId).to.not.empty();
});
it('saves the expected state for logFiles', async () => {
const state = {
datasetName: 'my-dataset',
serviceName: 'my-service',
namespace: 'my-namespace',
logFilePaths: ['my-service-logs.log'],
};
const response = await adminClient.post('/internal/observability_onboarding/logs/flow').send({
type: 'logFiles',
name: 'name',
state,
});
const savedState = await kibanaServer.savedObjects.get({
type: OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
id: response.body.onboardingId,
});
expect(savedState.attributes).to.be.eql({ type: 'logFiles', state, progress: {} });
});
});
}

View file

@ -1,74 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { load } from 'js-yaml';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { SupertestWithRoleScopeType } from '../../../services';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
const roleScopedSupertest = getService('roleScopedSupertest');
let adminClient: SupertestWithRoleScopeType;
describe('Generate Elastic Agent configuration', () => {
before(async () => {
adminClient = await roleScopedSupertest.getSupertestWithRoleScope('admin', {
withInternalHeaders: true,
useCookieHeader: true,
});
});
it(`should return input properties empty when onboardingId doesn't exists`, async () => {
const response = await adminClient
.get('/internal/observability_onboarding/elastic_agent/config')
.query({ onboardingId: 'my-onboarding-id' });
expect(response.status).to.be(200);
const ymlConfig = load(response.text);
expect(ymlConfig.inputs[0].data_stream.namespace).to.be('');
expect(ymlConfig.inputs[0].streams[0].data_stream.dataset).to.be('');
expect(ymlConfig.inputs[0].streams[0].paths).to.be.empty();
});
it('should return input properties configured when onboardingId exists', async () => {
const datasetName = 'api-tests';
const namespace = 'default';
const logFilepath = '/my-logs.log';
const serviceName = 'my-service';
const createFlowResponse = await adminClient
.post('/internal/observability_onboarding/logs/flow')
.send({
type: 'logFiles',
name: 'name',
state: {
datasetName,
namespace,
logFilePaths: [logFilepath],
serviceName,
},
});
const onboardingId = createFlowResponse.body.onboardingId;
const response = await adminClient
.get('/internal/observability_onboarding/elastic_agent/config')
.query({ onboardingId });
expect(response.status).to.be(200);
const ymlConfig = load(response.text);
expect(ymlConfig.inputs[0].data_stream.namespace).to.be(namespace);
expect(ymlConfig.inputs[0].streams[0].data_stream.dataset).to.be(datasetName);
expect(ymlConfig.inputs[0].streams[0].paths).to.be.eql([logFilepath]);
expect(ymlConfig.inputs[0].streams[0].processors[0].add_fields.fields.name).to.be.eql(
serviceName
);
});
});
}

View file

@ -1,49 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { type DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { type SupertestWithRoleScopeType } from '../../../services';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
const roleScopedSupertest = getService('roleScopedSupertest');
let viewerClientWithAPIKey: SupertestWithRoleScopeType;
let adminClientWithAPIKey: SupertestWithRoleScopeType;
describe('Api Key privileges check', () => {
before(async () => {
viewerClientWithAPIKey = await roleScopedSupertest.getSupertestWithRoleScope('viewer', {
withInternalHeaders: true,
});
adminClientWithAPIKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', {
withInternalHeaders: true,
});
});
after(async () => {
await viewerClientWithAPIKey.destroy();
await adminClientWithAPIKey.destroy();
});
it('returns false when user has reader privileges', async () => {
const response = await viewerClientWithAPIKey.get(
`/internal/observability_onboarding/logs/setup/privileges`
);
expect(response.body.hasPrivileges).not.ok();
});
it('returns true when user has admin privileges', async () => {
const response = await adminClientWithAPIKey.get(
`/internal/observability_onboarding/logs/setup/privileges`
);
expect(response.body.hasPrivileges).ok();
});
});
}

View file

@ -31,18 +31,12 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
});
const createFlowResponse = await adminClient
.post('/internal/observability_onboarding/logs/flow')
.post('/internal/observability_onboarding/flow')
.send({
type: 'logFiles',
name: 'name',
state: {
datasetName,
namespace,
logFilePaths: ['my-service.log'],
},
name: 'test-onboarding',
});
onboardingId = createFlowResponse.body.onboardingId;
onboardingId = createFlowResponse.body.onboardingFlow.id;
});
it(`fails with a 404 error when onboardingId doesn't exists`, async () => {
@ -54,17 +48,6 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
expect(response.body.message).to.contain('onboarding session not found');
});
it('should skip log verification and return log-ingest as incomplete when ea-status is not complete', async () => {
const response = await adminClient.get(
`/internal/observability_onboarding/flow/${onboardingId}/progress`
);
expect(response.status).to.be(200);
const logsIngestProgress = response.body.progress['logs-ingest'];
expect(logsIngestProgress).to.have.property('status', 'incomplete');
});
describe('when ea-status is complete', () => {
describe('should not skip logs verification', () => {
const agentId = 'my-agent-id';

View file

@ -9,10 +9,7 @@ import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_cont
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
describe('Onboarding', () => {
loadTestFile(require.resolve('./create_logs_onboarding_flow'));
loadTestFile(require.resolve('./get_elastic_agent_config'));
loadTestFile(require.resolve('./get_progress'));
loadTestFile(require.resolve('./update_progress'));
loadTestFile(require.resolve('./get_privileges'));
});
}

View file

@ -49,19 +49,12 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
beforeEach(async () => {
const createFlowResponse = await adminClient
.post('/internal/observability_onboarding/logs/flow')
.post('/internal/observability_onboarding/flow')
.send({
type: 'logFiles',
name: 'name',
state: {
datasetName: 'my-dataset',
serviceName: 'my-service',
namespace: 'my-namespace',
logFilePaths: ['my-service.log'],
},
name: 'test-onboarding',
});
onboardingId = createFlowResponse.body.onboardingId;
onboardingId = createFlowResponse.body.onboardingFlow.id;
const savedState = await kibanaServer.savedObjects.get({
type: OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,

View file

@ -34,26 +34,18 @@ export default function ApiTest({ getService }: FtrProviderContext) {
registry.when('Get progress', { config: 'basic' }, () => {
let onboardingId: string;
const datasetName = 'api-tests';
const namespace = 'default';
before(async () => {
const req = await observabilityOnboardingApiClient.logMonitoringUser({
endpoint: 'POST /internal/observability_onboarding/logs/flow',
endpoint: 'POST /internal/observability_onboarding/flow',
params: {
body: {
type: 'logFiles',
name: 'name',
state: {
datasetName,
namespace,
logFilePaths: ['my-service.log'],
},
name: 'test-onboarding',
},
},
});
onboardingId = req.body.onboardingId;
onboardingId = req.body.onboardingFlow.id;
});
describe('when missing required privileges', () => {

View file

@ -1,55 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { MOCKED_KIBANA_URL, MOCKED_PUBLIC_BASE_URL } from '../../configs';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const observabilityOnboardingApiClient = getService('observabilityOnboardingApiClient');
async function callApiWithPrivileges() {
return await observabilityOnboardingApiClient.logMonitoringUser({
endpoint: 'GET /internal/observability_onboarding/logs/setup/environment',
});
}
registry.when('Install shipper setup', { config: 'basic' }, () => {
it('returns apiEndpoint and scriptDownloadUrl prioritizing server.publicBaseUrl', async () => {
const request = await callApiWithPrivileges();
expect(request.status).to.be(200);
expect(request.body.apiEndpoint).to.be(
`${MOCKED_PUBLIC_BASE_URL}/internal/observability_onboarding`
);
expect(request.body.scriptDownloadUrl).to.match(
new RegExp(
`${MOCKED_PUBLIC_BASE_URL}/.+?/plugins/observabilityOnboarding/assets/standalone_agent_setup.sh`,
'i'
)
);
});
});
registry.when('Install shipper setup', { config: 'cloud' }, () => {
it('returns apiEndpoint and scriptDownloadUrl prioritizing cloudId', async () => {
const request = await callApiWithPrivileges();
expect(request.status).to.be(200);
expect(request.body.apiEndpoint).to.be(
`${MOCKED_KIBANA_URL}/internal/observability_onboarding`
);
expect(request.body.scriptDownloadUrl).to.match(
new RegExp(
`${MOCKED_KIBANA_URL}/.+?/plugins/observabilityOnboarding/assets/standalone_agent_setup.sh`,
'i'
)
);
});
});
}

View file

@ -1,31 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { ObservabilityOnboardingApiClientKey } from '../../common/config';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const observabilityOnboardingApiClient = getService('observabilityOnboardingApiClient');
async function callApiAs(user: ObservabilityOnboardingApiClientKey) {
return await observabilityOnboardingApiClient[user]({
endpoint: 'GET /internal/observability_onboarding/logs/setup/privileges',
});
}
registry.when('Api Key privileges check', { config: 'basic' }, () => {
describe('when missing required privileges', () => {
it('returns false when user has no access privileges', async () => {
const privileges = await callApiAs('noAccessUser');
expect(privileges.body.hasPrivileges).not.ok();
});
});
});
}