Skip to content

Commit

Permalink
UI: Clean up Dynamic UI for CRUD (#6994)
Browse files Browse the repository at this point in the history
  • Loading branch information
madalynrose authored Jul 1, 2019
1 parent 1b7a8ba commit 15d2fdd
Show file tree
Hide file tree
Showing 22 changed files with 340 additions and 373 deletions.
3 changes: 3 additions & 0 deletions builtin/credential/ldap/path_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ func pathConfig(b *backend) *framework.Path {

HelpSynopsis: pathConfigHelpSyn,
HelpDescription: pathConfigHelpDesc,
DisplayAttrs: &framework.DisplayAttributes{
Action: "Configure",
},
}

tokenutil.AddTokenFields(p.Fields)
Expand Down
3 changes: 3 additions & 0 deletions builtin/credential/ldap/path_groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func pathGroups(b *backend) *framework.Path {

HelpSynopsis: pathGroupHelpSyn,
HelpDescription: pathGroupHelpDesc,
DisplayAttrs: &framework.DisplayAttributes{
Action: "Create",
},
}
}

Expand Down
4 changes: 4 additions & 0 deletions builtin/credential/ldap/path_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func pathUsersList(b *backend) *framework.Path {
HelpDescription: pathUserHelpDesc,
DisplayAttrs: &framework.DisplayAttributes{
Navigation: true,
Action: "Create",
},
}
}
Expand Down Expand Up @@ -54,6 +55,9 @@ func pathUsers(b *backend) *framework.Path {

HelpSynopsis: pathUserHelpSyn,
HelpDescription: pathUserHelpDesc,
DisplayAttrs: &framework.DisplayAttributes{
Action: "Create",
},
}
}

Expand Down
31 changes: 31 additions & 0 deletions ui/app/components/generated-item-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { getOwner } from '@ember/application';

/**
* @module GeneratedItemList
* The `GeneratedItemList` component lists generated items related to mounts (e.g. groups, roles, users)
*
* @example
* ```js
* <GeneratedItemList @model={{model}} @itemType={{itemType/>
* ```
*
* @property model=null {DS.Model} - The corresponding item model that is being configured.
* @property itemType {String} - the type of item displayed
*
*/

export default Component.extend({
model: null,
itemType: null,
router: service(),
store: service(),
actions: {
refreshItemList() {
let route = getOwner(this).lookup(`route:${this.router.currentRouteName}`);
this.store.clearAllDatasets();
route.refresh();
},
},
});
2 changes: 1 addition & 1 deletion ui/app/components/generated-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import DS from 'ember-data';

/**
* @module GeneratedItem
* The `GeneratedItem` is the form to configure generated items related to mounts (e.g. groups, roles, users)
* The `GeneratedItem` component is the form to configure generated items related to mounts (e.g. groups, roles, users)
*
* @example
* ```js
Expand Down
1 change: 0 additions & 1 deletion ui/app/helpers/tabs-for-auth-section.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export function tabsForAuthSection([model, sectionType = 'authSettings', paths])
});
return tabs;
}

if (paths) {
tabs = paths.map(path => {
let itemName = path.slice(1); //get rid of leading slash
Expand Down
1 change: 0 additions & 1 deletion ui/app/routes/vault/cluster/access/method.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { inject as service } from '@ember/service';
export default Route.extend({
pathHelp: service('path-help'),
model(params) {
// eslint-disable-line rule
const { path } = params;
return this.store.findAll('auth-method').then(modelArray => {
const model = modelArray.findBy('id', path);
Expand Down
7 changes: 5 additions & 2 deletions ui/app/routes/vault/cluster/access/method/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Route from '@ember/routing/route';

import { tabsForAuthSection } from 'vault/helpers/tabs-for-auth-section';
export default Route.extend({
beforeModel() {
return this.transitionTo('vault.cluster.access.method.section', 'configuration');
let { methodType, paths } = this.modelFor('vault.cluster.access.method');
paths = paths ? paths.navPaths.reduce((acc, cur) => acc.concat(cur.path), []) : null;
const activeTab = tabsForAuthSection([methodType, 'authConfig', paths])[0].routeParams;
return this.transitionTo(...activeTab);
},
});
21 changes: 12 additions & 9 deletions ui/app/routes/vault/cluster/access/method/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,25 @@ export default Route.extend({
pathHelp: service('path-help'),

beforeModel() {
const { item_type: itemType } = this.paramsFor(this.routeName);
const { path: method } = this.paramsFor('vault.cluster.access.method');
let methodModel = this.modelFor('vault.cluster.access.method');
let { apiPath, type } = methodModel;
const { apiPath, type, method, itemType } = this.getMethodAndModelInfo();
let modelType = `generated-${singularize(itemType)}-${type}`;
return this.pathHelp.getNewModel(modelType, method, apiPath, itemType);
},

getMethodAndModelInfo() {
const { item_type: itemType } = this.paramsFor(this.routeName);
const { path: method } = this.paramsFor('vault.cluster.access.method');
const methodModel = this.modelFor('vault.cluster.access.method');
const { apiPath, type } = methodModel;
return { apiPath, type, method, itemType };
},

setupController(controller) {
this._super(...arguments);
const { item_type: itemType } = this.paramsFor(this.routeName);
const { path } = this.paramsFor('vault.cluster.access.method');
const { apiPath } = this.modelFor('vault.cluster.access.method');
const { apiPath, method, itemType } = this.getMethodAndModelInfo();
controller.set('itemType', itemType);
controller.set('method', path);
this.pathHelp.getPaths(apiPath, path, itemType).then(paths => {
controller.set('method', method);
this.pathHelp.getPaths(apiPath, method, itemType).then(paths => {
controller.set('paths', Array.from(paths.list, pathInfo => pathInfo.path));
});
},
Expand Down
28 changes: 18 additions & 10 deletions ui/app/routes/vault/cluster/access/method/item/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import ListRoute from 'vault/mixins/list-route';
export default Route.extend(ListRoute, {
wizard: service(),
pathHelp: service('path-help'),
model() {

getMethodAndModelInfo() {
const { item_type: itemType } = this.paramsFor('vault.cluster.access.method.item');
const { page, pageFilter } = this.paramsFor(this.routeName);
const { path: method } = this.paramsFor('vault.cluster.access.method');
const methodModel = this.modelFor('vault.cluster.access.method');
const { type } = methodModel;
const { apiPath, type } = methodModel;
return { apiPath, type, method, itemType };
},

model() {
const { type, method, itemType } = this.getMethodAndModelInfo();
const { page, pageFilter } = this.paramsFor(this.routeName);
let modelType = `generated-${singularize(itemType)}-${type}`;
const { path: method } = this.paramsFor('vault.cluster.access.method');

return this.store
.lazyPaginatedQuery(modelType, {
Expand All @@ -38,16 +44,18 @@ export default Route.extend(ListRoute, {
}
return true;
},
reload() {
this.store.clearAllDatasets();
this.refresh();
},
},
setupController(controller) {
this._super(...arguments);
const { item_type: itemType } = this.paramsFor('vault.cluster.access.method.item');
let { path } = this.paramsFor('vault.cluster.access.method');
const { apiPath, method, itemType } = this.getMethodAndModelInfo();
controller.set('itemType', singularize(itemType));
controller.set('method', path);
const { apiPath } = this.modelFor('vault.cluster.access.method');
this.pathHelp.getPaths(apiPath, path, itemType).then(paths => {
controller.set('paths', Array.from(paths.list, pathInfo => pathInfo.path));
controller.set('method', method);
this.pathHelp.getPaths(apiPath, method, itemType).then(paths => {
controller.set('paths', paths.navPaths.reduce((acc, cur) => acc.concat(cur.path), []));
});
},
});
1 change: 1 addition & 0 deletions ui/app/routes/vault/cluster/access/method/section.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import DS from 'ember-data';

export default Route.extend({
wizard: service(),

model(params) {
const { section_name: section } = params;
if (section !== 'configuration') {
Expand Down
133 changes: 58 additions & 75 deletions ui/app/services/path-help.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ export default Service.extend({
//if we have an item we want the create info for that itemType
let tag, path;
if (itemType) {
tag = paths.create[0].tag;
path = paths.create[0].path;
const createPath = paths.create.find(path => path.path.includes(itemType));
tag = createPath.tag; //tag is for type of backend, e.g. auth or secret
path = createPath.path;
path = path.slice(0, path.indexOf('{') - 1) + '/example';
} else {
//we need the mount config
Expand All @@ -75,80 +76,59 @@ export default Service.extend({
}
},

getPaths(apiPath, backend, itemType) {
debug(`Fetching relevant paths for ${backend} from ${apiPath}`);
return this.ajax(`/v1/${apiPath}?help=1`, backend).then(help => {
const pathInfo = help.openapi.paths;
let paths = Object.keys(pathInfo);
reducePaths(paths, currentPath) {
const pathName = currentPath[0];
const pathInfo = currentPath[1];
//config is a get/post endpoint that doesn't take route params
//and isn't also a list endpoint
if (
pathInfo.post &&
pathInfo.get &&
(pathInfo['x-vault-displayAttrs'] && pathInfo['x-vault-displayAttrs'].action === 'Configure')
) {
paths.configPath.push({ path: pathName, tag: pathInfo.get.tags[0] });
return paths; //config path should only be config path
}

//TODO: consolidate this into a single reduce()
//config is a get/post endpoint that doesn't take route params
//and isn't also a list endpoint
const configPath = paths
.map(path => {
if (
pathInfo[path].post &&
!path.includes('{') &&
pathInfo[path].get &&
(!pathInfo[path].get.parameters || pathInfo[path].get.parameters[0].name !== 'list')
) {
return { path: path, tag: pathInfo[path].get.tags[0] };
}
})
.compact();
//list endpoints all have { name: "list" } in their get parameters
if (pathInfo.get && pathInfo.get.parameters && pathInfo.get.parameters[0].name === 'list') {
paths.list.push({ path: pathName, tag: pathInfo.get.tags[0] });
}

//list endpoints all have { name: "list" } in their get parameters
const listPaths = paths
.map(path => {
if (
pathInfo[path].get &&
pathInfo[path].get.parameters &&
pathInfo[path].get.parameters[0].name == 'list'
) {
return { path: path, tag: pathInfo[path].get.tags[0] };
}
})
.compact();
if (pathInfo.delete) {
paths.delete.push({ path: pathName, tag: pathInfo.delete.tags[0] });
}

//we always want to keep list endpoints for menus
//but only use scoped post/delete endpoints
if (itemType) {
paths = paths.filter(path => path.includes(itemType));
}
const deletePaths = paths
.map(path => {
if (pathInfo[path].delete) {
return { path: path, tag: pathInfo[path].delete.tags[0] };
}
})
.compact();
//create endpoints have path an action (e.g. "Create" or "Generate")
if (pathInfo.post && pathInfo['x-vault-displayAttrs'] && pathInfo['x-vault-displayAttrs'].action) {
paths.create.push({
path: pathName,
tag: pathInfo.post.tags[0],
action: pathInfo['x-vault-displayAttrs'].action,
});
}

//create endpoints have path params, signified by "{}"
//we have to filter out login endpoints for auth methods
const createPaths = paths
.map(path => {
if (pathInfo[path].post && path.includes('{') && !path.includes('login')) {
return { path: path, tag: pathInfo[path].post.tags[0] };
}
})
.compact();
if (pathInfo['x-vault-displayAttrs'] && pathInfo['x-vault-displayAttrs'].navigation) {
paths.navPaths.push({ path: pathName });
}

return paths;
},

const navPaths = paths
.map(path => {
if (pathInfo[path]['x-vault-displayAttrs'] && pathInfo[path]['x-vault-displayAttrs'].navigation) {
return { path: path };
}
})
.compact();
//return paths object with all relevant information
return {
apiPath: apiPath,
configPath: configPath,
list: listPaths,
create: createPaths,
delete: deletePaths,
navPaths: navPaths,
};
getPaths(apiPath, backend) {
debug(`Fetching relevant paths for ${backend} from ${apiPath}`);
return this.ajax(`/v1/${apiPath}?help=1`, backend).then(help => {
const pathInfo = help.openapi.paths;
let paths = Object.entries(pathInfo);

return paths.reduce(this.reducePaths, {
apiPath: [],
configPath: [],
list: [],
create: [],
delete: [],
navPaths: [],
});
});
},

Expand All @@ -157,6 +137,7 @@ export default Service.extend({
//as determined by the expandOpenApiProps util
getProps(helpUrl, backend) {
debug(`Fetching schema properties for ${backend} from ${helpUrl}`);

return this.ajax(helpUrl, backend).then(help => {
//paths is an array but it will have a single entry
// for the scope we're in
Expand Down Expand Up @@ -196,9 +177,11 @@ export default Service.extend({
getNewAdapter(backend, paths, itemType) {
//we need list and create paths to set the correct urls for actions
const { list, create } = paths;
const createPath = create.find(path => path.path.includes(itemType));
const listPath = list.find(pathInfo => pathInfo.path.includes(itemType));
const deletePath = paths.delete.find(path => path.path.includes(itemType));
return generatedItemAdapter.extend({
urlForItem(method, id) {
let listPath = list.find(pathInfo => pathInfo.path.includes(itemType));
let { tag, path } = listPath;
let url = `${this.buildURL()}/${tag}/${backend}${path}/`;
if (id) {
Expand All @@ -212,20 +195,20 @@ export default Service.extend({
},

urlForUpdateRecord(id) {
let { tag, path } = create[0];
let { tag, path } = createPath;
path = path.slice(0, path.indexOf('{') - 1);
return `${this.buildURL()}/${tag}/${backend}${path}/${id}`;
},

urlForCreateRecord(modelType, snapshot) {
const { id } = snapshot;
let { tag, path } = create[0];
let { tag, path } = createPath;
path = path.slice(0, path.indexOf('{') - 1);
return `${this.buildURL()}/${tag}/${backend}${path}/${id}`;
},

urlForDeleteRecord(id) {
let { tag, path } = paths.delete[0];
let { tag, path } = deletePath;
path = path.slice(0, path.indexOf('{') - 1);
return `${this.buildURL()}/${tag}/${backend}${path}/${id}`;
},
Expand Down
Loading

0 comments on commit 15d2fdd

Please sign in to comment.