diff --git a/app/models/globalrole.js b/app/models/globalrole.js index 6b1cf6e6c..aa58d662c 100644 --- a/app/models/globalrole.js +++ b/app/models/globalrole.js @@ -9,15 +9,23 @@ const SPECIAL = [BASE, ADMIN, USER]; export default Resource.extend({ + access: service(), intl: service(), router: service(), - canRemove: false, // I think its safe to hack around this - wjw _displayState: 'active', // because of this the state shows as "Unknown" with bright yellow background stateColor: 'text-success', + canClone: computed('access.me', 'id', function() { + return this.access.allows('globalrole', 'create', 'global'); + }), + + canRemove: computed('id', 'builtin', function() { + return !this.builtin; + }), + isHidden: computed('id', function() { return SPECIAL.includes(get(this, 'id')); }), @@ -74,5 +82,14 @@ export default Resource.extend({ edit() { this.get('router').transitionTo('global-admin.security.roles.edit', this.get('id'), { queryParams: { type: 'global' } }); }, + + clone() { + this.router.transitionTo('global-admin.security.roles.new', { + queryParams: { + context: 'global', + id: this.id + } + }); + } } }); diff --git a/app/styles/components/_searchable-select.scss b/app/styles/components/_searchable-select.scss index 871c96a6b..f210564b0 100644 --- a/app/styles/components/_searchable-select.scss +++ b/app/styles/components/_searchable-select.scss @@ -43,6 +43,7 @@ font-weight: bold; border-bottom: 1px solid $accent-border; background: $dropdown-bg; + text-align: left; } > div { padding-left: $indent + $group-indent; diff --git a/lib/global-admin/addon/components/new-edit-role/component.js b/lib/global-admin/addon/components/new-edit-role/component.js index c614c3ab0..2d43e6c8e 100644 --- a/lib/global-admin/addon/components/new-edit-role/component.js +++ b/lib/global-admin/addon/components/new-edit-role/component.js @@ -1,10 +1,11 @@ -import { alias, equal, or } from '@ember/object/computed'; +import { alias, or } from '@ember/object/computed'; import { inject as service } from '@ember/service'; import Component from '@ember/component'; -import NewOrEdit from 'ui/mixins/new-or-edit'; import C from 'ui/utils/constants'; import layout from './template'; import { get, computed, set } from '@ember/object'; +import { isEmpty } from '@ember/utils'; +import ViewNewEdit from 'shared/mixins/view-new-edit'; const ruleVerbs = C.RULE_VERBS.map((verb) => `rolesPage.new.form.allow.${ verb }`); @@ -23,22 +24,22 @@ const BASIC_CONTEXT = [ }, ]; -export default Component.extend(NewOrEdit, { - intl: service(), - router: service(), +export default Component.extend(ViewNewEdit, { + intl: service(), + router: service(), layout, - model: null, + model: null, - ruleArray: null, - roleArray: null, - readOnly: null, - roleType: null, - contexts: BASIC_CONTEXT, + ruleArray: null, + roleArray: null, + readOnly: null, + roleType: null, + contexts: BASIC_CONTEXT, + mode: 'new', ruleVerbs, - primaryResource: alias('model.role'), - isGlobal: equal('roleType', 'global'), - readOnlyBuiltInOrGlobal: or('readOnly', 'builtIn', 'isGlobal'), + primaryResource: alias('model.role'), + readOnlyOrBuiltIn: or('readOnly', 'builtIn', 'isView'), init() { this._super(...arguments); @@ -119,6 +120,59 @@ export default Component.extend(NewOrEdit, { return get(this, 'model.roles').filter((role) => get(this, 'model.role.id') !== role.id); }), + ruleResources: computed('model.globalRoles.[]', 'model.roleTemplates.[]', function() { + const { + model: { globalRoles, roles: roleTemplates }, + roleType + } = this; + let groupedResourceRules; + + switch (roleType) { + case 'global': + if (!isEmpty(globalRoles)) { + groupedResourceRules = this.getRuleResourceList(globalRoles); + } + break; + default: + if (!isEmpty(roleTemplates)) { + groupedResourceRules = this.getRuleResourceList(roleTemplates.filterBy('context', roleType)); + } + break; + } + + return groupedResourceRules; + }), + + getRuleResourceList(roles) { + const groupedResourceRules = []; + + roles.forEach((role) => { + if (!isEmpty(role.rules)) { + // currently is ungrouped but can be grouped. + // The problem is that these are just default resources in a particular role, + // they are not unique so they show up duplicated under different groups. + // we need some discussion whether this is okay or not + // const group = role.name; + + role.rules.forEach((rule) => { + if (!isEmpty(rule.resources)) { + rule.resources.forEach((resource) => { + if (resource !== '*') { + groupedResourceRules.push({ + // group, + label: resource, + value: resource + }); + } + }); + } + }); + } + }); + + return groupedResourceRules.uniqBy('value').sortBy('label'); + }, + getDefaultField(type) { let out = ''; diff --git a/lib/global-admin/addon/components/new-edit-role/template.hbs b/lib/global-admin/addon/components/new-edit-role/template.hbs index 6eb93d2c6..da1b31566 100644 --- a/lib/global-admin/addon/components/new-edit-role/template.hbs +++ b/lib/global-admin/addon/components/new-edit-role/template.hbs @@ -1,9 +1,9 @@

- {{#if readOnlyBuiltInOrGlobal}} + {{#if readOnlyOrBuiltIn}} {{t "rolesPage.title"}}: {{model.role.name}} - {{else if editing}} + {{else if isEdit}} {{t "rolesPage.editRole"}} {{else}} {{t "rolesPage.addRole" context=readableRole}} @@ -12,7 +12,7 @@

-{{#unless readOnlyBuiltInOrGlobal}} +{{#unless readOnlyOrBuiltIn}}
{{form-name-description model=model.role @@ -22,7 +22,7 @@
{{/unless}} -{{#if (and model.role.description readOnlyBuiltInOrGlobal)}} +{{#if (and model.role.description readOnlyOrBuiltIn)}}
{{banner-message color="bg-secondary mb-0 mt-10" @@ -65,7 +65,7 @@
{{/if}} - {{#if editing}} + {{#if isEdit}}

{{t "rolesPage.new.form.locked.detail"}}

@@ -105,7 +105,7 @@ {{/if}} - {{#if editing}} + {{#if isEdit}}

{{t "rolesPage.new.form.locked.detail"}}

@@ -125,7 +125,7 @@ title=(t "rolesPage.resources.title") }} {{#if ruleArray.length}} - +
{{#each ruleVerbs as |verb|}} @@ -140,21 +140,23 @@ {{#each ruleArray as |rule|}} - {{role-rule-row - readOnly=readOnlyBuiltInOrGlobal - rule=rule - remove=(action "removeRule") - }} + {{/each}}
- {{else if readOnlyBuiltInOrGlobal}} + {{else if readOnlyOrBuiltIn}} {{t "generic.none"}} {{/if}} - {{#unless readOnlyBuiltInOrGlobal}} + {{#unless readOnlyOrBuiltIn}}
-
- {{/unless}} - {{/accordion-list-item}} + {{#unless readOnlyOrBuiltIn}} +
+ +
+ {{/unless}} + {{/accordion-list-item}} + {{/unless}} {{/accordion-list}} {{top-errors errors=errors}} {{#unless readOnly}} {{save-cancel - editing=editing + editing=isEdit save=(action "save") cancel=(action "cancel") }} diff --git a/lib/global-admin/addon/components/role-rule-row/component.js b/lib/global-admin/addon/components/role-rule-row/component.js index 90f72f81a..de2087324 100644 --- a/lib/global-admin/addon/components/role-rule-row/component.js +++ b/lib/global-admin/addon/components/role-rule-row/component.js @@ -2,6 +2,7 @@ import Component from '@ember/component'; import { get, set, observer } from '@ember/object' import C from 'ui/utils/constants'; import layout from './template'; +import { isEmpty } from '@ember/utils'; const verbs = C.RULE_VERBS; @@ -12,13 +13,15 @@ export default Component.extend({ resource: null, apiGroup: null, readOnly: null, + editing: true, tagName: 'TR', classNames: 'main-row', init() { this._super(...arguments); - const rule = get(this, 'rule'); + const { rule } = this; + let { rules } = this; const currentVerbs = get(rule, 'verbs'); set(this, 'verbs', verbs.map((verb) => { @@ -27,14 +30,18 @@ export default Component.extend({ value: currentVerbs.indexOf('*') > -1 || currentVerbs.indexOf(verb) > -1, }; })); - const rules = C.ROLE_RULES.sort(); - set(this, 'rules', rules.map((rule) => { - return { - label: rule, - value: rule.toLowerCase(), - }; - })); + if (isEmpty(rules)) { + rules = C.ROLE_RULES.sort(); + + set(this, 'rules', rules.map((rule) => { + return { + label: rule, + value: rule.toLowerCase(), + }; + })); + } + if ((get(rule, 'resources') || []).get('length') > 0) { set(this, 'resource', get(rule, 'resources').join(',')); } diff --git a/lib/global-admin/addon/components/role-rule-row/template.hbs b/lib/global-admin/addon/components/role-rule-row/template.hbs index ddc1a534d..1222fcf44 100644 --- a/lib/global-admin/addon/components/role-rule-row/template.hbs +++ b/lib/global-admin/addon/components/role-rule-row/template.hbs @@ -1,5 +1,5 @@ {{#each verbs as |verb|}} - + {{#if readOnly}} {{#if verb.value}} @@ -7,28 +7,52 @@ {{/if}} {{else}} - {{input type="checkbox" checked=verb.value disabled=readOnly}} + {{/if}} {{/each}} - + {{#if (and readOnly rule.nonResourceURLs)}} - Non-Resource URL: {{join-array rule.nonResourceURLs}} + + Non-Resource URL: {{join-array rule.nonResourceURLs}} + {{else}} - {{searchable-select allowCustom=true content=rules value=resource readOnly=readOnly}} + + + {{/if}} -  - - {{input type="text" - value=apiGroup - classNames="form-control" - disabled=readOnly - }} +  + + + + -  +  {{#unless readOnly}}
- +
{{/unless}} diff --git a/lib/global-admin/addon/security/roles/detail/template.hbs b/lib/global-admin/addon/security/roles/detail/template.hbs index f8c994b6c..cde6dfb07 100644 --- a/lib/global-admin/addon/security/roles/detail/template.hbs +++ b/lib/global-admin/addon/security/roles/detail/template.hbs @@ -1,6 +1,6 @@ -{{new-edit-role - model=model - readOnly=true - editing=false - roleType=type -}} \ No newline at end of file + \ No newline at end of file diff --git a/lib/global-admin/addon/security/roles/edit/template.hbs b/lib/global-admin/addon/security/roles/edit/template.hbs index 5901c1343..7cc692309 100644 --- a/lib/global-admin/addon/security/roles/edit/template.hbs +++ b/lib/global-admin/addon/security/roles/edit/template.hbs @@ -1,5 +1,5 @@ -{{new-edit-role - model=model - editing=true - roleType=type -}} \ No newline at end of file + \ No newline at end of file diff --git a/lib/global-admin/addon/security/roles/index/controller.js b/lib/global-admin/addon/security/roles/index/controller.js index e1cc92026..ec42056ed 100644 --- a/lib/global-admin/addon/security/roles/index/controller.js +++ b/lib/global-admin/addon/security/roles/index/controller.js @@ -65,7 +65,7 @@ export default Controller.extend({ return get(this, 'model.roleTemplates').filter( (role ) => !get(role, 'hidden') && (get(role, 'context') !== 'project') || !role.hasOwnProperty('context')); }), - filteredContent: computed('context', 'model.roleTemplates.@each.{name,state,transitioning}', 'showOnlyDefaults', function() { + filteredContent: computed('context', 'clusterRows.@each.{name,state,transitioning}', 'projectRows.@each.{name,state,transitioning}', 'showOnlyDefaults', 'globalRows.@each.{name,state}', function() { let content = null; const { context, showOnlyDefaults } = this; let headers = [...HEADERS]; diff --git a/lib/global-admin/addon/security/roles/index/route.js b/lib/global-admin/addon/security/roles/index/route.js index a4da34335..3a609de1d 100644 --- a/lib/global-admin/addon/security/roles/index/route.js +++ b/lib/global-admin/addon/security/roles/index/route.js @@ -10,7 +10,7 @@ export default Route.extend({ model(/* params */) { return hash({ roleTemplates: get(this, 'roleTemplateService.allVisibleRoleTemplates'), - globalRoles: get(this, 'globalStore').find('globalRole'), + globalRoles: get(this, 'globalStore').findAll('globalrole'), }); }, diff --git a/lib/global-admin/addon/security/roles/index/template.hbs b/lib/global-admin/addon/security/roles/index/template.hbs index 16edbd1e6..08adb7f99 100644 --- a/lib/global-admin/addon/security/roles/index/template.hbs +++ b/lib/global-admin/addon/security/roles/index/template.hbs @@ -10,19 +10,17 @@ {{t "rolesPage.headers.project"}} - {{#unless (eq context "global")}} -
- {{#link-to - "security.roles.new" - (query-params context=context) - classNames="btn btn-sm bg-primary right-divider-btn" - disabled=(rbac-prevents resource="roletemplate" scope="global" permission="create") - }} - {{t "rolesPage.addRole" context=readableMode}} - {{/link-to}} - -
- {{/unless}} +
+ {{#link-to + "security.roles.new" + (query-params context=context) + classNames="btn btn-sm bg-primary right-divider-btn" + disabled=(rbac-prevents resource="globalrole" scope="global" permission="create") + }} + {{t "rolesPage.addRole" context=readableMode}} + {{/link-to}} + +
{{#sortable-table diff --git a/lib/global-admin/addon/security/roles/new/controller.js b/lib/global-admin/addon/security/roles/new/controller.js index 28cb8e64a..b6d7a2e84 100644 --- a/lib/global-admin/addon/security/roles/new/controller.js +++ b/lib/global-admin/addon/security/roles/new/controller.js @@ -1,6 +1,7 @@ import Controller from '@ember/controller'; export default Controller.extend({ - queryParams: ['id'], + queryParams: ['id', 'context'], id: null, + context: null, }); diff --git a/lib/global-admin/addon/security/roles/new/route.js b/lib/global-admin/addon/security/roles/new/route.js index 50d457abb..1216bd425 100644 --- a/lib/global-admin/addon/security/roles/new/route.js +++ b/lib/global-admin/addon/security/roles/new/route.js @@ -6,37 +6,53 @@ import { hash } from 'rsvp'; export default Route.extend({ globalStore: service(), roleTemplateService: service('roleTemplate'), + model( params ) { const store = get(this, 'globalStore'); return hash({ - policies: store.find('podSecurityPolicyTemplate'), - roles: get(this, 'roleTemplateService').fetchFilteredRoleTemplates(null, null), + policies: store.find('podSecurityPolicyTemplate'), + roles: get(this, 'roleTemplateService').fetchFilteredRoleTemplates(null, null), + globalRoles: store.find('globalRole'), }).then( (res) => { - const id = get(params, 'id'); + const { id, context = 'project' } = params; let role; if ( id ) { - role = res.roles.findBy('id', id); + if (context === 'global') { + role = res.globalRoles.findBy('id', id); + } else { + role = res.roles.findBy('id', id); + } if ( !role ) { this.replaceWith('security.roles.index'); } role = role.cloneForNew() ; + + set(role, 'context', context); + delete role['builtin']; delete role['annotations']; delete role['labels']; delete role['links']; } else { - role = store.createRecord({ - type: 'roleTemplate', - context: get(params, 'context') || 'project', - name: '', - rules: [], - hidden: false, - locked: false, - }); + if (context === 'global') { + role = store.createRecord({ + type: 'globalRole', + context + }); + } else { + role = store.createRecord({ + type: 'roleTemplate', + name: '', + rules: [], + hidden: false, + locked: false, + context + }); + } } set(res, 'role', role); @@ -50,6 +66,10 @@ export default Route.extend({ setProperties(controller, { type: get(model, 'role.context') }); }, - queryParams: { context: { refreshModel: false } }, + + queryParams: { + context: { refreshModel: true }, + id: { refreshModel: true } + }, }); diff --git a/lib/global-admin/addon/security/roles/new/template.hbs b/lib/global-admin/addon/security/roles/new/template.hbs index 64d3260cd..b75a95483 100644 --- a/lib/global-admin/addon/security/roles/new/template.hbs +++ b/lib/global-admin/addon/security/roles/new/template.hbs @@ -1,5 +1,5 @@ -{{new-edit-role - model=model - editing=false - roleType=type -}} \ No newline at end of file + \ No newline at end of file