diff --git a/app/apps-tab/detail/controller.js b/app/apps-tab/detail/controller.js new file mode 100644 index 000000000..4b43de61a --- /dev/null +++ b/app/apps-tab/detail/controller.js @@ -0,0 +1,206 @@ +import Controller from '@ember/controller'; +import { get, computed } from '@ember/object'; +import { + searchFields as containerSearchFields +} from 'shared/components/pod-dots/component'; + +const podsHeaders = [ + { + name: 'expand', + sort: false, + searchField: null, + width: 30 + }, + { + name: 'state', + sort: ['sortState', 'displayName'], + searchField: 'displayState', + translationKey: 'generic.state', + width: 120 + }, + { + name: 'name', + sort: ['sortName', 'id'], + searchField: 'displayName', + translationKey: 'generic.name', + }, + { + name: 'image', + sort: ['image', 'displayName'], + searchField: 'image', + translationKey: 'generic.image', + }, + { + name: 'scale', + sort: ['scale:desc', 'isGlobalScale:desc', 'displayName'], + searchField: null, + translationKey: 'stacksPage.table.scale', + classNames: 'text-center', + width: 100 + }, +] + +const ingressHeaders = [ + { + name: 'state', + sort: ['sortState','displayName'], + searchField: 'displayState', + translationKey: 'generic.state', + width: 120 + }, + { + name: 'name', + sort: ['sortName','id'], + searchField: 'displayName', + translationKey: 'generic.name', + }, + { + name: 'created', + sort: ['created','id'], + classNames: 'text-right pr-20', + searchField: 'created', + translationKey: 'generic.created', + }, +] + +const servicesHeaders = [ + { + name: 'state', + sort: ['stack.isDefault:desc','stack.displayName','sortState','displayName'], + searchField: 'displayState', + translationKey: 'generic.state', + width: 120 + }, + { + name: 'name', + sort: ['stack.isDefault:desc','stack.displayName','displayName','id'], + searchField: 'displayName', + translationKey: 'generic.name', + }, + { + name: 'displayType', + sort: ['displayType','displayName','id'], + searchField: 'displayType', + translationKey: 'generic.type', + width: 150, + }, + { + name: 'target', + sort: false, + searchField: 'displayTargets', + translationKey: 'dnsPage.table.target', + }, +] + +const volumesHeaders = [ + // { + // name: 'expand', + // sort: false, + // searchField: null, + // width: 30 + // }, + { + name: 'state', + sort: ['sortState','displayName'], + searchField: 'displayState', + translationKey: 'generic.state', + width: 120 + }, + { + name: 'name', + sort: ['displayName','id'], + searchField: 'displayName', + translationKey: 'volumesPage.claimName.label', + }, + { + name: 'size', + sort: ['sizeBytes'], + search: ['sizeBytes','displaySize'], + translationKey: 'generic.size', + width: 120 + }, + { + name: 'volume', + sort: ['volume.displayName','displayName','id'], + translationKey: 'volumesPage.volume.label', + searchField: null, + }, + { + name: 'storageClass', + sort: ['storageClass.displayName','displayName','id'], + translationKey: 'volumesPage.storageClass.label', + searchField: null, + }, +] + +const secretsHeaders = [ + { + name: 'state', + sort: ['sortState','name','id'], + type: 'string', + searchField: 'displayState', + translationKey: 'generic.state', + width: 125, + }, + { + name: 'name', + sort: ['name','id'], + translationKey: 'generic.name', + }, + { + name: 'namespace', + translationKey: 'generic.namespace', + searchField: 'namespace.displayName', + sort: ['namespace.displayName','name','id'], + }, + { + name: 'keys', + translationKey: 'secretsPage.table.keys', + searchField: 'keys', + sort: ['firstKey','name','id'], + }, + { + name: 'created', + translationKey: 'generic.created', + sort: ['created:desc','name','id'], + searchField: false, + type: 'string', + width: 150, + }, +] + +export default Controller.extend({ + // TODO =- expand logic? + expandedInstances: [], + ingressHeaders: ingressHeaders, + servicesHeaders: servicesHeaders, + volumesHeaders: volumesHeaders, + secretsHeaders: secretsHeaders, + ingressSearchText: '', + secretsSearchText: '', + podsHeaders: podsHeaders, + podsSearchText: '', + servicesSearchText: '', + volumesSearchText: '', + sortBy: 'name', + extraSearchFields: ['id:prefix', 'displayIp:ip'], + extraSearchSubFields: containerSearchFields, + actions: { + toggleExpand() { + // ??? + }, + }, + stdOut: computed('model.app.stdOut', function() { + return get(this, 'model.app.status.stdOutput'); + }), + stderr: computed('model.app.stdErr', function() { + return get(this, 'model.app.status.stdError'); + }), + + workloadsAndPods: computed('model.app.workloads', 'model.app.pods', function() { + let out = []; + out = this.get('model.app.pods').filter(obj => !obj.get('workloadId')); + out.pushObjects(this.get('model.app.workloads').slice()); + return out; + }), +}); diff --git a/app/apps-tab/detail/route.js b/app/apps-tab/detail/route.js new file mode 100644 index 000000000..632e785d4 --- /dev/null +++ b/app/apps-tab/detail/route.js @@ -0,0 +1,16 @@ +import { get } from '@ember/object'; +import { inject as service } from '@ember/service'; +import Route from '@ember/routing/route'; +import { hash } from 'rsvp'; + +export default Route.extend({ + catalog: service(), + store: service(), + + model(params) { + const store = get(this, 'store'); + return hash({ + app: store.find('app', get(params, 'app_id')), + }); + }, +}); diff --git a/app/apps-tab/detail/template.hbs b/app/apps-tab/detail/template.hbs new file mode 100644 index 000000000..4e5426a4b --- /dev/null +++ b/app/apps-tab/detail/template.hbs @@ -0,0 +1,243 @@ +
+
+

+ {{t 'appDetailPage.header' appName=model.app.displayName}} +

+
+
+
+ {{badge-state model=model.app}} + {{action-menu model=model.app showPrimary=false classNames="ml-10 pull-right" size="sm"}} +
+
+ +
+ {{#accordion-list as |al expandFn|}} + {{#accordion-list-item + title=(t 'appDetailPage.output.title') + detail=(t 'appDetailPage.output.detail') + expandAll=al.expandAll + expand=(action expandFn) + expandOnInit=true + }} +
{{stdOut}}
+ {{/accordion-list-item}} + + {{#accordion-list-item + title=(t 'appDetailPage.workloads.title') + detail=(t 'appDetailPage.workloads.detail') + expandAll=al.expandAll + expand=(action expandFn) + expandOnInit=true + }} + {{#sortable-table + tableClassNames="double-rows" + classNames="grid sortable-table" + body=workloadsAndPods + searchText=podsSearchText + sortBy=sortBy + bulkActions=true + subRows=true + pagingLabel="pagination.workload" + subSearchField="pods" + extraSearchFields=extraSearchFields + extraSearchSubFields=extraSearchSubFields + headers=podsHeaders as |sortable kind inst dt|}} + {{#if (eq kind "row")}} + {{#if (eq inst.type "pod")}} + {{pod-row + model=inst + dt=dt + showNode=true + expandPlaceholder=true + scalePlaceholder=true + fullColspan=sortable.fullColspan + toggle=(action "toggleExpand" inst.id) + expanded=(array-includes expandedInstances inst.id) + }} + {{else}} + {{workload-row + model=inst + toggle=(action "toggleExpand" inst.id) + expanded=(array-includes expandedInstances inst.id) + searchText=searchText + subMatches=sortable.subMatches + fullColspan=sortable.fullColspan + dt=dt + }} + {{/if}} + {{else if (eq kind "nomatch")}} + {{t 'containersPage.table.noMatch'}} + {{else if (eq kind "norows")}} + {{t 'appDetailPage.workloads.nodata'}} + {{/if}} + {{/sortable-table}} + {{!-- + --}} + {{/accordion-list-item}} + + {{#accordion-list-item + title=(t 'appDetailPage.ingress.title') + detail=(t 'appDetailPage.ingress.detail') + expandAll=al.expandAll + expand=(action expandFn) + expandOnInit=true + }} +
+ {{#sortable-table + classNames="grid sortable-table" + body=model.app.ingress + searchText=ingressSearchText + sortBy=sortBy + bulkActions=true + pagingLabel="pagination.ingress" + subSearchField="instances" + headers=ingressHeaders as |sortable kind inst dt| + }} + {{#if (eq kind "row")}} + + + {{check-box nodeId=inst.id}} + + + {{badge-state model=inst}} + + + {{inst.displayName}} + + + {{date-calendar inst.created}} + + + {{action-menu model=inst}} + + + {{else if (eq kind "nomatch")}} + {{t 'ingressPage.table.noMatch'}} + {{else if (eq kind "norows")}} + {{t 'appDetailPage.ingress.nodata'}} + {{/if}} + {{/sortable-table}} +
+ {{/accordion-list-item}} + + {{#accordion-list-item + title=(t 'appDetailPage.services.title') + detail=(t 'appDetailPage.services.detail') + expandAll=al.expandAll + expand=(action expandFn) + expandOnInit=true + }} + {{#sortable-table + classNames="grid sortable-table" + body=model.app.services + searchText=servicesSearchText + sortBy=sortBy + bulkActions=true + pagingLabel="pagination.dnsRecord" + headers=servicesHeaders as |sortable kind inst dt| + }} + {{#if (eq kind "row")}} + {{dns-row + model=inst + searchText=searchText + fullColspan=sortable.fullColspan + dt=dt + }} + {{else if (eq kind "nomatch")}} + {{t 'dnsPage.noMatch'}} + {{else if (eq kind "norows")}} + {{t 'appDetailPage.services.nodata'}} + {{/if}} + {{/sortable-table}} + {{/accordion-list-item}} + + {{#accordion-list-item + title=(t 'appDetailPage.volumes.title') + detail=(t 'appDetailPage.volumes.detail') + expandAll=al.expandAll + expand=(action expandFn) + expandOnInit=true + }} +
+ {{#sortable-table + classNames="grid sortable-table" + body=model.app.volumes + searchText=volumesSearchText + sortBy=sortBy + bulkActions=true + pagingLabel="pagination.volume" + headers=volumesHeaders as |sortable kind obj dt| + }} + {{#if (eq kind "row")}} + + + {{check-box nodeId=obj.id}} + + + {{badge-state model=obj}} + + + {{obj.displayName}} + + + {{obj.displaySize}} + + + {{#if obj.persistentVolume}} + + {{obj.persistentVolume.displayName}} + + {{else}} + + {{/if}} + + + {{#if obj.storageClass}} + + {{obj.storageClass.displayName}} + + {{else}} + + {{/if}} + + + {{action-menu model=obj}} + + + {{else if (eq kind "nomatch")}} + {{t 'volumesPage.noMatch'}} + {{else if (eq kind "norows")}} + {{t 'appDetailPage.volumes.nodata'}} + {{/if}} + {{/sortable-table}} +
+ {{/accordion-list-item}} + + {{#accordion-list-item + title=(t 'appDetailPage.secrets.title') + detail=(t 'appDetailPage.secrets.detail') + expandAll=al.expandAll + expand=(action expandFn) + expandOnInit=true + }} + {{#sortable-table + classNames="grid sortable-table" + body=model.app.secrets + sortBy=sortBy + bulkActions=true + searchText=secretsSearchText + headers=secretsHeaders as |sortable kind row dt| + }} + {{#if (eq kind "row")}} + {{secret-row model=row dt=dt}} + {{else if (eq kind "nomatch")}} + {{t 'secretsPage.index.noMatch'}} + {{else if (eq kind "norows")}} + {{t 'appDetailPage.secrets.nodata'}} + {{/if}} + {{/sortable-table}} + {{/accordion-list-item}} + + {{/accordion-list}} +
\ No newline at end of file diff --git a/app/models/app.js b/app/models/app.js index d4bc28fae..dc9258ded 100644 --- a/app/models/app.js +++ b/app/models/app.js @@ -1,14 +1,27 @@ import Resource from 'ember-api-store/models/resource'; -import { hasMany } from 'ember-api-store/utils/denormalize'; +import { hasMany, reference } from 'ember-api-store/utils/denormalize'; import { computed, get } from '@ember/object'; import { parseHelmExternalId } from 'ui/utils/parse-externalid'; import StateCounts from 'ui/mixins/state-counts'; import { inject as service } from '@ember/service'; const App = Resource.extend(StateCounts, { - catalog: service(), - router: service(), - pods: hasMany('installNamespace', 'pod', 'namespaceId'), + catalog: service(), + router: service(), + // pods: hasMany('id', 'pod', 'appId'), + // services: hasMany('id', 'service', 'appId'), + // workloads: hasMany('id', 'workload', 'appId'), + // secrets: hasMany('id', 'secret', 'appId'), + // ingress: hasMany('id', 'ingress', 'appId'), + // volumes: hasMany('id', 'persistentVolumeClaim', 'appId'), + pods: hasMany('installNamespace', 'pod', 'namespaceId'), + services: hasMany('installNamespace', 'service', 'namespaceId'), + workloads: hasMany('installNamespace', 'workload', 'namespaceId'), + secrets: hasMany('installNamespace', 'secret', 'namespaceId'), + ingress: hasMany('installNamespace', 'ingress', 'namespaceId'), + volumes: hasMany('installNamespace', 'persistentVolumeClaim', 'namespaceId'), + namespace: reference('namespaceId', 'namespace', 'clusterStore'), + //workloads on pod init() { this._super(...arguments); diff --git a/app/router.js b/app/router.js index 708e3c694..25ac7bb1b 100644 --- a/app/router.js +++ b/app/router.js @@ -150,11 +150,13 @@ Router.map(function() { // Catalog this.route('apps-tab', {path: '/apps', resetNamespace: true}, function() { this.route('index', {path: '/'}); + this.route('detail', {path: '/:app_id'}); this.route('catalog-tab', {path: '/catalog', resetNamespace: true}, function() { this.route('index', {path: '/'}); this.route('launch', {path: '/:template'}); }); + }); // Resources diff --git a/lib/global-admin/addon/security/authentication/activedirectory/controller.js b/lib/global-admin/addon/security/authentication/activedirectory/controller.js index 50456d4ef..6a7c1c55b 100644 --- a/lib/global-admin/addon/security/authentication/activedirectory/controller.js +++ b/lib/global-admin/addon/security/authentication/activedirectory/controller.js @@ -4,71 +4,71 @@ import Controller from '@ember/controller'; import Errors from 'ui/utils/errors'; import C from 'ui/utils/constants'; import { alias } from '@ember/object/computed'; -import { computed, observer } from '@ember/object'; +import { get, set, computed, observer } from '@ember/object'; var PLAIN_PORT = 389; var TLS_PORT = 636; export default Controller.extend({ - access: service(), - settings: service(), + access: service(), + settings: service(), confirmDisable: false, - errors: null, - testing: false, + errors: null, + testing: false, - providerName: 'ldap.providerName.ad', - userType: C.PROJECT.TYPE_LDAP_USER, - groupType: C.PROJECT.TYPE_LDAP_GROUP, + providerName: 'ldap.providerName.ad', + userType: C.PROJECT.TYPE_LDAP_USER, + groupType: C.PROJECT.TYPE_LDAP_GROUP, - addUserInput: '', - addOrgInput: '', + addUserInput: '', + addOrgInput: '', - username: '', - password: '', - isEnabled: alias('model.activeDirectory.enabled'), - adConfig: alias('model.activeDirectory'), + username: '', + password: '', + isEnabled: alias('model.activeDirectory.enabled'), + adConfig: alias('model.activeDirectory'), init() { this._super(...arguments); - if (this.get('adConfig')){ + if (get(this, 'adConfig')){ this.tlsChanged(); } }, createDisabled: computed('username.length','password.length', function() { - return !this.get('username.length') || !this.get('password.length'); + return !get(this, 'username.length') || !get(this, 'password.length'); }), numUsers: computed('adConfig.allowedIdentities.@each.externalIdType','userType','groupType', function() { - return (this.get('adConfig.allowedIdentities')||[]).filterBy('externalIdType', this.get('userType')).get('length'); + return ( get(this, 'adConfig.allowedPrincipalIds') || [] ).filter(principal => principal.includes(C.PROJECT.TYPE_ACTIVE_DIRECTORY_USER)).get('length'); }), numGroups: computed('adConfig.allowedIdentities.@each.externalIdType','userType','groupType', function() { - return (this.get('adConfig.allowedIdentities')||[]).filterBy('externalIdType', this.get('groupType')).get('length'); + return ( get(this, 'adConfig.allowedPrincipalIds') || [] ).filter(principal => principal.includes(C.PROJECT.TYPE_ACTIVE_DIRECTORY_GROUP)).get('length'); }), configServers: computed('adConfig.servers', { get() { - return (this.get('adConfig.servers')||[]).join(','); + return (get(this, 'adConfig.servers')||[]).join(','); }, set(key, value) { - this.set('adConfig.servers', value.split(',')); + set(this, 'adConfig.servers', value.split(',')); return value; } }), tlsChanged: observer('adConfig.tls', function() { - var on = (this.get('adConfig.tls')||false); - var port = parseInt(this.get('adConfig.port'),10); + var on = (get(this, 'adConfig.tls')||false); + var port = parseInt(get(this, 'adConfig.port'),10); if ( on && port === PLAIN_PORT ) { - this.set('adConfig.port', TLS_PORT); + set(this, 'adConfig.port', TLS_PORT); } else if ( !on /* && port === TLS_PORT */ ) // TODO 2.0 { - this.set('adConfig.port', PLAIN_PORT); - this.set('adConfig.tls', false); + set(this, 'adConfig.port', PLAIN_PORT); + set(this, 'adConfig.tls', false); } }), @@ -76,7 +76,7 @@ export default Controller.extend({ test: function() { this.send('clearError'); - var model = this.get('adConfig'); + var model = get(this, 'adConfig'); model.setProperties({ accessMode: 'unrestricted', }); @@ -84,16 +84,16 @@ export default Controller.extend({ var errors = model.validationErrors(); if ( errors.get('length') ) { - this.set('errors', errors); + set(this, 'errors', errors); } else { - this.set('testing', true); + set(this, 'testing', true); model.doAction('testAndApply', { activeDirectoryConfig: model, enabled: true, - username: this.get('username'), - password: this.get('password'), + username: get(this, 'username'), + password: get(this, 'password'), }).then( () => { this.send('waitAndRefresh'); }).catch((err) => { @@ -111,25 +111,25 @@ export default Controller.extend({ }, promptDisable: function() { - this.set('confirmDisable', true); + set(this, 'confirmDisable', true); later(this, function() { - this.set('confirmDisable', false); + set(this, 'confirmDisable', false); }, 10000); }, gotError: function(err) { - this.set('errors', [Errors.stringify(err)]); - this.set('testing', false); + set(this, 'errors', [Errors.stringify(err)]); + set(this, 'testing', false); }, clearError: function() { - this.set('errors', null); + set(this, 'errors', null); }, disable: function() { this.send('clearError'); - var model = this.get('adConfig'); + var model = get(this, 'adConfig'); model.setProperties({ enabled: false, }); @@ -140,7 +140,7 @@ export default Controller.extend({ }).catch((err) => { this.send('gotError', err); }).finally(() => { - this.set('confirmDisable', false); + set(this, 'confirmDisable', false); }); }, }, diff --git a/lib/global-admin/addon/security/authentication/github/controller.js b/lib/global-admin/addon/security/authentication/github/controller.js index e49e53387..d6eae4a42 100644 --- a/lib/global-admin/addon/security/authentication/github/controller.js +++ b/lib/global-admin/addon/security/authentication/github/controller.js @@ -4,6 +4,7 @@ import { once, later } from '@ember/runloop'; import { alias } from '@ember/object/computed'; import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; +import C from 'ui/utils/constants'; export default Controller.extend({ github: service(), @@ -52,13 +53,12 @@ export default Controller.extend({ }), numUsers: computed('githubConfig.allowedPrincipals.@each.externalIdType','wasRestricted', function() { - return 3; //TODO - // return get(this, 'githubConfig.principals').filterBy('externalIdType',C.PROJECT.TYPE_GITHUB_USER).get('length'); + return ( get(this, 'githubConfig.allowedPrincipalIds') || []).filter(principal => principal.includes(C.PROJECT.TYPE_GITHUB_USER)).get('length'); }), numOrgs: computed('githubConfig.allowedPrincipals.@each.externalIdType','wasRestricted',function() { - return 4; //TODO - // return get(this, 'githubConfig.principals').filterBy('externalIdType',C.PROJECT.TYPE_GITHUB_ORG).get('length'); + + return ( get(this, 'githubConfig.allowedPrincipalIds') || []).filter(principal => principal.includes(C.PROJECT.TYPE_GITHUB_ORG)).get('length'); }), destinationUrl: computed(function() { diff --git a/lib/global-admin/addon/templates/-ldap-config.hbs b/lib/global-admin/addon/templates/-ldap-config.hbs index 5e05315bd..a0870f6e0 100644 --- a/lib/global-admin/addon/templates/-ldap-config.hbs +++ b/lib/global-admin/addon/templates/-ldap-config.hbs @@ -46,7 +46,7 @@

{{t 'ldap.accessEnabled.general.header'}}

-
{{t 'ldap.accessEnabled.general.server'}} {{adConfig.server}}:{{adConfig.port}}
+
{{t 'ldap.accessEnabled.general.server'}} {{adConfig.servers.firstObject}}:{{adConfig.port}}
{{t 'ldap.accessEnabled.general.tls'}} {{if adConfig.tls "Yes" "No"}}
{{t 'ldap.accessEnabled.general.serviceAccount'}} {{adConfig.serviceAccountUsername}}
{{#unless isOpenLdap}} diff --git a/lib/login/addon/components/login-ad/component.js b/lib/login/addon/components/login-ad/component.js deleted file mode 100644 index 5c9f78d90..000000000 --- a/lib/login/addon/components/login-ad/component.js +++ /dev/null @@ -1,110 +0,0 @@ -import { get, set, computed } from '@ember/object'; -import { next } from '@ember/runloop'; -import { inject as service } from '@ember/service'; -import Component from '@ember/component'; -import C from 'ui/utils/constants'; - -export default Component.extend({ - access: service(), - cookies: service(), - isCaas: computed('app.mode', function() { - return this.get('app.mode') === 'caas' ? true : false; - }), - waiting: null, - - username: null, - rememberUsername: false, - password: null, - shown: false, - provider: null, - readableProvider: null, - - actions: { - showLocal() { - this.toggleProperty('shown'); - next(this, 'focusSomething'); - }, - authenticate: function() { - const username = get(this, 'username'); - let password = get(this, 'password'); - const remember = get(this, 'rememberUsername'); - - if (password && get(this, 'provider') === 'local') { - password = password.trim(); - } - - const code = { - username: username, - password: password, - }; - - if ( remember ) { - if (get(this, 'provider') === 'local') { - get(this, 'cookies').setWithOptions(C.COOKIE.USERNAME, username, {expire: 365, secure: 'auto'}); - } else { - get(this, 'cookies').setWithOptions(`${get(this, 'provider').toUpperCase()}-USERNAME`, username, {expire: 365, secure: 'auto'}); - } - } else { - get(this, 'cookies').remove(C.COOKIE.USERNAME); - } - - set(this, 'password', ''); - if ( get(this,'access.providers') ) { - this.sendAction('action', get(this, 'provider'), code); - } - } - }, - - init() { - this._super(...arguments); - - var username = null; - if (get(this, 'provider') === 'local') { - username = get(this, `cookies.${C.COOKIE.USERNAME}`); - } else { - username = get(this, `cookies.${get(this, 'provider').toUpperCase()}-USERNAME`); - } - - if ( username ) { - set(this, 'username', username); - set(this, 'rememberUsername', true); - } - - if (get(this, 'provider')) { - let pv = null; - switch(get(this, 'provider')) { - case 'activedirectory': - pv = 'Active Directory'; - break; - case 'local': - default: - pv = 'Local Auth'; - break; - } - - set(this, 'readableProvider', pv); - - // console.log(this.get('provider')); - } - - }, - - focusSomething() { - if ( this.isDestroyed || this.isDestroying ) { - return; - } - - let elem = this.$('#login-username-ad'); - if ( get(this, 'username') ) { - elem = this.$('#login-password-ad'); - } - - if ( elem && elem[0] ) { - elem[0].focus(); - } - }, - - didInsertElement() { - next(this, 'focusSomething'); - }, -}); diff --git a/lib/login/addon/components/login-ad/template.hbs b/lib/login/addon/components/login-ad/template.hbs deleted file mode 100644 index 524078407..000000000 --- a/lib/login/addon/components/login-ad/template.hbs +++ /dev/null @@ -1,42 +0,0 @@ -{{#if shown}} -
-
-
- {{#if isCaas}} - - {{else}} - - {{/if}} -
- -
-
-
- {{input type="text" id="login-username-ad" autocomplete="username" class="form-control login-user" value=username placeholder=(t 'loginUserPass.userPlaceholder')}} -
-
- -
- -
- {{input id="login-password-ad" autocomplete="password" type="password" class="form-control login-pass" value=password}} -
-
- -

- -

-
-{{else}} - {{#if (eq provider 'local')}} - {{t 'loginUserPass.local'}} - {{else}} - {{t 'loginUserPass.provider' kind=readableProvider}} - {{/if}} -{{/if}} diff --git a/lib/login/addon/components/login-user-pass/component.js b/lib/login/addon/components/login-user-pass/component.js index 29de7b02b..fd2027817 100644 --- a/lib/login/addon/components/login-user-pass/component.js +++ b/lib/login/addon/components/login-user-pass/component.js @@ -108,4 +108,9 @@ export default Component.extend({ didInsertElement() { next(this, 'focusSomething'); }, + + willDestroyElement() { + set(this, 'shown', false); + }, + }); diff --git a/lib/login/addon/components/login-user-pass/template.hbs b/lib/login/addon/components/login-user-pass/template.hbs index da9747587..1095f74b9 100644 --- a/lib/login/addon/components/login-user-pass/template.hbs +++ b/lib/login/addon/components/login-user-pass/template.hbs @@ -12,14 +12,14 @@
- {{input type="text" id="login-username" autocomplete="username" class="form-control login-user" value=username placeholder=(t 'loginUserPass.userPlaceholder')}} + {{input type="text" id=(concat "login-username-" provider) autocomplete="username" class="form-control login-user" value=username placeholder=(t 'loginUserPass.userPlaceholder')}}
- {{input id="login-password" autocomplete="password" type="password" class="form-control login-pass" value=password}} + {{input id=(concat "login-password-" provider) autocomplete="password" type="password" class="form-control login-pass" value=password}}
diff --git a/lib/login/addon/login/controller.js b/lib/login/addon/login/controller.js index e23be83f7..d5cfb2dec 100644 --- a/lib/login/addon/login/controller.js +++ b/lib/login/addon/login/controller.js @@ -24,6 +24,9 @@ export default Controller.extend({ isForbidden: equal('errorCode', '403'), waiting: false, + adWaiting: false, + localWaiting: false, + shibbolethWaiting: false, errorMsg: null, errorCode: null, resetPassword: false, @@ -76,7 +79,6 @@ export default Controller.extend({ }, actions: { - started() { setProperties(this, { 'waiting': true, @@ -84,6 +86,27 @@ export default Controller.extend({ }); }, + waiting(provider) { + // setProperties(this, { + // 'waiting': true, + // 'errorMsg': null, + // }); + set(this, 'errorMsg', null); + switch (provider) { + case 'local': + this.toggleProperty('localWaiting'); + break; + case 'activedirectory': + this.toggleProperty('adWaiting'); + break; + case 'shibboleth': + this.toggleProperty('shibbolethWaiting'); + break; + default: + break; + } + }, + complete(success) { if (success) { this.shouldSetServerUrl().then((proceed) => { @@ -102,7 +125,7 @@ export default Controller.extend({ }, authenticate(provider, code) { - this.send('started'); + this.send('waiting', provider); later(() => { get(this, 'access').login(provider, code).then((user) => { @@ -118,9 +141,10 @@ export default Controller.extend({ get(this, 'access').set('userCode', null); get(this, 'access').set('firstLogin', false); this.send('complete', true); + this.send('waiting', provider); } }).catch((err) => { - set(this, 'waiting', false); + this.send('waiting', provider); if ( err && err.status === 401 ) { let key = 'loginPage.error.authFailed' diff --git a/lib/login/addon/login/route.js b/lib/login/addon/login/route.js index 6d2361985..55df8a5fa 100644 --- a/lib/login/addon/login/route.js +++ b/lib/login/addon/login/route.js @@ -54,8 +54,13 @@ export default Route.extend({ resetController(controller, isExisting /*, transition*/ ) { if (isExisting) { - controller.set('changePassword', false); - controller.set('waiting',false); + controller.setProperties({ + changePassword: false, + waiting: false, + adWaiting: false, + shibbolethWaiting: false, + localWaiting: false, + }) } } }); diff --git a/lib/login/addon/login/template.hbs b/lib/login/addon/login/template.hbs index dd6d8f031..3698efac5 100644 --- a/lib/login/addon/login/template.hbs +++ b/lib/login/addon/login/template.hbs @@ -16,7 +16,7 @@
{{login-shibboleth action="started" - waiting=waiting + waiting=shibbolethWaiting }} {{/if}} @@ -25,23 +25,23 @@ {{/if}} {{#if isActiveDirectory}} - {{login-ad - classNames="row" + {{login-user-pass action="authenticate" - waiting=waiting - shown=true + classNames="row" provider="activedirectory" + shown=true + waiting=adWaiting }} {{/if}} {{#if isLocal}} {{login-user-pass - classNames="row" action="authenticate" - waiting=waiting - shown=onlyLocal + classNames="row" onlyLocal=onlyLocal provider="local" + shown=onlyLocal + waiting=localWaiting }} {{/if}} diff --git a/lib/shared/addon/catalog/service.js b/lib/shared/addon/catalog/service.js index f75fd05f3..55225ee00 100644 --- a/lib/shared/addon/catalog/service.js +++ b/lib/shared/addon/catalog/service.js @@ -5,7 +5,7 @@ import C from 'shared/utils/constants'; import EmberObject from '@ember/object' import { get } from '@ember/object'; import { /* parseExternalId, */ parseHelmExternalId } from 'ui/utils/parse-externalid'; -import { all } from 'rsvp'; +import { all, allSettled } from 'rsvp'; const RANCHER_VERSION = 'rancherVersion'; const DEFAULT_BASE = 'kubernetes'; @@ -47,7 +47,7 @@ export default Service.extend({ deps.push(this.fetchTemplate(extInfo.templateId, false)); }); - return all(deps); + return allSettled(deps); }, fetchCatalogs(opts) { diff --git a/lib/shared/addon/components/input-answers/component.js b/lib/shared/addon/components/input-answers/component.js index e7871ec65..f375726f4 100644 --- a/lib/shared/addon/components/input-answers/component.js +++ b/lib/shared/addon/components/input-answers/component.js @@ -9,6 +9,7 @@ export default Component.extend({ questions: alias('selectedTemplate.questions'), pasteOrUpload: false, accept : '.yml, .yaml', + showHeader: true, _boundChange : null, didInsertElement() { this.set('_boundChange', (event) => { this.change(event); }); diff --git a/lib/shared/addon/components/input-answers/template.hbs b/lib/shared/addon/components/input-answers/template.hbs index 015e3933c..7e552efac 100644 --- a/lib/shared/addon/components/input-answers/template.hbs +++ b/lib/shared/addon/components/input-answers/template.hbs @@ -3,8 +3,13 @@ -

{{t 'inputAnswers.config'}}

- {{t 'inputAnswers.protip'}} +
+ {{#if showHeader}} +

{{t 'inputAnswers.config'}}

+ {{t 'inputAnswers.protip'}} + {{/if}} +   +
{{#if pasteOrUpload}} {{textarea-autogrow diff --git a/lib/shared/addon/components/namespace-app/template.hbs b/lib/shared/addon/components/namespace-app/template.hbs index 2e67ed915..37906572b 100644 --- a/lib/shared/addon/components/namespace-app/template.hbs +++ b/lib/shared/addon/components/namespace-app/template.hbs @@ -5,9 +5,19 @@
- {{model.displayName}} + {{#link-to "apps-tab.detail" model.id}} + {{model.displayName}} + {{/link-to}}
+ {{#upgrade-btn model=model classNames="btn-xs " as |btn|}} + {{#if btn.model.externalIdInfo.version}} + {{t (concat 'upgradeBtn.status.' btn.upgradeStatus)}} + ({{btn.latestVersion}}) + {{else}} + {{t (concat 'upgradeBtn.status.' btn.upgradeStatus)}} + {{/if}} + {{/upgrade-btn}} {{badge-state model=model classNames="btn-xs"}} {{action-menu model=model classNames="inline-block"}}
@@ -25,17 +35,15 @@ {{model.pods.length}}
- {{#upgrade-btn model=model classNames="btn-sm" as |btn|}} - {{model.externalIdInfo.version}} - {{#if btn.model.externalIdInfo.version}} - {{t (concat 'upgradeBtn.status.' btn.upgradeStatus)}} - ({{btn.latestVersion}}) - {{else}} - {{t (concat 'upgradeBtn.status.' btn.upgradeStatus)}} +
+ {{#if model.workloads}} +
+ {{#each model.workloads as |workload|}} + {{workload.displayEndpoints}} + {{/each}} +
{{/if}} - {{/upgrade-btn}} +
- -{{#if expanded}} -{{/if}} + \ No newline at end of file diff --git a/lib/shared/addon/components/new-catalog/component.js b/lib/shared/addon/components/new-catalog/component.js index e190e621e..530e32e73 100644 --- a/lib/shared/addon/components/new-catalog/component.js +++ b/lib/shared/addon/components/new-catalog/component.js @@ -121,7 +121,7 @@ export default Component.extend(NewOrEdit, { value: file.name } }); - files.addObject({label: 'answers', value: 'answers'}); + files.addObject({label: 'answers.yaml', value: 'answers'}); return files.sortBy('label'); }), diff --git a/lib/shared/addon/components/new-catalog/template.hbs b/lib/shared/addon/components/new-catalog/template.hbs index 94e543306..4b75a0528 100644 --- a/lib/shared/addon/components/new-catalog/template.hbs +++ b/lib/shared/addon/components/new-catalog/template.hbs @@ -8,131 +8,134 @@ {{/if}} -
-
- {{#if templateResource.links.icon}} - {{templateResource.name}} - {{/if}} - -
- {{t 'newCatalog.catalog'}} {{templateResource.catalogId}} +
{{!-- container-section --}} +
{{!-- header-section --}} +
+ {{t 'newCatalog.appInfo'}}
-
- {{t 'newCatalog.kind'}} {{templateKind}} +
+
{{!-- content-section --}} +
+ {{#if templateResource.links.icon}} + {{templateResource.name}} + {{/if}}
-
- {{t 'newCatalog.category'}} {{join-array templateResource.categoryArray}} -
- {{#if (eq templateResource.certifiedType 'rancher')}} - {{t 'newCatalog.support'}} {{t 'newCatalog.official'}} + {{#if readmeContent}} +
+ {{marked-down markdown=readmeContent}} +
{{else}} - {{t 'newCatalog.support'}} {{t 'newCatalog.maintainedBy'}} - {{/if}} - {{#if templateResource.maintainer}} -
- {{t 'newCatalog.maintainer'}} {{templateResource.maintainer}} -
- {{/if}} - {{#if templateResource.license}} -
- {{t 'newCatalog.license'}} {{templateResource.license}} -
- {{/if}} - {{#if templateResource.links.project}} - {{t 'newCatalog.url'}} {{templateResource.cleanProjectUrl}} +

+ {{t (if editing titleUpgrade titleAdd) name=templateResource.name}} +

+

{{templateResource.description}}

{{/if}}
- {{#if readmeContent}} -
- {{marked-down markdown=readmeContent}} -
- {{else}} -

- {{t (if editing titleUpgrade titleAdd) name=templateResource.name}} -

-

{{templateResource.description}}

- {{/if}} -
-
-

{{t 'newCatalog.newApp'}}

- {{form-name-description - model=catalogApp - nameRequired=true - descriptionShow=false - nameDisabled=showName - }} + - {{#if showName}} - {{#advanced-section}} -
- {{form-namespace - namespace=primaryResource - mode='reuse' - errors=namespaceErrors - }} - {{/advanced-section}} - {{/if}} - -
- -
-

{{t 'newCatalog.templateVersion'}}

-
-
- {{new-select - classNames="form-control" - content=sortedVersions - prompt="newCatalog.version.prompt" - localizedPrompt=true - optionLabelPath="version" - optionValuePath="link" - value=selectedTemplateUrl - disabled=getTemplate.isRunning - }} -

{{t (if editing selectVersionUpgrade selectVersionAdd)}}

+
+
+
+ {{t 'newCatalog.newApp'}}
-
+
+
+
+ {{form-name-description + model=catalogApp + nameRequired=true + descriptionShow=false + nameDisabled=showName + bothColClass="col span-12" + colClass="col span-12" + }} +
+
{{!-- matches styles of form-name-description --}} + + {{new-select + classNames="form-control" + content=sortedVersions + prompt="newCatalog.version.prompt" + localizedPrompt=true + optionLabelPath="version" + optionValuePath="link" + value=selectedTemplateUrl + disabled=getTemplate.isRunning + }} +

{{t (if editing selectVersionUpgrade selectVersionAdd)}}

+
+
+
+ {{#if showName}} + {{#advanced-section}} +
-
- {{#if getTemplate.isRunning}} + {{form-namespace + namespace=primaryResource + mode='reuse' + errors=namespaceErrors + }} + {{/advanced-section}} + {{/if}} +
+
+ + +{{#if getTemplate.isRunning}} +
- {{/if}} - - {{#if selectedTemplateModel}} - {{#if (eq templateKind 'native')}} - {{input-answers - selectedTemplate=selectedTemplateModel - }} - {{else}} -

{{t 'newCatalog.helm.label'}}

-
- {{#if selectedTemplateModel.questions}} - {{input-answers - selectedTemplate=selectedTemplateModel - }} +
+{{else}} +
+
+
+ {{#if (or (eq templateKind 'native') selectedTemplateModel.questions)}} + {{t 'inputAnswers.config'}} + {{t 'inputAnswers.protip'}} {{else}} - {{form-key-value - initialMap=catalogApp.answers - changed=(action (mut catalogApp.answers)) - allowEmptyValue=false - editing=true - header=(t 'newCatalog.answers.label') - addActionLabel="newCatalog.answers.addAction" - keyLabel="newContainer.environment.keyLabel" - keyPlaceholder="newContainer.environment.keyPlaceholder" - valueLabel="newContainer.environment.valueLabel" - valuePlaceholder="newContainer.environment.valuePlaceholder" - }} + {{t 'newCatalog.helm.label'}} + {{t 'newCatalog.helm.protip'}} {{/if}}
- {{/if}} - - {{/if}} - +
+
+ {{#if selectedTemplateModel}} + {{#if (eq templateKind 'native')}} + {{input-answers + selectedTemplate=selectedTemplateModel + showHeader=false + }} + {{else}} +
+ {{#if selectedTemplateModel.questions}} + {{input-answers + selectedTemplate=selectedTemplateModel + showHeader=false + }} + {{else}} + {{form-key-value + initialMap=catalogApp.answers + changed=(action (mut catalogApp.answers)) + allowEmptyValue=false + editing=true + header=(t 'newCatalog.answers.label') + addActionLabel="newCatalog.answers.addAction" + keyLabel="newContainer.environment.keyLabel" + keyPlaceholder="newContainer.environment.keyPlaceholder" + valueLabel="newContainer.environment.valueLabel" + valuePlaceholder="newContainer.environment.valuePlaceholder" + }} + {{/if}} +
+ {{/if}} + {{/if}} +
+
+{{/if}} {{#if (and selectedTemplateModel (not getTemplate.isRunning))}} {{#if showPreview}} diff --git a/lib/shared/addon/components/upgrade-btn/template.hbs b/lib/shared/addon/components/upgrade-btn/template.hbs index 5a6b900a7..e256cd7b0 100644 --- a/lib/shared/addon/components/upgrade-btn/template.hbs +++ b/lib/shared/addon/components/upgrade-btn/template.hbs @@ -1,6 +1,6 @@ {{#if hasBlock}} - {{ yield this}} -{{ else }} + {{yield this}} +{{else}} {{#if currentVersion}} {{#tooltip-element type="tooltip-basic" model=currentVersion tooltipTemplate='tooltip-static' aria-describedby="tooltip-base" tooltipFor="upgrade"}} {{t (concat 'upgradeBtn.status.' upgradeStatus)}} diff --git a/lib/shared/addon/mixins/upgrade-component.js b/lib/shared/addon/mixins/upgrade-component.js index da98ffbb9..793b641cb 100644 --- a/lib/shared/addon/mixins/upgrade-component.js +++ b/lib/shared/addon/mixins/upgrade-component.js @@ -44,7 +44,7 @@ export default Mixin.create({ color: computed('upgradeStatus', function() { switch ( get(this, 'upgradeStatus') ) { case NONE: - return 'hide'; + return 'none'; case CURRENT: case LOADING: return 'bg-default'; @@ -98,10 +98,11 @@ export default Mixin.create({ }, updateStatus() { - let state = get(this, 'model.state'); - let info = get(this, 'model.externalIdInfo'); + let state = get(this, 'model.state'); + let info = get(this, 'model.externalIdInfo'); let catalogTemplate = get(this, 'model.catalogTemplate'); - let upgradeVersions={}; + let upgradeVersions = {}; + let allVersions = {}; if ( state === 'upgraded' ) { set(this, 'upgradeStatus', UPGRADED); @@ -119,7 +120,14 @@ export default Mixin.create({ set(this, 'upgradeStatus', NONE); } - upgradeVersions = parseUpgradeVersions(catalogTemplate.get('versionLinks'), info.version, get(this, 'model')); + if ( catalogTemplate ) { + upgradeVersions = parseUpgradeVersions( + get(catalogTemplate, 'versionLinks'), + get(info, 'version'), + get(this, 'model') + ); + get(catalogTemplate, 'versionLinks'); + } if (Object.keys(upgradeVersions).length >= 1) { setProperties(this, { @@ -127,7 +135,10 @@ export default Mixin.create({ latestVersion: Object.keys(upgradeVersions)[Object.keys(upgradeVersions).length-1], }); } else { - set(this, 'upgradeStatus', NONE); + setProperties(this, { + upgradeStatus: CURRENT, + latestVersion: get(info, 'version'), + }); } // console.log('upgradeVersions', upgradeVersions); @@ -149,7 +160,7 @@ export default Mixin.create({ } setProperties(this, { - allVersions: catalogTemplate.get('versionLinks'), + allVersions: allVersions, upgradeVersions: upgradeVersions }); return; diff --git a/lib/shared/addon/utils/constants.js b/lib/shared/addon/utils/constants.js index 548fd46e6..7dd2d48a2 100644 --- a/lib/shared/addon/utils/constants.js +++ b/lib/shared/addon/utils/constants.js @@ -188,18 +188,20 @@ var C = { }, PROJECT: { - TYPE_RANCHER: 'local', - TYPE_AZURE_USER: 'azuread_user', - TYPE_AZURE_GROUP: 'azuread_group', - TYPE_GITHUB_USER: 'github_user', - TYPE_GITHUB_TEAM: 'github_team', - TYPE_GITHUB_ORG: 'github_org', - TYPE_LDAP_USER: 'ldap_user', - TYPE_LDAP_GROUP: 'ldap_group', - TYPE_OPENLDAP_USER: 'openldap_user', - TYPE_OPENLDAP_GROUP: 'openldap_group', - TYPE_SHIBBOLETH_USER: 'shibboleth_user', - TYPE_SHIBBOLETH_GROUP: 'shibboleth_group', + TYPE_RANCHER: 'local', + TYPE_ACTIVE_DIRECTORY_USER: 'activedirectory_user', + TYPE_ACTIVE_DIRECTORY_GROUP: 'activedirectory_group', + TYPE_AZURE_USER: 'azuread_user', + TYPE_AZURE_GROUP: 'azuread_group', + TYPE_GITHUB_USER: 'github_user', + TYPE_GITHUB_TEAM: 'github_team', + TYPE_GITHUB_ORG: 'github_org', + TYPE_LDAP_USER: 'ldap_user', + TYPE_LDAP_GROUP: 'ldap_group', + TYPE_OPENLDAP_USER: 'openldap_user', + TYPE_OPENLDAP_GROUP: 'openldap_group', + TYPE_SHIBBOLETH_USER: 'shibboleth_user', + TYPE_SHIBBOLETH_GROUP: 'shibboleth_group', PERSON: 'person', TEAM: 'team', diff --git a/translations/en-us.yaml b/translations/en-us.yaml index df605e7cb..9bcedeeba 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -167,6 +167,32 @@ accountsPage: modal: password: Change Password +appDetailPage: + header: "App: {appName}" + output: + title: Launch Output + detail: TBD + workloads: + title: Workloads + detail: Workloads created for this application. + nodata: No workloads were created for this application. + ingress: + title: Ingress Rules + detail: Ingress rules created for this application. + nodata: No ingress rules were created for this application. + services: + title: Services + detail: Services created with this application + nodata: No services were created for this application. + volumes: + title: Volumes + detail: Persistant Volume claims created with this application + nodata: No volume claims were made for this application. + secrets: + title: Secrets + detail: Secrets associated with this application + nodata: This application has no secrets + podSecurityPoliciesPage: index: header: Pod Security Policies @@ -311,8 +337,6 @@ authPage: buttonText: pre: Authenticate with IDP post: Waiting to hear back from IDP - - providerName: shibboleth: Shibboleth root: @@ -323,33 +347,32 @@ authPage: header: enabled: label: "{github} is enabled" - required: "{appName} is configured to allow access to authorized users and organizations." - restricted: "{appName} is configured to allow access to project members, authorized users and organizations." -# required: | -# {appName} is configured to allow access to {orgs, plural, -# =0 {no organizations} -# =1 {# organization} -# other {# organizations} -# } and {users, plural, -# =0 {no users} -# =1 {# user} -# other {# users} -# }. -# restricted: | -# {appName} is configured to allow access to project members, {orgs, plural, -# =0 {no organizations} -# =1 {# organization} -# other {# organizations} -# } and {users, plural, -# =0 {no users} -# =1 {# user} -# other {# users} -# }. -# unrestricted: "{appName} is configured to allow access to any {github} user." + # required: "{appName} is configured to allow access to authorized users and organizations." + # restricted: "{appName} is configured to allow access to project members, authorized users and organizations." + required: | + {appName} is configured to allow access to {orgs, plural, + =0 {no organizations} + =1 {# organization} + other {# organizations} + } and {users, plural, + =0 {no users} + =1 {# user} + other {# users} + }. + restricted: | + {appName} is configured to allow access to project members, {orgs, plural, + =0 {no organizations} + =1 {# organization} + other {# organizations} + } and {users, plural, + =0 {no users} + =1 {# user} + other {# users} + }. + # unrestricted: "{appName} is configured to allow access to any {github} user." disabled: label: GitHub is not configured warning: "{appName} can be configured to restrict access to a set of GitHub users and organization members." - authenticated: header: text: Authentication @@ -398,7 +421,6 @@ authPage: buttonText: pre: Authenticate with GitHub post: Waiting to hear back from GitHub - azuread: header: enabled: 'Azure AD Authentication is enabled' @@ -439,8 +461,6 @@ authPage: label: Login Password pre: Authenticate with Azure post: Waiting to hear back from Azure - - localAuth: header: enabled: 'Local Authentication is enabled' @@ -652,6 +672,7 @@ ingressPage: header: 'Ingress: {name}' table: noMatch: No ingresses match the current search. + noData: You do not have any ingress rules yet. containerPage: header: 'Container: {name}' portsTab: @@ -4162,6 +4183,7 @@ newCatalog: maintainer: "Maintainer:" newNamespace: New Namespace newApp: New Application + appInfo: Application Read Me noConfig: This template has no configuration options official: Officially Certified preview: Preview @@ -4945,7 +4967,7 @@ upgradeBtn: version: current: 'Current' status: - none: 'None' + none: 'Upgrade: None' loading: 'Checking upgrades...' current: 'Up to date' available: 'Upgrade available'