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 @@
+
+
+
+ {{#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}}
-
-{{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}}
-

- {{/if}}
-
-
-
{{t 'newCatalog.catalog'}} {{templateResource.catalogId}}
+
{{!-- container-section --}}
+
- {{#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)}}
+
+
-
+
+
+
+ {{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 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'