vault/ui/app/components/secret-create-or-update.js
Chelsea Shaw 392b907989
KV V2 remove old kv v2 (#22691)
* Remove component: diff version selector

* delete SecretVersionMenu

* remove secret logic from GetCredentialsCard

* remove DiffVersionSelector hbs file and references

* delete more css for diff version view

* remove diff route

* fix credential card selector

* ui: refactor SecretFormShow (#22723)

* refactor secret form show

* fix selector typo

* remove version route (#22738)

* Remove old KV2 delete things (#23015)

* remove kv2 old delete things

* comment

* Remove old metadata (#22747)

* wip to remove metadata

* review comments

* UI/remove kv2 secret create or update (#23039)

* remove is v2 param

* permissions clean up

* remove version things

* remove excess from form show

* clean up

* created time was never a thing for cubbyhole, confirmed on api

* update tune test

* fix control group tests:

* Remove kv v2 models (#23087)

* remove is v2 param

* permissions clean up

* remove version things

* remove excess from form show

* clean up

* created time was never a thing for cubbyhole, confirmed on api

* update tune test

* fix control group tests:

* remove models

* Update ui/app/models/secret-engine.js

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>

* blah prettier

---------

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>

* UI/config update (#23111)

* sweep through clean up

* remove component

* remove unused selectors

* remove unncessary

---------

Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
Co-authored-by: clairebontempo@gmail.com <clairebontempo@gmail.com>
Co-authored-by: Angel Garbarino <Monkeychip@users.noreply.github.com>
Co-authored-by: Angel Garbarino <angel@hashicorp.com>
2023-09-19 09:49:04 -06:00

222 lines
6.4 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
/**
* @module SecretCreateOrUpdate
* SecretCreateOrUpdate component displays either the form for creating a new secret or creating a new version of the secret
*
* @example
* ```js
* <SecretCreateOrUpdate
* @mode="create"
* @model={{model}}
* @showAdvancedMode=true
* @modelForData={{@modelForData}}
* @secretData={{@secretData}}
* @buttonDisabled={{this.saving}}
* />
* ```
* @param {string} mode - create, edit, show determines what view to display
* @param {object} model - the route model
* @param {boolean} showAdvancedMode - whether or not to show the JSON editor
* @param {object} modelForData - a class that helps track secret data, defined in secret-edit
* @param {object} secretData - class that is created in secret-edit
* @param {boolean} buttonDisabled - if true, disables the submit button on the create/update form
*/
import Component from '@glimmer/component';
import ControlGroupError from 'vault/lib/control-group-error';
import Ember from 'ember';
import keys from 'core/utils/key-codes';
import { action, set } from '@ember/object';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { isBlank, isNone } from '@ember/utils';
import { task, waitForEvent } from 'ember-concurrency';
const LIST_ROUTE = 'vault.cluster.secrets.backend.list';
const LIST_ROOT_ROUTE = 'vault.cluster.secrets.backend.list-root';
const SHOW_ROUTE = 'vault.cluster.secrets.backend.show';
export default class SecretCreateOrUpdate extends Component {
@tracked codemirrorString = null;
@tracked error = null;
@tracked secretPaths = null;
@tracked pathWhiteSpaceWarning = false;
@tracked validationErrorCount = 0;
@tracked validationMessages = null;
@service controlGroup;
@service flashMessages;
@service router;
@service store;
@action
setup(elem, [secretData, mode]) {
this.codemirrorString = secretData.toJSONString();
this.validationMessages = {
path: '',
};
// for validation, return array of path names already assigned
if (Ember.testing) {
this.secretPaths = ['beep', 'bop', 'boop'];
}
this.checkRows();
if (mode === 'edit') {
this.addRow();
}
}
checkRows() {
if (this.args.secretData.length === 0) {
this.addRow();
}
}
checkValidation(name, value) {
if (name === 'path') {
// check for whitespace
this.pathHasWhiteSpace(value);
!value
? set(this.validationMessages, name, `${name} can't be blank.`)
: set(this.validationMessages, name, '');
}
const values = Object.values(this.validationMessages);
this.validationErrorCount = values.filter(Boolean).length;
}
onEscape(e) {
if (e.keyCode !== keys.ESC || this.args.mode !== 'show') {
return;
}
const parentKey = this.args.model.parentKey;
if (parentKey) {
this.transitionToRoute(LIST_ROUTE, parentKey);
} else {
this.transitionToRoute(LIST_ROOT_ROUTE);
}
}
pathHasWhiteSpace(value) {
const validation = new RegExp('\\s', 'g'); // search for whitespace
this.pathWhiteSpaceWarning = validation.test(value);
}
// successCallback is called in the context of the component
persistKey(successCallback) {
const secret = this.args.model;
const secretData = this.args.modelForData;
let key = secretData.get('path') || secret.id;
if (key.startsWith('/')) {
key = key.replace(/^\/+/g, '');
secretData.set(secretData.pathAttr, key);
}
return secretData
.save()
.then(() => {
if (!secretData.isError) {
this.saveComplete(successCallback, key);
}
})
.catch((error) => {
if (error instanceof ControlGroupError) {
const errorMessage = this.controlGroup.logFromError(error);
this.error = errorMessage.content;
this.controlGroup.saveTokenFromError(error);
}
throw error;
});
}
saveComplete(callback, key) {
callback(key);
}
transitionToRoute() {
return this.router.transitionTo(...arguments);
}
@(task(function* (name, value) {
this.checkValidation(name, value);
while (true) {
const event = yield waitForEvent(document.body, 'keyup');
this.onEscape(event);
}
})
.on('didInsertElement')
.cancelOn('willDestroyElement'))
waitForKeyUp;
@action
addRow() {
const data = this.args.secretData;
// fired off on init
if (isNone(data.findBy('name', ''))) {
data.pushObject({ name: '', value: '' });
this.handleChange();
}
this.checkRows();
}
@action
codemirrorUpdated(val, codemirror) {
this.error = null;
codemirror.performLint();
const noErrors = codemirror.state.lint.marked.length === 0;
if (noErrors) {
try {
this.args.secretData.fromJSONString(val);
set(this.args.modelForData, 'secretData', this.args.secretData.toJSON());
} catch (e) {
this.error = e.message;
}
}
this.codemirrorString = val;
}
@action
createOrUpdateKey(type, event) {
event.preventDefault();
if (type === 'create' && isBlank(this.args.modelForData.path || this.args.modelForData.id)) {
this.checkValidation('path', '');
return;
}
const secretPath = type === 'create' ? this.args.modelForData.path : this.args.model.id;
this.persistKey(() => {
// Show flash message in case there's a control group on read
this.flashMessages.success(
`Secret ${secretPath} ${type === 'create' ? 'created' : 'updated'} successfully.`
);
this.transitionToRoute(SHOW_ROUTE, secretPath);
});
}
@action
deleteRow(name) {
const data = this.args.secretData;
const item = data.findBy('name', name);
if (isBlank(item.name)) {
return;
}
data.removeObject(item);
this.checkRows();
this.handleChange();
}
@action
formatJSON() {
this.codemirrorString = this.args.secretData.toJSONString(true);
}
@action
handleMaskedInputChange(secret, index, value) {
const row = { ...secret, value };
set(this.args.secretData, index, row);
this.handleChange();
}
@action
handleChange() {
this.codemirrorString = this.args.secretData.toJSONString(true);
set(this.args.modelForData, 'secretData', this.args.secretData.toJSON());
}
@action
updateValidationErrorCount(errorCount) {
this.validationErrorCount = errorCount;
}
}