From 655b80a9ddbeb5979ac5d511e4743b33b1974adb Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Thu, 18 Jun 2020 15:52:43 -0600 Subject: [PATCH] Ui/replication status discoverability (#8705) * Sidebranch: add new route on DR secondary (#8640) * setup, not complete * update routing * clean up * add test * add link from status menu * clean up * fixes per pr comments * revert back to two if statements due to refresh bug * Sidebranch: Setup Replication Page as component to be consumed in all pages for project (#8661) * setup, not complete * update routing * clean up * add test * add link from status menu * clean up * fixes per pr comments * setup dashboard with contextual components, and toggle * setup option to show tabs or not * handle conditional nav menu * pass in whole model object * rename to replication-page * clean up * clean up based on pr feedback * fix linting error * Sidebranch: setup replication dashboard with scss and initial card components (#8670) * setup replication dashboard with scss and initial card components * sync with Noelles changes and clean up the inner grid container inside the selectable card * set up nested contextual components for selectable-cards * setup component for table row * address walk through notes * remove name selectable from card component as it is not selectable * add missing space * Ui/dr primary/initial page setup (#8671) * add helperText param to InfoTableRow * initial page setup * format card with padding and correct number of rows * style card titles with margin * move styles inside replication class; add todos * move replication-summary styles into core app so hot reloading works * prevent known secondaries card from being cut off on the right hand side * make cards have the correct column span * make code elements inside tables black * WIP - start VltTable component * simplify css * renamed VltTable to ReplicationTable and use divs instead of table elements * fix position of known secondaries * use table element for secondaries card * add todo * move replication components to replication engine * Revert "move replication components to replication engine" This reverts commit 2228b8392199a1376815dc8b7642de438aad68b5. * move ReplicationPrimaryCards to components * remove hover box shadow since cards are not selectable yet * only apply padding to replication selectable-cards * specify replication vlt-table in classname * move replication toggle and toggle into core addon * remove extra toolbar border * remove duplicate css * move ReplicationTableRows to core addon and use them on DR primary page * clean up todos * add jsdoc comments * rename ReplicationTable to KnownSecondaries * update replicaiton table api to accept flexible data * rename replicationAttrs to data * move replication components to core addon * Ui/dr primary components (#8711) * populate table with actual secondary ids * add todo * make KnownSecondariesCard component * move KnownSecondariesCard styles to own file * add EmptyState when there are no known secondaries * fix known secondaries manage link * fix Add Secondary link; bring in capabilities model to repliation index route so we can check for adding secondaries * fix JSDOC comments and updata data to replicationAttrs * Sidebranch: DR Secondary Dashboard - pr4 (#8706) * setup styling for delta * clean up * replication table remove and rename model to data * remove old replication header component now that it is in addons * move replication secondary card component * calc delta * clean up * remove unused components that are now in addon * address pr comments * remove test * fix failing test * address pr comments * attempting to fix test * move to computed components * fix test error * fix state of null error * Sidebranch: DR Secondary Dashboard state message handlers (#8741) * setup styling for delta * clean up * replication table remove and rename model to data * remove old replication header component now that it is in addons * move replication secondary card component * calc delta * clean up * remove unused components that are now in addon * address pr comments * remove test * fix failing test * address pr comments * attempting to fix test * initial setup before merge updates * move to computed components * fix test error * fix state of null error * clean up * setup alert banner and documentation link * setup alert banner with second icon * remove underscore to dash * add in missing error messages * add connection-state shutdown * add storybook update to alert banner * pr comments * move css class to helper class * address pr comments * add in connection states correct endpoint * Ui/dr dashboard tests (#8732) * initial test setup * use margin when there are no knownsecondaries * set up replication tests in ember engine * set knownSecondaries * move tests to host app and set resolver * finish known-secondaries-card-test * make knownSecondaries array match the API response * add known secondaries table test * oops, remove stories directory * wip - replication table rows test * remove extra code * finish replication table rows tests * add | Enterprise | to test module * remove unncessary assertion: * show dynamic state glyph (#8747) * show dynamic state glyph * show state glyph after state * move LearnLink into core addon * make ReplicationDocLink component * prevent double lines at the bottom of the dashboard * Sidebranch: dr-secondary-dashboard-pr7 (#8792) * move dr to higher level component and setup isDisabled for error state when dr mode is disabled. * add in error messages final * refactor transistion on submit handler focused on dr secondary * handle transition * update empty state component to include icon and add empty state to details page * fix spelling * address pr comments * merge with replication branch * remove component structure for replication-secondary-card * compute title and error message * make specific empty state messages * fix test * address pr comments * regenerate the storyboook for empty state * Replication Primary Dashboard: handle errors (#8845) * use h3 instead of code elements * use correct property names for StateDisplay * WIP * remove todo * move cluster states into a map; make status menu icon match cluster state * show error in state card using the same state map in the cluster model * whitespace * move cluster-states into a helper and update usage * use circle success icon for stream-wals because that is the ideal state * more refactoring of cluster state display * use new cluster-states helper * whitespace * use clusterStates helper in replication secondary card * remove extra import * add default values for when state isn't recognized * make sure that state exists before getting state details from clusterStates helper * be more strict when state cannot be found * use brace expansion to fix linting error * add tests for error states * fix text wrapping issue on secondary cards; make titles match mocks * use unknown if metric isn't foudn * remove extra border on selectable card when there is an error * use outline square in status menu for error * Ui/replication/refactor dashboard components (#8878) * use ReplicationDashboard component * move syncing and alert banners into dashboard component * only show primary cluster addr if dashboard is for a secondary * use ReplicationPage and Dashboard * move isSecondary to page component * remove duplication * remove dead code * refactored table rows * make sure dashboards update data when we are switching between mclusterModes and replicationTypes * clarified replicationMode and clusterMode * remove extra margin * get rid of data * remove syncProgress * remove Enterprise filter from tests so component tests are run * Ui/replication/primary reindexing (#8906) * fix typo * fetch replication/mode/status and pass to dashboard component * add reindexing stage to AlertBanner; use real value for isReindexing * remove dr since we don't need it anymore * add indentation * remove TODO * capitalize reindexing_stage and make progress 0 by default * remove Toggle since we don't need it anymore * get allllll the variables at once * only run secondary details test on enterprise * Sidebranch: component and acceptance tests (#8903) * address secondary card overflow issue * setup replicaiton header test * address secondary card overflow issue * setup replication secondary card test * setup replicaiton header test * setup replicaiton page test * setup replication secondary card test * setup replication dashboard test * setup replicaiton page test * remove unused code * fix overflow * finish test for rep dashboard * update rep secondary card test * finish rep header test * fix rep table rows and header test * fix header test * fix missing data-test-primary-cluster * add to secondary test * remove pauseTest * add to enterprise replication test * add mode to dr secondary test * remove pauseTest * add enterprise to test * amend per pr commments * re organize rep secondary card test * adjust error heights with design input * move const around in rep secondary card test * move const around and message for rep dashboard test * amend per pr review comments * remove styling from grid-item-left * remove dup hasErrorClass key * quick fix * test failure fix * fix test due to merge * remove hasErrorClass * modify test message * Sidebranch: remove delta, toggle, and make auto-refresh (#8945) * change styling * remove replication toggle * modifications for auto refresh and final removal of delta and last wal * fix refresh issue by removing replicationMode on this.reset which conflicts with the same property being set on the cluster model * remove comments * add unknown placeholder * add auto refresh to other components and remove mention of toggle * remove meep and primary cluster heading area * ensure status menu displays replication state, not just one (#8959) * Add Replication Reindexing Progress Bar (#8975) * whitespace * rename consts * rename variables * test that dashboard shows a reindexing alert banner * standardize shamir and ui wizard progress bar * make new progressbar component * just kidding, we can use the html5 progress bar * make top margins consistent across primary and secondary dashboards * clean up AlertBanner JSDocs and markdown * show a progress bar inside an AlertBanner if cluster is reindexing * add example AlertBanner with Progress Bar * add reindexing tests * add a tiny left margin to progress bars inside alert banners * keep old class names in wizard to prevent bug, but keep consistent progress background color * use spacing variables * remove extra border when secondary card has an error * make card header sizes and weight consistent * Sidebranch: Performance Secondary Dashboard (#8956) * setup rep dashboard to dynamically take in the component to render and dynamically setup the css based on mode of cluster * conditional pass in the correct props to the Dashboard.card component and add margin to reindexing alertBanner * update replication dashboard test * add performance secondary test and clean up replication-secondary-card test * fix message * replace cluster-id with secondaryId * remove reindexing test as its a duplicate of the branch noelle is working on * cleanup * address pr comments * small test fixes * add secondaryId to header test * fix tests description * Ui/replication/test update (#8995) * make sure progress bar updates and animates * ensure dashboard updates when replication mode has changed * make sure we update isSyncing when state has changed * wip - console log statements to see if components are getting new attrs * Revert "wip - console log statements to see if components are getting new attrs" This reverts commit d05219ba6c14c64a9f2e867892476faf7dad4659. * style progress bar in mozilla; allow testing the progress bar in storybook * test that primary and secondary card container don't display at the same time * prepare KnownSecondariesTable for backend compatibility (#9029) * Ui/replication mgmt action block (#9053) This does some low-impact work to prepare for the refactor of replication-actions. Includes: - Move modal to addon in lib/core - Update modal to take a "type" param which changes the header color + icon - Add tests for modal changes - Add action-block style only component - Add styles-only replication-action grid that the action-blocks will live inside of * Sidebranch: address transition issues on replication engine and actions (#9010) * small formatting changes * change findRecord to peekRecord so it keeps track of the changing data. * add styling such that when page is loading it does not spread across the whole page * help with reload and styling on replication route * initial setup for new flow that handles adding a perf secondary, and also some on a dr secondary * clean up * add loader on rep page for situations when data is still loading, and add loading mode in header, seperate from the modeForUrl used in other places to help transistion * fix transitionTo when coming from different replication.mode vs replication.index route * set default of mode for radio checkboxes after removing from DEFAULTS var * reset and cont using onEnable because TransitionTo is not working inside of component * remove console * the reason we were getting transition errors :( * remove modeObjecT * fix error by removing peek record from application and moving it lower down in a property replicationAttrs * Readd back space * this one really does fix the issue * add back peek record and add conditional to isLoadingData * figure out cluster id from service instead of hardcoded * fix capabilities-self error by adding a 1 sceond delay for when transition from replication.index to replication.mode.index on enable performance secondary * remove attempt to circumvent the peekRecord in application * add to replication page tests and clarify replicationMode to formattedReplicationMode, it's super confusing when seeing replicationMode being duplicated throughout the computed components. this clarifies its computed only for formatting * fix repetive conditional * capture the state when either dr.mode or performance.mode are undefined, which happens during a transition. If this is the case add a loader on the replicationindex page. * address some pr comments * small change * add bootstrapping mode to test * add Replication Learn Links to wizard (#9106) * Ui/summary dashboard (#9079) * move key value to lib/core/addon so I can use inside replication engine * setup summary dasbhoard on replication summary component * set title for summary dashboard * do not show replication table rows on summary dashboard * show that last_wal updates every 10 seconds * show replication table rows on individual dashboards, but not summary * remove extra bottom border on replication-dashboard * add replicationDetailsSummary object and replication-summary-card * setup structure and data calcs of replication summary card * fix links and styling on summary card * breadcrumbs * match state title on summary dashboard to individual dashboards * add margin below replication header * update breadcrumbs to show replication mode * align details link right * add margin below tabs in replication header * user helper-text to make card text styling consistent across dashboards * remove unneeded code * add bottom border to summary state * add bottom margin to summary dashboard * add negative margins to bring values closer to related cell * fix failing test due to data-test attribute change and make storybook component for replication-summary-card * setup replication summary card test. I suspect we'll move the hasError test to the dashboard where the error will show around the state display * add to replication acceptance test for new summary dashboard * remove pauseTest * add is-active to li element * clean up * dashboard test and clean up * addressing pr comments * fix replication/null/status error * add JSDocs for rep page and rep dash * more pr cleanup * remove conditional and fix styling blue link * fix conditional on when loading summary dashboard to check for primary on both. wrap code in div so it lands on another line. Co-authored-by: Noelle Daley * change message with bold 'not' if primary (#9112) * Add JSDocs to components (#9125) * jsdocs * remove todo that is no longer relevant * clean up wording * wordsmithing * fix spelling * example for clusterMode * Replication Management Sidebranch: Replication Action Disable (#9061) Set up dr-secondary management page with new action flow * Create confirmation-modal component * Refactor replication-dr-secondary splash page to replication manage page * Refactor replication-action-disable component to use confirmation modal * Add details/manage tab to replication-dr-secondary section * Refactor Replication Action: Promote to use modal flow (#9122) * Ui/replication mgmt/reindex action (#9126) * Replication Management Sidebranch: Replication Action Disable (#9061) * Ui/replication mgmt/recover action (#9127) * Replication Management Sidebranch: Replication Action Recover (#9061) * Close link-to tag in header (#9139) Fixes bad merge conflict * UI: Fix replication management tests (#9136) * do not show replication mode or id when replication isn't enabled * fix broken tag * fill in confirmation text when disabling replication in tests * fix typo * fix demote primary test selector * add test selectors and update tests to match new format * fill in Performance when disabling performance secondary * Ui/replication mgmt/update primary action (#9149) * Update Primary replication action uses modal flow * Update modal max-height to accommodate for the navbar * Ui/secondary token flow dr (#9150) * setup token modal flow * calc expirationDate * fix date-format test after moving it in addon * fix icon conditional in modal title * decode token to get epoch expiration date and convert * handle clicking outside of modal * remove extra copy button * add modal check in rep acceptance test * look only at day and month and remove console * fix spelling * cleanup * replace dr with variable * make string check longer in test * fix test variables * refactor enterprise test for secondary token flow * make cluster model property replicationModeForDisplay to handle all cases where we were either conditionally displaying the DR, Disaster Recovery, etc. or where we were hardcoding it into the hbs. For situations where it was DR before, I am now keeping it more consistent and using Disaster Recovery as on the manage page we do not show the Diaster Recovery (DR) anywhere. * set initial value for ttl picker to fix issue where itwas setting seconds to minutes * clean up * add comment about ttl picker * Add known primaries info table (#9152) * replace primaryClusterAddr with knownPrimaryClusterAddrs * rename state to Status; fix css layout * add InfoTable component * only show label column if there is a label * add grid-item-middle class * whitespace * fix grid layout * die tagName, die * set table max-height * prep InfoTable for Storybook * ensure cards always have the same height * remove duplicate max height since vlt-table already has max-height * add InfoTable tests * add InfoTable to Storybook * organize grid item css; rename for consistency * add sticky header to table * add sticky-header class to keep table styles in scope * whoops, do not use fake data * Ui/rep design updates (#9169) * show secondaryId in table rows * show primary_cluster_addr in table rows * remove cluster Ids from replication headers * Ui/fix enable overflow (#9173) * only show primary_cluster_addr for primary * fix overflow on replication index * remove display from cluster-states because it is not used anywhere * fix missing replication mode from description * add comments * use helper to consolidate replication descriptions * fix text wrapping on medium screen sizes * Ui/replication mgmt/demote action (#9168) * Replication demote action uses modal flow Co-authored-by: Noelle Daley Co-authored-by: Angel Garbarino * Ui/replication merge cleanup 2 (#9212) * replace with replicationModeForDisplay that is defined on the cluster * fix spelling on replication and confirmed with design for placeholder when Not defined * remove extra div with box class * change manage link to take you to the secondaries manage as it's within the known secondaries card * fix scroll always showing by adding auto, and decreasing the height. WIP * add empty state to known_primary_cluster_addrs * address pr comments * Add real connected state and API address (#9219) * fix title of secondary card * show connected status * fix tests * fix enterprise test (#9229) * fix enterprise test * add n * add another n * Ui/replication mgmt/generate token action (#9187) Generate operation token flow from replication DR Secondary. Clicking 'Cancel' on the modal after the operation has started results in cancelling generate operation and restarting the process. * use none set instead of not defined Co-authored-by: Noelle Daley Co-authored-by: Chelsea Shaw Co-authored-by: Noelle Daley --- ui/README.md | 8 +- ui/app/adapters/cluster.js | 5 +- ui/app/adapters/replication-mode.js | 14 + ui/app/app.js | 3 + ui/app/components/shamir-modal-flow.js | 39 +++ .../index.js} | 5 + ui/app/mixins/cluster-route.js | 8 + ui/app/models/cluster.js | 51 +--- ui/app/models/replication-attributes.js | 11 +- ui/app/models/replication-mode.js | 38 +++ ui/app/router.js | 4 +- .../details.js} | 2 +- .../cluster/replication-dr-promote/index.js | 10 + ui/app/serializers/replication-mode.js | 12 + ui/app/styles/components/action-block.scss | 67 +++++ ui/app/styles/components/empty-state.scss | 7 +- ui/app/styles/components/info-table-row.scss | 6 + ui/app/styles/components/info-table.scss | 9 + .../components/known-secondaries-card.scss | 18 ++ ui/app/styles/components/modal.scss | 34 +++ .../components/replication-dashboard.scss | 131 +++++++++ .../components/replication-doc-link.scss | 8 + .../styles/components/replication-header.scss | 5 + .../components/replication-mode-summary.scss | 11 + .../styles/components/replication-page.scss | 10 + .../components/replication-primary-card.scss | 15 ++ .../components/replication-summary.scss | 9 + ui/app/styles/components/selectable-card.scss | 9 + ui/app/styles/components/ui-wizard.scss | 2 +- ui/app/styles/components/vlt-table.scss | 14 + ui/app/styles/core.scss | 11 + ui/app/styles/core/helpers.scss | 30 ++- ui/app/styles/core/message.scss | 4 + ui/app/styles/core/progress.scss | 32 ++- ui/app/styles/utils/_bulma_variables.scss | 2 +- ui/app/templates/components/shamir-flow.hbs | 2 +- .../components/shamir-modal-flow.hbs | 194 +++++++++++++ .../components/transit-key-action/datakey.hbs | 2 +- .../components/wizard/replication-details.hbs | 19 -- .../components/wizard/replication-setup.hbs | 10 + ui/app/templates/partials/status/cluster.hbs | 24 ++ .../templates/vault/cluster/oidc-callback.hbs | 2 +- .../vault/cluster/replication-dr-promote.hbs | 62 ----- .../replication-dr-promote/details.hbs | 45 ++++ .../cluster/replication-dr-promote/index.hbs | 41 +++ ui/lib/core/addon/components/alert-banner.js | 14 +- .../addon/components/confirmation-modal.js | 37 +++ ui/lib/core/addon/components/empty-state.js | 3 +- .../core/addon/components/info-table-row.js | 4 +- ui/lib/core/addon/components/info-table.js | 27 ++ .../addon}/components/key-value-header.js | 2 + .../core/addon}/components/learn-link.js | 0 .../core/addon}/components/modal.js | 20 ++ .../replication-action-generate-token.js | 6 + .../components/replication-actions-single.js | 2 +- .../addon/components/replication-actions.js | 15 +- .../addon/components/replication-dashboard.js | 102 +++++++ .../addon/components/replication-doc-link.js | 17 ++ .../addon/components/replication-header.js | 31 +++ .../components/replication-mode-summary.js | 7 +- .../core/addon/components/replication-page.js | 142 ++++++++++ .../components/replication-secondary-card.js | 58 ++++ .../components/replication-summary-card.js | 44 +++ .../components/replication-table-rows.js | 37 +++ ui/lib/core/addon/helpers/cluster-states.js | 64 +++++ .../core/addon}/helpers/date-format.js | 0 .../core/addon}/helpers/format-number.js | 0 ui/lib/core/addon/helpers/message-types.js | 6 + .../helpers/replication-action-for-mode.js | 3 +- .../helpers/replication-mode-description.js | 14 + .../core/addon/mixins/replication-actions.js | 27 +- .../templates/components/alert-banner.hbs | 21 +- .../templates/components/alert-inline.hbs | 2 +- .../components/confirmation-modal.hbs | 44 +++ .../templates/components/empty-state.hbs | 9 + .../templates/components/info-table-row.hbs | 13 +- .../addon/templates/components/info-table.hbs | 23 ++ .../templates/components/key-value-header.hbs | 0 .../templates/components/layout-loading.hbs | 2 +- .../addon}/templates/components/modal.hbs | 15 +- .../components/replication-action-demote.hbs | 93 ++++--- .../components/replication-action-disable.hbs | 112 ++++---- .../replication-action-generate-token.hbs | 34 +++ .../components/replication-action-promote.hbs | 205 ++++++-------- .../components/replication-action-recover.hbs | 64 +++-- .../components/replication-action-reindex.hbs | 69 +++-- .../replication-action-update-primary.hbs | 239 +++++++--------- .../components/replication-actions.hbs | 24 +- .../components/replication-dashboard.hbs | 63 +++++ .../components/replication-doc-link.hbs | 6 + .../components/replication-header.hbs | 84 ++++++ .../components/replication-mode-summary.hbs | 89 +++--- .../templates/components/replication-page.hbs | 29 ++ .../components/replication-secondary-card.hbs | 110 ++++++++ .../components/replication-summary-card.hbs | 48 ++++ .../components/replication-table-rows.hbs | 26 ++ .../core/app/components/confirmation-modal.js | 1 + ui/lib/core/app/components/info-table.js | 1 + .../core/app/components/key-value-header.js | 1 + ui/lib/core/app/components/learn-link.js | 1 + ui/lib/core/app/components/modal.js | 1 + .../replication-action-generate-token.js | 1 + .../app/components/replication-dashboard.js | 1 + .../app/components/replication-doc-link.js | 1 + .../core/app/components/replication-header.js | 1 + .../core/app/components/replication-page.js | 1 + .../components/replication-secondary-card.js | 1 + .../components/replication-summary-card.js | 1 + .../app/components/replication-table-rows.js | 1 + ui/lib/core/app/helpers/cluster-states.js | 1 + ui/lib/core/app/helpers/date-format.js | 1 + ui/lib/core/app/helpers/format-number.js | 1 + .../helpers/replication-mode-description.js | 5 + ui/lib/core/package.json | 2 + ui/lib/core/stories/alert-banner.md | 2 + ui/lib/core/stories/alert-banner.stories.js | 20 ++ ui/lib/core/stories/empty-state.md | 6 +- ui/lib/core/stories/info-table-row.md | 6 +- ui/lib/core/stories/info-table-row.stories.js | 6 +- ui/lib/core/stories/info-table.md | 30 +++ ui/lib/core/stories/info-table.stories.js | 27 ++ .../components/known-secondaries-card.js | 19 ++ .../components/known-secondaries-table.js | 18 ++ .../components/replication-primary-card.js | 41 +++ .../addon/components/replication-summary.js | 13 +- .../addon/controllers/application.js | 30 +++ .../addon/controllers/replication-mode.js | 17 +- ui/lib/replication/addon/engine.js | 1 + .../replication/addon/routes/application.js | 4 +- ui/lib/replication/addon/routes/mode/index.js | 32 ++- .../components/known-secondaries-card.hbs | 24 ++ .../components/known-secondaries-table.hbs | 50 ++++ .../components/replication-primary-card.hbs | 21 ++ .../components/replication-summary.hbs | 254 +++++++++--------- ui/lib/replication/addon/templates/index.hbs | 46 ++-- ui/lib/replication/addon/templates/mode.hbs | 107 ++++---- .../addon/templates/mode/secondaries/add.hbs | 135 +++++----- .../acceptance/enterprise-replication-test.js | 163 ++++++++++- .../components/confirmation-modal-test.js | 35 +++ .../integration/components/info-table-test.js | 36 +++ .../components/known-secondaries-card-test.js | 68 +++++ .../known-secondaries-table-test.js | 60 +++++ ui/tests/integration/components/modal-test.js | 13 + .../replication-action-generate-token-test.js | 21 ++ .../components/replication-actions-test.js | 98 +++++-- .../components/replication-dashboard-test.js | 198 ++++++++++++++ .../components/replication-header-test.js | 64 +++++ .../components/replication-page-test.js | 38 +++ .../replication-primary-card-test.js | 55 ++++ .../replication-secondary-card-test.js | 92 +++++++ .../replication-summary-card-test.js | 55 ++++ .../components/replication-table-rows-test.js | 57 ++++ .../components/shamir-modal-flow-test.js | 100 +++++++ .../integration/helpers/date-format-test.js | 36 ++- 154 files changed, 4314 insertions(+), 958 deletions(-) create mode 100644 ui/app/adapters/replication-mode.js create mode 100644 ui/app/components/shamir-modal-flow.js rename ui/app/controllers/vault/cluster/{replication-dr-promote.js => replication-dr-promote/index.js} (51%) create mode 100644 ui/app/models/replication-mode.js rename ui/app/routes/vault/cluster/{replication-dr-promote.js => replication-dr-promote/details.js} (83%) create mode 100644 ui/app/routes/vault/cluster/replication-dr-promote/index.js create mode 100644 ui/app/serializers/replication-mode.js create mode 100644 ui/app/styles/components/action-block.scss create mode 100644 ui/app/styles/components/info-table.scss create mode 100644 ui/app/styles/components/known-secondaries-card.scss create mode 100644 ui/app/styles/components/replication-dashboard.scss create mode 100644 ui/app/styles/components/replication-doc-link.scss create mode 100644 ui/app/styles/components/replication-header.scss create mode 100644 ui/app/styles/components/replication-mode-summary.scss create mode 100644 ui/app/styles/components/replication-page.scss create mode 100644 ui/app/styles/components/replication-primary-card.scss create mode 100644 ui/app/styles/components/replication-summary.scss create mode 100644 ui/app/templates/components/shamir-modal-flow.hbs delete mode 100644 ui/app/templates/components/wizard/replication-details.hbs delete mode 100644 ui/app/templates/vault/cluster/replication-dr-promote.hbs create mode 100644 ui/app/templates/vault/cluster/replication-dr-promote/details.hbs create mode 100644 ui/app/templates/vault/cluster/replication-dr-promote/index.hbs create mode 100644 ui/lib/core/addon/components/confirmation-modal.js create mode 100644 ui/lib/core/addon/components/info-table.js rename ui/{app => lib/core/addon}/components/key-value-header.js (96%) rename ui/{app => lib/core/addon}/components/learn-link.js (100%) rename ui/{app => lib/core/addon}/components/modal.js (54%) create mode 100644 ui/lib/core/addon/components/replication-action-generate-token.js create mode 100644 ui/lib/core/addon/components/replication-dashboard.js create mode 100644 ui/lib/core/addon/components/replication-doc-link.js create mode 100644 ui/lib/core/addon/components/replication-header.js create mode 100644 ui/lib/core/addon/components/replication-page.js create mode 100644 ui/lib/core/addon/components/replication-secondary-card.js create mode 100644 ui/lib/core/addon/components/replication-summary-card.js create mode 100644 ui/lib/core/addon/components/replication-table-rows.js create mode 100644 ui/lib/core/addon/helpers/cluster-states.js rename ui/{app => lib/core/addon}/helpers/date-format.js (100%) rename ui/{app => lib/core/addon}/helpers/format-number.js (100%) create mode 100644 ui/lib/core/addon/helpers/replication-mode-description.js create mode 100644 ui/lib/core/addon/templates/components/confirmation-modal.hbs create mode 100644 ui/lib/core/addon/templates/components/info-table.hbs rename ui/{app => lib/core/addon}/templates/components/key-value-header.hbs (100%) rename ui/{app => lib/core/addon}/templates/components/modal.hbs (54%) create mode 100644 ui/lib/core/addon/templates/components/replication-action-generate-token.hbs create mode 100644 ui/lib/core/addon/templates/components/replication-dashboard.hbs create mode 100644 ui/lib/core/addon/templates/components/replication-doc-link.hbs create mode 100644 ui/lib/core/addon/templates/components/replication-header.hbs create mode 100644 ui/lib/core/addon/templates/components/replication-page.hbs create mode 100644 ui/lib/core/addon/templates/components/replication-secondary-card.hbs create mode 100644 ui/lib/core/addon/templates/components/replication-summary-card.hbs create mode 100644 ui/lib/core/addon/templates/components/replication-table-rows.hbs create mode 100644 ui/lib/core/app/components/confirmation-modal.js create mode 100644 ui/lib/core/app/components/info-table.js create mode 100644 ui/lib/core/app/components/key-value-header.js create mode 100644 ui/lib/core/app/components/learn-link.js create mode 100644 ui/lib/core/app/components/modal.js create mode 100644 ui/lib/core/app/components/replication-action-generate-token.js create mode 100644 ui/lib/core/app/components/replication-dashboard.js create mode 100644 ui/lib/core/app/components/replication-doc-link.js create mode 100644 ui/lib/core/app/components/replication-header.js create mode 100644 ui/lib/core/app/components/replication-page.js create mode 100644 ui/lib/core/app/components/replication-secondary-card.js create mode 100644 ui/lib/core/app/components/replication-summary-card.js create mode 100644 ui/lib/core/app/components/replication-table-rows.js create mode 100644 ui/lib/core/app/helpers/cluster-states.js create mode 100644 ui/lib/core/app/helpers/date-format.js create mode 100644 ui/lib/core/app/helpers/format-number.js create mode 100644 ui/lib/core/app/helpers/replication-mode-description.js create mode 100644 ui/lib/core/stories/info-table.md create mode 100644 ui/lib/core/stories/info-table.stories.js create mode 100644 ui/lib/replication/addon/components/known-secondaries-card.js create mode 100644 ui/lib/replication/addon/components/known-secondaries-table.js create mode 100644 ui/lib/replication/addon/components/replication-primary-card.js create mode 100644 ui/lib/replication/addon/templates/components/known-secondaries-card.hbs create mode 100644 ui/lib/replication/addon/templates/components/known-secondaries-table.hbs create mode 100644 ui/lib/replication/addon/templates/components/replication-primary-card.hbs create mode 100644 ui/tests/integration/components/confirmation-modal-test.js create mode 100644 ui/tests/integration/components/info-table-test.js create mode 100644 ui/tests/integration/components/known-secondaries-card-test.js create mode 100644 ui/tests/integration/components/known-secondaries-table-test.js create mode 100644 ui/tests/integration/components/replication-action-generate-token-test.js create mode 100644 ui/tests/integration/components/replication-dashboard-test.js create mode 100644 ui/tests/integration/components/replication-header-test.js create mode 100644 ui/tests/integration/components/replication-page-test.js create mode 100644 ui/tests/integration/components/replication-primary-card-test.js create mode 100644 ui/tests/integration/components/replication-secondary-card-test.js create mode 100644 ui/tests/integration/components/replication-summary-card-test.js create mode 100644 ui/tests/integration/components/replication-table-rows-test.js create mode 100644 ui/tests/integration/components/shamir-modal-flow-test.js diff --git a/ui/README.md b/ui/README.md index b5fb1a511008..338c34618b48 100644 --- a/ui/README.md +++ b/ui/README.md @@ -17,6 +17,7 @@ - [Writing Stories](#writing-stories) - [Adding a new story](#adding-a-new-story) - [Code Generators](#code-generators-1) + - [Storybook Deployment](#storybook-deployment) - [Further Reading / Useful Links](#further-reading--useful-links) @@ -67,6 +68,11 @@ long-form version of the npm script: Make use of the many generators for code, try `ember help generate` for more details. If you're using a component that can be widely-used, consider making it an `addon` component instead (see [this PR](https://github.com/hashicorp/vault/pull/6629) for more details) +eg. a reusable component named foo that you'd like in the core engine + +- `ember g component foo --in lib/core` +- `echo "export { default } from 'core/components/foo';" > lib/core/app/components/foo.js` + ### Running Tests Running tests will spin up a Vault dev server on port 9200 via a @@ -158,7 +164,7 @@ Note that placing a param inside brackets (e.g. `[closedLabel=More options]` ind 2. Generate a new story with `ember generate story [name-of-component]` 3. Inside the newly generated `stories` file, add at least one example of the component. If the component should be interactive, enable the [Storybook Knobs addon](https://github.com/storybooks/storybook/tree/master/addons/knobs). -4. Generate the `notes` file for the component with `yarn gen-story-md [name-of-component]` (e.g. `yarn gen-md alert-banner`). This will generate markdown documentation of the component and place it at `vault/ui/stories/[name-of-component].md`. If your component is a template-only component, you will need to manually create the markdown file. +4. Generate the `notes` file for the component with `yarn gen-story-md [name-of-component] [name-of-engine-or-addon]` (e.g. `yarn gen-md alert-banner core`). This will generate markdown documentation of the component and place it at `vault/ui/stories/[name-of-component].md`. If your component is a template-only component, you will need to manually create the markdown file. See the [Storybook Docs](https://storybook.js.org/docs/basics/introduction/) for more information on writing stories. diff --git a/ui/app/adapters/cluster.js b/ui/app/adapters/cluster.js index 110060a6eab1..dd7d17220a8a 100644 --- a/ui/app/adapters/cluster.js +++ b/ui/app/adapters/cluster.js @@ -183,7 +183,10 @@ export default ApplicationAdapter.extend({ }, generateDrOperationToken(data, options) { - const verb = options && options.checkStatus ? 'GET' : 'PUT'; + let verb = options && options.checkStatus ? 'GET' : 'PUT'; + if (options.cancel) { + verb = 'DELETE'; + } let url = `${this.buildURL()}/replication/dr/secondary/generate-operation-token/`; if (!data || data.pgp_key || data.attempt) { // start the generation diff --git a/ui/app/adapters/replication-mode.js b/ui/app/adapters/replication-mode.js new file mode 100644 index 000000000000..baa09570f821 --- /dev/null +++ b/ui/app/adapters/replication-mode.js @@ -0,0 +1,14 @@ +import ApplicationAdapter from './application'; + +export default ApplicationAdapter.extend({ + getStatusUrl(mode) { + return this.buildURL() + `/replication/${mode}/status`; + }, + + fetchStatus(mode) { + let url = this.getStatusUrl(mode); + return this.ajax(url, 'GET', { unauthenticated: true }).then(resp => { + return resp.data; + }); + }, +}); diff --git a/ui/app/app.js b/ui/app/app.js index 067a6f8c8744..509014ac6830 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -31,6 +31,9 @@ App = Application.extend({ 'version', 'wizard', ], + externalRoutes: { + replication: 'vault.cluster.replication.index', + }, }, }, kmip: { diff --git a/ui/app/components/shamir-modal-flow.js b/ui/app/components/shamir-modal-flow.js new file mode 100644 index 000000000000..2a06e6f18561 --- /dev/null +++ b/ui/app/components/shamir-modal-flow.js @@ -0,0 +1,39 @@ +/** + * @module ShamirModalFlow + * ShamirModalFlow is an extension of the ShamirFlow component that does the Generate Action Token workflow inside of a Modal. + * Please note, this is not an extensive list of the required parameters -- please see ShamirFlow for others + * + * @example + * ```js + * This copy is the main paragraph when the token flow has not started + * ``` + * @param {function} onClose - This function will be triggered when the modal intends to be closed + */ +import { inject as service } from '@ember/service'; +import ShamirFlow from './shamir-flow'; +import layout from '../templates/components/shamir-modal-flow'; + +export default ShamirFlow.extend({ + layout, + store: service(), + onClose: () => {}, + actions: { + onCancelClose() { + if (this.encoded_token) { + this.send('reset'); + } else if (this.generateAction && !this.started) { + if (this.generateStep !== 'chooseMethod') { + this.send('reset'); + } + } else { + const adapter = this.get('store').adapterFor('cluster'); + adapter.generateDrOperationToken(this.model, { cancel: true }); + this.send('reset'); + } + this.onClose(); + }, + onClose() { + this.onClose(); + }, + }, +}); diff --git a/ui/app/controllers/vault/cluster/replication-dr-promote.js b/ui/app/controllers/vault/cluster/replication-dr-promote/index.js similarity index 51% rename from ui/app/controllers/vault/cluster/replication-dr-promote.js rename to ui/app/controllers/vault/cluster/replication-dr-promote/index.js index 35ca35f0e782..0adb652d4f54 100644 --- a/ui/app/controllers/vault/cluster/replication-dr-promote.js +++ b/ui/app/controllers/vault/cluster/replication-dr-promote/index.js @@ -3,4 +3,9 @@ import Controller from '@ember/controller'; export default Controller.extend({ queryParams: ['action'], action: '', + actions: { + onPromote() { + this.transitionToRoute('vault.cluster.replication.mode.index', 'dr'); + }, + }, }); diff --git a/ui/app/mixins/cluster-route.js b/ui/app/mixins/cluster-route.js index b11940db7142..b16695627493 100644 --- a/ui/app/mixins/cluster-route.js +++ b/ui/app/mixins/cluster-route.js @@ -9,6 +9,7 @@ const CLUSTER = 'vault.cluster'; const CLUSTER_INDEX = 'vault.cluster.index'; const OIDC_CALLBACK = 'vault.cluster.oidc-callback'; const DR_REPLICATION_SECONDARY = 'vault.cluster.replication-dr-promote'; +const DR_REPLICATION_SECONDARY_DETAILS = 'vault.cluster.replication-dr-promote.details'; const EXCLUDED_REDIRECT_URLS = ['/vault/logout']; export { INIT, UNSEAL, AUTH, CLUSTER, CLUSTER_INDEX, DR_REPLICATION_SECONDARY }; @@ -70,6 +71,13 @@ export default Mixin.create({ return UNSEAL; } if (get(cluster, 'dr.isSecondary')) { + if (transition && transition.targetName === DR_REPLICATION_SECONDARY_DETAILS) { + return DR_REPLICATION_SECONDARY_DETAILS; + } + if (this.router.currentRouteName === DR_REPLICATION_SECONDARY_DETAILS) { + return DR_REPLICATION_SECONDARY_DETAILS; + } + return DR_REPLICATION_SECONDARY; } if (!isAuthed) { diff --git a/ui/app/models/cluster.js b/ui/app/models/cluster.js index f7542c1ce2e9..dea9ae2317dd 100644 --- a/ui/app/models/cluster.js +++ b/ui/app/models/cluster.js @@ -46,57 +46,22 @@ export default DS.Model.extend({ //otherwise the particular mode will have the relevant mode attr through replication-attributes mode: attr('string'), allReplicationDisabled: and('{dr,performance}.replicationDisabled'), - anyReplicationEnabled: or('{dr,performance}.replicationEnabled'), - stateDisplay(state) { - if (!state) { - return null; - } - const defaultDisp = 'Synced'; - const displays = { - 'stream-wals': 'Streaming', - 'merkle-diff': 'Determining sync status', - 'merkle-sync': 'Syncing', - }; - - return displays[state] || defaultDisp; - }, - - drStateDisplay: computed('dr.state', function() { - return this.stateDisplay(this.get('dr.state')); - }), - - performanceStateDisplay: computed('performance.state', function() { - return this.stateDisplay(this.get('performance.state')); - }), - - stateGlyph(state) { - const glyph = 'check-circle-outline'; - - const glyphs = { - 'stream-wals': 'android-sync', - 'merkle-diff': 'android-sync', - 'merkle-sync': null, - }; - - return glyphs[state] || glyph; - }, - - drStateGlyph: computed('dr.state', function() { - return this.stateGlyph(this.get('dr.state')); - }), - - performanceStateGlyph: computed('performance.state', function() { - return this.stateGlyph(this.get('performance.state')); - }), - dr: fragment('replication-attributes'), performance: fragment('replication-attributes'), // this service exposes what mode the UI is currently viewing // replicationAttrs will then return the relevant `replication-attributes` fragment rm: service('replication-mode'), replicationMode: alias('rm.mode'), + replicationModeForDisplay: computed('replicationMode', function() { + return this.replicationMode === 'dr' ? 'Disaster Recovery' : 'Performance'; + }), + replicationIsInitializing: computed('dr.mode', 'performance.mode', function() { + // a mode of null only happens when a cluster is being initialized + // otherwise the mode will be 'disabled', 'primary', 'secondary' + return !this.dr.mode || !this.performance.mode; + }), replicationAttrs: computed('dr.mode', 'performance.mode', 'replicationMode', function() { const replicationMode = this.get('replicationMode'); return replicationMode ? get(this, replicationMode) : null; diff --git a/ui/app/models/replication-attributes.js b/ui/app/models/replication-attributes.js index 24c2f464ea6f..55bba8104b78 100644 --- a/ui/app/models/replication-attributes.js +++ b/ui/app/models/replication-attributes.js @@ -18,16 +18,25 @@ export default Fragment.extend({ isPrimary: match('mode', /primary/), knownSecondaries: attr('array'), + secondaries: attr('array'), // secondary attrs isSecondary: match('mode', /secondary/), - + connection_state: attr('string'), modeForUrl: computed('mode', function() { const mode = this.get('mode'); return mode === 'bootstrapping' ? 'bootstrapping' : (this.get('isSecondary') && 'secondary') || (this.get('isPrimary') && 'primary'); }), + modeForHeader: computed('mode', function() { + const mode = this.mode; + if (!mode) { + // mode will be false or undefined if it calls the status endpoint while still setting up the cluster + return 'loading'; + } + return mode; + }), secondaryId: attr('string'), primaryClusterAddr: attr('string'), knownPrimaryClusterAddrs: attr('array'), diff --git a/ui/app/models/replication-mode.js b/ui/app/models/replication-mode.js new file mode 100644 index 000000000000..df79d46ac94f --- /dev/null +++ b/ui/app/models/replication-mode.js @@ -0,0 +1,38 @@ +import DS from 'ember-data'; +const { attr } = DS; + +/* sample response + +{ + "request_id": "d81bba81-e8a1-0ee9-240e-a77d36e3e08f", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "cluster_id": "ab7d4191-d1a3-b4d6-6297-5a41af6154ae", + "known_secondaries": [ + "test" + ], + "last_performance_wal": 72, + "last_reindex_epoch": "1588281113", + "last_wal": 73, + "merkle_root": "c8d258d376f01d98156f74e8d8f82ea2aca8dc4a", + "mode": "primary", + "primary_cluster_addr": "", + "reindex_building_progress": 26838, + "reindex_building_total": 305443, + "reindex_in_progress": true, + "reindex_stage": "building", + "state": "running" + }, + "wrap_info": null, + "warnings": null, + "auth": null +} + + +*/ + +export default DS.Model.extend({ + status: attr('object'), +}); diff --git a/ui/app/router.js b/ui/app/router.js index 2682227e0265..916dacbb8829 100644 --- a/ui/app/router.js +++ b/ui/app/router.js @@ -127,7 +127,9 @@ Router.map(function() { this.route('show', { path: '/:policy_name' }); this.route('edit', { path: '/:policy_name/edit' }); }); - this.route('replication-dr-promote'); + this.route('replication-dr-promote', function() { + this.route('details'); + }); if (config.addRootMounts) { config.addRootMounts.call(this); } diff --git a/ui/app/routes/vault/cluster/replication-dr-promote.js b/ui/app/routes/vault/cluster/replication-dr-promote/details.js similarity index 83% rename from ui/app/routes/vault/cluster/replication-dr-promote.js rename to ui/app/routes/vault/cluster/replication-dr-promote/details.js index 84de8c5881be..3d716ad990db 100644 --- a/ui/app/routes/vault/cluster/replication-dr-promote.js +++ b/ui/app/routes/vault/cluster/replication-dr-promote/details.js @@ -1,5 +1,5 @@ import { inject as service } from '@ember/service'; -import Base from './cluster-route-base'; +import Base from '../cluster-route-base'; export default Base.extend({ replicationMode: service(), diff --git a/ui/app/routes/vault/cluster/replication-dr-promote/index.js b/ui/app/routes/vault/cluster/replication-dr-promote/index.js new file mode 100644 index 000000000000..3d716ad990db --- /dev/null +++ b/ui/app/routes/vault/cluster/replication-dr-promote/index.js @@ -0,0 +1,10 @@ +import { inject as service } from '@ember/service'; +import Base from '../cluster-route-base'; + +export default Base.extend({ + replicationMode: service(), + beforeModel() { + this._super(...arguments); + this.get('replicationMode').setMode('dr'); + }, +}); diff --git a/ui/app/serializers/replication-mode.js b/ui/app/serializers/replication-mode.js new file mode 100644 index 000000000000..2e6e9210bf9f --- /dev/null +++ b/ui/app/serializers/replication-mode.js @@ -0,0 +1,12 @@ +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + normalizeResponse(store, primaryModelClass, payload, id, requestType) { + const normalizedPayload = { + id: payload.id, + status: payload.data, + }; + + return this._super(store, primaryModelClass, normalizedPayload, id, requestType); + }, +}); diff --git a/ui/app/styles/components/action-block.scss b/ui/app/styles/components/action-block.scss new file mode 100644 index 000000000000..137b4fa540c9 --- /dev/null +++ b/ui/app/styles/components/action-block.scss @@ -0,0 +1,67 @@ +@mixin stacked-grid { + grid-template-columns: 1fr; + grid-row: 1/1; +} +@mixin stacked-content { + margin-bottom: $spacing-l; +} + +.action-block { + @extend .selectable-card; + grid-template-columns: 2fr 1fr; + display: grid; + padding: $spacing-m $spacing-l; + line-height: inherit; + grid-gap: $spacing-m; + + @include until($mobile) { + @include stacked-grid(); + } +} + +.action-block-info { + @include until($mobile) { + @include stacked-content(); + } +} + +.action-block.stacked { + @include stacked-grid(); +} +.stacked > .action-block-info { + @include stacked-content(); +} + +.action-block-title { + font-size: $size-5; + font-weight: $font-weight-bold; +} +.action-block-action { + text-align: right; + @include until($mobile) { + text-align: left; + } +} + +/* Action Block Grid */ +.replication-actions-grid-layout { + display: flex; + flex-wrap: wrap; + margin: $spacing-m 0; + @include until($tablet) { + display: block; + } +} + +.replication-actions-grid-item { + flex-basis: 50%; + padding: $spacing-s; +} + +.replication-actions-grid-item .action-block { + height: 100%; + width: 100%; + @include until($tablet) { + height: inherit; + } +} diff --git a/ui/app/styles/components/empty-state.scss b/ui/app/styles/components/empty-state.scss index b023403cfadb..fb4d4b6e6c49 100644 --- a/ui/app/styles/components/empty-state.scss +++ b/ui/app/styles/components/empty-state.scss @@ -1,8 +1,8 @@ .empty-state { align-items: center; color: $grey; - display: flex; background: $ui-gray-010; + display: flex; justify-content: center; padding: $spacing-xxl $spacing-s; box-shadow: 0 -2px 0 -1px $ui-gray-300; @@ -36,3 +36,8 @@ margin-left: $spacing-s; } } + +.empty-state-icon > .hs-icon { + float: left; + margin-right: $spacing-xs; +} diff --git a/ui/app/styles/components/info-table-row.scss b/ui/app/styles/components/info-table-row.scss index bca18fd24b7c..d3a0193c6369 100644 --- a/ui/app/styles/components/info-table-row.scss +++ b/ui/app/styles/components/info-table-row.scss @@ -17,6 +17,8 @@ } .column { + align-self: center; + &.info-table-row-edit { padding-bottom: 0.3rem; padding-top: 0.3rem; @@ -25,6 +27,10 @@ textarea { min-height: 35px; } + + .helper-text { + font-weight: normal; + } } .hs-icon { diff --git a/ui/app/styles/components/info-table.scss b/ui/app/styles/components/info-table.scss new file mode 100644 index 000000000000..00d9678a13d2 --- /dev/null +++ b/ui/app/styles/components/info-table.scss @@ -0,0 +1,9 @@ +.info-table { + &.vlt-table td { + padding-top: 0px; + padding-bottom: 0px; + } + .info-table-row { + box-shadow: none; + } +} diff --git a/ui/app/styles/components/known-secondaries-card.scss b/ui/app/styles/components/known-secondaries-card.scss new file mode 100644 index 000000000000..63f1025be2f2 --- /dev/null +++ b/ui/app/styles/components/known-secondaries-card.scss @@ -0,0 +1,18 @@ +.selectable-card.secondaries { + grid-column: 2/3; + grid-row: 1/3; + + @include until($mobile) { + grid-column: 1/1; + grid-row: 1/1; + } + + .secondaries-table { + margin-bottom: $spacing-s; + } + + .link { + font-size: $size-7; + text-decoration: none; + } +} diff --git a/ui/app/styles/components/modal.scss b/ui/app/styles/components/modal.scss index fe784140d97b..fe629265737a 100644 --- a/ui/app/styles/components/modal.scss +++ b/ui/app/styles/components/modal.scss @@ -5,6 +5,8 @@ .modal-card { box-shadow: $box-shadow-highest; border: 1px solid $grey-light; + max-height: calc(100vh - 70px); + margin-top: 60px; &-head { border-radius: 0; @@ -39,6 +41,38 @@ } } +.modal-card-title.title { + display: flex; + align-items: center; +} + pre { background-color: inherit; } + +.is-highlight { + .modal-card-head { + background: $yellow-010; + border: 1px solid $yellow-100; + } + .modal-card-title { + color: $yellow-dark; + } +} + +.modal-confirm-section .is-help { + color: $grey; + margin: $spacing-xxs 0; + strong { + color: inherit; + } +} + +.modal-confirm-section { + margin: $spacing-xl 0 $spacing-m; +} + +.modal-card-foot-outlined { + background: #f7f8fa; + border-top: 1px solid #bac1cc; +} diff --git a/ui/app/styles/components/replication-dashboard.scss b/ui/app/styles/components/replication-dashboard.scss new file mode 100644 index 000000000000..3cca15c76a63 --- /dev/null +++ b/ui/app/styles/components/replication-dashboard.scss @@ -0,0 +1,131 @@ +.replication-dashboard { + box-shadow: none; + + .selectable-card { + line-height: normal; + + &:hover { + box-shadow: 0 0 0 1px rgba($grey-dark, 0.3); + } + + .toolbar-link { + color: $blue-500; + } + } + + .helper-text { + font-weight: $font-weight-normal; + } + + .title.is-6 { + margin-bottom: $spacing-xs; + } + + .reindexing-alert, + .syncing-alert { + margin-top: $spacing-xl; + } + + .selectable-card-container { + margin-top: $spacing-xl; + display: grid; + + &.primary, + .summary { + margin: 2rem 0 2rem 0; + grid-template-columns: 1fr 2fr; + + @include until($mobile) { + grid-template-columns: 1fr; + } + } + + &.secondary { + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + grid-gap: $spacing-xl; + } + + .card-container { + display: grid; + grid-gap: $spacing-s; + grid-template-columns: 1fr 1fr; + grid-template-rows: 0.2fr 0.2fr 0.2fr; + padding: $spacing-l; + line-height: 1.5; + + &.summary { + grid-template-rows: 0.2fr 1fr 0.2fr 1fr; + } + + &.has-border-danger:hover { + box-shadow: none; + } + + @include until(1320px) { + // prevent an issue with the card descriptions wrapping and expanding height + min-height: 250px; + } + + .grid-item-top-left { + grid-column: 1 / span 1; + display: flex; + } + .grid-item-top-right { + grid-column: 2 / span 1; + justify-self: right; + } + .grid-item-left { + grid-column: 1/1; + grid-row: 2/2; + } + .grid-item-right { + grid-column: 2/2; + grid-row: 2/2; + } + + .grid-item-bottom-left { + grid-column: 1/1; + grid-row: 3/3; + display: flex; + align-items: center; + } + .grid-item-bottom-right { + grid-column: 2/2; + grid-row: 3/3; + } + + .grid-item-second-row { + grid-column: 1 / span 2; + grid-row: 2/2; + } + + .grid-item-third-row { + grid-column: 1 / span 2; + grid-row: 3/4; + + .empty-state { + padding: 0px 12px; + box-shadow: none; + } + } + .grid-item-bottom-row { + grid-column: 1 / span 2; + grid-row: 4/4; + } + } + + &.summary { + margin-bottom: $spacing-xl; + } + } + .summary-state { + padding-bottom: $spacing-xl; + border-bottom: 1px solid rgba($grey-dark, 0.3); + } + + // prevent double lines at the bottom of the dashboard + &.box { + padding-bottom: 0; + padding-top: 1px; // at least 1px so border still shows + } +} diff --git a/ui/app/styles/components/replication-doc-link.scss b/ui/app/styles/components/replication-doc-link.scss new file mode 100644 index 000000000000..6bf2d02dfc60 --- /dev/null +++ b/ui/app/styles/components/replication-doc-link.scss @@ -0,0 +1,8 @@ +.documentation-link { + margin: $spacing-s 0 $spacing-l 0; + float: right; + + .doc-link { + font-weight: normal; + } +} diff --git a/ui/app/styles/components/replication-header.scss b/ui/app/styles/components/replication-header.scss new file mode 100644 index 000000000000..7a93426306da --- /dev/null +++ b/ui/app/styles/components/replication-header.scss @@ -0,0 +1,5 @@ +.replication-header { + .tabs-container { + margin-bottom: $spacing-l; + } +} diff --git a/ui/app/styles/components/replication-mode-summary.scss b/ui/app/styles/components/replication-mode-summary.scss new file mode 100644 index 000000000000..27ba8a577026 --- /dev/null +++ b/ui/app/styles/components/replication-mode-summary.scss @@ -0,0 +1,11 @@ +.replication-description { + flex-shrink: 1; + + .title { + margin-bottom: $spacing-xs; + } + + .detail-tags { + margin-bottom: $spacing-m; + } +} diff --git a/ui/app/styles/components/replication-page.scss b/ui/app/styles/components/replication-page.scss new file mode 100644 index 000000000000..8809a1f6dc1b --- /dev/null +++ b/ui/app/styles/components/replication-page.scss @@ -0,0 +1,10 @@ +.replication-page { + .empty-state { + background: none; + + .empty-state-message { + padding-bottom: $spacing-s; + border-bottom: 1px solid $grey-light; + } + } +} diff --git a/ui/app/styles/components/replication-primary-card.scss b/ui/app/styles/components/replication-primary-card.scss new file mode 100644 index 000000000000..8cf99be14e84 --- /dev/null +++ b/ui/app/styles/components/replication-primary-card.scss @@ -0,0 +1,15 @@ +.replication { + .selectable-card { + display: initial; + line-height: normal; + padding: $spacing-l; + + &:hover { + box-shadow: 0 0 0 1px rgba($grey-dark, 0.3); + } + + .card-title { + margin-bottom: 2rem; + } + } +} diff --git a/ui/app/styles/components/replication-summary.scss b/ui/app/styles/components/replication-summary.scss new file mode 100644 index 000000000000..7e308a2d4cd7 --- /dev/null +++ b/ui/app/styles/components/replication-summary.scss @@ -0,0 +1,9 @@ +.replication { + .toolbar { + border-top: 0px; + } + + .helper-text { + font-weight: normal; + } +} diff --git a/ui/app/styles/components/selectable-card.scss b/ui/app/styles/components/selectable-card.scss index a2939ceccd94..4bdc15c1a1d1 100644 --- a/ui/app/styles/components/selectable-card.scss +++ b/ui/app/styles/components/selectable-card.scss @@ -67,12 +67,21 @@ font-weight: 500; line-height: 1.33; } + + .vlt-table { + max-height: 200px; + overflow-y: auto; + } } .selectable-card.is-rounded { border-radius: $radius; } +.selectable-card.has-border-danger { + box-shadow: none; +} + .change-metric-icon.is-decrease { transform: rotate(135deg); } diff --git a/ui/app/styles/components/ui-wizard.scss b/ui/app/styles/components/ui-wizard.scss index 186bfadbe820..3dd3397b57a0 100644 --- a/ui/app/styles/components/ui-wizard.scss +++ b/ui/app/styles/components/ui-wizard.scss @@ -191,7 +191,7 @@ } .progress-bar { - background: $ui-gray-100; + background: $progress-bar-background-color; box-shadow: inset 0 0 0 1px $ui-gray-200; display: flex; height: $wizard-progress-bar-height; diff --git a/ui/app/styles/components/vlt-table.scss b/ui/app/styles/components/vlt-table.scss index 2b61b079fec5..c55633de6906 100644 --- a/ui/app/styles/components/vlt-table.scss +++ b/ui/app/styles/components/vlt-table.scss @@ -4,6 +4,15 @@ height: 0; } + &.sticky-header { + thead th { + position: sticky; + background: #fff; + box-shadow: 0 1px 0px 0px rgba($grey-dark, 0.3); + top: 0; + } + } + th, td { padding: $spacing-s; @@ -34,4 +43,9 @@ td.no-padding { padding: 0; } + + code { + font-size: $size-7; + color: $black; + } } diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss index a9c4d267b61a..1a91cc109b87 100644 --- a/ui/app/styles/core.scss +++ b/ui/app/styles/core.scss @@ -60,9 +60,11 @@ @import './components/http-requests-bar-chart'; @import './components/http-requests-table'; @import './components/init-illustration'; +@import './components/info-table'; @import './components/info-table-row'; @import './components/input-hint'; @import './components/kmip-role-edit'; +@import './components/known-secondaries-card.scss'; @import './components/linked-block'; @import './components/list-item-row'; @import './components/list-pagination'; @@ -78,10 +80,19 @@ @import './components/radio-card'; @import './components/radial-progress'; @import './components/raft-join'; +@import './components/replication-dashboard'; +@import './components/replication-doc-link'; +@import './components/replication-header'; +@import './components/replication-mode-summary'; +@import './components/replication-page'; +@import './components/replication-primary-card'; +@import './components/replication-summary'; @import './components/role-item'; @import './components/search-select'; @import './components/selectable-card'; @import './components/selectable-card-container.scss'; +// action-block extends selectable-card +@import './components/action-block.scss'; @import './components/shamir-progress'; @import './components/sidebar'; @import './components/splash-page'; diff --git a/ui/app/styles/core/helpers.scss b/ui/app/styles/core/helpers.scss index 7ba150425685..884ffd0dfcbd 100644 --- a/ui/app/styles/core/helpers.scss +++ b/ui/app/styles/core/helpers.scss @@ -39,6 +39,10 @@ display: flex; flex-direction: column; } +.is-flex-row { + display: flex; + flex-direction: row; +} .is-flex-v-centered { display: flex; align-items: center; @@ -143,6 +147,30 @@ font-size: $size-8; text-transform: lowercase; } -.has-bottom-margin { +.has-bottom-margin-xs { + margin-bottom: $spacing-xs; +} +.has-bottom-margin-s { + margin-bottom: $spacing-s; +} +.has-bottom-margin-m { margin-bottom: $spacing-m; } +.has-bottom-margin-l { + margin-bottom: $spacing-l; +} +.has-top-margin-xl { + margin-top: $spacing-xl; +} +.has-border-danger { + border: 1px solid $danger; +} + +ul.bullet { + list-style: disc; + padding-left: $spacing-m; +} + +.has-text-semibold { + font-weight: $font-weight-semibold; +} diff --git a/ui/app/styles/core/message.scss b/ui/app/styles/core/message.scss index f1a60248eae2..83e4d70f3388 100644 --- a/ui/app/styles/core/message.scss +++ b/ui/app/styles/core/message.scss @@ -25,6 +25,10 @@ font-size: 16px; font-weight: $font-weight-bold; line-height: 1.25; + + .progress { + margin-left: $spacing-xs; + } } .close-button + .message-title { diff --git a/ui/app/styles/core/progress.scss b/ui/app/styles/core/progress.scss index 3454e73bd7a3..ceb4f8e9827e 100644 --- a/ui/app/styles/core/progress.scss +++ b/ui/app/styles/core/progress.scss @@ -1,9 +1,9 @@ -.progress[value]::-webkit-progress-bar, -.progress[value]::-webkit-progress-value { - border-radius: 2px; -} .progress { - border-radius: 0; + -webkit-appearance: none; + -moz-appearance: none; + background: $progress-bar-background-color; + box-shadow: inset 0 0 0 1px $ui-gray-200; + border-radius: $radius; margin-bottom: 0; &.is-small { height: 0.5rem; @@ -11,10 +11,24 @@ &.is-narrow { width: 30px; } + &.is-medium { + width: 120px; + } } -.progress.is-rounded { - border-radius: 2px; + +// style the container in chrome +.progress[value]::-webkit-progress-bar { + box-shadow: inset 0 0 0 1px $ui-gray-200; +} + +// style the bar in chrome +.progress[value]::-webkit-progress-value { + border-radius: $radius; + transition: width 1s ease-out; } -.progress.is-bordered { - box-shadow: 0 0 0 4px $progress-bar-background-color; + +// style the bar in firefox +.progress[value]::-moz-progress-bar { + border-radius: $radius; + transition: width 1s ease-out; } diff --git a/ui/app/styles/utils/_bulma_variables.scss b/ui/app/styles/utils/_bulma_variables.scss index 339d478d718f..bc20bb541356 100644 --- a/ui/app/styles/utils/_bulma_variables.scss +++ b/ui/app/styles/utils/_bulma_variables.scss @@ -65,7 +65,7 @@ $navbar-background-color: transparent; $menu-item-hover-background-color: $blue; $menu-item-hover-color: $white; -$progress-bar-background-color: lighten($grey-light, 15%); +$progress-bar-background-color: $ui-gray-050; $base-border: 1px solid $ui-gray-300; $light-border: 1px solid $ui-gray-200; diff --git a/ui/app/templates/components/shamir-flow.hbs b/ui/app/templates/components/shamir-flow.hbs index edc58ea02e8a..ace02e1d89a0 100644 --- a/ui/app/templates/components/shamir-flow.hbs +++ b/ui/app/templates/components/shamir-flow.hbs @@ -119,7 +119,7 @@ {{else}}
-
+
{{#if errors}}
{{message-error errors=errors}} diff --git a/ui/app/templates/components/shamir-modal-flow.hbs b/ui/app/templates/components/shamir-modal-flow.hbs new file mode 100644 index 000000000000..954a9e20dbfd --- /dev/null +++ b/ui/app/templates/components/shamir-modal-flow.hbs @@ -0,0 +1,194 @@ + + +
+ +
+
\ No newline at end of file diff --git a/ui/app/templates/components/transit-key-action/datakey.hbs b/ui/app/templates/components/transit-key-action/datakey.hbs index b79fbac460e9..ed311aea82c6 100644 --- a/ui/app/templates/components/transit-key-action/datakey.hbs +++ b/ui/app/templates/components/transit-key-action/datakey.hbs @@ -89,7 +89,7 @@
-

Plaintext is base64 encoded

+

Plaintext is base64 encoded

Ciphertext

{{ciphertext}} diff --git a/ui/app/templates/components/wizard/replication-details.hbs b/ui/app/templates/components/wizard/replication-details.hbs deleted file mode 100644 index e04d4da686c7..000000000000 --- a/ui/app/templates/components/wizard/replication-details.hbs +++ /dev/null @@ -1,19 +0,0 @@ - - -

- Here you can see the details about your new replication cluster, manage or disable replication, and handle secondary clusters. You can also get a quick status by hovering over the "Replication" link at the top. -

-
-
-

- Ready to move on? -

- -
-
diff --git a/ui/app/templates/components/wizard/replication-setup.hbs b/ui/app/templates/components/wizard/replication-setup.hbs index 23ac6056e1ff..449ac22b0b33 100644 --- a/ui/app/templates/components/wizard/replication-setup.hbs +++ b/ui/app/templates/components/wizard/replication-setup.hbs @@ -8,6 +8,16 @@

Vault has two kinds of replication, each for a different purpose. Do you want to keep a backup of your data, or are you more interested in speed of access?

+ + + + + + +

diff --git a/ui/app/templates/partials/status/cluster.hbs b/ui/app/templates/partials/status/cluster.hbs index 150e8dd0daac..b084a56d639c 100644 --- a/ui/app/templates/partials/status/cluster.hbs +++ b/ui/app/templates/partials/status/cluster.hbs @@ -62,6 +62,30 @@


{{/if}} + {{else}} + {{#if (has-permission 'status' routeParams='replication')}} + +
+ {{/if}} {{/if}} {{/if}} {{/unless}} diff --git a/ui/app/templates/vault/cluster/oidc-callback.hbs b/ui/app/templates/vault/cluster/oidc-callback.hbs index e0317112a891..9f73e7e5910a 100644 --- a/ui/app/templates/vault/cluster/oidc-callback.hbs +++ b/ui/app/templates/vault/cluster/oidc-callback.hbs @@ -1,7 +1,7 @@
-
+
- -

- Disaster Recovery secondary is enabled -

-
- - - {{#if (eq action 'promote')}} - - - {{/if}} - {{#if (eq action 'update')}} - - {{/if}} - {{#unless action}} - -

- Generate an Operation Token by entering a portion of the master key. - Once all portions are entered, the generated operation token may be used to manage your secondary Disaster Recovery cluster. -

-
- {{/unless}} -
- diff --git a/ui/app/templates/vault/cluster/replication-dr-promote/details.hbs b/ui/app/templates/vault/cluster/replication-dr-promote/details.hbs new file mode 100644 index 000000000000..8ee73bd585c4 --- /dev/null +++ b/ui/app/templates/vault/cluster/replication-dr-promote/details.hbs @@ -0,0 +1,45 @@ +
+
+ + + {{#if Page.isDisabled}} + + + + {{else}} + + + + + + {{/if}} + +
+
diff --git a/ui/app/templates/vault/cluster/replication-dr-promote/index.hbs b/ui/app/templates/vault/cluster/replication-dr-promote/index.hbs new file mode 100644 index 000000000000..da24814d600d --- /dev/null +++ b/ui/app/templates/vault/cluster/replication-dr-promote/index.hbs @@ -0,0 +1,41 @@ +
+
+ + + {{#if Page.isDisabled}} + + + + {{else}} +
+ +
+ {{/if}} +
+
+
diff --git a/ui/lib/core/addon/components/alert-banner.js b/ui/lib/core/addon/components/alert-banner.js index 275b4b94eeb1..9eb6dde75ec7 100644 --- a/ui/lib/core/addon/components/alert-banner.js +++ b/ui/lib/core/addon/components/alert-banner.js @@ -12,9 +12,11 @@ import layout from '../templates/components/alert-banner'; * * ``` * - * @param type=null {String} - The banner type. This comes from the message-types helper. - * @param [message=null {String}] - The message to display within the banner. - * @param [title=null {String}] - A title to show above the message. If this is not provided, there are default values for each type of alert. + * @param {String} type=null - The banner type. This comes from the message-types helper. + * @param {String} [secondIconType=null] - If you want a second icon to appear to the right of the title. This comes from the message-types helper. + * @param {Object} [progressBar=null] - An object containing a value and maximum for a progress bar. Will be displayed next to the message title. + * @param {String} [message=null] - The message to display within the banner. + * @param {String} [title=null] - A title to show above the message. If this is not provided, there are default values for each type of alert. * */ @@ -23,6 +25,8 @@ export default Component.extend({ type: null, message: null, title: null, + secondIconType: null, + progressBar: null, yieldWithoutColumn: false, classNameBindings: ['containerClass'], @@ -33,4 +37,8 @@ export default Component.extend({ alertType: computed('type', function() { return messageTypes([this.get('type')]); }), + + secondAlertType: computed('secondIconType', function() { + return messageTypes([this.get('secondIconType')]); + }), }); diff --git a/ui/lib/core/addon/components/confirmation-modal.js b/ui/lib/core/addon/components/confirmation-modal.js new file mode 100644 index 000000000000..b2bac1d6582f --- /dev/null +++ b/ui/lib/core/addon/components/confirmation-modal.js @@ -0,0 +1,37 @@ +/** + * @module ConfirmationModal + * ConfirmationModal components are used to provide an alternative to ConfirmationButton that automatically prompts the user to fill in confirmation text before they can continue with a potentially destructive action. It is built off the Modal component + * + * @example + * ```js + * + * ``` + * @param {function} onConfirm - onConfirm is the action that happens when user clicks onConfirm after filling in the confirmation block + * @param {boolean} isActive - Controls whether the modal is "active" eg. visible or not. + * @param {string} title - Title of the modal + * @param {function} onClose - specify what to do when user attempts to close modal + * @param {string} [buttonText=Confirm] - Button text on the confirm button + * @param {string} [confirmText=Yes] - The confirmation text that the user must type before continuing + * @param {string} [buttonClass=is-danger] - extra class to add to confirm button (eg. "is-danger") + * @param {sting} [type=warning] - Applies message-type styling to header. Override to default with empty string + * @param {string} [toConfirmMsg] - Finishes the sentence "Type YES to confirm ..." + * @param {string} [testSelector] - The unique test selector used on the input to fill in text during tests. + */ + +import Component from '@ember/component'; +import layout from '../templates/components/confirmation-modal'; + +export default Component.extend({ + layout, + buttonClass: 'is-danger', + buttonText: 'Confirm', + confirmText: 'Yes', + type: 'warning', + toConfirmMsg: '', + testSelector: '', +}); diff --git a/ui/lib/core/addon/components/empty-state.js b/ui/lib/core/addon/components/empty-state.js index 6d7c6d550bca..2a2f36ac47f7 100644 --- a/ui/lib/core/addon/components/empty-state.js +++ b/ui/lib/core/addon/components/empty-state.js @@ -13,7 +13,7 @@ import layout from '../templates/components/empty-state'; * * @param title=null{String} - A short label for the empty state * @param message=null{String} - A description of why a user might be seeing the empty state and possibly instructions for actions they may take. - * + * @param [icon='']{String} - A optional param to display icon to the right of the title */ export default Component.extend({ @@ -21,4 +21,5 @@ export default Component.extend({ tagName: '', title: null, message: null, + icon: '', }); diff --git a/ui/lib/core/addon/components/info-table-row.js b/ui/lib/core/addon/components/info-table-row.js index ef6895c25ab4..2fadd1fa4c15 100644 --- a/ui/lib/core/addon/components/info-table-row.js +++ b/ui/lib/core/addon/components/info-table-row.js @@ -11,11 +11,12 @@ import layout from '../templates/components/info-table-row'; * * @example * ```js - * + * * ``` * * @param value=null {any} - The the data to be displayed - by default the content of the component will only show if there is a value. Also note that special handling is given to boolean values - they will render `Yes` for true and `No` for false. * @param label=null {string} - The display name for the value. + * @param helperText=null {string} - Text to describe the value displayed beneath the label. * @param alwaysRender=false {Boolean} - Indicates if the component content should be always be rendered. When false, the value of `value` will be used to determine if the component should render. * */ @@ -27,6 +28,7 @@ export default Component.extend({ alwaysRender: false, label: null, + helperText: null, value: null, valueIsBoolean: computed('value', function() { diff --git a/ui/lib/core/addon/components/info-table.js b/ui/lib/core/addon/components/info-table.js new file mode 100644 index 000000000000..13959817d403 --- /dev/null +++ b/ui/lib/core/addon/components/info-table.js @@ -0,0 +1,27 @@ +import Component from '@ember/component'; +import layout from '../templates/components/info-table'; + +/** + * @module InfoTable + * InfoTable components are a table with a single column and header. They are used to render a list of InfoTableRow components. + * + * @example + * ```js + * + * ``` + * @param {String} [title=Info Table] - The title of the table. Used for accessibility purposes. + * @param {String} header=null - The column header. + * @param {Array} items=null - An array of strings which will be used as the InfoTableRow value. + */ + +export default Component.extend({ + layout, + tagName: '', + title: 'Info Table', + header: null, + items: null, +}); diff --git a/ui/app/components/key-value-header.js b/ui/lib/core/addon/components/key-value-header.js similarity index 96% rename from ui/app/components/key-value-header.js rename to ui/lib/core/addon/components/key-value-header.js index fb743b9f9990..79bf7bcf21fc 100644 --- a/ui/app/components/key-value-header.js +++ b/ui/lib/core/addon/components/key-value-header.js @@ -1,9 +1,11 @@ import { computed } from '@ember/object'; import Component from '@ember/component'; import utils from 'vault/lib/key-utils'; +import layout from '../templates/components/key-value-header'; import { encodePath } from 'vault/utils/path-encoding-helpers'; export default Component.extend({ + layout, tagName: 'nav', classNames: 'key-value-header breadcrumb', ariaLabel: 'breadcrumbs', diff --git a/ui/app/components/learn-link.js b/ui/lib/core/addon/components/learn-link.js similarity index 100% rename from ui/app/components/learn-link.js rename to ui/lib/core/addon/components/learn-link.js diff --git a/ui/app/components/modal.js b/ui/lib/core/addon/components/modal.js similarity index 54% rename from ui/app/components/modal.js rename to ui/lib/core/addon/components/modal.js index 55c5dc1f4987..fda8df3a2e5c 100644 --- a/ui/app/components/modal.js +++ b/ui/lib/core/addon/components/modal.js @@ -10,12 +10,32 @@ * @param {function} onClose - onClose is the action taken when someone clicks the modal background or close button (if shown). * @param {string} [title] - This text shows up in the header section of the modal. * @param {boolean} [showCloseButton=false] - controls whether the close button in the top right corner shows. + * @param {string} type=null - The header type. This comes from the message-types helper. */ import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { messageTypes } from 'core/helpers/message-types'; +import layout from '../templates/components/modal'; export default Component.extend({ + layout, title: null, showCloseButton: false, + type: null, + glyph: computed('type', function() { + const modalType = this.get('type'); + if (!modalType) { + return; + } + return messageTypes([this.get('type')]); + }), + modalClass: computed('type', function() { + const modalType = this.get('type'); + if (!modalType) { + return 'modal'; + } + return 'modal ' + messageTypes([this.get('type')]).class; + }), onClose: () => {}, }); diff --git a/ui/lib/core/addon/components/replication-action-generate-token.js b/ui/lib/core/addon/components/replication-action-generate-token.js new file mode 100644 index 000000000000..4127ea6bea64 --- /dev/null +++ b/ui/lib/core/addon/components/replication-action-generate-token.js @@ -0,0 +1,6 @@ +import Actions from './replication-actions-single'; +import layout from '../templates/components/replication-action-generate-token'; + +export default Actions.extend({ + layout, +}); diff --git a/ui/lib/core/addon/components/replication-actions-single.js b/ui/lib/core/addon/components/replication-actions-single.js index 90573cbea1ca..b922c382a9ae 100644 --- a/ui/lib/core/addon/components/replication-actions-single.js +++ b/ui/lib/core/addon/components/replication-actions-single.js @@ -3,7 +3,7 @@ import Component from '@ember/component'; export default Component.extend({ onSubmit() {}, replicationMode: null, - replicationDisplayMode: null, + replicationModeForDisplay: null, model: null, actions: { diff --git a/ui/lib/core/addon/components/replication-actions.js b/ui/lib/core/addon/components/replication-actions.js index 53b009a52596..7081749f0d99 100644 --- a/ui/lib/core/addon/components/replication-actions.js +++ b/ui/lib/core/addon/components/replication-actions.js @@ -1,6 +1,5 @@ import { alias } from '@ember/object/computed'; import Component from '@ember/component'; -import { computed } from '@ember/object'; import ReplicationActions from 'core/mixins/replication-actions'; import layout from '../templates/components/replication-actions'; @@ -10,7 +9,6 @@ const DEFAULTS = { primary_cluster_addr: null, errors: [], id: null, - replicationMode: null, force: false, }; @@ -19,7 +17,6 @@ export default Component.extend(ReplicationActions, DEFAULTS, { replicationMode: null, model: null, cluster: alias('model'), - reset() { if (!this || this.isDestroyed || this.isDestroying) { return; @@ -27,19 +24,9 @@ export default Component.extend(ReplicationActions, DEFAULTS, { this.setProperties(DEFAULTS); }, - replicationDisplayMode: computed('replicationMode', function() { - const replicationMode = this.get('replicationMode'); - if (replicationMode === 'dr') { - return 'DR'; - } - if (replicationMode === 'performance') { - return 'Performance'; - } - }), - actions: { onSubmit() { - return this.submitHandler(...arguments); + return this.submitHandler.perform(...arguments); }, clear() { this.reset(); diff --git a/ui/lib/core/addon/components/replication-dashboard.js b/ui/lib/core/addon/components/replication-dashboard.js new file mode 100644 index 000000000000..650a7ec24757 --- /dev/null +++ b/ui/lib/core/addon/components/replication-dashboard.js @@ -0,0 +1,102 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { clusterStates } from 'core/helpers/cluster-states'; +import { capitalize } from '@ember/string'; +import { htmlSafe } from '@ember/template'; +import layout from '../templates/components/replication-dashboard'; + +/** + * @module ReplicationDashboard + * The `ReplicationDashboard` component is a contextual component of the replication-page component. + * It organizes cluster data specific to mode (dr or performance) and also the type (primary or secondary). + * It is the parent contextual component of the replication--card components. + * + * @example + * ```js + * + * ``` + * @param {Object} data=null - An Ember data object that is pulled from the Ember Cluster Model. + * @param {String} [componentToRender=''] - A string that determines which card component is displayed. There are three options, replication-primary-card, replication-secondary-card, replication-summary-card. + * @param {Boolean} [isSecondary=false] - Used to determine the title and display logic. + * @param {Boolean} [isSummaryDashboard=false] - Only true when the cluster is both a dr and performance primary. If true, replicationDetailsSummary is populated and used to pass through the cluster details. + * @param {Object} replicationDetailsSummary=null - An Ember data object computed off the Ember Model. It combines the Model.dr and Model.performance objects into one and contains details specific to the mode replication. + * @param {Object} replicationDetails=null - An Ember data object pulled from the Ember Model. It contains details specific to the whether the replication is dr or performance. + * @param {String} clusterMode=null - The cluster mode passed through to a table component. + * @param {Object} reindexingDetails=null - An Ember data object used to show a reindexing progress bar. + */ + +export default Component.extend({ + layout, + componentToRender: '', + data: null, + isSecondary: false, + isSummaryDashboard: false, + replicationDetails: null, + replicationDetailsSummary: null, + isSyncing: computed('replicationDetails.{state}', 'isSecondary', function() { + const { state } = this.replicationDetails; + const isSecondary = this.isSecondary; + return isSecondary && state && clusterStates([state]).isSyncing; + }), + isReindexing: computed('replicationDetails.{reindex_in_progress}', function() { + const { replicationDetails } = this; + return !!replicationDetails.reindex_in_progress; + }), + reindexingStage: computed('replicationDetails.{reindex_stage}', function() { + const { replicationDetails } = this; + const stage = replicationDetails.reindex_stage; + // specify the stage if we have one + if (stage) { + return `: ${capitalize(stage)}`; + } + return ''; + }), + progressBar: computed('replicationDetails.{reindex_building_progress,reindex_building_total}', function() { + const { reindex_building_progress, reindex_building_total } = this.replicationDetails; + let progressBar = null; + + if (reindex_building_progress && reindex_building_total) { + progressBar = { + value: reindex_building_progress, + max: reindex_building_total, + }; + } + + return progressBar; + }), + summaryState: computed( + 'replicationDetailsSummary.dr.{state}', + 'replicationDetailsSummary.performance.{state}', + function() { + const { replicationDetailsSummary } = this; + const drState = replicationDetailsSummary.dr.state; + const performanceState = replicationDetailsSummary.performance.state; + + if (drState !== performanceState) { + // when DR and Performance is enabled on the same cluster, + // the states should always be the same + // we are leaving this console log statement to be sure + console.log('DR State: ', drState, 'Performance State: ', performanceState); + } + + return drState; + } + ), + reindexMessage: computed('isSecondary', 'progressBar', function() { + if (!this.isSecondary) { + return htmlSafe( + 'This can cause a delay depending on the size of the data store. You can not use Vault during this time.' + ); + } + return 'This can cause a delay depending on the size of the data store. You can use Vault during this time.'; + }), +}); diff --git a/ui/lib/core/addon/components/replication-doc-link.js b/ui/lib/core/addon/components/replication-doc-link.js new file mode 100644 index 000000000000..af4f814a4ab2 --- /dev/null +++ b/ui/lib/core/addon/components/replication-doc-link.js @@ -0,0 +1,17 @@ +import Component from '@ember/component'; +import layout from '../templates/components/replication-doc-link'; + +/** + * @module ReplicationDocLink + * The `ReplicationDocLink` component is a learn link with helper text used on the Replication Dashboards. + * The link takes you to the key monitoring metrics learn doc. + * + * @example + * ```js + * + * ``` + */ + +export default Component.extend({ + layout, +}); diff --git a/ui/lib/core/addon/components/replication-header.js b/ui/lib/core/addon/components/replication-header.js new file mode 100644 index 000000000000..f34a1ac4db54 --- /dev/null +++ b/ui/lib/core/addon/components/replication-header.js @@ -0,0 +1,31 @@ +import Component from '@ember/component'; +import layout from '../templates/components/replication-header'; + +/** + * @module ReplicationHeader + * The `ReplicationHeader` is a header component used on the Replication Dashboards. + * + * @example + * ```js + * + * ``` + * @param {Object} model=null - An Ember data object pulled from the Ember cluster model. + * @param {String} title=null - The title of the header. + * @param {String} [secondaryID=null] - The secondaryID pulled off of the model object. + * @param {Boolean} isSummaryDashboard=false - True when you have both a primary performance and dr cluster dashboard. + */ + +export default Component.extend({ + layout, + data: null, + classNames: ['replication-header'], + isSecondary: null, + secondaryId: null, + isSummaryDashboard: false, + 'data-test-replication-header': true, +}); diff --git a/ui/lib/core/addon/components/replication-mode-summary.js b/ui/lib/core/addon/components/replication-mode-summary.js index 7e19a887eeea..8b0126898761 100644 --- a/ui/lib/core/addon/components/replication-mode-summary.js +++ b/ui/lib/core/addon/components/replication-mode-summary.js @@ -15,7 +15,7 @@ export default Component.extend({ version: service(), router: service(), namespace: service(), - classNameBindings: ['isMenu::box', 'isMenu::level'], + classNameBindings: ['isMenu::box'], attributeBindings: ['href', 'target'], display: 'banner', isMenu: equal('display', 'menu'), @@ -48,4 +48,9 @@ export default Component.extend({ clusterIdDisplay: replicationAttr('clusterIdDisplay'), mode: null, cluster: null, + modeState: computed('cluster', 'mode', function() { + const { cluster, mode } = this; + const clusterState = cluster[mode].state; + return clusterState; + }), }); diff --git a/ui/lib/core/addon/components/replication-page.js b/ui/lib/core/addon/components/replication-page.js new file mode 100644 index 000000000000..10876f744cf3 --- /dev/null +++ b/ui/lib/core/addon/components/replication-page.js @@ -0,0 +1,142 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import layout from '../templates/components/replication-page'; +import { inject as service } from '@ember/service'; +import { task } from 'ember-concurrency'; + +/** + * @module ReplicationPage + * The `ReplicationPage` component is the parent contextual component that holds the replication-dashboard, and various replication--card components. + * It is the top level component on routes displaying replication dashboards. + * + * @example + * ```js + * + * ``` + * @param {Object} cluster=null - An Ember data object that is pulled from the Ember Cluster Model. + */ + +const MODE = { + dr: 'Disaster Recovery', + performance: 'Performance', +}; + +export default Component.extend({ + layout, + store: service(), + router: service(), + reindexingDetails: null, + didReceiveAttrs() { + this._super(arguments); + this.getReplicationModeStatus.perform(); + }, + getReplicationModeStatus: task(function*() { + let resp; + const { replicationMode } = this.model; + + if (this.isSummaryDashboard) { + // the summary dashboard is not mode specific and will error + // while running replication/null/status in the replication-mode adapter + return; + } + + try { + resp = yield this.get('store') + .adapterFor('replication-mode') + .fetchStatus(replicationMode); + } catch (e) { + // do not handle error + } + this.set('reindexingDetails', resp); + }), + isSummaryDashboard: computed('model.dr.{mode}', 'model.performance.{mode}', function() { + const router = this.router; + const currentRoute = router.get('currentRouteName'); + + // we only show the summary dashboard in the replication index route + if (currentRoute === 'vault.cluster.replication.index') { + const drMode = this.model.dr.mode; + const performanceMode = this.model.performance.mode; + return drMode === 'primary' && performanceMode === 'primary'; + } + }), + formattedReplicationMode: computed('model.{replicationMode}', 'isSummaryDashboard', function() { + // dr or performance 🤯 + const { isSummaryDashboard } = this; + if (isSummaryDashboard) { + return 'Disaster Recovery & Performance'; + } + const mode = this.model.replicationMode; + return MODE[mode]; + }), + clusterMode: computed('model.{replicationAttrs}', 'isSummaryDashboard', function() { + // primary or secondary + const { model } = this; + const { isSummaryDashboard } = this; + if (isSummaryDashboard) { + // replicationAttrs does not exist when summaryDashboard + return 'primary'; + } + return model.replicationAttrs.mode; + }), + isLoadingData: computed('clusterMode', 'model.{replicationAttrs}', function() { + const { clusterMode } = this; + const { model } = this; + const { isSummaryDashboard } = this; + if (isSummaryDashboard) { + return false; + } + const clusterId = model.replicationAttrs.clusterId; + const replicationDisabled = model.replicationAttrs.replicationDisabled; + if (clusterMode === 'bootstrapping' || (!clusterId && !replicationDisabled)) { + // if clusterMode is bootstrapping + // if no clusterId, the data hasn't loaded yet, wait for another status endpoint to be called + return true; + } + return false; + }), + isSecondary: computed('clusterMode', function() { + const { clusterMode } = this; + return clusterMode === 'secondary'; + }), + replicationDetailsSummary: computed('isSummaryDashboard', function() { + const { model } = this; + const { isSummaryDashboard } = this; + if (!isSummaryDashboard) { + return; + } + if (isSummaryDashboard) { + let combinedObject = {}; + combinedObject.dr = model['dr']; + combinedObject.performance = model['performance']; + return combinedObject; + } + }), + replicationDetails: computed('model.{replicationMode}', 'isSummaryDashboard', function() { + const { model } = this; + const { isSummaryDashboard } = this; + if (isSummaryDashboard) { + // Cannot return null + return {}; + } + const replicationMode = model.replicationMode; + return model[replicationMode]; + }), + isDisabled: computed('replicationDetails.{mode}', function() { + if (this.replicationDetails.mode === 'disabled' || this.replicationDetails.mode === 'primary') { + return true; + } + return false; + }), + message: computed('model.{anyReplicationEnabled}', 'formattedReplicationMode', function() { + let msg; + if (this.model.anyReplicationEnabled) { + msg = `This ${this.formattedReplicationMode} secondary has not been enabled. You can do so from the ${this.formattedReplicationMode} Primary.`; + } else { + msg = `This cluster has not been enabled as a ${this.formattedReplicationMode} Secondary. You can do so by enabling replication and adding a secondary from the ${this.formattedReplicationMode} Primary.`; + } + return msg; + }), +}); diff --git a/ui/lib/core/addon/components/replication-secondary-card.js b/ui/lib/core/addon/components/replication-secondary-card.js new file mode 100644 index 000000000000..a72535e6ce47 --- /dev/null +++ b/ui/lib/core/addon/components/replication-secondary-card.js @@ -0,0 +1,58 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import layout from '../templates/components/replication-secondary-card'; +import { clusterStates } from 'core/helpers/cluster-states'; + +/** + * @module ReplicationSecondaryCard + * The `ReplicationSecondaryCard` component is a card-like component. It displays cluster mode details specific for DR and Performance Secondaries. + * + * @example + * ```js + * + * ``` + * @param {String} [title=null] - The title to be displayed on the top left corner of the card. + * @param {Object} replicationDetails=null - An Ember data object pulled from the Ember Model. It contains details specific to the mode's replication. + */ + +export default Component.extend({ + layout, + tagName: '', + title: null, + replicationDetails: null, + state: computed('replicationDetails.{state}', function() { + return this.replicationDetails && this.replicationDetails.state + ? this.replicationDetails.state + : 'unknown'; + }), + connection: computed('replicationDetails.{connection_state}', function() { + return this.replicationDetails.connection_state ? this.replicationDetails.connection_state : 'unknown'; + }), + lastRemoteWAL: computed('replicationDetails.{lastRemoteWAL}', function() { + return this.replicationDetails && this.replicationDetails.lastRemoteWAL + ? this.replicationDetails.lastRemoteWAL + : 0; + }), + inSyncState: computed('state', function() { + // if our definition of what is considered 'synced' changes, + // we should use the clusterStates helper instead + return this.state === 'stream-wals'; + }), + hasErrorClass: computed('replicationDetails', 'title', 'state', 'connection', function() { + const { title, state, connection } = this; + + // only show errors on the state card + if (title === 'Status') { + const currentClusterisOk = clusterStates([state]).isOk; + const primaryIsOk = clusterStates([connection]).isOk; + return !(currentClusterisOk && primaryIsOk); + } + return false; + }), + knownPrimaryClusterAddrs: computed('replicationDetails.{knownPrimaryClusterAddrs}', function() { + return this.replicationDetails.knownPrimaryClusterAddrs; + }), +}); diff --git a/ui/lib/core/addon/components/replication-summary-card.js b/ui/lib/core/addon/components/replication-summary-card.js new file mode 100644 index 000000000000..d4b7adc0c28e --- /dev/null +++ b/ui/lib/core/addon/components/replication-summary-card.js @@ -0,0 +1,44 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import layout from '../templates/components/replication-summary-card'; + +/** + * @module ReplicationSummaryCard + * The `ReplicationSummaryCard` is a card-like component. It displays cluster mode details for both DR and Performance + * + * @example + * ```js + * + * ``` + * @param {String} [title=null] - The title to be displayed on the top left corner of the card. + * @param {Object} replicationDetails=null - An Ember data object computed off the Ember Model. It combines the Model.dr and Model.performance objects into one and contains details specific to the mode replication. + */ + +export default Component.extend({ + layout, + title: null, + replicationDetails: null, + lastDrWAL: computed('replicationDetails.dr.{lastWAL}', function() { + return this.replicationDetails.dr.lastWAL || 0; + }), + lastPerformanceWAL: computed('replicationDetails.performance.{lastWAL}', function() { + return this.replicationDetails.performance.lastWAL || 0; + }), + merkleRootDr: computed('replicationDetails.dr.{merkleRoot}', function() { + return this.replicationDetails.dr.merkleRoot || ''; + }), + merkleRootPerformance: computed('replicationDetails.performance.{merkleRoot}', function() { + return this.replicationDetails.performance.merkleRoot || ''; + }), + knownSecondariesDr: computed('replicationDetails.dr.{knownSecondaries}', function() { + const knownSecondaries = this.replicationDetails.dr.knownSecondaries; + return knownSecondaries.length; + }), + knownSecondariesPerformance: computed('replicationDetails.performance.{knownSecondaries}', function() { + const knownSecondaries = this.replicationDetails.performance.knownSecondaries; + return knownSecondaries.length; + }), +}); diff --git a/ui/lib/core/addon/components/replication-table-rows.js b/ui/lib/core/addon/components/replication-table-rows.js new file mode 100644 index 000000000000..c9f868e3a107 --- /dev/null +++ b/ui/lib/core/addon/components/replication-table-rows.js @@ -0,0 +1,37 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import layout from '../templates/components/replication-table-rows'; + +/** + * @module ReplicationTableRows + * The `ReplicationTableRows` component is table component. It displays cluster mode details specific to the cluster of the Dashboard it is used on. + * + * @example + * ```js + * + * ``` + * @param {Object} replicationDetails=null - An Ember data object pulled from the Ember Model. It contains details specific to the whether the replication is dr or performance. + * @param {String} clusterMode=null - The cluster mode (e.g. primary or secondary) passed through to a table component. + */ + +export default Component.extend({ + layout, + classNames: ['replication-table-rows'], + replicationDetails: null, + clusterMode: null, + secondaryId: computed('replicationDetails.{secondaryId}', function() { + return this.replicationDetails.secondaryId; + }), + primaryClusterAddr: computed('replicationDetails.{primaryClusterAddr}', function() { + return this.replicationDetails.primaryClusterAddr || 'None set'; + }), + merkleRoot: computed('replicationDetails.{merkleRoot}', function() { + return this.replicationDetails.merkleRoot || 'unknown'; + }), + clusterId: computed('replicationDetails.{clusterId}', function() { + return this.replicationDetails.clusterId || 'unknown'; + }), +}); diff --git a/ui/lib/core/addon/helpers/cluster-states.js b/ui/lib/core/addon/helpers/cluster-states.js new file mode 100644 index 000000000000..6b149af36b34 --- /dev/null +++ b/ui/lib/core/addon/helpers/cluster-states.js @@ -0,0 +1,64 @@ +import { helper as buildHelper } from '@ember/component/helper'; + +// A hash of cluster states to ensure that the status menu and replication dashboards +// display states and glyphs consistently +// this includes states for the primary vault cluster and the connection_state + +export const CLUSTER_STATES = { + running: { + glyph: 'check-circle-outline', + isOk: true, + isSyncing: false, + }, + ready: { + glyph: 'check-circle-outline', + isOk: true, + isSyncing: false, + }, + 'stream-wals': { + glyph: 'check-circle-outline', + isOk: true, + isSyncing: false, + }, + 'merkle-diff': { + glyph: 'android-sync', + isOk: true, + isSyncing: true, + }, + connecting: { + glyph: 'android-sync', + isOk: true, + isSyncing: true, + }, + 'merkle-sync': { + glyph: 'android-sync', + isOk: true, + isSyncing: true, + }, + idle: { + glyph: 'cancel-square-outline', + isOk: false, + isSyncing: false, + }, + transient_failure: { + glyph: 'cancel-circle-outline', + isOk: false, + isSyncing: false, + }, + shutdown: { + glyph: 'cancel-circle-outline', + isOk: false, + isSyncing: false, + }, +}; + +export function clusterStates([state]) { + const defaultDisplay = { + glyph: '', + isOk: null, + isSyncing: null, + }; + return CLUSTER_STATES[state] || defaultDisplay; +} + +export default buildHelper(clusterStates); diff --git a/ui/app/helpers/date-format.js b/ui/lib/core/addon/helpers/date-format.js similarity index 100% rename from ui/app/helpers/date-format.js rename to ui/lib/core/addon/helpers/date-format.js diff --git a/ui/app/helpers/format-number.js b/ui/lib/core/addon/helpers/format-number.js similarity index 100% rename from ui/app/helpers/format-number.js rename to ui/lib/core/addon/helpers/format-number.js diff --git a/ui/lib/core/addon/helpers/message-types.js b/ui/lib/core/addon/helpers/message-types.js index 87d79083ea0d..76958e95e169 100644 --- a/ui/lib/core/addon/helpers/message-types.js +++ b/ui/lib/core/addon/helpers/message-types.js @@ -25,6 +25,12 @@ export const MESSAGE_TYPES = { glyph: 'alert-triangle', text: 'Warning', }, + loading: { + class: 'is-success', + glyphClass: 'has-text-success', + glyph: 'loading', + text: 'Loading', + }, }; export function messageTypes([type]) { diff --git a/ui/lib/core/addon/helpers/replication-action-for-mode.js b/ui/lib/core/addon/helpers/replication-action-for-mode.js index 82c198d24f90..d560871f43a4 100644 --- a/ui/lib/core/addon/helpers/replication-action-for-mode.js +++ b/ui/lib/core/addon/helpers/replication-action-for-mode.js @@ -8,7 +8,8 @@ const ACTIONS = { }, dr: { primary: ['disable', 'recover', 'reindex', 'demote'], - secondary: ['promote'], + // TODO: add disable, recover, and reindex when API is ready + secondary: ['promote', 'update-primary', 'generate-token'], bootstrapping: ['disable', 'recover', 'reindex'], }, }; diff --git a/ui/lib/core/addon/helpers/replication-mode-description.js b/ui/lib/core/addon/helpers/replication-mode-description.js new file mode 100644 index 000000000000..014ed5d92bd5 --- /dev/null +++ b/ui/lib/core/addon/helpers/replication-mode-description.js @@ -0,0 +1,14 @@ +import { helper as buildHelper } from '@ember/component/helper'; + +const REPLICATION_MODE_DESCRIPTIONS = { + dr: + 'Disaster Recovery Replication is designed to protect against catastrophic failure of entire clusters. Secondaries do not forward service requests until they are elected and become a new primary.', + performance: + 'Performance Replication scales workloads horizontally across clusters to make requests faster. Local secondaries handle read requests but forward writes to the primary to be handled.', +}; + +export function replicationModeDescription([mode]) { + return REPLICATION_MODE_DESCRIPTIONS[mode]; +} + +export default buildHelper(replicationModeDescription); diff --git a/ui/lib/core/addon/mixins/replication-actions.js b/ui/lib/core/addon/mixins/replication-actions.js index c09a60aa22d2..8e67d15aa260 100644 --- a/ui/lib/core/addon/mixins/replication-actions.js +++ b/ui/lib/core/addon/mixins/replication-actions.js @@ -10,7 +10,8 @@ export default Mixin.create({ loading: or('save.isRunning', 'submitSuccess.isRunning'), onEnable() {}, onDisable() {}, - submitHandler(action, clusterMode, data, event) { + onPromote() {}, + submitHandler: task(function*(action, clusterMode, data, event) { let replicationMode = (data && data.replicationMode) || this.get('replicationMode'); if (event && event.preventDefault) { event.preventDefault(); @@ -22,15 +23,18 @@ export default Mixin.create({ data = Object.keys(data).reduce((newData, key) => { var val = data[key]; if (isPresent(val)) { - newData[key] = val; + if (key === 'dr_operation_token_primary' || key === 'dr_operation_token_promote') { + newData['dr_operation_token'] = val; + } else { + newData[key] = val; + } } return newData; }, {}); delete data.replicationMode; } - - return this.save.perform(action, replicationMode, clusterMode, data); - }, + return yield this.save.perform(action, replicationMode, clusterMode, data); + }), save: task(function*(action, replicationMode, clusterMode, data) { let resp; @@ -41,7 +45,7 @@ export default Mixin.create({ } catch (e) { return this.submitError(e); } - yield this.submitSuccess.perform(resp, action, clusterMode); + return yield this.submitSuccess.perform(resp, action, clusterMode); }).drop(), submitSuccess: task(function*(resp, action, mode) { @@ -89,12 +93,13 @@ export default Mixin.create({ if (action === 'disable') { yield this.onDisable(); } - if (action === 'enable') { - yield this.onEnable(replicationMode); + if (action === 'promote') { + yield this.onPromote(); } - - if (mode === 'secondary' && replicationMode === 'dr') { - yield this.router.transitionTo('vault.cluster'); + if (action === 'enable') { + /// onEnable is a method available only to route vault.cluster.replication.index + // if action 'enable' is called from vault.cluster.replication.mode.index this method is not called + yield this.onEnable(replicationMode, mode); } }).drop(), diff --git a/ui/lib/core/addon/templates/components/alert-banner.hbs b/ui/lib/core/addon/templates/components/alert-banner.hbs index 9bdbe7a2e43e..e1ebd35a9cb3 100644 --- a/ui/lib/core/addon/templates/components/alert-banner.hbs +++ b/ui/lib/core/addon/templates/components/alert-banner.hbs @@ -1,12 +1,12 @@ -
+
{{#if @yieldWithoutColumn}} {{yield}} @@ -14,6 +14,21 @@
{{or @title this.alertType.text}} + {{#if @secondIconType}} +
{{#if @message}}

diff --git a/ui/lib/core/addon/templates/components/alert-inline.hbs b/ui/lib/core/addon/templates/components/alert-inline.hbs index 9bfd0f733450..973623816579 100644 --- a/ui/lib/core/addon/templates/components/alert-inline.hbs +++ b/ui/lib/core/addon/templates/components/alert-inline.hbs @@ -2,6 +2,6 @@ @glyph={{this.alertType.glyph}} class={{this.alertType.glyphClass}} /> -

+

{{@message}}

diff --git a/ui/lib/core/addon/templates/components/confirmation-modal.hbs b/ui/lib/core/addon/templates/components/confirmation-modal.hbs new file mode 100644 index 000000000000..9b28de1e78a1 --- /dev/null +++ b/ui/lib/core/addon/templates/components/confirmation-modal.hbs @@ -0,0 +1,44 @@ + + +
+ + +
+
diff --git a/ui/lib/core/addon/templates/components/empty-state.hbs b/ui/lib/core/addon/templates/components/empty-state.hbs index 0fb2a1899263..eccbf827411e 100644 --- a/ui/lib/core/addon/templates/components/empty-state.hbs +++ b/ui/lib/core/addon/templates/components/empty-state.hbs @@ -1,8 +1,17 @@
+ {{#if icon}} +
+ +

+ {{title}} +

+
+ {{else}}

{{title}}

+ {{/if}} {{#if message}}

{{message}} diff --git a/ui/lib/core/addon/templates/components/info-table-row.hbs b/ui/lib/core/addon/templates/components/info-table-row.hbs index fecdfe419c1f..bd7f3afcdb98 100644 --- a/ui/lib/core/addon/templates/components/info-table-row.hbs +++ b/ui/lib/core/addon/templates/components/info-table-row.hbs @@ -1,7 +1,14 @@ {{#if (or alwaysRender value)}} -

- {{label}} -
+ {{#if label}} +
+ {{label}} + {{#if helperText}} +
+ {{helperText}} +
+ {{/if}} +
+ {{/if}}
{{#if (has-block)}} {{yield}} diff --git a/ui/lib/core/addon/templates/components/info-table.hbs b/ui/lib/core/addon/templates/components/info-table.hbs new file mode 100644 index 000000000000..6586cd1a1303 --- /dev/null +++ b/ui/lib/core/addon/templates/components/info-table.hbs @@ -0,0 +1,23 @@ + diff --git a/ui/app/templates/components/key-value-header.hbs b/ui/lib/core/addon/templates/components/key-value-header.hbs similarity index 100% rename from ui/app/templates/components/key-value-header.hbs rename to ui/lib/core/addon/templates/components/key-value-header.hbs diff --git a/ui/lib/core/addon/templates/components/layout-loading.hbs b/ui/lib/core/addon/templates/components/layout-loading.hbs index b144ff68bf68..b0e99bd89572 100644 --- a/ui/lib/core/addon/templates/components/layout-loading.hbs +++ b/ui/lib/core/addon/templates/components/layout-loading.hbs @@ -1,4 +1,4 @@ -
+
diff --git a/ui/app/templates/components/modal.hbs b/ui/lib/core/addon/templates/components/modal.hbs similarity index 54% rename from ui/app/templates/components/modal.hbs rename to ui/lib/core/addon/templates/components/modal.hbs index 30bf5107371b..f2eb136eaa72 100644 --- a/ui/app/templates/components/modal.hbs +++ b/ui/lib/core/addon/templates/components/modal.hbs @@ -1,9 +1,20 @@ {{#ember-wormhole to="modal-wormhole"}} -