Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Clean up Dynamic UI for CRUD #6994

Merged
merged 20 commits into from
Jul 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions builtin/credential/ldap/path_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ func pathConfig(b *backend) *framework.Path {

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

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: [],
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice cleanup!

});
},

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