Ui/pricing metrics page setup (#10049)

* Create model and adapter for metrics/activity

* Query activity and return fake data on adapterError

* Add stub of pricing metrics cards and search form elements to metrics template

* Metrics page has pricing metrics rather than all-time tokens and requests

* update metrics config model

* Add metrics-config route and page

* Remove metrics/http-requests route and template

* remove log

* Add alert banner for when tracking disabled, and add result dates

* Small edits
This commit is contained in:
Chelsea Shaw
2020-10-01 14:37:33 -05:00
committed by GitHub
parent d7e7db8c96
commit 79a982ee47
16 changed files with 221 additions and 74 deletions

View File

@@ -0,0 +1,14 @@
import Application from '../application';
export default Application.extend({
queryRecord() {
return this.ajax(this.urlForQuery(), 'GET').then(resp => {
resp.id = resp.request_id;
return resp;
});
},
urlForQuery() {
return this.buildURL() + '/internal/counters/activity';
},
});

View File

@@ -0,0 +1,14 @@
import Application from '../application';
export default Application.extend({
queryRecord() {
return this.ajax(this.urlForQuery(), 'GET').then(resp => {
resp.id = resp.request_id;
return resp;
});
},
urlForQuery() {
return this.buildURL() + '/internal/counters/config';
},
});

View File

@@ -0,0 +1,24 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
export default Controller.extend({
infoRows: computed(function() {
return [
{
label: 'Usage data collection',
helperText: 'Enable or disable collecting data to track clients.',
valueKey: 'enabled',
},
{
label: 'Retention period',
helperText: 'The number of months of activity logs to maintain for client tracking.',
valueKey: 'retentionMonths',
},
{
label: 'Default display',
helperText: 'The number of months well display in the Vault usage dashboard by default.',
valueKey: 'defaultReportMonths',
},
];
}),
});

View File

@@ -0,0 +1,7 @@
import DS from 'ember-data';
export default DS.Model.extend({
total: DS.attr('object'),
endTime: DS.attr('string'),
startTime: DS.attr('string'),
});

View File

@@ -0,0 +1,10 @@
import DS from 'ember-data';
const { attr } = DS;
export default DS.Model.extend({
queriesAvailable: attr('boolean'),
defaultReportMonths: attr('number'),
retentionMonths: attr('number'),
enabled: attr('string'),
});

View File

@@ -15,10 +15,7 @@ Router.map(function() {
this.route('logout');
this.mount('open-api-explorer', { path: '/api-explorer' });
this.route('license');
this.route('metrics', function() {
this.route('index', { path: '/' });
this.route('http-requests');
});
this.route('metrics');
this.route('storage', { path: '/storage/raft' });
this.route('storage-restore', { path: '/storage/raft/restore' });
this.route('settings', function() {
@@ -137,6 +134,7 @@ Router.map(function() {
}
this.route('not-found', { path: '/*path' });
this.route('metrics-config');
});
this.route('not-found', { path: '/*path' });
});

View File

@@ -0,0 +1,8 @@
import Route from '@ember/routing/route';
import ClusterRoute from 'vault/mixins/cluster-route';
export default Route.extend(ClusterRoute, {
model() {
return this.store.queryRecord('metrics/config', {});
},
});

View File

@@ -1,8 +1,16 @@
import Route from '@ember/routing/route';
import ClusterRoute from 'vault/mixins/cluster-route';
import { hash } from 'rsvp';
export default Route.extend(ClusterRoute, {
model() {
return {};
let config = this.store.queryRecord('metrics/config', {});
let activity = this.store.queryRecord('metrics/activity', {});
return hash({
activity,
config,
});
},
});

View File

@@ -1,7 +0,0 @@
import ClusterRouteBase from '../cluster-route-base';
export default ClusterRouteBase.extend({
model() {
return this.store.queryRecord('metrics/http-requests', {});
},
});

View File

@@ -1,26 +0,0 @@
import Route from '@ember/routing/route';
import ClusterRoute from 'vault/mixins/cluster-route';
import { hash } from 'rsvp';
export default Route.extend(ClusterRoute, {
model() {
let totalEntities = this.store.queryRecord('metrics/entity', {}).then(response => {
return response.entities.total;
});
let httpsRequests = this.store.queryRecord('metrics/http-requests', {}).then(response => {
let reverseArray = response.counters.reverse();
return reverseArray;
});
let totalTokens = this.store.queryRecord('metrics/token', {}).then(response => {
return response.service_tokens.total;
});
return hash({
totalEntities,
httpsRequests,
totalTokens,
});
},
});

View File

@@ -0,0 +1,3 @@
import ApplicationSerializer from '../application';
export default ApplicationSerializer.extend({});

View File

@@ -0,0 +1,12 @@
import ApplicationSerializer from '../application';
export default ApplicationSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
const normalizedPayload = {
id: payload.id,
...payload.data,
enabled: payload.data.enabled.includes('enabled') ? 'On' : 'Off',
};
return this._super(store, primaryModelClass, normalizedPayload, id, requestType);
},
});

View File

@@ -0,0 +1,48 @@
<PageHeader as |p|>
<p.levelLeft>
<h1 class="title is-3">
Metrics
</h1>
</p.levelLeft>
</PageHeader>
<div class="tabs-container box is-bottomless is-marginless is-fullwidth is-paddingless">
<nav class="tabs">
<ul>
{{#link-to
"vault.cluster.metrics"
tagName="li"
activeClass="is-active"
}}
{{#link-to
"vault.cluster.metrics"
data-test-configuration-tab=false
}}
Vault usage
{{/link-to}}
{{/link-to}}
{{#link-to
"vault.cluster.metrics-config"
tagName="li"
activeClass="is-active"
}}
{{#link-to
"vault.cluster.metrics-config"
data-test-configuration-tab=true
}}
Configuration
{{/link-to}}
{{/link-to}}
</ul>
</nav>
</div>
<div class="tabs-container box is-bottomless is-marginless is-fullwidth is-paddingless">
{{#each infoRows as |item|}}
{{info-table-row
label=item.label
helperText=item.helperText
value=(get model item.valueKey)
}}
{{/each}}
</div>

View File

@@ -0,0 +1,70 @@
<PageHeader as |p|>
<p.levelLeft>
<h1 class="title is-3">
Metrics
</h1>
</p.levelLeft>
</PageHeader>
<div class="tabs-container box is-bottomless is-marginless is-fullwidth is-paddingless">
<nav class="tabs">
<ul>
{{#link-to
"vault.cluster.metrics"
tagName="li"
activeClass="is-active"
}}
{{#link-to
"vault.cluster.metrics"
data-test-configuration-tab=false
}}
Vault usage
{{/link-to}}
{{/link-to}}
{{#link-to
"vault.cluster.metrics-config"
tagName="li"
activeClass="is-active"
}}
{{#link-to
"vault.cluster.metrics-config"
data-test-configuration-tab=true
}}
Configuration
{{/link-to}}
{{/link-to}}
</ul>
</nav>
</div>
<div class="box is-sideless is-fullwidth is-marginless is-bottomless">
{{#unless (eq model.config.enabled 'On')}}
<AlertBanner
@type="warning"
@title="Tracking is disabled"
>
This feature is currently disabled and data is not being collected. {{#link-to 'vault.cluster.metrics-config'}}Edit the configuration{{/link-to}} to enable tracking again.
</AlertBanner>
{{/unless}}
<p class="has-bottom-margin-s">The active clients metric contributes to billing. It is collected at the end of each month alongside unique entities and direct active tokens.</p>
<h2 class="title is-4">
{{date-format model.activity.startTime "MMM DD, YYYY"}} through {{date-format model.activity.endTime "MMM DD, YYYY"}}
</h2>
<div class="selectable-card-container">
<SelectableCard
@cardTitle="Active clients"
@total={{model.activity.total.clients}}
@subText="Current namespace"
/>
<SelectableCard
@cardTitle="Unique entities"
@total={{model.activity.total.distinct_entities}}
@subText="Current namespace"
/>
<SelectableCard
@cardTitle="Active direct tokens"
@total={{model.activity.total.non_entity_tokens}}
@subText="Current namespace"
/>
</div>
</div>

View File

@@ -1,21 +0,0 @@
<PageHeader as |p|>
<p.top>
<nav class="key-value-header breadcrumb">
<ul>
<li>
<span class="sep">&#x0002f;</span>
{{#link-to "vault.cluster.metrics"}}
Metrics
{{/link-to}}
</li>
</ul>
</nav>
</p.top>
<p.levelLeft>
<h1 class="title is-3">
HTTP Request Volume
</h1>
</p.levelLeft>
</PageHeader>
<HttpRequestsContainer @counters={{model.counters}}/>

View File

@@ -1,15 +0,0 @@
<PageHeader as |p|>
<p.levelLeft>
<h1 class="title is-3">
Metrics
</h1>
</p.levelLeft>
</PageHeader>
<div class="box is-sideless is-fullwidth is-marginless is-bottomless">
{{#if (gt model.httpsRequests.length 1) }}
<SelectableCardContainer @counters={{model}} @gridContainer="true"/>
{{else}}
<SelectableCardContainer @counters={{model}} />
{{/if}}
</div>