diff --git a/package.json b/package.json index c345d2ee7c913..8d23546927524 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,7 @@ "moment": "2.13.0", "moment-timezone": "0.5.4", "ngreact": "0.5.1", + "mustache": "2.3.0", "no-ui-slider": "1.2.0", "node-fetch": "1.3.2", "pegjs": "0.9.0", diff --git a/src/core_plugins/kibana/common/tutorials/filebeat_cloud_instructions.js b/src/core_plugins/kibana/common/tutorials/filebeat_cloud_instructions.js new file mode 100644 index 0000000000000..d0fd6da8d2cf8 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/filebeat_cloud_instructions.js @@ -0,0 +1,40 @@ +export const FILEBEAT_CLOUD_INSTRUCTIONS = { + CONFIG: { + OSX: { + title: 'Edit the configuration', + textPre: 'Modify `filebeat.yml` to set the connection information for Elastic Cloud:', + commands: [ + 'cloud.id: "{config.cloud.id}"', + 'cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + }, + DEB: { + title: 'Edit the configuration', + textPre: 'Modify `/etc/filebeat/filebeat.yml` to set the connection information for Elastic Cloud:', + commands: [ + 'cloud.id: "{config.cloud.id}"', + 'cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + }, + RPM: { + title: 'Edit the configuration', + textPre: 'Modify `/etc/filebeat/filebeat.yml` to set the connection information for Elastic Cloud:', + commands: [ + 'cloud.id: "{config.cloud.id}"', + 'cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + }, + WINDOWS: { + title: 'Edit the configuration', + textPre: 'Modify `C:\\Program Files\\Filebeat\\filebeat.yml` to set the connection information for Elastic Cloud:', + commands: [ + 'cloud.id: "{config.cloud.id}"', + 'cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + } + } +}; diff --git a/src/core_plugins/kibana/common/tutorials/filebeat_instructions.js b/src/core_plugins/kibana/common/tutorials/filebeat_instructions.js new file mode 100644 index 0000000000000..b941f3b3c67f1 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/filebeat_instructions.js @@ -0,0 +1,166 @@ +export const FILEBEAT_INSTRUCTIONS = { + INSTALL: { + OSX: { + title: 'Download and install Filebeat', + textPre: 'First time using Filebeat? See the [Getting Started Guide]' + + '({config.docs.beats.filebeat}/filebeat-getting-started.html).', + commands: [ + 'curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{config.kibana.version}-darwin-x86_64.tar.gz', + 'tar xzvf filebeat-{config.kibana.version}-darwin-x86_64.tar.gz', + 'cd filebeat-{config.kibana.version}-darwin-x86_64/', + ] + }, + DEB: { + title: 'Download and install Filebeat', + textPre: 'First time using Filebeat? See the [Getting Started Guide]' + + '({config.docs.beats.filebeat}/filebeat-getting-started.html).', + commands: [ + 'curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{config.kibana.version}-amd64.deb', + 'sudo dpkg -i filebeat-{config.kibana.version}-amd64.deb' + ], + textPost: 'Looking for the 32 bits packages? See the [Download page](https://www.elastic.co/downloads/beats/filebeat).' + }, + RPM: { + title: 'Download and install Filebeat', + textPre: 'First time using Filebeat? See the [Getting Started Guide]' + + '({config.docs.beats.filebeat}/filebeat-getting-started.html).', + commands: [ + 'curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{config.kibana.version}-x86_64.rpm', + 'sudo rpm -vi filebeat-{config.kibana.version}-x86_64.rpm' + ], + textPost: 'Looking for the 32 bits packages? See the [Download page](https://www.elastic.co/downloads/beats/filebeat).' + }, + WINDOWS: { + title: 'Download and install Filebeat', + textPre: 'First time using Filebeat? See the [Getting Started Guide]' + + '({config.docs.beats.filebeat}/filebeat-getting-started.html).\n' + + '1. Download the Filebeat Windows zip file from the [downloads](https://www.elastic.co/downloads/beats/filebeat) page.\n' + + '2. Extract the contents of the zip file into `C:\\Program Files`.\n' + + '3. Rename the `filebeat-{config.kibana.version}-windows` directory to `Filebeat`.\n' + + '4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select' + + ' Run As Administrator). If you are running Windows XP, you may need to download and install PowerShell.\n' + + '5. From the PowerShell prompt, run the following commands to install Filebeat as a Windows service.', + commands: [ + 'PS > cd C:\\Program Files\\Filebeat', + 'PS C:\\Program Files\\Filebeat> .\\install-service-filebeat.ps1' + ], + textPost: 'Modify the settings under `output.elasticsearch` in the ' + + '`C:\\Program Files\\Filebeat\\filebeat.yml` file to point to your Elasticsearch installation.' + } + }, + START: { + OSX: { + title: 'Start Filebeat', + textPre: 'The `setup` command loads the Kibana dashboards.' + + ' If the dashboards are already set up, omit this command.', + commands: [ + './filebeat setup', + './filebeat -e', + ] + }, + DEB: { + title: 'Start Filebeat', + textPre: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' + + 'omit this command.', + commands: [ + 'sudo filebeat setup', + 'sudo service filebeat start', + ] + }, + RPM: { + title: 'Start Filebeat', + textPre: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' + + 'omit this command.', + commands: [ + 'sudo filebeat setup', + 'sudo service filebeat start', + ], + }, + WINDOWS: { + title: 'Start Filebeat', + textPre: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' + + 'omit this command.', + commands: [ + 'PS C:\\Program Files\\Filebeat> filebeat.exe setup', + 'PS C:\\Program Files\\Filebeat> Service-Start filebeat', + ] + } + }, + CONFIG: { + OSX: { + title: 'Edit the configuration', + textPre: 'Modify `filebeat.yml` to set the connection information:', + commands: [ + 'output.elasticsearch:', + ' hosts: [""]', + ' username: "elastic"', + ' password: ""', + 'setup.kibana:', + ' host: ""' + ], + textPost: 'Where `` is the password of the `elastic` user, ' + + '`` is the URL of Elasticsearch, and `` is the URL of Kibana.' + }, + DEB: { + title: 'Edit the configuration', + textPre: 'Modify `/etc/filebeat/filebeat.yml` to set the connection information:', + commands: [ + 'output.elasticsearch:', + ' hosts: [""]', + ' username: "elastic"', + ' password: ""', + 'setup.kibana:', + ' host: ""' + ], + textPost: 'Where `` is the password of the `elastic` user, ' + + '`` is the URL of Elasticsearch, and `` is the URL of Kibana.' + }, + RPM: { + title: 'Edit the configuration', + textPre: 'Modify `/etc/filebeat/filebeat.yml` to set the connection information:', + commands: [ + 'output.elasticsearch:', + ' hosts: [""]', + ' username: "elastic"', + ' password: ""', + 'setup.kibana:', + ' host: ""' + ], + textPost: 'Where `` is the password of the `elastic` user, ' + + '`` is the URL of Elasticsearch, and `` is the URL of Kibana.' + }, + WINDOWS: { + title: 'Edit the configuration', + textPre: 'Modify `C:\\Program Files\\Filebeat\\filebeat.yml` to set the connection information:', + commands: [ + 'output.elasticsearch:', + ' hosts: [""]', + ' username: "elastic"', + ' password: ""', + 'setup.kibana:', + ' host: ""' + ], + textPost: 'Where `` is the password of the `elastic` user, ' + + '`` is the URL of Elasticsearch, and `` is the URL of Kibana.' + } + }, + PLUGINS: { + GEOIP_AND_UA: { + title: 'Install Elasticsearch GeoIP and user agent plugins', + textPre: 'This module requires two Elasticsearch plugins that are not ' + + 'installed by default.\n\nFrom the Elasticsearch installation folder, run:', + commands: [ + 'bin/elasticsearch-plugin install ingest-geoip', + 'bin/elasticsearch-plugin install ingest-user-agent' + ] + }, + GEOIP: { + title: 'Install Elasticsearch GeoIP plugin', + textPre: 'This module requires an Elasticsearch plugin that is not ' + + 'installed by default.\n\nFrom the Elasticsearch installation folder, run:', + commands: [ + 'bin/elasticsearch-plugin install ingest-geoip' + ] + } + } +}; diff --git a/src/core_plugins/kibana/common/tutorials/filebeat_onprem_cloud_instructions.js b/src/core_plugins/kibana/common/tutorials/filebeat_onprem_cloud_instructions.js new file mode 100644 index 0000000000000..e385fb127b4a2 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/filebeat_onprem_cloud_instructions.js @@ -0,0 +1,2 @@ +export const FILEBEAT_ONPREM_CLOUD_INSTRUCTIONS = { +}; diff --git a/src/core_plugins/kibana/common/tutorials/instruction_variant.js b/src/core_plugins/kibana/common/tutorials/instruction_variant.js new file mode 100644 index 0000000000000..909def999d2b8 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/instruction_variant.js @@ -0,0 +1,34 @@ +export const INSTRUCTION_VARIANT = { + OSX: 'osx', + DEB: 'deb', + RPM: 'rpm', + DOCKER: 'docker', + WINDOWS: 'windows', + NODE: 'node', + DJANGO: 'django', + FLASK: 'flask' +}; + +const DISPLAY_MAP = { + [INSTRUCTION_VARIANT.OSX]: 'macOS', + [INSTRUCTION_VARIANT.DEB]: 'DEB', + [INSTRUCTION_VARIANT.RPM]: 'RPM', + [INSTRUCTION_VARIANT.DOCKER]: 'Docker', + [INSTRUCTION_VARIANT.WINDOWS]: 'Windows', + [INSTRUCTION_VARIANT.NODE]: 'Node.js', + [INSTRUCTION_VARIANT.DJANGO]: 'Django', + [INSTRUCTION_VARIANT.FLASK]: 'Flask', +}; + +/** + * Convert instruction variant id into display text. + * + * @params {String} id - instruction variant id as defined from INSTRUCTION_VARIANT + * @return {String} display name + */ +export function getDisplayText(id) { + if (id in DISPLAY_MAP) { + return DISPLAY_MAP[id]; + } + return id; +} diff --git a/src/core_plugins/kibana/common/tutorials/logstash_instructions.js b/src/core_plugins/kibana/common/tutorials/logstash_instructions.js new file mode 100644 index 0000000000000..5439e52fab653 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/logstash_instructions.js @@ -0,0 +1,27 @@ +export const LOGSTASH_INSTRUCTIONS = { + INSTALL: { + OSX: [ + { + title: 'Download and install the Java Runtime Environment', + textPre: 'Follow the installation instructions [here](https://docs.oracle.com/javase/8/docs/technotes/guides/install/mac_jre.html).' + }, + { + title: 'Download and install Logstash', + commands: [ + 'curl -L -O https://artifacts.elastic.co/downloads/logstash/logstash-{config.kibana.version}.tar.gz', + 'tar xzvf logstash-{config.kibana.version}.tar.gz' + ] + } + ], + WINDOWS: [ + { + title: 'Download and install the Java runtime environment', + textPre: 'Follow the installation instructions [here](https://docs.oracle.com/javase/8/docs/technotes/guides/install/windows_jre_install.html).' + }, + { + title: 'Download and install Logstash', + textPre: 'Download Logstash from [here](https://artifacts.elastic.co/downloads/logstash/logstash-{config.kibana.version}.zip) and unzip it.' + } + ], + } +}; diff --git a/src/core_plugins/kibana/common/tutorials/metricbeat_cloud_instructions.js b/src/core_plugins/kibana/common/tutorials/metricbeat_cloud_instructions.js new file mode 100644 index 0000000000000..a450045cd6436 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/metricbeat_cloud_instructions.js @@ -0,0 +1,40 @@ +export const METRICBEAT_CLOUD_INSTRUCTIONS = { + CONFIG: { + OSX: { + title: 'Edit the configuration', + textPre: 'Modify `metricbeat.yml` to set the connection information for Elastic Cloud:', + commands: [ + 'cloud.id: "{config.cloud.id}"', + 'cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + }, + DEB: { + title: 'Edit the configuration', + textPre: 'Modify `/etc/metricbeat/metricbeat.yml` to set the connection information for Elastic Cloud:', + commands: [ + 'cloud.id: "{config.cloud.id}"', + 'cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + }, + RPM: { + title: 'Edit the configuration', + textPre: 'Modify `/etc/metricbeat/metricbeat.yml` to set the connection information for Elastic Cloud:', + commands: [ + 'cloud.id: "{config.cloud.id}"', + 'cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + }, + WINDOWS: { + title: 'Edit the configuration', + textPre: 'Modify `C:\\Program Files\\Filebeat\\metricbeat.yml` to set the connection information for Elastic Cloud:', + commands: [ + 'cloud.id: "{config.cloud.id}"', + 'cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + } + } +}; diff --git a/src/core_plugins/kibana/common/tutorials/metricbeat_instructions.js b/src/core_plugins/kibana/common/tutorials/metricbeat_instructions.js new file mode 100644 index 0000000000000..5bc7761c429d9 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/metricbeat_instructions.js @@ -0,0 +1,147 @@ +export const METRICBEAT_INSTRUCTIONS = { + INSTALL: { + OSX: { + title: 'Download and install Metricbeat', + textPre: 'First time using Metricbeat? See the [Getting Started Guide]' + + '({config.docs.beats.metricbeat}/metricbeat-getting-started.html).', + commands: [ + 'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-darwin-x86_64.tar.gz', + 'tar xzvf metricbeat-{config.kibana.version}-darwin-x86_64.tar.gz', + 'cd metricbeat-{config.kibana.version}-darwin-x86_64/', + ] + }, + DEB: { + title: 'Download and install Metricbeat', + textPre: 'First time using Metricbeat? See the [Getting Started Guide]' + + '({config.docs.beats.metricbeat}/metricbeat-getting-started.html).', + commands: [ + 'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-amd64.deb', + 'sudo dpkg -i metricbeat-{config.kibana.version}-amd64.deb' + ], + textPost: 'Looking for the 32 bits packages? See the [Download page](https://www.elastic.co/downloads/beats/metricbeat).' + }, + RPM: { + title: 'Download and install Metricbeat', + textPre: 'First time using Metricbeat? See the [Getting Started Guide]' + + '({config.docs.beats.metricbeat}/metricbeat-getting-started.html).', + commands: [ + 'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-x86_64.rpm', + 'sudo rpm -vi metricbeat-{config.kibana.version}-x86_64.rpm' + ], + textPost: 'Looking for the 32 bits packages? See the [Download page](https://www.elastic.co/downloads/beats/metricbeat).' + }, + WINDOWS: { + title: 'Download and install Metricbeat', + textPre: 'First time using Metricbeat? See the [Getting Started Guide]' + + '({config.docs.beats.metricbeat}/metricbeat-getting-started.html).\n' + + '1. Download the Metricbeat Windows zip file from the [downloads](https://www.elastic.co/downloads/beats/metricbeat) page.\n' + + '2. Extract the contents of the zip file into `C:\\Program Files`.\n' + + '3. Rename the `metricbeat-{config.kibana.version}-windows` directory to `Metricbeat`.\n' + + '4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select' + + ' Run As Administrator). If you are running Windows XP, you may need to download and install PowerShell.\n' + + '5. From the PowerShell prompt, run the following commands to install Metricbeat as a Windows service.', + commands: [ + 'PS > cd C:\\Program Files\\Metricbeat', + 'PS C:\\Program Files\\Metricbeat> .\\install-service-metricbeat.ps1' + ], + textPost: 'Modify the settings under `output.elasticsearch` in the ' + + '`C:\\Program Files\\Metricbeat\\metricbeat.yml` file to point to your Elasticsearch installation.' + } + }, + START: { + OSX: { + title: 'Start Metricbeat', + textPre: 'The `setup` command loads the Kibana dashboards.' + + ' If the dashboards are already set up, omit this command.', + commands: [ + './metricbeat setup', + './metricbeat -e', + ] + }, + DEB: { + title: 'Start Metricbeat', + textPre: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' + + 'omit this command.', + commands: [ + 'sudo metricbeat setup', + 'sudo service metricbeat start', + ] + }, + RPM: { + title: 'Start Metricbeat', + textPre: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' + + 'omit this command.', + commands: [ + 'sudo metricbeat setup', + 'sudo service metricbeat start', + ], + }, + WINDOWS: { + title: 'Start Metricbeat', + textPre: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, ' + + 'omit this command.', + commands: [ + 'PS C:\\Program Files\\Metricbeat> metricbeat.exe setup', + 'PS C:\\Program Files\\Metricbeat> Service-Start metricbeat', + ] + } + }, + CONFIG: { + OSX: { + title: 'Edit the configuration', + textPre: 'Modify `metricbeat.yml` to set the connection information:', + commands: [ + 'output.elasticsearch:', + ' hosts: [""]', + ' username: "elastic"', + ' password: ""', + 'setup.kibana:', + ' host: ""' + ], + textPost: 'Where `` is the password of the `elastic` user, ' + + '`` is the URL of Elasticsearch, and `` is the URL of Kibana.' + }, + DEB: { + title: 'Edit the configuration', + textPre: 'Modify `/etc/metricbeat/metricbeat.yml` to set the connection information:', + commands: [ + 'output.elasticsearch:', + ' hosts: [""]', + ' username: "elastic"', + ' password: ""', + 'setup.kibana:', + ' host: ""' + ], + textPost: 'Where `` is the password of the `elastic` user, ' + + '`` is the URL of Elasticsearch, and `` is the URL of Kibana.' + }, + RPM: { + title: 'Edit the configuration', + textPre: 'Modify `/etc/metricbeat/metricbeat.yml` to set the connection information:', + commands: [ + 'output.elasticsearch:', + ' hosts: [""]', + ' username: "elastic"', + ' password: ""', + 'setup.kibana:', + ' host: ""' + ], + textPost: 'Where `` is the password of the `elastic` user, ' + + '`` is the URL of Elasticsearch, and `` is the URL of Kibana.' + }, + WINDOWS: { + title: 'Edit the configuration', + textPre: 'Modify `C:\\Program Files\\Metricbeat\\metricbeat.yml` to set the connection information:', + commands: [ + 'output.elasticsearch:', + ' hosts: [""]', + ' username: "elastic"', + ' password: ""', + 'setup.kibana:', + ' host: ""' + ], + textPost: 'Where `` is the password of the `elastic` user, ' + + '`` is the URL of Elasticsearch, and `` is the URL of Kibana.' + } + } +}; diff --git a/src/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js b/src/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js new file mode 100644 index 0000000000000..19720fb3d0e9b --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js @@ -0,0 +1,16 @@ +export const TRYCLOUD_OPTION1 = { + title: 'Option 1: Try module in Elastic Cloud', + textPre: 'Go to [Elastic Cloud](https://cloud.elastic.co/). Register if you ' + + 'don\'t have an account.\n' + + ' * Select **Create Cluster**, leave size slider at 4 GB RAM, and click **Create**.\n' + + ' * Wait for the cluster plan to complete.\n' + + ' * Go to the new Cloud Kibana instance and follow the Kibana Home instructions.' + +}; + +export const TRYCLOUD_OPTION2 = { + title: 'Option 2: Connect local Kibana to a Cloud instance', + textPre: 'If you are running this Kibana instance against a hosted Elasticsearch instance,' + + ' proceed with manual setup.\n\n' + + 'In **Overview >> Endpoints** note **Elasticsearch** as ``.' +}; diff --git a/src/core_plugins/kibana/common/tutorials/param_types.js b/src/core_plugins/kibana/common/tutorials/param_types.js new file mode 100644 index 0000000000000..f5e5b58ac2e00 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/param_types.js @@ -0,0 +1,4 @@ +export const PARAM_TYPES = { + NUMBER: 'number', + STRING: 'string' +}; diff --git a/src/core_plugins/kibana/common/tutorials/tutorial_category.js b/src/core_plugins/kibana/common/tutorials/tutorial_category.js new file mode 100644 index 0000000000000..e112b3d36d251 --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/tutorial_category.js @@ -0,0 +1,5 @@ +export const TUTORIAL_CATEGORY = { + LOGGING: 'logging', + SECURITY: 'security', + METRICS: 'metrics' +}; diff --git a/src/core_plugins/kibana/common/tutorials/tutorial_schema.js b/src/core_plugins/kibana/common/tutorials/tutorial_schema.js new file mode 100644 index 0000000000000..ae83478224f4c --- /dev/null +++ b/src/core_plugins/kibana/common/tutorials/tutorial_schema.js @@ -0,0 +1,75 @@ +import Joi from 'joi'; +import { PARAM_TYPES } from './param_types'; +import { TUTORIAL_CATEGORY } from './tutorial_category'; + +const dashboardSchema = Joi.object({ + title: Joi.string().required(), + linkLabel: Joi.string().when('isOverview', { + is: true, + then: Joi.required(), + }), + // Is this an Overview / Entry Point dashboard? + isOverview: Joi.boolean().required() +}); + +const artifactsSchema = Joi.object({ + // Fields present in Elasticsearch documents created by this product. + exportedFields: Joi.object({ + documentationUrl: Joi.string() + }), + // Kibana dashboards created by this product. + dashboards: Joi.array().items(dashboardSchema).required() +}); + +const instructionSchema = Joi.object({ + title: Joi.string(), + textPre: Joi.string(), + commands: Joi.array().items(Joi.string()), + textPost: Joi.string() +}); + +const instructionVariantSchema = Joi.object({ + id: Joi.string().required(), + instructions: Joi.array().items(instructionSchema).required() +}); + +const instructionSetSchema = Joi.object({ + title: Joi.string(), + // Variants (OSes, languages, etc.) for which tutorial instructions are specified. + instructionVariants: Joi.array().items(instructionVariantSchema).required() +}); + +const paramSchema = Joi.object({ + defaultValue: Joi.required(), + id: Joi.string().regex(/^[a-zA-Z_]+$/).required(), + label: Joi.string().required(), + type: Joi.string().valid(Object.values(PARAM_TYPES)).required() +}); + +const instructionsSchema = Joi.object({ + instructionSets: Joi.array().items(instructionSetSchema).required(), + params: Joi.array().items(paramSchema) +}); + +export const tutorialSchema = { + id: Joi.string().regex(/^[a-zA-Z0-9-]+$/).required(), + category: Joi.string().valid(Object.values(TUTORIAL_CATEGORY)).required(), + name: Joi.string().required(), + shortDescription: Joi.string().required(), + iconPath: Joi.string(), + longDescription: Joi.string().required(), + completionTimeMinutes: Joi.number().integer(), + previewImagePath: Joi.string(), + + // kibana and elastic cluster running on prem + onPrem: instructionsSchema.required(), + + // kibana and elastic cluster running in elastic's cloud + elasticCloud: instructionsSchema.required(), + + // kibana running on prem and elastic cluster running in elastic's cloud + onPremElasticCloud: instructionsSchema.required(), + + // Elastic stack artifacts produced by product when it is setup and run. + artifacts: artifactsSchema, +}; diff --git a/src/core_plugins/kibana/index.js b/src/core_plugins/kibana/index.js index 7b24b12dc048d..6175bfd115599 100644 --- a/src/core_plugins/kibana/index.js +++ b/src/core_plugins/kibana/index.js @@ -8,9 +8,11 @@ import search from './server/routes/api/search'; import { scrollSearchApi } from './server/routes/api/scroll_search'; import { importApi } from './server/routes/api/import'; import { exportApi } from './server/routes/api/export'; +import { homeApi } from './server/routes/api/home'; import scripts from './server/routes/api/scripts'; import { registerSuggestionsApi } from './server/routes/api/suggestions'; import { registerFieldFormats } from './server/field_formats/register'; +import { registerTutorials } from './server/tutorials/register'; import * as systemApi from './server/lib/system_api'; import handleEsError from './server/lib/handle_es_error'; import mappings from './mappings.json'; @@ -147,9 +149,10 @@ export default function (kibana) { scrollSearchApi(server); importApi(server); exportApi(server); + homeApi(server); registerSuggestionsApi(server); registerFieldFormats(server); - + registerTutorials(server); server.expose('systemApi', systemApi); server.expose('handleEsError', handleEsError); server.expose('injectVars', injectVars); diff --git a/src/core_plugins/kibana/public/home/components/feature_directory.js b/src/core_plugins/kibana/public/home/components/feature_directory.js index fb573c03498b1..c79ac1078b5d6 100644 --- a/src/core_plugins/kibana/public/home/components/feature_directory.js +++ b/src/core_plugins/kibana/public/home/components/feature_directory.js @@ -52,7 +52,7 @@ export class FeatureDirectory extends React.Component { renderTabs = () => { return this.tabs.map((tab, index) => ( this.onSelectedTabChanged(tab.id)} isSelected={tab.id === this.state.selectedTabId} key={index} @@ -97,10 +97,10 @@ export class FeatureDirectory extends React.Component {
- + {this.renderTabs()} - + { this.renderDirectories() }
diff --git a/src/core_plugins/kibana/public/home/components/home.js b/src/core_plugins/kibana/public/home/components/home.js index a150ea8ec33c8..4b1e53f2ff7cc 100644 --- a/src/core_plugins/kibana/public/home/components/home.js +++ b/src/core_plugins/kibana/public/home/components/home.js @@ -3,6 +3,12 @@ import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { KuiLinkButton, + KuiCardGroup, + KuiCard, + KuiCardDescription, + KuiCardDescriptionTitle, + KuiCardDescriptionText, + KuiCardFooter, } from 'ui_framework/components'; import { @@ -18,6 +24,9 @@ import { import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import chrome from 'ui/chrome'; +const kbnBaseUrl = chrome.getInjected('kbnBaseUrl'); + export function Home({ addBasePath, directories }) { const renderDirectories = (category) => { @@ -39,6 +48,95 @@ export function Home({ addBasePath, directories }) { }); }; + const renderPromo = () => { + const cardStyle = { + width: '250px', + 'minWidth': '200px' + }; + return ( +
+ + + + + +

+ Logging +

+
+ + + Ingest logs from popular data sources and easily visualize in preconfigured dashboards. + +
+ + + + Add data + + +
+ + + + + +

+ Metrics +

+
+ + + Collect metrics from the operating system and services running on your servers. + +
+ + + + Add data + + +
+ + + + + +

+ Security Analytics +

+
+ + + Centralize security events for interactive investigation in ready-to-go visualizations. + +
+ + + + Add data + + +
+
+
+ ); + }; return ( @@ -49,7 +147,7 @@ export function Home({ addBasePath, directories }) { > -

Welcome to Kibana

+

Add Data to Kibana

@@ -74,6 +172,13 @@ export function Home({ addBasePath, directories }) { +

+ These turnkey solutions will help you quickly add data into Kibana and turn it into + pre-built dashboards and monitoring systems. +

+ + { renderPromo() } + diff --git a/src/core_plugins/kibana/public/home/components/home_app.js b/src/core_plugins/kibana/public/home/components/home_app.js index 2a5622fda45a2..4256f9a94f713 100644 --- a/src/core_plugins/kibana/public/home/components/home_app.js +++ b/src/core_plugins/kibana/public/home/components/home_app.js @@ -2,16 +2,51 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Home } from './home'; import { FeatureDirectory } from './feature_directory'; +import { TutorialDirectory } from './tutorial_directory'; +import { Tutorial } from './tutorial/tutorial'; import { HashRouter as Router, Switch, Route } from 'react-router-dom'; +import { getTutorial } from '../load_tutorials'; +import { replaceTemplateStrings } from './tutorial/replace_template_strings'; +import chrome from 'ui/chrome'; export function HomeApp({ addBasePath, directories }) { + + const renderTutorialDirectory = (props) => { + return ( + + ); + }; + + const renderTutorial = (props) => { + return ( + + ); + }; + return ( + + diff --git a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/content.test.js.snap b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/content.test.js.snap new file mode 100644 index 0000000000000..922c6d013f3e4 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/content.test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render content with markdown 1`] = ` +
I am some content with markdown

+", + } + } +/> +`; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap new file mode 100644 index 0000000000000..72a0209d9d6f4 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render ELASTIC_CLOUD instructions when isCloudEnabled is true 1`] = ` +
+
+ + Home + + / + + Add Data + +
+ +
+
+ +
+
+
+
+`; + +exports[`should render ON_PREM instructions with instructions cloud toggle when isCloudEnabled is false 1`] = ` +
+
+ + Home + + / + + Add Data + +
+ +
+ +
+
+ +
+
+
+
+`; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/content.js b/src/core_plugins/kibana/public/home/components/tutorial/content.js new file mode 100644 index 0000000000000..9b3e2e80bb94f --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/content.js @@ -0,0 +1,23 @@ +import classNames from 'classnames'; +import React from 'react'; +import PropTypes from 'prop-types'; +import MarkdownIt from 'markdown-it'; + +const markdownIt = new MarkdownIt('zero', { html: false, linkify: true }); +// list of rules can be found at https://github.com/markdown-it/markdown-it/issues/361 +markdownIt.enable(['backticks', 'emphasis', 'link', 'list']); + +export function Content({ className, text }) { + const classes = classNames('kuiText kuiSubduedText tutorialContent markdown-body', className); + return ( +
+ ); +} + +Content.propTypes = { + className: PropTypes.string, + text: PropTypes.string.isRequired +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/content.test.js b/src/core_plugins/kibana/public/home/components/tutorial/content.test.js new file mode 100644 index 0000000000000..2dac509645c64 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/content.test.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + Content, +} from './content'; + +test('should render content with markdown', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); // eslint-disable-line +}); diff --git a/src/core_plugins/kibana/public/home/components/tutorial/copy_button.js b/src/core_plugins/kibana/public/home/components/tutorial/copy_button.js new file mode 100644 index 0000000000000..0ce94ef1bd219 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/copy_button.js @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Tooltip } from 'pui-react-tooltip'; +import { OverlayTrigger } from 'pui-react-overlay-trigger'; +import { KuiButton } from 'ui_framework/components'; +import { copyToClipboard } from '../../copy_to_clipboard'; + +const UNCOPIED_MSG = 'Copy to clipboard'; +const COPIED_MSG = 'Copied'; + +export class CopyButton extends React.Component { + + constructor(props) { + super(props); + + this.state = { + tooltipText: UNCOPIED_MSG + }; + } + + copySnippet = () => { + const isCopied = copyToClipboard(this.props.textToCopy); + if (isCopied) { + this.setState({ + tooltipText: COPIED_MSG, + }); + } + } + + resetTooltipText = () => { + this.setState({ + tooltipText: UNCOPIED_MSG, + }); + } + + render() { + return ( + + {this.state.tooltipText} + + } + > + + Copy snippet + + + ); + } +} + +CopyButton.propTypes = { + textToCopy: PropTypes.string.isRequired, +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/instruction.js b/src/core_plugins/kibana/public/home/components/tutorial/instruction.js new file mode 100644 index 0000000000000..86b53cf5483e9 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/instruction.js @@ -0,0 +1,85 @@ +import './instruction.less'; +import React from 'react'; +import PropTypes from 'prop-types'; +import { Content } from './content'; +import { CopyButton } from './copy_button'; + +import { + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; + +export function Instruction({ commands, paramValues, textPost, textPre, replaceTemplateStrings }) { + let pre; + if (textPre) { + pre = ( + + ); + } + + let post; + if (textPost) { + post = ( +
+ + +
+ ); + } + + let copyButton; + let commandBlock; + if (commands) { + const cmdText = commands.map(cmd => { return replaceTemplateStrings(cmd, paramValues); }).join('\n'); + copyButton = ( + + ); + commandBlock = ( +
+ + + {cmdText} + +
+ ); + } + + return ( +
+ + + + {pre} + + + + {copyButton} + + + + {commandBlock} + + {post} + +
+ ); +} + +Instruction.propTypes = { + commands: PropTypes.array, + paramValues: PropTypes.object.isRequired, + textPost: PropTypes.string, + textPre: PropTypes.string, + replaceTemplateStrings: PropTypes.func.isRequired, +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/instruction.less b/src/core_plugins/kibana/public/home/components/tutorial/instruction.less new file mode 100644 index 0000000000000..dbc3639b62d82 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/instruction.less @@ -0,0 +1,8 @@ +@import (reference) "~ui/styles/variables.less"; + +.instruction { + border-left: medium solid @globalColorLightGray; + margin-left: 16px; + padding-left: 24px; +} + diff --git a/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.js b/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.js new file mode 100644 index 0000000000000..b1f1eb493c842 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.js @@ -0,0 +1,174 @@ +import classNames from 'classnames'; +import React from 'react'; +import PropTypes from 'prop-types'; +import { + KuiBar, + KuiBarSection, + KuiTabs, + KuiTab +} from 'ui_framework/components'; +import { Instruction } from './instruction'; +import { Step } from './step'; +import { ParameterForm } from './parameter_form'; +import { getDisplayText } from '../../../../common/tutorials/instruction_variant'; + +export class InstructionSet extends React.Component { + + constructor(props) { + super(props); + + this.tabs = props.instructionVariants.map((variant) => { + return { + id: variant.id, + name: getDisplayText(variant.id) + }; + }); + + this.state = { + isParamFormVisible: false + }; + + if (this.tabs.length > 0) { + this.state.selectedTabId = this.tabs[0].id; + } + } + + handleToggleVisibility = () => { + this.setState(prevState => ( + { isParamFormVisible: !prevState.isParamFormVisible } + )); + } + + onSelectedTabChanged = id => { + this.setState({ + selectedTabId: id, + }); + }; + + renderTabs = () => { + return this.tabs.map((tab, index) => ( + this.onSelectedTabChanged(tab.id)} + isSelected={tab.id === this.state.selectedTabId} + key={index} + > + {tab.name} + + )); + } + + renderInstructions = () => { + const instructionVariant = this.props.instructionVariants.find(variant => { + return variant.id === this.state.selectedTabId; + }); + if (!instructionVariant) { + return; + } + + return instructionVariant.instructions.map((instruction, index) => ( + + + + )); + } + + renderHeader = () => { + let paramsVisibilityToggle; + if (this.props.params) { + const visibilityToggleClasses = classNames('kuiIcon kuiSideBarCollapsibleTitle__caret', { + 'fa-caret-right': !this.state.isParamFormVisible, + 'fa-caret-down': this.state.isParamFormVisible + }); + paramsVisibilityToggle = ( +
+
+ + + Customize your code snippets + +
+
+ ); + } + + return ( + + +
+ {this.props.title} +
+
+ + + {paramsVisibilityToggle} + +
+ ); + } + + render() { + let paramsForm; + if (this.props.params && this.state.isParamFormVisible) { + paramsForm = ( + + ); + } + + return ( +
+ + {this.renderHeader()} + + {paramsForm} + + + {this.renderTabs()} + + + {this.renderInstructions()} + +
+ ); + } +} + +const instructionShape = PropTypes.shape({ + title: PropTypes.string, + textPre: PropTypes.string, + commands: PropTypes.arrayOf(PropTypes.string), + textPost: PropTypes.string +}); + +const instructionVariantShape = PropTypes.shape({ + id: PropTypes.string.isRequired, + instructions: PropTypes.arrayOf(instructionShape).isRequired, +}); + +InstructionSet.propTypes = { + title: PropTypes.string.isRequired, + instructionVariants: PropTypes.arrayOf(instructionVariantShape).isRequired, + offset: PropTypes.number.isRequired, + params: PropTypes.array, + paramValues: PropTypes.object.isRequired, + setParameter: PropTypes.func, + replaceTemplateStrings: PropTypes.func.isRequired, +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/introduction.js b/src/core_plugins/kibana/public/home/components/tutorial/introduction.js new file mode 100644 index 0000000000000..54e16e5094223 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/introduction.js @@ -0,0 +1,48 @@ +import './introduction.less'; +import React from 'react'; +import PropTypes from 'prop-types'; +import { Content } from './content'; + +import { + EuiImage, +} from '@elastic/eui'; + +export function Introduction({ description, previewUrl, title }) { + let img; + if (previewUrl) { + img = ( + + ); + } + return ( +
+
+ +
+

+ {title} +

+ +
+ +
+ {img} +
+ +
+
+ ); +} + +Introduction.propTypes = { + description: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + previewUrl: PropTypes.string +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/introduction.less b/src/core_plugins/kibana/public/home/components/tutorial/introduction.less new file mode 100644 index 0000000000000..cd3b8d106fef8 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/introduction.less @@ -0,0 +1,3 @@ +.introduction { + margin: 24px; +} diff --git a/src/core_plugins/kibana/public/home/components/tutorial/number_parameter.js b/src/core_plugins/kibana/public/home/components/tutorial/number_parameter.js new file mode 100644 index 0000000000000..c3f94ed02fcdc --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/number_parameter.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export function NumberParameter({ id, label, value, setParameter }) { + const handleChange = (evt) => { + setParameter(id, parseFloat(evt.target.value)); + }; + + return ( +
+ +
+ +
+
+ ); +} + +NumberParameter.propTypes = { + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + value: PropTypes.number.isRequired, + setParameter: PropTypes.func.isRequired, +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/parameter_form.js b/src/core_plugins/kibana/public/home/components/tutorial/parameter_form.js new file mode 100644 index 0000000000000..686cb1742aab6 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/parameter_form.js @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { NumberParameter } from './number_parameter'; +import { StringParameter } from './string_parameter'; + +export class ParameterForm extends React.Component { + + renderInputs = () => { + return this.props.params.map(param => { + switch (param.type) { + case 'number': + return ( + + ); + case 'string': + return ( + + ); + default: + throw new Error(`Unhandled parameter type ${param.type}`); + } + }); + } + + render() { + return ( +
+ +
+ {this.renderInputs()} +
+ +
+ ); + } +} + +const paramsShape = PropTypes.shape({ + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, +}); + +ParameterForm.propTypes = { + params: PropTypes.arrayOf(paramsShape).isRequired, + paramValues: PropTypes.object.isRequired, + setParameter: PropTypes.func.isRequired +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/radio_button_group.js b/src/core_plugins/kibana/public/home/components/tutorial/radio_button_group.js new file mode 100644 index 0000000000000..9897f00cbbb24 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/radio_button_group.js @@ -0,0 +1,74 @@ +import './radio_button_group.less'; +import React from 'react'; +import PropTypes from 'prop-types'; +import { + KuiButtonGroup, + KuiButton +} from 'ui_framework/components'; + +export class RadioButtonGroup extends React.Component { + + constructor(props) { + super(props); + + this.state = {}; + + if (props.buttons.length > 0) { + const matchingButton = props.buttons.find(button => { + return props.selectedBtnLabel === button.label; + }); + if (matchingButton) { + this.state.selectedBtnLabel = props.selectedBtnLabel; + } else { + this.state.selectedBtnLabel = props.buttons[0].label; + } + } + } + + renderButtons = () => { + return this.props.buttons.map((button, index) => { + const handleOnClick = () => { + this.setState({ + selectedBtnLabel: button.label + }); + button.onClick(); + }; + + let buttonType = 'secondary'; + if (button.label === this.state.selectedBtnLabel) { + buttonType = 'primary'; + } + return ( + + {button.label} + + ); + }); + } + + render = () => { + return ( + + {this.renderButtons()} + + ); + } +} + +RadioButtonGroup.propTypes = { + buttons: PropTypes.arrayOf(PropTypes.shape({ + onClick: PropTypes.func.isRequired, + label: PropTypes.string.isRequired, + dataTestSubj: PropTypes.string + })).isRequired, + selectedBtnLabel: PropTypes.string +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/radio_button_group.less b/src/core_plugins/kibana/public/home/components/tutorial/radio_button_group.less new file mode 100644 index 0000000000000..8630f2caf2570 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/radio_button_group.less @@ -0,0 +1,10 @@ + +// remove space between buttons +.kuiRadioButton { + margin-left: 0px !important; +} + +// give primary button same border as secondary button so they are even heights when placed side-by-side +.kuiRadioButton.kuiButton--primary { + border: solid 1px #0079a5; +} diff --git a/src/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js b/src/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js new file mode 100644 index 0000000000000..63c3ede5cf603 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js @@ -0,0 +1,35 @@ +import Mustache from 'mustache'; +import chrome from 'ui/chrome'; +import { metadata } from 'ui/metadata'; +import { + DOC_LINK_VERSION, + ELASTIC_WEBSITE_URL, + documentationLinks +} from 'ui/documentation_links/documentation_links'; + +const TEMPLATE_TAGS = ['{', '}']; + +export function replaceTemplateStrings(text, params = {}) { + const variables = { + config: { + cloud: { + id: chrome.getInjected('cloudId') + }, + docs: { + base_url: ELASTIC_WEBSITE_URL, + beats: { + filebeat: documentationLinks.filebeat.base, + metricbeat: documentationLinks.metricbeat.base + }, + logstash: documentationLinks.logstash.base, + version: DOC_LINK_VERSION + }, + kibana: { + version: metadata.version + } + }, + params: params + }; + Mustache.parse(text, TEMPLATE_TAGS); + return Mustache.render(text, variables); +} diff --git a/src/core_plugins/kibana/public/home/components/tutorial/step.js b/src/core_plugins/kibana/public/home/components/tutorial/step.js new file mode 100644 index 0000000000000..820aca1fe1d8d --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/step.js @@ -0,0 +1,30 @@ +import './step.less'; +import React from 'react'; +import PropTypes from 'prop-types'; + +export function Step({ children, step, title }) { + return ( +
+ +
+
+ {step} +
+

+ {title} +

+
+ +
+ {children} +
+ +
+ ); +} + +Step.propTypes = { + children: PropTypes.node.isRequired, + step: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/step.less b/src/core_plugins/kibana/public/home/components/tutorial/step.less new file mode 100644 index 0000000000000..3bbb3cd654bde --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/step.less @@ -0,0 +1,17 @@ +@import (reference) "~ui/styles/variables.less"; + +.step { + width: 32px; + height: 32px; + display: inline-block; + line-height: 32px; + text-align: center; + color: @white; + border-radius: 100%; + background-color: @globalColorBlue; +} + +.title { + display: inline-block; + margin-left: 16px; +} diff --git a/src/core_plugins/kibana/public/home/components/tutorial/string_parameter.js b/src/core_plugins/kibana/public/home/components/tutorial/string_parameter.js new file mode 100644 index 0000000000000..e8e425819d6c8 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/string_parameter.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export function StringParameter({ id, label, value, setParameter }) { + const handleChange = (evt) => { + setParameter(id, evt.target.value); + }; + + return ( +
+ +
+ +
+
+ ); +} + +StringParameter.propTypes = { + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + setParameter: PropTypes.func.isRequired, +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/tutorial.js b/src/core_plugins/kibana/public/home/components/tutorial/tutorial.js new file mode 100644 index 0000000000000..1c5c319f6d32e --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/tutorial.js @@ -0,0 +1,202 @@ +import './tutorial.less'; +import _ from 'lodash'; +import React from 'react'; +import PropTypes from 'prop-types'; +import { Introduction } from './introduction'; +import { InstructionSet } from './instruction_set'; +import { RadioButtonGroup } from './radio_button_group'; + +const INSTRUCTIONS_TYPE = { + ELASTIC_CLOUD: 'elasticCloud', + ON_PREM: 'onPrem', + ON_PREM_ELASTIC_CLOUD: 'onPremElasticCloud' +}; + +export class Tutorial extends React.Component { + + constructor(props) { + super(props); + + this.state = { + notFound: false, + paramValues: {}, + tutorial: null + }; + + if (props.isCloudEnabled) { + this.state.visibleInstructions = INSTRUCTIONS_TYPE.ELASTIC_CLOUD; + } else { + this.state.visibleInstructions = INSTRUCTIONS_TYPE.ON_PREM; + } + } + + componentWillMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } + + async componentDidMount() { + const tutorial = await this.props.getTutorial(this.props.tutorialId); + + if (!this._isMounted) { + return; + } + + if (tutorial) { + // eslint-disable-next-line react/no-did-mount-set-state + this.setState({ + tutorial: tutorial + }, this.setParamDefaults); + } else { + // eslint-disable-next-line react/no-did-mount-set-state + this.setState({ + notFound: true, + }); + } + } + + getInstructions = () => { + if (!this.state.tutorial) { + return { instructionSets: [] }; + } + + switch(this.state.visibleInstructions) { + case INSTRUCTIONS_TYPE.ELASTIC_CLOUD: + return this.state.tutorial.elasticCloud; + case INSTRUCTIONS_TYPE.ON_PREM: + return this.state.tutorial.onPrem; + case INSTRUCTIONS_TYPE.ON_PREM_ELASTIC_CLOUD: + return this.state.tutorial.onPremElasticCloud; + default: + throw new Error(`Unhandled instruction type ${this.state.visibleInstructions}`); + } + } + + setParamDefaults = () => { + const instructions = this.getInstructions(); + const paramValues = {}; + if (instructions.params) { + instructions.params.forEach((param => { + paramValues[param.id] = param.defaultValue; + })); + } + this.setState({ + paramValues: paramValues + }); + } + + setVisibleInstructions = (instructionsType) => { + this.setState({ + visibleInstructions: instructionsType + }, this.setParamDefaults); + } + + setParameter = (paramId, newValue) => { + this.setState(previousState => { + const paramValues = _.cloneDeep(previousState.paramValues); + paramValues[paramId] = newValue; + return { paramValues: paramValues }; + }); + } + + onPrem = () => { + this.setVisibleInstructions(INSTRUCTIONS_TYPE.ON_PREM); + } + + onPremElasticCloud = () => { + this.setVisibleInstructions(INSTRUCTIONS_TYPE.ON_PREM_ELASTIC_CLOUD); + } + + renderInstructionSetsToggle = () => { + if (!this.props.isCloudEnabled) { + const radioButtons = [ + { onClick: this.onPrem, label: 'On premise', dataTestSubj: 'onPremBtn' }, + { onClick: this.onPremElasticCloud, label: 'Elastic Cloud', dataTestSubj: 'onPremElasticCloudBtn' }, + ]; + return ( + + ); + } + } + + renderInstructionSets = (instructions) => { + let offset = 1; + return instructions.instructionSets.map((instructionSet, index) => { + const currentOffset = offset; + offset += instructionSet.instructionVariants[0].instructions.length; + return ( + + ); + }); + } + + render() { + let content; + if (this.state.notFound) { + content = ( +
+

+ Unable to find tutorial {this.props.tutorialId} +

+
+ ); + } + + if (this.state.tutorial) { + let previewUrl; + if (this.state.tutorial.previewImagePath) { + previewUrl = this.props.addBasePath(this.state.tutorial.previewImagePath); + } + + const instructions = this.getInstructions(); + content = ( +
+ + +
+ {this.renderInstructionSetsToggle()} +
+ +
+ {this.renderInstructionSets(instructions)} +
+
+ ); + } + return ( +
+
+ Home / Add Data + {content} +
+
+ ); + } +} + +Tutorial.propTypes = { + addBasePath: PropTypes.func.isRequired, + isCloudEnabled: PropTypes.bool.isRequired, + getTutorial: PropTypes.func.isRequired, + replaceTemplateStrings: PropTypes.func.isRequired, + tutorialId: PropTypes.string.isRequired +}; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/tutorial.less b/src/core_plugins/kibana/public/home/components/tutorial/tutorial.less new file mode 100644 index 0000000000000..ddc39e54f04f0 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/tutorial.less @@ -0,0 +1,17 @@ +.tutorialContent { + /* + * 1. remove bottom margin placed on p element by bootstrap + */ + p { + margin-bottom: 0; /* 1 */ + } +} + +.text-center > .kuiButtonGroup { + display: inline-block !important; +} + +.homePanel { + background: white; + padding: 24px; +} diff --git a/src/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js b/src/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js new file mode 100644 index 0000000000000..c20f83e272f41 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js @@ -0,0 +1,87 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { findTestSubject } from '@elastic/eui/lib/test'; + +import { + Tutorial, +} from './tutorial'; + +function buildInstructionSet(type) { + return { + instructionSets: [ + { + title: 'Instruction title', + instructionVariants: [ + { + id: 'platform id', + instructions: [ + { + title: `${type} instructions`, + } + ] + } + ] + } + ] + }; +} +const tutorial = { + name: 'jest test tutorial', + longDescription: 'tutorial used to drive jest tests', + elasticCloud: buildInstructionSet('elasticCloud'), + onPrem: buildInstructionSet('onPrem'), + onPremElasticCloud: buildInstructionSet('onPremElasticCloud') +}; +const loadTutorialPromise = Promise.resolve(tutorial); +const getTutorial = () => { + return loadTutorialPromise; +}; +const addBasePath = (path) => { + return `BASE_PATH/${path}`; +}; +const replaceTemplateStrings = (text) => { + return text; +}; + +test('should render ON_PREM instructions with instructions cloud toggle when isCloudEnabled is false', () => { + const component = shallow(); + loadTutorialPromise.then(() => { + component.update(); + expect(component).toMatchSnapshot(); // eslint-disable-line + }); +}); + +test('should render ELASTIC_CLOUD instructions when isCloudEnabled is true', () => { + const component = shallow(); + loadTutorialPromise.then(() => { + component.update(); + expect(component).toMatchSnapshot(); // eslint-disable-line + }); +}); + +test('should display ON_PREM_ELASTIC_CLOUD instructions when cloud toggle is clicked', () => { + const component = mount(); + loadTutorialPromise.then(() => { + component.update(); + findTestSubject(component, 'onPremElasticCloudBtn').simulate('click'); + expect(component.state('visibleInstructions')).toBe('onPremElasticCloud'); + }); +}); diff --git a/src/core_plugins/kibana/public/home/components/tutorial_directory.js b/src/core_plugins/kibana/public/home/components/tutorial_directory.js new file mode 100644 index 0000000000000..c8ab44334bc7a --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial_directory.js @@ -0,0 +1,120 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Synopsis } from './synopsis'; +import { + KuiTabs, + KuiTab, + KuiFlexItem, + KuiFlexGrid, +} from 'ui_framework/components'; +import { getTutorials } from '../load_tutorials'; + +const ALL = 'all'; + +export class TutorialDirectory extends React.Component { + + constructor(props) { + super(props); + + this.tabs = [{ + id: ALL, + name: 'All', + }, { + id: 'logging', + name: 'Logging', + }, { + id: 'metrics', + name: 'Metrics', + }, { + id: 'security', + name: 'Security Analytics', + }]; + + let openTab = ALL; + if (props.openTab && this.tabs.some(tab => { return tab.id === props.openTab; })) { + openTab = props.openTab; + } + this.state = { + selectedTabId: openTab, + tutorials: [] + }; + } + + async componentWillMount() { + const tutorials = await getTutorials(); + this.setState({ + tutorials: tutorials, + }); + } + + onSelectedTabChanged = id => { + this.setState({ + selectedTabId: id, + }); + }; + + renderTabs = () => { + return this.tabs.map((tab, index) => ( + this.onSelectedTabChanged(tab.id)} + isSelected={tab.id === this.state.selectedTabId} + key={index} + > + {tab.name} + + )); + } + + renderTutorials = () => { + return this.state.tutorials + .filter((tutorial) => { + if (this.state.selectedTabId === ALL) { + return true; + } + return this.state.selectedTabId === tutorial.category; + }) + .map((tutorial) => { + return ( + + + + ); + }); + }; + + render() { + return ( +
+
+ +
+ Home +

+ Add Data to Kibana +

+
+ +
+ + {this.renderTabs()} + + + { this.renderTutorials() } + +
+ +
+
+ ); + } +} + +TutorialDirectory.propTypes = { + addBasePath: PropTypes.func.isRequired, + openTab: PropTypes.string +}; diff --git a/src/core_plugins/kibana/public/home/copy_to_clipboard.js b/src/core_plugins/kibana/public/home/copy_to_clipboard.js new file mode 100644 index 0000000000000..aee6ed3472690 --- /dev/null +++ b/src/core_plugins/kibana/public/home/copy_to_clipboard.js @@ -0,0 +1,47 @@ + +function createHiddenTextElement(text) { + const textElement = document.createElement('span'); + textElement.textContent = text; + textElement.style.all = 'unset'; + // prevents scrolling to the end of the page + textElement.style.position = 'fixed'; + textElement.style.top = 0; + textElement.style.clip = 'rect(0, 0, 0, 0)'; + // used to preserve spaces and line breaks + textElement.style.whiteSpace = 'pre'; + // do not inherit user-select (it may be `none`) + textElement.style.webkitUserSelect = 'text'; + textElement.style.MozUserSelect = 'text'; + textElement.style.msUserSelect = 'text'; + textElement.style.userSelect = 'text'; + return textElement; +} + +export function copyToClipboard(text) { + let isCopied = true; + const range = document.createRange(); + const selection = document.getSelection(); + const elementToBeCopied = createHiddenTextElement(text); + + document.body.appendChild(elementToBeCopied); + range.selectNode(elementToBeCopied); + selection.empty(); + selection.addRange(range); + + if (!document.execCommand('copy')) { + isCopied = false; + console.warn('Unable to copy to clipboard.'); // eslint-disable-line no-console + } + + if (selection) { + if (typeof selection.removeRange === 'function') { + selection.removeRange(range); + } else { + selection.removeAllRanges(); + } + } + + document.body.removeChild(elementToBeCopied); + + return isCopied; +} diff --git a/src/core_plugins/kibana/public/home/home.less b/src/core_plugins/kibana/public/home/home.less index b96f1dbd95097..df4ed0ddfb0ca 100644 --- a/src/core_plugins/kibana/public/home/home.less +++ b/src/core_plugins/kibana/public/home/home.less @@ -5,7 +5,19 @@ min-height: 100vh; } -.homeFeatureDirectory { +.kuiCard__descriptionTitle img { + display: inline-block; + width: 64px; + height: 64px; + margin-bottom: 10px; +} + +.kuiCardGroup .kuiCard { + flex: 1 1 0 !important; + background-color: white; +} + +.homeDirectory { background: @white; margin: 0; border-left: 1px solid @globalColorLightGray; @@ -14,6 +26,6 @@ padding: 16px; } -.homeFeatureCategoryTab { +.homeDirectoryTab { background-color: @globalColorLightestGray; } diff --git a/src/core_plugins/kibana/public/home/index.js b/src/core_plugins/kibana/public/home/index.js index 3098ccd508be1..15e3999157fd4 100644 --- a/src/core_plugins/kibana/public/home/index.js +++ b/src/core_plugins/kibana/public/home/index.js @@ -27,3 +27,5 @@ function getRoute() { // redirect us to the default page by encountering a url it isn't marked as being able to handle. routes.when('/home', getRoute()); routes.when('/home/feature_directory', getRoute()); +routes.when('/home/tutorial_directory/:tab?', getRoute()); +routes.when('/home/tutorial/:id', getRoute()); diff --git a/src/core_plugins/kibana/public/home/load_tutorials.js b/src/core_plugins/kibana/public/home/load_tutorials.js new file mode 100644 index 0000000000000..46aeec6ff6add --- /dev/null +++ b/src/core_plugins/kibana/public/home/load_tutorials.js @@ -0,0 +1,52 @@ +import _ from 'lodash'; +import chrome from 'ui/chrome'; +import { notify } from 'ui/notify'; + +const baseUrl = chrome.addBasePath('/api/kibana/home/tutorials'); +const headers = new Headers(); +headers.append('Accept', 'application/json'); +headers.append('Content-Type', 'application/json'); +headers.append('kbn-xsrf', 'kibana'); + +let tutorials = []; +let turorialsLoaded = false; + +async function loadTutorials() { + try { + const response = await fetch(baseUrl, { + method: 'get', + credentials: 'include', + headers: headers, + }); + if (response.status >= 300) { + throw new Error(`Request failed with status code: ${response.status}`); + } + + tutorials = await response.json(); + turorialsLoaded = true; + } catch(err) { + notify.error(`Unable to load tutorials, ${err}`); + } +} + +export async function getTutorials() { + if (!turorialsLoaded) { + await loadTutorials(); + } + + return _.cloneDeep(tutorials); +} + +export async function getTutorial(id) { + if (!turorialsLoaded) { + await loadTutorials(); + } + + const tutorial = tutorials.find(tutorial => { + return tutorial.id === id; + }); + + if (tutorial) { + return _.cloneDeep(tutorial); + } +} diff --git a/src/core_plugins/kibana/public/home/tutorial_resources/apache_logs/screenshot.png b/src/core_plugins/kibana/public/home/tutorial_resources/apache_logs/screenshot.png new file mode 100644 index 0000000000000..a2039096ce041 Binary files /dev/null and b/src/core_plugins/kibana/public/home/tutorial_resources/apache_logs/screenshot.png differ diff --git a/src/core_plugins/kibana/public/home/tutorial_resources/apache_metrics/screenshot.png b/src/core_plugins/kibana/public/home/tutorial_resources/apache_metrics/screenshot.png new file mode 100644 index 0000000000000..ac40d5a637a5f Binary files /dev/null and b/src/core_plugins/kibana/public/home/tutorial_resources/apache_metrics/screenshot.png differ diff --git a/src/core_plugins/kibana/public/home/tutorial_resources/mysql_logs/screenshot.png b/src/core_plugins/kibana/public/home/tutorial_resources/mysql_logs/screenshot.png new file mode 100644 index 0000000000000..d380042ca72e8 Binary files /dev/null and b/src/core_plugins/kibana/public/home/tutorial_resources/mysql_logs/screenshot.png differ diff --git a/src/core_plugins/kibana/public/home/tutorial_resources/mysql_metrics/screenshot.png b/src/core_plugins/kibana/public/home/tutorial_resources/mysql_metrics/screenshot.png new file mode 100644 index 0000000000000..e3091f5156c5a Binary files /dev/null and b/src/core_plugins/kibana/public/home/tutorial_resources/mysql_metrics/screenshot.png differ diff --git a/src/core_plugins/kibana/public/home/tutorial_resources/nginx_logs/screenshot.png b/src/core_plugins/kibana/public/home/tutorial_resources/nginx_logs/screenshot.png new file mode 100644 index 0000000000000..10522377112cb Binary files /dev/null and b/src/core_plugins/kibana/public/home/tutorial_resources/nginx_logs/screenshot.png differ diff --git a/src/core_plugins/kibana/public/home/tutorial_resources/nginx_metrics/screenshot.png b/src/core_plugins/kibana/public/home/tutorial_resources/nginx_metrics/screenshot.png new file mode 100644 index 0000000000000..003ea39191cdf Binary files /dev/null and b/src/core_plugins/kibana/public/home/tutorial_resources/nginx_metrics/screenshot.png differ diff --git a/src/core_plugins/kibana/public/home/tutorial_resources/system_logs/screenshot.png b/src/core_plugins/kibana/public/home/tutorial_resources/system_logs/screenshot.png new file mode 100644 index 0000000000000..dfb1d832e3a86 Binary files /dev/null and b/src/core_plugins/kibana/public/home/tutorial_resources/system_logs/screenshot.png differ diff --git a/src/core_plugins/kibana/public/home/tutorial_resources/system_metrics/screenshot.png b/src/core_plugins/kibana/public/home/tutorial_resources/system_metrics/screenshot.png new file mode 100644 index 0000000000000..2ee3d14f164b6 Binary files /dev/null and b/src/core_plugins/kibana/public/home/tutorial_resources/system_metrics/screenshot.png differ diff --git a/src/core_plugins/kibana/server/routes/api/home/index.js b/src/core_plugins/kibana/server/routes/api/home/index.js new file mode 100644 index 0000000000000..154d9eea1a33e --- /dev/null +++ b/src/core_plugins/kibana/server/routes/api/home/index.js @@ -0,0 +1,5 @@ +import { registerTutorials } from './register_tutorials'; + +export function homeApi(server) { + registerTutorials(server); +} diff --git a/src/core_plugins/kibana/server/routes/api/home/register_tutorials.js b/src/core_plugins/kibana/server/routes/api/home/register_tutorials.js new file mode 100644 index 0000000000000..b22e135288f71 --- /dev/null +++ b/src/core_plugins/kibana/server/routes/api/home/register_tutorials.js @@ -0,0 +1,10 @@ + +export function registerTutorials(server) { + server.route({ + path: '/api/kibana/home/tutorials', + method: ['GET'], + handler: async function (req, reply) { + reply(server.getTutorials()); + } + }); +} diff --git a/src/core_plugins/kibana/server/tutorials/apache_logs/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/apache_logs/elastic_cloud.js new file mode 100644 index 0000000000000..9a517988c9a9d --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_logs/elastic_cloud.js @@ -0,0 +1,50 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { FILEBEAT_CLOUD_INSTRUCTIONS } from '../../../common/tutorials/filebeat_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/apache_logs/enable.js b/src/core_plugins/kibana/server/tutorials/apache_logs/enable.js new file mode 100644 index 0000000000000..fe0ce9e416834 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_logs/enable.js @@ -0,0 +1,32 @@ +export const ENABLE_INSTRUCTIONS = { + OSX: { + title: 'Enable and configure the apache2 module', + textPre: 'From the installation directory, run:', + commands: [ + './filebeat modules enable apache2', + ], + textPost: 'Modify the settings in the `modules.d/apache2.yml` file.' + }, + DEB: { + title: 'Enable and configure the apache2 module', + commands: [ + 'sudo filebeat modules enable apache2', + ], + textPost: 'Modify the settings in the `/etc/filebeat/modules.d/apache2.yml` file.' + }, + RPM: { + title: 'Enable and configure the apache2 module', + commands: [ + 'sudo filebeat modules enable apache2', + ], + textPost: 'Modify the settings in the `/etc/filebeat/modules.d/apache2.yml` file.' + }, + WINDOWS: { + title: 'Enable and configure the apache2 module', + textPre: 'From the `C:\\Program Files\\Filebeat` folder, run:', + commands: [ + 'PS C:\\Program Files\\Filebeat> filebeat.exe modules enable apache2', + ], + textPost: 'Modify the settings in the `modules.d/apache2.yml` file.' + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/apache_logs/index.js b/src/core_plugins/kibana/server/tutorials/apache_logs/index.js new file mode 100644 index 0000000000000..405ef51fce56f --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_logs/index.js @@ -0,0 +1,34 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function apacheLogsSpecProvider() { + return { + id: 'apacheLogs', + name: 'Apache logs', + category: TUTORIAL_CATEGORY.LOGGING, + shortDescription: 'Collect and parse access and error logs created by the Apache HTTP server.', + longDescription: 'The apache2 Filebeat module parses access and error logs created by the Apache 2 HTTP server.' + + ' [Learn more]({config.docs.beats.filebeat}/filebeat-module-apache2.html)' + + ' about the apache2 module.', + //iconPath: '', TODO + artifacts: { + dashboards: [ + { + title: 'Filebeat-Apache2-Dashboard', + linkLabel: 'Apache2 logs dashboard', + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-apache2.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/apache_logs/screenshot.png', + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/apache_logs/on_prem.js b/src/core_plugins/kibana/server/tutorials/apache_logs/on_prem.js new file mode 100644 index 0000000000000..9ecaa557d2407 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_logs/on_prem.js @@ -0,0 +1,53 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP_AND_UA, + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP_AND_UA, + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP_AND_UA, + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP_AND_UA, + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/apache_logs/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/apache_logs/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..2266d33eccd1f --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_logs/on_prem_elastic_cloud.js @@ -0,0 +1,61 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/apache_metrics/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/apache_metrics/elastic_cloud.js new file mode 100644 index 0000000000000..408ae869571e7 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_metrics/elastic_cloud.js @@ -0,0 +1,50 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { METRICBEAT_CLOUD_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/apache_metrics/enable.js b/src/core_plugins/kibana/server/tutorials/apache_metrics/enable.js new file mode 100644 index 0000000000000..9e18661701c7a --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_metrics/enable.js @@ -0,0 +1,32 @@ +export const ENABLE_INSTRUCTIONS = { + OSX: { + title: 'Enable and configure the apache module', + textPre: 'From the installation directory, run:', + commands: [ + './metricbeat modules enable apache', + ], + textPost: 'Modify the settings in the `modules.d/apache.yml` file.' + }, + DEB: { + title: 'Enable and configure the apache module', + commands: [ + 'sudo metricbeat modules enable apache', + ], + textPost: 'Modify the settings in the `/etc/metricbeat/modules.d/apache.yml` file.' + }, + RPM: { + title: 'Enable and configure the apache module', + commands: [ + 'sudo metricbeat modules enable apache', + ], + textPost: 'Modify the settings in the `/etc/metricbeat/modules.d/apache.yml` file.' + }, + WINDOWS: { + title: 'Enable and configure the apache module', + textPre: 'From the `C:\\Program Files\\Metricbeat` folder, run:', + commands: [ + 'PS C:\\Program Files\\Metricbeat> metricbeat.exe modules enable apache', + ], + textPost: 'Modify the settings in the `modules.d/apache.yml` file.' + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/apache_metrics/index.js b/src/core_plugins/kibana/server/tutorials/apache_metrics/index.js new file mode 100644 index 0000000000000..9e8b84bd06a76 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_metrics/index.js @@ -0,0 +1,34 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function apacheMetricsSpecProvider() { + return { + id: 'apacheMetrics', + name: 'Apache metrics', + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: 'Fetches internal metrics from the Apache 2 HTTP server.', + longDescription: 'The `apache` Metricbeat module fetches internal metrics from the Apache 2 HTTP server.' + + ' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-apache.html)' + + ' about the apache module.', + //iconPath: '', TODO + artifacts: { + dashboards: [ + { + title: 'Metricbeat-Apache-HTTPD-server-status', + linkLabel: 'Apache metrics dashboard', + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-apache.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/apache_metrics/screenshot.png', + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/apache_metrics/on_prem.js b/src/core_plugins/kibana/server/tutorials/apache_metrics/on_prem.js new file mode 100644 index 0000000000000..8a47014420ebe --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_metrics/on_prem.js @@ -0,0 +1,49 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/apache_metrics/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/apache_metrics/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..fe1cdbcde9391 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/apache_metrics/on_prem_elastic_cloud.js @@ -0,0 +1,61 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/mysql_logs/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/mysql_logs/elastic_cloud.js new file mode 100644 index 0000000000000..9a517988c9a9d --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_logs/elastic_cloud.js @@ -0,0 +1,50 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { FILEBEAT_CLOUD_INSTRUCTIONS } from '../../../common/tutorials/filebeat_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/mysql_logs/enable.js b/src/core_plugins/kibana/server/tutorials/mysql_logs/enable.js new file mode 100644 index 0000000000000..a303c18717511 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_logs/enable.js @@ -0,0 +1,32 @@ +export const ENABLE_INSTRUCTIONS = { + OSX: { + title: 'Enable and configure the mysql module', + textPre: 'From the installation directory, run:', + commands: [ + './filebeat modules enable mysql', + ], + textPost: 'Modify the settings in the `modules.d/mysql.yml` file.' + }, + DEB: { + title: 'Enable and configure the mysql module', + commands: [ + 'sudo filebeat modules enable mysql', + ], + textPost: 'Modify the settings in the `/etc/filebeat/modules.d/mysql.yml` file.' + }, + RPM: { + title: 'Enable and configure the mysql module', + commands: [ + 'sudo filebeat modules enable mysql', + ], + textPost: 'Modify the settings in the `/etc/filebeat/modules.d/mysql.yml` file.' + }, + WINDOWS: { + title: 'Enable and configure the mysql module', + textPre: 'From the `C:\\Program Files\\Filebeat` folder, run:', + commands: [ + 'PS C:\\Program Files\\Filebeat> filebeat.exe modules enable mysql', + ], + textPost: 'Modify the settings in the `modules.d/mysql.yml` file.' + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/mysql_logs/index.js b/src/core_plugins/kibana/server/tutorials/mysql_logs/index.js new file mode 100644 index 0000000000000..e87ecb9415e25 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_logs/index.js @@ -0,0 +1,34 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function mysqlLogsSpecProvider() { + return { + id: 'mysqlLogs', + name: 'MySQL logs', + category: TUTORIAL_CATEGORY.LOGGING, + shortDescription: 'Collect and parse error and slow logs created by MySQL.', + longDescription: 'The `mysql` Filebeat module parses error and slow logs created by MySQL.' + + ' [Learn more]({config.docs.beats.filebeat}/filebeat-module-mysql.html)' + + ' about the `mysql` module.', + //iconPath: '', TODO + artifacts: { + dashboards: [ + { + title: 'Filebeat-MySQL-Dashboard', + linkLabel: 'MySQL logs dashboard', + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-mysql.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/mysql_logs/screenshot.png', + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/mysql_logs/on_prem.js b/src/core_plugins/kibana/server/tutorials/mysql_logs/on_prem.js new file mode 100644 index 0000000000000..e280230d7b78e --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_logs/on_prem.js @@ -0,0 +1,49 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/mysql_logs/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/mysql_logs/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..2266d33eccd1f --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_logs/on_prem_elastic_cloud.js @@ -0,0 +1,61 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/mysql_metrics/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/mysql_metrics/elastic_cloud.js new file mode 100644 index 0000000000000..408ae869571e7 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_metrics/elastic_cloud.js @@ -0,0 +1,50 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { METRICBEAT_CLOUD_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/mysql_metrics/enable.js b/src/core_plugins/kibana/server/tutorials/mysql_metrics/enable.js new file mode 100644 index 0000000000000..815f68f794910 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_metrics/enable.js @@ -0,0 +1,32 @@ +export const ENABLE_INSTRUCTIONS = { + OSX: { + title: 'Enable and configure the mysql module', + textPre: 'From the installation directory, run:', + commands: [ + './metricbeat modules enable mysql', + ], + textPost: 'Modify the settings in the `modules.d/mysql.yml` file.' + }, + DEB: { + title: 'Enable and configure the mysql module', + commands: [ + 'sudo metricbeat modules enable mysql', + ], + textPost: 'Modify the settings in the `/etc/metricbeat/modules.d/mysql.yml` file.' + }, + RPM: { + title: 'Enable and configure the mysql module', + commands: [ + 'sudo metricbeat modules enable mysql', + ], + textPost: 'Modify the settings in the `/etc/metricbeat/modules.d/mysql.yml` file.' + }, + WINDOWS: { + title: 'Enable and configure the mysql module', + textPre: 'From the `C:\\Program Files\\Metricbeat` folder, run:', + commands: [ + 'PS C:\\Program Files\\Metricbeat> metricbeat.exe modules enable mysql', + ], + textPost: 'Modify the settings in the `modules.d/mysql.yml` file.' + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/mysql_metrics/index.js b/src/core_plugins/kibana/server/tutorials/mysql_metrics/index.js new file mode 100644 index 0000000000000..a8cacd7f60a77 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_metrics/index.js @@ -0,0 +1,34 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function mysqlMetricsSpecProvider() { + return { + id: 'mysqlMetrics', + name: 'MySQL metrics', + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: 'Fetches internal metrics from MySQL.', + longDescription: 'The `mysql` Metricbeat module fetches internal metrics from the MySQL server.' + + ' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-mysql.html)' + + ' about the mysql module.', + //iconPath: '', TODO + artifacts: { + dashboards: [ + { + title: '66881e90-0006-11e7-bf7f-c9acc3d3e306', + linkLabel: 'MySQL metrics dashboard', + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-mysql.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/mysql_metrics/screenshot.png', + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/mysql_metrics/on_prem.js b/src/core_plugins/kibana/server/tutorials/mysql_metrics/on_prem.js new file mode 100644 index 0000000000000..8a47014420ebe --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_metrics/on_prem.js @@ -0,0 +1,49 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/mysql_metrics/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/mysql_metrics/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..fe1cdbcde9391 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/mysql_metrics/on_prem_elastic_cloud.js @@ -0,0 +1,61 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/netflow/common_instructions.js b/src/core_plugins/kibana/server/tutorials/netflow/common_instructions.js new file mode 100644 index 0000000000000..f6fbadb9ac2ae --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/netflow/common_instructions.js @@ -0,0 +1,91 @@ +export const COMMON_NETFLOW_INSTRUCTIONS = { + CONFIG: { + ON_PREM: { + OSX: [ + { + title: 'Edit the configuration', + textPre: 'In the Logstash installation directory, modify `config/logstash.yml` to set the' + + ' configuration parameters for the Netflow module.', + commands: [ + 'modules:', + ' - name: netflow', + ' var.input.udp.port: ', + ' var.elasticsearch.hosts: [ "" ]', + ' var.kibana.host: ":"' + ] + } + ], + WINDOWS: [ + { + title: 'Edit the configuration', + textPre: 'While in the Logstash install directory, modify `config\\logstash.yml` to set the' + + ' configuration parameters for the Netflow module:', + commands: [ + 'modules:', + ' - name: netflow', + ' var.input.udp.port: ', + ' var.elasticsearch.hosts: [ "" ]', + ' var.kibana.host: ":"' + ] + } + ] + }, + ELASTIC_CLOUD: { + OSX: [ + { + title: 'Edit the configuration', + textPre: 'In the Logstash installation directory, modify `config/logstash.yml` to set the' + + ' configuration parameters for the Netflow module.', + commands: [ + 'modules:', + ' - name: netflow', + ' var.input.udp.port: ', + ' cloud.id: "{config.cloud.id}"', + ' cloud.auth: "elastic:"' + ], + textPost: 'Where `` is the password of the `elastic` user.' + } + ], + WINDOWS: [ + { + title: 'Edit the configuration', + textPre: 'While in the Logstash install directory, modify `config\\logstash.yml` to set the' + + ' configuration parameters for the Netflow module:', + commands: [ + 'modules:', + ' - name: netflow', + ' var.input.udp.port: ', + ' cloud.id: "{config.cloud.id}"', + ' cloud.auth: "elastic:"' + ] + } + ] + } + }, + SETUP: { + OSX: [ + { + title: 'Run the Netflow module', + textPre: 'In the Logstash installation directory, run the following command to set up the Netflow module.', + commands: [ + './bin/logstash --modules netflow --setup', + ], + textPost: 'The `--setup` option creates a `netflow-*` index pattern in Elasticsearch and imports' + + ' Kibana dashboards and visualizations. Omit this option for subsequent runs of the module to avoid' + + ' overwriting existing Kibana dashboards.' + } + ], + WINDOWS: [ + { + title: 'Set up and run the Netflow module', + textPre: 'In the Logstash install directory, run the following command to set up the Netflow module.', + commands: [ + 'bin\\logstash --modules netflow --setup', + ], + textPost: 'The `--setup` option creates a `netflow-*` index pattern in Elasticsearch and imports' + + ' Kibana dashboards and visualizations. Omit this option for subsequent runs of the module to avoid' + + ' overwriting existing Kibana dashboards.' + } + ] + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/netflow/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/netflow/elastic_cloud.js new file mode 100644 index 0000000000000..b27ec3a021713 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/netflow/elastic_cloud.js @@ -0,0 +1,30 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { LOGSTASH_INSTRUCTIONS } from '../../../common/tutorials/logstash_instructions'; +import { COMMON_NETFLOW_INSTRUCTIONS } from './common_instructions'; + +// TODO: compare with onPremElasticCloud and onPrem scenarios and extract out common bits +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + ...LOGSTASH_INSTRUCTIONS.INSTALL.OSX, + ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ELASTIC_CLOUD.OSX, + ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + ...LOGSTASH_INSTRUCTIONS.INSTALL.WINDOWS, + ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ELASTIC_CLOUD.WINDOWS, + ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/netflow/index.js b/src/core_plugins/kibana/server/tutorials/netflow/index.js new file mode 100644 index 0000000000000..324432c1cd4df --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/netflow/index.js @@ -0,0 +1,23 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function netflowSpecProvider() { + return { + id: 'netflow', + name: 'Netflow', + category: TUTORIAL_CATEGORY.SECURITY, + shortDescription: 'Collect Netflow records sent by a Netflow exporter', + longDescription: 'The Logstash Netflow module simplifies the collection, normalization, and visualization of network flow data. ' + + 'With a single command, the module parses network flow data, indexes the events into Elasticsearch, and installs a suite of Kibana ' + + 'dashboards to get you exploring your data immediately. Logstash modules support Netflow Version 5 and 9. [Learn more]' + + '({config.docs.logstash}/netflow-module.html) about the Netflow module', + //iconPath: '', TODO + completionTimeMinutes: 10, + //previewImagePath: 'kibana-apache.png', TODO + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/netflow/on_prem.js b/src/core_plugins/kibana/server/tutorials/netflow/on_prem.js new file mode 100644 index 0000000000000..c398555f9efb9 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/netflow/on_prem.js @@ -0,0 +1,30 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { LOGSTASH_INSTRUCTIONS } from '../../../common/tutorials/logstash_instructions'; +import { COMMON_NETFLOW_INSTRUCTIONS } from './common_instructions'; + +// TODO: compare with onPremElasticCloud and elasticCloud scenarios and extract out common bits +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + ...LOGSTASH_INSTRUCTIONS.INSTALL.OSX, + ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ON_PREM.OSX, + ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + ...LOGSTASH_INSTRUCTIONS.INSTALL.WINDOWS, + ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ON_PREM.WINDOWS, + ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/netflow/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/netflow/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..7afb95671d708 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/netflow/on_prem_elastic_cloud.js @@ -0,0 +1,38 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { LOGSTASH_INSTRUCTIONS } from '../../../common/tutorials/logstash_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { COMMON_NETFLOW_INSTRUCTIONS } from './common_instructions'; + +// TODO: compare with onPrem and elasticCloud scenarios and extract out common bits +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + ...LOGSTASH_INSTRUCTIONS.INSTALL.OSX, + ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ON_PREM.OSX, + ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + ...LOGSTASH_INSTRUCTIONS.INSTALL.WINDOWS, + ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ON_PREM.WINDOWS, + ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/nginx_logs/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/nginx_logs/elastic_cloud.js new file mode 100644 index 0000000000000..9a517988c9a9d --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_logs/elastic_cloud.js @@ -0,0 +1,50 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { FILEBEAT_CLOUD_INSTRUCTIONS } from '../../../common/tutorials/filebeat_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/nginx_logs/enable.js b/src/core_plugins/kibana/server/tutorials/nginx_logs/enable.js new file mode 100644 index 0000000000000..70be5bc520555 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_logs/enable.js @@ -0,0 +1,32 @@ +export const ENABLE_INSTRUCTIONS = { + OSX: { + title: 'Enable and configure the nginx module', + textPre: 'From the installation directory, run:', + commands: [ + './filebeat modules enable nginx', + ], + textPost: 'Modify the settings in the `modules.d/nginx.yml` file.' + }, + DEB: { + title: 'Enable and configure the nginx module', + commands: [ + 'sudo filebeat modules enable nginx', + ], + textPost: 'Modify the settings in the `/etc/filebeat/modules.d/nginx.yml` file.' + }, + RPM: { + title: 'Enable and configure the nginx module', + commands: [ + 'sudo filebeat modules enable nginx', + ], + textPost: 'Modify the settings in the `/etc/filebeat/modules.d/nginx.yml` file.' + }, + WINDOWS: { + title: 'Enable and configure the nginx module', + textPre: 'From the `C:\\Program Files\\Filebeat` folder, run:', + commands: [ + 'PS C:\\Program Files\\Filebeat> filebeat.exe modules enable nginx', + ], + textPost: 'Modify the settings in the `modules.d/nginx.yml` file.' + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/nginx_logs/index.js b/src/core_plugins/kibana/server/tutorials/nginx_logs/index.js new file mode 100644 index 0000000000000..f6ab47590c153 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_logs/index.js @@ -0,0 +1,34 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function nginxLogsSpecProvider() { + return { + id: 'nginxLogs', + name: 'Nginx logs', + category: TUTORIAL_CATEGORY.LOGGING, + shortDescription: 'Collect and parse access and error logs created by the Nginx HTTP server.', + longDescription: 'The `nginx` Filebeat module parses access and error logs created by the Nginx HTTP server.' + + ' [Learn more]({config.docs.beats.filebeat}/filebeat-module-nginx.html)' + + ' about the nginx module.', + //iconPath: '', TODO + artifacts: { + dashboards: [ + { + title: '55a9e6e0-a29e-11e7-928f-5dbe6f6f5519', + linkLabel: 'Nginx logs dashboard', + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-nginx.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/nginx_logs/screenshot.png', + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/nginx_logs/on_prem.js b/src/core_plugins/kibana/server/tutorials/nginx_logs/on_prem.js new file mode 100644 index 0000000000000..9ecaa557d2407 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_logs/on_prem.js @@ -0,0 +1,53 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP_AND_UA, + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP_AND_UA, + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP_AND_UA, + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP_AND_UA, + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/nginx_logs/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/nginx_logs/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..2266d33eccd1f --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_logs/on_prem_elastic_cloud.js @@ -0,0 +1,61 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/nginx_metrics/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/nginx_metrics/elastic_cloud.js new file mode 100644 index 0000000000000..408ae869571e7 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_metrics/elastic_cloud.js @@ -0,0 +1,50 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { METRICBEAT_CLOUD_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/nginx_metrics/enable.js b/src/core_plugins/kibana/server/tutorials/nginx_metrics/enable.js new file mode 100644 index 0000000000000..8f30486c54c90 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_metrics/enable.js @@ -0,0 +1,32 @@ +export const ENABLE_INSTRUCTIONS = { + OSX: { + title: 'Enable and configure the nginx module', + textPre: 'From the installation directory, run:', + commands: [ + './metricbeat modules enable nginx', + ], + textPost: 'Modify the settings in the `modules.d/nginx.yml` file.' + }, + DEB: { + title: 'Enable and configure the nginx module', + commands: [ + 'sudo metricbeat modules enable nginx', + ], + textPost: 'Modify the settings in the `/etc/metricbeat/modules.d/nginx.yml` file.' + }, + RPM: { + title: 'Enable and configure the nginx module', + commands: [ + 'sudo metricbeat modules enable nginx', + ], + textPost: 'Modify the settings in the `/etc/metricbeat/modules.d/nginx.yml` file.' + }, + WINDOWS: { + title: 'Enable and configure the nginx module', + textPre: 'From the `C:\\Program Files\\Metricbeat` folder, run:', + commands: [ + 'PS C:\\Program Files\\Metricbeat> metricbeat.exe modules enable nginx', + ], + textPost: 'Modify the settings in the `modules.d/nginx.yml` file.' + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/nginx_metrics/index.js b/src/core_plugins/kibana/server/tutorials/nginx_metrics/index.js new file mode 100644 index 0000000000000..860422d892a6b --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_metrics/index.js @@ -0,0 +1,37 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function nginxMetricsSpecProvider() { + return { + id: 'nginxMetrics', + name: 'Nginx metrics', + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: 'Fetches internal metrics from the Nginx HTTP server.', + longDescription: 'The `nginx` Metricbeat module fetches internal metrics from the Nginx HTTP server.' + + ' The module scrapes the server status data from the web page generated by the' + + ' [ngx_http_stub_status_module](http://nginx.org/en/docs/http/ngx_http_stub_status_module.html)' + + ' module, which needs to be enabled in you Nginx installation.' + + ' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-nginx.html)' + + ' about the nginx module.', + //iconPath: '', TODO + artifacts: { + dashboards: [ + { + title: '023d2930-f1a5-11e7-a9ef-93c69af7b129', + linkLabel: 'Nginx metrics dashboard', + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-nginx.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/nginx_metrics/screenshot.png', + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/nginx_metrics/on_prem.js b/src/core_plugins/kibana/server/tutorials/nginx_metrics/on_prem.js new file mode 100644 index 0000000000000..8a47014420ebe --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_metrics/on_prem.js @@ -0,0 +1,49 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/nginx_metrics/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/nginx_metrics/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..fe1cdbcde9391 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/nginx_metrics/on_prem_elastic_cloud.js @@ -0,0 +1,61 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/register.js b/src/core_plugins/kibana/server/tutorials/register.js new file mode 100644 index 0000000000000..7383f10de5cff --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/register.js @@ -0,0 +1,21 @@ +import { systemLogsSpecProvider } from './system_logs'; +import { systemMetricsSpecProvider } from './system_metrics'; +import { apacheLogsSpecProvider } from './apache_logs'; +import { apacheMetricsSpecProvider } from './apache_metrics'; +import { nginxLogsSpecProvider } from './nginx_logs'; +import { nginxMetricsSpecProvider } from './nginx_metrics'; +import { mysqlLogsSpecProvider } from './mysql_logs'; +import { mysqlMetricsSpecProvider } from './mysql_metrics'; +import { netflowSpecProvider } from './netflow'; + +export function registerTutorials(server) { + server.registerTutorial(systemLogsSpecProvider); + server.registerTutorial(systemMetricsSpecProvider); + server.registerTutorial(apacheLogsSpecProvider); + server.registerTutorial(apacheMetricsSpecProvider); + server.registerTutorial(nginxLogsSpecProvider); + server.registerTutorial(nginxMetricsSpecProvider); + server.registerTutorial(mysqlLogsSpecProvider); + server.registerTutorial(mysqlMetricsSpecProvider); + server.registerTutorial(netflowSpecProvider); +} diff --git a/src/core_plugins/kibana/server/tutorials/system_logs/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/system_logs/elastic_cloud.js new file mode 100644 index 0000000000000..9a517988c9a9d --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_logs/elastic_cloud.js @@ -0,0 +1,50 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { FILEBEAT_CLOUD_INSTRUCTIONS } from '../../../common/tutorials/filebeat_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_CLOUD_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/system_logs/enable.js b/src/core_plugins/kibana/server/tutorials/system_logs/enable.js new file mode 100644 index 0000000000000..32ed797e76367 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_logs/enable.js @@ -0,0 +1,32 @@ +export const ENABLE_INSTRUCTIONS = { + OSX: { + title: 'Enable and configure the system module', + textPre: 'From the installation directory, run:', + commands: [ + './filebeat modules enable system', + ], + textPost: 'Modify the settings in the `modules.d/system.yml` file.' + }, + DEB: { + title: 'Enable and configure the system module', + commands: [ + 'sudo filebeat modules enable system', + ], + textPost: 'Modify the settings in the `/etc/filebeat/modules.d/system.yml` file.' + }, + RPM: { + title: 'Enable and configure the system module', + commands: [ + 'sudo filebeat modules enable system', + ], + textPost: 'Modify the settings in the `/etc/filebeat/modules.d/system.yml` file.' + }, + WINDOWS: { + title: 'Enable and configure the system module', + textPre: 'From the `C:\\Program Files\\Filebeat` folder, run:', + commands: [ + 'PS C:\\Program Files\\Filebeat> filebeat.exe modules enable system', + ], + textPost: 'Modify the settings in the `modules.d/system.yml` file.' + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/system_logs/index.js b/src/core_plugins/kibana/server/tutorials/system_logs/index.js new file mode 100644 index 0000000000000..40bec08f98401 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_logs/index.js @@ -0,0 +1,35 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function systemLogsSpecProvider() { + return { + id: 'systemLogs', + name: 'System logs', + category: TUTORIAL_CATEGORY.LOGGING, + shortDescription: 'Collect and parse logs written by the local Syslog server.', + longDescription: 'The `system` Filebeat module collects and parses logs created by the system logging service of common ' + + ' Unix/Linux based distributions. This module is not available on Windows.' + + ' [Learn more]({config.docs.beats.filebeat}/filebeat-module-system.html)' + + ' about the `system` module.', + //iconPath: '', TODO + artifacts: { + dashboards: [ + { + title: 'Filebeat-syslog-dashboard', + linkLabel: 'System logs dashboard', + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-system.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/system_logs/screenshot.png', + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/system_logs/on_prem.js b/src/core_plugins/kibana/server/tutorials/system_logs/on_prem.js new file mode 100644 index 0000000000000..47c9024326573 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_logs/on_prem.js @@ -0,0 +1,53 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP, + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP, + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP, + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + FILEBEAT_INSTRUCTIONS.PLUGINS.GEOIP, + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/system_logs/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/system_logs/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..2266d33eccd1f --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_logs/on_prem_elastic_cloud.js @@ -0,0 +1,61 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { FILEBEAT_INSTRUCTIONS } from '../../../common/tutorials/filebeat_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.OSX, + FILEBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + FILEBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.DEB, + FILEBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + FILEBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.RPM, + FILEBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + FILEBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + FILEBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + FILEBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + FILEBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/system_metrics/elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/system_metrics/elastic_cloud.js new file mode 100644 index 0000000000000..408ae869571e7 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_metrics/elastic_cloud.js @@ -0,0 +1,50 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { METRICBEAT_CLOUD_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_CLOUD_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/system_metrics/enable.js b/src/core_plugins/kibana/server/tutorials/system_metrics/enable.js new file mode 100644 index 0000000000000..f81af30a80ef7 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_metrics/enable.js @@ -0,0 +1,32 @@ +export const ENABLE_INSTRUCTIONS = { + OSX: { + title: 'Enable and configure the system module', + textPre: 'From the installation directory, run:', + commands: [ + './metricbeat modules enable system', + ], + textPost: 'Modify the settings in the `modules.d/system.yml` file.' + }, + DEB: { + title: 'Enable and configure the system module', + commands: [ + 'sudo metricbeat modules enable system', + ], + textPost: 'Modify the settings in the `/etc/metricbeat/modules.d/system.yml` file.' + }, + RPM: { + title: 'Enable and configure the system module', + commands: [ + 'sudo metricbeat modules enable system', + ], + textPost: 'Modify the settings in the `/etc/metricbeat/modules.d/system.yml` file.' + }, + WINDOWS: { + title: 'Enable and configure the system module', + textPre: 'From the `C:\\Program Files\\Metricbeat` folder, run:', + commands: [ + 'PS C:\\Program Files\\Metricbeat> metricbeat.exe modules enable system', + ], + textPost: 'Modify the settings in the `modules.d/system.yml` file.' + } +}; diff --git a/src/core_plugins/kibana/server/tutorials/system_metrics/index.js b/src/core_plugins/kibana/server/tutorials/system_metrics/index.js new file mode 100644 index 0000000000000..0ffec8b8b2e66 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_metrics/index.js @@ -0,0 +1,35 @@ +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; +import { ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS } from './on_prem_elastic_cloud'; + +export function systemMetricsSpecProvider() { + return { + id: 'systemMetrics', + name: 'System metrics', + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: 'Collects CPU, memory, network, and disk statistics from the host.', + longDescription: 'The `system` Metricbeat module collects CPU, memory, network, and disk statistics from the host.' + + ' It collects system wide statistics as well as per process and per filesystem statistics.' + + ' [Learn more]({config.docs.beats.metricbeat}/metricbeat-module-system.html)' + + ' about the system module.', + //iconPath: '', TODO + artifacts: { + dashboards: [ + { + title: 'Metricbeat-system-overview', + linkLabel: 'System metrics dashboard', + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-system.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/system_metrics/screenshot.png', + onPrem: ON_PREM_INSTRUCTIONS, + elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, + onPremElasticCloud: ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS + }; +} diff --git a/src/core_plugins/kibana/server/tutorials/system_metrics/on_prem.js b/src/core_plugins/kibana/server/tutorials/system_metrics/on_prem.js new file mode 100644 index 0000000000000..8a47014420ebe --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_metrics/on_prem.js @@ -0,0 +1,49 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/core_plugins/kibana/server/tutorials/system_metrics/on_prem_elastic_cloud.js b/src/core_plugins/kibana/server/tutorials/system_metrics/on_prem_elastic_cloud.js new file mode 100644 index 0000000000000..fe1cdbcde9391 --- /dev/null +++ b/src/core_plugins/kibana/server/tutorials/system_metrics/on_prem_elastic_cloud.js @@ -0,0 +1,61 @@ +import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; +import { METRICBEAT_INSTRUCTIONS } from '../../../common/tutorials/metricbeat_instructions'; +import { + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2 +} from '../../../common/tutorials/onprem_cloud_instructions'; +import { ENABLE_INSTRUCTIONS } from './enable'; + +export const ON_PREM_ELASTIC_CLOUD_INSTRUCTIONS = { + instructionSets: [ + { + title: 'Getting Started', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.OSX, + METRICBEAT_INSTRUCTIONS.CONFIG.OSX, + ENABLE_INSTRUCTIONS.OSX, + METRICBEAT_INSTRUCTIONS.START.OSX + ] + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.DEB, + METRICBEAT_INSTRUCTIONS.CONFIG.DEB, + ENABLE_INSTRUCTIONS.DEB, + METRICBEAT_INSTRUCTIONS.START.DEB + ] + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.RPM, + METRICBEAT_INSTRUCTIONS.CONFIG.RPM, + ENABLE_INSTRUCTIONS.RPM, + METRICBEAT_INSTRUCTIONS.START.RPM + ] + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: [ + TRYCLOUD_OPTION1, + TRYCLOUD_OPTION2, + METRICBEAT_INSTRUCTIONS.INSTALL.WINDOWS, + METRICBEAT_INSTRUCTIONS.CONFIG.WINDOWS, + ENABLE_INSTRUCTIONS.WINDOWS, + METRICBEAT_INSTRUCTIONS.START.WINDOWS + ] + } + ] + } + ] +}; diff --git a/src/ui/public/documentation_links/documentation_links.js b/src/ui/public/documentation_links/documentation_links.js index a776994d629bc..c796450cb028f 100644 --- a/src/ui/public/documentation_links/documentation_links.js +++ b/src/ui/public/documentation_links/documentation_links.js @@ -5,6 +5,7 @@ export const ELASTIC_WEBSITE_URL = 'https://www.elastic.co/'; export const documentationLinks = { filebeat: { + base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, installation: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-installation.html`, configuration: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-configuration.html`, elasticsearchOutput: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/elasticsearch-output.html`, @@ -13,6 +14,12 @@ export const documentationLinks = { startup: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-starting.html`, exportedFields: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/exported-fields.html` }, + metricbeat: { + base: `${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}` + }, + logstash: { + base: `${ELASTIC_WEBSITE_URL}guide/en/logstash/${DOC_LINK_VERSION}` + }, scriptedFields: { scriptFields: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/search-request-script-fields.html`, scriptAggs: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/search-aggregations.html#_values_source`, diff --git a/src/ui/tutorials_mixin.js b/src/ui/tutorials_mixin.js new file mode 100644 index 0000000000000..47e23cce24953 --- /dev/null +++ b/src/ui/tutorials_mixin.js @@ -0,0 +1,21 @@ +import _ from 'lodash'; +import Joi from 'joi'; +import { tutorialSchema } from '../core_plugins/kibana/common/tutorials/tutorial_schema'; + +export function tutorialsMixin(kbnServer, server) { + const tutorials = []; + + server.decorate('server', 'getTutorials', () => { + return _.cloneDeep(tutorials); + }); + + server.decorate('server', 'registerTutorial', (specProvider) => { + const { error, value } = Joi.validate(specProvider(), tutorialSchema); + + if (error) { + throw new Error(`Unable to register tutorial spec because its invalid. ${error}`); + } + + tutorials.push(value); + }); +} diff --git a/src/ui/ui_mixin.js b/src/ui/ui_mixin.js index 0c94fc457c5b6..01770061a26bc 100644 --- a/src/ui/ui_mixin.js +++ b/src/ui/ui_mixin.js @@ -1,5 +1,6 @@ import { uiExportsMixin } from './ui_exports'; import { fieldFormatsMixin } from './field_formats'; +import { tutorialsMixin } from './tutorials_mixin'; import { uiAppsMixin } from './ui_apps'; import { uiI18nMixin } from './ui_i18n'; import { uiBundlesMixin } from './ui_bundles'; @@ -13,6 +14,7 @@ export async function uiMixin(kbnServer) { await kbnServer.mixin(uiBundlesMixin); await kbnServer.mixin(uiSettingsMixin); await kbnServer.mixin(fieldFormatsMixin); + await kbnServer.mixin(tutorialsMixin); await kbnServer.mixin(uiNavLinksMixin); await kbnServer.mixin(uiI18nMixin); await kbnServer.mixin(uiRenderMixin); diff --git a/test/functional/apps/home/_add_data.js b/test/functional/apps/home/_add_data.js new file mode 100644 index 0000000000000..40e7ce5f290d9 --- /dev/null +++ b/test/functional/apps/home/_add_data.js @@ -0,0 +1,19 @@ +import expect from 'expect.js'; + +export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'header', 'home']); + + describe('add data tutorials', function describeIndexTests() { + + it('directory should display registered tutorials', async ()=> { + await PageObjects.common.navigateToUrl('home', 'tutorial_directory'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async () => { + const tutorialExists = await PageObjects.home.doesSynopsisExist('netflow'); + expect(tutorialExists).to.be(true); + }); + }); + + }); +} diff --git a/test/functional/apps/home/index.js b/test/functional/apps/home/index.js index cad5e2b98f62f..bae4b963d9a2c 100644 --- a/test/functional/apps/home/index.js +++ b/test/functional/apps/home/index.js @@ -7,5 +7,6 @@ export default function ({ getService, loadTestFile }) { }); loadTestFile(require.resolve('./_home')); + loadTestFile(require.resolve('./_add_data')); }); } diff --git a/test/functional/config.js b/test/functional/config.js index 2de12e9b3079e..ebce7e9a9877d 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -106,6 +106,10 @@ export default async function ({ readConfigFile }) { pathname: '/app/kibana', hash: '/dev_tools/console', }, + home: { + pathname: '/app/kibana', + hash: '/home', + }, }, junit: { reportName: 'UI Functional Tests' diff --git a/test/functional/page_objects/home_page.js b/test/functional/page_objects/home_page.js index 7f8b0605d3c30..8c50816498b3a 100644 --- a/test/functional/page_objects/home_page.js +++ b/test/functional/page_objects/home_page.js @@ -1,7 +1,7 @@ export function HomePageProvider({ getService }) { const testSubjects = getService('testSubjects'); - //const PageObjects = getPageObjects(['common', 'header']); + class HomePage { async clickKibanaIcon() { @@ -12,6 +12,9 @@ export function HomePageProvider({ getService }) { await testSubjects.click(`homeSynopsisLink${title}`); } + async doesSynopsisExist(title) { + return await testSubjects.exists(`homeSynopsisLink${title}`); + } }