Merge pull request #1844 from loganhz/fixes

Catalog and Fixes
This commit is contained in:
Vincent Fiduccia 2018-04-24 14:52:24 -07:00 committed by GitHub
commit cb5653835b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 478 additions and 135 deletions

View File

@ -204,6 +204,16 @@ export default Controller.extend({
this.set('expandedInstances',[]);
},
publicEndpoints: computed('model.app.workloads.@each.publicEndpoints', function() {
let out = [];
get(this, 'model.app.workloads').forEach((workload) => {
(get(workload, 'publicEndpoints') || []).forEach((endpoint) => {
out.push(endpoint);
});
});
return out;
}),
workloadsAndPods: computed('model.app.workloads', 'model.app.pods', function() {
let out = [];
out = this.get('model.app.pods').filter(obj => !obj.get('workloadId'));

View File

@ -54,6 +54,18 @@
expandOnInit=false
}}
{{#accordion-list-item
title=(t 'appDetailPage.endpoints.title')
detail=(t 'appDetailPage.endpoints.detail')
expandAll=al.expandAll
expand=(action expandFn)
expandOnInit=true
}}
{{form-endpoints
model=publicEndpoints
}}
{{/accordion-list-item}}
{{#accordion-list-item
title=(t 'appDetailPage.workloads.title')
detail=(t 'appDetailPage.workloads.detail')

View File

@ -24,8 +24,11 @@ export default Controller.extend({
return get(this,'model.projects').filterBy('clusterId', get(this,'scope.currentCluster.id'));
}),
projectsWithoutNamespaces: computed('projects.@each.{id,state,clusterId}', 'rows.@each.namespaces', function(){
return get(this, 'projects').filter(p => get(p, 'namespaces.length') <= 0);
projectsWithoutNamespaces: computed('projects.@each.{id,state,clusterId}', 'rows.@each.projectId', function(){
return get(this, 'projects').filter(p => {
const namespaces = get(this, 'rows').filterBy('projectId', get(p, 'id')) || [];
return get(namespaces, 'length') <= 0;
});
}),
});

View File

@ -33,6 +33,7 @@ export default Route.extend(Preload,{
this.preload('dnsRecord'),
this.preload('secret'),
this.preload('service'),
this.preload('configmap'),
this.preload('namespacedSecret'),
this.preload('persistentVolumeClaim'),
]).then(() => {

View File

@ -98,19 +98,14 @@ export default Route.extend({
});
});
},
setupController(controller, model) {
this._super(controller, model);
if (model.upgradeTemplate) {
controller.set('showName', false);
}
},
resetController: function (controller, isExiting/*, transition*/) {
if (isExiting)
{
controller.set('namespaceId', null);
controller.set('template', null);
controller.set('upgrade', null);
controller.set('catalog', null);
controller.set('namespaceId', null);
controller.set('appId', null);
}
}
});

View File

@ -4,7 +4,6 @@
catalogApp=model.catalogApp
namespaceResource=model.namespace
parentRoute=parentRoute
showName=showName
upgrade=model.upgradeTemplate
templateResource=model.tpl
templateKind=model.tplKind

View File

@ -152,6 +152,9 @@ const rootNav = [
ctx: [getClusterId],
resource: ['project'],
resourceScope: 'global',
condition: function() {
return this.get('cluster.isReady');
}
},
{
scope: 'cluster',

View File

@ -46,14 +46,16 @@ var Namespace = Resource.extend(StateCounts, {
catalog: service(),
scope: service(),
router: service(),
projectStore: service('store'),
globalStore: service(),
clusterStore: service(),
pods: hasMany('id', 'pod', 'namespaceId', 'store'),
workloads: hasMany('id', 'workload', 'namespaceId', 'store'),
services: hasMany('id', 'service', 'namespaceId', 'store'),
secrets: hasMany('id', 'namespacedSecret', 'namespaceId', 'store'),
ingress: hasMany('id', 'ingress', 'namespaceId', 'store'),
volumes: hasMany('id', 'persistentVolumeClaim', 'namespaceId', 'store'),
pods: hasMany('id', 'pod', 'namespaceId', 'projectStore', null, 'clusterStore'),
workloads: hasMany('id', 'workload', 'namespaceId', 'projectStore', null, 'clusterStore'),
services: hasMany('id', 'service', 'namespaceId', 'projectStore', null, 'clusterStore'),
secrets: hasMany('id', 'namespacedSecret', 'namespaceId', 'projectStore', null, 'clusterStore'),
ingress: hasMany('id', 'ingress', 'namespaceId', 'projectStore', null, 'clusterStore'),
volumes: hasMany('id', 'persistentVolumeClaim', 'namespaceId', 'projectStore', null, 'clusterStore'),
project: reference('projectId', 'project', 'globalStore'),
init() {

View File

@ -62,6 +62,10 @@ export default Resource.extend({
});
},
resetDefault() {
this.setDefault(false)
},
edit() {
get(this, 'router').transitionTo('authenticated.cluster.storage.classes.detail.edit', get(this, 'id'));
},
@ -80,8 +84,8 @@ export default Resource.extend({
annotations[DEFAULT_ANNOTATION] = 'true';
annotations[BETA_ANNOTATION] = 'true';
} else {
delete annotations[DEFAULT_ANNOTATION];
delete annotations[BETA_ANNOTATION];
annotations[DEFAULT_ANNOTATION] = 'false';
annotations[BETA_ANNOTATION] = 'false';
}
this.save();
@ -92,6 +96,7 @@ export default Resource.extend({
let out = [
{ label: 'action.makeDefault', icon: 'icon icon-star-fill', action: 'makeDefault', enabled: !isDefault },
{ label: 'action.resetDefault', icon: 'icon icon-star-line', action: 'resetDefault', enabled: isDefault },
];
return out;

View File

@ -52,7 +52,7 @@ const Template = Resource.extend({
}.property('category','categories.[]'),
categoryLowerArray: function() {
return this.get('categoryArray').map(x => (x||'').toLowerCase());
return this.get('categoryArray').map(x => (x||'').underscore().toLowerCase());
}.property('categoryArray.[]'),
supported: function() {

View File

@ -1,4 +1,4 @@
$container-height: 300px;
$container-height: 321px;
$container-width: 283px;
$font-size: 12px;
$line-height: 1.4;

View File

@ -1,7 +1,7 @@
import { inject as service } from '@ember/service';
import Controller from '@ember/controller';
import C from 'ui/utils/constants';
import { computed, get } from '@ember/object';
import { computed, get, set } from '@ember/object';
import { send } from 'ember-metal/events';
export default Controller.extend({
@ -11,15 +11,30 @@ export default Controller.extend({
settings: service(),
catalog: service(),
togglingHelmIncubator: false,
togglingHelmStable: false,
togglingLibrary: false,
actions: {
disableLibrary() {
if( get(this, 'togglingLibrary') ) {
return;
}
set(this, 'togglingLibrary', true);
get(this, 'library').delete().catch((err) => {
get(this, 'growl').fromError('Error removing Library', err);
}).finally(() => {
send(this, 'refresh')
set(this, 'togglingLibrary', false);
send(this, 'refresh');
});
},
enableLibrary() {
if( get(this, 'togglingLibrary') ) {
return;
}
set(this, 'togglingLibrary', true);
get(this, 'globalStore').createRecord({
type: 'catalog',
name: C.CATALOG.LIBRARY_KEY,
@ -29,18 +44,30 @@ export default Controller.extend({
}).save().catch((err) => {
get(this, 'growl').fromError('Error saving Library', err);
}).finally(() => {
send(this, 'refresh')
set(this, 'togglingLibrary', false);
send(this, 'refresh');
});
},
disableHelmIncubator() {
if( get(this, 'togglingHelmIncubator') ) {
return;
}
set(this, 'togglingHelmIncubator', true);
get(this, 'helmIncubator').delete().catch((err) => {
get(this, 'growl').fromError('Error removing Incubator', err);
}).finally(() => {
send(this, 'refresh')
set(this, 'togglingHelmIncubator', false);
send(this, 'refresh');
});
},
enableHelmIncubator() {
if( get(this, 'togglingHelmIncubator') ) {
return;
}
set(this, 'togglingHelmIncubator', true);
get(this, 'globalStore').createRecord({
type: 'catalog',
name: C.CATALOG.HELM_INCUBATOR_KEY,
@ -50,18 +77,30 @@ export default Controller.extend({
}).save().catch((err) => {
get(this, 'growl').fromError('Error saving Incubator', err);
}).finally(() => {
send(this, 'refresh')
set(this, 'togglingHelmIncubator', false);
send(this, 'refresh');
});
},
disableHelmStable() {
if( get(this, 'togglingHelmStable') ) {
return;
}
set(this, 'togglingHelmStable', true);
get(this, 'helmStable').delete().catch((err) => {
get(this, 'growl').fromError('Error removing Stable', err);
}).finally(() => {
send(this, 'refresh')
set(this, 'togglingHelmStable', false);
send(this, 'refresh');
});
},
enableHelmStable() {
if( get(this, 'togglingHelmStable') ) {
return;
}
set(this, 'togglingHelmStable', true);
get(this, 'globalStore').createRecord({
type: 'catalog',
name: C.CATALOG.HELM_STABLE_KEY,
@ -71,28 +110,8 @@ export default Controller.extend({
}).save().catch((err) => {
get(this, 'growl').fromError('Error saving Stable', err);
}).finally(() => {
send(this, 'refresh')
});
},
enableLibrary() {
get(this, 'globalStore').createRecord({
type: 'catalog',
name: C.CATALOG.LIBRARY_KEY,
url: C.CATALOG.LIBRARY_VALUE,
branch: C.CATALOG.LIBRARY_BRANCH,
}).save().catch((err) => {
get(this, 'growl').fromError('Error saving Library', err);
}).finally(() => {
send(this, 'refresh')
});
},
disableLibrary() {
get(this, 'stdLibrary').delete().catch((err) => {
get(this, 'growl').fromError('Error removing Library', err);
}).finally(() => {
send(this, 'refresh')
set(this, 'togglingHelmStable', false);
send(this, 'refresh');
});
},

View File

@ -14,13 +14,13 @@
<div>
{{#if library}}
<div class="btn-group no-inline-space">
<button class="btn btn-link btn-sm bg-default" {{action "disableLibrary"}}>{{t 'generic.disable'}}</button>
<button class="btn btn-link btn-sm bg-default" {{action "disableLibrary"}} disabled={{togglingLibrary}}>{{#if togglingLibrary}}<i class="icon icon-spinner icon-spin"></i> {{/if}}{{t 'generic.disable'}}</button>
<button class="btn btn-link btn-sm bg-success">{{t 'generic.enabled'}}</button>
</div>
{{else}}
<div class="btn-group no-inline-space">
<button class="btn btn-link btn-sm bg-primary">{{t 'generic.disabled'}}</button>
<button class="btn btn-link btn-sm bg-defualt" {{action "enableLibrary"}}>{{t 'generic.enable'}}</button>
<button class="btn btn-link btn-sm bg-defualt" {{action "enableLibrary"}} disabled={{togglingLibrary}}>{{#if togglingLibrary}}<i class="icon icon-spinner icon-spin"></i> {{/if}}{{t 'generic.enable'}}</button>
</div>
{{/if}}
</div>
@ -34,13 +34,13 @@
<div>
{{#if helmStable}}
<div class="btn-group no-inline-space">
<button class="btn btn-link btn-sm bg-default" {{action "disableHelmStable"}}>{{t 'generic.disable'}}</button>
<button class="btn btn-link btn-sm bg-default" {{action "disableHelmStable"}} disabled={{togglingHelmStable}}>{{#if togglingHelmStable}}<i class="icon icon-spinner icon-spin"></i> {{/if}}{{t 'generic.disable'}}</button>
<button class="btn btn-link btn-sm bg-success">{{t 'generic.enabled'}}</button>
</div>
{{else}}
<div class="btn-group no-inline-space">
<button class="btn btn-link btn-sm bg-primary">{{t 'generic.disabled'}}</button>
<button class="btn btn-link btn-sm bg-defualt" {{action "enableHelmStable"}}>{{t 'generic.enable'}}</button>
<button class="btn btn-link btn-sm bg-defualt" {{action "enableHelmStable"}} disabled={{togglingHelmStable}}>{{#if togglingHelmStable}}<i class="icon icon-spinner icon-spin"></i> {{/if}}{{t 'generic.enable'}}</button>
</div>
{{/if}}
</div>
@ -54,13 +54,13 @@
<div>
{{#if helmIncubator}}
<div class="btn-group no-inline-space">
<button class="btn btn-link btn-sm bg-default" {{action "disableHelmIncubator"}}>{{t 'generic.disable'}}</button>
<button class="btn btn-link btn-sm bg-default" {{action "disableHelmIncubator"}} disabled={{togglingHelmIncubator}}>{{#if togglingHelmIncubator}}<i class="icon icon-spinner icon-spin"></i> {{/if}}{{t 'generic.disable'}}</button>
<button class="btn btn-link btn-sm bg-success">{{t 'generic.enabled'}}</button>
</div>
{{else}}
<div class="btn-group no-inline-space">
<button class="btn btn-link btn-sm bg-primary">{{t 'generic.disabled'}}</button>
<button class="btn btn-link btn-sm bg-defualt" {{action "enableHelmIncubator"}}>{{t 'generic.enable'}}</button>
<button class="btn btn-link btn-sm bg-defualt" {{action "enableHelmIncubator"}} disabled={{togglingHelmIncubator}}>{{#if togglingHelmIncubator}}<i class="icon icon-spinner icon-spin"></i> {{/if}}{{t 'generic.enable'}}</button>
</div>
{{/if}}
</div>

View File

@ -0,0 +1,24 @@
import { get, set, computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import Service, { inject as service } from '@ember/service';
export default Service.extend({
clusterStore: service(),
_allStorageClasses: null,
init() {
const clusterStore = get(this, 'clusterStore');
set(this, '_allStorageClasses', clusterStore.all('storageclass'));
},
storageClasses: computed('_allStorageClasses.[]', function () {
return get(this, '_allStorageClasses').sortBy('name');
}),
list: alias('storageClasses'),
byId(id) {
return get(this, '_allStorageClasses').findBy('id', id);
},
});

View File

@ -3,7 +3,7 @@ import Service, { inject as service } from '@ember/service';
import { addQueryParams, uniqKeys } from 'shared/utils/util';
import C from 'shared/utils/constants';
import EmberObject from '@ember/object'
import { get } from '@ember/object';
import { set, get, observer } from '@ember/object';
import { /* parseExternalId, */ parseHelmExternalId } from 'ui/utils/parse-externalid';
import { allSettled } from 'rsvp';
@ -19,6 +19,32 @@ export default Service.extend({
templateCache: null,
catalogs: null,
_allCatalogs: null,
_refreshMap: null,
init() {
const store = get(this,'globalStore');
set(this, '_allCatalogs', store.all('catalog'));
set(this, '_refreshMap', {});
},
catalogsDidChange: observer('_allCatalogs.@each.state', '_refreshMap', function() {
if ( get(this, 'templateCache') !== null ) {
const oldRefreshMap = get(this, '_refreshMap');
const newRefreshMap = {};
(get(this, '_allCatalogs') || []).forEach((c) => {
newRefreshMap[get(c, 'id')] = get(c, 'lastRefreshTimestamp');
});
let needRefresh = false;
for (let k of new Set([...Object.keys(newRefreshMap), ...Object.keys(oldRefreshMap)])) {
if ( !oldRefreshMap.hasOwnProperty(k) || !newRefreshMap.hasOwnProperty(k) || oldRefreshMap[k] !== newRefreshMap[k] ) {
needRefresh = true;
}
}
set(this, 'needRefresh', needRefresh);
}
}),
reset() {
this.setProperties({
templateCache: null,
@ -99,11 +125,18 @@ export default Service.extend({
}
// If the catalogIds dont match we need to go get the other catalog from the store since we do not cache all catalogs
if ( cache && cache.catalogId === catalogId)
if ( cache && cache.catalogId === catalogId && !get(this, 'needRefresh') )
{
return resolve(this.filter(cache, params.category));
}
const catalogs = get(this, '_allCatalogs');
const refreshMap = {};
catalogs.forEach((c) => {
refreshMap[get(c, 'id')] = get(c, 'lastRefreshTimestamp');
});
set(this, '_refreshMap', refreshMap);
let url = this._addLimits(`${get(this,'app.apiEndpoint')}/templates`, qp);
return get(this,'store').request({url: url, headers: {[C.HEADER.PROJECT_ID]: get(this,'scope.currentProject.id')}}).then((res) => {
res.catalogId = catalogId;
@ -132,15 +165,15 @@ export default Service.extend({
},
filter(data, category) {
category = (category||'all').toLowerCase();
category = (category||'').toLowerCase();
let categories = [];
data.forEach((obj) => { categories.pushObjects(obj.get('categoryArray')); });
categories = uniqKeys(categories);
categories.unshift('all');
categories.unshift('');
data = data.filter((tpl) => {
if ( category !== 'all' && !tpl.get('categoryLowerArray').includes(category) ) {
if ( category !== '' && !tpl.get('categoryLowerArray').includes(category) ) {
return false;
}

View File

@ -70,7 +70,7 @@ export default Component.extend({
categories.sort().forEach((ctgy) => {
let normalized = ctgy.underscore();
if (out[normalized] && ctgy !== 'all') {
if (out[normalized] && ctgy) {
out[normalized].count++;
} else {
out[normalized] = {

View File

@ -37,7 +37,7 @@
{{#if opt.localizedLabel}}
{{t opt.localizedLabel}}
{{else}}
{{opt.label}}
<div class="text-capitalize">{{opt.label}}</div>
{{/if}}
</a>
</li>
@ -92,9 +92,10 @@
{{#each arrangedContent as |catalogItem|}}
{{#catalog-box model=catalogItem showSource=showCatalogDropdown as |section|}}
{{#if (eq section 'body')}}
<h3>
<a {{action (action launch catalogItem.id )}}>{{catalogItem.displayName}}</a>
<h3 class="mb-0">
<a class="pointer" {{action (action launch catalogItem.id )}}>{{catalogItem.displayName}}</a>
</h3>
<div>({{t 'generic.from'}} <span class="text-capitalize">{{catalogItem.catalogId}}</span>)</div>
<div class="mt-10 description">{{catalogItem.description}}</div>
{{else if (eq section 'footer')}}
<button type="button" class="btn btn-sm bg-primary" {{action (action launch catalogItem.id )}}>{{t 'catalogPage.index.action.launch'}}</button>

View File

@ -51,6 +51,7 @@
{{#if (eq step 1)}}
{{top-errors errors=errors}}
{{top-errors errors=otherErrors}}
{{top-errors errors=clusterErrors}}
{{save-cancel
save="awsLogin"
cancel=close
@ -95,6 +96,7 @@
{{top-errors errors=errors}}
{{top-errors errors=otherErrors}}
{{top-errors errors=clusterErrors}}
{{save-cancel editing=(eq mode 'edit') save="driverSave" cancel=close}}
{{/if}}
{{/accordion-list}}

View File

@ -190,4 +190,5 @@
{{top-errors errors=errors}}
{{top-errors errors=otherErrors}}
{{top-errors errors=clusterErrors}}
{{save-cancel editing=(eq mode 'edit') save="driverSave" cancel=close}}

View File

@ -22,6 +22,7 @@
{{top-errors errors=errors}}
{{top-errors errors=otherErrors}}
{{top-errors errors=clusterErrors}}
{{save-cancel
createLabel="clusterNew.googlegke.checkServiceAccount"
savingLabel="clusterNew.googlegke.checkingServiceAccount"
@ -138,5 +139,6 @@
{{top-errors errors=errors}}
{{top-errors errors=otherErrors}}
{{top-errors errors=clusterErrors}}
{{save-cancel editing=(eq mode 'edit') save="driverSave" cancel=close}}
{{/if}}

View File

@ -54,6 +54,13 @@ export default Component.extend(ClusterDriver, {
set(this, 'token', token);
set(this, 'loading', false);
}).catch((err) => {
if ( this.isDestroyed || this.isDestroying ) {
return;
}
get(this,'growl').fromError('Error getting command', err);
set(this, 'loading', false);
});
}
});

View File

@ -35,6 +35,7 @@
{{#if isEdit}}
{{top-errors errors=otherErrors}}
{{top-errors errors=clusterErrors}}
{{save-cancel save="driverSave" editing=true cancel="close"}}
{{else}}
<div class="footer-actions">
@ -43,5 +44,6 @@
{{/if}}
{{else}}
{{top-errors errors=otherErrors}}
{{top-errors errors=clusterErrors}}
{{save-cancel save="driverSave" editing=isEdit cancel="close"}}
{{/if}}

View File

@ -12,6 +12,7 @@ export default Component.extend(ClusterDriver, {
layout,
globalStore: service(),
settings: service(),
growl: service(),
intl: service(),
configField: 'rancherKubernetesEngineConfig',
@ -100,6 +101,9 @@ export default Component.extend(ClusterDriver, {
}),
});
set(this, 'cluster.rancherKubernetesEngineConfig', config);
}
if ( get(this, 'isNew') ) {
this.driverDidChange();
}
},
@ -181,6 +185,13 @@ export default Component.extend(ClusterDriver, {
}
set(this, 'token', token);
set(this, 'loading', false);
}).catch((err) => {
if ( this.isDestroyed || this.isDestroying ) {
return;
}
get(this,'growl').fromError('Error getting command', err);
set(this, 'loading', false);
});
},
@ -223,11 +234,11 @@ export default Component.extend(ClusterDriver, {
}),
isAddressValid: computed('address', function() {
return get(this, 'address.length') === 0 || validateEndpoint(get(this, 'address'));
return get(this, 'address') === undefined || get(this, 'address.length') === 0 || validateEndpoint(get(this, 'address'));
}),
isInternalAddressValid: computed('internalAddress', function() {
return get(this, 'internalAddress.length') === 0 || validateEndpoint(get(this, 'internalAddress'));
return get(this, 'internalAddress') === undefined || get(this, 'internalAddress.length') === 0 || validateEndpoint(get(this, 'internalAddress'));
}),
// Custom stuff
@ -271,6 +282,10 @@ export default Component.extend(ClusterDriver, {
command: computed(`settings.${C.SETTING.AGENT_IMAGE}`, 'labels', 'token.nodeCommand', 'etcd', 'controlplane', 'worker', 'address', 'internalAddress', function() {
let out = get(this, 'token.nodeCommand');
if ( !out ) {
return;
}
const address = get(this, 'address');
if(address) {
out += ` --address ${address}`;

View File

@ -160,7 +160,7 @@
<div class="copy-pre mt-20 mb-20">
{{#if loading}}
<div class="text-center"><i class="icon icon-spinner icon-spin"></i> {{t 'generic.loading'}}</div>
{{else}}
{{else if command}}
{{copy-to-clipboard clipboardText=command tagName="div" classNames="copy-to-pre"}}
<pre id="registration-command" style="font-size: 14px;">{{command}}</pre>
{{/if}}
@ -241,6 +241,7 @@
{{/if}}
{{top-errors errors=errors}}
{{top-errors errors=clusterErrors}}
{{top-errors errors=otherErrors}}
{{#if (or isEdit (eq step 1))}}
{{save-cancel createLabel=(if isCustom 'saveCancel.next' 'saveCancel.create') editing=isEdit save="driverSave" cancel="close"}}

View File

@ -1,5 +1,5 @@
import { next } from '@ember/runloop';
import { get } from '@ember/object';
import { set, get, observer } from '@ember/object';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import Util from 'ui/utils/util';
@ -18,6 +18,7 @@ export default Component.extend({
showProtip: true,
status: 'connecting',
containerName: null,
socket: null,
actions: {
@ -42,6 +43,13 @@ export default Component.extend({
},
},
init() {
this._super(...arguments);
const containerName = get(this, 'instance.containers.firstObject.name');
set(this, 'containerName', containerName);
},
didInsertElement: function () {
this._super();
next(this, () => {
@ -54,11 +62,11 @@ export default Component.extend({
},
exec: function () {
var instance = this.get('instance');
var instance = get(this, 'instance');
const clusterId = get(this, 'scope.currentCluster.id');
const namespaceId = get(instance, 'namespaceId');
const podName = get(instance, 'name');
const containerName = get(instance, 'containers.firstObject.name');
const containerName = get(this, 'containerName');
const scheme = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
let url = `${scheme}${window.location.host}/k8s/clusters/${clusterId}/api/v1/namespaces/${namespaceId}/pods/${podName}/log`;
url += `?container=${encodeURIComponent(containerName)}&tailLines=${LINES}&follow=true&timestamps=true`;
@ -68,20 +76,20 @@ export default Component.extend({
connect: function (url) {
var socket = new WebSocket(url, 'base64.binary.k8s.io');
this.set('socket', socket);
set(this, 'socket', socket);
var body = this.$('.log-body')[0];
var $body = $(body);
this.set('status', 'initializing');
set(this, 'status', 'initializing');
socket.onopen = () => {
this.set('status', 'connected');
set(this, 'status', 'connected');
};
socket.onmessage = (message) => {
let ansiup = new AnsiUp.default;
this.set('status', 'connected');
set(this, 'status', 'connected');
var isFollow = ($body.scrollTop() + $body.outerHeight() + 10) >= body.scrollHeight;
const data = decodeURIComponent(window.escape(AWS.util.base64.decode(message.data)));
@ -119,17 +127,17 @@ export default Component.extend({
return;
}
this.set('status', 'disconnected');
set(this, 'status', 'disconnected');
};
},
disconnect: function () {
this.set('status', 'closed');
set(this, 'status', 'closed');
var socket = this.get('socket');
var socket = get(this, 'socket');
if (socket) {
socket.close();
this.set('socket', null);
set(this, 'socket', null);
}
},
@ -138,6 +146,12 @@ export default Component.extend({
this._super();
},
containerDidChange: observer('containerName', function() {
this.disconnect();
this.send('clear');
this.exec();
}),
isDate(date) {
return new Date(date) !== "Invalid Date" && !isNaN(new Date(date))
}

View File

@ -1,5 +1,20 @@
<h2>
<i class="icon icon-file"></i> {{t 'containerLogs.title' instanceName=instance.displayName}}
<i class="icon icon-file"></i> {{t 'containerLogs.title'}}
{{#if displayName}}
{{displayName}}
{{else if (gt instance.containers.length 1)}}
<div class="container-select">
{{new-select
classNames="form-control"
optionValuePath="name"
optionLabelPath="name"
content=instance.containers
value=containerName
}}
</div>
{{else}}
{{containerName}}
{{/if}}
<div class="console-status text-muted pull-right">{{t (concat 'containerLogs.status.' status)}}</div>
</h2>
{{#if showProtip}}

View File

@ -70,6 +70,7 @@
model=model
originalCluster=originalCluster
otherErrors=memberErrors
clusterErrors=errors
save=(action 'save')
close=(action 'close')
registerHook=(action "registerHook")

View File

@ -86,6 +86,8 @@
{{radio-button selection=useStorageClass value=true disabled=(not canUseStorageClass)}}
{{t 'cruPersistentVolumeClaim.source.storageClass'}}
</label>
</div>
<div class="radio">
<label>
{{radio-button selection=useStorageClass value=false}}
{{t 'cruPersistentVolumeClaim.source.pv'}}

View File

@ -0,0 +1,26 @@
import Component from '@ember/component';
import layout from './template';
export default Component.extend({
layout,
tagName: '',
model: null,
expandAll: null,
expandFn: null,
sortBy: 'linkEndpoint',
descending: true,
headers: [
{
name: 'linkEndpoint',
sort: ['linkEndpoint'],
translationKey: 'appDetailPage.endpoints.endpoint',
searchField: 'linkEndpoint',
},
{
name: 'protocol',
sort: ['linkEndpoint', 'protocol'],
translationKey: 'appDetailPage.endpoints.protocol',
searchField: 'protocol',
}
],
});

View File

@ -0,0 +1,36 @@
{{#sortable-table
classNames="grid fixed mb-0 sortable-table"
bulkActions=false
rowActions=false
paging=false
search=true
sortBy=sortBy
stickyHeader=false
descending=descending
headers=headers
body=model
as |sortable kind endpoint|
}}
{{#if (eq kind "row")}}
<tr>
<td data-title="{{t 'appDetailPage.endpoints.endpoint'}}:" class="force-wrap">
{{#if endpoint.linkEndpoint}}
<a target="_blank" rel="noreferrer nofollow" href="{{endpoint.linkEndpoint}}">{{endpoint.linkEndpoint}}</a>
{{else}}
{{endpoint.linkEndpoint}}
{{/if}}
</td>
<td data-title="{{t 'appDetailPage.endpoints.protocol'}}:" class="force-wrap">
<div class="text-uppercase">{{endpoint.protocol}}</div>
</td>
</tr>
{{else if (eq kind "nomatch")}}
<tr>
<td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-20 pb-20">{{t 'appDetailPage.endpoints.noMatch'}}</td>
</tr>
{{else if (eq kind "norows")}}
<tr>
<td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-20 pb-20">{{t 'appDetailPage.endpoints.noData'}}</td>
</tr>
{{/if}}
{{/sortable-table}}

View File

@ -1,4 +1,4 @@
import { observer, get } from '@ember/object';
import { observer, get, set } from '@ember/object';
import { next } from '@ember/runloop';
import { alias, equal } from '@ember/object/computed';
import { inject as service } from '@ember/service';
@ -13,6 +13,7 @@ export default Component.extend({
layout,
intl: service(),
scope: service(),
clusterStore: service(),
createLabel: 'formNamespace.label.create',
reuseLabel: 'formNamespace.label.reuse',
@ -31,13 +32,14 @@ export default Component.extend({
hookName: 'saveNamespace',
classNames: ['inline-form'],
choices: alias('scope.currentProject.namespaces'),
choices: null,
init() {
this._super(...arguments);
let all = this.get('choices');
set(this, 'choices', get(this, 'clusterStore').all('namespace').filterBy('projectId', get(this, 'scope.currentProject.id')));
let all = get(this, 'choices');
this.set('createNamespace', this.get('clusterStore').createRecord({
set(this, 'createNamespace', get(this, 'clusterStore').createRecord({
type: 'namespace',
name: '',
projectId: get(this, 'scope.currentProject.id'),
@ -45,21 +47,21 @@ export default Component.extend({
// TODO
// Find a namespace
if ( this.get('mode') === REUSE ) {
if ( get(this, 'mode') === REUSE ) {
let namespace = get(this,'namespace') || // Passed in
all.findBy('isDefault', true) || // The default one
all.objectAt(0); // Ok any one
if ( namespace && namespace.id) {
this.set('reuseNamespaceId', get(namespace, 'id'));
set(this, 'reuseNamespaceId', get(namespace, 'id'));
} else if (namespace){
this.set('createNamespace', namespace);
this.set('mode', CREATE);
set(this, 'createNamespace', namespace);
set(this, 'mode', CREATE);
return;
} else {
next(() => {
this.set('mode', CREATE);
this.get('createNamespace.name', 'default')
set(this, 'mode', CREATE);
get(this, 'createNamespace.name', 'default')
});
}
@ -73,8 +75,8 @@ export default Component.extend({
actions: {
toggle() {
let mode = (this.get('mode') === REUSE ? CREATE : REUSE);
this.set('mode', mode);
let mode = (get(this, 'mode') === REUSE ? CREATE : REUSE);
set(this, 'mode', mode);
if ( mode === CREATE ) {
next(() => {
let elem = this.$('.new-name')[0];
@ -89,23 +91,23 @@ export default Component.extend({
updateNamespace: observer('reuseNamespaceId','mode', function() {
let namespace;
if ( this.get('mode') === REUSE ) {
namespace = this.get('choices').findBy('id', this.get('reuseNamespaceId'));
if ( get(this, 'mode') === REUSE ) {
namespace = get(this, 'choices').findBy('id', get(this, 'reuseNamespaceId'));
}
if ( !namespace ) {
namespace = this.get('createNamespace');
namespace = get(this, 'createNamespace');
}
this.set('namespace', namespace);
set(this, 'namespace', namespace);
}),
validate: observer('namespace.{id,name}', function() {
let intl = this.get('intl');
let intl = get(this, 'intl');
let errors = [];
let namespace = this.get('namespace');
if ( namespace && namespace.get('name') ) {
let namespace = get(this, 'namespace');
if ( namespace && get(namespace, 'name') ) {
namespace.validationErrors().forEach((err) => {
errors.push(intl.t('formNamespace.errors.validation', {error: err}))
});
@ -114,9 +116,9 @@ export default Component.extend({
}
if ( errors.length ) {
this.set('errors', errors);
set(this, 'errors', errors);
} else {
this.set('errors', null);
set(this, 'errors', null);
}
}),

View File

@ -10,7 +10,7 @@ export default Component.extend({
layout,
originQuestions: alias('selectedTemplate.questions'),
pasteOrUpload: false,
accept : '.yml, .yaml',
accept: '*',
showHeader: true,
answerGroups: null,
_boundChange : null,

View File

@ -1,7 +1,6 @@
<form class="{{if getTemplate.isRunning 'hide'}}">
<div class="btn-group pull-right">
<button class="btn btn-sm bg-primary" {{action 'showPaste'}}>{{t 'generic.paste'}} <span class="icon icon-copy"></span></button>
<button class="btn btn-sm bg-primary" {{action 'upload'}}>{{t 'generic.upload'}} <span class="icon icon-upload"></span></button>
<button class="btn btn-sm bg-primary" {{action 'upload'}}>{{t 'uploadFile.label'}} <span class="icon icon-upload"></span></button>
</div>
<div>
{{#if showHeader}}

View File

@ -10,7 +10,6 @@ import { compare as compareVersion } from 'ui/utils/parse-version';
import { task } from 'ember-concurrency';
import YAML from 'npm:yamljs';
import layout from './template';
import { isEmpty } from '@ember/utils';
export default Component.extend(NewOrEdit, {
@ -31,7 +30,7 @@ export default Component.extend(NewOrEdit, {
actuallySave: true,
showHeader: true,
showPreview: true,
showName: isEmpty('catalogApp.id'),
customizeNamespace: false,
titleAdd: 'newCatalog.titleAdd',
titleUpgrade: 'newCatalog.titleUpgrade',
selectVersionAdd: 'newCatalog.selectVersionAdd',
@ -48,6 +47,7 @@ export default Component.extend(NewOrEdit, {
decoded: false,
srcSet: false,
detailExpanded: false,
previewOpen: false,
previewTab: null,
questionsArray: null,
@ -59,6 +59,14 @@ export default Component.extend(NewOrEdit, {
noAppReadme: null,
actions: {
toogleDetailedDescriptions: function () {
set(this, 'detailExpanded', true);
},
toogleNamespace: function () {
set(this, 'customizeNamespace', true);
},
cancel: function() {
this.sendAction('cancel');
},
@ -84,6 +92,8 @@ export default Component.extend(NewOrEdit, {
this.decodeFiles();
}
debugger
scheduleOnce('afterRender', () => {
if ( get(this, 'selectedTemplateUrl') ) {
@ -283,6 +293,9 @@ export default Component.extend(NewOrEdit, {
(get(this, 'selectedTemplateModel.questions') || []).forEach((item) => {
out[item.variable] = item.answer;
(get(item, 'subquestions') || []).forEach((sub) => {
out[sub.variable] = sub.answer;
});
});
return out;

View File

@ -16,15 +16,23 @@
{{/if}}
</div>
<div class="col span-8">
{{#unless appReadmeContent}}
{{#unless noAppReadme}}
{{#if appReadmeContent}}
{{marked-down markdown=appReadmeContent}}
{{else if (not noAppReadme)}}
<div class="text-center">
<i class="icon icon-spinner icon-spin" style="font-size:36px;"></i>
</div>
{{/unless}}
{{else}}
{{marked-down markdown=appReadmeContent}}
{{/unless}}
{{else if noAppReadme}}
<h1 class="mb-10 text-capitalize">
{{templateResource.name}}
</h1>
<p>{{templateResource.description}}</p>
<div class="row">
<button class="btn btn-sm bg-transparent pl-0" {{action 'toogleDetailedDescriptions'}}>
{{t 'newCatalog.seeMore'}}
</button>
</div>
{{/if}}
</div>
</div>
</section>
@ -35,6 +43,7 @@
detail=(t 'newCatalog.appInfoDetail')
expandAll=al.expandAll
expand=(action expandFn)
expanded=detailExpanded
}}
<div class="row">
{{#if readmeContent}}
@ -65,7 +74,7 @@
model=catalogApp
nameRequired=true
descriptionShow=false
nameDisabled=showName
nameDisabled=editing
bothColClass="col span-12"
colClass="col span-12"
}}
@ -86,9 +95,18 @@
</div>
</div>
<div class="row">
{{#unless customizeNamespace}}
<div class="col span-12">
{{#if showName}}
{{#advanced-section}}
{{t 'newCatalog.customizeNamespace' namespaceId=primaryResource.name htmlSafe=true}}
{{#unless editing}}
<button class="btn btn-sm bg-transparent pl-0" {{action 'toogleNamespace'}}>
{{t 'generic.customize'}}
</button>
{{/unless}}
</div>
{{/unless}}
<div class="col span-12">
{{#if customizeNamespace}}
<hr class="mt-20 mb-20"/>
{{form-namespace
@ -96,7 +114,6 @@
mode='reuse'
errors=namespaceErrors
}}
{{/advanced-section}}
{{/if}}
</div>
</div>

View File

@ -136,6 +136,7 @@ export default Component.extend(HoverDropdown, {
}.observes(
'pageScope',
'clusterId',
'cluster.isReady',
'projectId',
'stacks.@each.group',
`prefs.${C.PREFS.ACCESS_WARNING}`,

View File

@ -25,8 +25,8 @@ export default Component.extend({
init() {
this._super(...arguments);
set(this,'projectSecrets', get(this,'store').all('secret'));
set(this,'namespaceSecrets', get(this,'store').all('namespacedSecret'));
set(this,'projectSecrets', get(this,'store').all('secret').filterBy('type','secret'));
set(this,'namespaceSecrets', get(this,'store').all('namespacedSecret').filterBy('type','namespacedSecret'));
let def = get(this,'value') || get(this,'field.default');
if ( def && !get(this,'selected') ) {

View File

@ -0,0 +1,50 @@
import { alias } from '@ember/object/computed';
import { get, set, observer} from '@ember/object';
import { next } from '@ember/runloop';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import layout from './template';
export default Component.extend({
layout,
allStorageClasses: service(),
field: null,
value: null,
selected: null,
disabled: false,
storageClassesOptions: alias('allStorageClasses.list'),
init() {
this._super(...arguments);
const def = get(this,'value') || get(this,'field.default');
if ( def && !get(this,'selected') ) {
const exact = get(this, 'storageClassesOptions').findBy('id', def);
next(() => {
if ( exact ) {
set(this, 'selected', get(exact, 'id') || null);
} else {
set(this, 'selected', null);
}
});
}
},
selectedChanged: observer('selected', function() {
let id = get(this,'selected');
let str = null;
if ( id ) {
const storageClass = get(this, 'storageClassesOptions').findBy('id', id);
if ( storageClass ) {
str = get(storageClass, 'id');
}
}
set(this,'value', str);
}),
});

View File

@ -0,0 +1,9 @@
{{searchable-select
content=storageClassesOptions
class="form-control"
value=selected
prompt=(t 'schema.inputStorageClass.prompt')
optionValuePath="id"
optionLabelPath="displayName"
placeholder=(t 'schema.inputStorageClass.prompt')
}}

View File

@ -574,6 +574,7 @@ C.SUPPORTED_SCHEMA_INPUTS= [
'multiline',
'password',
'service',
'storageclass',
'string',
'masked',
'secret',

View File

@ -104,8 +104,8 @@ export function absoluteUrl(url) {
}
export function validateEndpoint(str) {
//credit to https://stackoverflow.com/questions/9208814/validate-ipv4-ipv6-and-hostname
return /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/.test(str);
//credit to https://stackoverflow.com/questions/4460586/javascript-regular-expression-to-check-for-ip-addresses
return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(str);
}
export function addAuthorization(url, user, pass) {

View File

@ -0,0 +1 @@
export { default } from 'shared/all-storage-classes/service';

View File

@ -0,0 +1 @@
export { default } from 'shared/components/form-endpoints/component';

View File

@ -0,0 +1 @@
export { default } from 'shared/components/schema/input-storageclass/component';

View File

@ -22,6 +22,7 @@ generic:
containers: Containers
created: Created
createdDate: "Created {date}"
customize: Customize
default: Default
description: Description
details: Details
@ -39,6 +40,7 @@ generic:
entrypoint: Entrypoint
environment: Environment
expandAll: Expand All
from: from
gigabyte: 'GB'
generic: 'Generic'
hardware: Hardware
@ -178,6 +180,13 @@ accountsPage:
appDetailPage:
header: "App: {appName}"
endpoints:
title: Endpoints
detail: 'Public Endpoints of this application'
endpoint: Endpoint
protocol: Protocol
noData: No public endpoints were created for this application.
noMatch: No public endpoints match the current search.
notes:
title: Notes
detail: 'Instructions on how to use this application'
@ -1957,7 +1966,7 @@ confirmDelete:
largeDeleteText: '{key} and {othersCount} others'
containerLogs:
title: "Logs: {instanceName}"
title: "Logs: "
onlyCombined: "<b>Note:</b> Only combined stdout/stderr logs are available for this container because it was run with the TTY (-t) flag."
combined: Combined
stdout: Standard Out
@ -3934,6 +3943,9 @@ inputPassword:
inputTextFile:
tooltip: Read from a file
uploadFile:
label: Read from File
podsSection:
title: Pods
detail: Pods in this workload
@ -4277,12 +4289,14 @@ newCatalog:
maintainedBy: Maintained by community members
maintainer: "Maintainer:"
newNamespace: New Namespace
customizeNamespace: "This application will be deployed into the <code>{namespaceId}</code> namespace"
newAppDetail: Choose application version and namespace for the application
appInfo: Detailed Descriptions
appInfoDetail: Application information and user guid
noConfig: This template has no configuration options
official: Officially Certified
preview: Preview
seeMore: More information...
saveConfigure: Configure
saveNew: Launch
saveUpgrade: Upgrade
@ -5064,6 +5078,8 @@ schema:
custom: Custom
inputDnsRecord:
prompt: Choose a Service...
inputStorageClass:
prompt: Use the default class...
inputSecret:
prompt: Choose a Secret...
@ -5334,6 +5350,7 @@ action:
garbageCollect: Cleanup
logs: View Logs
makeDefault: Set as default
resetDefault: Reset default
nodeConfig: Download Keys
move: Move
pause: Pause Orchestration

View File

@ -1076,7 +1076,7 @@ confirmDelete:
cancelAction: Cancelar
largeDeleteText: '{key} y {othersCount} otros'
containerLogs:
title: 'Registros: {instanceName}'
title: 'Registros: '
onlyCombined: "<b>Nota:</b> Solo estarán disponibles los registros combinados de stdout y stderr para este contenedor porque fue lanzado son la instrucción TTY (-t)."
combined: Combinado
stdout: Standard Out

View File

@ -990,7 +990,7 @@ confirmDelete:
cancelAction: Annuler
largeDeleteText: '{key} et {othersCount} dautres'
containerLogs:
title: 'Journaux : {instanceName}'
title: 'Journaux : '
onlyCombined: "<b>Remarque :</b> Seuls les journaux combinés des sorties standard et d'erreurs est disponible pour ce conteneur parce quil a été exécuté avec l'option TTY (-t) ."
combined: Combiné
stdout: Sortie Standard

View File

@ -830,7 +830,7 @@ confirmDelete:
cancelAction: Mégsem
largeDeleteText: '{key} és {othersCount} mások'
containerLogs:
title: 'Naplók: {instanceName}'
title: 'Naplók: '
combined: Kombinált
stdout: Szabványos kimenet
stderr: Standard hiba

View File

@ -1086,7 +1086,7 @@ confirmDelete:
confirmAction: 削除
cancelAction: キャンセル
containerLogs:
title: 'ログ: {instanceName}'
title: 'ログ: '
onlyCombined: "<b>Note:</b> このコンテナでは TTY (-t) フラグが有効なため、標準出力/標準エラー出力が結合されたログのみが利用可能です。"
combined: 結合された出力
stdout: 標準出力

View File

@ -929,7 +929,7 @@ confirmDelete:
confirmAction: Удалить
cancelAction: Отмена
containerLogs:
title: 'Логи: {instanceName}'
title: 'Логи: '
combined: Комбинированный
stderr: Ошибки
protip: 'Подсказка: удерживайте клавишу {key}, чтобы открыть журнал событий в новом окне.'

View File

@ -1105,7 +1105,7 @@ confirmDelete:
confirmAction: Видалити
cancelAction: Відміна
containerLogs:
title: 'Логи: {instanceName}'
title: 'Логи: '
onlyCombined: "<b>Примітка:</b> Для цього контейнера доступні комбіновані stdout/stderr журнали, тому що він був запущений з TTY (-t)."
combined: Комбіновані
stdout: Стандартний вивід

View File

@ -1137,7 +1137,7 @@ confirmDelete:
cancelAction: 取消
largeDeleteText: '{key} 及 {othersCount} 其他'
containerLogs:
title: '日志: {instanceName}'
title: '日志: '
onlyCombined: "<b>注意:</b> 此容器运行时带有TTY(-t)参数,仅有合并的标准输出和标准错误日志可见"
combined: 合并日志
stdout: 标准输出