Skip to content

Commit

Permalink
UI: add pagination to new PKI (#23193) (#23239)
Browse files Browse the repository at this point in the history
* UI: add pagination to new PKI (#23193)

* fixes store type import

* fixes tests

---------

Co-authored-by: Jordan Reimer <[email protected]>
  • Loading branch information
hashishaw and zofskeez authored Sep 22, 2023
1 parent 202eb06 commit e345dd8
Show file tree
Hide file tree
Showing 28 changed files with 657 additions and 265 deletions.
3 changes: 3 additions & 0 deletions changelog/23193.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: Add pagination to PKI roles, keys, issuers, and certificates list pages
```
6 changes: 5 additions & 1 deletion ui/app/services/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,12 @@ export default Store.extend({
// pageFilter: a string that will be used to do a fuzzy match against the
// results, this is done pre-pagination
lazyPaginatedQuery(modelType, query /*, options*/) {
const skipCache = query.skipCache;
// We don't want skipCache to be part of the actual query key, so remove it
delete query.skipCache;
const adapter = this.adapterFor(modelType);
const modelName = normalizeModelName(modelType);
const dataCache = this.getDataset(modelName, query);
const dataCache = skipCache ? this.clearDataset(modelName) : this.getDataset(modelName, query);
const responsePath = query.responsePath;
assert('responsePath is required', responsePath);
assert('page is required', typeof query.page === 'number');
Expand Down Expand Up @@ -144,6 +147,7 @@ export default Store.extend({
prevPage: clamp(currentPage - 1, 1, lastPage),
total: dataset.length || 0,
filteredTotal: data.length || 0,
pageSize: size,
};

return response;
Expand Down
170 changes: 108 additions & 62 deletions ui/lib/pki/addon/components/page/pki-issuer-list.hbs
Original file line number Diff line number Diff line change
@@ -1,65 +1,111 @@
{{#each @issuers as |pkiIssuer idx|}}
<LinkedBlock class="list-item-row" @params={{array "issuers.issuer.details" pkiIssuer.id}} @linkPrefix={{@mountPoint}}>
<div class="level is-mobile">
<div class="level-left">
<div data-test-issuer-list={{pkiIssuer.id}}>
<Icon @name="certificate" class="has-text-grey-light" />
<span class="has-text-weight-semibold is-underline">
{{pkiIssuer.issuerRef}}
{{#if pkiIssuer.issuerName}}
<span class="tag has-text-grey-dark">{{pkiIssuer.id}}</span>
{{/if}}
</span>
<div class="is-flex-row has-left-margin-l has-top-margin-xs">
{{#if pkiIssuer.isDefault}}
<span class="tag has-text-grey-dark" data-test-is-default={{idx}}>default issuer</span>
{{/if}}
{{#if (not (eq pkiIssuer.isRoot undefined))}}
<span class="tag has-text-grey-dark" data-test-is-root-tag={{idx}}>{{if
pkiIssuer.isRoot
"root"
"intermediate"
}}</span>
{{/if}}
{{#if pkiIssuer.serialNumber}}
<span class="tag is-transparent has-right-margin-none" data-test-serial-number={{idx}}>
<InfoTooltip>
Serial number
</InfoTooltip>
{{pkiIssuer.serialNumber}}
<PkiPaginatedList @listRoute="issuers.index" @list={{@issuers}}>
<:actions>
<ToolbarLink @route="issuers.import" data-test-generate-issuer="import">
Import
</ToolbarLink>
<BasicDropdown @class="popup-menu" @horizontalPosition="auto-right" @verticalPosition="below" as |D|>
<D.Trigger
class={{concat "toolbar-link" (if D.isOpen " is-active")}}
@htmlTag="button"
data-test-issuer-generate-dropdown
>
Generate
<Chevron @direction="down" @isButton={{true}} />
</D.Trigger>
<D.Content @defaultClass="popup-menu-content">
<nav class="box menu" aria-label="generate options">
<ul class="menu-list">
<li class="action">
<LinkTo @route="issuers.generate-root" {{on "click" (fn this.onLinkClick D)}} data-test-generate-issuer="root">
Root
</LinkTo>
</li>
<li class="action">
<LinkTo
@route="issuers.generate-intermediate"
{{on "click" (fn this.onLinkClick D)}}
data-test-generate-issuer="intermediate"
>
Intermediate CSR
</LinkTo>
</li>
</ul>
</nav>
</D.Content>
</BasicDropdown>
</:actions>
<:list as |issuers|>
{{#each issuers as |pkiIssuer idx|}}
<LinkedBlock class="list-item-row" @params={{array "issuers.issuer.details" pkiIssuer.id}} @linkPrefix={{@mountPoint}}>
<div class="level is-mobile">
<div class="level-left">
<div data-test-issuer-list={{pkiIssuer.id}}>
<Icon @name="certificate" class="has-text-grey-light" />
<span class="has-text-weight-semibold is-underline">
{{pkiIssuer.issuerRef}}
{{#if pkiIssuer.issuerName}}
<span class="tag has-text-grey-dark">{{pkiIssuer.id}}</span>
{{/if}}
</span>
{{/if}}
{{#if pkiIssuer.parsedCertificate.common_name}}
<span class="tag is-transparent has-left-margin-none" data-test-common-name={{idx}}>
<InfoTooltip>
Common name
</InfoTooltip>
{{pkiIssuer.parsedCertificate.common_name}}
</span>
{{/if}}
<div class="is-flex-row has-left-margin-l has-top-margin-xs">
{{#if pkiIssuer.isDefault}}
<span class="tag has-text-grey-dark" data-test-is-default={{idx}}>default issuer</span>
{{/if}}
{{#if (not-eq pkiIssuer.isRoot undefined)}}
<span class="tag has-text-grey-dark" data-test-is-root-tag={{idx}}>{{if
pkiIssuer.isRoot
"root"
"intermediate"
}}</span>
{{/if}}
{{#if pkiIssuer.serialNumber}}
<span class="tag is-transparent has-right-margin-none" data-test-serial-number={{idx}}>
<InfoTooltip>
Serial number
</InfoTooltip>
{{pkiIssuer.serialNumber}}
</span>
{{/if}}
{{#if pkiIssuer.parsedCertificate.common_name}}
<span class="tag is-transparent has-left-margin-none" data-test-common-name={{idx}}>
<InfoTooltip>
Common name
</InfoTooltip>
{{pkiIssuer.parsedCertificate.common_name}}
</span>
{{/if}}
</div>
</div>
</div>
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu>
<nav class="menu" aria-label="issuer config options">
<ul class="menu-list">
<li data-test-popup-menu-details>
<LinkTo @route="issuers.issuer.details" @model={{pkiIssuer.id}}>
Details
</LinkTo>
</li>
<li>
<LinkTo @route="issuers.issuer.edit" @model={{pkiIssuer.id}}>
Edit
</LinkTo>
</li>
</ul>
</nav>
</PopupMenu>
</div>
</div>
</div>
</div>
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu>
<nav class="menu" aria-label="issuer config options">
<ul class="menu-list">
<li data-test-popup-menu-details>
<LinkTo @route="issuers.issuer.details" @model={{pkiIssuer.id}}>
Details
</LinkTo>
</li>
<li>
<LinkTo @route="issuers.issuer.edit" @model={{pkiIssuer.id}}>
Edit
</LinkTo>
</li>
</ul>
</nav>
</PopupMenu>
</div>
</div>
</div>
</LinkedBlock>
{{/each}}
</LinkedBlock>
{{/each}}
</:list>
<:empty>
<EmptyState @title="PKI not configured" @message={{this.notConfiguredMessage}}>
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>
</EmptyState>
</:empty>
</PkiPaginatedList>
29 changes: 29 additions & 0 deletions ui/lib/pki/addon/components/page/pki-issuer-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import { action } from '@ember/object';
import { next } from '@ember/runloop';
import Component from '@glimmer/component';
import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
import type PkiIssuerModel from 'vault/models/pki/issuer';

interface BasicDropdown {
actions: {
close: CallableFunction;
};
}
interface Args {
issuers: PkiIssuerModel[];
mountPoint: string;
}

export default class PkiIssuerList extends Component<Args> {
notConfiguredMessage = PKI_DEFAULT_EMPTY_STATE_MSG;

// To prevent production build bug of passing D.actions to on "click": https://github.com/hashicorp/vault/pull/16983
@action onLinkClick(D: BasicDropdown) {
next(() => D.actions.close());
}
}
125 changes: 68 additions & 57 deletions ui/lib/pki/addon/components/page/pki-key-list.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Toolbar>
<ToolbarActions>
<PkiPaginatedList @listRoute="keys.index" @list={{@keyModels}} @hasConfig={{@hasConfig}}>
<:actions>
{{#if @canImportKey}}
<ToolbarLink @route="keys.import" @type="download" data-test-pki-key-import>
Import
Expand All @@ -10,62 +10,73 @@
Generate
</ToolbarLink>
{{/if}}
</ToolbarActions>
</Toolbar>
<p class="has-padding">Below is information about the private keys used by the issuers to sign certificates. While
certificates represent a public assertion of an identity, private keys represent the private part of that identity, a
secret used to prove who they are and who they trust.</p>

{{#if @keyModels.length}}
{{#each @keyModels as |pkiKey|}}
<LinkedBlock class="list-item-row" @params={{array "keys.key.details" pkiKey.keyId}} @linkPrefix={{@mountPoint}}>
<div class="level is-mobile">
<div class="level-left">
<div>
<Icon @name="certificate" class="has-text-grey-light" />
<span class="has-text-weight-semibold is-underline" data-test-key={{if pkiKey.keyName "name" "id"}}>
{{or pkiKey.keyName pkiKey.id}}
</span>
<div class="is-flex-row has-left-margin-l has-top-margin-xs">
{{#if pkiKey.keyName}}
<span class="tag has-text-grey-dark" data-test-key="id">{{pkiKey.id}}</span>
{{/if}}
</:actions>
<:description>
<p class="has-padding">Below is information about the private keys used by the issuers to sign certificates. While
certificates represent a public assertion of an identity, private keys represent the private part of that identity, a
secret used to prove who they are and who they trust.</p>
</:description>
<:list as |keys|>
{{#each keys as |pkiKey|}}
<LinkedBlock class="list-item-row" @params={{array "keys.key.details" pkiKey.keyId}} @linkPrefix={{@mountPoint}}>
<div class="level is-mobile">
<div class="level-left">
<div>
<Icon @name="certificate" class="has-text-grey-light" />
<span class="has-text-weight-semibold is-underline" data-test-key={{if pkiKey.keyName "name" "id"}}>
{{or pkiKey.keyName pkiKey.id}}
</span>
<div class="is-flex-row has-left-margin-l has-top-margin-xs">
{{#if pkiKey.keyName}}
<span class="tag has-text-grey-dark" data-test-key="id">{{pkiKey.id}}</span>
{{/if}}
</div>
</div>
</div>
</div>
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu>
<nav class="menu">
<ul class="menu-list">
<li>
<LinkTo
@route="keys.key.details"
@model={{pkiKey.keyId}}
@disabled={{not @canRead}}
data-test-key-menu-link="details"
>
Details
</LinkTo>
</li>
<li>
<LinkTo
@route="keys.key.edit"
@model={{pkiKey.keyId}}
@disabled={{not @canEdit}}
data-test-key-menu-link="edit"
>
Edit
</LinkTo>
</li>
</ul>
</nav>
</PopupMenu>
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu>
<nav class="menu">
<ul class="menu-list">
<li>
<LinkTo
@route="keys.key.details"
@model={{pkiKey.keyId}}
@disabled={{not @canRead}}
data-test-key-menu-link="details"
>
Details
</LinkTo>
</li>
<li>
<LinkTo
@route="keys.key.edit"
@model={{pkiKey.keyId}}
@disabled={{not @canEdit}}
data-test-key-menu-link="edit"
>
Edit
</LinkTo>
</li>
</ul>
</nav>
</PopupMenu>
</div>
</div>
</div>
</div>
</LinkedBlock>
{{/each}}
{{else}}
<EmptyState @title="No keys yet" @message="There are no keys in this PKI mount. You can generate or create one." />
{{/if}}
</LinkedBlock>
{{/each}}
</:list>

<:empty>
<EmptyState @title="No keys yet" @message="There are no keys in this PKI mount. You can generate or create one." />
</:empty>

<:configure>
<EmptyState @title="PKI not configured" @message={{this.notConfiguredMessage}}>
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>
</EmptyState>
</:configure>
</PkiPaginatedList>
22 changes: 22 additions & 0 deletions ui/lib/pki/addon/components/page/pki-key-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import Component from '@glimmer/component';
import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
import type PkiKeyModel from 'vault/models/pki/key';

interface Args {
keyModels: PkiKeyModel[];
mountPoint: string;
canImportKey: boolean;
canGenerateKey: boolean;
canRead: boolean;
canEdit: boolean;
hasConfig: boolean;
}

export default class PkiKeyList extends Component<Args> {
notConfiguredMessage = PKI_DEFAULT_EMPTY_STATE_MSG;
}
Loading

0 comments on commit e345dd8

Please sign in to comment.