mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 20:36:26 +02:00
Ui/transform roles list create (#9852)
* Can see list of roles, templates, and alphabets when you click on corresponding tab inside a transform secrets engine * Cannot click on items in list other than transformations * Can create a new transform role from the empty state or toolbar * Creating a role redirects to the view of that role * Breadcrumb links on transform roles work * Role create form handles error
This commit is contained in:
parent
6478665b5e
commit
d9ee6252bf
7
ui/app/adapters/transform/alphabet.js
Normal file
7
ui/app/adapters/transform/alphabet.js
Normal file
@ -0,0 +1,7 @@
|
||||
import BaseAdapter from './base';
|
||||
|
||||
export default BaseAdapter.extend({
|
||||
pathForType() {
|
||||
return 'alphabet';
|
||||
},
|
||||
});
|
||||
63
ui/app/adapters/transform/base.js
Normal file
63
ui/app/adapters/transform/base.js
Normal file
@ -0,0 +1,63 @@
|
||||
import ApplicationAdapter from '../application';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
|
||||
export default ApplicationAdapter.extend({
|
||||
namespace: 'v1',
|
||||
|
||||
pathForType(type) {
|
||||
return type.replace('transform/', '');
|
||||
},
|
||||
|
||||
createOrUpdate(store, type, snapshot) {
|
||||
const serializer = store.serializerFor(type.modelName);
|
||||
const data = serializer.serialize(snapshot);
|
||||
const { id } = snapshot;
|
||||
let url = this.url(snapshot.record.get('backend'), type.modelName, id);
|
||||
|
||||
return this.ajax(url, 'POST', { data });
|
||||
},
|
||||
|
||||
createRecord() {
|
||||
return this.createOrUpdate(...arguments);
|
||||
},
|
||||
|
||||
updateRecord() {
|
||||
return this.createOrUpdate(...arguments, 'update');
|
||||
},
|
||||
|
||||
deleteRecord(store, type, snapshot) {
|
||||
const { id } = snapshot;
|
||||
return this.ajax(this.url(snapshot.record.get('backend'), type.modelName, id), 'DELETE');
|
||||
},
|
||||
|
||||
url(backend, modelType, id) {
|
||||
let type = this.pathForType(modelType);
|
||||
let url = `/${this.namespace}/${encodePath(backend)}/${encodePath(type)}`;
|
||||
if (id) {
|
||||
return `${url}/${encodePath(id)}`;
|
||||
}
|
||||
return url + '?list=true';
|
||||
},
|
||||
|
||||
fetchByQuery(query) {
|
||||
const { backend, modelName, id } = query;
|
||||
return this.ajax(this.url(backend, modelName, id), 'GET').then(resp => {
|
||||
return resp;
|
||||
});
|
||||
},
|
||||
|
||||
query(store, type, query) {
|
||||
return this.fetchByQuery(query);
|
||||
},
|
||||
|
||||
queryRecord(store, type, query) {
|
||||
return this.ajax(this.url(query.backend, type.modelName, query.id), 'GET').then(result => {
|
||||
return {
|
||||
id: query.id,
|
||||
...result,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// buildUrl(modelName, id, snapshot, requestType, query, returns) {},
|
||||
});
|
||||
@ -1,25 +1,7 @@
|
||||
import ApplicationAdapater from '../application';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
|
||||
export default ApplicationAdapater.extend({
|
||||
namespace: 'v1',
|
||||
import BaseAdapter from './base';
|
||||
|
||||
export default BaseAdapter.extend({
|
||||
pathForType() {
|
||||
return 'role';
|
||||
},
|
||||
|
||||
_url(backend, id) {
|
||||
let type = this.pathForType();
|
||||
let base = `/v1/${encodePath(backend)}/${type}`;
|
||||
if (id) {
|
||||
return `${base}/${encodePath(id)}`;
|
||||
}
|
||||
return base + '?list=true';
|
||||
},
|
||||
|
||||
query(store, type, query) {
|
||||
return this.ajax(this._url(query.backend), 'GET').then(result => {
|
||||
return result;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,25 +1,7 @@
|
||||
import ApplicationAdapater from '../application';
|
||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
|
||||
export default ApplicationAdapater.extend({
|
||||
namespace: 'v1',
|
||||
import BaseAdapter from './base';
|
||||
|
||||
export default BaseAdapter.extend({
|
||||
pathForType() {
|
||||
return 'template';
|
||||
},
|
||||
|
||||
_url(backend, id) {
|
||||
let type = this.pathForType();
|
||||
let base = `${this.buildURL()}/${encodePath(backend)}/${type}`;
|
||||
if (id) {
|
||||
return `${base}/${encodePath(id)}`;
|
||||
}
|
||||
return base + '?list=true';
|
||||
},
|
||||
|
||||
query(store, type, query) {
|
||||
return this.ajax(this._url(query.backend), 'GET').then(result => {
|
||||
return result;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
5
ui/app/adapters/transform/transformation.js
Normal file
5
ui/app/adapters/transform/transformation.js
Normal file
@ -0,0 +1,5 @@
|
||||
import BaseAdapter from './base';
|
||||
|
||||
export default BaseAdapter.extend({
|
||||
// custom stuff for transformation
|
||||
});
|
||||
@ -47,6 +47,14 @@ export default Component.extend(FocusOnInsertMixin, {
|
||||
this.get('router').transitionTo(...arguments);
|
||||
},
|
||||
|
||||
modelPrefixFromType(modelType) {
|
||||
let modelPrefix = '';
|
||||
if (modelType && modelType.startsWith('transform/')) {
|
||||
modelPrefix = `${modelType.replace('transform/', '')}/`;
|
||||
}
|
||||
return modelPrefix;
|
||||
},
|
||||
|
||||
onEscape(e) {
|
||||
if (e.keyCode !== keys.ESC || this.get('mode') !== 'show') {
|
||||
return;
|
||||
@ -74,6 +82,7 @@ export default Component.extend(FocusOnInsertMixin, {
|
||||
createOrUpdate(type, event) {
|
||||
event.preventDefault();
|
||||
const modelId = this.get('model.id') || this.get('model.name'); // transform comes in as model.name
|
||||
const modelPrefix = this.modelPrefixFromType(this.get('model.constructor.modelName'));
|
||||
// prevent from submitting if there's no key
|
||||
// maybe do something fancier later
|
||||
if (type === 'create' && isBlank(modelId)) {
|
||||
@ -82,7 +91,7 @@ export default Component.extend(FocusOnInsertMixin, {
|
||||
|
||||
this.persist('save', () => {
|
||||
this.hasDataChanges();
|
||||
this.transitionToRoute(SHOW_ROUTE, modelId);
|
||||
this.transitionToRoute(SHOW_ROUTE, `${modelPrefix}${modelId}`);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
3
ui/app/components/transform-role-edit.js
Normal file
3
ui/app/components/transform-role-edit.js
Normal file
@ -0,0 +1,3 @@
|
||||
import TransformBase from './transform-edit-base';
|
||||
|
||||
export default TransformBase.extend({});
|
||||
@ -3,9 +3,10 @@ import BackendCrumbMixin from 'vault/mixins/backend-crumb';
|
||||
|
||||
export default Controller.extend(BackendCrumbMixin, {
|
||||
backendController: controller('vault.cluster.secrets.backend'),
|
||||
queryParams: ['initialKey'],
|
||||
queryParams: ['initialKey', 'itemType'],
|
||||
|
||||
initialKey: '',
|
||||
itemType: '',
|
||||
|
||||
actions: {
|
||||
refresh: function() {
|
||||
|
||||
@ -68,40 +68,38 @@ const SECRET_BACKENDS = {
|
||||
create: 'Create transformation',
|
||||
editComponent: 'transformation-edit',
|
||||
},
|
||||
// TODO: Add tabs as needed
|
||||
// {
|
||||
// name: 'roles',
|
||||
// modelPrefix: 'role/',
|
||||
// label: 'Roles',
|
||||
// searchPlaceholder: 'Filter roles',
|
||||
// item: 'roles',
|
||||
// create: 'Create role',
|
||||
// tab: 'role',
|
||||
// listItemPartial: 'partials/secret-list/item',
|
||||
// editComponent: 'transform-role-edit',
|
||||
// },
|
||||
// {
|
||||
// name: 'templates',
|
||||
// modelPrefix: 'template/',
|
||||
// label: 'Templates',
|
||||
// searchPlaceholder: 'Filter templates',
|
||||
// item: 'templates',
|
||||
// create: 'Create template',
|
||||
// tab: 'template',
|
||||
// listItemPartial: 'partials/secret-list/item',
|
||||
// editComponent: 'transform-template-edit',
|
||||
// },
|
||||
// {
|
||||
// name: 'alphabets',
|
||||
// modelPrefix: 'alphabet/',
|
||||
// label: 'Alphabets',
|
||||
// searchPlaceholder: 'Filter alphabets',
|
||||
// item: 'alphabets',
|
||||
// create: 'Create alphabet',
|
||||
// tab: 'alphabet',
|
||||
// listItemPartial: 'partials/secret-list/item',
|
||||
// editComponent: 'alphabet-edit',
|
||||
// },
|
||||
{
|
||||
name: 'role',
|
||||
modelPrefix: 'role/',
|
||||
label: 'Roles',
|
||||
searchPlaceholder: 'Filter roles',
|
||||
item: 'role',
|
||||
create: 'Create role',
|
||||
tab: 'role',
|
||||
editComponent: 'transform-role-edit',
|
||||
},
|
||||
{
|
||||
name: 'templates',
|
||||
modelPrefix: 'template/',
|
||||
label: 'Templates',
|
||||
searchPlaceholder: 'Filter templates',
|
||||
item: 'templates',
|
||||
create: 'Create template',
|
||||
tab: 'templates',
|
||||
editComponent: 'transform-template-edit',
|
||||
hideCreate: true,
|
||||
},
|
||||
{
|
||||
name: 'alphabets',
|
||||
modelPrefix: 'alphabet/',
|
||||
label: 'Alphabets',
|
||||
searchPlaceholder: 'Filter alphabets',
|
||||
item: 'alphabets',
|
||||
create: 'Create alphabet',
|
||||
tab: 'alphabets',
|
||||
editComponent: 'alphabet-edit',
|
||||
hideCreate: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
transit: {
|
||||
|
||||
@ -92,5 +92,6 @@ const Model = DS.Model.extend({
|
||||
});
|
||||
|
||||
export default attachCapabilities(Model, {
|
||||
// TODO: Update to dynamic backend name
|
||||
updatePath: apiPath`transform/transformation/${'id'}`,
|
||||
});
|
||||
|
||||
5
ui/app/models/transform/alphabet.js
Normal file
5
ui/app/models/transform/alphabet.js
Normal file
@ -0,0 +1,5 @@
|
||||
import DS from 'ember-data';
|
||||
|
||||
export default DS.Model.extend({
|
||||
templates: DS.hasMany('template'),
|
||||
});
|
||||
@ -1,3 +1,42 @@
|
||||
import { computed } from '@ember/object';
|
||||
import DS from 'ember-data';
|
||||
import { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import attachCapabilities from 'vault/lib/attach-capabilities';
|
||||
|
||||
export default DS.Model.extend({});
|
||||
const { attr } = DS;
|
||||
|
||||
const Model = DS.Model.extend({
|
||||
// used for getting appropriate options for backend
|
||||
idPrefix: 'role/',
|
||||
// the id prefixed with `role/` so we can use it as the *secret param for the secret show route
|
||||
idForNav: computed('id', 'idPrefix', function() {
|
||||
let modelId = this.id || '';
|
||||
return `${this.idPrefix}${modelId}`;
|
||||
}),
|
||||
|
||||
name: attr('string', {
|
||||
// TODO: make this required for making a transformation
|
||||
label: 'Name',
|
||||
fieldValue: 'id',
|
||||
readOnly: true,
|
||||
subText: 'The name for your role. This cannot be edited later.',
|
||||
}),
|
||||
transformations: attr('string', {
|
||||
editType: 'searchSelect',
|
||||
fallbackComponent: 'string-list',
|
||||
label: 'Transformations',
|
||||
models: ['transform'],
|
||||
subLabel: 'Transformations',
|
||||
subText: 'Select which transformations this role will have access to. It must already exist.',
|
||||
}),
|
||||
|
||||
attrs: computed('transformations', function() {
|
||||
let keys = ['name', 'transformations'];
|
||||
return expandAttributeMeta(this, keys);
|
||||
}),
|
||||
});
|
||||
|
||||
export default attachCapabilities(Model, {
|
||||
updatePath: apiPath`${'backend'}/role/${'id'}`,
|
||||
});
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
import DS from 'ember-data';
|
||||
|
||||
export default DS.Model.extend({});
|
||||
export default DS.Model.extend({
|
||||
name: DS.attr('string'),
|
||||
alphabet: DS.belongsTo('transform/alphabet'),
|
||||
transformations: DS.hasMany('transformation'),
|
||||
});
|
||||
|
||||
7
ui/app/models/transform/transformation.js
Normal file
7
ui/app/models/transform/transformation.js
Normal file
@ -0,0 +1,7 @@
|
||||
import DS from 'ember-data';
|
||||
|
||||
export default DS.Model.extend({
|
||||
name: DS.attr('string'),
|
||||
template: DS.belongsTo('transform/template'),
|
||||
roles: DS.belongsTo('transform/role'),
|
||||
});
|
||||
@ -20,14 +20,24 @@ let secretModel = (store, backend, key) => {
|
||||
return secret;
|
||||
};
|
||||
|
||||
const transformModel = queryParams => {
|
||||
let modelType = 'transform';
|
||||
if (!queryParams || !queryParams.itemType) return modelType;
|
||||
|
||||
return `${modelType}/${queryParams.itemType}`;
|
||||
};
|
||||
|
||||
export default EditBase.extend({
|
||||
wizard: service(),
|
||||
createModel(transition) {
|
||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||
const modelType = this.modelType(backend);
|
||||
let modelType = this.modelType(backend);
|
||||
if (modelType === 'role-ssh') {
|
||||
return this.store.createRecord(modelType, { keyType: 'ca' });
|
||||
}
|
||||
if (modelType === 'transform') {
|
||||
modelType = transformModel(transition.queryParams);
|
||||
}
|
||||
if (modelType !== 'secret' && modelType !== 'secret-v2') {
|
||||
if (this.get('wizard.featureState') === 'details' && this.get('wizard.componentState') === 'transit') {
|
||||
this.get('wizard').transitionFeatureMachine('details', 'CONTINUE', 'transit');
|
||||
|
||||
@ -22,6 +22,25 @@ export default Route.extend({
|
||||
},
|
||||
},
|
||||
|
||||
modelTypeForTransform(tab) {
|
||||
let modelType;
|
||||
switch (tab) {
|
||||
case 'role':
|
||||
modelType = 'transform/role';
|
||||
break;
|
||||
case 'templates':
|
||||
modelType = 'transform/template';
|
||||
break;
|
||||
case 'alphabets':
|
||||
modelType = 'transform/alphabet';
|
||||
break;
|
||||
default:
|
||||
modelType = 'transform'; // CBS TODO: transform/transformation
|
||||
break;
|
||||
}
|
||||
return modelType;
|
||||
},
|
||||
|
||||
secretParam() {
|
||||
let { secret } = this.paramsFor(this.routeName);
|
||||
return secret ? normalizePath(secret) : '';
|
||||
@ -56,7 +75,7 @@ export default Route.extend({
|
||||
let types = {
|
||||
transit: 'transit-key',
|
||||
ssh: 'role-ssh',
|
||||
transform: 'transform',
|
||||
transform: this.modelTypeForTransform(tab),
|
||||
aws: 'role-aws',
|
||||
pki: tab === 'certs' ? 'pki-certificate' : 'role-pki',
|
||||
// secret or secret-v2
|
||||
|
||||
@ -71,7 +71,7 @@ export default Route.extend(UnloadModelRoute, {
|
||||
let types = {
|
||||
transit: 'transit-key',
|
||||
ssh: 'role-ssh',
|
||||
transform: 'transform',
|
||||
transform: secret && secret.startsWith('role/') ? 'transform/role' : 'transform', // CBS TODO: switch out better
|
||||
aws: 'role-aws',
|
||||
pki: secret && secret.startsWith('cert/') ? 'pki-certificate' : 'role-pki',
|
||||
cubbyhole: 'secret',
|
||||
@ -192,13 +192,16 @@ export default Route.extend(UnloadModelRoute, {
|
||||
let secret = this.secretParam();
|
||||
let backend = this.enginePathParam();
|
||||
let modelType = this.modelType(backend, secret);
|
||||
|
||||
if (!secret) {
|
||||
secret = '\u0020';
|
||||
}
|
||||
if (modelType === 'pki-certificate') {
|
||||
secret = secret.replace('cert/', '');
|
||||
}
|
||||
if (modelType.startsWith('transform/')) {
|
||||
// CBS TODO: we'll have more things to replace than just role/
|
||||
secret = secret.replace('role/', '');
|
||||
}
|
||||
let secretModel;
|
||||
|
||||
let capabilities = this.capabilities(secret);
|
||||
|
||||
101
ui/app/templates/components/transform-role-edit.hbs
Normal file
101
ui/app/templates/components/transform-role-edit.hbs
Normal file
@ -0,0 +1,101 @@
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
{{key-value-header
|
||||
baseKey=(hash display=model.id id=model.idForNav)
|
||||
path="vault.cluster.secrets.backend.list"
|
||||
mode=mode
|
||||
root=root
|
||||
showCurrent=true
|
||||
}}
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-secret-header="true">
|
||||
{{#if (eq mode "create") }}
|
||||
Create Role
|
||||
{{else if (eq mode "edit")}}
|
||||
Edit Role
|
||||
{{else}}
|
||||
Role <code>{{model.id}}</code>
|
||||
{{/if}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
{{#if (eq mode "show")}}
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
{{!-- TODO: Ability to delete and edit role
|
||||
{{#if (or capabilities.canUpdate capabilities.canDelete)}}
|
||||
<div class="toolbar-separator" />
|
||||
{{/if}}
|
||||
{{#if capabilities.canDelete}}
|
||||
<a
|
||||
class="toolbar-link"
|
||||
onclick={{action "delete"}}
|
||||
data-test-transformation-role-delete
|
||||
>
|
||||
Delete role
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if capabilities.canUpdate }}
|
||||
<ToolbarSecretLink
|
||||
@secret={{model.id}}
|
||||
@mode="edit"
|
||||
@data-test-edit-link=true
|
||||
@replace=true
|
||||
>
|
||||
Edit role
|
||||
</ToolbarSecretLink>
|
||||
{{/if}} --}}
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
{{/if}}
|
||||
|
||||
{{#if (or (eq mode 'edit') (eq mode 'create'))}}
|
||||
<form onsubmit={{action "createOrUpdate" mode}}>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
{{message-error model=model}}
|
||||
<NamespaceReminder @mode={{mode}} @noun="Transform role" />
|
||||
{{#each model.attrs as |attr|}}
|
||||
<FormField
|
||||
data-test-field
|
||||
@attr={{attr}}
|
||||
@model={{model}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="field is-grouped-split box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={{buttonDisabled}}
|
||||
class="button is-primary"
|
||||
data-test-role-ssh-create=true
|
||||
>
|
||||
{{#if (eq mode 'create')}}
|
||||
Create transformation
|
||||
{{else if (eq mode 'edit')}}
|
||||
Save
|
||||
{{/if}}
|
||||
</button>
|
||||
{{#secret-link
|
||||
mode=(if (eq mode "create") "list" "show")
|
||||
class="button"
|
||||
secret=model.id
|
||||
}}
|
||||
Cancel
|
||||
{{/secret-link}}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{else}}
|
||||
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
{{#each model.attrs as |attr|}}
|
||||
{{#if (eq attr.type "object")}}
|
||||
{{info-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(stringify (get model attr.name))}}
|
||||
{{else}}
|
||||
{{info-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
@ -1,60 +1,76 @@
|
||||
{{!-- TODO do not let click if !canRead --}}
|
||||
{{#linked-block
|
||||
"vault.cluster.secrets.backend.show"
|
||||
item.id
|
||||
class="list-item-row"
|
||||
data-test-secret-link=item.id
|
||||
encode=true
|
||||
queryParams=(secret-query-params backendModel.type)
|
||||
}}
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-10">
|
||||
<SecretLink
|
||||
@mode="show"
|
||||
@secret={{item.id}}
|
||||
@queryParams={{if (eq backendModel.type "transform") (query-params tab="actions") ""}}
|
||||
@class="has-text-black has-text-weight-semibold">
|
||||
<Icon
|
||||
@glyph='file-outline'
|
||||
@class="has-text-grey-light"/>
|
||||
{{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}}
|
||||
</SecretLink>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<PopupMenu name="secret-menu">
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if (or item.versionPath.isLoading item.secretPath.isLoading)}}
|
||||
<li class="action">
|
||||
<button disabled type="button" class="link button is-loading is-transparent">
|
||||
loading
|
||||
</button>
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if item.updatePath.canRead}}
|
||||
{{!-- CBS TODO: make this generic to any transform model type? --}}
|
||||
{{#if (eq options.item "transformation")}}
|
||||
{{#linked-block
|
||||
"vault.cluster.secrets.backend.show"
|
||||
item.id
|
||||
class="list-item-row"
|
||||
data-test-secret-link=item.id
|
||||
encode=true
|
||||
queryParams=(secret-query-params backendModel.type)
|
||||
}}
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-10">
|
||||
<SecretLink
|
||||
@mode="show"
|
||||
@secret={{item.id}}
|
||||
@queryParams={{if (eq backendModel.type "transform") (query-params tab="actions") ""}}
|
||||
@class="has-text-black has-text-weight-semibold">
|
||||
<Icon
|
||||
@glyph='file-outline'
|
||||
@class="has-text-grey-light"/>
|
||||
{{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}}
|
||||
</SecretLink>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
{{#if (or item.updatePath.canRead item.updatePath.canUpdate)}}
|
||||
<PopupMenu name="secret-menu">
|
||||
<nav class="menu">
|
||||
<ul class="menu-list">
|
||||
{{#if (or item.versionPath.isLoading item.secretPath.isLoading)}}
|
||||
<li class="action">
|
||||
<SecretLink
|
||||
@mode="show"
|
||||
@secret={{item.id}}
|
||||
@class="has-text-black has-text-weight-semibold">
|
||||
Details
|
||||
</SecretLink>
|
||||
<button disabled type="button" class="link button is-loading is-transparent">
|
||||
loading
|
||||
</button>
|
||||
</li>
|
||||
{{else}}
|
||||
{{#if item.updatePath.canRead}}
|
||||
<li class="action">
|
||||
<SecretLink
|
||||
@mode="show"
|
||||
@secret={{item.id}}
|
||||
@class="has-text-black has-text-weight-semibold">
|
||||
Details
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if item.updatePath.canUpdate}}
|
||||
<li class="action">
|
||||
<SecretLink
|
||||
@mode="edit"
|
||||
@secret={{item.id}}
|
||||
@class="has-text-black has-text-weight-semibold">
|
||||
Edit
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if item.updatePath.canUpdate}}
|
||||
<li class="action">
|
||||
<SecretLink
|
||||
@mode="edit"
|
||||
@secret={{item.id}}
|
||||
@class="has-text-black has-text-weight-semibold">
|
||||
Edit
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/linked-block}}
|
||||
{{/linked-block}}
|
||||
{{else}}
|
||||
<div class="list-item-row">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-12 has-text-grey has-text-weight-semibold">
|
||||
<Icon
|
||||
@glyph='file-outline'
|
||||
@class="has-text-grey-light"/>
|
||||
{{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
@ -42,15 +42,17 @@
|
||||
{{/if}}
|
||||
|
||||
<ToolbarActions>
|
||||
<ToolbarSecretLink
|
||||
@secret=''
|
||||
@mode="create"
|
||||
@type="add"
|
||||
@queryParams={{query-params initialKey=(or filter baseKey.id)}}
|
||||
@data-test-secret-create=true
|
||||
>
|
||||
{{options.create}}
|
||||
</ToolbarSecretLink>
|
||||
{{#unless options.hideCreate}}
|
||||
<ToolbarSecretLink
|
||||
@secret=''
|
||||
@mode="create"
|
||||
@type="add"
|
||||
@queryParams={{query-params initialKey=(or filter baseKey.id) itemType=tab}}
|
||||
@data-test-secret-create=true
|
||||
>
|
||||
{{options.create}}
|
||||
</ToolbarSecretLink>
|
||||
{{/unless}}
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
{{/if}}
|
||||
@ -84,7 +86,7 @@
|
||||
{{#secret-link
|
||||
mode="create"
|
||||
secret=''
|
||||
queryParams=(query-params initialKey=(or filter baseKey.id))
|
||||
queryParams=(query-params initialKey=(or filter baseKey.id) itemType=tab)
|
||||
class="link"
|
||||
}}
|
||||
{{options.create}}
|
||||
|
||||
25
ui/tests/integration/components/transform-role-edit-test.js
Normal file
25
ui/tests/integration/components/transform-role-edit-test.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { module, skip } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | transform-role-edit', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
skip('it renders', async function(assert) {
|
||||
// TODO: Fill out these tests, merging without to unblock other work
|
||||
|
||||
await render(hbs`{{transform-role-edit}}`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
{{#transform-role-edit}}
|
||||
template block text
|
||||
{{/transform-role-edit}}
|
||||
`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), 'template block text');
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user