mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
* delete activity component, convert date-formatters to ts * add "month" filter to overview tab * add test coverage for date range dropdown * add month filtering to client-list * remove old comment * wire up clients to route filters for client-list * adds changelog * only link to client-list for enterprise versions * add refresh page link * render all tabs, add custom empty state for secret sycn clients * cleanup unused service imports * revert billing periods as first of the month * first round of test updates * update client count utils test * fix comment typo * organize tests Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
This commit is contained in:
parent
50fba16df4
commit
5ead15b8f2
34 changed files with 792 additions and 569 deletions
3
changelog/_9148.txt
Normal file
3
changelog/_9148.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui/activity: Adds filtering by month to the Client Count dashboard to link client counts to specific client IDs from the export API
|
||||
```
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
// base component for counts child routes that can be extended as needed
|
||||
// contains getters that filter and extract data from activity model for use in charts
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
import type ClientsActivityModel from 'vault/models/clients/activity';
|
||||
import type { ActivityExportData, ClientFilterTypes } from 'core/utils/client-count-utils';
|
||||
|
||||
/* This component does not actually render and is the base class to house
|
||||
shared computations between the Clients::Page::Overview and Clients::Page::List components */
|
||||
export interface Args {
|
||||
activity: ClientsActivityModel;
|
||||
exportData: ActivityExportData[];
|
||||
onFilterChange: CallableFunction;
|
||||
filterQueryParams: Record<ClientFilterTypes, string>;
|
||||
}
|
||||
|
||||
export default class ClientsActivityComponent extends Component<Args> {
|
||||
@action
|
||||
handleFilter(filters: Record<ClientFilterTypes, string>) {
|
||||
const { namespace_path, mount_path, mount_type } = filters;
|
||||
this.args.onFilterChange({ namespace_path, mount_path, mount_type });
|
||||
}
|
||||
|
||||
@action
|
||||
resetFilters() {
|
||||
this.handleFilter({ namespace_path: '', mount_path: '', mount_type: '' });
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,10 @@ import { action } from '@ember/object';
|
|||
import { service } from '@ember/service';
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
import { buildISOTimestamp, parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
import timestamp from 'core/utils/timestamp';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import type VersionService from 'vault/services/version';
|
||||
import type { HTMLElementEvent } from 'forms';
|
||||
|
||||
|
|
@ -67,16 +68,11 @@ export default class ClientsDateRangeComponent extends Component<Args> {
|
|||
const periods: string[] = [];
|
||||
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const startDate = new Date(this.args.billingStartTime);
|
||||
const utcMonth = startDate.getUTCMonth();
|
||||
const startDate = parseAPITimestamp(this.args.billingStartTime) as Date;
|
||||
const utcYear = startDate.getUTCFullYear() - i;
|
||||
|
||||
startDate.setUTCFullYear(utcYear);
|
||||
startDate.setUTCMonth(utcMonth);
|
||||
|
||||
periods.push(startDate.toISOString());
|
||||
}
|
||||
|
||||
return periods;
|
||||
}
|
||||
|
||||
|
|
@ -128,18 +124,11 @@ export default class ClientsDateRangeComponent extends Component<Args> {
|
|||
}
|
||||
|
||||
// HELPERS
|
||||
formatModalTimestamp(modalValue: string, isEnd: boolean) {
|
||||
formatModalTimestamp(modalValue: string, isEndDate: boolean) {
|
||||
const [yearString, month] = modalValue.split('-');
|
||||
const monthIdx = Number(month) - 1;
|
||||
const year = Number(yearString);
|
||||
// day = 0 for Date.UTC(year, month, day) returns the last day of the previous month,
|
||||
// which is why the monthIdx is increased by one for end dates.
|
||||
// Date.UTC() also returns December if -1 is passed (which happens when January is selected)
|
||||
const utc = isEnd
|
||||
? new Date(Date.UTC(year, monthIdx + 1, 0, 23, 59, 59))
|
||||
: new Date(Date.UTC(year, monthIdx, 1));
|
||||
|
||||
return utc.toISOString();
|
||||
return buildISOTimestamp({ monthIdx, year, isEndDate });
|
||||
}
|
||||
|
||||
setTrackedFromArgs() {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
@selected={{eq item (get this filterProperty)}}
|
||||
data-test-dropdown-item={{item}}
|
||||
>
|
||||
{{item}}
|
||||
{{if (eq filterProperty "month") (this.formatTimestamp item) item}}
|
||||
</D.Checkmark>
|
||||
{{else}}
|
||||
<D.Description
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
{{#if value}}
|
||||
<div>
|
||||
<Hds::Tag
|
||||
@text={{value}}
|
||||
@text={{if (eq filter "month") (this.formatTimestamp value) value}}
|
||||
{{! Filter renders in a tooltip if exceeds 20 characters }}
|
||||
@tooltipPlacement="bottom"
|
||||
@onDismiss={{fn this.clearFilters filter}}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,21 @@ import { cached, tracked } from '@glimmer/tracking';
|
|||
import { action } from '@ember/object';
|
||||
import { debounce } from '@ember/runloop';
|
||||
import { capitalize } from '@ember/string';
|
||||
import { buildISOTimestamp, parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
|
||||
import { ClientFilters, type ClientFilterTypes } from 'core/utils/client-count-utils';
|
||||
import {
|
||||
ActivityExportData,
|
||||
ClientFilters,
|
||||
MountClients,
|
||||
type ClientFilterTypes,
|
||||
} from 'core/utils/client-count-utils';
|
||||
import type { HTMLElementEvent } from 'vault/forms';
|
||||
|
||||
interface Args {
|
||||
filterQueryParams: Record<ClientFilterTypes, string>;
|
||||
// Dataset objects technically have more keys than the client filter types, but at minimum they contain ClientFilterTypes
|
||||
dataset: Record<ClientFilterTypes, string>[];
|
||||
dataset: ActivityExportData[] | MountClients[];
|
||||
onFilter: CallableFunction;
|
||||
dropdownMonths?: string[];
|
||||
}
|
||||
|
||||
// Correspond to each search input's tracked variable in the component class
|
||||
|
|
@ -29,18 +35,21 @@ export default class ClientsFilterToolbar extends Component<Args> {
|
|||
@tracked namespace_path: string;
|
||||
@tracked mount_path: string;
|
||||
@tracked mount_type: string;
|
||||
@tracked month: string;
|
||||
|
||||
// Tracked search inputs
|
||||
@tracked namespacePathSearch = '';
|
||||
@tracked mountPathSearch = '';
|
||||
@tracked mountTypeSearch = '';
|
||||
@tracked monthSearch = '';
|
||||
|
||||
constructor(owner: unknown, args: Args) {
|
||||
super(owner, args);
|
||||
const { namespace_path, mount_path, mount_type } = this.args.filterQueryParams;
|
||||
const { namespace_path, mount_path, mount_type, month } = this.args.filterQueryParams;
|
||||
this.namespace_path = namespace_path || '';
|
||||
this.mount_path = mount_path || '';
|
||||
this.mount_type = mount_type || '';
|
||||
this.month = month || '';
|
||||
}
|
||||
|
||||
get anyFilters() {
|
||||
|
|
@ -52,6 +61,7 @@ export default class ClientsFilterToolbar extends Component<Args> {
|
|||
const namespacePaths = new Set<string>();
|
||||
const mountPaths = new Set<string>();
|
||||
const mountTypes = new Set<string>();
|
||||
const months = new Set<string>();
|
||||
|
||||
// iterate over dataset once to get dropdown items
|
||||
this.args.dataset.forEach((d) => {
|
||||
|
|
@ -60,12 +70,24 @@ export default class ClientsFilterToolbar extends Component<Args> {
|
|||
if (namespace) namespacePaths.add(namespace);
|
||||
if (d.mount_path) mountPaths.add(d.mount_path);
|
||||
if (d.mount_type) mountTypes.add(d.mount_type);
|
||||
// `client_first_used_time` only exists for the dataset rendered in the "Client list" tab (ActivityExportData)
|
||||
// the "Overview tab" manually passes an array of months
|
||||
if ('client_first_used_time' in d && d.client_first_used_time) {
|
||||
// for now, we're only concerned with month granularity so we want the dropdown filter to contain an ISO timestamp
|
||||
// of the first of the month for each client_first_used_time
|
||||
const date = parseAPITimestamp(d.client_first_used_time) as Date;
|
||||
const year = date.getUTCFullYear();
|
||||
const monthIdx = date.getUTCMonth();
|
||||
const timestamp = buildISOTimestamp({ year, monthIdx, isEndDate: false });
|
||||
months.add(timestamp);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
[ClientFilters.NAMESPACE]: [...namespacePaths],
|
||||
[ClientFilters.MOUNT_PATH]: [...mountPaths],
|
||||
[ClientFilters.MOUNT_TYPE]: [...mountTypes],
|
||||
[ClientFilters.MONTH]: this.args.dropdownMonths || [...months],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +109,11 @@ export default class ClientsFilterToolbar extends Component<Args> {
|
|||
dropdownItems: this.dropdownItems[ClientFilters.MOUNT_TYPE],
|
||||
searchProperty: 'mountTypeSearch',
|
||||
},
|
||||
[ClientFilters.MONTH]: {
|
||||
label: 'month',
|
||||
dropdownItems: this.dropdownItems[ClientFilters.MONTH],
|
||||
searchProperty: 'monthSearch',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +125,8 @@ export default class ClientsFilterToolbar extends Component<Args> {
|
|||
.flatMap((f: ClientFilters) => {
|
||||
const filterValue = this.filterProps[f];
|
||||
const inDropdown = this.dropdownItems[f].includes(filterValue);
|
||||
// Don't show an alert for the "Month" filter because it doesn't match dataset values one to one
|
||||
if (ClientFilters.MONTH === f) return [];
|
||||
return !inDropdown && filterValue ? [alert(this.dropdownConfig[f].label, filterValue)] : [];
|
||||
})
|
||||
.join(' ');
|
||||
|
|
@ -137,6 +166,7 @@ export default class ClientsFilterToolbar extends Component<Args> {
|
|||
this.namespace_path = '';
|
||||
this.mount_path = '';
|
||||
this.mount_type = '';
|
||||
this.month = '';
|
||||
}
|
||||
this.applyFilters();
|
||||
}
|
||||
|
|
@ -159,6 +189,8 @@ export default class ClientsFilterToolbar extends Component<Args> {
|
|||
}
|
||||
|
||||
// TEMPLATE HELPERS
|
||||
formatTimestamp = (isoTimestamp: string) => parseAPITimestamp(isoTimestamp, 'MMMM yyyy');
|
||||
|
||||
searchDropdown = (dropdownItems: string[], searchProperty: SearchProperty) => {
|
||||
const searchInput = this[searchProperty];
|
||||
return searchInput
|
||||
|
|
|
|||
|
|
@ -10,8 +10,17 @@
|
|||
|
||||
{{#if @activityTimestamp}}
|
||||
<PH.Subtitle>
|
||||
Last Updated:
|
||||
Dashboard last updated:
|
||||
{{date-format @activityTimestamp "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
|
||||
<Hds::Button
|
||||
@color="tertiary"
|
||||
@icon="reload"
|
||||
@isIconOnly={{true}}
|
||||
@size="small"
|
||||
@text="Refresh page"
|
||||
data-test-button="Refresh page"
|
||||
{{on "click" this.refreshRoute}}
|
||||
/>
|
||||
</PH.Subtitle>
|
||||
{{/if}}
|
||||
{{#if this.version.isEnterprise}}
|
||||
|
|
@ -25,10 +34,6 @@
|
|||
</PH.Description>
|
||||
{{/if}}
|
||||
<PH.Description>
|
||||
This is the dashboard for your overall client count usages. Review Vault's
|
||||
<Hds::Link::Inline @href={{doc-link "/vault/docs/concepts/client-count"}}>client counting documentation
|
||||
</Hds::Link::Inline>for more information.
|
||||
|
||||
{{#if this.showCommunity}}
|
||||
<div class="has-top-padding-m">
|
||||
<Hds::Text::Display @tag="h3">Client counting period</Hds::Text::Display>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { task } from 'ember-concurrency';
|
|||
export default class ClientsPageHeaderComponent extends Component {
|
||||
@service download;
|
||||
@service namespace;
|
||||
@service router;
|
||||
@service store;
|
||||
@service version;
|
||||
|
||||
|
|
@ -120,9 +121,8 @@ export default class ClientsPageHeaderComponent extends Component {
|
|||
});
|
||||
|
||||
@action
|
||||
setExportFormat(evt) {
|
||||
const { value } = evt.target;
|
||||
this.exportFormat = value;
|
||||
refreshRoute() {
|
||||
this.router.refresh();
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
@ -136,6 +136,12 @@ export default class ClientsPageHeaderComponent extends Component {
|
|||
this.showEditModal = visible;
|
||||
}
|
||||
|
||||
@action
|
||||
setExportFormat(evt) {
|
||||
const { value } = evt.target;
|
||||
this.exportFormat = value;
|
||||
}
|
||||
|
||||
// LOCAL TEMPLATE HELPERS
|
||||
parseAPITimestamp = (timestamp, format) => {
|
||||
return parseAPITimestamp(timestamp, format);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,18 @@
|
|||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Hds::Text::Body class="has-top-margin-l has-bottom-margin-m" @tag="p" @size="100" @color="faint">The client list data below
|
||||
comes from the
|
||||
<Hds::Link::Inline
|
||||
@icon="docs-link"
|
||||
@iconPosition="trailing"
|
||||
@isHrefExternal={{true}}
|
||||
@href={{doc-link "/vault/api-docs/system/internal-counters#activity-export"}}
|
||||
>
|
||||
Activity Export API
|
||||
</Hds::Link::Inline>. It may take up to ten minutes for new client IDs to appear in the export data.
|
||||
</Hds::Text::Body>
|
||||
|
||||
<Clients::CountsCard data-test-card="Export activity data">
|
||||
<:subheader>
|
||||
<Clients::FilterToolbar
|
||||
|
|
@ -19,7 +31,7 @@
|
|||
<T.Tab @count={{or tableData.length "0"}} data-test-tab={{tabName}}>{{tabName}}</T.Tab>
|
||||
<T.Panel>
|
||||
<div class="has-top-margin-xs">
|
||||
{{#if this.anyFilters}}
|
||||
{{#if this.filtersAreApplied}}
|
||||
<Hds::Text::Body @tag="p" @color="faint" class="has-bottom-margin-xs" data-test-table-summary={{tabName}}>
|
||||
Summary:
|
||||
{{pluralize tableData.length "client"}}
|
||||
|
|
@ -38,11 +50,33 @@
|
|||
@setPageSize={{50}}
|
||||
@showPaginationSizeSelector={{true}}
|
||||
>
|
||||
|
||||
<:emptyState>
|
||||
<Hds::ApplicationState as |A|>
|
||||
<A.Header @title="No data found" />
|
||||
<A.Body @text="Clear or change filters to view client count data." />
|
||||
</Hds::ApplicationState>
|
||||
{{#if (and (eq tabName "Secret sync") (not this.flags.secretsSyncIsActivated))}}
|
||||
<Hds::ApplicationState as |A|>
|
||||
<A.Header @title="No secret sync clients" />
|
||||
<A.Body @text="No data is available because Secrets Sync has not been activated." />
|
||||
<A.Body>
|
||||
<Hds::Link::Standalone
|
||||
@icon="chevron-right"
|
||||
@iconPosition="trailing"
|
||||
@text="Activate Secrets Sync"
|
||||
@route="vault.cluster.sync.secrets.overview"
|
||||
/>
|
||||
</A.Body>
|
||||
</Hds::ApplicationState>
|
||||
{{else}}
|
||||
<Hds::ApplicationState as |A|>
|
||||
<A.Header @title="No data found" />
|
||||
<A.Body
|
||||
@text="Select another client type {{if
|
||||
this.filtersAreApplied
|
||||
'or update filters'
|
||||
''
|
||||
}} to view client count data."
|
||||
/>
|
||||
</Hds::ApplicationState>
|
||||
{{/if}}
|
||||
</:emptyState>
|
||||
</Clients::Table>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@
|
|||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import ActivityComponent, { Args } from '../activity';
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { HTMLElementEvent } from 'vault/forms';
|
||||
|
||||
import { filterIsSupported, filterTableData, type ActivityExportData } from 'core/utils/client-count-utils';
|
||||
import {
|
||||
filterIsSupported,
|
||||
filterTableData,
|
||||
type ClientFilterTypes,
|
||||
type ActivityExportData,
|
||||
} from 'core/utils/client-count-utils';
|
||||
import { service } from '@ember/service';
|
||||
import FlagsService from 'vault/services/flags';
|
||||
|
||||
// Define the base mapping to derive types from
|
||||
const CLIENT_TYPE_MAP = {
|
||||
|
|
@ -21,24 +27,30 @@ const CLIENT_TYPE_MAP = {
|
|||
// Dynamically derive the tab values from the mapping
|
||||
type ClientListTabs = (typeof CLIENT_TYPE_MAP)[keyof typeof CLIENT_TYPE_MAP];
|
||||
|
||||
export default class ClientsClientListPageComponent extends ActivityComponent {
|
||||
@tracked selectedTab: ClientListTabs;
|
||||
@tracked exportDataByTab;
|
||||
export interface Args {
|
||||
exportData: ActivityExportData[];
|
||||
onFilterChange: CallableFunction;
|
||||
filterQueryParams: Record<ClientFilterTypes, string>;
|
||||
}
|
||||
|
||||
export default class ClientsClientListPageComponent extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
|
||||
@tracked selectedTab: ClientListTabs = 'Entity';
|
||||
@tracked exportDataByTab: Record<ClientListTabs, ActivityExportData[]> = {
|
||||
Entity: [],
|
||||
'Non-entity': [],
|
||||
ACME: [],
|
||||
'Secret sync': [],
|
||||
};
|
||||
|
||||
constructor(owner: unknown, args: Args) {
|
||||
super(owner, args);
|
||||
|
||||
this.exportDataByTab = this.args.exportData.reduce(
|
||||
(obj, data) => {
|
||||
const clientLabel = CLIENT_TYPE_MAP[data.client_type];
|
||||
if (!obj[clientLabel]) {
|
||||
obj[clientLabel] = [];
|
||||
}
|
||||
obj[clientLabel].push(data);
|
||||
return obj;
|
||||
},
|
||||
{} as Record<ClientListTabs, ActivityExportData[]>
|
||||
);
|
||||
this.args.exportData.forEach((data: ActivityExportData) => {
|
||||
const tabName = CLIENT_TYPE_MAP[data.client_type];
|
||||
this.exportDataByTab[tabName].push(data);
|
||||
});
|
||||
|
||||
const firstTab = Object.keys(this.exportDataByTab)[0] as ClientListTabs;
|
||||
this.selectedTab = firstTab;
|
||||
|
|
@ -53,13 +65,18 @@ export default class ClientsClientListPageComponent extends ActivityComponent {
|
|||
return Object.keys(this.exportDataByTab) as ClientListTabs[];
|
||||
}
|
||||
|
||||
@action
|
||||
handleFilter(filters: Record<ClientFilterTypes, string>) {
|
||||
this.args.onFilterChange(filters);
|
||||
}
|
||||
|
||||
@action
|
||||
onClickTab(_event: HTMLElementEvent<HTMLInputElement>, idx: number) {
|
||||
const tab = this.tabs[idx];
|
||||
this.selectedTab = tab ?? this.tabs[0]!;
|
||||
}
|
||||
|
||||
get anyFilters() {
|
||||
get filtersAreApplied() {
|
||||
return (
|
||||
Object.keys(this.args.filterQueryParams).every((f) => filterIsSupported(f)) &&
|
||||
Object.values(this.args.filterQueryParams).some((v) => !!v)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@
|
|||
</div>
|
||||
|
||||
<div class="has-top-bottom-margin">
|
||||
<Hds::Text::Body @tag="p" @size="100">
|
||||
This is the dashboard for your overall client count usages. Review Vault's
|
||||
<Hds::Link::Inline @href={{doc-link "/vault/docs/concepts/client-count"}} @isHrefExternal={{true}}>
|
||||
client counting documentation</Hds::Link::Inline>
|
||||
for more information.
|
||||
</Hds::Text::Body>
|
||||
|
||||
{{#if (eq @activity.id "no-data")}}
|
||||
<Clients::NoData @config={{@config}} />
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import { parseAPITimestamp } from 'core/utils/date-formatters';
|
|||
import { filterVersionHistory } from 'core/utils/client-count-utils';
|
||||
|
||||
import type AdapterError from '@ember-data/adapter/error';
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type Store from '@ember-data/store';
|
||||
import type VersionService from 'vault/services/version';
|
||||
import type ClientsActivityModel from 'vault/models/clients/activity';
|
||||
import type ClientsConfigModel from 'vault/models/clients/config';
|
||||
|
|
@ -28,9 +26,7 @@ interface Args {
|
|||
}
|
||||
|
||||
export default class ClientsCountsPageComponent extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service declare readonly version: VersionService;
|
||||
@service declare readonly store: Store;
|
||||
|
||||
get formattedStartDate() {
|
||||
return this.args.startTimestamp ? parseAPITimestamp(this.args.startTimestamp, 'MMMM yyyy') : null;
|
||||
|
|
|
|||
|
|
@ -12,35 +12,15 @@
|
|||
@description="Select a month to view the client count per mount for that month."
|
||||
>
|
||||
<:subheader>
|
||||
<Hds::Form::Select::Base
|
||||
class="has-top-margin-m"
|
||||
aria-label="Month"
|
||||
name="month"
|
||||
{{on "input" this.selectMonth}}
|
||||
@width="200px"
|
||||
data-test-select="attribution-month"
|
||||
as |S|
|
||||
>
|
||||
<S.Options>
|
||||
<option value="">Select month</option>
|
||||
{{#each this.months as |m|}}
|
||||
<option value={{m.timestamp}} selected={{eq m.timestamp this.selectedMonth}}>{{m.display}}</option>
|
||||
{{/each}}
|
||||
</S.Options>
|
||||
</Hds::Form::Select::Base>
|
||||
|
||||
{{#if this.selectedMonth}}
|
||||
<div>
|
||||
<Hds::Text::Body class="has-top-margin-l has-bottom-margin-m" @tag="p" @size="100" @color="faint">Use the filters
|
||||
to view the clients attributed by path.
|
||||
</Hds::Text::Body>
|
||||
<Clients::FilterToolbar
|
||||
@dataset={{this.activityData}}
|
||||
@onFilter={{this.handleFilter}}
|
||||
@filterQueryParams={{@filterQueryParams}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
<Hds::Text::Body class="has-top-margin-l has-bottom-margin-m" @tag="p" @size="100" @color="faint">Use the filters to
|
||||
view the clients attributed by path.
|
||||
</Hds::Text::Body>
|
||||
<Clients::FilterToolbar
|
||||
@dataset={{this.activityData}}
|
||||
@onFilter={{this.handleFilter}}
|
||||
@filterQueryParams={{@filterQueryParams}}
|
||||
@dropdownMonths={{this.months}}
|
||||
/>
|
||||
</:subheader>
|
||||
|
||||
<:table>
|
||||
|
|
|
|||
|
|
@ -3,21 +3,20 @@
|
|||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import ActivityComponent from '../activity';
|
||||
import { service } from '@ember/service';
|
||||
import Component from '@glimmer/component';
|
||||
import { cached, tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { HTMLElementEvent } from 'vault/forms';
|
||||
import { parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
import { filterTableData, flattenMounts } from 'core/utils/client-count-utils';
|
||||
import { filterTableData, flattenMounts, type ClientFilterTypes } from 'core/utils/client-count-utils';
|
||||
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type ClientsActivityModel from 'vault/vault/models/clients/activity';
|
||||
|
||||
export default class ClientsOverviewPageComponent extends ActivityComponent {
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
export interface Args {
|
||||
activity: ClientsActivityModel;
|
||||
onFilterChange: CallableFunction;
|
||||
filterQueryParams: Record<ClientFilterTypes, string>;
|
||||
}
|
||||
|
||||
export default class ClientsOverviewPageComponent extends Component<Args> {
|
||||
@tracked selectedMonth = '';
|
||||
|
||||
@cached
|
||||
|
|
@ -25,13 +24,12 @@ export default class ClientsOverviewPageComponent extends ActivityComponent {
|
|||
return this.args.activity.byMonth?.map((m) => m?.new_clients) || [];
|
||||
}
|
||||
|
||||
@cached
|
||||
// Supplies data passed to dropdown filters
|
||||
// Supplies data passed to dropdown filters (except months which is computed below )
|
||||
get activityData() {
|
||||
// Find the namespace data for the selected month
|
||||
// If no month is selected the table displays all of the activity for the queried date range
|
||||
const namespaceData = this.selectedMonth
|
||||
? this.byMonthNewClients.find((m) => m.timestamp === this.selectedMonth)?.namespaces
|
||||
// If no month is selected the table displays all of the activity for the queried date range.
|
||||
const selectedMonth = this.args.filterQueryParams.month;
|
||||
const namespaceData = selectedMonth
|
||||
? this.byMonthNewClients.find((m) => m.timestamp === selectedMonth)?.namespaces
|
||||
: this.args.activity.byNamespace;
|
||||
|
||||
// Get the array of "mounts" data nested in each namespace object and flatten
|
||||
|
|
@ -40,14 +38,16 @@ export default class ClientsOverviewPageComponent extends ActivityComponent {
|
|||
|
||||
@cached
|
||||
get months() {
|
||||
return this.byMonthNewClients
|
||||
.reverse()
|
||||
.map((m) => ({ timestamp: m.timestamp, display: parseAPITimestamp(m.timestamp, 'MMMM yyyy') }));
|
||||
return this.byMonthNewClients.reverse().map((m) => m.timestamp);
|
||||
}
|
||||
|
||||
get tableData() {
|
||||
if (this.activityData?.length) {
|
||||
return filterTableData(this.activityData, this.args.filterQueryParams);
|
||||
// Reset the `month` query param because it determines which dataset (see this.activityData)
|
||||
// is passed to the table and is does not filter for key/value pairs within this dataset.
|
||||
const filters = { ...this.args.filterQueryParams };
|
||||
filters.month = '';
|
||||
return filterTableData(this.activityData, filters);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -62,11 +62,7 @@ export default class ClientsOverviewPageComponent extends ActivityComponent {
|
|||
}
|
||||
|
||||
@action
|
||||
selectMonth(e: HTMLElementEvent<HTMLInputElement>) {
|
||||
this.selectedMonth = e.target.value;
|
||||
// Reset filters when no month is selected
|
||||
if (this.selectedMonth === '') {
|
||||
this.resetFilters();
|
||||
}
|
||||
handleFilter(filters: Record<ClientFilterTypes, string>) {
|
||||
this.args.onFilterChange(filters);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@
|
|||
<B.Td data-test-table-data={{key}} class="white-space-nowrap">
|
||||
<Hds::Copy::Snippet @textToCopy={{value}} @color="secondary" />
|
||||
</B.Td>
|
||||
{{else if (and (eq key "clients") this.version.isEnterprise)}}
|
||||
<B.Td data-test-table-data={{key}} class="white-space-nowrap">
|
||||
<Hds::Link::Inline
|
||||
@route="vault.cluster.clients.counts.client-list"
|
||||
@query={{this.generateQueryParams B.data}}
|
||||
>{{value}}</Hds::Link::Inline>
|
||||
</B.Td>
|
||||
{{else}}
|
||||
<B.Td class="white-space-nowrap" data-test-table-data={{key}}>
|
||||
{{! stringify value if it is an array or object, otherwise render directly }}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ import { cached, tracked } from '@glimmer/tracking';
|
|||
import { paginate } from 'core/utils/paginate-list';
|
||||
import { next } from '@ember/runloop';
|
||||
|
||||
import type { ClientFilterTypes } from 'core/utils/client-count-utils';
|
||||
import { service } from '@ember/service';
|
||||
import VersionService from 'vault/services/version';
|
||||
|
||||
/**
|
||||
* @module ClientsTable
|
||||
* ClientsTable renders a paginated table for a passed dataset. HDS table components handle basic sorting
|
||||
|
|
@ -48,6 +52,8 @@ interface Args {
|
|||
}
|
||||
|
||||
export default class ClientsTable extends Component<Args> {
|
||||
@service declare readonly version: VersionService;
|
||||
|
||||
@tracked currentPage = 1;
|
||||
@tracked pageSize = 5; // Can be overridden by @setPageSize
|
||||
@tracked sortColumn = '';
|
||||
|
|
@ -132,4 +138,9 @@ export default class ClientsTable extends Component<Args> {
|
|||
|
||||
// TEMPLATE HELPERS
|
||||
isObject = (value: any) => typeof value === 'object';
|
||||
|
||||
generateQueryParams = (datum: Record<ClientFilterTypes, any>) => {
|
||||
const { namespace_path = '', mount_path = '', mount_type = '' } = datum;
|
||||
return { namespace_path, mount_path, mount_type };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import Controller from '@ember/controller';
|
||||
import { action, set } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { ClientFilters } from 'core/utils/client-count-utils';
|
||||
|
||||
import type { ClientsCountsRouteParams } from 'vault/routes/vault/cluster/clients/counts';
|
||||
|
|
@ -22,6 +23,17 @@ export default class ClientsCountsController extends Controller {
|
|||
namespace_path = '';
|
||||
mount_path = '';
|
||||
mount_type = '';
|
||||
// Tracked because clients/page/overview.ts has a getter that needs to recompute when this changes
|
||||
@tracked month = '';
|
||||
|
||||
get filterQueryParams() {
|
||||
return {
|
||||
namespace_path: this.namespace_path,
|
||||
mount_path: this.mount_path,
|
||||
mount_type: this.mount_type,
|
||||
month: this.month,
|
||||
};
|
||||
}
|
||||
|
||||
// using router.transitionTo to update the query params results in the model hook firing each time
|
||||
// this happens when the queryParams object is not added to the route or refreshModel is explicitly set to false
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export interface ClientsCountsRouteParams {
|
|||
namespace_path?: string;
|
||||
mount_path?: string;
|
||||
mount_type?: string;
|
||||
month?: string;
|
||||
}
|
||||
|
||||
interface ActivityAdapterQuery {
|
||||
|
|
@ -44,6 +45,7 @@ export default class ClientsCountsRoute extends Route {
|
|||
namespace_path: { refreshModel: false, replace: true },
|
||||
mount_path: { refreshModel: false, replace: true },
|
||||
mount_type: { refreshModel: false, replace: true },
|
||||
month: { refreshModel: false, replace: true },
|
||||
};
|
||||
|
||||
beforeModel() {
|
||||
|
|
@ -92,11 +94,12 @@ export default class ClientsCountsRoute extends Route {
|
|||
resetController(controller: ClientsCountsController, isExiting: boolean) {
|
||||
if (isExiting) {
|
||||
controller.setProperties({
|
||||
start_time: undefined,
|
||||
end_time: undefined,
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
namespace_path: '',
|
||||
mount_path: '',
|
||||
mount_type: '',
|
||||
month: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import type Store from '@ember-data/store';
|
|||
export default class ClientsCountsClientListRoute extends Route {
|
||||
@service declare readonly store: Store;
|
||||
|
||||
// TODO - will there always be a start/end timestamp? What's the error scenario
|
||||
async fetchAndFormatExportData(startTimestamp: string | undefined, endTimestamp: string | undefined) {
|
||||
const adapter = this.store.adapterFor('clients/activity');
|
||||
let exportData, exportError;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
@icon="docs-link"
|
||||
@iconPosition="trailing"
|
||||
@text="Client Export Documentation"
|
||||
@href={{doc-link "/vault/api-docs/secret/databases"}}
|
||||
@isHrefExternal={{true}}
|
||||
@href={{doc-link "/vault/api-docs/system/internal-counters#activity-export"}}
|
||||
/>
|
||||
</Hds::Text::Body>
|
||||
{{/if}}
|
||||
|
|
@ -28,10 +29,6 @@
|
|||
<Clients::Page::ClientList
|
||||
@exportData={{this.model.exportData}}
|
||||
@onFilterChange={{this.countsController.updateQueryParams}}
|
||||
@filterQueryParams={{hash
|
||||
namespace_path=this.countsController.namespace_path
|
||||
mount_path=this.countsController.mount_path
|
||||
mount_type=this.countsController.mount_type
|
||||
}}
|
||||
@filterQueryParams={{this.countsController.filterQueryParams}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
|
@ -6,9 +6,5 @@
|
|||
<Clients::Page::Overview
|
||||
@activity={{this.model.activity}}
|
||||
@onFilterChange={{this.countsController.updateQueryParams}}
|
||||
@filterQueryParams={{hash
|
||||
namespace_path=this.countsController.namespace_path
|
||||
mount_path=this.countsController.mount_path
|
||||
mount_type=this.countsController.mount_type
|
||||
}}
|
||||
@filterQueryParams={{this.countsController.filterQueryParams}}
|
||||
/>
|
||||
|
|
@ -3,8 +3,10 @@
|
|||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
import { isSameMonthUTC, parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
import { compareAsc, isWithinInterval } from 'date-fns';
|
||||
import { ROOT_NAMESPACE } from 'vault/services/namespace';
|
||||
import { sanitizePath } from './sanitize-path';
|
||||
|
||||
import type ClientsVersionHistoryModel from 'vault/vault/models/clients/version-history';
|
||||
|
||||
|
|
@ -31,6 +33,8 @@ export enum ClientFilters {
|
|||
NAMESPACE = 'namespace_path',
|
||||
MOUNT_PATH = 'mount_path',
|
||||
MOUNT_TYPE = 'mount_type',
|
||||
// this filter/query param does not map to a key in either API response and is handled ~special~
|
||||
MONTH = 'month',
|
||||
}
|
||||
|
||||
export type ClientFilterTypes = (typeof ClientFilters)[keyof typeof ClientFilters];
|
||||
|
|
@ -126,7 +130,8 @@ export const formatByNamespace = (namespaceArray: NamespaceObject[] | null): ByN
|
|||
label: m.mount_path,
|
||||
namespace_path: nsLabel,
|
||||
mount_path: m.mount_path,
|
||||
mount_type: m.mount_type,
|
||||
// sanitized so it matches activity export data because mount_type there does NOT have a trailing slash
|
||||
mount_type: sanitizePath(m.mount_type),
|
||||
...destructureClientCounts(m.counts),
|
||||
}));
|
||||
}
|
||||
|
|
@ -185,17 +190,24 @@ export function filterTableData(
|
|||
}
|
||||
|
||||
const matchesFilter = (
|
||||
datum: MountClients | ActivityExportData,
|
||||
datum: ActivityExportData | MountClients,
|
||||
filterKey: ClientFilterTypes,
|
||||
filterValue: string
|
||||
) => {
|
||||
// Only ActivityExportData data is ever filtered by 'client_first_used_time' (not MountClients)
|
||||
if (filterKey === ClientFilters.MONTH) {
|
||||
return 'client_first_used_time' in datum
|
||||
? isSameMonthUTC(datum.client_first_used_time, filterValue)
|
||||
: false;
|
||||
}
|
||||
|
||||
const datumValue = datum[filterKey];
|
||||
// The API returns and empty string as the namespace_path for the "root" namespace.
|
||||
// When a user selects "root" as a namespace filter we need to match the datum value
|
||||
// as either an empty string (for the activity export data) OR as "root"
|
||||
// (the by_namespace data is serialized to make "root" the namespace_path).
|
||||
if (filterKey === 'namespace_path' && filterValue === 'root') {
|
||||
return datumValue === '' || datumValue === filterValue;
|
||||
if (filterKey === ClientFilters.NAMESPACE && filterValue === 'root') {
|
||||
return datumValue === ROOT_NAMESPACE || datumValue === filterValue;
|
||||
}
|
||||
return datumValue === filterValue;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { format, parseISO } from 'date-fns';
|
||||
|
||||
export const datetimeLocalStringFormat = "yyyy-MM-dd'T'HH:mm";
|
||||
|
||||
export const ARRAY_OF_MONTHS = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
|
||||
// convert API timestamp ( '2021-03-21T00:00:00Z' ) to date object, optionally format
|
||||
export const parseAPITimestamp = (timestamp, style) => {
|
||||
if (typeof timestamp !== 'string') return timestamp;
|
||||
const date = parseISO(timestamp.split('T')[0]);
|
||||
if (!style) return date;
|
||||
return format(date, style);
|
||||
};
|
||||
64
ui/lib/core/addon/utils/date-formatters.ts
Normal file
64
ui/lib/core/addon/utils/date-formatters.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { format, parse, parseISO } from 'date-fns';
|
||||
import isValid from 'date-fns/isValid';
|
||||
|
||||
export const datetimeLocalStringFormat = "yyyy-MM-dd'T'HH:mm";
|
||||
|
||||
export const ARRAY_OF_MONTHS = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
|
||||
// convert API timestamp ( '2021-03-21T00:00:00Z' ) to date object, optionally format
|
||||
export const parseAPITimestamp = (timestamp: string, style?: string): Date | string | null => {
|
||||
if (!timestamp || typeof timestamp !== 'string') return null;
|
||||
|
||||
if (!style) {
|
||||
// If no style, return a date object in UTC
|
||||
const parsed = parseISO(timestamp) as Date;
|
||||
return isValid(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
// Otherwise format it as a calendar date that is timezone agnostic.
|
||||
const yearMonthDay = timestamp.split('T')[0] ?? '';
|
||||
const date = parse(yearMonthDay, 'yyyy-MM-dd', new Date()); // 'yyyy-MM-dd' lets parse() know the format of yearMonthDay
|
||||
return format(date, style) as string;
|
||||
};
|
||||
|
||||
export const buildISOTimestamp = (args: { monthIdx: number; year: number; isEndDate: boolean }) => {
|
||||
const { monthIdx, year, isEndDate } = args;
|
||||
// passing `0` for the "day" arg to Date.UTC() returns the last day of the previous month
|
||||
// which is why the monthIdx is increased by one for end dates.
|
||||
// Date.UTC() also returns December if -1 is passed (which happens when January is selected)
|
||||
const utc = isEndDate
|
||||
? new Date(Date.UTC(year, monthIdx + 1, 0, 23, 59, 59))
|
||||
: new Date(Date.UTC(year, monthIdx, 1));
|
||||
|
||||
// remove milliseconds to return a UTC timestamp that matches the API
|
||||
// e.g. "2025-05-01T00:00:00Z" or "2025-09-30T23:59:59Z"
|
||||
return utc.toISOString().replace('.000', '');
|
||||
};
|
||||
|
||||
export const isSameMonthUTC = (timestampA: string, timestampB: string): boolean => {
|
||||
const dateA = parseAPITimestamp(timestampA) as Date;
|
||||
const dateB = parseAPITimestamp(timestampB) as Date;
|
||||
if (isValid(dateA) && isValid(dateB)) {
|
||||
// Compare in UTC as any date-fns comparisons will be in localized timezones!
|
||||
return dateA.getUTCFullYear() === dateB.getUTCFullYear() && dateA.getUTCMonth() === dateB.getUTCMonth();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
import {
|
||||
addMonths,
|
||||
differenceInCalendarMonths,
|
||||
endOfMonth,
|
||||
formatRFC3339,
|
||||
fromUnixTime,
|
||||
|
|
@ -13,7 +12,6 @@ import {
|
|||
isBefore,
|
||||
isSameMonth,
|
||||
isWithinInterval,
|
||||
startOfMonth,
|
||||
subMonths,
|
||||
} from 'date-fns';
|
||||
import { parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
|
|
@ -30,7 +28,7 @@ export const STATIC_NOW = new Date('2024-01-25T23:59:59Z');
|
|||
export const STATIC_PREVIOUS_MONTH = new Date('2023-12-25T23:59:59Z');
|
||||
const COUNTS_START = subMonths(STATIC_NOW, 12); // user started Vault cluster on 2023-01-25
|
||||
// upgrade happened 2 month after license start
|
||||
export const UPGRADE_DATE = addMonths(LICENSE_START, 2); // monthly attribution added
|
||||
export const UPGRADE_DATE = new Date('2023-09-01T00:00:00Z'); // monthly attribution added
|
||||
|
||||
// exported so that tests not using this scenario can use the same response
|
||||
export const CONFIG_RESPONSE = {
|
||||
|
|
@ -67,7 +65,8 @@ function generateMountBlock(path, counts) {
|
|||
return obj;
|
||||
}, {});
|
||||
// this logic is random nonsense just to have some mounts be "deleted"
|
||||
const setMountType = () => (counts.clients % 5 <= 1 ? 'deleted mount' : path.split('/')[1]);
|
||||
// "mount_type" from /sys/internal/counters/activity ends in a trailing slash (but in the export activity data it does not)
|
||||
const setMountType = () => (counts.clients % 5 <= 1 ? 'deleted mount' : `${path.split('/')[1]}/`);
|
||||
return {
|
||||
mount_path: path,
|
||||
mount_type: setMountType(),
|
||||
|
|
@ -100,13 +99,13 @@ function generateNamespaceBlock(idx = 0, isLowerCounts = false, ns, skipCounts =
|
|||
|
||||
// each mount type generates a different type of client
|
||||
return [
|
||||
generateMountBlock(`auth/token/${idx}`, {
|
||||
generateMountBlock(`auth/token/${idx}/`, {
|
||||
clients: non_entity_clients + entity_clients,
|
||||
non_entity_clients,
|
||||
entity_clients,
|
||||
}),
|
||||
generateMountBlock(`secrets/kv/${idx}`, { clients: secret_syncs, secret_syncs }),
|
||||
generateMountBlock(`acme/pki/${idx}`, { clients: acme_clients, acme_clients }),
|
||||
generateMountBlock(`secrets/kv/${idx}/`, { clients: secret_syncs, secret_syncs }),
|
||||
generateMountBlock(`acme/pki/${idx}/`, { clients: acme_clients, acme_clients }),
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -122,19 +121,23 @@ function generateNamespaceBlock(idx = 0, isLowerCounts = false, ns, skipCounts =
|
|||
function generateMonths(startDate, endDate, namespaces) {
|
||||
const startDateObject = parseAPITimestamp(startDate);
|
||||
const endDateObject = parseAPITimestamp(endDate);
|
||||
const numberOfMonths = differenceInCalendarMonths(endDateObject, startDateObject) + 1;
|
||||
const startMonth = startDateObject.getUTCMonth() + startDateObject.getUTCFullYear() * 12;
|
||||
const endMonth = endDateObject.getUTCMonth() + endDateObject.getUTCFullYear() * 12;
|
||||
const numberOfMonths = endMonth - startMonth + 1;
|
||||
const months = [];
|
||||
|
||||
// only generate monthly block if queried dates span or follow upgrade to 1.10
|
||||
const upgradeWithin = isWithinInterval(UPGRADE_DATE, { start: startDateObject, end: endDateObject });
|
||||
const upgradeAfter = isAfter(startDateObject, UPGRADE_DATE);
|
||||
|
||||
if (upgradeWithin || upgradeAfter) {
|
||||
for (let i = 0; i < numberOfMonths; i++) {
|
||||
const month = addMonths(startOfMonth(startDateObject), i);
|
||||
const month = new Date(Date.UTC(startDateObject.getUTCFullYear(), startDateObject.getUTCMonth() + i));
|
||||
|
||||
const hasNoData = isBefore(month, UPGRADE_DATE) && !isSameMonth(month, UPGRADE_DATE);
|
||||
if (hasNoData) {
|
||||
months.push({
|
||||
timestamp: formatRFC3339(month),
|
||||
timestamp: month.toISOString(),
|
||||
counts: null,
|
||||
namespaces: null,
|
||||
new_clients: null,
|
||||
|
|
@ -145,7 +148,7 @@ function generateMonths(startDate, endDate, namespaces) {
|
|||
const monthNs = namespaces.map((ns, idx) => generateNamespaceBlock(idx, false, ns));
|
||||
const newClients = namespaces.map((ns, idx) => generateNamespaceBlock(idx, true, ns));
|
||||
months.push({
|
||||
timestamp: formatRFC3339(month),
|
||||
timestamp: month.toISOString(),
|
||||
counts: getTotalCounts(monthNs),
|
||||
namespaces: monthNs.sort((a, b) => b.counts.clients - a.counts.clients),
|
||||
new_clients: {
|
||||
|
|
@ -155,7 +158,6 @@ function generateMonths(startDate, endDate, namespaces) {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
return months;
|
||||
}
|
||||
|
||||
|
|
@ -295,6 +297,7 @@ export default function (server) {
|
|||
const activities = schema['clients/activities'];
|
||||
const namespace = req.requestHeaders['X-Vault-Namespace'];
|
||||
let { start_time, end_time } = req.queryParams;
|
||||
|
||||
if (!start_time) {
|
||||
// if there are no date query params, the activity log default behavior
|
||||
// queries from the builtin license start timestamp to the current month
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@ import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
|||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { CHARTS, CLIENT_COUNT, FILTERS } from 'vault/tests/helpers/clients/client-count-selectors';
|
||||
import timestamp from 'core/utils/timestamp';
|
||||
import { format } from 'date-fns';
|
||||
import { ACTIVITY_RESPONSE_STUB } from 'vault/tests/helpers/clients/client-count-helpers';
|
||||
import {
|
||||
ACTIVITY_EXPORT_STUB,
|
||||
ACTIVITY_RESPONSE_STUB,
|
||||
} from 'vault/tests/helpers/clients/client-count-helpers';
|
||||
import { ClientFilters, flattenMounts } from 'core/utils/client-count-utils';
|
||||
import { parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
|
||||
module('Acceptance | clients | overview', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
|
@ -29,8 +32,10 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
|
||||
hooks.beforeEach(async function () {
|
||||
sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW));
|
||||
// These tests use the clientsHandler which dynamically generates activity data, used for asserting date querying, etc
|
||||
clientsHandler(this.server);
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.version = this.owner.lookup('service:version');
|
||||
});
|
||||
|
||||
test('it should hide secrets sync stats when feature is NOT on license', async function (assert) {
|
||||
|
|
@ -45,59 +50,45 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
assert.dom(CHARTS.legend).hasText('Entity clients Non-entity clients ACME clients');
|
||||
});
|
||||
|
||||
// These tests use the clientsHandler which dynamically generates activity data, used for asserting date querying, etc
|
||||
module('dynamic data', function (hooks) {
|
||||
test('it should render charts', async function (assert) {
|
||||
await login();
|
||||
await visit('/vault/clients/counts/overview');
|
||||
assert
|
||||
.dom(`${GENERAL.flashMessage}.is-info`)
|
||||
.includesText(
|
||||
'counts returned in this usage period are an estimate',
|
||||
'Shows warning from API about client count estimations'
|
||||
);
|
||||
assert
|
||||
.dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
|
||||
.hasText('July 2023', 'start month is correctly parsed from license');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.dateRange.dateDisplay('end'))
|
||||
.hasText('January 2024', 'end month is correctly parsed from STATIC_NOW');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.exists('Shows running totals with monthly breakdown charts');
|
||||
assert
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends for selected billing period')} ${CHARTS.xAxisLabel}`)
|
||||
.hasText('7/23', 'x-axis labels start with billing start date');
|
||||
assert.dom(CHARTS.xAxisLabel).exists({ count: 7 }, 'chart months matches query');
|
||||
});
|
||||
|
||||
module('community', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
// stub secrets sync being activated
|
||||
this.server.get('/sys/activation-flags', function () {
|
||||
return {
|
||||
data: {
|
||||
activated: ['secrets-sync'],
|
||||
unactivated: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
this.activity = await this.store.findRecord('clients/activity', 'some-activity-id');
|
||||
this.mostRecentMonth = this.activity.byMonth[this.activity.byMonth.length - 1];
|
||||
this.version.type = 'community';
|
||||
await login();
|
||||
return visit('/vault/clients/counts/overview');
|
||||
});
|
||||
|
||||
test('it should render charts', async function (assert) {
|
||||
assert
|
||||
.dom(`${GENERAL.flashMessage}.is-info`)
|
||||
.includesText(
|
||||
'counts returned in this usage period are an estimate',
|
||||
'Shows warning from API about client count estimations'
|
||||
);
|
||||
assert
|
||||
.dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
|
||||
.hasText('July 2023', 'billing start month is correctly parsed from license');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.dateRange.dateDisplay('end'))
|
||||
.hasText('January 2024', 'billing start month is correctly parsed from license');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.exists('Shows running totals with monthly breakdown charts');
|
||||
assert
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends for selected billing period')} ${CHARTS.xAxisLabel}`)
|
||||
.hasText('7/23', 'x-axis labels start with billing start date');
|
||||
assert.dom(CHARTS.xAxisLabel).exists({ count: 7 }, 'chart months matches query');
|
||||
return await visit('/vault/clients/counts/overview');
|
||||
});
|
||||
|
||||
test('it should update charts when querying date ranges', async function (assert) {
|
||||
// query for single, historical month with no new counts (July 2023), which means there is no monthly breakdown
|
||||
const service = this.owner.lookup('service:version');
|
||||
service.type = 'community';
|
||||
|
||||
const licenseStartMonth = format(LICENSE_START, 'yyyy-MM');
|
||||
const upgradeMonth = format(UPGRADE_DATE, 'yyyy-MM');
|
||||
const endMonth = format(STATIC_PREVIOUS_MONTH, 'yyyy-MM');
|
||||
// Use parseAPITimestamp because we want a date string that is timezone agnostic (so it stays in UTC)
|
||||
const clientCountingStartDate = parseAPITimestamp(LICENSE_START.toISOString(), 'yyyy-MM');
|
||||
const upgradeMonth = parseAPITimestamp(UPGRADE_DATE.toISOString(), 'yyyy-MM');
|
||||
const endMonth = parseAPITimestamp(STATIC_PREVIOUS_MONTH.toISOString(), 'yyyy-MM');
|
||||
await click(CLIENT_COUNT.dateRange.edit);
|
||||
await fillIn(CLIENT_COUNT.dateRange.editDate('start'), licenseStartMonth);
|
||||
await fillIn(CLIENT_COUNT.dateRange.editDate('end'), licenseStartMonth);
|
||||
|
||||
await fillIn(CLIENT_COUNT.dateRange.editDate('start'), clientCountingStartDate);
|
||||
await fillIn(CLIENT_COUNT.dateRange.editDate('end'), clientCountingStartDate);
|
||||
await click(GENERAL.submitButton);
|
||||
assert
|
||||
.dom(CLIENT_COUNT.usageStats('Client usage'))
|
||||
|
|
@ -111,9 +102,10 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
await fillIn(CLIENT_COUNT.dateRange.editDate('start'), upgradeMonth);
|
||||
await fillIn(CLIENT_COUNT.dateRange.editDate('end'), endMonth);
|
||||
await click(GENERAL.submitButton);
|
||||
|
||||
assert
|
||||
.dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
|
||||
.hasText('September 2023', 'billing start month is correctly parsed from license');
|
||||
.hasText('September 2023', 'client count start month is correctly parsed from start query');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.exists('Shows running totals with monthly breakdown charts');
|
||||
|
|
@ -139,10 +131,10 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
|
||||
assert
|
||||
.dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
|
||||
.hasText('September 2023', 'billing start month is correctly parsed from license');
|
||||
.hasText('September 2023', 'it displays correct start time');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.dateRange.dateDisplay('end'))
|
||||
.hasText('December 2023', 'billing start month is correctly parsed from license');
|
||||
.hasText('December 2023', 'it displays correct end time');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.exists('Shows running totals with monthly breakdown charts');
|
||||
|
|
@ -153,6 +145,12 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
.dom(xAxisLabels[xAxisLabels.length - 1])
|
||||
.hasText('12/23', 'x-axis labels end with queried end month');
|
||||
});
|
||||
|
||||
test('it does not render client list links for community versions', async function (assert) {
|
||||
assert
|
||||
.dom(`${GENERAL.tableData(0, 'clients')} a`)
|
||||
.doesNotExist('client counts do not render as hyperlinks');
|
||||
});
|
||||
});
|
||||
|
||||
// * FILTERING ASSERTIONS
|
||||
|
|
@ -176,9 +174,11 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
test('it filters attribution table when filters are applied', async function (assert) {
|
||||
const url = '/vault/clients/counts/overview';
|
||||
const topMount = flattenMounts(this.staticMostRecentMonth.new_clients.namespaces)[0];
|
||||
const timestamp = this.staticMostRecentMonth.timestamp;
|
||||
const { namespace_path, mount_type, mount_path } = topMount;
|
||||
assert.strictEqual(currentURL(), url, 'URL does not contain query params');
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.staticMostRecentMonth.timestamp);
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.MONTH));
|
||||
await click(FILTERS.dropdownItem(timestamp));
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.NAMESPACE));
|
||||
await click(FILTERS.dropdownItem(namespace_path));
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH));
|
||||
|
|
@ -187,12 +187,12 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
await click(FILTERS.dropdownItem(mount_type));
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`${url}?mount_path=${encodeURIComponent(
|
||||
`${url}?month=${encodeURIComponent(timestamp)}&mount_path=${encodeURIComponent(
|
||||
mount_path
|
||||
)}&mount_type=${mount_type}&namespace_path=${namespace_path}`,
|
||||
'url query params match filters'
|
||||
);
|
||||
assert.dom(FILTERS.tag()).exists({ count: 3 }, '3 filter tags render');
|
||||
assert.dom(FILTERS.tag()).exists({ count: 4 }, '4 filter tags render');
|
||||
assert.dom(GENERAL.tableRow()).exists({ count: 1 }, 'it only renders the filtered table row');
|
||||
assert.dom(GENERAL.tableData(0, 'namespace_path')).hasText(namespace_path);
|
||||
assert.dom(GENERAL.tableData(0, 'mount_type')).hasText(mount_type);
|
||||
|
|
@ -202,8 +202,10 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
test('it updates table when filters are cleared', async function (assert) {
|
||||
const url = '/vault/clients/counts/overview';
|
||||
const mounts = flattenMounts(this.staticMostRecentMonth.new_clients.namespaces);
|
||||
const timestamp = this.staticMostRecentMonth.timestamp;
|
||||
const { namespace_path, mount_type, mount_path } = mounts[0];
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.staticMostRecentMonth.timestamp);
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.MONTH));
|
||||
await click(FILTERS.dropdownItem(timestamp));
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.NAMESPACE));
|
||||
await click(FILTERS.dropdownItem(namespace_path));
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH));
|
||||
|
|
@ -214,13 +216,15 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
await click(FILTERS.clearTag(namespace_path));
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`${url}?mount_path=${encodeURIComponent(mount_path)}&mount_type=${mount_type}`,
|
||||
`${url}?month=${encodeURIComponent(timestamp)}&mount_path=${encodeURIComponent(
|
||||
mount_path
|
||||
)}&mount_type=${mount_type}`,
|
||||
'url does not have namespace_path query param'
|
||||
);
|
||||
assert.dom(GENERAL.tableRow()).exists({ count: 2 }, 'it renders 2 data rows that match filters');
|
||||
assert.dom(GENERAL.tableData(0, 'namespace_path')).hasText('root');
|
||||
assert.dom(GENERAL.tableData(0, 'mount_type')).hasText(mount_type);
|
||||
assert.dom(GENERAL.tableData(1, 'namespace_path')).hasText('ns1');
|
||||
assert.dom(GENERAL.tableData(1, 'namespace_path')).hasText('ns1/');
|
||||
assert.dom(GENERAL.tableData(1, 'mount_type')).hasText(mount_type);
|
||||
assert.dom(GENERAL.tableData(1, 'mount_path')).hasText(mount_path);
|
||||
await click(GENERAL.button('Clear filters'));
|
||||
|
|
@ -230,11 +234,13 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
.exists({ count: mounts.length }, 'it renders all data when filters are cleared');
|
||||
});
|
||||
|
||||
test('it clears query params when month is unselected', async function (assert) {
|
||||
test('it renders client counts for full billing period when month is unselected', async function (assert) {
|
||||
const url = '/vault/clients/counts/overview';
|
||||
const mounts = flattenMounts(this.staticMostRecentMonth.new_clients.namespaces);
|
||||
const timestamp = this.staticMostRecentMonth.timestamp;
|
||||
const { namespace_path, mount_type, mount_path } = mounts[0];
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.staticMostRecentMonth.timestamp);
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.MONTH));
|
||||
await click(FILTERS.dropdownItem(timestamp));
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.NAMESPACE));
|
||||
await click(FILTERS.dropdownItem(namespace_path));
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH));
|
||||
|
|
@ -243,13 +249,48 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
await click(FILTERS.dropdownItem(mount_type));
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`${url}?mount_path=${encodeURIComponent(
|
||||
`${url}?month=${encodeURIComponent(timestamp)}&mount_path=${encodeURIComponent(
|
||||
mount_path
|
||||
)}&mount_type=${mount_type}&namespace_path=${namespace_path}`,
|
||||
'url query params match filters'
|
||||
);
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), '');
|
||||
assert.strictEqual(currentURL(), url, 'url query params clear when month is not selected');
|
||||
await click(FILTERS.clearTag('September 2023'));
|
||||
assert
|
||||
.dom(GENERAL.tableData(0, 'clients'))
|
||||
.hasText('4003', 'the table renders clients for the full billing period (not September)');
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`${url}?mount_path=${encodeURIComponent(
|
||||
mount_path
|
||||
)}&mount_type=${mount_type}&namespace_path=${namespace_path}`,
|
||||
'url does not include month'
|
||||
);
|
||||
});
|
||||
|
||||
test('enterprise: it navigates to the client list page when clicking the client count hyperlink', async function (assert) {
|
||||
const mockResponse = {
|
||||
status: 200,
|
||||
ok: true,
|
||||
text: () => Promise.resolve(ACTIVITY_EXPORT_STUB.trim()),
|
||||
};
|
||||
const adapter = this.store.adapterFor('clients/activity');
|
||||
const exportDataStub = sinon.stub(adapter, 'exportData');
|
||||
exportDataStub.resolves(mockResponse);
|
||||
const timestamp = this.staticMostRecentMonth.timestamp;
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.MONTH));
|
||||
await click(FILTERS.dropdownItem(timestamp));
|
||||
await click(`${GENERAL.tableData(0, 'clients')} a`);
|
||||
const url = '/vault/clients/counts/client-list';
|
||||
const monthQp = encodeURIComponent(timestamp);
|
||||
const ns = encodeURIComponent('ns1/');
|
||||
const mPath = encodeURIComponent('auth/userpass/0/');
|
||||
const mType = 'userpass';
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`${url}?month=${monthQp}&mount_path=${mPath}&mount_type=${mType}&namespace_path=${ns}`,
|
||||
'url query params match filters'
|
||||
);
|
||||
exportDataStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
by_namespace: [
|
||||
{
|
||||
namespace_id: 'e67m31',
|
||||
namespace_path: 'ns1',
|
||||
namespace_path: 'ns1/',
|
||||
counts: {
|
||||
acme_clients: 5699,
|
||||
clients: 18903,
|
||||
|
|
@ -45,8 +45,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_type: 'userpass',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 8394,
|
||||
|
|
@ -56,8 +56,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_type: 'pki',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki/',
|
||||
counts: {
|
||||
acme_clients: 5699,
|
||||
clients: 5699,
|
||||
|
|
@ -67,8 +67,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_type: 'kv',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 4810,
|
||||
|
|
@ -91,8 +91,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_type: 'userpass',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 8091,
|
||||
|
|
@ -102,8 +102,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_type: 'kv',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 4290,
|
||||
|
|
@ -113,8 +113,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_type: 'pki',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki/',
|
||||
counts: {
|
||||
acme_clients: 4003,
|
||||
clients: 4003,
|
||||
|
|
@ -155,8 +155,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_type: 'userpass',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 200,
|
||||
|
|
@ -166,8 +166,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_type: 'pki',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki/',
|
||||
counts: {
|
||||
acme_clients: 100,
|
||||
clients: 100,
|
||||
|
|
@ -177,8 +177,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_type: 'kv',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 100,
|
||||
|
|
@ -211,8 +211,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_type: 'userpass',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 200,
|
||||
|
|
@ -222,8 +222,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_type: 'pki',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki/',
|
||||
counts: {
|
||||
acme_clients: 100,
|
||||
clients: 100,
|
||||
|
|
@ -233,8 +233,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_type: 'kv',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 100,
|
||||
|
|
@ -270,8 +270,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_type: 'userpass',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 200,
|
||||
|
|
@ -281,8 +281,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_type: 'pki',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki/',
|
||||
counts: {
|
||||
acme_clients: 100,
|
||||
clients: 100,
|
||||
|
|
@ -292,8 +292,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_type: 'kv',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 100,
|
||||
|
|
@ -322,7 +322,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
namespaces: [
|
||||
{
|
||||
namespace_id: 'e67m31',
|
||||
namespace_path: 'ns1',
|
||||
namespace_path: 'ns1/',
|
||||
counts: {
|
||||
acme_clients: 934,
|
||||
clients: 1981,
|
||||
|
|
@ -332,7 +332,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0/',
|
||||
counts: {
|
||||
acme_clients: 934,
|
||||
clients: 934,
|
||||
|
|
@ -342,7 +342,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 890,
|
||||
|
|
@ -352,7 +352,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 157,
|
||||
|
|
@ -375,7 +375,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0/',
|
||||
counts: {
|
||||
acme_clients: 994,
|
||||
clients: 994,
|
||||
|
|
@ -385,7 +385,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 872,
|
||||
|
|
@ -395,7 +395,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 81,
|
||||
|
|
@ -428,8 +428,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_type: 'pki',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki/',
|
||||
counts: {
|
||||
acme_clients: 91,
|
||||
clients: 91,
|
||||
|
|
@ -439,8 +439,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_type: 'userpass',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 75,
|
||||
|
|
@ -450,8 +450,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_type: 'kv',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 25,
|
||||
|
|
@ -464,7 +464,7 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
{
|
||||
namespace_id: 'e67m31',
|
||||
namespace_path: 'ns1',
|
||||
namespace_path: 'ns1/',
|
||||
counts: {
|
||||
acme_clients: 53,
|
||||
clients: 173,
|
||||
|
|
@ -474,8 +474,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
mounts: [
|
||||
{
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_type: 'userpass',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 96,
|
||||
|
|
@ -485,8 +485,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_type: 'pki',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki/',
|
||||
counts: {
|
||||
acme_clients: 53,
|
||||
clients: 53,
|
||||
|
|
@ -496,8 +496,8 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||
},
|
||||
},
|
||||
{
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_type: 'kv',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv/',
|
||||
counts: {
|
||||
acme_clients: 0,
|
||||
clients: 24,
|
||||
|
|
@ -561,7 +561,7 @@ export const MIXED_ACTIVITY_RESPONSE_STUB = {
|
|||
non_entity_clients: 0,
|
||||
secret_syncs: 0,
|
||||
},
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
},
|
||||
],
|
||||
|
|
@ -613,7 +613,7 @@ export const MIXED_ACTIVITY_RESPONSE_STUB = {
|
|||
non_entity_clients: 0,
|
||||
secret_syncs: 0,
|
||||
},
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
},
|
||||
],
|
||||
|
|
@ -658,7 +658,7 @@ export const MIXED_ACTIVITY_RESPONSE_STUB = {
|
|||
non_entity_clients: 0,
|
||||
secret_syncs: 0,
|
||||
},
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
},
|
||||
],
|
||||
|
|
@ -683,7 +683,7 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
},
|
||||
by_namespace: [
|
||||
{
|
||||
label: 'ns1',
|
||||
label: 'ns1/',
|
||||
acme_clients: 5699,
|
||||
clients: 18903,
|
||||
entity_clients: 4256,
|
||||
|
|
@ -691,10 +691,10 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 4810,
|
||||
mounts: [
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
namespace_path: 'ns1',
|
||||
namespace_path: 'ns1/',
|
||||
acme_clients: 0,
|
||||
clients: 8394,
|
||||
entity_clients: 4256,
|
||||
|
|
@ -702,10 +702,10 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki',
|
||||
namespace_path: 'ns1',
|
||||
namespace_path: 'ns1/',
|
||||
acme_clients: 5699,
|
||||
clients: 5699,
|
||||
entity_clients: 0,
|
||||
|
|
@ -713,10 +713,10 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv',
|
||||
namespace_path: 'ns1',
|
||||
namespace_path: 'ns1/',
|
||||
acme_clients: 0,
|
||||
clients: 4810,
|
||||
entity_clients: 0,
|
||||
|
|
@ -734,8 +734,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 4290,
|
||||
mounts: [
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
namespace_path: 'root',
|
||||
acme_clients: 0,
|
||||
|
|
@ -745,8 +745,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv',
|
||||
namespace_path: 'root',
|
||||
acme_clients: 0,
|
||||
|
|
@ -756,8 +756,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 4290,
|
||||
},
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki',
|
||||
namespace_path: 'root',
|
||||
acme_clients: 4003,
|
||||
|
|
@ -795,9 +795,9 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 100,
|
||||
mounts: [
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
namespace_path: 'root',
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
acme_clients: 0,
|
||||
clients: 200,
|
||||
|
|
@ -806,9 +806,9 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
namespace_path: 'root',
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki',
|
||||
acme_clients: 100,
|
||||
clients: 100,
|
||||
|
|
@ -817,9 +817,9 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
namespace_path: 'root',
|
||||
mount_path: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv',
|
||||
acme_clients: 0,
|
||||
clients: 100,
|
||||
|
|
@ -847,8 +847,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 100,
|
||||
mounts: [
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
namespace_path: 'root',
|
||||
acme_clients: 0,
|
||||
|
|
@ -858,8 +858,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
namespace_path: 'root',
|
||||
mount_type: 'pki',
|
||||
acme_clients: 100,
|
||||
|
|
@ -869,8 +869,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv',
|
||||
namespace_path: 'root',
|
||||
acme_clients: 0,
|
||||
|
|
@ -901,8 +901,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 100,
|
||||
mounts: [
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
namespace_path: 'root',
|
||||
mount_type: 'userpass',
|
||||
acme_clients: 0,
|
||||
|
|
@ -912,8 +912,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
namespace_path: 'root',
|
||||
mount_type: 'pki',
|
||||
acme_clients: 100,
|
||||
|
|
@ -924,8 +924,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
},
|
||||
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
namespace_path: 'root',
|
||||
mount_type: 'kv',
|
||||
acme_clients: 0,
|
||||
|
|
@ -951,7 +951,7 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 238,
|
||||
namespaces: [
|
||||
{
|
||||
label: 'ns1',
|
||||
label: 'ns1/',
|
||||
acme_clients: 934,
|
||||
clients: 1981,
|
||||
entity_clients: 708,
|
||||
|
|
@ -959,8 +959,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 157,
|
||||
mounts: [
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
acme_clients: 934,
|
||||
clients: 934,
|
||||
entity_clients: 0,
|
||||
|
|
@ -968,8 +968,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
acme_clients: 0,
|
||||
clients: 890,
|
||||
entity_clients: 708,
|
||||
|
|
@ -977,8 +977,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
acme_clients: 0,
|
||||
clients: 157,
|
||||
entity_clients: 0,
|
||||
|
|
@ -996,8 +996,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 81,
|
||||
mounts: [
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
acme_clients: 994,
|
||||
clients: 994,
|
||||
entity_clients: 0,
|
||||
|
|
@ -1005,8 +1005,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
acme_clients: 0,
|
||||
clients: 872,
|
||||
entity_clients: 124,
|
||||
|
|
@ -1014,8 +1014,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
acme_clients: 0,
|
||||
clients: 81,
|
||||
entity_clients: 0,
|
||||
|
|
@ -1042,8 +1042,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 25,
|
||||
mounts: [
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki',
|
||||
acme_clients: 91,
|
||||
clients: 91,
|
||||
|
|
@ -1052,8 +1052,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
acme_clients: 0,
|
||||
clients: 75,
|
||||
|
|
@ -1062,8 +1062,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv',
|
||||
acme_clients: 0,
|
||||
clients: 25,
|
||||
|
|
@ -1074,7 +1074,7 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
],
|
||||
},
|
||||
{
|
||||
label: 'ns1',
|
||||
label: 'ns1/',
|
||||
acme_clients: 53,
|
||||
clients: 173,
|
||||
entity_clients: 34,
|
||||
|
|
@ -1082,8 +1082,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 24,
|
||||
mounts: [
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
acme_clients: 0,
|
||||
clients: 96,
|
||||
|
|
@ -1092,8 +1092,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki',
|
||||
acme_clients: 53,
|
||||
clients: 53,
|
||||
|
|
@ -1102,8 +1102,8 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv',
|
||||
acme_clients: 0,
|
||||
clients: 24,
|
||||
|
|
@ -1119,61 +1119,62 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||
],
|
||||
};
|
||||
|
||||
export const ENTITY_EXPORT = `{"entity_name":"entity_b3e2a7ff","entity_alias_name":"bob","local_entity_alias":false,"client_id":"5692c6ef-c871-128e-fb06-df2be7bfc0db","client_type":"entity","namespace_id":"root","namespace_path":"","mount_accessor":"auth_userpass_f47ad0b4","mount_type":"userpass","mount_path":"auth/userpass/","token_creation_time":"2025-08-15T23:48:09Z","client_first_used_time":"2025-08-15T23:48:09Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":["7537e6b7-3b06-65c2-1fb2-c83116eb5e6f"]}
|
||||
{"entity_name":"bob-smith","entity_alias_name":"bob","local_entity_alias":false,"client_id":"23a04911-5d72-ba98-11d3-527f2fcf3a81","client_type":"entity","namespace_id":"root","namespace_path":"","mount_accessor":"auth_userpass_de28062c","mount_type":"userpass","mount_path":"auth/userpass-test/","token_creation_time":"2025-08-15T23:52:38Z","client_first_used_time":"2025-08-15T23:53:19Z","policies":["base"],"entity_metadata":{"organization":"ACME Inc.","team":"QA"},"entity_alias_metadata":{},"entity_alias_custom_metadata":{"account":"Tester Account"},"entity_group_ids":["7537e6b7-3b06-65c2-1fb2-c83116eb5e6f"]}
|
||||
{"entity_name":"alice-johnson","entity_alias_name":"alice","local_entity_alias":false,"client_id":"a7c8d912-4f61-23b5-88e4-627a3dcf2b92","client_type":"entity","namespace_id":"root","namespace_path":"","mount_accessor":"auth_userpass_f47ad0b4","mount_type":"userpass","mount_path":"auth/userpass/","token_creation_time":"2025-08-16T09:15:42Z","client_first_used_time":"2025-08-16T09:16:03Z","policies":["admin","audit"],"entity_metadata":{"organization":"TechCorp","team":"DevOps","location":"San Francisco"},"entity_alias_metadata":{"department":"Engineering"},"entity_alias_custom_metadata":{"role":"Senior Engineer"},"entity_group_ids":["7537e6b7-3b06-65c2-1fb2-c83116eb5e6f","a1b2c3d4-5e6f-7g8h-9i0j-k1l2m3n4o5p6"]}
|
||||
{"entity_name":"charlie-brown","entity_alias_name":"charlie","local_entity_alias":true,"client_id":"b9e5f824-7c92-34d6-a1f8-738b4ecf5d73","client_type":"entity","namespace_id":"whUNi","namespace_path":"ns2/","mount_accessor":"auth_ldap_8a3b9c2d","mount_type":"ldap","mount_path":"auth/ldap/","token_creation_time":"2025-08-16T14:22:17Z","client_first_used_time":"2025-08-16T14:22:45Z","policies":["developer","read-only"],"entity_metadata":{"organization":"StartupXYZ","team":"Backend"},"entity_alias_metadata":{"cn":"charlie.brown","ou":"development"},"entity_alias_custom_metadata":{"project":"microservices"},"entity_group_ids":["c7d8e9f0-1a2b-3c4d-5e6f-789012345678"]}
|
||||
{"entity_name":"diana-prince","entity_alias_name":"diana","local_entity_alias":false,"client_id":"e4f7a935-2b68-47c9-b3e6-849c5dfb7a84","client_type":"entity","namespace_id":"aT9S5","namespace_path":"ns1/","mount_accessor":"auth_oidc_1f2e3d4c","mount_type":"oidc","mount_path":"auth/oidc/","token_creation_time":"2025-08-17T11:08:33Z","client_first_used_time":"2025-08-17T11:09:01Z","policies":["security","compliance"],"entity_metadata":{"organization":"SecureTech","team":"Security","clearance":"high"},"entity_alias_metadata":{"email":"diana.prince@securetech.com"},"entity_alias_custom_metadata":{"access_level":"L4"},"entity_group_ids":["f8e7d6c5-4b3a-2918-7654-321098765432"]}
|
||||
{"entity_name":"frank-castle","entity_alias_name":"frank","local_entity_alias":false,"client_id":"c6b9d248-5a71-39e4-c7f2-951d8eaf6b95","client_type":"entity","namespace_id":"root","namespace_path":"","mount_accessor":"auth_jwt_9d8c7b6a","mount_type":"jwt","mount_path":"auth/jwt/","token_creation_time":"2025-08-17T16:43:28Z","client_first_used_time":"2025-08-17T16:44:12Z","policies":["operations","monitoring"],"entity_metadata":{"organization":"CloudOps","team":"SRE","region":"us-east-1"},"entity_alias_metadata":{"sub":"frank.castle@cloudops.io","iss":"https://auth.cloudops.io"},"entity_alias_custom_metadata":{"on_call":"true","expertise":"kubernetes"},"entity_group_ids":["9a8b7c6d-5e4f-3210-9876-543210fedcba"]}
|
||||
{"entity_name":"grace-hopper","entity_alias_name":"grace","local_entity_alias":true,"client_id":"d8a3e517-6f94-42b7-d5c8-062f9bce4a73","client_type":"entity","namespace_id":"YMjS8","namespace_path":"ns5/","mount_accessor":"auth_userpass_3e2d1c0b","mount_type":"userpass","mount_path":"auth/userpass-legacy/","token_creation_time":"2025-08-18T08:17:55Z","client_first_used_time":"2025-08-18T08:18:23Z","policies":["legacy-admin","data-access"],"entity_metadata":{"organization":"LegacySystems","team":"Platform","tenure":"senior"},"entity_alias_metadata":{"legacy_id":"grace.hopper.001"},"entity_alias_custom_metadata":{"system_access":"mainframe","certification":"vault-admin"},"entity_group_ids":["1f2e3d4c-5b6a-7980-1234-567890abcdef"]}
|
||||
export const ENTITY_EXPORT = `{"entity_name":"entity_b3e2a7ff","entity_alias_name":"bob","local_entity_alias":false,"client_id":"5692c6ef-c871-128e-fb06-df2be7bfc0db","client_type":"entity","namespace_id":"vK5Bt","namespace_path":"ns1/","mount_accessor":"auth_userpass_f47ad0b4","mount_type":"userpass","mount_path":"auth/userpass/0/","token_creation_time":"2022-09-15T23:48:09Z","client_first_used_time":"2023-09-15T23:48:09Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":["7537e6b7-3b06-65c2-1fb2-c83116eb5e6f"]}
|
||||
{"entity_name":"entity_b3e2a7ff","entity_alias_name":"bob","local_entity_alias":false,"client_id":"daf8420c-0b6b-34e6-ff38-ee1ed093bea9","client_type":"entity","namespace_id":"root","namespace_path":"","mount_accessor":"auth_userpass_f47ad0b4","mount_type":"userpass","mount_path":"auth/userpass/","token_creation_time":"2020-08-15T23:48:09Z","client_first_used_time":"2025-07-15T23:48:09Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":["7537e6b7-3b06-65c2-1fb2-c83116eb5e6f"]}
|
||||
{"entity_name":"bob-smith","entity_alias_name":"bob","local_entity_alias":false,"client_id":"23a04911-5d72-ba98-11d3-527f2fcf3a81","client_type":"entity","namespace_id":"root","namespace_path":"","mount_accessor":"auth_userpass_de28062c","mount_type":"userpass","mount_path":"auth/userpass-test/","token_creation_time":"2020-08-15T23:52:38Z","client_first_used_time":"2025-08-15T23:53:19Z","policies":["base"],"entity_metadata":{"organization":"ACME Inc.","team":"QA"},"entity_alias_metadata":{},"entity_alias_custom_metadata":{"account":"Tester Account"},"entity_group_ids":["7537e6b7-3b06-65c2-1fb2-c83116eb5e6f"]}
|
||||
{"entity_name":"alice-johnson","entity_alias_name":"alice","local_entity_alias":false,"client_id":"a7c8d912-4f61-23b5-88e4-627a3dcf2b92","client_type":"entity","namespace_id":"root","namespace_path":"","mount_accessor":"auth_userpass_f47ad0b4","mount_type":"userpass","mount_path":"auth/userpass/","token_creation_time":"2020-08-16T09:15:42Z","client_first_used_time":"2025-09-16T09:16:03Z","policies":["admin","audit"],"entity_metadata":{"organization":"TechCorp","team":"DevOps","location":"San Francisco"},"entity_alias_metadata":{"department":"Engineering"},"entity_alias_custom_metadata":{"role":"Senior Engineer"},"entity_group_ids":["7537e6b7-3b06-65c2-1fb2-c83116eb5e6f","a1b2c3d4-5e6f-7g8h-9i0j-k1l2m3n4o5p6"]}
|
||||
{"entity_name":"charlie-brown","entity_alias_name":"charlie","local_entity_alias":true,"client_id":"b9e5f824-7c92-34d6-a1f8-738b4ecf5d73","client_type":"entity","namespace_id":"whUNi","namespace_path":"ns2/","mount_accessor":"auth_ldap_8a3b9c2d","mount_type":"ldap","mount_path":"auth/ldap/","token_creation_time":"2020-08-16T14:22:17Z","client_first_used_time":"2025-10-16T14:22:45Z","policies":["developer","read-only"],"entity_metadata":{"organization":"StartupXYZ","team":"Backend"},"entity_alias_metadata":{"cn":"charlie.brown","ou":"development"},"entity_alias_custom_metadata":{"project":"microservices"},"entity_group_ids":["c7d8e9f0-1a2b-3c4d-5e6f-789012345678"]}
|
||||
{"entity_name":"diana-prince","entity_alias_name":"diana","local_entity_alias":false,"client_id":"e4f7a935-2b68-47c9-b3e6-849c5dfb7a84","client_type":"entity","namespace_id":"aT9S5","namespace_path":"ns1/","mount_accessor":"auth_oidc_1f2e3d4c","mount_type":"oidc","mount_path":"auth/oidc/","token_creation_time":"2020-08-17T11:08:33Z","client_first_used_time":"2025-11-17T11:09:01Z","policies":["security","compliance"],"entity_metadata":{"organization":"SecureTech","team":"Security","clearance":"high"},"entity_alias_metadata":{"email":"diana.prince@securetech.com"},"entity_alias_custom_metadata":{"access_level":"L4"},"entity_group_ids":["f8e7d6c5-4b3a-2918-7654-321098765432"]}
|
||||
{"entity_name":"frank-castle","entity_alias_name":"frank","local_entity_alias":false,"client_id":"c6b9d248-5a71-39e4-c7f2-951d8eaf6b95","client_type":"entity","namespace_id":"root","namespace_path":"","mount_accessor":"auth_jwt_9d8c7b6a","mount_type":"jwt","mount_path":"auth/jwt/","token_creation_time":"2020-08-17T16:43:28Z","client_first_used_time":"2025-12-17T16:44:12Z","policies":["operations","monitoring"],"entity_metadata":{"organization":"CloudOps","team":"SRE","region":"us-east-1"},"entity_alias_metadata":{"sub":"frank.castle@cloudops.io","iss":"https://auth.cloudops.io"},"entity_alias_custom_metadata":{"on_call":"true","expertise":"kubernetes"},"entity_group_ids":["9a8b7c6d-5e4f-3210-9876-543210fedcba"]}
|
||||
{"entity_name":"grace-hopper","entity_alias_name":"grace","local_entity_alias":true,"client_id":"d8a3e517-6f94-42b7-d5c8-062f9bce4a73","client_type":"entity","namespace_id":"YMjS8","namespace_path":"ns5/","mount_accessor":"auth_userpass_3e2d1c0b","mount_type":"userpass","mount_path":"auth/userpass-legacy/","token_creation_time":"2020-08-18T08:17:55Z","client_first_used_time":"2025-06-18T08:18:23Z","policies":["legacy-admin","data-access"],"entity_metadata":{"organization":"LegacySystems","team":"Platform","tenure":"senior"},"entity_alias_metadata":{"legacy_id":"grace.hopper.001"},"entity_alias_custom_metadata":{"system_access":"mainframe","certification":"vault-admin"},"entity_group_ids":["1f2e3d4c-5b6a-7980-1234-567890abcdef"]}
|
||||
`;
|
||||
|
||||
const NON_ENTITY_EXPORT = `{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"46dcOXXH+P1VEQiKTQjtWXEtBlbHdMOWwz+svXf3xuU=","client_type":"non-entity-token","namespace_id":"whUNi","namespace_path":"ns2/","mount_accessor":"auth_ns_token_3b2bf405","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:21Z","client_first_used_time":"2025-08-15T16:19:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"VKAJVITyTwyqF1GUzwYHwkaK6bbnL1zN8ZJ7viKR8no=","client_type":"non-entity-token","namespace_id":"omjn8","namespace_path":"ns8/","mount_accessor":"auth_ns_token_07b90be7","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:22Z","client_first_used_time":"2025-08-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"ww4L5n9WE32lPNh3UBgT3JxTDZb1a+m/3jqUffp04tQ=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"cBLb9erIROCw7cczXpfkXTOdnZoVwfWF4EAPD9k61lU=","client_type":"non-entity-token","namespace_id":"aT9S5","namespace_path":"ns1/","mount_accessor":"auth_ns_token_62a4e52a","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:21Z","client_first_used_time":"2025-08-15T16:19:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"KMHoH3Kvr6nnW2ZIs+i37pYvyVtnuaL3DmyVxUL6boI=","client_type":"non-entity-token","namespace_id":"YMjS8","namespace_path":"ns5/","mount_accessor":"auth_ns_token_45cbc810","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:22Z","client_first_used_time":"2025-08-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"hcMH4P4IGAN13cJqkwIJLXYoPLTodtOj/wPTZKS0x4U=","client_type":"non-entity-token","namespace_id":"ZNdL5","namespace_path":"ns7/","mount_accessor":"auth_ns_token_8bbd9440","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:22Z","client_first_used_time":"2025-08-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Oby0ABLmfhqYdfqGfljGHHhAA5zX+BwsGmFu4QGJZd0=","client_type":"non-entity-token","namespace_id":"bJIgY","namespace_path":"ns9/","mount_accessor":"auth_ns_token_8d188479","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:22Z","client_first_used_time":"2025-08-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Z6MjZuH/VD7HU11efiKoM/hfoxssSbeu4c6DhC7zUZ4=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"1UxaPHJUOPWrf0ivMgBURK6WHzbfXGkcn/C/xI3AeHQ=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:24Z","client_first_used_time":"2025-08-15T16:19:24Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"hfFbwhMucs/f84p2QTOiBLT72i0WLVkIgCGV7RIuWlo=","client_type":"non-entity-token","namespace_id":"x6sKN","namespace_path":"ns4/","mount_accessor":"auth_ns_token_2aaebdc2","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:21Z","client_first_used_time":"2025-08-15T16:19:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"sOdIr+zoNqOUa4hq6Jv4LCGVr0sTLGbvcRPVGAtUA7g=","client_type":"non-entity-token","namespace_id":"Rsvk5","namespace_path":"ns6/","mount_accessor":"auth_ns_token_f603fd8d","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:22Z","client_first_used_time":"2025-08-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"vOIAwNhe6P6HFdJQgUIU/8K6Z5e+oxyVP5x3KtTKS6U=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"ZOkJY3P7IzOqulsnEI0JAQQXwTPnXmpGUh9otqNUclc=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Lsha/HH+xLZq92XG4GYZVlwVQCiqPCUIuoego4aCybU=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Tsl/u7CDTYSXA9HRwlNTW7K/yyEe5PDkLOVTvTWy3q0=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"vnq6JntpiGV4FN6GDICLECe2in31aanLA6Q1UWqBmL0=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:24Z","client_first_used_time":"2025-08-15T16:19:24Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"MRMrywfPPL3QnKFMBGfRjjmaefBRH1VKpQVIfrd0Xb4=","client_type":"non-entity-token","namespace_id":"6aDiU","namespace_path":"ns3/","mount_accessor":"auth_ns_token_ef771c23","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:21Z","client_first_used_time":"2025-08-15T16:19:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Rce6fjHs15+hDl5XdXbWmzGNYrTcQsJuaoqfs9Vrhvw=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2025-08-15T16:19:24Z","client_first_used_time":"2025-08-15T16:19:24Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
const NON_ENTITY_EXPORT = `{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"46dcOXXH+P1VEQiKTQjtWXEtBlbHdMOWwz+svXf3xuU=","client_type":"non-entity-token","namespace_id":"whUNi","namespace_path":"ns2/","mount_accessor":"auth_ns_token_3b2bf405","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:21Z","client_first_used_time":"2025-05-15T16:19:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"VKAJVITyTwyqF1GUzwYHwkaK6bbnL1zN8ZJ7viKR8no=","client_type":"non-entity-token","namespace_id":"omjn8","namespace_path":"ns8/","mount_accessor":"auth_ns_token_07b90be7","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:22Z","client_first_used_time":"2025-05-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"ww4L5n9WE32lPNh3UBgT3JxTDZb1a+m/3jqUffp04tQ=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:23Z","client_first_used_time":"2025-05-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"cBLb9erIROCw7cczXpfkXTOdnZoVwfWF4EAPD9k61lU=","client_type":"non-entity-token","namespace_id":"aT9S5","namespace_path":"ns1/","mount_accessor":"auth_ns_token_62a4e52a","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:21Z","client_first_used_time":"2025-06-15T16:19:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"KMHoH3Kvr6nnW2ZIs+i37pYvyVtnuaL3DmyVxUL6boI=","client_type":"non-entity-token","namespace_id":"YMjS8","namespace_path":"ns5/","mount_accessor":"auth_ns_token_45cbc810","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:22Z","client_first_used_time":"2025-06-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"hcMH4P4IGAN13cJqkwIJLXYoPLTodtOj/wPTZKS0x4U=","client_type":"non-entity-token","namespace_id":"ZNdL5","namespace_path":"ns7/","mount_accessor":"auth_ns_token_8bbd9440","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:22Z","client_first_used_time":"2025-06-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Oby0ABLmfhqYdfqGfljGHHhAA5zX+BwsGmFu4QGJZd0=","client_type":"non-entity-token","namespace_id":"bJIgY","namespace_path":"ns9/","mount_accessor":"auth_ns_token_8d188479","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:22Z","client_first_used_time":"2025-06-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Z6MjZuH/VD7HU11efiKoM/hfoxssSbeu4c6DhC7zUZ4=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:23Z","client_first_used_time":"2025-07-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"1UxaPHJUOPWrf0ivMgBURK6WHzbfXGkcn/C/xI3AeHQ=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:24Z","client_first_used_time":"2025-07-15T16:19:24Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"hfFbwhMucs/f84p2QTOiBLT72i0WLVkIgCGV7RIuWlo=","client_type":"non-entity-token","namespace_id":"x6sKN","namespace_path":"ns4/","mount_accessor":"auth_ns_token_2aaebdc2","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:21Z","client_first_used_time":"2025-07-15T16:19:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"sOdIr+zoNqOUa4hq6Jv4LCGVr0sTLGbvcRPVGAtUA7g=","client_type":"non-entity-token","namespace_id":"Rsvk5","namespace_path":"ns6/","mount_accessor":"auth_ns_token_f603fd8d","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:22Z","client_first_used_time":"2025-07-15T16:19:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"vOIAwNhe6P6HFdJQgUIU/8K6Z5e+oxyVP5x3KtTKS6U=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"ZOkJY3P7IzOqulsnEI0JAQQXwTPnXmpGUh9otqNUclc=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Lsha/HH+xLZq92XG4GYZVlwVQCiqPCUIuoego4aCybU=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:23Z","client_first_used_time":"2025-08-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Tsl/u7CDTYSXA9HRwlNTW7K/yyEe5PDkLOVTvTWy3q0=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:23Z","client_first_used_time":"2025-09-15T16:19:23Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"vnq6JntpiGV4FN6GDICLECe2in31aanLA6Q1UWqBmL0=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:24Z","client_first_used_time":"2025-09-15T16:19:24Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"MRMrywfPPL3QnKFMBGfRjjmaefBRH1VKpQVIfrd0Xb4=","client_type":"non-entity-token","namespace_id":"6aDiU","namespace_path":"ns3/","mount_accessor":"auth_ns_token_ef771c23","mount_type":"ns_token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:21Z","client_first_used_time":"2025-09-15T16:19:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"Rce6fjHs15+hDl5XdXbWmzGNYrTcQsJuaoqfs9Vrhvw=","client_type":"non-entity-token","namespace_id":"root","namespace_path":"","mount_accessor":"auth_token_360f591b","mount_type":"token","mount_path":"auth/token/","token_creation_time":"2020-08-15T16:19:24Z","client_first_used_time":"2025-09-15T16:19:24Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
`;
|
||||
|
||||
const ACME_EXPORT = `{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.3U8nSB_yMBvrdu7PvAVykKurDiaH_vQGaEdAUsp-Cew","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:47:54Z","client_first_used_time":"2025-08-21T18:47:54Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.77tKDzxw0i81Nr4XLliTP9xRsztXLTuS16nN32B9jHA","client_type":"pki-acme","namespace_id":"whUNi","namespace_path":"ns2/","mount_accessor":"pki_06dad7b8","mount_type":"ns_pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:48:17Z","client_first_used_time":"2025-08-21T18:48:17Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.RoN77EahLU0wfem4z--ZqJSaqOZ7RvBWR3OkPHM_xaw","client_type":"pki-acme","namespace_id":"omjn8","namespace_path":"ns8/","mount_accessor":"pki_06dad7b8","mount_type":"ns_pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:49:26Z","client_first_used_time":"2025-08-21T18:49:26Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.5S7vaaJIrXormQSLHv4YkBhVfu6Ug0GERhTVTCrq-Fk","client_type":"pki-acme","namespace_id":"aT9S5","namespace_path":"ns1/","mount_accessor":"pki_06dad7b8","mount_type":"ns_pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:45:12Z","client_first_used_time":"2025-08-21T18:45:12Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.IeJQMwtkReJHVNL6fZLmqiu8-Re4JdKCQixXkfcaSRE","client_type":"pki-acme","namespace_id":"YMjS8","namespace_path":"ns5/","mount_accessor":"pki_06dad7b8","mount_type":"ns_pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:45:41Z","client_first_used_time":"2025-08-21T18:45:41Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.vTm3SCZom90qy3SuyIacpVsQgGLx7ASf3SeGpqn5XBA","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:47:19Z","client_first_used_time":"2025-08-21T18:47:19Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.64jWs15k6roUH6MiQ2u80K08Bmqw8IQOpqTpDZgZ1f4","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:47:25Z","client_first_used_time":"2025-08-21T18:47:25Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.RkjnwyIIn6bnc4LDdKQ9HNfnhuVXT7vQONXgGHJl4CE","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:49:21Z","client_first_used_time":"2025-08-21T18:49:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.uozIMLVXDMU7Fc2TFFwq0-uE1GFSui5rbTI1XyNAYBY","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:44:44Z","client_first_used_time":"2025-08-21T18:44:44Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.WiLdlzq93WtVmObB__CC2SPX6sI7EVLTTzxOIRHHN3o","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:44:49Z","client_first_used_time":"2025-08-21T18:44:49Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.P65jgamzwLYbKyxTlJFD5DL3sIUbusbXcQhYaysgzlU","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:45:59Z","client_first_used_time":"2025-08-21T18:45:59Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.2REWUkDLXAG2UB0ZJQcjPnHc4H39aq8fG3LMaHSHKow","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:46:05Z","client_first_used_time":"2025-08-21T18:46:05Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.Eeyq9-EfWv-iE9Aj3DzCU4r9P8V1Maewx51vcxMN-jA","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:46:10Z","client_first_used_time":"2025-08-21T18:46:10Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.vaeb2KR58sRuMUdUlv2TsbaOkSICTAxmJxhkuOs8ZiM","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:46:22Z","client_first_used_time":"2025-08-21T18:46:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.xEPG0eNfrAfRgXg6AKjsCrFPMs0IbLTCfUsCie_rfzY","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:46:51Z","client_first_used_time":"2025-08-21T18:46:51Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.Bkg4862LEoFXJUDWlfFtJHU9a69KRJPiEdw5XCbkkAI","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2025-08-21T18:47:42Z","client_first_used_time":"2025-08-21T18:47:42Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
const ACME_EXPORT = `{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.3U8nSB_yMBvrdu7PvAVykKurDiaH_vQGaEdAUsp-Cew","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:47:54Z","client_first_used_time":"2025-06-21T18:47:54Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.77tKDzxw0i81Nr4XLliTP9xRsztXLTuS16nN32B9jHA","client_type":"pki-acme","namespace_id":"whUNi","namespace_path":"ns2/","mount_accessor":"pki_06dad7b8","mount_type":"ns_pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:48:17Z","client_first_used_time":"2025-07-21T18:48:17Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.RoN77EahLU0wfem4z--ZqJSaqOZ7RvBWR3OkPHM_xaw","client_type":"pki-acme","namespace_id":"omjn8","namespace_path":"ns8/","mount_accessor":"pki_06dad7b8","mount_type":"ns_pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:49:26Z","client_first_used_time":"2025-08-21T18:49:26Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.5S7vaaJIrXormQSLHv4YkBhVfu6Ug0GERhTVTCrq-Fk","client_type":"pki-acme","namespace_id":"aT9S5","namespace_path":"ns1/","mount_accessor":"pki_06dad7b8","mount_type":"ns_pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:45:12Z","client_first_used_time":"2025-08-21T18:45:12Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.IeJQMwtkReJHVNL6fZLmqiu8-Re4JdKCQixXkfcaSRE","client_type":"pki-acme","namespace_id":"YMjS8","namespace_path":"ns5/","mount_accessor":"pki_06dad7b8","mount_type":"ns_pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:45:41Z","client_first_used_time":"2025-08-21T18:45:41Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.vTm3SCZom90qy3SuyIacpVsQgGLx7ASf3SeGpqn5XBA","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:47:19Z","client_first_used_time":"2025-08-21T18:47:19Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.64jWs15k6roUH6MiQ2u80K08Bmqw8IQOpqTpDZgZ1f4","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:47:25Z","client_first_used_time":"2025-08-21T18:47:25Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.RkjnwyIIn6bnc4LDdKQ9HNfnhuVXT7vQONXgGHJl4CE","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:49:21Z","client_first_used_time":"2025-08-21T18:49:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.uozIMLVXDMU7Fc2TFFwq0-uE1GFSui5rbTI1XyNAYBY","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:44:44Z","client_first_used_time":"2025-08-21T18:44:44Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.WiLdlzq93WtVmObB__CC2SPX6sI7EVLTTzxOIRHHN3o","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:44:49Z","client_first_used_time":"2025-08-21T18:44:49Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.P65jgamzwLYbKyxTlJFD5DL3sIUbusbXcQhYaysgzlU","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:45:59Z","client_first_used_time":"2025-08-21T18:45:59Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.2REWUkDLXAG2UB0ZJQcjPnHc4H39aq8fG3LMaHSHKow","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:46:05Z","client_first_used_time":"2025-08-21T18:46:05Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.Eeyq9-EfWv-iE9Aj3DzCU4r9P8V1Maewx51vcxMN-jA","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:46:10Z","client_first_used_time":"2025-08-21T18:46:10Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.vaeb2KR58sRuMUdUlv2TsbaOkSICTAxmJxhkuOs8ZiM","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:46:22Z","client_first_used_time":"2025-08-21T18:46:22Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.xEPG0eNfrAfRgXg6AKjsCrFPMs0IbLTCfUsCie_rfzY","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:46:51Z","client_first_used_time":"2025-08-21T18:46:51Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"pki-acme.Bkg4862LEoFXJUDWlfFtJHU9a69KRJPiEdw5XCbkkAI","client_type":"pki-acme","namespace_id":"root","namespace_path":"","mount_accessor":"pki_06dad7b8","mount_type":"pki","mount_path":"pki_int/","token_creation_time":"2020-08-21T18:47:42Z","client_first_used_time":"2025-08-21T18:47:42Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
`;
|
||||
|
||||
const SECRET_SYNC_EXPORT = `{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.3U8nSB_yMBvrdu7PvAVykKurDiaH_vQGaEdAUsp-Cew","client_type":"secret-sync","namespace_id":"root","namespace_path":"","mount_accessor":"kv_06dad7b8","mount_type":"kv","mount_path":"secrets/kv/0","token_creation_time":"2025-08-21T18:47:54Z","client_first_used_time":"2025-08-21T18:47:54Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.77tKDzxw0i81Nr4XLliTP9xRsztXLTuS16nN32B9jHA","client_type":"secret-sync","namespace_id":"ZNdL5","namespace_path":"ns7/","mount_accessor":"kv_06dad7b8","mount_type":"ns_kv","mount_path":"secrets/kv/0","token_creation_time":"2025-08-21T18:48:17Z","client_first_used_time":"2025-08-21T18:48:17Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.RoN77EahLU0wfem4z--ZqJSaqOZ7RvBWR3OkPHM_xaw","client_type":"secret-sync","namespace_id":"bJIgY","namespace_path":"ns9/","mount_accessor":"kv_12abc3d4","mount_type":"ns_kv","mount_path":"secrets/kv/1","token_creation_time":"2025-08-21T18:49:26Z","client_first_used_time":"2025-08-21T18:49:26Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.5S7vaaJIrXormQSLHv4YkBhVfu6Ug0GERhTVTCrq-Fk","client_type":"secret-sync","namespace_id":"x6sKN","namespace_path":"ns4/","mount_accessor":"kv_06dad7b8","mount_type":"ns_kv","mount_path":"secrets/kv/0","token_creation_time":"2025-08-21T18:45:12Z","client_first_used_time":"2025-08-21T18:45:12Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.IeJQMwtkReJHVNL6fZLmqiu8-Re4JdKCQixXkfcaSRE","client_type":"secret-sync","namespace_id":"Rsvk5","namespace_path":"ns6/","mount_accessor":"kv_12abc3d4","mount_type":"ns_kv","mount_path":"secrets/kv/1","token_creation_time":"2025-08-21T18:45:41Z","client_first_used_time":"2025-08-21T18:45:41Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.vTm3SCZom90qy3SuyIacpVsQgGLx7ASf3SeGpqn5XBA","client_type":"secret-sync","namespace_id":"root","namespace_path":"","mount_accessor":"kv_06dad7b8","mount_type":"kv","mount_path":"secrets/kv/0","token_creation_time":"2025-08-21T18:47:19Z","client_first_used_time":"2025-08-21T18:47:19Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.64jWs15k6roUH6MiQ2u80K08Bmqw8IQOpqTpDZgZ1f4","client_type":"secret-sync","namespace_id":"6aDiU","namespace_path":"ns3/","mount_accessor":"kv_12abc3d4","mount_type":"kv","mount_path":"secrets/kv/1","token_creation_time":"2025-08-21T18:47:25Z","client_first_used_time":"2025-08-21T18:47:25Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.RkjnwyIIn6bnc4LDdKQ9HNfnhuVXT7vQONXgGHJl4CE","client_type":"secret-sync","namespace_id":"root","namespace_path":"","mount_accessor":"kv_06dad7b8","mount_type":"kv","mount_path":"secrets/kv/0","token_creation_time":"2025-08-21T18:49:21Z","client_first_used_time":"2025-08-21T18:49:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
const SECRET_SYNC_EXPORT = `{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.3U8nSB_yMBvrdu7PvAVykKurDiaH_vQGaEdAUsp-Cew","client_type":"secret-sync","namespace_id":"root","namespace_path":"","mount_accessor":"kv_06dad7b8","mount_type":"kv","mount_path":"secrets/kv/0/","token_creation_time":"2020-08-21T18:47:54Z","client_first_used_time":"2025-05-21T18:47:54Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.77tKDzxw0i81Nr4XLliTP9xRsztXLTuS16nN32B9jHA","client_type":"secret-sync","namespace_id":"ZNdL5","namespace_path":"ns7/","mount_accessor":"kv_06dad7b8","mount_type":"ns_kv","mount_path":"secrets/kv/0/","token_creation_time":"2020-08-21T18:48:17Z","client_first_used_time":"2025-05-21T18:48:17Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.RoN77EahLU0wfem4z--ZqJSaqOZ7RvBWR3OkPHM_xaw","client_type":"secret-sync","namespace_id":"bJIgY","namespace_path":"ns9/","mount_accessor":"kv_12abc3d4","mount_type":"ns_kv","mount_path":"secrets/kv/1","token_creation_time":"2020-08-21T18:49:26Z","client_first_used_time":"2025-06-21T18:49:26Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.5S7vaaJIrXormQSLHv4YkBhVfu6Ug0GERhTVTCrq-Fk","client_type":"secret-sync","namespace_id":"x6sKN","namespace_path":"ns4/","mount_accessor":"kv_06dad7b8","mount_type":"ns_kv","mount_path":"secrets/kv/0/","token_creation_time":"2020-08-21T18:45:12Z","client_first_used_time":"2025-06-21T18:45:12Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.IeJQMwtkReJHVNL6fZLmqiu8-Re4JdKCQixXkfcaSRE","client_type":"secret-sync","namespace_id":"Rsvk5","namespace_path":"ns6/","mount_accessor":"kv_12abc3d4","mount_type":"ns_kv","mount_path":"secrets/kv/1","token_creation_time":"2020-08-21T18:45:41Z","client_first_used_time":"2025-07-21T18:45:41Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.vTm3SCZom90qy3SuyIacpVsQgGLx7ASf3SeGpqn5XBA","client_type":"secret-sync","namespace_id":"root","namespace_path":"","mount_accessor":"kv_06dad7b8","mount_type":"kv","mount_path":"secrets/kv/0/","token_creation_time":"2020-08-21T18:47:19Z","client_first_used_time":"2025-08-21T18:47:19Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.64jWs15k6roUH6MiQ2u80K08Bmqw8IQOpqTpDZgZ1f4","client_type":"secret-sync","namespace_id":"6aDiU","namespace_path":"ns3/","mount_accessor":"kv_12abc3d4","mount_type":"kv","mount_path":"secrets/kv/1","token_creation_time":"2020-08-21T18:47:25Z","client_first_used_time":"2025-08-21T18:47:25Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
{"entity_name":"","entity_alias_name":"","local_entity_alias":false,"client_id":"secret-sync.RkjnwyIIn6bnc4LDdKQ9HNfnhuVXT7vQONXgGHJl4CE","client_type":"secret-sync","namespace_id":"root","namespace_path":"","mount_accessor":"kv_06dad7b8","mount_type":"kv","mount_path":"secrets/kv/0/","token_creation_time":"2020-08-21T18:49:21Z","client_first_used_time":"2025-08-21T18:49:21Z","policies":[],"entity_metadata":{},"entity_alias_metadata":{},"entity_alias_custom_metadata":{},"entity_group_ids":[]}
|
||||
`;
|
||||
|
||||
export const ACTIVITY_EXPORT_STUB = ENTITY_EXPORT + NON_ENTITY_EXPORT + ACME_EXPORT + SECRET_SYNC_EXPORT;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ export const CLIENT_COUNT = {
|
|||
mountPaths: '[data-test-counts-auth-mounts]',
|
||||
},
|
||||
dateRange: {
|
||||
dropdownOption: (idx = 0) => `[data-test-date-range-billing-start="${idx}"]`,
|
||||
dropdownOption: (idx: number | null) =>
|
||||
typeof idx === 'number'
|
||||
? `[data-test-date-range-billing-start="${idx}"]`
|
||||
: '[data-test-date-range-billing-start]',
|
||||
dateDisplay: (name: string) => (name ? `[data-test-date-range="${name}"]` : '[data-test-date-range]'),
|
||||
edit: '[data-test-date-range-edit]',
|
||||
editModal: '[data-test-date-range-edit-modal]',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { click, fillIn, render } from '@ember/test-helpers';
|
||||
import { click, fillIn, findAll, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import Sinon from 'sinon';
|
||||
import timestamp from 'core/utils/timestamp';
|
||||
|
|
@ -22,7 +22,7 @@ module('Integration | Component | clients/date-range', function (hooks) {
|
|||
this.now = timestamp.now();
|
||||
this.startTimestamp = '2018-01-01T14:15:30';
|
||||
this.endTimestamp = '2019-01-31T14:15:30';
|
||||
this.billingStartTime = '2018-01-01T14:15:30';
|
||||
this.billingStartTime = '';
|
||||
this.retentionMonths = 48;
|
||||
this.onChange = Sinon.spy();
|
||||
this.setEditModalVisible = Sinon.stub().callsFake((visible) => {
|
||||
|
|
@ -51,8 +51,8 @@ module('Integration | Component | clients/date-range', function (hooks) {
|
|||
await fillIn(DATE_RANGE.editDate('end'), '2018-03');
|
||||
await click(GENERAL.submitButton);
|
||||
const { start_time, end_time } = this.onChange.lastCall.args[0];
|
||||
assert.strictEqual(start_time, '2018-01-01T00:00:00.000Z', 'it formats start_time param');
|
||||
assert.strictEqual(end_time, '2018-03-31T23:59:59.000Z', 'it formats end_time param');
|
||||
assert.strictEqual(start_time, '2018-01-01T00:00:00Z', 'it formats start_time param');
|
||||
assert.strictEqual(end_time, '2018-03-31T23:59:59Z', 'it formats end_time param');
|
||||
assert.dom(DATE_RANGE.editModal).doesNotExist('closes modal');
|
||||
});
|
||||
|
||||
|
|
@ -101,4 +101,29 @@ module('Integration | Component | clients/date-range', function (hooks) {
|
|||
await click(GENERAL.submitButton);
|
||||
assert.false(this.onChange.called);
|
||||
});
|
||||
|
||||
module('enterprise', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.version.type = 'enterprise';
|
||||
this.billingStartTime = '2018-01-01T14:15:30';
|
||||
});
|
||||
|
||||
test('it billing start date dropdown for enterprise', async function (assert) {
|
||||
await this.renderComponent();
|
||||
await click(DATE_RANGE.edit);
|
||||
const expectedPeriods = [
|
||||
'January 2018',
|
||||
'January 2017',
|
||||
'January 2016',
|
||||
'January 2015',
|
||||
'January 2014',
|
||||
];
|
||||
const dropdownList = findAll(DATE_RANGE.dropdownOption(null));
|
||||
dropdownList.forEach((item, idx) => {
|
||||
const month = expectedPeriods[idx];
|
||||
assert.dom(item).hasText(month, `dropdown index: ${idx} renders ${month}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ module('Integration | Component | clients/filter-toolbar', function (hooks) {
|
|||
{ namespace_path: 'ns1/', mount_type: 'ns_token/', mount_path: 'auth/token/' },
|
||||
];
|
||||
this.onFilter = sinon.spy();
|
||||
this.filterQueryParams = { namespace_path: '', mount_path: '', mount_type: '' };
|
||||
this.filterQueryParams = { namespace_path: '', mount_path: '', mount_type: '', month: '' };
|
||||
|
||||
this.renderComponent = async () => {
|
||||
await render(hbs`
|
||||
|
|
@ -260,7 +260,7 @@ module('Integration | Component | clients/filter-toolbar', function (hooks) {
|
|||
const [afterUpdate] = this.onFilter.lastCall.args;
|
||||
assert.propEqual(
|
||||
afterUpdate,
|
||||
{ namespace_path: 'admin/', mount_path: 'auth/userpass-root/', mount_type: 'token/' },
|
||||
{ namespace_path: 'admin/', mount_path: 'auth/userpass-root/', mount_type: 'token/', month: '' },
|
||||
'callback fires with updated selection'
|
||||
);
|
||||
assert.dom(FILTERS.tagContainer).hasText('Filters applied: admin/ auth/userpass-root/ token/');
|
||||
|
|
@ -273,7 +273,7 @@ module('Integration | Component | clients/filter-toolbar', function (hooks) {
|
|||
const [afterClear] = this.onFilter.lastCall.args;
|
||||
assert.propEqual(
|
||||
afterClear,
|
||||
{ namespace_path: '', mount_path: '', mount_type: '' },
|
||||
{ namespace_path: '', mount_path: '', mount_type: '', month: '' },
|
||||
'onFilter callback has empty values when "Clear filters" is clicked'
|
||||
);
|
||||
assert.dom(FILTERS.tagContainer).hasText('Filters applied: None');
|
||||
|
|
@ -286,7 +286,7 @@ module('Integration | Component | clients/filter-toolbar', function (hooks) {
|
|||
const afterClear = this.onFilter.lastCall.args[0];
|
||||
assert.propEqual(
|
||||
afterClear,
|
||||
{ namespace_path: '', mount_path: 'auth/userpass-root/', mount_type: 'token/' },
|
||||
{ namespace_path: '', mount_path: 'auth/userpass-root/', mount_type: 'token/', month: '' },
|
||||
'onFilter callback fires with empty namespace_path'
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -250,6 +250,8 @@ module('Integration | Component | clients/page/client-list', function (hooks) {
|
|||
});
|
||||
|
||||
test('it renders empty state message when filter selections yield no results', async function (assert) {
|
||||
const flags = this.owner.lookup('service:flags');
|
||||
flags.activatedFlags = ['secrets-sync'];
|
||||
this.filterQueryParams = { namespace_path: 'dev/', mount_path: 'pluto/', mount_type: 'banana' };
|
||||
await this.renderComponent();
|
||||
|
||||
|
|
@ -257,7 +259,18 @@ module('Integration | Component | clients/page/client-list', function (hooks) {
|
|||
await click(GENERAL.hdsTab(tabName));
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('table empty state'))
|
||||
.hasText('No data found Clear or change filters to view client count data.');
|
||||
.hasText('No data found Select another client type or update filters to view client count data.');
|
||||
}
|
||||
});
|
||||
|
||||
test('it renders empty state message when secret sync is not activated', async function (assert) {
|
||||
this.filterQueryParams = { namespace_path: 'dev/', mount_path: 'pluto/', mount_type: 'banana' };
|
||||
await this.renderComponent();
|
||||
await click(GENERAL.hdsTab('Secret sync'));
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('table empty state'))
|
||||
.hasText(
|
||||
'No secret sync clients No data is available because Secrets Sync has not been activated. Activate Secrets Sync'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -94,10 +94,10 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
|
|||
.hasText('Tracking is disabled', 'Config disabled alert renders');
|
||||
});
|
||||
|
||||
const jan23start = '2023-01-01T00:00:00.000Z';
|
||||
const jan23start = '2023-01-01T00:00:00Z';
|
||||
// license start is July 2, 2024 on date change it recalculates start to beginning of the month
|
||||
const july23start = '2023-07-01T00:00:00.000Z';
|
||||
const dec23end = '2023-12-31T23:59:59.000Z';
|
||||
const july23start = '2023-07-01T00:00:00Z';
|
||||
const dec23end = '2023-12-31T23:59:59Z';
|
||||
const testCases = [
|
||||
{
|
||||
scenario: 'changing start only',
|
||||
|
|
@ -123,7 +123,7 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
|
|||
},
|
||||
];
|
||||
testCases.forEach((testCase) => {
|
||||
test(`it should send correct millis value on filter change when ${testCase.scenario}`, async function (assert) {
|
||||
test(`it should send correct timestamp on filter change when ${testCase.scenario}`, async function (assert) {
|
||||
assert.expect(5);
|
||||
this.owner.lookup('service:version').type = 'community';
|
||||
this.onFilterChange = (params) => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { click, fillIn, find, findAll, render, triggerEvent } from '@ember/test-helpers';
|
||||
import { click, find, findAll, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { ACTIVITY_RESPONSE_STUB } from 'vault/tests/helpers/clients/client-count-helpers';
|
||||
|
|
@ -13,6 +13,7 @@ import { CLIENT_COUNT, FILTERS } from 'vault/tests/helpers/clients/client-count-
|
|||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import sinon from 'sinon';
|
||||
import { ClientFilters, flattenMounts } from 'core/utils/client-count-utils';
|
||||
import { parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
|
||||
module('Integration | Component | clients/page/overview', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
|
@ -30,7 +31,7 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
this.activity = await this.store.queryRecord('clients/activity', {});
|
||||
this.mostRecentMonth = this.activity.byMonth[this.activity.byMonth.length - 1];
|
||||
this.onFilterChange = sinon.spy();
|
||||
this.filterQueryParams = { namespace_path: '', mount_path: '', mount_type: '' };
|
||||
this.filterQueryParams = { namespace_path: '', mount_path: '', mount_type: '', month: '' };
|
||||
this.renderComponent = () =>
|
||||
render(hbs`
|
||||
<Clients::Page::Overview
|
||||
|
|
@ -40,11 +41,9 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
/>`);
|
||||
|
||||
this.assertTableData = async (assert, filterKey, filterValue) => {
|
||||
const expectedData = flattenMounts(this.mostRecentMonth.new_clients.namespaces).filter(
|
||||
const expectedData = flattenMounts(this.activity.byNamespace).filter(
|
||||
(d) => d[filterKey] === filterValue
|
||||
);
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.mostRecentMonth.timestamp);
|
||||
assert.dom(GENERAL.tableRow()).exists({ count: expectedData.length });
|
||||
// Find all rendered rows and assert they satisfy the filter value and table data matches expected values
|
||||
const rows = findAll(GENERAL.tableRow());
|
||||
rows.forEach((_, idx) => {
|
||||
|
|
@ -85,45 +84,6 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
this.activity = await this.store.queryRecord('clients/activity', {});
|
||||
await this.renderComponent();
|
||||
assert.dom(CLIENT_COUNT.card('Client attribution')).doesNotExist('it does not render attribution card');
|
||||
assert.dom(GENERAL.selectByAttr('attribution-month')).doesNotExist('it hides months dropdown');
|
||||
});
|
||||
|
||||
test('it shows correct state message when selected month has no data', async function (assert) {
|
||||
await this.renderComponent();
|
||||
assert.dom(GENERAL.selectByAttr('attribution-month')).exists('shows month selection dropdown');
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), '2023-06-01T00:00:00Z');
|
||||
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('table empty state'))
|
||||
.hasText('No data found Clear or change filters to view client count data. Client count documentation');
|
||||
});
|
||||
|
||||
test('it shows table when month selection has data', async function (assert) {
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(GENERAL.selectByAttr('attribution-month')).exists('shows month selection dropdown');
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), '9/23');
|
||||
|
||||
assert.dom(CLIENT_COUNT.card('table empty state')).doesNotExist('does not show card when table has data');
|
||||
assert.dom(GENERAL.table('attribution')).exists('shows table');
|
||||
assert.dom(GENERAL.paginationInfo).hasText('1–6 of 6', 'shows correct pagination info');
|
||||
assert.dom(GENERAL.paginationSizeSelector).hasValue('10', 'page size selector defaults to "10"');
|
||||
});
|
||||
|
||||
test('it shows correct month options for billing period', async function (assert) {
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(GENERAL.selectByAttr('attribution-month')).exists('shows month selection dropdown');
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), '');
|
||||
await triggerEvent(GENERAL.selectByAttr('attribution-month'), 'change');
|
||||
|
||||
// assert that months options in select are those of selected billing period
|
||||
// '' represents default state of 'Select month'
|
||||
const expectedOptions = ['', ...this.activity.byMonth.reverse().map((m) => m.timestamp)];
|
||||
const actualOptions = findAll(`${GENERAL.selectByAttr('attribution-month')} option`).map(
|
||||
(option) => option.value
|
||||
);
|
||||
assert.deepEqual(actualOptions, expectedOptions, 'All <option> values match expected list');
|
||||
});
|
||||
|
||||
test('it initially renders attribution with by_namespace data', async function (assert) {
|
||||
|
|
@ -136,18 +96,22 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
});
|
||||
|
||||
test('it renders dropdown lists from activity response to filter table data', async function (assert) {
|
||||
const mounts = flattenMounts(this.mostRecentMonth.new_clients.namespaces);
|
||||
const expectedMonths = this.activity.byMonth
|
||||
.map((m) => parseAPITimestamp(m.timestamp, 'MMMM yyyy'))
|
||||
.reverse();
|
||||
const mounts = flattenMounts(this.activity.byNamespace);
|
||||
const expectedNamespaces = [...new Set(mounts.map((m) => m.namespace_path))];
|
||||
const expectedMountPaths = [...new Set(mounts.map((m) => m.mount_path))];
|
||||
const expectedMountTypes = [...new Set(mounts.map((m) => m.mount_type))];
|
||||
await this.renderComponent();
|
||||
// Assert filters do not exist until month is selected
|
||||
assert.dom(FILTERS.dropdownToggle(ClientFilters.NAMESPACE)).doesNotExist();
|
||||
assert.dom(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH)).doesNotExist();
|
||||
assert.dom(FILTERS.dropdownToggle(ClientFilters.MOUNT_TYPE)).doesNotExist();
|
||||
// Select month
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.mostRecentMonth.timestamp);
|
||||
|
||||
// Select each filter
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.MONTH));
|
||||
findAll(`${FILTERS.dropdown(ClientFilters.MONTH)} li button`).forEach((item, idx) => {
|
||||
const expected = expectedMonths[idx];
|
||||
assert.dom(item).hasText(expected, `month dropdown renders: ${expected}`);
|
||||
});
|
||||
|
||||
await click(FILTERS.dropdownToggle(ClientFilters.NAMESPACE));
|
||||
findAll(`${FILTERS.dropdown(ClientFilters.NAMESPACE)} li button`).forEach((item, idx) => {
|
||||
const expected = expectedNamespaces[idx];
|
||||
|
|
@ -169,13 +133,23 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
|
||||
// * FILTERING ASSERTIONS
|
||||
// Filtering tests are split between integration and acceptance tests
|
||||
// because changing filters updates the URL query params/
|
||||
test('it filters attribution table by month', async function (assert) {
|
||||
// because changing filters updates the URL query params
|
||||
|
||||
test('it shows correct empty state message when selected month has no data', async function (assert) {
|
||||
this.filterQueryParams[ClientFilters.MONTH] = '2023-06-01T00:00:00Z';
|
||||
await this.renderComponent();
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('table empty state'))
|
||||
.hasText('No data found Clear or change filters to view client count data. Client count documentation');
|
||||
});
|
||||
|
||||
test('it filters data if @filterQueryParams specify a month', async function (assert) {
|
||||
const filterKey = 'month';
|
||||
const filterValue = this.mostRecentMonth.timestamp;
|
||||
this.filterQueryParams[filterKey] = filterValue;
|
||||
await this.renderComponent();
|
||||
const mostRecentMonth = this.mostRecentMonth;
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), mostRecentMonth.timestamp);
|
||||
// Drill down to new_clients then grab the first mount
|
||||
const sortedMounts = flattenMounts(mostRecentMonth.new_clients.namespaces).sort(
|
||||
const sortedMounts = flattenMounts(this.mostRecentMonth.new_clients.namespaces).sort(
|
||||
(a, b) => b.clients - a.clients
|
||||
);
|
||||
const topMount = sortedMounts[0];
|
||||
|
|
@ -184,29 +158,9 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
assert.dom(GENERAL.tableData(0, 'mount_path')).hasText(topMount.mount_path);
|
||||
});
|
||||
|
||||
test('it resets pagination when a month is selected change', async function (assert) {
|
||||
const attributionByMount = flattenMounts(this.activity.byNamespace);
|
||||
await this.renderComponent();
|
||||
// Decrease page size for test so we don't have to seed more data
|
||||
await fillIn(GENERAL.paginationSizeSelector, '5');
|
||||
assert.dom(GENERAL.paginationInfo).hasText(`1–5 of ${attributionByMount.length}`);
|
||||
// Change pages because we should go back to page 1 when a month is selected
|
||||
await click(GENERAL.nextPage);
|
||||
assert.dom(GENERAL.tableRow()).exists({ count: 1 }, '1 row render');
|
||||
assert.dom(GENERAL.paginationInfo).hasText(`6–6 of ${attributionByMount.length}`);
|
||||
// Select a month and assert table resets to page 1
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.mostRecentMonth.timestamp);
|
||||
const monthMounts = flattenMounts(this.mostRecentMonth.new_clients.namespaces);
|
||||
assert
|
||||
.dom(GENERAL.paginationInfo)
|
||||
.hasText(`1–5 of ${monthMounts.length}`, 'pagination resets to page one');
|
||||
assert.dom(GENERAL.tableRow()).exists({ count: 5 }, '5 rows render');
|
||||
assert.dom(GENERAL.paginationSizeSelector).hasValue('5', 'size selector does not reset to 10');
|
||||
});
|
||||
|
||||
test('it filters data if @filterQueryParams specify a namespace_path', async function (assert) {
|
||||
const filterKey = 'namespace_path';
|
||||
const filterValue = 'ns1';
|
||||
const filterValue = 'ns1/';
|
||||
this.filterQueryParams[filterKey] = filterValue;
|
||||
await this.renderComponent();
|
||||
await this.assertTableData(assert, filterKey, filterValue);
|
||||
|
|
@ -214,7 +168,7 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
|
||||
test('it filters data if @filterQueryParams specify a mount_path', async function (assert) {
|
||||
const filterKey = 'mount_path';
|
||||
const filterValue = 'acme/pki/0';
|
||||
const filterValue = 'acme/pki/0/';
|
||||
this.filterQueryParams[filterKey] = filterValue;
|
||||
await this.renderComponent();
|
||||
await this.assertTableData(assert, filterKey, filterValue);
|
||||
|
|
@ -230,14 +184,14 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
|
||||
test('it filters data if @filterQueryParams specify a multiple filters', async function (assert) {
|
||||
this.filterQueryParams = {
|
||||
namespace_path: 'ns1',
|
||||
mount_path: 'auth/userpass/0',
|
||||
month: this.mostRecentMonth.timestamp,
|
||||
namespace_path: 'ns1/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
};
|
||||
|
||||
const { namespace_path, mount_path, mount_type } = this.filterQueryParams;
|
||||
await this.renderComponent();
|
||||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.mostRecentMonth.timestamp);
|
||||
const expectedData = flattenMounts(this.mostRecentMonth.new_clients.namespaces).find(
|
||||
(d) => d.namespace_path === namespace_path && d.mount_path === mount_path && d.mount_type === mount_type
|
||||
);
|
||||
|
|
|
|||
|
|
@ -173,8 +173,8 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
|
||||
test('formatByNamespace: it formats namespace array with mounts', async function (assert) {
|
||||
const original = [...RESPONSE.by_namespace];
|
||||
const expectedNs1 = SERIALIZED_ACTIVITY_RESPONSE.by_namespace.find((ns) => ns.label === 'ns1');
|
||||
const formattedNs1 = formatByNamespace(RESPONSE.by_namespace).find((ns) => ns.label === 'ns1');
|
||||
const expectedNs1 = SERIALIZED_ACTIVITY_RESPONSE.by_namespace.find((ns) => ns.label === 'ns1/');
|
||||
const formattedNs1 = formatByNamespace(RESPONSE.by_namespace).find((ns) => ns.label === 'ns1/');
|
||||
assert.expect(2 + formattedNs1.mounts.length);
|
||||
|
||||
assert.propEqual(formattedNs1, expectedNs1, 'it formats ns1/ namespace');
|
||||
|
|
@ -295,8 +295,8 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
acme_clients: 0,
|
||||
clients: 1,
|
||||
entity_clients: 1,
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
namespace_path: 'root',
|
||||
non_entity_clients: 0,
|
||||
|
|
@ -338,8 +338,8 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
acme_clients: 0,
|
||||
clients: 1,
|
||||
entity_clients: 1,
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
namespace_path: 'root',
|
||||
non_entity_clients: 0,
|
||||
|
|
@ -398,10 +398,10 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
|
||||
const mountPathFilter = filterTableData(this.mockMountData, {
|
||||
namespace_path: '',
|
||||
mount_path: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: '',
|
||||
});
|
||||
const expectedMountPathFilter = this.mockMountData.filter((m) => m.mount_path === 'acme/pki/0');
|
||||
const expectedMountPathFilter = this.mockMountData.filter((m) => m.mount_path === 'acme/pki/0/');
|
||||
assert.propEqual(mountPathFilter, expectedMountPathFilter, 'it filters by mount_path');
|
||||
this.assertOriginal(assert);
|
||||
|
||||
|
|
@ -429,13 +429,13 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
|
||||
const allFilters = filterTableData(this.mockMountData, {
|
||||
namespace_path: 'root',
|
||||
mount_path: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
});
|
||||
const expectedAllFilters = [
|
||||
{
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
namespace_path: 'root',
|
||||
acme_clients: 0,
|
||||
|
|
@ -476,8 +476,8 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
acme_clients: 0,
|
||||
clients: 8091,
|
||||
entity_clients: 4002,
|
||||
label: 'auth/userpass/0',
|
||||
mount_path: 'auth/userpass/0',
|
||||
label: 'auth/userpass/0/',
|
||||
mount_path: 'auth/userpass/0/',
|
||||
mount_type: 'userpass',
|
||||
namespace_path: 'root',
|
||||
non_entity_clients: 4089,
|
||||
|
|
@ -487,8 +487,8 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
acme_clients: 0,
|
||||
clients: 4290,
|
||||
entity_clients: 0,
|
||||
label: 'secrets/kv/0',
|
||||
mount_path: 'secrets/kv/0',
|
||||
label: 'secrets/kv/0/',
|
||||
mount_path: 'secrets/kv/0/',
|
||||
mount_type: 'kv',
|
||||
namespace_path: 'root',
|
||||
non_entity_clients: 0,
|
||||
|
|
@ -498,16 +498,16 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
acme_clients: 4003,
|
||||
clients: 4003,
|
||||
entity_clients: 0,
|
||||
label: 'acme/pki/0',
|
||||
mount_path: 'acme/pki/0',
|
||||
label: 'acme/pki/0/',
|
||||
mount_path: 'acme/pki/0/',
|
||||
mount_type: 'pki',
|
||||
namespace_path: 'root',
|
||||
non_entity_clients: 0,
|
||||
secret_syncs: 0,
|
||||
},
|
||||
{
|
||||
client_first_used_time: '2025-08-15T23:48:09Z',
|
||||
client_id: '5692c6ef-c871-128e-fb06-df2be7bfc0db',
|
||||
client_first_used_time: '2025-07-15T23:48:09Z',
|
||||
client_id: 'daf8420c-0b6b-34e6-ff38-ee1ed093bea9',
|
||||
client_type: 'entity',
|
||||
entity_alias_custom_metadata: {},
|
||||
entity_alias_metadata: {},
|
||||
|
|
@ -522,7 +522,7 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
namespace_id: 'root',
|
||||
namespace_path: '',
|
||||
policies: [],
|
||||
token_creation_time: '2025-08-15T23:48:09Z',
|
||||
token_creation_time: '2020-08-15T23:48:09Z',
|
||||
},
|
||||
{
|
||||
client_first_used_time: '2025-08-15T23:53:19Z',
|
||||
|
|
@ -546,10 +546,10 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
namespace_id: 'root',
|
||||
namespace_path: '',
|
||||
policies: ['base'],
|
||||
token_creation_time: '2025-08-15T23:52:38Z',
|
||||
token_creation_time: '2020-08-15T23:52:38Z',
|
||||
},
|
||||
{
|
||||
client_first_used_time: '2025-08-16T09:16:03Z',
|
||||
client_first_used_time: '2025-09-16T09:16:03Z',
|
||||
client_id: 'a7c8d912-4f61-23b5-88e4-627a3dcf2b92',
|
||||
client_type: 'entity',
|
||||
entity_alias_custom_metadata: {
|
||||
|
|
@ -573,10 +573,10 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
namespace_id: 'root',
|
||||
namespace_path: '',
|
||||
policies: ['admin', 'audit'],
|
||||
token_creation_time: '2025-08-16T09:15:42Z',
|
||||
token_creation_time: '2020-08-16T09:15:42Z',
|
||||
},
|
||||
{
|
||||
client_first_used_time: '2025-08-17T16:44:12Z',
|
||||
client_first_used_time: '2025-12-17T16:44:12Z',
|
||||
client_id: 'c6b9d248-5a71-39e4-c7f2-951d8eaf6b95',
|
||||
client_type: 'entity',
|
||||
entity_alias_custom_metadata: {
|
||||
|
|
@ -602,14 +602,14 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||
namespace_id: 'root',
|
||||
namespace_path: '',
|
||||
policies: ['operations', 'monitoring'],
|
||||
token_creation_time: '2025-08-17T16:43:28Z',
|
||||
token_creation_time: '2020-08-17T16:43:28Z',
|
||||
},
|
||||
];
|
||||
assert.propEqual(
|
||||
filteredData,
|
||||
expected,
|
||||
"filtered data includes items with namespace_path equal to either 'root' or an empty string"
|
||||
);
|
||||
|
||||
filteredData.forEach((d, idx) => {
|
||||
const identifier = idx < 3 ? `label: ${d.label}` : `client_id: ${d.client_id}`;
|
||||
assert.propEqual(d, expected[idx], `filtered data contains ${identifier}`);
|
||||
});
|
||||
this.assertOriginal(assert);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
import { buildISOTimestamp, isSameMonthUTC, parseAPITimestamp } from 'core/utils/date-formatters';
|
||||
|
||||
module('Integration | Util | date formatters utils', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
|
@ -14,9 +14,14 @@ module('Integration | Util | date formatters utils', function (hooks) {
|
|||
const timestamp = '2025-05-01T00:00:00Z';
|
||||
const parsed = parseAPITimestamp(timestamp);
|
||||
assert.true(parsed instanceof Date, 'parsed timestamp is a date object');
|
||||
assert.strictEqual(parsed.getFullYear(), 2025, 'parsed timestamp is correct year');
|
||||
assert.strictEqual(parsed.getMonth(), 4, 'parsed timestamp is correct month (months are 0-indexed)');
|
||||
assert.strictEqual(parsed.getDate(), 1, 'parsed timestamp is first of the month');
|
||||
assert.strictEqual(parsed.getUTCFullYear(), 2025, 'parsed timestamp is correct year');
|
||||
assert.strictEqual(parsed.getUTCMonth(), 4, 'parsed timestamp is correct month (months are 0-indexed)');
|
||||
assert.strictEqual(parsed.getUTCDate(), 1, 'parsed timestamp is first of the month');
|
||||
assert.strictEqual(
|
||||
parsed.toISOString().replace('.000', ''),
|
||||
timestamp,
|
||||
'parsed ISO is the same date (in UTC)'
|
||||
);
|
||||
});
|
||||
|
||||
test('parseAPITimestamp: it formats midnight timestamps in UTC', async function (assert) {
|
||||
|
|
@ -26,14 +31,93 @@ module('Integration | Util | date formatters utils', function (hooks) {
|
|||
assert.strictEqual(formatted, '05 01 2025', 'it returns the expected year, month and day');
|
||||
});
|
||||
|
||||
test('parseAPITimestamp: it returns the original value if timestamp is not a string', async function (assert) {
|
||||
const unix = new Date().getTime();
|
||||
assert.strictEqual(parseAPITimestamp(unix), unix);
|
||||
});
|
||||
|
||||
test('parseAPITimestamp: it formats end of the day timestamps in UTC', async function (assert) {
|
||||
const timestamp = '2025-09-30T23:59:59Z';
|
||||
const formatted = parseAPITimestamp(timestamp, 'MM dd yyyy');
|
||||
assert.strictEqual(formatted, '09 30 2025', 'it formats the date in UTC');
|
||||
});
|
||||
|
||||
test('parseAPITimestamp: it returns null for invalid timestamps', function (assert) {
|
||||
const unix = new Date().getTime();
|
||||
assert.strictEqual(parseAPITimestamp(unix), null, 'it returns null for unix arg');
|
||||
assert.strictEqual(parseAPITimestamp(null), null, 'it returns null for null arg');
|
||||
assert.strictEqual(parseAPITimestamp(undefined), null, 'it returns null for undefined arg');
|
||||
assert.strictEqual(parseAPITimestamp(''), null, 'it returns null for an empty string arg');
|
||||
assert.strictEqual(parseAPITimestamp('invalid'), null, 'it returns null for an invalid string');
|
||||
});
|
||||
|
||||
test('parseAPITimestamp: it handles future dates to prep for the next y2k', function (assert) {
|
||||
const futureDate = '9999-12-31T23:59:59Z';
|
||||
const parsed = parseAPITimestamp(futureDate);
|
||||
assert.true(parsed instanceof Date, 'parsed future date is a date object');
|
||||
assert.strictEqual(parsed.getUTCFullYear(), 9999, 'parsed future date has correct year');
|
||||
assert.strictEqual(parsed.getUTCMonth(), 11, 'parsed future date has correct month');
|
||||
assert.strictEqual(parsed.getUTCDate(), 31, 'parsed future date has correct day');
|
||||
});
|
||||
|
||||
test('buildISOTimestamp: it formats an ISO timestamp for the start of the month', async function (assert) {
|
||||
const timestamp = buildISOTimestamp({ monthIdx: 0, year: 2025, isEndDate: false });
|
||||
assert.strictEqual(
|
||||
timestamp,
|
||||
'2025-01-01T00:00:00Z',
|
||||
'it returns an ISO string for the first of the month at midnight'
|
||||
);
|
||||
});
|
||||
|
||||
test('buildISOTimestamp: it formats an ISO timestamp for the end of the month', async function (assert) {
|
||||
const timestamp = buildISOTimestamp({ monthIdx: 0, year: 2025, isEndDate: true });
|
||||
assert.strictEqual(timestamp, '2025-01-31T23:59:59Z', 'ISO string is for the last day and hour');
|
||||
});
|
||||
|
||||
test('buildISOTimestamp: it formats an ISO timestamp for leap years', async function (assert) {
|
||||
const timestamp = buildISOTimestamp({ monthIdx: 1, year: 2024, isEndDate: true });
|
||||
assert.strictEqual(timestamp, '2024-02-29T23:59:59Z');
|
||||
});
|
||||
|
||||
test('isSameMonthUTC: it returns true for timestamps in the same month', function (assert) {
|
||||
const timestampA = '2025-07-01T00:00:00Z';
|
||||
const timestampB = '2025-07-15T23:48:09Z';
|
||||
assert.true(isSameMonthUTC(timestampA, timestampB));
|
||||
});
|
||||
|
||||
test('isSameMonthUTC: it returns false for timestamps in different months', function (assert) {
|
||||
const timestampA = '2025-09-01T00:00:00Z';
|
||||
const timestampB = '2025-07-15T23:48:09Z';
|
||||
assert.false(isSameMonthUTC(timestampA, timestampB));
|
||||
});
|
||||
|
||||
test('isSameMonthUTC: it returns false for timestamps in different years', function (assert) {
|
||||
const timestampA = '2025-12-31T23:59:59Z';
|
||||
const timestampB = '2026-12-01T00:00:00Z';
|
||||
assert.false(isSameMonthUTC(timestampA, timestampB));
|
||||
});
|
||||
|
||||
test('isSameMonthUTC: it returns true when both timestamps are the same', function (assert) {
|
||||
const timestamp = '2025-07-15T23:48:09Z';
|
||||
assert.true(isSameMonthUTC(timestamp, timestamp));
|
||||
});
|
||||
|
||||
test('isSameMonthUTC: it returns true for timestamps on the first and last day of the month', function (assert) {
|
||||
const start = '2025-07-01T00:00:00Z';
|
||||
const end = '2025-07-31T23:59:59Z';
|
||||
assert.true(isSameMonthUTC(start, end));
|
||||
});
|
||||
|
||||
test('isSameMonthUTC: it returns false for timestamps a second apart on different days', function (assert) {
|
||||
const endJuly = '2025-07-31T23:59:59Z';
|
||||
const startAugust = '2025-08-01T00:00:00Z';
|
||||
assert.false(isSameMonthUTC(endJuly, startAugust));
|
||||
});
|
||||
|
||||
test('isSameMonthUTC: it returns true if passed a timestamp with a timezone offset', function (assert) {
|
||||
const utc = '2025-07-01T00:00:00Z';
|
||||
const localTimezone = '2025-07-01T07:00:00+07:00'; // same time in UTC+7
|
||||
assert.true(isSameMonthUTC(utc, localTimezone));
|
||||
});
|
||||
|
||||
test('isSameMonthUTC: it returns false for invalid inputs', function (assert) {
|
||||
assert.false(isSameMonthUTC(null, '2025-07-01T00:00:00Z'), 'null input returns false');
|
||||
assert.false(isSameMonthUTC('2025-07-01T00:00:00Z', undefined), 'undefined input returns false');
|
||||
assert.false(isSameMonthUTC(12345, '2025-07-01T00:00:00Z'), 'non-string input returns false');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue