UI: PKI stub configure page (#18349)
This commit is contained in:
parent
fccc90ce75
commit
23a156122f
|
@ -0,0 +1,45 @@
|
|||
<div class="box is-bottomless is-fullwidth is-marginless">
|
||||
<div class="columns">
|
||||
{{#each this.configTypes as |option|}}
|
||||
<div class="column is-flex">
|
||||
<label for={{option.key}} class="box-label is-column {{if (eq this.configType option.key) 'is-selected'}}">
|
||||
<div>
|
||||
<h3 class="box-label-header title is-6">
|
||||
<Icon @size="24" @name={{option.icon}} />
|
||||
{{option.label}}
|
||||
</h3>
|
||||
<p class="help has-text-grey-dark">
|
||||
{{option.description}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<RadioButton
|
||||
id={{option.key}}
|
||||
name="pki-config-type"
|
||||
@value={{this.configType}}
|
||||
@groupValue={{this.replicationMode}}
|
||||
@onChange={{fn (mut this.configType) option.key}}
|
||||
data-test-pki-config-option={{option.key}}
|
||||
/>
|
||||
<label for={{option.key}}></label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#if this.configType}}
|
||||
{{! TODO: Forms }}
|
||||
{{else}}
|
||||
<EmptyState @title="Choose an option" @message="To see configuration options, choose your desired output above." />
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button type="button" class="button is-primary" disabled={{true}} data-test-pki-config-save>
|
||||
Done
|
||||
</button>
|
||||
<LinkTo @route="overview" class="button has-left-margin-s" data-test-pki-config-cancel>
|
||||
Cancel
|
||||
</LinkTo>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -0,0 +1,38 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
/**
|
||||
* @module PkiConfigureForm
|
||||
* PkiConfigureForm components are used to configure a PKI engine mount.
|
||||
*
|
||||
*/
|
||||
|
||||
export default class PkiConfigureForm extends Component {
|
||||
@tracked configType = '';
|
||||
|
||||
get configTypes() {
|
||||
return [
|
||||
{
|
||||
key: 'import',
|
||||
icon: 'download',
|
||||
label: 'Import a CA',
|
||||
description:
|
||||
'Import CA information via a PEM file containing the CA certificate and any private keys, concatenated together, in any order.',
|
||||
},
|
||||
{
|
||||
key: 'generate-root',
|
||||
icon: 'file-plus',
|
||||
label: 'Generate root',
|
||||
description:
|
||||
'Generates a new self-signed CA certificate and private key. This generated root will sign its own CRL.',
|
||||
},
|
||||
{
|
||||
key: 'generate-csr',
|
||||
icon: 'files',
|
||||
label: 'Generate intermediate CSR',
|
||||
description:
|
||||
'Generate a new CSR for signing, optionally generating a new private key. No new issuer is created by this call.',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import buildRoutes from 'ember-engines/routes';
|
|||
export default buildRoutes(function () {
|
||||
this.route('overview');
|
||||
this.route('configuration', function () {
|
||||
this.route('index', { path: '/' });
|
||||
this.route('tidy');
|
||||
this.route('create');
|
||||
this.route('edit');
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class PkiConfigurationCreateRoute extends Route {}
|
||||
export default class PkiConfigurationCreateRoute extends Route {
|
||||
@service secretMountPath;
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const backend = this.secretMountPath.currentPath || 'pki';
|
||||
controller.breadcrumbs = [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: backend, route: 'overview' },
|
||||
{ label: 'configure' },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class ConfigurationIndexRoute extends Route {}
|
|
@ -1,3 +1,42 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class PkiOverviewRoute extends Route {}
|
||||
export default class PkiOverviewRoute extends Route {
|
||||
@service secretMountPath;
|
||||
@service auth;
|
||||
@service store;
|
||||
|
||||
get win() {
|
||||
return this.window || window;
|
||||
}
|
||||
|
||||
hasConfig() {
|
||||
const endpoint = `${this.win.origin}/v1/${this.secretMountPath.currentPath}/issuers?list=true`;
|
||||
return this.auth
|
||||
.ajax(endpoint, 'GET', {})
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
async model() {
|
||||
return hash({
|
||||
hasConfig: this.hasConfig(),
|
||||
engine: this.store
|
||||
.query('secret-engine', {
|
||||
path: this.secretMountPath.currentPath,
|
||||
})
|
||||
.then((model) => {
|
||||
if (model) {
|
||||
return model.get('firstObject');
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const backend = this.secretMountPath.currentPath || 'pki';
|
||||
controller.breadcrumbs = [{ label: 'secrets', route: 'secrets', linkExternal: true }, { label: backend }];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1 @@
|
|||
<SecretListHeader
|
||||
@model={{this.model}}
|
||||
@backendCrumb={{hash
|
||||
label=this.model.id
|
||||
text=this.model.id
|
||||
path="vault.cluster.secrets.backend.list-root"
|
||||
model=this.model.id
|
||||
}}
|
||||
@isEngine={{true}}
|
||||
/>
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink @route="configuration.tidy">
|
||||
Tidy
|
||||
</ToolbarLink>
|
||||
<ToolbarLink @route="configuration.edit">
|
||||
Edit configuration
|
||||
</ToolbarLink>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
<div class="form-section">
|
||||
<label class="title has-padding-top is-5">
|
||||
URLs
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<label class="title has-padding-top is-5">
|
||||
CRL
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<label class="title has-padding-top is-5">
|
||||
Mount Configuration
|
||||
</label>
|
||||
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
{{! ARG TODO component and empty state }}
|
||||
</div>
|
||||
</div>
|
||||
{{outlet}}
|
|
@ -1 +1,12 @@
|
|||
route: pki.configuration.create
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-key-page-title>
|
||||
Configure PKI
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiConfigureForm />
|
|
@ -0,0 +1,40 @@
|
|||
<SecretListHeader
|
||||
@model={{this.model}}
|
||||
@backendCrumb={{hash
|
||||
label=this.model.id
|
||||
text=this.model.id
|
||||
path="vault.cluster.secrets.backend.list-root"
|
||||
model=this.model.id
|
||||
}}
|
||||
@isEngine={{true}}
|
||||
/>
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink @route="configuration.tidy">
|
||||
Tidy
|
||||
</ToolbarLink>
|
||||
<ToolbarLink @route="configuration.edit">
|
||||
Edit configuration
|
||||
</ToolbarLink>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
<div class="form-section">
|
||||
<label class="title has-padding-top is-5">
|
||||
URLs
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<label class="title has-padding-top is-5">
|
||||
CRL
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<label class="title has-padding-top is-5">
|
||||
Mount Configuration
|
||||
</label>
|
||||
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
{{! ARG TODO component and empty state }}
|
||||
</div>
|
||||
</div>
|
||||
{{outlet}}
|
|
@ -1,23 +1,29 @@
|
|||
<SecretListHeader
|
||||
@model={{this.model}}
|
||||
@model={{this.model.engine}}
|
||||
@backendCrumb={{hash
|
||||
label=this.model.id
|
||||
text=this.model.id
|
||||
label=this.model.engine.id
|
||||
text=this.model.engine.id
|
||||
path="vault.cluster.secrets.backend.list-root"
|
||||
model=this.model.id
|
||||
model=this.model.engine.id
|
||||
}}
|
||||
@isEngine={{true}}
|
||||
/>
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink @route="configuration.create">
|
||||
Configure PKI
|
||||
</ToolbarLink>
|
||||
{{#unless this.model.hasConfig}}
|
||||
<ToolbarLink @route="configuration.create">
|
||||
Configure PKI
|
||||
</ToolbarLink>
|
||||
{{/unless}}
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
<EmptyState @title="PKI not configured" @message="This PKI mount hasn’t yet been configured with a certificate issuer.">
|
||||
<LinkTo @route="configuration.create" @model={{this.model}}>
|
||||
Configure PKI
|
||||
</LinkTo>
|
||||
</EmptyState>
|
||||
{{#if this.model.hasConfig}}
|
||||
{{! TODO show overview items }}
|
||||
{{else}}
|
||||
<EmptyState @title="PKI not configured" @message="This PKI mount hasn’t yet been configured with a certificate issuer.">
|
||||
<LinkTo @route="configuration.create" @model={{this.model.engine}}>
|
||||
Configure PKI
|
||||
</LinkTo>
|
||||
</EmptyState>
|
||||
{{/if}}
|
|
@ -0,0 +1,30 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import { setupEngine } from 'ember-engines/test-support';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
const SELECTORS = {
|
||||
option: '[data-test-pki-config-option]',
|
||||
optionByKey: (key) => `[data-test-pki-config-option="${key}"]`,
|
||||
cancelButton: '[data-test-pki-config-cancel]',
|
||||
saveButton: '[data-test-pki-config-save]',
|
||||
};
|
||||
module('Integration | Component | pki-configure-form', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupEngine(hooks, 'pki');
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`<PkiConfigureForm />`, { owner: this.engine });
|
||||
|
||||
assert.dom(SELECTORS.option).exists({ count: 3 }, 'Three configuration options are shown');
|
||||
assert.dom(SELECTORS.cancelButton).exists('Cancel link is shown');
|
||||
assert.dom(SELECTORS.saveButton).isDisabled('Done button is disabled');
|
||||
|
||||
await click(SELECTORS.optionByKey('import'));
|
||||
assert.dom(SELECTORS.optionByKey('import')).isChecked('Selected item is checked');
|
||||
|
||||
await click(SELECTORS.optionByKey('generate-csr'));
|
||||
assert.dom(SELECTORS.optionByKey('generate-csr')).isChecked('Selected item is checked');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue