mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 02:57:59 +00:00
UI: Upgrade Ember data 5.3.2 (and upgrade minor versions of ember-source and ember-cli) (#28798)
* upgrade ember-data 5.3.2, uninstall legacy compat, upgrade ember-cli, ember-source * use query instead of findAll for auth methods, update tests * set mutableId for kmip * show generated private key data before transitioning to details * update kv metadata test * remove deprecated methods from path help service * add changelog, update readme version matrix * remove toggle template helper
This commit is contained in:
3
changelog/28798.txt
Normal file
3
changelog/28798.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:change
|
||||||
|
ui: Upgrade Ember data to v5.3.2 (and minor upgrade of ember-cli, ember-source to v5.8.0)
|
||||||
|
```
|
||||||
25
ui/README.md
25
ui/README.md
@@ -24,20 +24,19 @@
|
|||||||
|
|
||||||
This README outlines the details of collaborating on this Ember application.
|
This README outlines the details of collaborating on this Ember application.
|
||||||
|
|
||||||
## Ember CLI Version Upgrade Matrix
|
## Ember Version Upgrade Matrix
|
||||||
|
|
||||||
| Vault Version | Ember Version |
|
Respective versions for `ember-cli`, `ember-source` and `ember-data` for each version of Vault that contains an upgrade.
|
||||||
| ------------- | ------------- |
|
|
||||||
| 1.17.x | 5.4.2 |
|
| Vault Version | Ember CLI | Ember Source | Ember Data |
|
||||||
| 1.15.x | 4.12.0 |
|
| ------------- | --------- | ------------ | ---------- |
|
||||||
| 1.14.x | 4.4.0 |
|
| 1.19.x | 5.8.0 | 5.8.0 | 5.3.2 |
|
||||||
| 1.13.x | 4.4.0 |
|
| 1.17.x | 5.4.2 | 5.4.0 | 4.12.4 |
|
||||||
| 1.12.x | 3.28.5 |
|
| 1.15.x | 4.12.1 | 4.12.0 | 4.11.3 |
|
||||||
| 1.11.x | 3.28.5 |
|
| 1.13.x | 4.4.0 | 4.4.4 | 4.5.0 |
|
||||||
| 1.10.x | 3.28.5 |
|
| 1.11.x | 3.28.5 | 3.28.10 | 3.28.6 |
|
||||||
| 1.9.x | 3.22.0 |
|
| 1.10.x | 3.24.0 | 3.24.7 | 3.24.0 |
|
||||||
| 1.8.x | 3.22.0 |
|
| 1.9.x | 3.22.0 | 3.22.0 | 3.22.0 |
|
||||||
| 1.7.x | 3.11.0 |
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
|
|||||||
@@ -23,26 +23,21 @@ export default ApplicationAdapter.extend({
|
|||||||
const isUnauthenticated = snapshotRecordArray?.adapterOptions?.unauthenticated;
|
const isUnauthenticated = snapshotRecordArray?.adapterOptions?.unauthenticated;
|
||||||
// sys/internal/ui/mounts returns the actual value of the system TTL
|
// sys/internal/ui/mounts returns the actual value of the system TTL
|
||||||
// instead of '0' which just indicates the mount is using system defaults
|
// instead of '0' which just indicates the mount is using system defaults
|
||||||
const useMountsEndpoint = snapshotRecordArray?.adapterOptions?.useMountsEndpoint;
|
if (isUnauthenticated) {
|
||||||
if (isUnauthenticated || useMountsEndpoint) {
|
|
||||||
const url = `/${this.urlPrefix()}/internal/ui/mounts`;
|
const url = `/${this.urlPrefix()}/internal/ui/mounts`;
|
||||||
return this.ajax(url, 'GET', {
|
return this.ajax(url, 'GET', {
|
||||||
unauthenticated: isUnauthenticated,
|
unauthenticated: true,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
return {
|
return {
|
||||||
data: result.data.auth,
|
data: result.data.auth,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch(() => {
|
||||||
if (isUnauthenticated) return { data: {} };
|
return { data: {} };
|
||||||
|
|
||||||
if (e instanceof AdapterError) {
|
|
||||||
set(e, 'policyPath', 'sys/internal/ui/mounts');
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// if authenticated, findAll will use GET sys/auth instead
|
||||||
return this.ajax(this.url(), 'GET').catch((e) => {
|
return this.ajax(this.url(), 'GET').catch((e) => {
|
||||||
if (e instanceof AdapterError) {
|
if (e instanceof AdapterError) {
|
||||||
set(e, 'policyPath', 'sys/auth');
|
set(e, 'policyPath', 'sys/auth');
|
||||||
@@ -51,6 +46,25 @@ export default ApplicationAdapter.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// findAll makes a network request and supplements the ember-data store with what the API returns.
|
||||||
|
// after upgrading to ember-data 5.3.2 the store was becoming cluttered with outdated records, so
|
||||||
|
// use query to refresh the store with each request. this is ideal for list views
|
||||||
|
query() {
|
||||||
|
const url = `/${this.urlPrefix()}/internal/ui/mounts`;
|
||||||
|
return this.ajax(url, 'GET')
|
||||||
|
.then((result) => {
|
||||||
|
return {
|
||||||
|
data: result.data.auth,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (e instanceof AdapterError) {
|
||||||
|
set(e, 'policyPath', 'sys/internal/ui/mounts');
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
createRecord(store, type, snapshot) {
|
createRecord(store, type, snapshot) {
|
||||||
const serializer = store.serializerFor(type.modelName);
|
const serializer = store.serializerFor(type.modelName);
|
||||||
const data = serializer.serialize(snapshot);
|
const data = serializer.serialize(snapshot);
|
||||||
|
|||||||
@@ -16,9 +16,18 @@ export default BaseAdapter.extend({
|
|||||||
return this._url(...arguments);
|
return this._url(...arguments);
|
||||||
},
|
},
|
||||||
urlForCreateRecord(modelName, snapshot) {
|
urlForCreateRecord(modelName, snapshot) {
|
||||||
return this._url(snapshot.id, modelName, snapshot);
|
const id = snapshot.record.mutableId;
|
||||||
|
return this._url(id, modelName, snapshot);
|
||||||
},
|
},
|
||||||
urlForUpdateRecord() {
|
urlForUpdateRecord() {
|
||||||
return this._url(...arguments);
|
return this._url(...arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createRecord(store, type, snapshot) {
|
||||||
|
return this._super(...arguments).then(() => {
|
||||||
|
// saving returns a 204, return object with id to please ember-data...
|
||||||
|
const id = snapshot.record.mutableId;
|
||||||
|
return { id };
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ export default Component.extend({
|
|||||||
),
|
),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
handleToggle(e) {
|
||||||
|
set(this.key, 'enterAsText', e.target.checked);
|
||||||
|
},
|
||||||
pickedFile(e) {
|
pickedFile(e) {
|
||||||
const { files } = e.target;
|
const { files } = e.target;
|
||||||
if (!files.length) {
|
if (!files.length) {
|
||||||
|
|||||||
@@ -15,28 +15,26 @@ export default Route.extend({
|
|||||||
|
|
||||||
model(params) {
|
model(params) {
|
||||||
const { path } = params;
|
const { path } = params;
|
||||||
return this.store
|
return this.store.query('auth-method', {}).then((modelArray) => {
|
||||||
.findAll('auth-method', { adapterOptions: { useMountsEndpoint: true } })
|
const model = modelArray.find((m) => m.id === path);
|
||||||
.then((modelArray) => {
|
if (!model) {
|
||||||
const model = modelArray.find((m) => m.id === path);
|
const error = new AdapterError();
|
||||||
if (!model) {
|
set(error, 'httpStatus', 404);
|
||||||
const error = new AdapterError();
|
throw error;
|
||||||
set(error, 'httpStatus', 404);
|
}
|
||||||
throw error;
|
const supportManaged = supportedManagedAuthBackends();
|
||||||
}
|
if (!supportManaged.includes(model.methodType)) {
|
||||||
const supportManaged = supportedManagedAuthBackends();
|
// do not fetch path-help for unmanaged auth types
|
||||||
if (!supportManaged.includes(model.methodType)) {
|
model.set('paths', {
|
||||||
// do not fetch path-help for unmanaged auth types
|
apiPath: model.apiPath,
|
||||||
model.set('paths', {
|
paths: [],
|
||||||
apiPath: model.apiPath,
|
|
||||||
paths: [],
|
|
||||||
});
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
return this.pathHelp.getPaths(model.apiPath, path).then((paths) => {
|
|
||||||
model.set('paths', paths);
|
|
||||||
return model;
|
|
||||||
});
|
});
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
return this.pathHelp.getPaths(model.apiPath, path).then((paths) => {
|
||||||
|
model.set('paths', paths);
|
||||||
|
return model;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ export default class VaultClusterAccessMethodsRoute extends Route {
|
|||||||
};
|
};
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
return this.store.findAll('auth-method');
|
return this.store.query('auth-method', {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default Route.extend(UnloadModelRoute, {
|
|||||||
// if you haven't saved a config, the API 404s, so create one here to edit and return it
|
// if you haven't saved a config, the API 404s, so create one here to edit and return it
|
||||||
if (e.httpStatus === 404) {
|
if (e.httpStatus === 404) {
|
||||||
config = this.store.createRecord(modelType, {
|
config = this.store.createRecord(modelType, {
|
||||||
id: backend.id,
|
mutableId: backend.id,
|
||||||
});
|
});
|
||||||
config.set('backend', backend);
|
config.set('backend', backend);
|
||||||
|
|
||||||
|
|||||||
@@ -49,13 +49,6 @@ export default class PathHelpService extends Service {
|
|||||||
|
|
||||||
// bust cache in EmberData's model lookup
|
// bust cache in EmberData's model lookup
|
||||||
delete store._modelFactoryCache[modelType];
|
delete store._modelFactoryCache[modelType];
|
||||||
|
|
||||||
// bust cache in schema service
|
|
||||||
const schemas = store.getSchemaDefinitionService?.();
|
|
||||||
if (schemas) {
|
|
||||||
delete schemas._relationshipsDefCache[modelType];
|
|
||||||
delete schemas._attributesDefCache[modelType];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
name={{concat "useText-" this.elementId}}
|
name={{concat "useText-" this.elementId}}
|
||||||
class="toggle is-success is-small"
|
class="toggle is-success is-small"
|
||||||
checked={{this.key.enterAsText}}
|
checked={{this.key.enterAsText}}
|
||||||
onchange={{action (toggle "enterAsText" this.key)}}
|
onchange={{action "handleToggle"}}
|
||||||
/>
|
/>
|
||||||
<label for={{concat "useText-" this.elementId}} class="has-text-weight-bold is-size-8">
|
<label for={{concat "useText-" this.elementId}} class="has-text-weight-bold is-size-8">
|
||||||
Enter as text
|
Enter as text
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default Route.extend({
|
|||||||
return this.store.findRecord('kmip/config', this.secretMountPath.currentPath).catch((err) => {
|
return this.store.findRecord('kmip/config', this.secretMountPath.currentPath).catch((err) => {
|
||||||
if (err.httpStatus === 404) {
|
if (err.httpStatus === 404) {
|
||||||
const model = this.store.createRecord('kmip/config');
|
const model = this.store.createRecord('kmip/config');
|
||||||
model.set('id', this.secretMountPath.currentPath);
|
model.set('mutableId', this.secretMountPath.currentPath);
|
||||||
return model;
|
return model;
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
@@ -3,62 +3,71 @@
|
|||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
~}}
|
~}}
|
||||||
|
|
||||||
<form {{on "submit" (perform this.save)}}>
|
{{! private_key is only available after initial save }}
|
||||||
<div class="box is-sideless is-fullwidth is-marginless">
|
{{#if this.generatedKey.privateKey}}
|
||||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
<Page::PkiKeyDetails
|
||||||
<NamespaceReminder @mode={{if @model.isNew "generate" "update"}} @noun="PKI key" />
|
@key={{this.generatedKey}}
|
||||||
{{#if @model.isNew}}
|
@canDelete={{this.generatedKey.canDelete}}
|
||||||
{{#each @model.formFieldGroups as |fieldGroup|}}
|
@canEdit={{this.generatedKey.canEdit}}
|
||||||
{{#each-in fieldGroup as |group fields|}}
|
/>
|
||||||
{{#if (eq group "Key parameters")}}
|
{{else}}
|
||||||
<PkiKeyParameters @model={{@model}} @fields={{fields}} @modelValidations={{this.modelValidations}} />
|
<form {{on "submit" (perform this.save)}}>
|
||||||
{{else}}
|
<div class="box is-sideless is-fullwidth is-marginless">
|
||||||
{{#each fields as |attr|}}
|
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||||
<FormField
|
<NamespaceReminder @mode={{if @model.isNew "generate" "update"}} @noun="PKI key" />
|
||||||
data-test-field={{attr}}
|
{{#if @model.isNew}}
|
||||||
@attr={{attr}}
|
{{#each @model.formFieldGroups as |fieldGroup|}}
|
||||||
@model={{@model}}
|
{{#each-in fieldGroup as |group fields|}}
|
||||||
@modelValidations={{this.modelValidations}}
|
{{#if (eq group "Key parameters")}}
|
||||||
@showHelpText={{false}}
|
<PkiKeyParameters @model={{@model}} @fields={{fields}} @modelValidations={{this.modelValidations}} />
|
||||||
/>
|
{{else}}
|
||||||
{{/each}}
|
{{#each fields as |attr|}}
|
||||||
{{/if}}
|
<FormField
|
||||||
{{/each-in}}
|
data-test-field={{attr}}
|
||||||
{{/each}}
|
@attr={{attr}}
|
||||||
{{else}}
|
@model={{@model}}
|
||||||
{{! only key name is edit-able }}
|
@modelValidations={{this.modelValidations}}
|
||||||
{{#let (find-by "name" "keyName" @model.formFields) as |keyName|}}
|
@showHelpText={{false}}
|
||||||
<FormField data-test-field={{keyName}} @attr={{keyName}} @model={{@model}} @showHelpText={{false}} />
|
/>
|
||||||
{{/let}}
|
{{/each}}
|
||||||
{{#let (find-by "name" "keyType" @model.formFields) as |keyType|}}
|
{{/if}}
|
||||||
<ReadonlyFormField @attr={{keyType}} @value={{@model.keyType}} />
|
{{/each-in}}
|
||||||
{{/let}}
|
{{/each}}
|
||||||
{{/if}}
|
{{else}}
|
||||||
</div>
|
{{! only key name is edit-able }}
|
||||||
<Hds::ButtonSet class="has-top-padding-s">
|
{{#let (find-by "name" "keyName" @model.formFields) as |keyName|}}
|
||||||
<Hds::Button
|
<FormField data-test-field={{keyName}} @attr={{keyName}} @model={{@model}} @showHelpText={{false}} />
|
||||||
@text={{if @model.isNew "Generate key" "Edit key"}}
|
{{/let}}
|
||||||
@icon={{if this.save.isRunning "loading"}}
|
{{#let (find-by "name" "keyType" @model.formFields) as |keyType|}}
|
||||||
type="submit"
|
<ReadonlyFormField @attr={{keyType}} @value={{@model.keyType}} />
|
||||||
disabled={{this.save.isRunning}}
|
{{/let}}
|
||||||
data-test-save
|
{{/if}}
|
||||||
/>
|
|
||||||
<Hds::Button
|
|
||||||
@text="Cancel"
|
|
||||||
@color="secondary"
|
|
||||||
disabled={{this.save.isRunning}}
|
|
||||||
{{on "click" @onCancel}}
|
|
||||||
data-test-cancel
|
|
||||||
/>
|
|
||||||
</Hds::ButtonSet>
|
|
||||||
{{#if this.invalidFormAlert}}
|
|
||||||
<div class="control">
|
|
||||||
<AlertInline
|
|
||||||
@type="danger"
|
|
||||||
class="has-top-padding-s"
|
|
||||||
@message={{this.invalidFormAlert}}
|
|
||||||
data-test-pki-key-validation-error
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
<Hds::ButtonSet class="has-top-padding-s">
|
||||||
</form>
|
<Hds::Button
|
||||||
|
@text={{if @model.isNew "Generate key" "Edit key"}}
|
||||||
|
@icon={{if this.save.isRunning "loading"}}
|
||||||
|
type="submit"
|
||||||
|
disabled={{this.save.isRunning}}
|
||||||
|
data-test-save
|
||||||
|
/>
|
||||||
|
<Hds::Button
|
||||||
|
@text="Cancel"
|
||||||
|
@color="secondary"
|
||||||
|
disabled={{this.save.isRunning}}
|
||||||
|
{{on "click" @onCancel}}
|
||||||
|
data-test-cancel
|
||||||
|
/>
|
||||||
|
</Hds::ButtonSet>
|
||||||
|
{{#if this.invalidFormAlert}}
|
||||||
|
<div class="control">
|
||||||
|
<AlertInline
|
||||||
|
@type="danger"
|
||||||
|
class="has-top-padding-s"
|
||||||
|
@message={{this.invalidFormAlert}}
|
||||||
|
data-test-pki-key-validation-error
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</form>
|
||||||
|
{{/if}}
|
||||||
@@ -39,6 +39,8 @@ export default class PkiKeyForm extends Component<Args> {
|
|||||||
@tracked invalidFormAlert = '';
|
@tracked invalidFormAlert = '';
|
||||||
@tracked modelValidations: ValidationMap | null = null;
|
@tracked modelValidations: ValidationMap | null = null;
|
||||||
|
|
||||||
|
@tracked generatedKey: PkiKeyModel | null = null;
|
||||||
|
|
||||||
@task
|
@task
|
||||||
@waitFor
|
@waitFor
|
||||||
*save(event: Event) {
|
*save(event: Event) {
|
||||||
@@ -51,11 +53,15 @@ export default class PkiKeyForm extends Component<Args> {
|
|||||||
this.invalidFormAlert = invalidFormMessage;
|
this.invalidFormAlert = invalidFormMessage;
|
||||||
}
|
}
|
||||||
if (!isValid && isNew) return;
|
if (!isValid && isNew) return;
|
||||||
yield this.args.model.save({ adapterOptions: { import: false } });
|
this.generatedKey = yield this.args.model.save({ adapterOptions: { import: false } });
|
||||||
this.flashMessages.success(
|
this.flashMessages.success(
|
||||||
`Successfully ${isNew ? 'generated' : 'updated'} key${keyName ? ` ${keyName}.` : '.'}`
|
`Successfully ${isNew ? 'generated' : 'updated'} key${keyName ? ` ${keyName}.` : '.'}`
|
||||||
);
|
);
|
||||||
this.args.onSave();
|
|
||||||
|
// only transition to details if there is no private_key data to display
|
||||||
|
if (!this.generatedKey?.privateKey) {
|
||||||
|
this.args.onSave();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorBanner = errorMessage(error);
|
this.errorBanner = errorMessage(error);
|
||||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||||
|
|||||||
@@ -36,4 +36,10 @@ export default class PkiRolesCreateRoute extends Route {
|
|||||||
{ label: 'create' },
|
{ label: 'create' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
willTransition() {
|
||||||
|
// after upgrading to Ember Data 5.3.2 we saw duplicate records in the store after creating and saving a new role
|
||||||
|
// it's unclear why this ghost record is persisting, manually unloading refreshes the store
|
||||||
|
this.store.unloadAll('pki/role');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,6 @@
|
|||||||
"@babel/preset-env": "^7.24.6",
|
"@babel/preset-env": "^7.24.6",
|
||||||
"@babel/preset-typescript": "^7.24.6",
|
"@babel/preset-typescript": "^7.24.6",
|
||||||
"@docfy/ember": "^0.8.5",
|
"@docfy/ember": "^0.8.5",
|
||||||
"@ember-data/legacy-compat": "~4.12.4",
|
|
||||||
"@ember/legacy-built-in-components": "^0.4.1",
|
"@ember/legacy-built-in-components": "^0.4.1",
|
||||||
"@ember/optional-features": "^2.0.0",
|
"@ember/optional-features": "^2.0.0",
|
||||||
"@ember/render-modifiers": "^1.0.2",
|
"@ember/render-modifiers": "^1.0.2",
|
||||||
@@ -106,7 +105,7 @@
|
|||||||
"dompurify": "^3.0.2",
|
"dompurify": "^3.0.2",
|
||||||
"ember-a11y-testing": "^7.0.1",
|
"ember-a11y-testing": "^7.0.1",
|
||||||
"ember-basic-dropdown": "^8.0.4",
|
"ember-basic-dropdown": "^8.0.4",
|
||||||
"ember-cli": "~5.4.2",
|
"ember-cli": "~5.8.0",
|
||||||
"ember-cli-babel": "^8.2.0",
|
"ember-cli-babel": "^8.2.0",
|
||||||
"ember-cli-clean-css": "^3.0.0",
|
"ember-cli-clean-css": "^3.0.0",
|
||||||
"ember-cli-content-security-policy": "2.0.3",
|
"ember-cli-content-security-policy": "2.0.3",
|
||||||
@@ -123,7 +122,7 @@
|
|||||||
"ember-cli-terser": "^4.0.2",
|
"ember-cli-terser": "^4.0.2",
|
||||||
"ember-composable-helpers": "5.0.0",
|
"ember-composable-helpers": "5.0.0",
|
||||||
"ember-concurrency": "^4.0.2",
|
"ember-concurrency": "^4.0.2",
|
||||||
"ember-data": "~4.12.4",
|
"ember-data": "~5.3.2",
|
||||||
"ember-engines": "0.8.23",
|
"ember-engines": "0.8.23",
|
||||||
"ember-exam": "^9.0.0",
|
"ember-exam": "^9.0.0",
|
||||||
"ember-inflector": "4.0.2",
|
"ember-inflector": "4.0.2",
|
||||||
@@ -138,7 +137,7 @@
|
|||||||
"ember-responsive": "5.0.0",
|
"ember-responsive": "5.0.0",
|
||||||
"ember-service-worker": "meirish/ember-service-worker#configurable-scope",
|
"ember-service-worker": "meirish/ember-service-worker#configurable-scope",
|
||||||
"ember-sinon-qunit": "^7.4.0",
|
"ember-sinon-qunit": "^7.4.0",
|
||||||
"ember-source": "~5.4.0",
|
"ember-source": "~5.8.0",
|
||||||
"ember-style-modifier": "^4.1.0",
|
"ember-style-modifier": "^4.1.0",
|
||||||
"ember-svg-jar": "2.4.4",
|
"ember-svg-jar": "2.4.4",
|
||||||
"ember-template-lint": "^6.0.0",
|
"ember-template-lint": "^6.0.0",
|
||||||
|
|||||||
@@ -3,22 +3,17 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { currentRouteName, click } from '@ember/test-helpers';
|
import { currentRouteName, click, find, findAll, visit } from '@ember/test-helpers';
|
||||||
import { clickTrigger } from 'ember-power-select/test-support/helpers';
|
import { clickTrigger } from 'ember-power-select/test-support/helpers';
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupApplicationTest } from 'ember-qunit';
|
import { setupApplicationTest } from 'ember-qunit';
|
||||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
import { create } from 'ember-cli-page-object';
|
|
||||||
import page from 'vault/tests/pages/access/methods';
|
|
||||||
import authEnable from 'vault/tests/pages/settings/auth/enable';
|
|
||||||
import authPage from 'vault/tests/pages/auth';
|
|
||||||
import ss from 'vault/tests/pages/components/search-select';
|
|
||||||
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
|
|
||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||||
|
import { mountAuthCmd, runCmd } from 'vault/tests/helpers/commands';
|
||||||
|
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
||||||
|
|
||||||
const consoleComponent = create(consoleClass);
|
const { searchSelect } = GENERAL;
|
||||||
const searchSelect = create(ss);
|
|
||||||
|
|
||||||
module('Acceptance | auth-methods list view', function (hooks) {
|
module('Acceptance | auth-methods list view', function (hooks) {
|
||||||
setupApplicationTest(hooks);
|
setupApplicationTest(hooks);
|
||||||
@@ -26,14 +21,13 @@ module('Acceptance | auth-methods list view', function (hooks) {
|
|||||||
|
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
this.uid = uuidv4();
|
this.uid = uuidv4();
|
||||||
return authPage.login();
|
return login();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it navigates to auth method', async function (assert) {
|
test('it navigates to auth method', async function (assert) {
|
||||||
await page.visit();
|
await visit('/vault/access/');
|
||||||
assert.strictEqual(currentRouteName(), 'vault.cluster.access.methods', 'navigates to the correct route');
|
assert.strictEqual(currentRouteName(), 'vault.cluster.access.methods', 'navigates to the correct route');
|
||||||
assert.ok(page.methodsLink.isActive, 'the first link is active');
|
assert.dom('[data-test-sidebar-nav-link="Authentication Methods"]').hasClass('active');
|
||||||
assert.strictEqual(page.methodsLink.text, 'Authentication Methods');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it filters by name and auth type', async function (assert) {
|
test('it filters by name and auth type', async function (assert) {
|
||||||
@@ -41,50 +35,52 @@ module('Acceptance | auth-methods list view', function (hooks) {
|
|||||||
const authPath1 = `userpass-1-${this.uid}`;
|
const authPath1 = `userpass-1-${this.uid}`;
|
||||||
const authPath2 = `userpass-2-${this.uid}`;
|
const authPath2 = `userpass-2-${this.uid}`;
|
||||||
const type = 'userpass';
|
const type = 'userpass';
|
||||||
await authEnable.visit();
|
await visit('/vault/settings/auth/enable');
|
||||||
await authEnable.enable(type, authPath1);
|
await runCmd(mountAuthCmd(type, authPath1));
|
||||||
await authEnable.visit();
|
await visit('/vault/settings/auth/enable');
|
||||||
await authEnable.enable(type, authPath2);
|
await runCmd(mountAuthCmd(type, authPath2));
|
||||||
await page.visit();
|
await visit('/vault/access/');
|
||||||
// filter by auth type
|
|
||||||
|
|
||||||
|
// filter by auth type
|
||||||
await clickTrigger('#filter-by-auth-type');
|
await clickTrigger('#filter-by-auth-type');
|
||||||
await searchSelect.options.objectAt(0).click();
|
await click(searchSelect.option(searchSelect.optionIndex(type)));
|
||||||
const rows = document.querySelectorAll('[data-test-auth-backend-link]');
|
let rows = findAll('[data-test-auth-backend-link]');
|
||||||
const rowsUserpass = Array.from(rows).filter((row) => row.innerText.includes('userpass'));
|
const rowsUserpass = Array.from(rows).filter((row) => row.innerText.includes('userpass'));
|
||||||
|
|
||||||
assert.strictEqual(rows.length, rowsUserpass.length, 'all rows returned are userpass');
|
assert.strictEqual(rows.length, rowsUserpass.length, 'all rows returned are userpass');
|
||||||
|
|
||||||
// filter by name
|
// filter by name
|
||||||
await clickTrigger('#filter-by-auth-name');
|
await clickTrigger('#filter-by-auth-name');
|
||||||
const firstItemToSelect = searchSelect.options.objectAt(0).text;
|
await click(searchSelect.option());
|
||||||
await searchSelect.options.objectAt(0).click();
|
const selectedItem = find(`#filter-by-auth-name ${searchSelect.selectedOption()}`).innerText;
|
||||||
const singleRow = document.querySelectorAll('[data-test-auth-backend-link]');
|
const singleRow = findAll('[data-test-auth-backend-link]');
|
||||||
|
|
||||||
assert.strictEqual(singleRow.length, 1, 'returns only one row');
|
assert.strictEqual(singleRow.length, 1, 'returns only one row');
|
||||||
assert.dom(singleRow[0]).includesText(firstItemToSelect, 'shows the filtered by auth name');
|
assert.dom(singleRow[0]).includesText(selectedItem, 'shows the filtered by auth name');
|
||||||
// clear filter by engine name
|
// clear filter by name
|
||||||
await searchSelect.deleteButtons.objectAt(1).click();
|
await click(`#filter-by-auth-name ${searchSelect.removeSelected}`);
|
||||||
const rowsAgain = document.querySelectorAll('[data-test-auth-backend-link]');
|
rows = findAll('[data-test-auth-backend-link]');
|
||||||
assert.ok(rowsAgain.length > 1, 'filter has been removed');
|
assert.true(rows.length > 1, 'filter has been removed');
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
await consoleComponent.runCommands([`delete sys/auth/${authPath1}`]);
|
await runCmd(`delete sys/auth/${authPath1}`);
|
||||||
await consoleComponent.runCommands([`delete sys/auth/${authPath2}`]);
|
await runCmd(`delete sys/auth/${authPath2}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it should show all methods in list view', async function (assert) {
|
test('it should show all methods in list view', async function (assert) {
|
||||||
this.server.get('/sys/auth', () => ({
|
this.server.get('/sys/internal/ui/mounts', () => ({
|
||||||
data: {
|
data: {
|
||||||
'token/': { accessor: 'auth_token_263b8b4e', type: 'token' },
|
auth: {
|
||||||
'userpass/': { accessor: 'auth_userpass_87aca1f8', type: 'userpass' },
|
'token/': { accessor: 'auth_token_263b8b4e', type: 'token' },
|
||||||
|
'userpass/': { accessor: 'auth_userpass_87aca1f8', type: 'userpass' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
await page.visit();
|
await visit('/vault/access/');
|
||||||
assert.dom('[data-test-auth-backend-link]').exists({ count: 2 }, 'All auth methods appear in list view');
|
assert.dom('[data-test-auth-backend-link]').exists({ count: 2 }, 'All auth methods appear in list view');
|
||||||
await authEnable.visit();
|
await visit('/vault/settings/auth/enable');
|
||||||
await click('[data-test-sidebar-nav-link="OIDC Provider"]');
|
await click('[data-test-sidebar-nav-link="OIDC Provider"]');
|
||||||
await page.visit();
|
await visit('/vault/access/');
|
||||||
assert
|
assert
|
||||||
.dom('[data-test-auth-backend-link]')
|
.dom('[data-test-auth-backend-link]')
|
||||||
.exists({ count: 2 }, 'All auth methods appear in list view after navigating back');
|
.exists({ count: 2 }, 'All auth methods appear in list view after navigating back');
|
||||||
|
|||||||
@@ -307,11 +307,15 @@ module('Acceptance | pki workflow', function (hooks) {
|
|||||||
await visit(`/vault/secrets/${this.mountPath}/pki/keys`);
|
await visit(`/vault/secrets/${this.mountPath}/pki/keys`);
|
||||||
await click(PKI_KEYS.generateKey);
|
await click(PKI_KEYS.generateKey);
|
||||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/keys/create`);
|
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/keys/create`);
|
||||||
await fillIn(GENERAL.inputByAttr('type'), 'exported');
|
await fillIn(GENERAL.inputByAttr('type'), 'exported'); // exported keys generated private_key data
|
||||||
await fillIn(GENERAL.inputByAttr('keyType'), 'rsa');
|
await fillIn(GENERAL.inputByAttr('keyType'), 'rsa');
|
||||||
await click(GENERAL.saveButton);
|
await click(GENERAL.saveButton);
|
||||||
keyId = find(GENERAL.infoRowValue('Key ID')).textContent?.trim();
|
keyId = find(GENERAL.infoRowValue('Key ID')).textContent?.trim();
|
||||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/keys/${keyId}/details`);
|
assert.strictEqual(
|
||||||
|
currentURL(),
|
||||||
|
`/vault/secrets/${this.mountPath}/pki/keys/create`,
|
||||||
|
'it does not transition to details private_key data exists'
|
||||||
|
);
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(PKI_KEYS.nextStepsAlert)
|
.dom(PKI_KEYS.nextStepsAlert)
|
||||||
|
|||||||
@@ -3,15 +3,14 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { click, currentRouteName, settled } from '@ember/test-helpers';
|
import { click, currentRouteName, fillIn, visit } from '@ember/test-helpers';
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupApplicationTest } from 'ember-qunit';
|
import { setupApplicationTest } from 'ember-qunit';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||||
import page from 'vault/tests/pages/settings/auth/enable';
|
|
||||||
import listPage from 'vault/tests/pages/access/methods';
|
|
||||||
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
||||||
|
import { deleteAuthCmd, runCmd } from 'vault/tests/helpers/commands';
|
||||||
|
import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
|
||||||
|
|
||||||
module('Acceptance | settings/auth/enable', function (hooks) {
|
module('Acceptance | settings/auth/enable', function (hooks) {
|
||||||
setupApplicationTest(hooks);
|
setupApplicationTest(hooks);
|
||||||
@@ -25,30 +24,34 @@ module('Acceptance | settings/auth/enable', function (hooks) {
|
|||||||
// always force the new mount to the top of the list
|
// always force the new mount to the top of the list
|
||||||
const path = `aaa-approle-${this.uid}`;
|
const path = `aaa-approle-${this.uid}`;
|
||||||
const type = 'approle';
|
const type = 'approle';
|
||||||
await page.visit();
|
await visit('/vault/settings/auth/enable');
|
||||||
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.auth.enable');
|
assert.strictEqual(currentRouteName(), 'vault.cluster.settings.auth.enable');
|
||||||
await page.enable(type, path);
|
await click(SES.mountType(type));
|
||||||
await settled();
|
await fillIn(GENERAL.inputByAttr('path'), path);
|
||||||
assert.strictEqual(
|
await click(SES.mountSubmit);
|
||||||
page.flash.latestMessage,
|
assert
|
||||||
`Successfully mounted the ${type} auth method at ${path}.`,
|
.dom(GENERAL.latestFlashContent)
|
||||||
'success flash shows'
|
.hasText(`Successfully mounted the ${type} auth method at ${path}.`);
|
||||||
);
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
currentRouteName(),
|
currentRouteName(),
|
||||||
'vault.cluster.settings.auth.configure.section',
|
'vault.cluster.settings.auth.configure.section',
|
||||||
'redirects to the auth config page'
|
'redirects to the auth config page'
|
||||||
);
|
);
|
||||||
|
|
||||||
await listPage.visit();
|
await visit('/vault/access/');
|
||||||
assert.ok(listPage.findLinkById(path), 'mount is present in the list');
|
assert.dom(`[data-test-auth-backend-link=${path}]`).exists('mount is present in the list');
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
await runCmd(deleteAuthCmd(path));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it renders default config details', async function (assert) {
|
test('it renders default config details', async function (assert) {
|
||||||
const path = `approle-config-${this.uid}`;
|
const path = `approle-config-${this.uid}`;
|
||||||
const type = 'approle';
|
const type = 'approle';
|
||||||
await page.visit();
|
await visit('/vault/settings/auth/enable');
|
||||||
await page.enable(type, path);
|
await click(SES.mountType(type));
|
||||||
|
await fillIn(GENERAL.inputByAttr('path'), path);
|
||||||
|
await click(SES.mountSubmit);
|
||||||
// the config details is updated to query mount details from sys/internal/ui/mounts
|
// the config details is updated to query mount details from sys/internal/ui/mounts
|
||||||
// but we still want these forms to continue using sys/auth which returns 0 for default ttl values
|
// but we still want these forms to continue using sys/auth which returns 0 for default ttl values
|
||||||
// check tune form (right after enabling)
|
// check tune form (right after enabling)
|
||||||
@@ -64,5 +67,8 @@ module('Acceptance | settings/auth/enable', function (hooks) {
|
|||||||
await click('[data-test-configure-link]');
|
await click('[data-test-configure-link]');
|
||||||
assert.dom(GENERAL.toggleInput('Default Lease TTL')).isNotChecked('default lease ttl is still unset');
|
assert.dom(GENERAL.toggleInput('Default Lease TTL')).isNotChecked('default lease ttl is still unset');
|
||||||
assert.dom(GENERAL.toggleInput('Max Lease TTL')).isNotChecked('max lease ttl is still unset');
|
assert.dom(GENERAL.toggleInput('Max Lease TTL')).isNotChecked('max lease ttl is still unset');
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
await runCmd(deleteAuthCmd(path));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) HashiCorp, Inc.
|
|
||||||
* SPDX-License-Identifier: BUSL-1.1
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { create, attribute, visitable, collection, hasClass, text } from 'ember-cli-page-object';
|
|
||||||
|
|
||||||
export default create({
|
|
||||||
visit: visitable('/vault/access/'),
|
|
||||||
methodsLink: {
|
|
||||||
isActive: hasClass('active'),
|
|
||||||
text: text(),
|
|
||||||
scope: '[data-test-sidebar-nav-link="Authentication Methods"]',
|
|
||||||
},
|
|
||||||
|
|
||||||
backendLinks: collection('[data-test-auth-backend-link]', {
|
|
||||||
path: text('[data-test-path]'),
|
|
||||||
id: attribute('data-test-id', '[data-test-path]'),
|
|
||||||
}),
|
|
||||||
|
|
||||||
findLinkById(id) {
|
|
||||||
return this.backendLinks.filterBy('id', id)[0];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -54,7 +54,7 @@ module('Unit | Adapter | auth method', function (hooks) {
|
|||||||
await this.store.findAll('auth-method', { adapterOptions: { unauthenticated: true } });
|
await this.store.findAll('auth-method', { adapterOptions: { unauthenticated: true } });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('findAll makes request to correct endpoint when useMountsEndpoint is true', async function (assert) {
|
test('query makes request to correct endpoint ', async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
this.server.get('sys/internal/ui/mounts', () => {
|
this.server.get('sys/internal/ui/mounts', () => {
|
||||||
@@ -62,6 +62,6 @@ module('Unit | Adapter | auth method', function (hooks) {
|
|||||||
return this.mockResponse;
|
return this.mockResponse;
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.store.findAll('auth-method', { adapterOptions: { useMountsEndpoint: true } });
|
await this.store.query('auth-method', {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ module('Unit | Adapter | kv/metadata', function (hooks) {
|
|||||||
let record = await this.store.peekRecord('kv/metadata', data.id);
|
let record = await this.store.peekRecord('kv/metadata', data.id);
|
||||||
|
|
||||||
await record.destroyRecord();
|
await record.destroyRecord();
|
||||||
assert.true(record.isDestroyed, 'record is destroyed');
|
assert.true(record.isDeleted, 'record is deleted');
|
||||||
record = await this.store.peekRecord('kv/metadata', this.id);
|
record = await this.store.peekRecord('kv/metadata', this.id);
|
||||||
assert.strictEqual(record, null, 'record is no longer in store');
|
assert.strictEqual(record, null, 'record is no longer in store');
|
||||||
});
|
});
|
||||||
|
|||||||
898
ui/yarn.lock
898
ui/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user