diff --git a/changelog/19799.txt b/changelog/19799.txt new file mode 100644 index 000000000000..aee76ca689aa --- /dev/null +++ b/changelog/19799.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Fix bad link to namespace when namespace name includes `.` +``` \ No newline at end of file diff --git a/ui/app/adapters/namespace.js b/ui/app/adapters/namespace.js index 64e1df53091f..cf5ae6868135 100644 --- a/ui/app/adapters/namespace.js +++ b/ui/app/adapters/namespace.js @@ -5,28 +5,28 @@ import ApplicationAdapter from './application'; -export default ApplicationAdapter.extend({ +export default class NamespaceAdapter extends ApplicationAdapter { pathForType() { return 'namespaces'; - }, + } urlForFindAll(modelName, snapshot) { if (snapshot.adapterOptions && snapshot.adapterOptions.forUser) { return `/${this.urlPrefix()}/internal/ui/namespaces`; } return `/${this.urlPrefix()}/namespaces?list=true`; - }, + } urlForCreateRecord(modelName, snapshot) { const id = snapshot.attr('path'); return this.buildURL(modelName, id); - }, + } createRecord(store, type, snapshot) { const id = snapshot.attr('path'); - return this._super(...arguments).then(() => { + return super.createRecord(...arguments).then(() => { return { id }; }); - }, + } findAll(store, type, sinceToken, snapshot) { if (snapshot.adapterOptions && typeof snapshot.adapterOptions.namespace !== 'undefined') { @@ -34,9 +34,9 @@ export default ApplicationAdapter.extend({ namespace: snapshot.adapterOptions.namespace, }); } - return this._super(...arguments); - }, + return super.findAll(...arguments); + } query() { return this.ajax(`/${this.urlPrefix()}/namespaces?list=true`); - }, -}); + } +} diff --git a/ui/app/components/namespace-link.js b/ui/app/components/namespace-link.js index 3f2e2ca88238..45a213ee3330 100644 --- a/ui/app/components/namespace-link.js +++ b/ui/app/components/namespace-link.js @@ -16,10 +16,13 @@ export default Component.extend({ //public api targetNamespace: null, showLastSegment: false, + // set to true if targetNamespace is passed in unmodified + // otherwise, this assumes it is parsed as in namespace-picker + unparsed: false, - normalizedNamespace: computed('targetNamespace', function () { - const ns = this.targetNamespace; - return (ns || '').replace(/\.+/g, '/').replace(/☃/g, '.'); + normalizedNamespace: computed('targetNamespace', 'unparsed', function () { + const ns = this.targetNamespace || ''; + return this.unparsed ? ns : ns.replace(/\.+/g, '/').replace(/☃/g, '.'); }), namespaceDisplay: computed('normalizedNamespace', 'showLastSegment', function () { diff --git a/ui/app/models/namespace.js b/ui/app/models/namespace.js index 205d934a01b7..87b964b1008f 100644 --- a/ui/app/models/namespace.js +++ b/ui/app/models/namespace.js @@ -4,21 +4,21 @@ */ import Model, { attr } from '@ember-data/model'; -import { computed } from '@ember/object'; -import { expandAttributeMeta } from 'vault/utils/field-to-attrs'; +import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes'; -export default Model.extend({ - path: attr('string', { +@withExpandedAttributes() +export default class NamespaceModel extends Model { + @attr('string', { validationAttr: 'pathIsValid', invalidMessage: 'You have entered and invalid path please only include letters, numbers, -, ., and _.', - }), - pathIsValid: computed('path', function () { + }) + path; + + get pathIsValid() { return this.path && this.path.match(/^[\w\d-.]+$/g); - }), - description: attr('string', { - editType: 'textarea', - }), - fields: computed(function () { - return expandAttributeMeta(this, ['path']); - }), -}); + } + + get fields() { + return ['path'].map((f) => this.allByKey[f]); + } +} diff --git a/ui/app/serializers/namespace.js b/ui/app/serializers/namespace.js index ed75185c898c..f94a818718d7 100644 --- a/ui/app/serializers/namespace.js +++ b/ui/app/serializers/namespace.js @@ -5,7 +5,11 @@ import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ +export default class NamespaceSerializer extends ApplicationSerializer { + attrs = { + path: { serialize: false }, + }; + normalizeList(payload) { const data = payload.data.keys ? payload.data.keys.map((key) => ({ @@ -16,7 +20,7 @@ export default ApplicationSerializer.extend({ : payload.data; return data; - }, + } normalizeResponse(store, primaryModelClass, payload, id, requestType) { const nullResponses = ['deleteRecord', 'createRecord']; @@ -24,6 +28,6 @@ export default ApplicationSerializer.extend({ const normalizedPayload = nullResponses.includes(requestType) ? { id: cid, path: cid } : this.normalizeList(payload); - return this._super(store, primaryModelClass, normalizedPayload, id, requestType); - }, -}); + return super.normalizeResponse(store, primaryModelClass, normalizedPayload, id, requestType); + } +} diff --git a/ui/app/templates/vault/cluster/access/namespaces/index.hbs b/ui/app/templates/vault/cluster/access/namespaces/index.hbs index 989078788ba3..c4e56997ca6d 100644 --- a/ui/app/templates/vault/cluster/access/namespaces/index.hbs +++ b/ui/app/templates/vault/cluster/access/namespaces/index.hbs @@ -35,7 +35,7 @@ {{#let (concat this.currentNamespace (if this.currentNamespace "/") list.item.id) as |targetNamespace|}} {{#if (includes targetNamespace this.accessibleNamespaces)}}