From 5a99642cfbebc0611b15666244aa5c91ec2935f9 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Tue, 18 Feb 2020 21:30:38 -0500 Subject: [PATCH] Add Ingest Manager plugin to master (#56567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Fleet] Add permissions checks (#48143) * Add permissions check to API * Add permissions check to UI * Undo changes * Update index.tsx * Update enroll.ts * add API tests for permissions * add fleet to trial license check * Agent enrollment flyout (#48173) * Fix agent table empty prompt * First pass at agent enrollment flyout with placeholders * Move enrollment commands into separate file and enable templated vars * Use default double braces for templating * [Fleet] unenroll agent from the details page (#48286) * [Fleet] add API to unenroll agents * [Fleet] return a 403 for inactive agent * [Fleet] UI to unenroll an agent from the detail page * [Fleet] Filter inactive agent from the listing (#48466) * [Fleet] Expose policy change method from fleet plugins (#48562) * [IM] add screenshot detail view and image endpoint (#48149) * add api endpoint to handle images * #47978 add screenshots component with single image * update padding around screenshot * import existing API_ROOT * move ImageRequestParams interface and fix type * pass the content-type through and add test * fix ie11 issues with nested flex items, change radius to use eui variable * use eui variables for padding * add aria label and image description * [IM] Use EPM in variables & types (#48453) * IntegrationsManager -> EPM * integrationsManager -> epm * [iI]ntegration -> [pP]ackage. Update tests. * Don't rename integrations registry URL. * Update i18n key in x-pack/.i18nrc.json * Update path to functional test config * Add epm to recently re-enabled privileges test * Update two values recently added in main feature branch * Move/update screenshot tests * [Fleet] Allow to edit metadata (#48682) * [Fleet] Add a toggle to show Inactive users (#48917) * Multiselect to unenroll agents (#48852) * Initial pass at multiselect to unenroll agents * Adjust loading state button text * Fix types; use unenroll provider in agent details * Update select all agents kuery * Prevent inactive agents from being selected * [Fleet] Fix privilege tests after merge of master (#49118) * [Fleet] Temporary use the elastic user as kibana user to be able to create API keys (#49037) * [EPM] Fix package version requirement structure (#49172) * [EPM] Fix package version requirement structure In https://github.com/elastic/integrations-registry/pull/134 the API structure changes. This PR should adjust to the new structure. * Update x-pack/legacy/plugins/integrations_manager/common/types.ts Co-Authored-By: John Schulz * Update x-pack/legacy/plugins/integrations_manager/common/types.ts Co-Authored-By: John Schulz * Update x-pack/legacy/plugins/integrations_manager/common/types.ts Co-Authored-By: John Schulz * Update x-pack/legacy/plugins/integrations_manager/common/types.ts Co-Authored-By: John Schulz * [EPM] 48799 markdown into readme (#49180) * add markdown component and fetch markdown * add package.json file with react-markdown dependency * add markdown renderers, use readme path * remove description and rename to readme * remove dropshadow on images * add loading component for readme request * comment on loading components * Add command to spin up a mock server for ingest endpoints (#49259) * 48654 files from registry (#49580) * Replace image path/handler with generic file one * Incorporate tests from #48696 * [EPM] Make screenshot image captions optional (#48912) * Make screenshot image captions optional * fix typos, type, rename variable * [EPM] update to layout and and spacing (#49413) * fix height and color issues * make container full height * remove panels per design update and make white bg color * adjust spacing * check for empty response * fix eslint issue * fix layout in safari * remove unused component * [EPM] Rename registry url to new path (#49598) The integrations-registry repository was renamed to package-registry and with it also all the assets inside. The url under which the service is served will also be changed (not done yet). As soon as this is done, this PR should be merged. * [EPM] rewrite relative image paths (#49637) * rewrite relative image paths * remove EuiImage, use transformImageUri, clean up * [EPM] Use NP registerFeature (#49625) * Use NP registerFeature * Added features plugin to deps. Used it in setup. Moved `registerFeature` from `init` to plugin setup. Moved rest of init into `createSetupShim`. `init` is now ```ts init(server: Legacy.Server) { const { initializerContext, coreSetup, pluginsSetup } = createSetupShim(server); new Plugin(initializerContext).setup(coreSetup, pluginsSetup); }, ``` Moved feature (argument for `registerFeature`) to separate file. Renamed plugin-specific `CoreSetup` to `EPMCoreSetup` * [Fleet] Filter agents list table by policy name (#49968) * Add policies lib and adapters * Fix mock server headers * Initial pass at policy filter dropdown by manipulating kuery string * Adjust spec return values * Use separate state for policy filter; fix typings and disable some eslint rules * Fix react-related eslint errors /Users/jfsiii/work/kibana/x-pack/legacy/plugins/integrations_manager/public/hooks/use_links.tsx 26:20 error React Hook "useCore" is called in function "addBasePath" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks /Users/jfsiii/work/kibana/x-pack/legacy/plugins/integrations_manager/public/screens/detail/readme.tsx 26:35 error React Hook "useLinks" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks 39:6 error React Hook useEffect has a missing dependency: 'readmePath'. Either include it or remove the dependency array react-hooks/exhaustive-deps /Users/jfsiii/work/kibana/x-pack/legacy/plugins/integrations_manager/public/screens/home/search_results.tsx 27:42 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities 27:49 error `"` can be escaped with `"`, `“`, `"`, `”` react/no-unescaped-entities ✖ 5 problems (5 errors, 0 warnings) 1 error and 0 warnings potentially fixable with the `--fix` option. * Remove ui/* imports (#50094) * [Fleet] Use ES api keys for agent authentication (#49639) * add collapsible read me component (#49990) * add collapsible read me component * Update x-pack/legacy/plugins/integrations_manager/public/components/content_collapse.tsx Co-Authored-By: John Schulz * add empty array as second argument to useLayoutEffect * [EPM] Make API consistent for package installation and removal (#48745) * Adjust type names * Install package in one go. * Integration -> Package * Really install all known assets * Remove ingest pipelines on package deletion. * [EPM] Replace image paths/handlers with generic ones for file (#48688) * Replace image path/handler with generic file one * Incorporate tests from #48696 * Fix merge error. * Type tuning * Adjust types * Remove asset types from routes altogether * Add timelion-sheet back to types. * Respond with full package info to install/delete * Be specific in return type. * Keep installAssets() as separate step * [EPM] Add unit tests for pathParts (#50279) * [EPM] Add unit tests for pathParts This adds unit tests for the pathParts function. I initially wanted to enhance the function to also support paths like `iptables-1.0.4/dataset/log/elasticsearch/ingest-pipeline/pipeline.json`. But without unit tests it was hard for me to make the changes. This adds a unit test driven by a table so we can extend it later. Adding these tests also helps me to better understand the code. As a note for myself, the command to run these tests is: ``` node scripts/jest --watch ./legacy/plugins/integrations_manager/server/registry/index.test.ts ``` * Update docs for merging master into feature branch (#50396) * [Fleet] Remove in memory repository (#50431) * [EPM] Documentation of HTTP routes & TS types for Ingest (#48798) * Add beginning models and two routes for Ingest * Update types & models per discussion w/Ruflin Also reviewed data structures listed at https://docs.google.com/document/d/1IBR3f9dpHqJmXYEdg06WV34KSMd3g5k4aMGa4jde_Eg/edit# * Update: /policies always returns array. /policy returns single policy * Add pagination for /policy & /datasources. Uses per_page & page params * Add API metadata. Standardize policy_id param name. * Update descriptions to match Google Doc. Move use case to Policy. Disabled the '@typescript-eslint/array-type' rule because it was going around in circles. It didn't like Datasource[] or Array * Return to initial TS annotation for Arrays Remove the line disabling @typescript-eslint/array-type now that it's behaving normally again :shrug: * [EPM] Add directory structure for server/lib. (#50469) * Add directory structure for server/lib. * 'tests' seems to be more common than 'test' * Make CI happy * [EPM] Add basic documentation directory (#50478) * [EPM] Add basic documentation directory Having the doc directory around allows us to easily add docs from here on to document how EPM works. To run the docs build, use the following command from the kibana directory: ``` ../docs/build_docs --doc docs/epm/index.asciidoc --open ``` The above assumes that docs (https://github.com/elastic/docs) are checked out in the same directory as Kibana. With this change, the EPM docs build is not included yet in the overall docs build. For this adjustments to https://github.com/elastic/docs/blob/master/conf.yaml must be made. * [EPM] Add basic index template (#50471) This PR adds the very basic index template we will use for the packages. It contains all the basic settings and some examples. The examples will be remove as soon as we have an actual implementation with packages but for now is convenient to see if it is a valid package. This code is put into the lib directory as it does not tie directly into any handlers. It also adds an functional tests for loading a template. This means we have a way to check if a template is valid in Elasticsearch. Based on this we can check in the future all our generated templates for validity with Elasticsearch. To run the functional test, go to the Kibana x-pack directory. Start the first command: ``` node scripts/functional_tests_server.js --config test/epm_api_integration/config.ts ``` Keep the above running and switch to an other Terminal. Now run: ``` node scripts/functional_test_runner.js --config x-pack/test/epm_api_integration/config.ts ``` * 40752 rewrite ingest pipeline (#50627) * Add directory structure for server/lib. * 'tests' seems to be more common than 'test' * Make CI happy * Implement pipeline rewriting. * Add more testcases * For posterity (comment change) * Allow beats-style template delimiters * Be more succinct * Document better * Replace AssetType enum with union type (#50696) See https://github.com/elastic/kibana/pull/50609#discussion_r346080439 Discussed in Slack and agree to revert for now. Can track down the issues & restore later * Remove unnecessary await if we can return the promise (#50329) * Fix whitespace per figma comments. Closes #47348 (#47350) * Add fix & comment for TS 3.7.2 regression * [EPM] cleanup assets, filter assets for those currently supported (#50609) * cleanup assets, filter assets for those currently supported * removed unused type * fix type * add comment for better type * change type name to be more descriptive * hardcode image width for ie11 (#49796) * hardcode image width for ie11 * eslint * improve comment * add maxWidth * [EPM] useKibana hook & render in plugin.start (#50110) * plugin.start now does reactdom.render vs returning react element export plugin function from public/index * Move setClient call from plugin.start to plugin.setup * Use `useUiSetting$` from `useKibana` hooks * Fix broken app due to bad hooks usage Can't use useKibana outside a React component. Reverting to prior approach since it's still NP. Can revisit context usage in a followup PR * [EPM] Install package from detail view on button click (#50735) * Support basic "click button -> show spinner -> installed" install flow * Remove incorrect comments. Add TS return types to data functions. * [EPM] Use NP feature_catalogue.register (#50108) * Use NP feature_catalogue.register * Use type from NP plugin * fix linting * fix types * fix headers in Fleet * skipping test due to ES param change * Revert "skipping test due to ES param change" This reverts commit d05f20decfbfc4d91069816a6f8dfde26b5bd6bc. * remove type field * remove unused import * [EPM] Final(?) update from integrations_manager -> EPM (#50976) * Update (all remaining?) references from integrations_manager to EPM * Update path in i18n file * [EPM] update compatibility section (#50975) * change min max version to single range value * add elastic stack icon and change text * remove badge from version and use code font * remove euistyled * add back version component lost in merge * remove euiStyled * remove old file * Restore RequirementVersionRange type * Disable test for elasticsearch username. Temporary work around until we know how the stack wants to add the permissions we need. Either adding to the kibana user or creating a new user. * Revert "Disable test for elasticsearch username." This reverts commit f1020e4eab2ada5d854eacc44cdb6d5bd23c267f. * Disable test for elasticsearch username. Temporary work around until we know how the stack wants to add the permissions we need. Either adding to the kibana user or creating a new user. * Fix EPM typing issues in register feature * Fix typings after master merge * [EPM] CI fixes (#51284) * Initialize es in test. * Add it(), no es.init() * Clean up. * [EPM] add confirmation modal (#51172) * add confirmation modal, move install state to Header * update callout to use title * move components only used in detail view to detail dir * use better variable names * update to more descriptive variable names * Restore prior response vaulues for install & delete package (#51252) Discussed this with @skh https://github.com/elastic/kibana/pull/51112#commitcomment-35961413 & https://github.com/elastic/kibana/pull/51112#commitcomment-35970664 as well as in a video call Also added some TS type annotations for data fetching functions to make the contracts more explicit * [EPM] /package API only lists installable assets. Restore enums. (#51414) * API only shows installable assets. Server types to own file. Restore enums * Fix type imports * Only return installable asset types (kibana for now) * server/types now only has code from hapi which shouldn't go to client * Add more restricted TS types to DisplayAssets object * Flip order of arguments to Extract In these cases it still works the same, but looking at https://www.typescriptlang.org/docs/handbook/advanced-types.html the signature is `Extract` - Extract from `T` those types that are assignable to `U` so the larger set should be first * [Fleet] Enrollment api key UI (#51495) * Make button pretty in dark mode as well. (#51610) * [EPM] Add docs entry about registryUrl config (#51697) This documentation is at the moment mainly for internal use. I found myself searching for this URL several times in the code or PRs so I thought I rather add it to the docs for now. * [EPM] Remove encoding of Kibana objects as not needed anymore * [Fleet] Move agent status server side and API to get aggregated status for a policy (#51673) * [EPM] Add basic docs around install/delete API endpoint (#51728) This is mainly for internal usage at the moment to look up. * Ingest/policy (#51741) * wip policy * tweaks * tweaks * FIX TYPOS * WIP move policy => agent config conversion to fleet, WIP policy changed method * fix tests and bugs * updates tests and snaps * more fixes * use AGENT_POLLING_INTERVAL * cleanup and fix some formatting * Update x-pack/legacy/plugins/ingest/server/libs/datasources.ts Co-Authored-By: John Schulz * Update x-pack/legacy/plugins/ingest/server/libs/datasources.ts Co-Authored-By: John Schulz * Update x-pack/legacy/plugins/ingest/server/libs/outputs.ts Co-Authored-By: John Schulz * Update x-pack/legacy/plugins/fleet/server/libs/policy.ts Co-Authored-By: John Schulz * fix things broken by PR review suggestions * remove unused field * fix types * fix mappings * add datasource mappings * Fix mappings and remove get full policy from checkin * Fix ingest api integration tests * run es-lint to fix fleet * Fix typescript issues * [EPM] Track package install state and add toast notification (#51734) * add notifications from core to plugin * add package install state hook * fix type error * use toMountPoint helper to add jsx to notification * add warning notification to failed install * make notifications dependency explicit prop * move PackageInstall provider lower * add comment about InstallStatus type overlapping InstallationStatus * use InstallStatus type in InstallationButton component * fix type * [Ingest] Adds support for a working default output (#51841) * aadding config * add working settings to the default output * remove default username and password * update libs * [EPM] Add basics for creating the ILM setup (#50474) This contains the basic objects to setup ILM * Create index and alias with a write index * Get the policy The code does not contain any functional tests yet as it is still open on how to do it best. I suggest to get this in as a foundation and then iterate on top of it. * [EPM] Add datasource (ingest pipeline) from package (#51851) ## Summary This mixes a few concerns but I think it's worth it to show the parts working together. Take a look at the individual commits for a better separation of features. This adds - the `/datasource/install/{pkgkey}` endpoint which installs ingest pipelines from a package into ES and saves a reference to them in the EPM state Saved Object - Connects the "Add datasource" button in the successful installation Toast to the new API - Adds a toast notification to inform the user the datasource was added correctly - Adds a "Delete Package" button on the details page so we can uninstall a package while we're waiting for the separate view which allows deletes - b99eda6 Pushes logic that was in the detail view into `InstallationButton`. This consolidates the logic in one component (or one component & the existing hook) and, iiic, means we can put `` on any view and get the same behavior I'm marking this as a normal PR so people can merge if they wish ![add-datasource-delete-package-small](https://user-images.githubusercontent.com/57655/69775686-7fb39280-1167-11ea-8d41-e2b8a02252a1.gif) * [EPM] Add basic processing of fields.yml file (#51148) The fields.yml is used to generate the Elasticsearch template and Kibana index pattern. This PR adds a very basic implementation of processing the fields.yml and then create an Elasticsearch template out of it. The only fields that are supported at the moment are keyword fields, more will be added as a follow up. The testing was implemented with a golden file. The output from the method is compared to a json file. If the input is changed or the method is changed, it is possible to regenerate the files with the `-generate` flag as following: ``` node scripts/jest ./legacy/plugins/epm/server/lib/template/template.test.ts -generate ``` This will allow us to quickly test many inputs / outputs in the future, make adjustments to the existing files and generate the new outputs. We then can compare it in the diff it the changes make sense. * [EPM] Create basic implementation to merge input template and dataset manifest (#51803) * [EPM] Create basic implementation to merge input template and dataset manifest With this code it is possible to take an input template for the agent and merge it with the config variables from the dataset manifest file. Currently only the name and the default value are merged. Later on we must implement to be able to pass user configured variables to it and make decision based on OS selection. Closing https://github.com/elastic/kibana/issues/51794 * [EPM] Refactoring of lib structure (#51885) This refactors the structure of lib. As so far all the lib parts are related to assets in the package, it is organised the same way with the same structure. Each directory has its own tests directory if it needs one. This makes it possible to (almost) not need relative paths for tests. * [EPM] Allow to read files from fields directory (#51958) This change allows to also extract files from the `fields` directory. Previously this was not possible because it always assumed a service must be there. * [EPM] Install Elasticsearch Index Template for data source (#51878) This installs the Elasticsearch index template for each dataset in a package. For now the names are hardcoded based on package key and dataset name but will be more dynamic later on when we pass the full dataset information. The dataset extractions is a bit "hacky" at the moment and we should get a full implementation of dataset at a later stage and replace this code. * [Fleet] Policy list, details, create, edit UIs (#51950) * Set up simple policies list view * Adjust spec to return single policy * Set up simple policy details page * Add demo stats/chart to policy details * Add description string * Initial setup of policy form and create policy UI * Policy create/edit form; integrate policy list api * Integrate create policy api * Integrate policy detail, agent status, and policy edit APIs; adjust policy list api integration in agent enrollment * Fix edit policy mock meta * Fix policy list search bar * PR and linting fixes; use typings from ingest plugin * Fix i18n * [EPM] Add datasource saved object type (#51871) ## Summary This PR makes a few assumptions, and contains a lot of refactoring. It might be beneficial to look at the resulting directory structure under `server` first to get the (new) big picture. Assumptions: - our API deals with several concerns, for now these are packages and datasources - we manage our own HTTP API endpoints for these concerns (in particular, don't use the ingest plugin for that) - we manage (for now) the Kibana saved object in which datasources are saved. importing and calling methods from the ingest plugin to do that down the road will (hopefully) be a manageable change This led to the following decisions: - the code is separated into subdirectories by concern, containing all the route handlers and tightly coupled code - for now, these directories are in `server/packages` and `server/datasources`. I'm tempted to move them into `server/api/{packages,datasources}` but wanted to limit the amount of refactoring in one PR - shared code lives in `server/lib` - some code from `server/packages` has been almost duplicated to handle saving to Datasource saved objects, some has been refactored and is used from both places. The deduplication needs further improvement - maybe `server/registry` should also move under `server/lib` (but see above, I'm trying to not move everything around all at once) Testing: * Please note that this is a breaking change because the saved object type for package information has also been renamed. You'll need to start with a fresh `.kibana-*` index. Restarting `yarn es snapshot` (withouth specifying a data directory) should do the trick. * Package installation should still work, e.g. with a GET request to `http://localhost:5601/api/epm/package/coredns-1.0.1`. The saved objects for packages can be inspected with a GET request to `http://localhost:5601/api/saved_objects/epm-package/$PKG_KEY`, e.g. `http://localhost:5601/api/saved_objects/epm-package/coredns-1.0.1` * Datasource creation should still work, e.g. with a GET request to `http://localhost:5601/api/epm/datasource/install/coredns-1.0.1`. The saved objects for datasources can be inspected with a GET to `http://localhost:5601/api/saved_objects/epm-datasource/$PKG_KEY`, e.g. `http://localhost:5601/api/saved_objects/epm-datasource/coredns-1.0.1` * [Fleet] Expose policy during agent checkin (#51968) * [EPM] Add /epr prefix to the tar.gz download path (#51881) The registry slightly changed the .tar.gz path because of download stats reason. This adjusts for it. See https://github.com/elastic/package-registry/pull/169 * [EPM] Move template installation to lib and add asset helper (#52049) * [EPM] Move template installation to lib and add asset helper All the logic related to the installation of the templates for a package should be inside the template library folder. This moves the logic into this folder. A few refactorings were made to simplify installation: * Introduction of DataSet interface: This interface is needed to extract the data sets inside a package and install one template per data set. * Pass package instead of package key to installation process: Passing the package instead of the package key means fetching of package information is decoupled from the installation process and abstracted. This separates the two concerns and should simplify testing. * getAsssets method: The getAssets methods works on top of the package object to extract asset paths. It is inspired by get_objects methods but supports passing a package and a dataset. Currently one problem with testing that exists is that to fetch the content of an asset is not decoupled yet. * [EPM] Reduce data source to one type (#52061) Between Fleet / Ingest / EPM there had been several interface definitions of Datasource and the related types. This reduces it to one place for the definition. The same applies to the policy definition. The goal of this is that from now on we all rely on the same definition. If we make changes, we make them in all parts of the code. In this PR is only the minimal change needed to get us all on one interface. Further changes will be needed that we all rely on the same saved objects etc. * add export command * revert 2 more files to rely on export * revert imports * Fix types for Datasource Saved Object * merge in master * fix type check * Run VSCode's organize imports on EPM files (#52234) Learned about it on Slack from https://twitter.com/ryanchenkie/status/1201883268527927301 Blog at https://code.visualstudio.com/updates/v1_23#_run-code-actions-on-save Basically does the order we've been loosely following (3rd party, then relative) & alphabetic by location and variable name. It's not customizable but it's reasonable and, afaict, consistent. * [EPM] More realistic datasource SO. Error if package not installed. (#52229) * Move cache 'hack' into getAssetsData * p -> pkg. package is reserved. pkgkey is used in many places * Remove unnecessary type cast * Clarify reasons behind asset path manipulation * Return the Datasource; not the Saved Object. * Use real values from package in fake datasource SO * Error if /datasource/install before /package/install ``` > curl --user elastic:changeme localhost:5601/api/epm/datasource/install/coredns-1.0.1 { "statusCode": 403, "error": "Forbidden", "message": "coredns-1.0.1 is not installed" } > curl --user elastic:changeme localhost:5601/api/epm/install/coredns-1.0.1 [ { "id": "53aa1f70-443e-11e9-8548-ab7fbe04f038", "type": "dashboard" }, { "id": "Metricbeat-CoreDNS-Dashboard-ecs", "type": "dashboard" }, { "id": "75743f70-443c-11e9-8548-ab7fbe04f038", "type": "visualization" }, { "id": "36e08510-53c4-11e9-b466-9be470bbd327-ecs", "type": "visualization" }, { "id": "277fc650-67a9-11e9-a534-715561d0bf42", "type": "visualization" }, { "id": "cfde7fb0-443d-11e9-8548-ab7fbe04f038", "type": "visualization" }, { "id": "a19df590-53c4-11e9-b466-9be470bbd327-ecs", "type": "visualization" }, { "id": "a58345f0-7298-11e9-b0d0-414c3011ddbb", "type": "visualization" }, { "id": "9dc640e0-4432-11e9-8548-ab7fbe04f038", "type": "visualization" }, { "id": "3ad75810-4429-11e9-8548-ab7fbe04f038", "type": "visualization" }, { "id": "57c74300-7308-11e9-b0d0-414c3011ddbb", "type": "visualization" }, { "id": "27da53f0-53d5-11e9-b466-9be470bbd327-ecs", "type": "visualization" }, { "id": "86177430-728d-11e9-b0d0-414c3011ddbb", "type": "visualization" }, { "id": "4804eaa0-7315-11e9-b0d0-414c3011ddbb", "type": "visualization" } ] > curl --user elastic:changeme localhost:5601/api/epm/datasource/install/coredns-1.0.1 [ { "id": "coredns_1_0_1_dataset_log_elasticsearch_ingest_pipeline_pipeline_plaintext_json", "type": "ingest-pipeline" }, { "id": "coredns_1_0_1_dataset_log_elasticsearch_ingest_pipeline_pipeline_json_json", "type": "ingest-pipeline" }, { "id": "coredns_1_0_1_dataset_log_elasticsearch_ingest_pipeline_pipeline_entry_json", "type": "ingest-pipeline" } ] ``` * fix duplicated imports * [EPM] Move golden files generation over to jest snapshot (#52203) * [EPM] Move golden files generation over to jest snapshot I initially used my own implementation to write the generated files. It runs out jest has a feature to write snapshots which simplifies the code a lot. I added a loop with an additional test file so in the future we can just keep adding test files without having to modify the test code. To updated the snapshots, the param `-u` has to be used: ``` node scripts/jest legacy/plugins/epm/server/lib/fields/field.test.ts -u ``` * [EPM] Create metrics-* and logs-* Kibana index pattern (#52277) This creates the very basic Kibana index patterns metrics-* and logs-* for Kibana. At the moment it is overwritten every time. We need to change this in the future to take the fields from all installed data sources and regenerate it. * [EPM] Create helper for elasticsearch asset names (#52265) Most of the Elasticsearch assets have the same base name. This creates a helper to get the base name for the assets. In case we decide to change the base name in the future, we can change it in one place. * fix tests and destructing * [EPM] Update Registry types. Prevent errors installing certain datasources. (#52285) * Update RegistryPackage type * Use download key from EPR to fetch archive * Fix errors caused by correcting the Registry types. The issues were largely that some Registry types like `title, `datasets` and `assets` where marked as required, but are actually optional. This highlighted area in the code were we relied on them always being present. We added to the issue by wrapping Registry types in `Required` which made those items which were correctly listed as optional, required for EPM code. Updated EPM types to reflect the largely pass-through nature of the EPM types. There are two properties which we ensure are in every EPM response, those were put into their own (unexported) type. Confirm by trying to add a datasource to a package which has no datasources, like apache-1.0.1 or system-2.0.1. In `feature-ingest` and the earlier version of this PR, the `/datasource/install` call returns a 500. In this PR it succeeds. * [EPM] Add setup of default ILM policies (#52272) This creates two ILM policies: logs-default and metrics-default. These are the default ILM policies used. Currently the policy content is hardcoded in the code but should be fetched from the base package in the future. The setup happens as part of the datasource installation. When a data source is installed it is a good time to check if the assets are there but we might extract this to a better place in the future. * [EPM] 52075 add data source first page (#52320) * Update RegistryPackage type * add first page of add data source * fix for ie11 flex min width bug * remove toDetailViewRelative * remove unneeded spread * Update TS type names for EPR search results (#52512) * `RegistryList -> RegistrySearchResults` * `RegistryListItem -> RegistrySearchResult` * Restore import sort order from #52234 (#52548) Many of the changes from #52234 were lost. Presumably due to PR(s) merging which were based on branches which had the previous unsorted order. * [EPM] Replace wildcard export (#52554) * PackageNotInstalledError -> packages/index.ts * pkgToPkgKey -> registry/index.ts (will convert existing `${name}-${version}` instances later) * Replace export * from packages. There's an argument that the import sites should be updated to import from `packages/get`, `packages/install`, etc but that can wait for a later PR. * [EPM] Reduce usage of epm-package SavedObject (#52576) * Delete existing Installation type. Rename InstallationAttributes to Installation * Reduce usage of EPM SO. Add getInstallation(). Replaced two calls of getInstallationObject() with getInstallation(). Two less places with knowledge of SO internals. Lots of potential improvements for EPM TS types remain (refactoring/removing Installable, etc), but this is a good incremental step, IMO * [EPM] Fix missing export link (#52628) Without it, things break. I am surprised CI did not catch this. * [EPM] Cleanup ILM loading (#52632) Before the check for the ILM policy to exist triggered an exception. With this change it is a normal response also if the policy does not exist yet. A follow up issue will be created in Elasticsearch to get a HEAD request for this available. * [EPM] Switch to staging URL for registry (#52626) The old cluster with the registry will be removed as soon as this is merged. * [EPM] Use Dataset interface to generate template (#52255) This will make sure we have to pass much feature params and can fully rely on the datasource object to create names for assets. * [Fleet] Use agent events to compute agent health (#52513) * [EPM] Data source integration tests (#52542) * Add fixtures for data source integration test. * Move test setup to beforeEach * Add test for datasource creation * Handle pipelines in yml format. * Make integration test for adding a data source pass. * Use EPR staging URL with CDN. (#52776) See https://github.com/elastic/kibana/pull/52626#pullrequestreview-330622868 * [EPM] Add Data Source page updates (#52705) * remove dupe type RegistryPackage * change switches to checkboxes, use datasets to create checkboxes, add some local form state * update types * [EPM] redirect after package install (#52771) * add callback after successful installation and redirect * add temp data sources tab content to access add data source page * remove assets tab for mvp * hide data sources link and redirect from data sources tab if package not installed * change callback name * remove commented out assets logic * add redirect to hook * fix type * Use ingest datasource api (#52964) Incremental change. Uses HTTP API for datasource creation. Will do follow-up PR which uses JS function instead * Remove duplicate fetchInfo & installTemplates I think this was from a bad merge, but pretty sure we don't want these functions called twice in the same function * WIP. Pushing so others can see * Improve correctness/flexibility of absolute URL * Disable datasource test & template installation * [Ingest] Data source APIs (#52448) * Clean up ingest imports and remove unneeded mock_spec files * Initial pass at datasources lib and API endpoints * Add add/remove datasource to/from policy API endpoints * Add datasource contract tests and related policy contract tests; update snapshots * Fix tests * Fix tests again * Fix tests 3 * Adjust routes, PR feedback * modify epm createDatasource endpoint to use user data (#52971) * change epm/datasource/install/{pkg} to POST, send user data to endpoint, install pipelines and templates based on user selected datasets * change test to post for installing a datasource * change some names and types around * delete request.headers['transfer-encoding'] being passed through from epm request * [EPM] Don't share CreateFakeDatasource type (#53068) It's not shared between client & server so it doesn't need to be in common. Also, it imports server code which would try to bring server types to the client. It's types so they're compiled away but it's important to keep common to what's truly common. Breaking this separation is why we thought enums broke the client. A lint rule just landed in master to prevent this. * [EPM] Index template generation fixes (#53104) * Only add keyword type field to mappings. * Index template installation * Handle empty fields definition files * Re-enable index template installation * [Fleet] Assign/Unassign data source from policy UI (#53058) * Add index files to export various modules; normalize imports * Clean up unused files; extract datasources table component from policy details page * Expose http client to frontend libs; remove unused types; import ES UI's useRequest lib * Adjust shape of rest api adapter interface to better match with rest of kibana; remove unused node adapter; change per_page param to perPage in agent events route * Initial pass at assign data sources flyout * Initial pass at unassigning data sources from policy * Make data sources table searchable by package values * Fix enrollment key lib for rest adapter param changes * Fix imports and types * `yarn.lock` changes after bootstrapping * [EPM] Implement getConfig for dataset (#53261) * [EPM] Implement getConfig for dataset * Implements a getConfig method on a dataset object. * Build the configuration for each dataset in a package. * construct and save streams into datasource saved object * [EPM] Fix template installation (#53272) As dataset.package was not set, the installed templates contained undefined in the template name. This changes fixes this. * [EM] Refactor ingest pipeline installation (#53309) * Refactor ingest pipeline installation * Only install index templates for requested datasets * Add index.default_pipeline to index template * Hook up pipeline rewriting * Add correct types. * change POST create datasources path (#53165) * change POST create datasources path * remove pkgkey from params * Fix creation of a data source with a custom ID (#53537) * [Ingest] Return associated policy IDs in data source info (#53350) * Return number of policies from data source, surface in assign data source UI * Update snapshots * [EPM]: Assign data source to policy in UI (#53597) * Let ES generate source ids. Refactor along the way. * Datasource.id isn't optional. It's just missing before we send to Ingest * Delete EPM's mapping of datasources saved object. Ingest handles that. * Keep datasource object-related work in constructDatasource * Move asset installation into own function. Keep entry point high-level. * More descriptive (less ambiguous) names for these two functions * Use enum values from Ingest instead of plain strings * Limit the 'type' key of references to known asset types. * Update variable names to clarify that we're merging arrays of references * Use [].flat instead .reduce + .concat to avoid error on empty arrays. * Pass PackageInfo value directly to component vs pulling off n properties * Name handlers/options based on the data, not the UI element * Populate policy combo box based on values from Ingest policy API * Mark Dataset.vars as optional. * Add TODOs * Add commands to run API tests to README (#53847) * Limit functions to 3 params max. Update those which used more (#53848) * [EPM] Code in 'common' directories shouldn't import server code (#53854) * [Fleet] Code in 'common' directories shouldn't import server code (#53938) * [Fleet] Remove server code from common folder in fleet * [Fleet] Fix typescript issues after master merge * [EPM] Fix typescript issues after master merge * Fix eslint issues * Fix typescript issues after merge * Fix merge master missing line * Fix merge conflict * [Fleet] Fix registration of Ingest management section (#54065) * Fix registration of Ingest management section * Fix i18n key * [Fleet] Remove server code from common folder in ingest (#53969) * [Fleet] Connect fleet to policy change update (#53201) * [Fleet] Send created event when a policy is created * [Fleet] updated created event when a policy is created * [Fleet] Send deleted event when a policy is deleted * [Fleet] Rename output.url => output.hosts (#54258) * [Ingest] Remove policies UI (#54308) * Remove meta field UI from policy add/edit form * Initial pass at policy bulk+single delete UI and API * Adjust policy links from agent list and detail pages so that links are only active if policy exists * Add delete policy UI to policy detail page * Disable policy delete button for default policy * Commit updated kbn-pm artifact. CI is failing with messages like 14:52:28 ERROR: 'yarn kbn run build -i @kbn/pm' caused changes to the following files: 14:52:28 14:52:28 packages/kbn-pm/dist/index.js Following advice from https://elastic.slack.com/archives/C0D8P2XK5/p1570032166063400 and running/committing build * Update kbn/pm package Signed-off-by: Tyler Smalley * [EPM] create logs metrics index patterns (#54037) * fixes bug in for loop returning too early and not looping through all yaml files creating incomplete index template, move loading yaml files to own function, other cleanup * use reduce in place of for loop * basic functionality for creating index patterns * separate logs and metrics index patterns * dedupe fields * adjust flattenFields to rename nested fields with parent name path * some tests * use yml files for tests * add awaits as part of installing the package * optimize loading of yaml files * fix typo * change type packageName to package * update tests to use all files from the beginning * fix type errors * fix test * Use dataset.package from registry https://github.com/elastic/kibana/pull/54037#pullrequestreview-340362812 * Form validation on add datasource page. (#53920) * [Ingest] Add support for policy `label` field (#54413) * Allow `label` field in policy APIs, update UIs to support `label` field * PR review changes, typo fixes, update tests * [Fleet] Fix api key creation (#54498) * [Fleet] Move agent acks to his own endpoint (#54401) * [Ingest] Fix MaxListenersExceededWarning during kibana boot (#54745) * [Fleet] Create a default api key for the default output (#54658) * [EPM] update index pattern fields (#54862) * add DatasetType type * move loadFieldsFromYaml to fields * add logic to determine field values * update tests * add back accidentally removed readFromDocValues * remove DatasetType and add IndexPatternType * rename dedup to dedupe * group tests * use null coalescing operator * Fix typing issues * [Fleet] Support agent event subtype STOPPING (#55231) * [EPM] Prevent double submit when creating data sources Update installationRequested state after submit (#55100) * [EPM] handle alias fields when creating kibana index pattern (#55254) * add alias support, update flattenFields to handle alias copying, update and add tests * update snapshot * update findFieldByPath to return undefined if not leaf node * remove temporary alias type from map * [EPM] handle multi fields when creating index patterns (#55554) * handle mult_fields * move nested function out * [Fleet] Fleet/spec docs (#55619) * [EPM] Start to document definitions (#55361) This is a first stab at creating a place where we define the terms we use across ingest management. This is not complete but defines a place where we can add all the future defintions. * [EPM] Indexing strategy docs (#55301) * [EPM] Indexing strategy docs This documentation is to start documenting our new indexing strategy in a public place and have it versioned. This will allow us to share the current state of the indexing strategy more easily in a single place, track it when updated and also already have it ready for our future users to look it up and understand the benefits of it. * update typos * fix one more typo * apply review feedback * skip fields that are disabled (#55735) * [Fleet] Fix fleet typing issues after merging master * [EPM] Create fieldFormatMap in kibana index pattern (#55892) * add support for fieldFormatMap * output params must be camel case * fix case * add url_template param * [Fleet] Use user from saved object to create apiKeys (#55458) * [EPM] Document package upgrade behaviour (#56138) This PR adds more detailed documentation on what should happen when a package is upgraded. * Remove some explicit typing to pass type checks. This abstraction will be removed in the single plugin going into master. * remove readFromDocValues (#56227) * Add symlink to yarn.lock to fix(?) CI https://github.com/elastic/kibana/pull/56443/checks?check_run_id=418123781 failed saying EPM directory "MUST have a 'yarn.lock' symlink" Seems to have originated with https://github.com/elastic/kibana/pull/55440 Following example from other legacy/plugins/* in that PR * Like 441d9ed, but correct * [Ingest] Convert `ingest` plugin to new platform `ingest_manager` plugin (#56262) * Seed Ingest Manager as a new NP plugin * Add contexts for core, deps, and config. Begin routing and nav UI * Export NP ready request from top-level es_ui_shared/public/ * Add nav styling w/ theming, add useRequest hook * Set up license and config server-side services; add test routes * Move most types and constants into /common * Initial pass at: * data stream and agent config models * data stream routes and schemas * Initial pass at agent config api route handlers * Change plugin id to camel case * Fix circular schema dependency, add security as optional plugin * Create appContext service, use request user info in agent config routes + libs * Create default agent config * Add default output host config, output typings, and create default output and its api key * Move saved object mapping to new plugin * Change data streams -> datasources * Add legacy plugin to bootstrap mappings * Adjust fleet's ingest dependencies * Disable policies UI in Fleet * Adjust EPM's ingest dependencies * Adjust ingest manager base API route * Adjust fleet's client side ingest dependencies * Remove more ingest dependencies from fleet * REMOVE MOST OF LEGACY INGEST PLUGIN * Add section for agent configs in UI nav * Allow useRequest and sendRequest consumers to specify typing for response * Initial pass at porting over agent config list UI * Port over agent config creation * Port over delete agent config functionality * Fix app routing * Port over fleet setup routes * Adjust fleet's ingest dependencies * Make fleet happy path work, skip some tests (MESSY! :)) * Remove policy list UI code from fleet * Change useRequestResponse error type * Add missing agent config schemas and hooks * Fix type check issues * Register IM under management * Fix type issues as a result of changes to use/sendRequest interfaces * Make all ingest saved objects *not* space-aware * Fix i18n path * Fix app categories import * Fix datasource package assets schema (array of asset objects) * Seed Ingest Manager privileges to fix tests * Change `features` to optional plugin instead of required * Fix security privileges tests * Fix feature test * Fix duplicate enrollment key created for default agent config * Fix fleet agent enrollment by catching agent config 404 * PR feedback * First pass at deleting everything related to EPM & Fleet plugins * Remove EPM-specific react-markdown * Remove some unwanted differences from master. Removed more EPM and Fleet changes * React.FC -> React.FunctionComponent to match the pattern in master * Selectively enable features based on config values * Fix typing and(?) CI * Enable xpack.ingestManger during tests Follow example from xpack.endpoint flags * Don't create BASE_URL value based on PLUGIN_ID * Don't add optional routes. Use config for registry url * Add README with some skeleton docs on tests, etc * Undo changes to some files based on PR feedback * Commit the result of 'yarn kbn run build -i @kbn/pm' * Commit the result of 'yarn kbn run build -i @kbn/pm' * WIP. But add some passing api integration tests └-: apis └-> "before all" hook └-: Ingest Manager plugin └-> "before all" hook └-: /agent_configs └-> "before all" hook └-> should get agent configs └-> "before each" hook: global before each └- ✓ pass (42ms) "apis Ingest Manager plugin /agent_configs should get agent configs" └-> should create agent config └-> "before each" hook: global before each └- ✓ pass (63ms) "apis Ingest Manager plugin /agent_configs should create agent config" └-> should have new agent config └-> "before each" hook: global before each └- ✓ pass (41ms) "apis Ingest Manager plugin /agent_configs should have new agent config" └-> should delete that new config └-> "before each" hook: global before each └- ✓ pass (1.0s) "apis Ingest Manager plugin /agent_configs should delete that new config" └-> should get datasources └-> "before each" hook: global before each └- ✓ pass (26ms) "apis Ingest Manager plugin /agent_configs should get datasources" └-> should 404 from EPM package api └-> "before each" hook: global before each └- ✓ pass (41ms) "apis Ingest Manager plugin /agent_configs should 404 from EPM package api" └-> "after all" hook └-> "after all" hook └-> "after all" hook 6 passing (1.2s) * WIP * Fix TS type errors. Types are created tests. Should they come from test project? * Add route to delete datasources (mirrors agent_configs api) * Remove old ESArchiver files * Add integration tests for each /agent_config & /datasources route * Update README Remove bash integration test scripts * More updates to README * Remove xpack.kueryAutocomplete from x-pack/.i18n.json * Use default appRoute. We can't use /app/ingest (at least for now). Looking at the app_router it seems like any route starting with `/app` must be `/app/:appId`. I also don't think appRoute is fully supported. I'll follow up with Platform. * Use appBasePath from app mount params for React Router basename * Remove out-of-date comments * Use the ES client from core * Move License & Config services WIP. Pushing to share * Move config & license services into App Context service. * Remove FTR tests for Ingest Manager * Remove license-related propperties * Remove clusterClient properties * Don't set this.security if not in dependencies * WIP (failing) integration_test * No more unknown key errors. Timeouts now * Test routes with default (disabled) and manager-only flags * Add tests to cover all permutations of flags (A, AB, AC, ABC) * Use TS types vs config-schema in common & public Move all @kbn/config-schema usage to server Co-authored-by: Matt Apperson Co-authored-by: Jen Huang Co-authored-by: Nicolas Chaulet Co-authored-by: Sandra Gonzales Co-authored-by: Nicolas Ruflin Co-authored-by: Sonja Krause-Harder Co-authored-by: Brian Seeders Co-authored-by: Tyler Smalley Co-authored-by: Elastic Machine --- .github/CODEOWNERS | 3 +- src/dev/typescript/projects.ts | 1 - src/plugins/es_ui_shared/public/index.ts | 9 + .../public/request/np_ready_request.ts | 20 +- x-pack/.i18nrc.json | 1 + x-pack/index.js | 2 + x-pack/legacy/plugins/ingest_manager/index.ts | 39 +++ x-pack/plugins/ingest_manager/README.md | 20 ++ .../common/constants/agent_config.ts | 18 ++ .../common/constants/datasource.ts | 7 + .../ingest_manager/common/constants/index.ts | 11 + .../ingest_manager/common/constants/output.ts | 18 ++ .../ingest_manager/common/constants/plugin.ts | 7 + .../ingest_manager/common/constants/routes.ts | 44 +++ x-pack/plugins/ingest_manager/common/index.ts | 8 + .../ingest_manager/common/services/index.ts | 6 + .../ingest_manager/common/services/routes.ts | 77 +++++ .../ingest_manager/common/types/index.ts | 20 ++ .../common/types/models/agent_config.ts | 32 ++ .../common/types/models/datasource.ts | 43 +++ .../common/types/models/index.ts | 8 + .../common/types/models/output.ts | 33 +++ .../common/types/rest_spec/agent_config.ts | 59 ++++ .../common/types/rest_spec/common.ts | 13 + .../common/types/rest_spec/datasource.ts | 36 +++ .../common/types/rest_spec/fleet_setup.ts | 19 ++ .../common/types/rest_spec/index.ts | 9 + x-pack/plugins/ingest_manager/kibana.json | 9 + .../ingest_manager/components/index.ts | 6 + .../ingest_manager/components/loading.tsx | 15 + .../ingest_manager/constants/index.ts | 18 ++ .../ingest_manager/hooks/index.ts | 13 + .../ingest_manager/hooks/use_config.ts | 18 ++ .../ingest_manager/hooks/use_core.ts | 18 ++ .../ingest_manager/hooks/use_debounce.tsx | 23 ++ .../ingest_manager/hooks/use_deps.ts | 18 ++ .../ingest_manager/hooks/use_link.ts | 13 + .../ingest_manager/hooks/use_pagination.tsx | 25 ++ .../hooks/use_request/agent_config.ts | 61 ++++ .../ingest_manager/hooks/use_request/index.ts | 7 + .../hooks/use_request/use_request.ts | 35 +++ .../applications/ingest_manager/index.tsx | 108 +++++++ .../ingest_manager/layouts/default.tsx | 90 ++++++ .../ingest_manager/layouts/index.tsx | 6 + .../components/config_delete_provider.tsx | 217 ++++++++++++++ .../agent_config/components/config_form.tsx | 96 ++++++ .../sections/agent_config/components/index.ts | 8 + .../sections/agent_config/index.tsx | 18 ++ .../list_page/components/create_config.tsx | 143 +++++++++ .../list_page/components/index.ts | 6 + .../sections/agent_config/list_page/index.tsx | 280 ++++++++++++++++++ .../ingest_manager/sections/epm/index.tsx | 13 + .../ingest_manager/sections/fleet/index.tsx | 12 + .../ingest_manager/sections/index.tsx | 11 + .../sections/overview/index.tsx | 10 + .../ingest_manager/services/index.ts | 7 + .../ingest_manager/types/index.ts | 19 ++ x-pack/plugins/ingest_manager/public/index.ts | 11 + .../plugins/ingest_manager/public/plugin.ts | 57 ++++ .../ingest_manager/server/constants/index.ts | 22 ++ x-pack/plugins/ingest_manager/server/index.ts | 43 +++ .../server/integration_tests/router.test.ts | 190 ++++++++++++ .../plugins/ingest_manager/server/plugin.ts | 102 +++++++ .../server/routes/agent_config/handlers.ts | 144 +++++++++ .../server/routes/agent_config/index.ts | 73 +++++ .../server/routes/datasource/handlers.ts | 131 ++++++++ .../server/routes/datasource/index.ts | 73 +++++ .../ingest_manager/server/routes/epm/index.ts | 75 +++++ .../server/routes/fleet_setup/handlers.ts | 55 ++++ .../server/routes/fleet_setup/index.ts | 31 ++ .../ingest_manager/server/routes/index.ts | 9 + .../ingest_manager/server/saved_objects.ts | 78 +++++ .../server/services/agent_config.ts | 258 ++++++++++++++++ .../server/services/app_context.ts | 50 ++++ .../server/services/datasource.ts | 132 +++++++++ .../ingest_manager/server/services/index.ts | 11 + .../ingest_manager/server/services/output.ts | 113 +++++++ .../ingest_manager/server/types/index.tsx | 12 + .../server/types/models/agent_config.ts | 38 +++ .../server/types/models/datasource.ts | 55 ++++ .../server/types/models/index.ts | 8 + .../server/types/models/output.ts | 37 +++ .../server/types/rest_spec/agent_config.ts | 61 ++++ .../server/types/rest_spec/common.ts | 14 + .../server/types/rest_spec/datasource.ts | 38 +++ .../server/types/rest_spec/fleet_setup.ts | 19 ++ .../server/types/rest_spec/index.ts | 9 + .../watch_visualization.tsx | 2 +- .../watch_list/components/watch_list.tsx | 2 +- .../watch_status/components/watch_history.tsx | 4 +- 90 files changed, 3827 insertions(+), 16 deletions(-) create mode 100644 x-pack/legacy/plugins/ingest_manager/index.ts create mode 100644 x-pack/plugins/ingest_manager/README.md create mode 100644 x-pack/plugins/ingest_manager/common/constants/agent_config.ts create mode 100644 x-pack/plugins/ingest_manager/common/constants/datasource.ts create mode 100644 x-pack/plugins/ingest_manager/common/constants/index.ts create mode 100644 x-pack/plugins/ingest_manager/common/constants/output.ts create mode 100644 x-pack/plugins/ingest_manager/common/constants/plugin.ts create mode 100644 x-pack/plugins/ingest_manager/common/constants/routes.ts create mode 100644 x-pack/plugins/ingest_manager/common/index.ts create mode 100644 x-pack/plugins/ingest_manager/common/services/index.ts create mode 100644 x-pack/plugins/ingest_manager/common/services/routes.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/index.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/models/agent_config.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/models/datasource.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/models/index.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/models/output.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/common.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/datasource.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts create mode 100644 x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts create mode 100644 x-pack/plugins/ingest_manager/kibana.json create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/loading.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_config.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_core.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_debounce.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_deps.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_link.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_pagination.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/index.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_delete_provider.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_form.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/index.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/index.ts create mode 100644 x-pack/plugins/ingest_manager/public/plugin.ts create mode 100644 x-pack/plugins/ingest_manager/server/constants/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts create mode 100644 x-pack/plugins/ingest_manager/server/plugin.ts create mode 100644 x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts create mode 100644 x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts create mode 100644 x-pack/plugins/ingest_manager/server/routes/datasource/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/routes/epm/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/routes/fleet_setup/handlers.ts create mode 100644 x-pack/plugins/ingest_manager/server/routes/fleet_setup/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/routes/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/saved_objects.ts create mode 100644 x-pack/plugins/ingest_manager/server/services/agent_config.ts create mode 100644 x-pack/plugins/ingest_manager/server/services/app_context.ts create mode 100644 x-pack/plugins/ingest_manager/server/services/datasource.ts create mode 100644 x-pack/plugins/ingest_manager/server/services/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/services/output.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/index.tsx create mode 100644 x-pack/plugins/ingest_manager/server/types/models/agent_config.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/models/datasource.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/models/index.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/models/output.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/rest_spec/common.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/rest_spec/datasource.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/rest_spec/fleet_setup.ts create mode 100644 x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 33fbddbd1faf8..56db8d3793f57 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -72,7 +72,8 @@ # Observability UIs /x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui /x-pack/plugins/infra/ @elastic/logs-metrics-ui -/x-pack/legacy/plugins/integrations_manager/ @elastic/epm +/x-pack/plugins/ingest_manager/ @elastic/ingest +/x-pack/legacy/plugins/ingest_manager/ @elastic/ingest /x-pack/plugins/observability/ @elastic/logs-metrics-ui @elastic/apm-ui @elastic/uptime @elastic/ingest # Machine Learning diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index b6353e44989ee..fb35e5ce526ed 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -19,7 +19,6 @@ import glob from 'glob'; import { resolve } from 'path'; - import { REPO_ROOT } from '../constants'; import { Project } from './project'; diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 67e3617a85115..2925e5e16458e 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -18,3 +18,12 @@ */ export { JsonEditor, OnJsonEditorUpdateHandler } from './components/json_editor'; + +export { + SendRequestConfig, + SendRequestResponse, + UseRequestConfig, + UseRequestResponse, + sendRequest, + useRequest, +} from './request/np_ready_request'; diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts index 790e29b6d3655..d7532553f45f9 100644 --- a/src/plugins/es_ui_shared/public/request/np_ready_request.ts +++ b/src/plugins/es_ui_shared/public/request/np_ready_request.ts @@ -28,8 +28,8 @@ export interface SendRequestConfig { body?: any; } -export interface SendRequestResponse { - data: any; +export interface SendRequestResponse { + data: D | null; error: Error | null; } @@ -39,18 +39,18 @@ export interface UseRequestConfig extends SendRequestConfig { deserializer?: (data: any) => any; } -export interface UseRequestResponse { +export interface UseRequestResponse { isInitialRequest: boolean; isLoading: boolean; - error: null | unknown; - data: any; - sendRequest: (...args: any[]) => Promise; + error: Error | null; + data: D | null; + sendRequest: (...args: any[]) => Promise>; } -export const sendRequest = async ( +export const sendRequest = async ( httpClient: HttpSetup, { path, method, body, query }: SendRequestConfig -): Promise => { +): Promise> => { try { const response = await httpClient[method](path, { body, query }); @@ -66,7 +66,7 @@ export const sendRequest = async ( } }; -export const useRequest = ( +export const useRequest = ( httpClient: HttpSetup, { path, @@ -77,7 +77,7 @@ export const useRequest = ( initialData, deserializer = (data: any): any => data, }: UseRequestConfig -): UseRequestResponse => { +): UseRequestResponse => { // Main states for tracking request status and data const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 775c661dd75e0..f0b590f7ffd6c 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -18,6 +18,7 @@ "xpack.idxMgmt": "legacy/plugins/index_management", "xpack.indexLifecycleMgmt": "legacy/plugins/index_lifecycle_management", "xpack.infra": "plugins/infra", + "xpack.ingestManager": "plugins/ingest_manager", "xpack.data": "plugins/data_enhanced", "xpack.lens": "legacy/plugins/lens", "xpack.licenseMgmt": "legacy/plugins/license_management", diff --git a/x-pack/index.js b/x-pack/index.js index 858c3e8b68d18..f3f569e021070 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -37,6 +37,7 @@ import { transform } from './legacy/plugins/transform'; import { actions } from './legacy/plugins/actions'; import { alerting } from './legacy/plugins/alerting'; import { lens } from './legacy/plugins/lens'; +import { ingestManager } from './legacy/plugins/ingest_manager'; import { triggersActionsUI } from './legacy/plugins/triggers_actions_ui'; module.exports = function(kibana) { @@ -74,6 +75,7 @@ module.exports = function(kibana) { snapshotRestore(kibana), actions(kibana), alerting(kibana), + ingestManager(kibana), triggersActionsUI(kibana), ]; }; diff --git a/x-pack/legacy/plugins/ingest_manager/index.ts b/x-pack/legacy/plugins/ingest_manager/index.ts new file mode 100644 index 0000000000000..c20cc7225d780 --- /dev/null +++ b/x-pack/legacy/plugins/ingest_manager/index.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + savedObjectMappings, + OUTPUT_SAVED_OBJECT_TYPE, + AGENT_CONFIG_SAVED_OBJECT_TYPE, + DATASOURCE_SAVED_OBJECT_TYPE, +} from '../../../plugins/ingest_manager/server'; + +// TODO https://github.com/elastic/kibana/issues/46373 +// const INDEX_NAMES = { +// INGEST: '.kibana', +// }; + +export function ingestManager(kibana: any) { + return new kibana.Plugin({ + id: 'ingestManager', + uiExports: { + savedObjectSchemas: { + [AGENT_CONFIG_SAVED_OBJECT_TYPE]: { + isNamespaceAgnostic: true, + // indexPattern: INDEX_NAMES.INGEST, + }, + [OUTPUT_SAVED_OBJECT_TYPE]: { + isNamespaceAgnostic: true, + // indexPattern: INDEX_NAMES.INGEST, + }, + [DATASOURCE_SAVED_OBJECT_TYPE]: { + isNamespaceAgnostic: true, + // indexPattern: INDEX_NAMES.INGEST, + }, + }, + mappings: savedObjectMappings, + }, + }); +} diff --git a/x-pack/plugins/ingest_manager/README.md b/x-pack/plugins/ingest_manager/README.md new file mode 100644 index 0000000000000..60c2a457a2806 --- /dev/null +++ b/x-pack/plugins/ingest_manager/README.md @@ -0,0 +1,20 @@ +# Ingest Manager + +## Getting started +See the Kibana docs for [how to set up your dev environment](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#setting-up-your-development-environment), [run Elasticsearch](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-elasticsearch), and [start Kibana](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-kibana). + +One common workflow is: + + 1. `yarn es snapshot` + 1. In another shell: `yarn start --xpack.ingestManager.enabled=true` (or set in `config.yml`) + +## HTTP API + 1. Nothing by default. If `xpack.ingestManager.enabled=true`, it adds the `DATASOURCE_API_ROUTES` and `AGENT_CONFIG_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts) + 1. [Integration tests](../../test/api_integration/apis/ingest_manager/endpoints.ts) + 1. In later versions the EPM and Fleet routes will be added when their flags are enabled. See the [currently disabled logic to add those routes](https://github.com/jfsiii/kibana/blob/feature-ingest-manager/x-pack/plugins/ingest_manager/server/plugin.ts#L86-L90). + +## Plugin architecture +Follows the `common`, `server`, `public` structure from the [Architecture Style Guide +](https://github.com/elastic/kibana/blob/master/style_guides/architecture_style_guide.md#file-and-folder-structure). + +We use New Platform approach (structure, APIs, etc) where possible. There's a `kibana.json` manifest, and the server uses the `server/{index,plugin}.ts` approach from [`MIGRATION.md`](https://github.com/elastic/kibana/blob/master/src/core/MIGRATION.md#architecture). \ No newline at end of file diff --git a/x-pack/plugins/ingest_manager/common/constants/agent_config.ts b/x-pack/plugins/ingest_manager/common/constants/agent_config.ts new file mode 100644 index 0000000000000..d0854d6ffeec7 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/constants/agent_config.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { AgentConfigStatus } from '../types'; + +export const AGENT_CONFIG_SAVED_OBJECT_TYPE = 'agent_configs'; + +export const DEFAULT_AGENT_CONFIG_ID = 'default'; + +export const DEFAULT_AGENT_CONFIG = { + name: 'Default config', + namespace: 'default', + description: 'Default agent configuration created by Kibana', + status: AgentConfigStatus.Active, + datasources: [], +}; diff --git a/x-pack/plugins/ingest_manager/common/constants/datasource.ts b/x-pack/plugins/ingest_manager/common/constants/datasource.ts new file mode 100644 index 0000000000000..0ff472b2afeb0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/constants/datasource.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const DATASOURCE_SAVED_OBJECT_TYPE = 'datasources'; diff --git a/x-pack/plugins/ingest_manager/common/constants/index.ts b/x-pack/plugins/ingest_manager/common/constants/index.ts new file mode 100644 index 0000000000000..aa3b204be4889 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/constants/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './plugin'; +export * from './routes'; + +export * from './agent_config'; +export * from './datasource'; +export * from './output'; diff --git a/x-pack/plugins/ingest_manager/common/constants/output.ts b/x-pack/plugins/ingest_manager/common/constants/output.ts new file mode 100644 index 0000000000000..e0262d0ca811c --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/constants/output.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { OutputType } from '../types'; + +export const OUTPUT_SAVED_OBJECT_TYPE = 'outputs'; + +export const DEFAULT_OUTPUT_ID = 'default'; + +export const DEFAULT_OUTPUT = { + name: DEFAULT_OUTPUT_ID, + type: OutputType.Elasticsearch, + hosts: [''], + ingest_pipeline: DEFAULT_OUTPUT_ID, + api_key: '', +}; diff --git a/x-pack/plugins/ingest_manager/common/constants/plugin.ts b/x-pack/plugins/ingest_manager/common/constants/plugin.ts new file mode 100644 index 0000000000000..7922e6cadfa28 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/constants/plugin.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const PLUGIN_ID = 'ingestManager'; diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts new file mode 100644 index 0000000000000..efd6ef17ba05b --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +// Base API paths +export const API_ROOT = `/api/ingest_manager`; +export const DATASOURCE_API_ROOT = `${API_ROOT}/datasources`; +export const AGENT_CONFIG_API_ROOT = `${API_ROOT}/agent_configs`; +export const EPM_API_ROOT = `${API_ROOT}/epm`; +export const FLEET_API_ROOT = `${API_ROOT}/fleet`; + +// EPM API routes +export const EPM_API_ROUTES = { + LIST_PATTERN: `${EPM_API_ROOT}/list`, + INFO_PATTERN: `${EPM_API_ROOT}/package/{pkgkey}`, + INSTALL_PATTERN: `${EPM_API_ROOT}/install/{pkgkey}`, + DELETE_PATTERN: `${EPM_API_ROOT}/delete/{pkgkey}`, + CATEGORIES_PATTERN: `${EPM_API_ROOT}/categories`, +}; + +// Datasource API routes +export const DATASOURCE_API_ROUTES = { + LIST_PATTERN: `${DATASOURCE_API_ROOT}`, + INFO_PATTERN: `${DATASOURCE_API_ROOT}/{datasourceId}`, + CREATE_PATTERN: `${DATASOURCE_API_ROOT}`, + UPDATE_PATTERN: `${DATASOURCE_API_ROOT}/{datasourceId}`, + DELETE_PATTERN: `${DATASOURCE_API_ROOT}/delete`, +}; + +// Agent config API routes +export const AGENT_CONFIG_API_ROUTES = { + LIST_PATTERN: `${AGENT_CONFIG_API_ROOT}`, + INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`, + CREATE_PATTERN: `${AGENT_CONFIG_API_ROOT}`, + UPDATE_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`, + DELETE_PATTERN: `${AGENT_CONFIG_API_ROOT}/delete`, +}; + +// Fleet setup API routes +export const FLEET_SETUP_API_ROUTES = { + INFO_PATTERN: `${FLEET_API_ROOT}/setup`, + CREATE_PATTERN: `${FLEET_API_ROOT}/setup`, +}; diff --git a/x-pack/plugins/ingest_manager/common/index.ts b/x-pack/plugins/ingest_manager/common/index.ts new file mode 100644 index 0000000000000..3d1c70ba2635e --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './constants'; +export * from './services'; +export * from './types'; diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts new file mode 100644 index 0000000000000..1b3ae4706e3a7 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './routes'; diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts new file mode 100644 index 0000000000000..bcd1646fe1f0c --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + EPM_API_ROOT, + EPM_API_ROUTES, + DATASOURCE_API_ROUTES, + AGENT_CONFIG_API_ROUTES, +} from '../constants'; + +export const epmRouteService = { + getCategoriesPath: () => { + return EPM_API_ROUTES.CATEGORIES_PATTERN; + }, + + getListPath: () => { + return EPM_API_ROUTES.LIST_PATTERN; + }, + + getInfoPath: (pkgkey: string) => { + return EPM_API_ROUTES.INFO_PATTERN.replace('{pkgkey}', pkgkey); + }, + + getFilePath: (filePath: string) => { + return `${EPM_API_ROOT}${filePath}`; + }, + + getInstallPath: (pkgkey: string) => { + return EPM_API_ROUTES.INSTALL_PATTERN.replace('{pkgkey}', pkgkey).replace(/\/$/, ''); // trim trailing slash + }, + + getRemovePath: (pkgkey: string) => { + return EPM_API_ROUTES.DELETE_PATTERN.replace('{pkgkey}', pkgkey).replace(/\/$/, ''); // trim trailing slash + }, +}; + +export const datasourceRouteService = { + getListPath: () => { + return DATASOURCE_API_ROUTES.LIST_PATTERN; + }, + + getInfoPath: (datasourceId: string) => { + return DATASOURCE_API_ROUTES.INFO_PATTERN.replace('{datasourceId}', datasourceId); + }, + + getCreatePath: () => { + return DATASOURCE_API_ROUTES.CREATE_PATTERN; + }, + + getUpdatePath: (datasourceId: string) => { + return DATASOURCE_API_ROUTES.UPDATE_PATTERN.replace('{datasourceId}', datasourceId); + }, +}; + +export const agentConfigRouteService = { + getListPath: () => { + return AGENT_CONFIG_API_ROUTES.LIST_PATTERN; + }, + + getInfoPath: (agentConfigId: string) => { + return AGENT_CONFIG_API_ROUTES.INFO_PATTERN.replace('{agentConfigId}', agentConfigId); + }, + + getCreatePath: () => { + return AGENT_CONFIG_API_ROUTES.CREATE_PATTERN; + }, + + getUpdatePath: (agentConfigId: string) => { + return AGENT_CONFIG_API_ROUTES.UPDATE_PATTERN.replace('{agentConfigId}', agentConfigId); + }, + + getDeletePath: () => { + return AGENT_CONFIG_API_ROUTES.DELETE_PATTERN; + }, +}; diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts new file mode 100644 index 0000000000000..4abb1b659f036 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './models'; +export * from './rest_spec'; + +export interface IngestManagerConfigType { + enabled: boolean; + epm: { + enabled: boolean; + registryUrl: string; + }; + fleet: { + enabled: boolean; + defaultOutputHost: string; + }; +} diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent_config.ts b/x-pack/plugins/ingest_manager/common/types/models/agent_config.ts new file mode 100644 index 0000000000000..1cc8b32afe3c1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/models/agent_config.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DatasourceSchema } from './datasource'; + +export enum AgentConfigStatus { + Active = 'active', + Inactive = 'inactive', +} + +interface AgentConfigBaseSchema { + name: string; + namespace: string; + description?: string; +} + +export type NewAgentConfigSchema = AgentConfigBaseSchema; + +export type AgentConfigSchema = AgentConfigBaseSchema & { + id: string; + status: AgentConfigStatus; + datasources: Array; + updated_on: string; + updated_by: string; +}; + +export type NewAgentConfig = NewAgentConfigSchema; + +export type AgentConfig = AgentConfigSchema; diff --git a/x-pack/plugins/ingest_manager/common/types/models/datasource.ts b/x-pack/plugins/ingest_manager/common/types/models/datasource.ts new file mode 100644 index 0000000000000..f28037845c7f7 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/models/datasource.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface DatasourceBaseSchema { + name: string; + namespace: string; + read_alias: string; + agent_config_id: string; + package: { + assets: Array<{ + id: string; + type: string; + }>; + description: string; + name: string; + title: string; + version: string; + }; + streams: Array<{ + config: Record; + input: { + type: string; + config: Record; + fields: Array>; + ilm_policy: string; + index_template: string; + ingest_pipelines: string[]; + }; + output_id: string; + processors: string[]; + }>; +} + +export type NewDatasourceSchema = DatasourceBaseSchema; + +export type DatasourceSchema = DatasourceBaseSchema & { id: string }; + +export type NewDatasource = NewDatasourceSchema; + +export type Datasource = DatasourceSchema; diff --git a/x-pack/plugins/ingest_manager/common/types/models/index.ts b/x-pack/plugins/ingest_manager/common/types/models/index.ts new file mode 100644 index 0000000000000..959dfe1d937b9 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/models/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './agent_config'; +export * from './datasource'; +export * from './output'; diff --git a/x-pack/plugins/ingest_manager/common/types/models/output.ts b/x-pack/plugins/ingest_manager/common/types/models/output.ts new file mode 100644 index 0000000000000..5f96fe33b5e16 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/models/output.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum OutputType { + Elasticsearch = 'elasticsearch', +} + +interface OutputBaseSchema { + name: string; + type: OutputType; + username?: string; + password?: string; + index_name?: string; + ingest_pipeline?: string; + hosts?: string[]; + api_key?: string; + admin_username?: string; + admin_password?: string; + config?: Record; +} + +export type NewOutputSchema = OutputBaseSchema; + +export type OutputSchema = OutputBaseSchema & { + id: string; +}; + +export type NewOutput = NewOutputSchema; + +export type Output = OutputSchema; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts new file mode 100644 index 0000000000000..5d281b03260db --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { AgentConfig, NewAgentConfigSchema } from '../models'; +import { ListWithKuerySchema } from './common'; + +export interface GetAgentConfigsRequestSchema { + query: ListWithKuerySchema; +} + +export interface GetAgentConfigsResponse { + items: AgentConfig[]; + total: number; + page: number; + perPage: number; + success: boolean; +} + +export interface GetOneAgentConfigRequestSchema { + params: { + agentConfigId: string; + }; +} + +export interface GetOneAgentConfigResponse { + item: AgentConfig; + success: boolean; +} + +export interface CreateAgentConfigRequestSchema { + body: NewAgentConfigSchema; +} + +export interface CreateAgentConfigResponse { + item: AgentConfig; + success: boolean; +} + +export type UpdateAgentConfigRequestSchema = GetOneAgentConfigRequestSchema & { + body: NewAgentConfigSchema; +}; + +export interface UpdateAgentConfigResponse { + item: AgentConfig; + success: boolean; +} + +export interface DeleteAgentConfigsRequestSchema { + body: { + agentConfigIds: string[]; + }; +} + +export type DeleteAgentConfigsResponse = Array<{ + id: string; + success: boolean; +}>; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/common.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/common.ts new file mode 100644 index 0000000000000..d247933d4011f --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/common.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ListWithKuerySchema { + page: number; + perPage: number; + kuery?: string; +} + +export type ListWithKuery = ListWithKuerySchema; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/datasource.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/datasource.ts new file mode 100644 index 0000000000000..78859f2008005 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/datasource.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { NewDatasourceSchema } from '../models'; +import { ListWithKuerySchema } from './common'; + +export interface GetDatasourcesRequestSchema { + query: ListWithKuerySchema; +} + +export interface GetOneDatasourceRequestSchema { + params: { + datasourceId: string; + }; +} + +export interface CreateDatasourceRequestSchema { + body: NewDatasourceSchema; +} + +export type UpdateDatasourceRequestSchema = GetOneDatasourceRequestSchema & { + body: NewDatasourceSchema; +}; + +export interface DeleteDatasourcesRequestSchema { + body: { + datasourceIds: string[]; + }; +} + +export type DeleteDatasourcesResponse = Array<{ + id: string; + success: boolean; +}>; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts new file mode 100644 index 0000000000000..926021baab0ef --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface GetFleetSetupRequestSchema {} + +export interface CreateFleetSetupRequestSchema { + body: { + admin_username: string; + admin_password: string; + }; +} + +export interface CreateFleetSetupResponse { + isInitialized: boolean; +} diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts new file mode 100644 index 0000000000000..7d0d7e67f2db0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './common'; +export * from './datasource'; +export * from './agent_config'; +export * from './fleet_setup'; diff --git a/x-pack/plugins/ingest_manager/kibana.json b/x-pack/plugins/ingest_manager/kibana.json new file mode 100644 index 0000000000000..cef1a293c104b --- /dev/null +++ b/x-pack/plugins/ingest_manager/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "ingestManager", + "version": "kibana", + "server": true, + "ui": true, + "configPath": ["xpack", "ingestManager"], + "requiredPlugins": ["licensing", "data", "encryptedSavedObjects"], + "optionalPlugins": ["security", "features"] +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts new file mode 100644 index 0000000000000..5133d82588494 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { Loading } from './loading'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/loading.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/loading.tsx new file mode 100644 index 0000000000000..c1fae19c5dab0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/loading.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; + +export const Loading: React.FunctionComponent<{}> = () => ( + + + + + +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts new file mode 100644 index 0000000000000..1af39a60455e0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { + PLUGIN_ID, + EPM_API_ROUTES, + DEFAULT_AGENT_CONFIG_ID, + AGENT_CONFIG_SAVED_OBJECT_TYPE, +} from '../../../../common'; + +export const BASE_PATH = '/app/ingestManager'; +export const EPM_PATH = '/epm'; +export const AGENT_CONFIG_PATH = '/configs'; +export const AGENT_CONFIG_DETAILS_PATH = `${AGENT_CONFIG_PATH}/`; +export const FLEET_PATH = '/fleet'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts new file mode 100644 index 0000000000000..a224b599c13af --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useCore, CoreContext } from './use_core'; +export { useConfig, ConfigContext } from './use_config'; +export { useDeps, DepsContext } from './use_deps'; +export { useLink } from './use_link'; +export { usePagination } from './use_pagination'; +export { useDebounce } from './use_debounce'; +export * from './use_request'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_config.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_config.ts new file mode 100644 index 0000000000000..d3f27a180cfd0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_config.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { IngestManagerConfigType } from '../../../plugin'; + +export const ConfigContext = React.createContext(null); + +export function useConfig() { + const config = useContext(ConfigContext); + if (config === null) { + throw new Error('ConfigContext not initialized'); + } + return config; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_core.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_core.ts new file mode 100644 index 0000000000000..c6e91444d21f5 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_core.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { CoreStart } from 'kibana/public'; + +export const CoreContext = React.createContext(null); + +export function useCore() { + const core = useContext(CoreContext); + if (core === null) { + throw new Error('CoreContext not initialized'); + } + return core; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_debounce.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_debounce.tsx new file mode 100644 index 0000000000000..f701ebeaadbe5 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_debounce.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState, useEffect } from 'react'; + +export function useDebounce(value: T, delay: number) { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_deps.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_deps.ts new file mode 100644 index 0000000000000..a2e2f278930e3 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_deps.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { IngestManagerSetupDeps } from '../../../plugin'; + +export const DepsContext = React.createContext(null); + +export function useDeps() { + const deps = useContext(DepsContext); + if (deps === null) { + throw new Error('DepsContext not initialized'); + } + return deps; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_link.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_link.ts new file mode 100644 index 0000000000000..333606cec8028 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_link.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BASE_PATH } from '../constants'; +import { useCore } from './'; + +export function useLink(path: string = '/') { + const core = useCore(); + return core.http.basePath.prepend(`${BASE_PATH}#${path}`); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_pagination.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_pagination.tsx new file mode 100644 index 0000000000000..ae0352a33b2ff --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_pagination.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState } from 'react'; + +export interface Pagination { + currentPage: number; + pageSize: number; +} + +export function usePagination() { + const [pagination, setPagination] = useState({ + currentPage: 1, + pageSize: 20, + }); + + return { + pagination, + setPagination, + pageSizeOptions: 20, + }; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts new file mode 100644 index 0000000000000..389909e1d99ef --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpFetchQuery } from 'kibana/public'; +import { useRequest, sendRequest } from './use_request'; +import { agentConfigRouteService } from '../../services'; +import { + GetAgentConfigsResponse, + GetOneAgentConfigResponse, + CreateAgentConfigRequestSchema, + CreateAgentConfigResponse, + UpdateAgentConfigRequestSchema, + UpdateAgentConfigResponse, + DeleteAgentConfigsRequestSchema, + DeleteAgentConfigsResponse, +} from '../../types'; + +export const useGetAgentConfigs = (query: HttpFetchQuery = {}) => { + return useRequest({ + path: agentConfigRouteService.getListPath(), + method: 'get', + query, + }); +}; + +export const useGetOneAgentConfig = (agentConfigId: string) => { + return useRequest({ + path: agentConfigRouteService.getInfoPath(agentConfigId), + method: 'get', + }); +}; + +export const sendCreateAgentConfig = (body: CreateAgentConfigRequestSchema['body']) => { + return sendRequest({ + path: agentConfigRouteService.getCreatePath(), + method: 'post', + body: JSON.stringify(body), + }); +}; + +export const sendUpdateAgentConfig = ( + agentConfigId: string, + body: UpdateAgentConfigRequestSchema['body'] +) => { + return sendRequest({ + path: agentConfigRouteService.getUpdatePath(agentConfigId), + method: 'put', + body: JSON.stringify(body), + }); +}; + +export const sendDeleteAgentConfigs = (body: DeleteAgentConfigsRequestSchema['body']) => { + return sendRequest({ + path: agentConfigRouteService.getDeletePath(), + method: 'post', + body: JSON.stringify(body), + }); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts new file mode 100644 index 0000000000000..68d67080d90ba --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { setHttpClient, sendRequest } from './use_request'; +export * from './agent_config'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts new file mode 100644 index 0000000000000..12b4d0bdf7df6 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/use_request.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { HttpSetup } from 'kibana/public'; +import { + SendRequestConfig, + SendRequestResponse, + UseRequestConfig, + sendRequest as _sendRequest, + useRequest as _useRequest, +} from '../../../../../../../../src/plugins/es_ui_shared/public'; + +let httpClient: HttpSetup; + +export const setHttpClient = (client: HttpSetup) => { + httpClient = client; +}; + +export const sendRequest = ( + config: SendRequestConfig +): Promise> => { + if (!httpClient) { + throw new Error('sendRequest has no http client set'); + } + return _sendRequest(httpClient, config); +}; + +export const useRequest = (config: UseRequestConfig) => { + if (!httpClient) { + throw new Error('sendRequest has no http client set'); + } + return _useRequest(httpClient, config); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx new file mode 100644 index 0000000000000..935eb42d0347e --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { useObservable } from 'react-use'; +import { HashRouter as Router, Redirect, Switch, Route, RouteProps } from 'react-router-dom'; +import { CoreStart, AppMountParameters } from 'kibana/public'; +import { EuiErrorBoundary } from '@elastic/eui'; +import { EuiThemeProvider } from '../../../../../legacy/common/eui_styled_components'; +import { IngestManagerSetupDeps, IngestManagerConfigType } from '../../plugin'; +import { EPM_PATH, FLEET_PATH, AGENT_CONFIG_PATH } from './constants'; +import { DefaultLayout } from './layouts'; +import { IngestManagerOverview, EPMApp, AgentConfigApp, FleetApp } from './sections'; +import { CoreContext, DepsContext, ConfigContext, setHttpClient, useConfig } from './hooks'; + +export interface ProtectedRouteProps extends RouteProps { + isAllowed?: boolean; + restrictedPath?: string; +} + +export const ProtectedRoute: React.FunctionComponent = ({ + isAllowed = false, + restrictedPath = '/', + ...routeProps +}: ProtectedRouteProps) => { + return isAllowed ? : ; +}; + +const IngestManagerRoutes = ({ ...rest }) => { + const { epm, fleet } = useConfig(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const IngestManagerApp = ({ + basepath, + coreStart, + deps, + config, +}: { + basepath: string; + coreStart: CoreStart; + deps: IngestManagerSetupDeps; + config: IngestManagerConfigType; +}) => { + const isDarkMode = useObservable(coreStart.uiSettings.get$('theme:darkMode')); + return ( + + + + + + + + + + + + ); +}; + +export function renderApp( + coreStart: CoreStart, + { element, appBasePath }: AppMountParameters, + deps: IngestManagerSetupDeps, + config: IngestManagerConfigType +) { + setHttpClient(coreStart.http); + ReactDOM.render( + , + element + ); + + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx new file mode 100644 index 0000000000000..eaf49fed3d933 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { + EuiPage, + EuiPageBody, + EuiTabs, + EuiTab, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import euiStyled from '../../../../../../legacy/common/eui_styled_components'; +import { Section } from '../sections'; +import { useLink, useConfig } from '../hooks'; +import { EPM_PATH, FLEET_PATH, AGENT_CONFIG_PATH } from '../constants'; + +interface Props { + section: Section; + children?: React.ReactNode; +} + +const Nav = euiStyled.nav` + background: ${props => props.theme.eui.euiColorEmptyShade}; + border-bottom: ${props => props.theme.eui.euiBorderThin}; + padding: ${props => + `${props.theme.eui.euiSize} ${props.theme.eui.euiSizeL} ${props.theme.eui.euiSize} ${props.theme.eui.euiSizeL}`}; + .euiTabs { + padding-left: 3px; + margin-left: -3px; + }; +`; + +export const DefaultLayout: React.FunctionComponent = ({ section, children }) => { + const { epm, fleet } = useConfig(); + return ( +
+ + + {children} + +
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/index.tsx new file mode 100644 index 0000000000000..858951bd0d38f --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/index.tsx @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { DefaultLayout } from './default'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_delete_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_delete_provider.tsx new file mode 100644 index 0000000000000..6f51415a562a3 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_delete_provider.tsx @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useRef, useState } from 'react'; +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { sendDeleteAgentConfigs, useCore, sendRequest } from '../../../hooks'; + +interface Props { + children: (deleteAgentConfigs: deleteAgentConfigs) => React.ReactElement; +} + +export type deleteAgentConfigs = (agentConfigs: string[], onSuccess?: OnSuccessCallback) => void; + +type OnSuccessCallback = (agentConfigsUnenrolled: string[]) => void; + +export const AgentConfigDeleteProvider: React.FunctionComponent = ({ children }) => { + const { notifications } = useCore(); + const [agentConfigs, setAgentConfigs] = useState([]); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isLoadingAgentsCount, setIsLoadingAgentsCount] = useState(false); + const [agentsCount, setAgentsCount] = useState(0); + const [isLoading, setIsLoading] = useState(false); + const onSuccessCallback = useRef(null); + + const deleteAgentConfigsPrompt: deleteAgentConfigs = ( + agentConfigsToDelete, + onSuccess = () => undefined + ) => { + if ( + agentConfigsToDelete === undefined || + (Array.isArray(agentConfigsToDelete) && agentConfigsToDelete.length === 0) + ) { + throw new Error('No agent configs specified for deletion'); + } + setIsModalOpen(true); + setAgentConfigs(agentConfigsToDelete); + fetchAgentsCount(agentConfigsToDelete); + onSuccessCallback.current = onSuccess; + }; + + const closeModal = () => { + setAgentConfigs([]); + setIsLoading(false); + setIsLoadingAgentsCount(false); + setIsModalOpen(false); + }; + + const deleteAgentConfigs = async () => { + setIsLoading(true); + + try { + const { data } = await sendDeleteAgentConfigs({ + agentConfigIds: agentConfigs, + }); + const successfulResults = data?.filter(result => result.success) || []; + const failedResults = data?.filter(result => !result.success) || []; + + if (successfulResults.length) { + const hasMultipleSuccesses = successfulResults.length > 1; + const successMessage = hasMultipleSuccesses + ? i18n.translate( + 'xpack.ingestManager.deleteAgentConfigs.successMultipleNotificationTitle', + { + defaultMessage: 'Deleted {count} agent configs', + values: { count: successfulResults.length }, + } + ) + : i18n.translate( + 'xpack.ingestManager.deleteAgentConfigs.successSingleNotificationTitle', + { + defaultMessage: "Deleted agent config '{id}'", + values: { id: successfulResults[0].id }, + } + ); + notifications.toasts.addSuccess(successMessage); + } + + if (failedResults.length) { + const hasMultipleFailures = failedResults.length > 1; + const failureMessage = hasMultipleFailures + ? i18n.translate( + 'xpack.ingestManager.deleteAgentConfigs.failureMultipleNotificationTitle', + { + defaultMessage: 'Error deleting {count} agent configs', + values: { count: failedResults.length }, + } + ) + : i18n.translate( + 'xpack.ingestManager.deleteAgentConfigs.failureSingleNotificationTitle', + { + defaultMessage: "Error deleting agent config '{id}'", + values: { id: failedResults[0].id }, + } + ); + notifications.toasts.addDanger(failureMessage); + } + + if (onSuccessCallback.current) { + onSuccessCallback.current(successfulResults.map(result => result.id)); + } + } catch (e) { + notifications.toasts.addDanger( + i18n.translate('xpack.ingestManager.deleteAgentConfigs.fatalErrorNotificationTitle', { + defaultMessage: 'Error deleting agent configs', + }) + ); + } + closeModal(); + }; + + const fetchAgentsCount = async (agentConfigsToCheck: string[]) => { + if (isLoadingAgentsCount) { + return; + } + setIsLoadingAgentsCount(true); + const { data } = await sendRequest<{ total: number }>({ + path: `/api/fleet/agents`, + method: 'get', + query: { + kuery: `agents.policy_id : (${agentConfigsToCheck.join(' or ')})`, + }, + }); + setAgentsCount(data?.total || 0); + setIsLoadingAgentsCount(false); + }; + + const renderModal = () => { + if (!isModalOpen) { + return null; + } + + return ( + + + } + onCancel={closeModal} + onConfirm={deleteAgentConfigs} + cancelButtonText={ + + } + confirmButtonText={ + isLoading || isLoadingAgentsCount ? ( + + ) : agentsCount ? ( + + ) : ( + + ) + } + buttonColor="danger" + confirmButtonDisabled={isLoading || isLoadingAgentsCount} + > + {isLoadingAgentsCount ? ( + + ) : agentsCount ? ( + + ) : ( + + )} + + + ); + }; + + return ( + + {children(deleteAgentConfigsPrompt)} + {renderModal()} + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_form.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_form.tsx new file mode 100644 index 0000000000000..5a25dc8bc92b5 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_form.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NewAgentConfig } from '../../../types'; + +interface ValidationResults { + [key: string]: JSX.Element[]; +} + +export const agentConfigFormValidation = ( + agentConfig: Partial +): ValidationResults => { + const errors: ValidationResults = {}; + + if (!agentConfig.name?.trim()) { + errors.name = [ + , + ]; + } + + return errors; +}; + +interface Props { + agentConfig: Partial; + updateAgentConfig: (u: Partial) => void; + validation: ValidationResults; +} + +export const AgentConfigForm: React.FunctionComponent = ({ + agentConfig, + updateAgentConfig, + validation, +}) => { + const [touchedFields, setTouchedFields] = useState<{ [key: string]: boolean }>({}); + const fields: Array<{ name: 'name' | 'description' | 'namespace'; label: JSX.Element }> = [ + { + name: 'name', + label: ( + + ), + }, + { + name: 'description', + label: ( + + ), + }, + { + name: 'namespace', + label: ( + + ), + }, + ]; + + return ( + + {fields.map(({ name, label }) => { + return ( + + updateAgentConfig({ [name]: e.target.value })} + isInvalid={Boolean(touchedFields[name] && validation[name])} + onBlur={() => setTouchedFields({ ...touchedFields, [name]: true })} + /> + + ); + })} + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts new file mode 100644 index 0000000000000..d838221cd844e --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AgentConfigForm, agentConfigFormValidation } from './config_form'; +export { AgentConfigDeleteProvider } from './config_delete_provider'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/index.tsx new file mode 100644 index 0000000000000..c80c4496198be --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { HashRouter as Router, Switch, Route } from 'react-router-dom'; +import { AgentConfigListPage } from './list_page'; + +export const AgentConfigApp: React.FunctionComponent = () => ( + + + + + + + +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx new file mode 100644 index 0000000000000..c6fea7b22bcd1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; +import { NewAgentConfig } from '../../../../types'; +import { useCore, sendCreateAgentConfig } from '../../../../hooks'; +import { AgentConfigForm, agentConfigFormValidation } from '../../components'; + +interface Props { + onClose: () => void; +} + +export const CreateAgentConfigFlyout: React.FunctionComponent = ({ onClose }) => { + const { notifications } = useCore(); + + const [agentConfig, setAgentConfig] = useState({ + name: '', + description: '', + namespace: '', + }); + const [isLoading, setIsLoading] = useState(false); + const validation = agentConfigFormValidation(agentConfig); + + const updateAgentConfig = (updatedFields: Partial) => { + setAgentConfig({ + ...agentConfig, + ...updatedFields, + }); + }; + + const createAgentConfig = async () => { + return await sendCreateAgentConfig(agentConfig); + }; + + const header = ( + + +

+ +

+
+
+ ); + + const body = ( + + + + ); + + const footer = ( + + + + + + + + + 0} + onClick={async () => { + setIsLoading(true); + try { + const { data, error } = await createAgentConfig(); + if (data?.success) { + notifications.toasts.addSuccess( + i18n.translate( + 'xpack.ingestManager.createAgentConfig.successNotificationTitle', + { + defaultMessage: "Agent config '{name}' created", + values: { name: agentConfig.name }, + } + ) + ); + } else { + notifications.toasts.addDanger( + error + ? error.message + : i18n.translate( + 'xpack.ingestManager.createAgentConfig.errorNotificationTitle', + { + defaultMessage: 'Unable to create agent config', + } + ) + ); + } + } catch (e) { + notifications.toasts.addDanger( + i18n.translate('xpack.ingestManager.createAgentConfig.errorNotificationTitle', { + defaultMessage: 'Unable to create agent config', + }) + ); + } + setIsLoading(false); + onClose(); + }} + > + + + + + + ); + + return ( + + {header} + {body} + {footer} + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/index.ts new file mode 100644 index 0000000000000..43668b4ffb804 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { CreateAgentConfigFlyout } from './create_config'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx new file mode 100644 index 0000000000000..ca9fb195166f6 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx @@ -0,0 +1,280 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useState } from 'react'; +import { + EuiPageBody, + EuiPageContent, + EuiTitle, + EuiSpacer, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiEmptyPrompt, + // @ts-ignore + EuiSearchBar, + EuiBasicTable, + EuiLink, + EuiBadge, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { AgentConfig } from '../../../types'; +import { DEFAULT_AGENT_CONFIG_ID, AGENT_CONFIG_DETAILS_PATH } from '../../../constants'; +// import { SearchBar } from '../../../components'; +import { useGetAgentConfigs, usePagination, useLink } from '../../../hooks'; +import { AgentConfigDeleteProvider } from '../components'; +import { CreateAgentConfigFlyout } from './components'; + +export const AgentConfigListPage: React.FunctionComponent<{}> = () => { + // Create agent config flyout state + const [isCreateAgentConfigFlyoutOpen, setIsCreateAgentConfigFlyoutOpen] = useState( + false + ); + + // Table and search states + const [search, setSearch] = useState(''); + const { pagination, setPagination } = usePagination(); + const [selectedAgentConfigs, setSelectedAgentConfigs] = useState([]); + + // Fetch agent configs + const { isLoading, data: agentConfigData, sendRequest } = useGetAgentConfigs(); + + // Base path for config details + const DETAILS_URI = useLink(AGENT_CONFIG_DETAILS_PATH); + + // Some configs retrieved, set up table props + const columns = [ + { + field: 'name', + name: i18n.translate('xpack.ingestManager.agentConfigList.nameColumnTitle', { + defaultMessage: 'Name', + }), + render: (name: string, agentConfig: AgentConfig) => name || agentConfig.id, + }, + { + field: 'namespace', + name: i18n.translate('xpack.ingestManager.agentConfigList.namespaceColumnTitle', { + defaultMessage: 'Namespace', + }), + render: (namespace: string) => (namespace ? {namespace} : null), + }, + { + field: 'description', + name: i18n.translate('xpack.ingestManager.agentConfigList.descriptionColumnTitle', { + defaultMessage: 'Description', + }), + }, + { + field: 'datasources', + name: i18n.translate('xpack.ingestManager.agentConfigList.datasourcesCountColumnTitle', { + defaultMessage: 'Datasources', + }), + render: (datasources: AgentConfig['datasources']) => (datasources ? datasources.length : 0), + }, + { + name: i18n.translate('xpack.ingestManager.agentConfigList.actionsColumnTitle', { + defaultMessage: 'Actions', + }), + actions: [ + { + render: ({ id }: AgentConfig) => { + return ( + + + + ); + }, + }, + ], + width: '100px', + }, + ]; + + const emptyPrompt = ( + + + + } + actions={ + setIsCreateAgentConfigFlyoutOpen(true)} + > + + + } + /> + ); + + return ( + + + {isCreateAgentConfigFlyoutOpen ? ( + { + setIsCreateAgentConfigFlyoutOpen(false); + sendRequest(); + }} + /> + ) : null} + + +

+ +

+
+ + + + + + + + + + + + + + {selectedAgentConfigs.length ? ( + + + {deleteAgentConfigsPrompt => ( + { + deleteAgentConfigsPrompt( + selectedAgentConfigs.map(agentConfig => agentConfig.id), + () => { + sendRequest(); + setSelectedAgentConfigs([]); + } + ); + }} + > + + + )} + + + ) : null} + + {/* { + setPagination({ + ...pagination, + currentPage: 1, + }); + setSearch(newSearch); + }} + fieldPrefix={AGENT_CONFIG_SAVED_OBJECT_TYPE} + /> */} + + + sendRequest()}> + + + + + setIsCreateAgentConfigFlyoutOpen(true)} + > + + + + + + + + ) : !search.trim() && agentConfigData?.total === 0 ? ( + emptyPrompt + ) : ( + setSearch('')}> + + + ), + }} + /> + ) + } + items={agentConfigData ? agentConfigData.items : []} + itemId="id" + columns={columns} + isSelectable={true} + selection={{ + selectable: (agentConfig: AgentConfig) => agentConfig.id !== DEFAULT_AGENT_CONFIG_ID, + onSelectionChange: (newSelectedAgentConfigs: AgentConfig[]) => { + setSelectedAgentConfigs(newSelectedAgentConfigs); + }, + }} + pagination={{ + pageIndex: pagination.currentPage - 1, + pageSize: pagination.pageSize, + totalItemCount: agentConfigData ? agentConfigData.total : 0, + }} + onChange={({ page }: { page: { index: number; size: number } }) => { + const newPagination = { + ...pagination, + currentPage: page.index + 1, + pageSize: page.size, + }; + setPagination(newPagination); + sendRequest(); // todo: fix this to send pagination options + }} + /> +
+
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx new file mode 100644 index 0000000000000..ca8c22be9c34c --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useConfig } from '../../hooks'; + +export const EPMApp: React.FunctionComponent = () => { + const { epm } = useConfig(); + return epm.enabled ?
hello world - epm app
: null; +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx new file mode 100644 index 0000000000000..978414769004d --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { useConfig } from '../../hooks'; + +export const FleetApp: React.FunctionComponent = () => { + const { fleet } = useConfig(); + return fleet.enabled ?
hello world - fleet app
: null; +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx new file mode 100644 index 0000000000000..c691bb609d435 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/index.tsx @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { IngestManagerOverview } from './overview'; +export { EPMApp } from './epm'; +export { AgentConfigApp } from './agent_config'; +export { FleetApp } from './fleet'; + +export type Section = 'overview' | 'epm' | 'agent_config' | 'fleet'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx new file mode 100644 index 0000000000000..da4a78a39e2fe --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/overview/index.tsx @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; + +export const IngestManagerOverview: React.FunctionComponent = () => { + return
Ingest manager overview page
; +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts new file mode 100644 index 0000000000000..6502b0fff7123 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { agentConfigRouteService } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts new file mode 100644 index 0000000000000..8597d6fd59323 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { + // Object types + AgentConfig, + NewAgentConfig, + // API schemas + GetAgentConfigsResponse, + GetOneAgentConfigResponse, + CreateAgentConfigRequestSchema, + CreateAgentConfigResponse, + UpdateAgentConfigRequestSchema, + UpdateAgentConfigResponse, + DeleteAgentConfigsRequestSchema, + DeleteAgentConfigsResponse, +} from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/index.ts b/x-pack/plugins/ingest_manager/public/index.ts new file mode 100644 index 0000000000000..a9e40a2a42302 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { PluginInitializerContext } from 'kibana/public'; +import { IngestManagerPlugin } from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => { + return new IngestManagerPlugin(initializerContext); +}; diff --git a/x-pack/plugins/ingest_manager/public/plugin.ts b/x-pack/plugins/ingest_manager/public/plugin.ts new file mode 100644 index 0000000000000..ae244e7ebec3d --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/plugin.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + AppMountParameters, + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, +} from 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; +import { DataPublicPluginSetup } from '../../../../src/plugins/data/public'; +import { LicensingPluginSetup } from '../../licensing/public'; +import { PLUGIN_ID } from '../common/constants'; +import { IngestManagerConfigType } from '../common/types'; + +export { IngestManagerConfigType } from '../common/types'; + +export type IngestManagerSetup = void; +export type IngestManagerStart = void; + +export interface IngestManagerSetupDeps { + licensing: LicensingPluginSetup; + data: DataPublicPluginSetup; +} + +export class IngestManagerPlugin implements Plugin { + private config: IngestManagerConfigType; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.config = this.initializerContext.config.get(); + } + + public setup(core: CoreSetup, deps: IngestManagerSetupDeps) { + const config = this.config; + + // Register main Ingest Manager app + core.application.register({ + id: PLUGIN_ID, + category: DEFAULT_APP_CATEGORIES.management, + title: i18n.translate('xpack.ingestManager.appTitle', { defaultMessage: 'Ingest Manager' }), + euiIconType: 'savedObjectsApp', + async mount(params: AppMountParameters) { + const [coreStart] = await core.getStartServices(); + const { renderApp } = await import('./applications/ingest_manager'); + return renderApp(coreStart, params, deps, config); + }, + }); + } + + public start(core: CoreStart) {} + + public stop() {} +} diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts new file mode 100644 index 0000000000000..6b54afa1d81cb --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/constants/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { + // Routes + PLUGIN_ID, + EPM_API_ROUTES, + DATASOURCE_API_ROUTES, + AGENT_CONFIG_API_ROUTES, + FLEET_SETUP_API_ROUTES, + // Saved object types + AGENT_CONFIG_SAVED_OBJECT_TYPE, + DATASOURCE_SAVED_OBJECT_TYPE, + OUTPUT_SAVED_OBJECT_TYPE, + // Defaults + DEFAULT_AGENT_CONFIG_ID, + DEFAULT_AGENT_CONFIG, + DEFAULT_OUTPUT_ID, + DEFAULT_OUTPUT, +} from '../../common'; diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts new file mode 100644 index 0000000000000..5228f1e0e3469 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/index.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { PluginInitializerContext } from 'kibana/server'; +import { IngestManagerPlugin } from './plugin'; + +export const config = { + exposeToBrowser: { + epm: true, + fleet: true, + }, + schema: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + epm: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + registryUrl: schema.uri({ defaultValue: 'https://epr-staging.elastic.co' }), + }), + fleet: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + defaultOutputHost: schema.string({ defaultValue: 'http://localhost:9200' }), + }), + }), +}; + +export const plugin = (initializerContext: PluginInitializerContext) => { + return new IngestManagerPlugin(initializerContext); +}; + +// Saved object information bootstrapped by legacy `ingest_manager` plugin +// TODO: Remove once saved object mappings can be done from NP +export { savedObjectMappings } from './saved_objects'; +export { + OUTPUT_SAVED_OBJECT_TYPE, + AGENT_CONFIG_SAVED_OBJECT_TYPE, + DATASOURCE_SAVED_OBJECT_TYPE, +} from './constants'; + +// TODO: Temporary exports for Fleet dependencies, remove once Fleet moved into this plugin +export { agentConfigService, outputService } from './services'; diff --git a/x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts b/x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts new file mode 100644 index 0000000000000..38976957173f4 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/integration_tests/router.test.ts @@ -0,0 +1,190 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolve } from 'path'; +import * as kbnTestServer from '../../../../../src/test_utils/kbn_server'; + +function createXPackRoot(config: {} = {}) { + return kbnTestServer.createRoot({ + plugins: { + scanDirs: [], + paths: [ + resolve(__dirname, '../../../../../x-pack/plugins/encrypted_saved_objects'), + resolve(__dirname, '../../../../../x-pack/plugins/ingest_manager'), + resolve(__dirname, '../../../../../x-pack/plugins/licensing'), + ], + }, + migrations: { skip: true }, + xpack: config, + }); +} + +describe('ingestManager', () => { + describe('default. manager, EPM, and Fleet all disabled', () => { + let root: ReturnType; + beforeAll(async () => { + root = createXPackRoot(); + await root.setup(); + await root.start(); + }, 30000); + + afterAll(async () => await root.shutdown()); + + it('does not have agent config api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/agent_configs').expect(404); + }); + + it('does not have datasources api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/datasources').expect(404); + }); + + it('does not have EPM api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/epm').expect(404); + }); + + it('does not have Fleet api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/fleet').expect(404); + }); + }); + + describe('manager only (no EPM, no Fleet)', () => { + let root: ReturnType; + beforeAll(async () => { + const ingestManagerConfig = { + enabled: true, + }; + root = createXPackRoot({ + ingestManager: ingestManagerConfig, + }); + await root.setup(); + await root.start(); + }, 30000); + + afterAll(async () => await root.shutdown()); + + it('has agent config api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/agent_configs').expect(200); + }); + + it('has datasources api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/datasources').expect(200); + }); + + it('does not have EPM api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/epm/list').expect(404); + }); + + it('does not have Fleet api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/fleet/setup').expect(404); + }); + }); + + // For now, only the manager routes (/agent_configs & /datasources) are added + // EPM and ingest will be conditionally added when we enable these lines + // https://github.com/jfsiii/kibana/blob/f73b54ebb7e0f6fc00efd8a6800a01eb2d9fb772/x-pack/plugins/ingest_manager/server/plugin.ts#L84 + // adding tests to confirm the Fleet & EPM routes are never added + + describe('manager and EPM; no Fleet', () => { + let root: ReturnType; + beforeAll(async () => { + const ingestManagerConfig = { + enabled: true, + epm: { enabled: true }, + }; + root = createXPackRoot({ + ingestManager: ingestManagerConfig, + }); + await root.setup(); + await root.start(); + }, 30000); + + afterAll(async () => await root.shutdown()); + + it('has agent config api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/agent_configs').expect(200); + }); + + it('has datasources api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/datasources').expect(200); + }); + + it('does not have EPM api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/epm/list').expect(404); + }); + + it('does not have Fleet api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/fleet/setup').expect(404); + }); + }); + + describe('manager and Fleet; no EPM)', () => { + let root: ReturnType; + beforeAll(async () => { + const ingestManagerConfig = { + enabled: true, + epm: { enabled: true }, + fleet: { enabled: true }, + }; + root = createXPackRoot({ + ingestManager: ingestManagerConfig, + }); + await root.setup(); + await root.start(); + }, 30000); + + afterAll(async () => await root.shutdown()); + + it('has agent config api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/agent_configs').expect(200); + }); + + it('has datasources api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/datasources').expect(200); + }); + + it('does not have EPM api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/epm/list').expect(404); + }); + + it('does not have Fleet api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/fleet/setup').expect(404); + }); + }); + + describe('all flags enabled: manager, EPM, and Fleet)', () => { + let root: ReturnType; + beforeAll(async () => { + const ingestManagerConfig = { + enabled: true, + epm: { enabled: true }, + fleet: { enabled: true }, + }; + root = createXPackRoot({ + ingestManager: ingestManagerConfig, + }); + await root.setup(); + await root.start(); + }, 30000); + + afterAll(async () => await root.shutdown()); + + it('has agent config api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/agent_configs').expect(200); + }); + + it('has datasources api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/datasources').expect(200); + }); + + it('does not have EPM api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/epm/list').expect(404); + }); + + it('does not have Fleet api', async () => { + await kbnTestServer.request.get(root, '/api/ingest_manager/fleet/setup').expect(404); + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts new file mode 100644 index 0000000000000..4f30a171ab0c0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Observable } from 'rxjs'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { EncryptedSavedObjectsPluginStart } from '../../encrypted_saved_objects/server'; +import { SecurityPluginSetup } from '../../security/server'; +import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; +import { PLUGIN_ID } from './constants'; +import { appContextService } from './services'; +import { registerDatasourceRoutes, registerAgentConfigRoutes } from './routes'; +import { IngestManagerConfigType } from '../common'; + +export interface IngestManagerSetupDeps { + licensing: LicensingPluginSetup; + security?: SecurityPluginSetup; + features?: FeaturesPluginSetup; +} + +export interface IngestManagerAppContext { + encryptedSavedObjects: EncryptedSavedObjectsPluginStart; + security?: SecurityPluginSetup; + config$?: Observable; +} + +export class IngestManagerPlugin implements Plugin { + private config$: Observable; + private security: SecurityPluginSetup | undefined; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.config$ = this.initializerContext.config.create(); + } + + public async setup(core: CoreSetup, deps: IngestManagerSetupDeps) { + if (deps.security) { + this.security = deps.security; + } + + // Register feature + // TODO: Flesh out privileges + if (deps.features) { + deps.features.registerFeature({ + id: PLUGIN_ID, + name: 'Ingest Manager', + icon: 'savedObjectsApp', + navLinkId: PLUGIN_ID, + app: [PLUGIN_ID, 'kibana'], + privileges: { + all: { + api: [PLUGIN_ID], + savedObject: { + all: [], + read: [], + }, + ui: ['show'], + }, + read: { + api: [PLUGIN_ID], + savedObject: { + all: [], + read: [], + }, + ui: ['show'], + }, + }, + }); + } + + // Create router + const router = core.http.createRouter(); + + // Register routes + registerAgentConfigRoutes(router); + registerDatasourceRoutes(router); + + // Optional route registration depending on Kibana config + // restore when EPM & Fleet features are added + // const config = await this.config$.pipe(first()).toPromise(); + // if (config.epm.enabled) registerEPMRoutes(router); + // if (config.fleet.enabled) registerFleetSetupRoutes(router); + } + + public async start( + core: CoreStart, + plugins: { + encryptedSavedObjects: EncryptedSavedObjectsPluginStart; + } + ) { + appContextService.start({ + encryptedSavedObjects: plugins.encryptedSavedObjects, + security: this.security, + config$: this.config$, + }); + } + + public async stop() { + appContextService.stop(); + } +} diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts new file mode 100644 index 0000000000000..67da6a4cf2f1d --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { TypeOf } from '@kbn/config-schema'; +import { RequestHandler } from 'kibana/server'; +import { appContextService, agentConfigService } from '../../services'; +import { + GetAgentConfigsRequestSchema, + GetAgentConfigsResponse, + GetOneAgentConfigRequestSchema, + GetOneAgentConfigResponse, + CreateAgentConfigRequestSchema, + CreateAgentConfigResponse, + UpdateAgentConfigRequestSchema, + UpdateAgentConfigResponse, + DeleteAgentConfigsRequestSchema, + DeleteAgentConfigsResponse, +} from '../../types'; + +export const getAgentConfigsHandler: RequestHandler< + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const { items, total, page, perPage } = await agentConfigService.list(soClient, request.query); + const body: GetAgentConfigsResponse = { + items, + total, + page, + perPage, + success: true, + }; + return response.ok({ body }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const getOneAgentConfigHandler: RequestHandler> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const agentConfig = await agentConfigService.get(soClient, request.params.agentConfigId); + if (agentConfig) { + const body: GetOneAgentConfigResponse = { + item: agentConfig, + success: true, + }; + return response.ok({ + body, + }); + } else { + return response.customError({ + statusCode: 404, + body: { message: 'Agent config not found' }, + }); + } + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const createAgentConfigHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const user = await appContextService.getSecurity()?.authc.getCurrentUser(request); + try { + const agentConfig = await agentConfigService.create(soClient, request.body, { + user: user || undefined, + }); + const body: CreateAgentConfigResponse = { item: agentConfig, success: true }; + return response.ok({ + body, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const updateAgentConfigHandler: RequestHandler< + TypeOf, + unknown, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const user = await appContextService.getSecurity()?.authc.getCurrentUser(request); + try { + const agentConfig = await agentConfigService.update( + soClient, + request.params.agentConfigId, + request.body, + { + user: user || undefined, + } + ); + const body: UpdateAgentConfigResponse = { item: agentConfig, success: true }; + return response.ok({ + body, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const deleteAgentConfigsHandler: RequestHandler< + unknown, + unknown, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const body: DeleteAgentConfigsResponse = await agentConfigService.delete( + soClient, + request.body.agentConfigIds + ); + return response.ok({ + body, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts new file mode 100644 index 0000000000000..67ad915b71e45 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IRouter } from 'kibana/server'; +import { PLUGIN_ID, AGENT_CONFIG_API_ROUTES } from '../../constants'; +import { + GetAgentConfigsRequestSchema, + GetOneAgentConfigRequestSchema, + CreateAgentConfigRequestSchema, + UpdateAgentConfigRequestSchema, + DeleteAgentConfigsRequestSchema, +} from '../../types'; +import { + getAgentConfigsHandler, + getOneAgentConfigHandler, + createAgentConfigHandler, + updateAgentConfigHandler, + deleteAgentConfigsHandler, +} from './handlers'; + +export const registerRoutes = (router: IRouter) => { + // List + router.get( + { + path: AGENT_CONFIG_API_ROUTES.LIST_PATTERN, + validate: GetAgentConfigsRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + getAgentConfigsHandler + ); + + // Get one + router.get( + { + path: AGENT_CONFIG_API_ROUTES.INFO_PATTERN, + validate: GetOneAgentConfigRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + getOneAgentConfigHandler + ); + + // Create + router.post( + { + path: AGENT_CONFIG_API_ROUTES.CREATE_PATTERN, + validate: CreateAgentConfigRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + createAgentConfigHandler + ); + + // Update + router.put( + { + path: AGENT_CONFIG_API_ROUTES.UPDATE_PATTERN, + validate: UpdateAgentConfigRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + updateAgentConfigHandler + ); + + // Delete + router.post( + { + path: AGENT_CONFIG_API_ROUTES.DELETE_PATTERN, + validate: DeleteAgentConfigsRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + deleteAgentConfigsHandler + ); +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts new file mode 100644 index 0000000000000..78cad2e21c5fa --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { TypeOf } from '@kbn/config-schema'; +import { RequestHandler } from 'kibana/server'; +import { datasourceService } from '../../services'; +import { + GetDatasourcesRequestSchema, + GetOneDatasourceRequestSchema, + CreateDatasourceRequestSchema, + UpdateDatasourceRequestSchema, + DeleteDatasourcesRequestSchema, + DeleteDatasourcesResponse, +} from '../../types'; + +export const getDatasourcesHandler: RequestHandler< + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const { items, total, page, perPage } = await datasourceService.list(soClient, request.query); + return response.ok({ + body: { + items, + total, + page, + perPage, + success: true, + }, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const getOneDatasourceHandler: RequestHandler> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const datasource = await datasourceService.get(soClient, request.params.datasourceId); + if (datasource) { + return response.ok({ + body: { + item: datasource, + success: true, + }, + }); + } else { + return response.customError({ + statusCode: 404, + body: { message: 'Datasource not found' }, + }); + } + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const createDatasourceHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const datasource = await datasourceService.create(soClient, request.body); + return response.ok({ + body: { item: datasource, success: true }, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const updateDatasourceHandler: RequestHandler< + TypeOf, + unknown, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const datasource = await datasourceService.update( + soClient, + request.params.datasourceId, + request.body + ); + return response.ok({ + body: { item: datasource, success: true }, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const deleteDatasourcesHandler: RequestHandler< + unknown, + unknown, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const body: DeleteDatasourcesResponse = await datasourceService.delete( + soClient, + request.body.datasourceIds + ); + return response.ok({ + body, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/datasource/index.ts b/x-pack/plugins/ingest_manager/server/routes/datasource/index.ts new file mode 100644 index 0000000000000..d9e3ba9de8838 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/datasource/index.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IRouter } from 'kibana/server'; +import { PLUGIN_ID, DATASOURCE_API_ROUTES } from '../../constants'; +import { + GetDatasourcesRequestSchema, + GetOneDatasourceRequestSchema, + CreateDatasourceRequestSchema, + UpdateDatasourceRequestSchema, + DeleteDatasourcesRequestSchema, +} from '../../types'; +import { + getDatasourcesHandler, + getOneDatasourceHandler, + createDatasourceHandler, + updateDatasourceHandler, + deleteDatasourcesHandler, +} from './handlers'; + +export const registerRoutes = (router: IRouter) => { + // List + router.get( + { + path: DATASOURCE_API_ROUTES.LIST_PATTERN, + validate: GetDatasourcesRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + getDatasourcesHandler + ); + + // Get one + router.get( + { + path: DATASOURCE_API_ROUTES.INFO_PATTERN, + validate: GetOneDatasourceRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + getOneDatasourceHandler + ); + + // Create + router.post( + { + path: DATASOURCE_API_ROUTES.CREATE_PATTERN, + validate: CreateDatasourceRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + createDatasourceHandler + ); + + // Update + router.put( + { + path: DATASOURCE_API_ROUTES.UPDATE_PATTERN, + validate: UpdateDatasourceRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + updateDatasourceHandler + ); + + // Delete + router.post( + { + path: DATASOURCE_API_ROUTES.DELETE_PATTERN, + validate: DeleteDatasourcesRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + deleteDatasourcesHandler + ); +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/index.ts b/x-pack/plugins/ingest_manager/server/routes/epm/index.ts new file mode 100644 index 0000000000000..7bdcafe633843 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/epm/index.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IRouter } from 'kibana/server'; +import { PLUGIN_ID, EPM_API_ROUTES } from '../../constants'; + +export const registerRoutes = (router: IRouter) => { + router.get( + { + path: EPM_API_ROUTES.CATEGORIES_PATTERN, + validate: false, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + async (context, req, res) => { + return res.ok({ body: { hello: 'world' } }); + } + ); + + router.get( + { + path: EPM_API_ROUTES.LIST_PATTERN, + validate: false, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + async (context, req, res) => { + return res.ok({ body: { hello: 'world' } }); + } + ); + + router.get( + { + path: `${EPM_API_ROUTES.INFO_PATTERN}/{filePath*}`, + validate: false, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + async (context, req, res) => { + return res.ok({ body: { hello: 'world' } }); + } + ); + + router.get( + { + path: EPM_API_ROUTES.INFO_PATTERN, + validate: false, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + async (context, req, res) => { + return res.ok({ body: { hello: 'world' } }); + } + ); + + router.get( + { + path: EPM_API_ROUTES.INSTALL_PATTERN, + validate: false, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + async (context, req, res) => { + return res.ok({ body: { hello: 'world' } }); + } + ); + + router.get( + { + path: EPM_API_ROUTES.DELETE_PATTERN, + validate: false, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + async (context, req, res) => { + return res.ok({ body: { hello: 'world' } }); + } + ); +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/fleet_setup/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/fleet_setup/handlers.ts new file mode 100644 index 0000000000000..72fe34eb23c5f --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/fleet_setup/handlers.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { TypeOf } from '@kbn/config-schema'; +import { RequestHandler } from 'kibana/server'; +import { DEFAULT_OUTPUT_ID } from '../../constants'; +import { outputService, agentConfigService } from '../../services'; +import { CreateFleetSetupRequestSchema, CreateFleetSetupResponse } from '../../types'; + +export const getFleetSetupHandler: RequestHandler = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const successBody: CreateFleetSetupResponse = { isInitialized: true }; + const failureBody: CreateFleetSetupResponse = { isInitialized: false }; + try { + const output = await outputService.get(soClient, DEFAULT_OUTPUT_ID); + if (output) { + return response.ok({ + body: successBody, + }); + } else { + return response.ok({ + body: failureBody, + }); + } + } catch (e) { + return response.ok({ + body: failureBody, + }); + } +}; + +export const createFleetSetupHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + await outputService.createDefaultOutput(soClient, { + username: request.body.admin_username, + password: request.body.admin_password, + }); + await agentConfigService.ensureDefaultAgentConfig(soClient); + return response.ok({ + body: { isInitialized: true }, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/fleet_setup/index.ts b/x-pack/plugins/ingest_manager/server/routes/fleet_setup/index.ts new file mode 100644 index 0000000000000..c23164db6b6eb --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/fleet_setup/index.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IRouter } from 'kibana/server'; +import { PLUGIN_ID, FLEET_SETUP_API_ROUTES } from '../../constants'; +import { GetFleetSetupRequestSchema, CreateFleetSetupRequestSchema } from '../../types'; +import { getFleetSetupHandler, createFleetSetupHandler } from './handlers'; + +export const registerRoutes = (router: IRouter) => { + // Get + router.get( + { + path: FLEET_SETUP_API_ROUTES.INFO_PATTERN, + validate: GetFleetSetupRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + getFleetSetupHandler + ); + + // Create + router.post( + { + path: FLEET_SETUP_API_ROUTES.CREATE_PATTERN, + validate: CreateFleetSetupRequestSchema, + options: { tags: [`access:${PLUGIN_ID}`] }, + }, + createFleetSetupHandler + ); +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/index.ts b/x-pack/plugins/ingest_manager/server/routes/index.ts new file mode 100644 index 0000000000000..b458ef31dee45 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { registerRoutes as registerAgentConfigRoutes } from './agent_config'; +export { registerRoutes as registerDatasourceRoutes } from './datasource'; +export { registerRoutes as registerEPMRoutes } from './epm'; +export { registerRoutes as registerFleetSetupRoutes } from './fleet_setup'; diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts new file mode 100644 index 0000000000000..976556f388acf --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + OUTPUT_SAVED_OBJECT_TYPE, + AGENT_CONFIG_SAVED_OBJECT_TYPE, + DATASOURCE_SAVED_OBJECT_TYPE, +} from './constants'; + +/* + * Saved object mappings + * + * Please update typings in `/common/types` if mappings are updated. + */ +export const savedObjectMappings = { + [AGENT_CONFIG_SAVED_OBJECT_TYPE]: { + properties: { + id: { type: 'keyword' }, + name: { type: 'text' }, + namespace: { type: 'keyword' }, + description: { type: 'text' }, + status: { type: 'keyword' }, + datasources: { type: 'keyword' }, + updated_on: { type: 'keyword' }, + updated_by: { type: 'keyword' }, + }, + }, + [OUTPUT_SAVED_OBJECT_TYPE]: { + properties: { + id: { type: 'keyword' }, + name: { type: 'keyword' }, + type: { type: 'keyword' }, + username: { type: 'keyword' }, + password: { type: 'keyword' }, + index_name: { type: 'keyword' }, + ingest_pipeline: { type: 'keyword' }, + hosts: { type: 'keyword' }, + api_key: { type: 'keyword' }, + admin_username: { type: 'binary' }, + admin_password: { type: 'binary' }, + config: { type: 'flattened' }, + }, + }, + [DATASOURCE_SAVED_OBJECT_TYPE]: { + properties: { + id: { type: 'keyword' }, + name: { type: 'keyword' }, + namespace: { type: 'keyword' }, + read_alias: { type: 'keyword' }, + agent_config_id: { type: 'keyword' }, + package: { + properties: { + assets: { + properties: { + id: { type: 'keyword' }, + type: { type: 'keyword' }, + }, + }, + description: { type: 'keyword' }, + name: { type: 'keyword' }, + title: { type: 'keyword' }, + version: { type: 'keyword' }, + }, + }, + streams: { + properties: { + config: { type: 'flattened' }, + id: { type: 'keyword' }, + input: { type: 'flattened' }, + output_id: { type: 'keyword' }, + processors: { type: 'keyword' }, + }, + }, + }, + }, +}; diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts new file mode 100644 index 0000000000000..0690e115ca862 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts @@ -0,0 +1,258 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SavedObjectsClientContract } from 'kibana/server'; +import { AuthenticatedUser } from '../../../security/server'; +import { + DEFAULT_AGENT_CONFIG_ID, + DEFAULT_AGENT_CONFIG, + AGENT_CONFIG_SAVED_OBJECT_TYPE, +} from '../constants'; +import { + NewAgentConfig, + AgentConfig, + AgentConfigStatus, + AgentConfigUpdateHandler, + ListWithKuery, + DeleteAgentConfigsResponse, +} from '../types'; +import { datasourceService } from './datasource'; + +const SAVED_OBJECT_TYPE = AGENT_CONFIG_SAVED_OBJECT_TYPE; + +class AgentConfigService { + private eventsHandler: AgentConfigUpdateHandler[] = []; + + public registerAgentConfigUpdateHandler(handler: AgentConfigUpdateHandler) { + this.eventsHandler.push(handler); + } + + public triggerAgentConfigUpdatedEvent: AgentConfigUpdateHandler = async ( + action, + agentConfigId + ) => { + for (const handler of this.eventsHandler) { + await handler(action, agentConfigId); + } + }; + + private async _update( + soClient: SavedObjectsClientContract, + id: string, + agentConfig: Partial, + user?: AuthenticatedUser + ): Promise { + await soClient.update(SAVED_OBJECT_TYPE, id, { + ...agentConfig, + updated_on: new Date().toString(), + updated_by: user ? user.username : 'system', + }); + + await this.triggerAgentConfigUpdatedEvent('updated', id); + + return (await this.get(soClient, id)) as AgentConfig; + } + + public async ensureDefaultAgentConfig(soClient: SavedObjectsClientContract) { + let defaultAgentConfig; + + try { + defaultAgentConfig = await this.get(soClient, DEFAULT_AGENT_CONFIG_ID); + } catch (err) { + if (!err.isBoom || err.output.statusCode !== 404) { + throw err; + } + } + + if (!defaultAgentConfig) { + const newDefaultAgentConfig: NewAgentConfig = { + ...DEFAULT_AGENT_CONFIG, + }; + + await this.create(soClient, newDefaultAgentConfig, { + id: DEFAULT_AGENT_CONFIG_ID, + }); + } + } + + public async create( + soClient: SavedObjectsClientContract, + agentConfig: NewAgentConfig, + options?: { id?: string; user?: AuthenticatedUser } + ): Promise { + const newSo = await soClient.create( + SAVED_OBJECT_TYPE, + { + ...agentConfig, + updated_on: new Date().toISOString(), + updated_by: options?.user?.username || 'system', + } as AgentConfig, + options + ); + + await this.triggerAgentConfigUpdatedEvent('created', newSo.id); + + return { + id: newSo.id, + ...newSo.attributes, + }; + } + + public async get(soClient: SavedObjectsClientContract, id: string): Promise { + const agentConfigSO = await soClient.get(SAVED_OBJECT_TYPE, id); + if (!agentConfigSO) { + return null; + } + + if (agentConfigSO.error) { + throw new Error(agentConfigSO.error.message); + } + + return { + id: agentConfigSO.id, + ...agentConfigSO.attributes, + datasources: + (await datasourceService.getByIDs( + soClient, + (agentConfigSO.attributes.datasources as string[]) || [] + )) || [], + }; + } + + public async list( + soClient: SavedObjectsClientContract, + options: ListWithKuery + ): Promise<{ items: AgentConfig[]; total: number; page: number; perPage: number }> { + const { page = 1, perPage = 20, kuery } = options; + + const agentConfigs = await soClient.find({ + type: SAVED_OBJECT_TYPE, + page, + perPage, + // To ensure users don't need to know about SO data structure... + filter: kuery + ? kuery.replace( + new RegExp(`${SAVED_OBJECT_TYPE}\.`, 'g'), + `${SAVED_OBJECT_TYPE}.attributes.` + ) + : undefined, + }); + + return { + items: agentConfigs.saved_objects.map(agentConfigSO => { + return { + id: agentConfigSO.id, + ...agentConfigSO.attributes, + }; + }), + total: agentConfigs.total, + page, + perPage, + }; + } + + public async update( + soClient: SavedObjectsClientContract, + id: string, + agentConfig: Partial, + options?: { user?: AuthenticatedUser } + ): Promise { + const oldAgentConfig = await this.get(soClient, id); + + if (!oldAgentConfig) { + throw new Error('Agent config not found'); + } + + if ( + oldAgentConfig.status === AgentConfigStatus.Inactive && + agentConfig.status !== AgentConfigStatus.Active + ) { + throw new Error( + `Agent config ${id} cannot be updated because it is ${oldAgentConfig.status}` + ); + } + + return this._update(soClient, id, agentConfig, options?.user); + } + + public async assignDatasources( + soClient: SavedObjectsClientContract, + id: string, + datasourceIds: string[], + options?: { user?: AuthenticatedUser } + ): Promise { + const oldAgentConfig = await this.get(soClient, id); + + if (!oldAgentConfig) { + throw new Error('Agent config not found'); + } + + return await this._update( + soClient, + id, + { + ...oldAgentConfig, + datasources: [...((oldAgentConfig.datasources || []) as string[])].concat(datasourceIds), + }, + options?.user + ); + } + + public async unassignDatasources( + soClient: SavedObjectsClientContract, + id: string, + datasourceIds: string[], + options?: { user?: AuthenticatedUser } + ): Promise { + const oldAgentConfig = await this.get(soClient, id); + + if (!oldAgentConfig) { + throw new Error('Agent config not found'); + } + + return await this._update( + soClient, + id, + { + ...oldAgentConfig, + datasources: [...((oldAgentConfig.datasources || []) as string[])].filter( + dsId => !datasourceIds.includes(dsId) + ), + }, + options?.user + ); + } + + public async delete( + soClient: SavedObjectsClientContract, + ids: string[] + ): Promise { + const result: DeleteAgentConfigsResponse = []; + + if (ids.includes(DEFAULT_AGENT_CONFIG_ID)) { + throw new Error('The default agent configuration cannot be deleted'); + } + + for (const id of ids) { + try { + await soClient.delete(SAVED_OBJECT_TYPE, id); + await this.triggerAgentConfigUpdatedEvent('deleted', id); + result.push({ + id, + success: true, + }); + } catch (e) { + result.push({ + id, + success: false, + }); + } + } + + return result; + } +} + +export const agentConfigService = new AgentConfigService(); diff --git a/x-pack/plugins/ingest_manager/server/services/app_context.ts b/x-pack/plugins/ingest_manager/server/services/app_context.ts new file mode 100644 index 0000000000000..69a014fca37fb --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/app_context.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { BehaviorSubject, Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; +import { SecurityPluginSetup } from '../../../security/server'; +import { IngestManagerConfigType } from '../../common'; +import { IngestManagerAppContext } from '../plugin'; + +class AppContextService { + private encryptedSavedObjects: EncryptedSavedObjectsPluginStart | undefined; + private security: SecurityPluginSetup | undefined; + private config$?: Observable; + private configSubject$?: BehaviorSubject; + + public async start(appContext: IngestManagerAppContext) { + this.encryptedSavedObjects = appContext.encryptedSavedObjects; + this.security = appContext.security; + + if (appContext.config$) { + this.config$ = appContext.config$; + const initialValue = await this.config$.pipe(first()).toPromise(); + this.configSubject$ = new BehaviorSubject(initialValue); + this.config$.subscribe(this.configSubject$); + } + } + + public stop() {} + + public getEncryptedSavedObjects() { + return this.encryptedSavedObjects; + } + + public getSecurity() { + return this.security; + } + + public getConfig() { + return this.configSubject$?.value; + } + + public getConfig$() { + return this.config$; + } +} + +export const appContextService = new AppContextService(); diff --git a/x-pack/plugins/ingest_manager/server/services/datasource.ts b/x-pack/plugins/ingest_manager/server/services/datasource.ts new file mode 100644 index 0000000000000..b305ccaab777b --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/datasource.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SavedObjectsClientContract } from 'kibana/server'; +import { DATASOURCE_SAVED_OBJECT_TYPE } from '../constants'; +import { NewDatasource, Datasource, DeleteDatasourcesResponse, ListWithKuery } from '../types'; + +const SAVED_OBJECT_TYPE = DATASOURCE_SAVED_OBJECT_TYPE; + +class DatasourceService { + public async create( + soClient: SavedObjectsClientContract, + datasource: NewDatasource, + options?: { id?: string } + ): Promise { + const newSo = await soClient.create( + SAVED_OBJECT_TYPE, + datasource as Datasource, + options + ); + + return { + id: newSo.id, + ...newSo.attributes, + }; + } + + public async get(soClient: SavedObjectsClientContract, id: string): Promise { + const datasourceSO = await soClient.get(SAVED_OBJECT_TYPE, id); + if (!datasourceSO) { + return null; + } + + if (datasourceSO.error) { + throw new Error(datasourceSO.error.message); + } + + return { + id: datasourceSO.id, + ...datasourceSO.attributes, + }; + } + + public async getByIDs( + soClient: SavedObjectsClientContract, + ids: string[] + ): Promise { + const datasourceSO = await soClient.bulkGet( + ids.map(id => ({ + id, + type: SAVED_OBJECT_TYPE, + })) + ); + if (!datasourceSO) { + return null; + } + + return datasourceSO.saved_objects.map(so => ({ + id: so.id, + ...so.attributes, + })); + } + + public async list( + soClient: SavedObjectsClientContract, + options: ListWithKuery + ): Promise<{ items: Datasource[]; total: number; page: number; perPage: number }> { + const { page = 1, perPage = 20, kuery } = options; + + const datasources = await soClient.find({ + type: SAVED_OBJECT_TYPE, + page, + perPage, + // To ensure users don't need to know about SO data structure... + filter: kuery + ? kuery.replace( + new RegExp(`${SAVED_OBJECT_TYPE}\.`, 'g'), + `${SAVED_OBJECT_TYPE}.attributes.` + ) + : undefined, + }); + + return { + items: datasources.saved_objects.map(datasourceSO => { + return { + id: datasourceSO.id, + ...datasourceSO.attributes, + }; + }), + total: datasources.total, + page, + perPage, + }; + } + + public async update( + soClient: SavedObjectsClientContract, + id: string, + datasource: NewDatasource + ): Promise { + await soClient.update(SAVED_OBJECT_TYPE, id, datasource); + return (await this.get(soClient, id)) as Datasource; + } + + public async delete( + soClient: SavedObjectsClientContract, + ids: string[] + ): Promise { + const result: DeleteDatasourcesResponse = []; + + for (const id of ids) { + try { + await soClient.delete(SAVED_OBJECT_TYPE, id); + result.push({ + id, + success: true, + }); + } catch (e) { + result.push({ + id, + success: false, + }); + } + } + + return result; + } +} + +export const datasourceService = new DatasourceService(); diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts new file mode 100644 index 0000000000000..dd0c898afa425 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { appContextService } from './app_context'; + +// Saved object services +export { datasourceService } from './datasource'; +export { agentConfigService } from './agent_config'; +export { outputService } from './output'; diff --git a/x-pack/plugins/ingest_manager/server/services/output.ts b/x-pack/plugins/ingest_manager/server/services/output.ts new file mode 100644 index 0000000000000..8f60ed295205d --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/output.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SavedObjectsClientContract, KibanaRequest } from 'kibana/server'; +import { NewOutput, Output } from '../types'; +import { DEFAULT_OUTPUT, DEFAULT_OUTPUT_ID, OUTPUT_SAVED_OBJECT_TYPE } from '../constants'; +import { appContextService } from './app_context'; + +const SAVED_OBJECT_TYPE = OUTPUT_SAVED_OBJECT_TYPE; + +class OutputService { + public async createDefaultOutput( + soClient: SavedObjectsClientContract, + adminUser: { username: string; password: string } + ) { + let defaultOutput; + + try { + defaultOutput = await this.get(soClient, DEFAULT_OUTPUT_ID); + } catch (err) { + if (!err.isBoom || err.output.statusCode !== 404) { + throw err; + } + } + + if (!defaultOutput) { + const newDefaultOutput = { + ...DEFAULT_OUTPUT, + hosts: [appContextService.getConfig()!.fleet.defaultOutputHost], + api_key: await this.createDefaultOutputApiKey(adminUser.username, adminUser.password), + admin_username: adminUser.username, + admin_password: adminUser.password, + } as NewOutput; + + await this.create(soClient, newDefaultOutput, { + id: DEFAULT_OUTPUT_ID, + }); + } + } + + public async getAdminUser() { + const so = await appContextService + .getEncryptedSavedObjects() + ?.getDecryptedAsInternalUser(OUTPUT_SAVED_OBJECT_TYPE, DEFAULT_OUTPUT_ID); + + return { + username: so!.attributes.admin_username, + password: so!.attributes.admin_password, + }; + } + + // TODO: TEMPORARY this is going to be per agent + private async createDefaultOutputApiKey(username: string, password: string): Promise { + const key = await appContextService.getSecurity()?.authc.createAPIKey( + { + headers: { + authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, + }, + } as KibanaRequest, + { + name: 'fleet-default-output', + role_descriptors: { + 'fleet-output': { + cluster: ['monitor'], + index: [ + { + names: ['logs-*', 'metrics-*'], + privileges: ['write'], + }, + ], + }, + }, + } + ); + if (!key) { + throw new Error('An error occured while creating default API Key'); + } + return `${key.id}:${key.api_key}`; + } + + public async create( + soClient: SavedObjectsClientContract, + output: NewOutput, + options?: { id?: string } + ): Promise { + const newSo = await soClient.create(SAVED_OBJECT_TYPE, output as Output, options); + + return { + id: newSo.id, + ...newSo.attributes, + }; + } + + public async get(soClient: SavedObjectsClientContract, id: string): Promise { + const outputSO = await soClient.get(SAVED_OBJECT_TYPE, id); + if (!outputSO) { + return null; + } + + if (outputSO.error) { + throw new Error(outputSO.error.message); + } + + return { + id: outputSO.id, + ...outputSO.attributes, + }; + } +} + +export const outputService = new OutputService(); diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx new file mode 100644 index 0000000000000..f44d03923d424 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './models'; +export * from './rest_spec'; + +export type AgentConfigUpdateHandler = ( + action: 'created' | 'updated' | 'deleted', + agentConfigId: string +) => Promise; diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts new file mode 100644 index 0000000000000..d3acb0c167837 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema, TypeOf } from '@kbn/config-schema'; +import { DatasourceSchema } from './datasource'; + +export enum AgentConfigStatus { + Active = 'active', + Inactive = 'inactive', +} + +const AgentConfigBaseSchema = { + name: schema.string(), + namespace: schema.string(), + description: schema.maybe(schema.string()), +}; + +export const NewAgentConfigSchema = schema.object({ + ...AgentConfigBaseSchema, +}); + +export const AgentConfigSchema = schema.object({ + ...AgentConfigBaseSchema, + id: schema.string(), + status: schema.oneOf([ + schema.literal(AgentConfigStatus.Active), + schema.literal(AgentConfigStatus.Inactive), + ]), + datasources: schema.oneOf([schema.arrayOf(schema.string()), schema.arrayOf(DatasourceSchema)]), + updated_on: schema.string(), + updated_by: schema.string(), +}); + +export type NewAgentConfig = TypeOf; + +export type AgentConfig = TypeOf; diff --git a/x-pack/plugins/ingest_manager/server/types/models/datasource.ts b/x-pack/plugins/ingest_manager/server/types/models/datasource.ts new file mode 100644 index 0000000000000..4179d4c3a511a --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/models/datasource.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema, TypeOf } from '@kbn/config-schema'; + +const DatasourceBaseSchema = { + name: schema.string(), + namespace: schema.maybe(schema.string()), + read_alias: schema.maybe(schema.string()), + agent_config_id: schema.string(), + package: schema.maybe( + schema.object({ + assets: schema.arrayOf( + schema.object({ + id: schema.string(), + type: schema.string(), + }) + ), + description: schema.string(), + name: schema.string(), + title: schema.string(), + version: schema.string(), + }) + ), + streams: schema.arrayOf( + schema.object({ + config: schema.recordOf(schema.string(), schema.any()), + input: schema.object({ + type: schema.string(), + config: schema.recordOf(schema.string(), schema.any()), + fields: schema.maybe(schema.arrayOf(schema.recordOf(schema.string(), schema.any()))), + ilm_policy: schema.maybe(schema.string()), + index_template: schema.maybe(schema.string()), + ingest_pipelines: schema.maybe(schema.arrayOf(schema.string())), + }), + output_id: schema.string(), + processors: schema.maybe(schema.arrayOf(schema.string())), + }) + ), +}; + +export const NewDatasourceSchema = schema.object({ + ...DatasourceBaseSchema, +}); + +export const DatasourceSchema = schema.object({ + ...DatasourceBaseSchema, + id: schema.string(), +}); + +export type NewDatasource = TypeOf; + +export type Datasource = TypeOf; diff --git a/x-pack/plugins/ingest_manager/server/types/models/index.ts b/x-pack/plugins/ingest_manager/server/types/models/index.ts new file mode 100644 index 0000000000000..959dfe1d937b9 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/models/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './agent_config'; +export * from './datasource'; +export * from './output'; diff --git a/x-pack/plugins/ingest_manager/server/types/models/output.ts b/x-pack/plugins/ingest_manager/server/types/models/output.ts new file mode 100644 index 0000000000000..610fa6796cc2d --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/models/output.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema, TypeOf } from '@kbn/config-schema'; + +export enum OutputType { + Elasticsearch = 'elasticsearch', +} + +const OutputBaseSchema = { + name: schema.string(), + type: schema.oneOf([schema.literal(OutputType.Elasticsearch)]), + username: schema.maybe(schema.string()), + password: schema.maybe(schema.string()), + index_name: schema.maybe(schema.string()), + ingest_pipeline: schema.maybe(schema.string()), + hosts: schema.maybe(schema.arrayOf(schema.string())), + api_key: schema.maybe(schema.string()), + admin_username: schema.maybe(schema.string()), + admin_password: schema.maybe(schema.string()), + config: schema.maybe(schema.recordOf(schema.string(), schema.any())), +}; + +export const NewOutputSchema = schema.object({ + ...OutputBaseSchema, +}); + +export const OutputSchema = schema.object({ + ...OutputBaseSchema, + id: schema.string(), +}); + +export type NewOutput = TypeOf; + +export type Output = TypeOf; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts new file mode 100644 index 0000000000000..cb4680a4eed04 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { AgentConfig, NewAgentConfigSchema } from '../models'; +import { ListWithKuerySchema } from './common'; + +export const GetAgentConfigsRequestSchema = { + query: ListWithKuerySchema, +}; + +export interface GetAgentConfigsResponse { + items: AgentConfig[]; + total: number; + page: number; + perPage: number; + success: boolean; +} + +export const GetOneAgentConfigRequestSchema = { + params: schema.object({ + agentConfigId: schema.string(), + }), +}; + +export interface GetOneAgentConfigResponse { + item: AgentConfig; + success: boolean; +} + +export const CreateAgentConfigRequestSchema = { + body: NewAgentConfigSchema, +}; + +export interface CreateAgentConfigResponse { + item: AgentConfig; + success: boolean; +} + +export const UpdateAgentConfigRequestSchema = { + ...GetOneAgentConfigRequestSchema, + body: NewAgentConfigSchema, +}; + +export interface UpdateAgentConfigResponse { + item: AgentConfig; + success: boolean; +} + +export const DeleteAgentConfigsRequestSchema = { + body: schema.object({ + agentConfigIds: schema.arrayOf(schema.string()), + }), +}; + +export type DeleteAgentConfigsResponse = Array<{ + id: string; + success: boolean; +}>; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/common.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/common.ts new file mode 100644 index 0000000000000..2c8134d2e8f92 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/common.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema, TypeOf } from '@kbn/config-schema'; + +export const ListWithKuerySchema = schema.object({ + page: schema.number({ defaultValue: 1 }), + perPage: schema.number({ defaultValue: 20 }), + kuery: schema.maybe(schema.string()), +}); + +export type ListWithKuery = TypeOf; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/datasource.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/datasource.ts new file mode 100644 index 0000000000000..5c165517e9c9d --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/datasource.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { NewDatasourceSchema } from '../models'; +import { ListWithKuerySchema } from './common'; + +export const GetDatasourcesRequestSchema = { + query: ListWithKuerySchema, +}; + +export const GetOneDatasourceRequestSchema = { + params: schema.object({ + datasourceId: schema.string(), + }), +}; + +export const CreateDatasourceRequestSchema = { + body: NewDatasourceSchema, +}; + +export const UpdateDatasourceRequestSchema = { + ...GetOneDatasourceRequestSchema, + body: NewDatasourceSchema, +}; + +export const DeleteDatasourcesRequestSchema = { + body: schema.object({ + datasourceIds: schema.arrayOf(schema.string()), + }), +}; + +export type DeleteDatasourcesResponse = Array<{ + id: string; + success: boolean; +}>; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/fleet_setup.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/fleet_setup.ts new file mode 100644 index 0000000000000..3772e6c24c56e --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/fleet_setup.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; + +export const GetFleetSetupRequestSchema = {}; + +export const CreateFleetSetupRequestSchema = { + body: schema.object({ + admin_username: schema.string(), + admin_password: schema.string(), + }), +}; + +export interface CreateFleetSetupResponse { + isInitialized: boolean; +} diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts new file mode 100644 index 0000000000000..7d0d7e67f2db0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './common'; +export * from './datasource'; +export * from './agent_config'; +export * from './fleet_setup'; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx index b2627e1c58aff..96f1669d8f0e3 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx @@ -180,7 +180,7 @@ export const WatchVisualization = () => { defaultMessage="Cannot load watch visualization" /> } - error={error as Error} + error={(error as unknown) as Error} /> diff --git a/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx index 2d552d7fbdda6..54f4209a137b9 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_list/components/watch_list.tsx @@ -226,7 +226,7 @@ export const WatchList = () => { defaultMessage="Error loading watches" /> } - error={error as Error} + error={(error as unknown) as Error} /> ); } else if (availableWatches) { diff --git a/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_history.tsx b/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_history.tsx index b78a2e4230171..541df89d042cb 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_history.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_status/components/watch_history.tsx @@ -107,7 +107,7 @@ export const WatchHistory = () => { defaultMessage="Error loading execution history" /> } - error={historyError as Error} + error={(historyError as unknown) as Error} /> ); @@ -186,7 +186,7 @@ export const WatchHistory = () => { defaultMessage="Error loading execution details" /> } - error={watchHistoryDetailsError as Error} + error={(watchHistoryDetailsError as unknown) as Error} data-test-subj="errorMessage" />