vault/ui/app/components/http-requests-bar-chart.js
Matthew Irish 7e9c016883
UI - add kmip engine (#6936)
* add kmip engine

* adjust where kmip engine is mounted and sketch out routes

* add secret mount path service to share params to engines

* move list-controller and list-route mixins to core addon and adjust imports

* properly link kmip secrets from the secrets list page

* tweak routes and add list controllers

* stub out some models and adapters

* fix mixin exports

* move a bunch of components into the core addon

* use new empty yield in list-view in the namespace template

* scopes list using list-view and list-item components

* simplify and flatten routes, templates for all of the list pages

* role show route and template and scope create template

* add ember-router-helpers

* add more packages to the dependencies of the core addon

* add field-group-show component for listing fields from a model

* move more components to the shared addon

* make configure and configuration routes work and save a generated model

* save and list scopes

* role create, list, read

* list credentials properly

* move allowed attributes to field group

* show allowed operations on role details page

* add kmip logo to mount secrets engine list page

* add role edit page

* show all model attributes on role show page

* enable role edit

* fix newFields error by creating open api role model on the role list route

* only show selected fields on role edit page

* do not send scope and backend attrs to api

* move path-or-array to core addon

* move string-list component to core addon

* remove extra top border when there is only one field group

* add icons for all of the list pages

* update kmip config model so defaultValue doesn't error

* generate credentials

* credential create and show

* only show kmip when feature is enabled

* fix saving of TTL fields generated from Open API

* move masked-input and list-pagination components to core addon

* add param on edit form to allow for calling onSave after render happens

* polish credential show page and redirect there after generating credentials

* add externalLink for kmip engine

* add kmip-breadcrumb component

* use kmip-breadcrumb component

* add linkPrefix param to linked-block component to allow for routing programmatically inside an engine

* redirect to the right place when enabling kmip

* fix linting

* review feedback

* update signature for path-help usage

* fix ttl field expansion test

* remove role filed from role form, fix generate redirect

* remove field-group-show because it's in the core addon

* remove bottom rule from show pages

* fix Max TTL displayAttrs for ssh role

* update edit-form to take fields or attrs

* fix linting

* remove listenAddrs and set default val on ttl if a val is passed in
2019-06-21 16:05:45 -05:00

164 lines
4.8 KiB
JavaScript

import Component from '@ember/component';
import d3 from 'd3-selection';
import d3Scale from 'd3-scale';
import d3Axis from 'd3-axis';
import d3TimeFormat from 'd3-time-format';
import { assign } from '@ember/polyfills';
import { computed } from '@ember/object';
import { run } from '@ember/runloop';
import { task, waitForEvent } from 'ember-concurrency';
/**
* @module HttpRequestsBarChart
* HttpRequestsBarChart components are used to render a bar chart with the total number of HTTP Requests to a Vault server per month.
*
* @example
* ```js
* <HttpRequestsBarChart @counters={{counters}} />
* ```
*
* @param counters=null {Array} - A list of objects containing the total number of HTTP Requests for each month. `counters` should be the response from the `/internal/counters/requests` endpoint which looks like:
* COUNTERS = [
* {
* "start_time": "2019-05-01T00:00:00Z",
* "total": 50
* }
* ]
*/
const HEIGHT = 240;
export default Component.extend({
classNames: ['http-requests-bar-chart-container'],
counters: null,
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */
margin: { top: 24, right: 16, bottom: 24, left: 16 },
padding: 0.04,
width: 0,
height() {
const { margin } = this;
return HEIGHT - margin.top - margin.bottom;
},
parsedCounters: computed('counters', function() {
// parse the start times so bars and ticks display properly
const { counters } = this;
return counters.map(counter => {
return assign({}, counter, { start_time: d3TimeFormat.isoParse(counter.start_time) });
});
}),
yScale: computed('parsedCounters', 'height', function() {
const { parsedCounters } = this;
const height = this.height();
const counterTotals = parsedCounters.map(c => c.total);
return d3Scale
.scaleLinear()
.domain([0, Math.max(...counterTotals)])
.range([height, 0]);
}),
xScale: computed('parsedCounters', 'width', function() {
const { parsedCounters, width, margin, padding } = this;
return d3Scale
.scaleBand()
.domain(parsedCounters.map(c => c.start_time))
.rangeRound([0, width - margin.left - margin.right], 0.05)
.paddingInner(padding)
.paddingOuter(padding);
}),
didInsertElement() {
this._super(...arguments);
const { margin } = this;
// set the width after the element has been rendered because the chart axes depend on it.
// this helps us avoid an arbitrary hardcoded width which causes alignment & resizing problems.
run.schedule('afterRender', this, () => {
this.set('width', this.element.clientWidth - margin.left - margin.right);
this.renderBarChart();
});
},
renderBarChart() {
const { margin, width, xScale, yScale, parsedCounters } = this;
const height = this.height();
const barChartSVG = d3.select('.http-requests-bar-chart');
const barsContainer = d3.select('#bars-container');
// render the chart
d3.select('.http-requests-bar-chart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.attr('viewBox', `0 0 ${width} ${height}`);
// scale and render the axes
const yAxis = d3Axis
.axisRight(yScale)
.ticks(3, '.0s')
.tickSizeOuter(0);
const xAxis = d3Axis
.axisBottom(xScale)
.tickFormat(d3TimeFormat.utcFormat('%b %Y'))
.tickSizeOuter(0);
barChartSVG
.select('g.x-axis')
.attr('transform', `translate(0,${height})`)
.call(xAxis);
barChartSVG
.select('g.y-axis')
.attr('transform', `translate(${width - margin.left - margin.right}, 0)`)
.call(yAxis);
// render the gridlines
const gridlines = d3Axis
.axisRight(yScale)
.ticks(3)
.tickFormat('')
.tickSize(width - margin.left - margin.right);
barChartSVG.select('.gridlines').call(gridlines);
// render the bars
const bars = barsContainer.selectAll('.bar').data(parsedCounters, c => +c.start_time);
bars.exit().remove();
const barsEnter = bars
.enter()
.append('rect')
.attr('class', 'bar');
bars
.merge(barsEnter)
.attr('width', xScale.bandwidth())
.attr('height', counter => height - yScale(counter.total))
// the offset between each bar
.attr('x', counter => xScale(counter.start_time))
.attr('y', counter => yScale(counter.total));
},
updateDimensions() {
const newWidth = this.element.clientWidth;
const { margin } = this;
this.set('width', newWidth - margin.left - margin.right);
this.renderBarChart();
},
waitForResize: task(function*() {
while (true) {
yield waitForEvent(window, 'resize');
run.scheduleOnce('afterRender', this, 'updateDimensions');
}
})
.on('didInsertElement')
.cancelOn('willDestroyElement')
.drop(),
});