Skip to content

Commit

Permalink
Ui/summary dashboard (#9079)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
Monkeychip and andaley authored May 29, 2020
1 parent ef8e476 commit bf88fdc
Show file tree
Hide file tree
Showing 25 changed files with 626 additions and 139 deletions.
3 changes: 3 additions & 0 deletions ui/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ App = Application.extend({
'version',
'wizard',
],
externalRoutes: {
replication: 'vault.cluster.replication.index',
},
},
},
kmip: {
Expand Down
37 changes: 33 additions & 4 deletions ui/app/styles/components/replication-dashboard.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
.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 {
Expand All @@ -24,7 +30,8 @@
margin-top: $spacing-xl;
display: grid;

&.primary {
&.primary,
.summary {
margin: 2rem 0 2rem 0;
grid-template-columns: 1fr 2fr;

Expand All @@ -35,7 +42,7 @@

&.secondary {
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
grid-gap: $spacing-xl; //ARG TODO double check ok
grid-gap: $spacing-xl;
}

.card-container {
Expand All @@ -46,6 +53,10 @@
padding: $spacing-l;
line-height: 1.5;

&.summary {
grid-template-rows: 0.2fr 1fr 0.2fr 1fr;
}

&.has-border-danger:hover {
box-shadow: none;
}
Expand All @@ -55,10 +66,14 @@
min-height: 250px;
}

.grid-item-title {
grid-column: 1 / span 2;
.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;
Expand All @@ -70,15 +85,29 @@
.grid-item-left-bottom {
grid-column: 1/1;
grid-row: 3/3;
margin-top: -8px;

display: flex;
align-items: center;
}
.grid-item-right-bottom {
grid-column: 2/2;
grid-row: 3/3;
margin-top: -8px;
}
.grid-item-full-bottom {
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
Expand Down
5 changes: 5 additions & 0 deletions ui/app/styles/components/replication-header.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.replication-header {
.tabs-container {
margin-bottom: $spacing-l;
}
}
1 change: 1 addition & 0 deletions ui/app/styles/core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
@import './components/raft-join';
@import './components/replication-dashboard';
@import './components/replication-doc-link';
@import './components/replication-header';
@import './components/replication-page';
@import './components/replication-primary-card';
@import './components/replication-summary';
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
52 changes: 51 additions & 1 deletion ui/lib/core/addon/components/replication-dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,43 @@ import { clusterStates } from 'core/helpers/cluster-states';
import { capitalize } from '@ember/string';
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-<name>-card components.
*
* @example
* ```js
* <ReplicationDashboard
@data={{model}}
@componentToRender='replication-primary-card'
@isSecondary=false
@isSummaryDashboard=false
@replicationDetailsSummary={}
@replicationDetails={{replicationDetails}}
@clusterMode=primary
@reindexingDetails={{reindexingDetails}}
/>
* ```
* @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,
isSecondary: null,
replicationDetailsSummary: null,
isSyncing: computed('replicationDetails.{state}', 'isSecondary', function() {
const { state } = this.replicationDetails;
const isSecondary = this.isSecondary;
Expand Down Expand Up @@ -40,4 +72,22 @@ export default Component.extend({

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;
}
),
});
2 changes: 2 additions & 0 deletions ui/lib/core/addon/components/replication-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import layout from '../templates/components/replication-header';

export default Component.extend({
layout,
classNames: ['replication-header'],
isSecondary: null,
secondaryId: null,
isSummaryDashboard: false,
'data-test-replication-header': true,
});
71 changes: 67 additions & 4 deletions ui/lib/core/addon/components/replication-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ 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-<name>-card components.
* It is the top level component on routes displaying replication dashboards.
*
* @example
* ```js
* <ReplicationPage
@model={{cluster}}
/>
* ```
* @param {Object} cluster=null - An Ember data object that is pulled from the Ember Cluster Model.
*/

const MODE = {
dr: 'Disaster Recovery',
performance: 'Performance',
Expand All @@ -12,6 +26,7 @@ const MODE = {
export default Component.extend({
layout,
store: service(),
router: service(),
reindexingDetails: null,
didReceiveAttrs() {
this._super(arguments);
Expand All @@ -20,6 +35,13 @@ export default Component.extend({
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')
Expand All @@ -29,22 +51,45 @@ export default Component.extend({
}
this.set('reindexingDetails', resp);
}),
formattedReplicationMode: computed('model.{replicationMode}', function() {
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}', function() {
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
Expand All @@ -56,8 +101,26 @@ export default Component.extend({
const { clusterMode } = this;
return clusterMode === 'secondary';
}),
replicationDetails: computed('model.{replicationMode}', function() {
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];
}),
Expand Down
8 changes: 4 additions & 4 deletions ui/lib/core/addon/components/replication-secondary-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { clusterStates } from 'core/helpers/cluster-states';

/**
* @module ReplicationSecondaryCard
* ReplicationSecondaryCard components
* The `ReplicationSecondaryCard` component is a card-like component. It displays cluster mode details specific for DR and Performance Secondaries.
*
* @example
* ```js
* <ReplicationSecondaryCard
@title='States'
@replicationDetails=replicationDetails
@replicationDetails={{replicationDetails}}
/>
* ```
* @param {string} [title=null] - The title to be displayed on the top left corner of the card.
* @param replicationDetails=null{DS.Model.replicationDetails} - An Ember data object off the Ember data model. It is computed at the parent component and passed through to this component.
* @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({
Expand Down
44 changes: 44 additions & 0 deletions ui/lib/core/addon/components/replication-summary-card.js
Original file line number Diff line number Diff line change
@@ -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
* <ReplicationSummaryCard
@title='States'
@replicationDetails={DS.Model.replicationDetailsSummary}
/>
* ```
* @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;
}),
});
Loading

0 comments on commit bf88fdc

Please sign in to comment.