Cluster table (#1336)

* Reload projects to get cluster default/system

* Export is an important word

* Cluster table
This commit is contained in:
Vincent Fiduccia 2017-08-31 17:31:59 -07:00 committed by GitHub
parent 82af52e71d
commit 96b8fab478
33 changed files with 308 additions and 168 deletions

View File

@ -8,7 +8,7 @@ export default Ember.Controller.extend({
{
translationKey: 'generic.uuid',
name: 'uuid',
sort: ['stateSort','address','uuid'],
sort: ['sortState','address','uuid'],
},
{
translationKey: 'haPage.table.address',

View File

@ -1,5 +1,52 @@
import Ember from 'ember';
import { headersWithCluster } from 'ui/components/cluster-box/component';
const headers = [
{
name: 'expand',
sort: false,
searchField: null,
width: 30
},
{
name: 'state',
sort: ['stateSort','name','id'],
translationKey: 'generic.state',
width: 125,
},
{
name: 'name',
sort: ['displayName','id'],
translationKey: 'clustersPage.cluster.label',
},
{
name: 'hosts',
sort: ['numHosts','name','id'],
translationKey: 'clustersPage.hosts.label',
width: 100,
classNames: 'text-center',
},
{
name: 'cpu',
sort: ['numGhz','name','id'],
translationKey: 'clustersPage.cpu.label',
width: 100,
classNames: 'text-center',
},
{
name: 'memory',
sort: ['numMem','name','id'],
translationKey: 'clustersPage.memory.label',
width: 100,
classNames: 'text-center',
},
{
name: 'storage',
sort: ['numStorage','name','id'],
translationKey: 'clustersPage.storage.label',
width: 100,
classNames: 'text-center',
},
];
export default Ember.Controller.extend({
queryParams: ['mode'],
@ -11,10 +58,24 @@ export default Ember.Controller.extend({
settings: Ember.inject.service(),
application: Ember.inject.controller(),
headers: headersWithCluster,
sortBy: 'cluster',
headers: headers,
sortBy: 'name',
searchText: null,
bulkActions: true,
sortClusters: ['name','id'],
arrangedClusters: Ember.computed.sort('model.clusters','sortClusters'),
init() {
this._super(...arguments);
this.set('expandedClusters',[]);
},
actions: {
toggleExpand(id) {
let list = this.get('expandedClusters');
if ( list.includes(id) ) {
list.removeObject(id);
} else {
list.addObject(id);
}
},
},
});

View File

@ -0,0 +1,18 @@
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
toggleGrouping() {
let choices = ['list','grouped'];
let cur = this.get('controller.mode');
let neu = choices[((choices.indexOf(cur)+1) % choices.length)];
Ember.run.next(() => {
this.set('controller.mode', neu);
});
},
},
shortcuts: {
'g': 'toggleGrouping',
}
});

View File

@ -15,6 +15,11 @@
<span class="darken"><i class="icon icon-cluster"></i></span>
<span>{{t 'clustersPage.newCluster'}}</span>
{{/link-to}}
{{#link-to "authenticated.clusters.new-project" class="btn bg-primary btn-sm icon-btn"}}
<span class="darken"><i class="icon icon-folder"></i></span>
<span>{{t 'clustersPage.newProject'}}</span>
{{/link-to}}
</div>
</section>
@ -30,25 +35,25 @@
{{/unless}}
{{#if (eq mode "grouped")}}
{{#each arrangedClusters as |cluster|}}
{{cluster-box model=cluster}}
{{/each}}
{{else}}
{{#sortable-table
classNames="grid sortable-table"
body=model.projects
body=model.clusters
searchText=searchText
sortBy=sortBy
bulkActions=true
bulkActions=false
fullRows=true
pagingLabel="pagination.project"
subRows=true
pagingLabel="pagination.cluster"
headers=headers as |sortable kind inst dt|
}}
{{project-row
{{cluster-row
model=inst
showCluster=true
fullColspan=sortable.fullColspan
toggle=(action "toggleExpand" inst.id)
expanded=(array-includes expandedClusters inst.id)
dt=dt
}}
{{/sortable-table}}
{{else}}
{{project-table model=model.projects showCluster=true}}
{{/if}}

View File

@ -13,7 +13,6 @@ export default Ember.Route.extend({
}).then((hash) => {
var project = userStore.createRecord({
type: 'project',
clusterId: params.clusterId,
name: '',
description: '',
});

View File

@ -5,15 +5,6 @@ export default Ember.Controller.extend({
access: Ember.inject.service(),
'tab-session': Ember.inject.service(),
sortBy: 'name',
sorts: {
state: ['stateSort','name','id'],
name: ['name','id'],
description: ['description','name','id'],
publicValue: ['publicValue','id'],
created: ['created','name','id'],
},
application: Ember.inject.controller(),
cookies: Ember.inject.service(),
projects: Ember.inject.service(),
@ -23,10 +14,11 @@ export default Ember.Controller.extend({
modalService: Ember.inject.service('modal'),
bulkActionHandler: Ember.inject.service(),
sortBy: 'name',
headers: [
{
name: 'state',
sort: ['stateSort','name','id'],
sort: ['sortState','name','id'],
translationKey: 'apiPage.table.state',
width: 125,
},

View File

@ -8,7 +8,7 @@ export default Ember.Controller.extend({
{
translationKey: 'generic.state',
name: 'state',
sort: ['stateSort','name','id'],
sort: ['sortState','name','id'],
width: '121'
},
{

View File

@ -5,7 +5,7 @@ export default Ember.Controller.extend({
headers: [
{
name: 'state',
sort: ['stateSort','name','id'],
sort: ['sortState','name','id'],
translationKey: 'certificatesPage.index.table.header.state',
width: 125,
},

View File

@ -1,105 +0,0 @@
<section class="clearfix pt-20">
<h4 class="pull-left m-0 pr-10 pt-5">{{t 'generic.cluster'}}: <a href="{{href-to "authenticated.clusters.cluster.index" model.id}}">{{model.displayName}}</a></h4>
{{#unless (eq model.state 'active')}}
{{badge-state model=model}}
{{/unless}}
<div class="pull-right">
{{#if model.registrationToken.hostCommand}}
{{#if model.registrationToken.clusterCommand}}
{{#link-to "authenticated.clusters.cluster.import" model.id class="btn btn-sm bg-transparent icon-btn ml-20"}}
<i class="icon icon-download"></i> {{t 'clusterBox.importCluster'}}
{{/link-to}}
{{/if}}
{{#link-to "authenticated.clusters.cluster.host-templates" model.id (query-params backTo="clusters") class="btn btn-sm bg-primary icon-btn ml-20"}}
<span class="darken"><i class="icon icon-host"></i></span>
<span>{{t 'clusterBox.addHost'}}</span>
{{/link-to}}
{{/if}}
{{#if model.registrationToken.clusterCommand}}
{{#link-to "authenticated.clusters.cluster.import" model.id class="btn btn-sm bg-primary icon-btn ml-20"}}
<span class="darken"><i class="icon icon-download"></i></span>
<span>{{t 'clusterBox.importCluster'}}</span>
{{/link-to}}
{{/if}}
{{action-menu model=model showPrimary=false classNames="ml-10 inline-block" size="sm"}}
</div>
</section>
<hr/>
{{#if model.showTransitioningMessage}}
<div class="{{model.stateColor}}">{{model.transitioningMessage}}</div>
{{/if}}
{{#if (eq model.state 'active')}}
<div class="row">
<div class="col span-3">
<div class="banner bg-info">
<div style="width: 90px" class="banner-icon"><span class="icon icon-host"></span> Hosts</div>
<div class="banner-message">
<p>{{model.numHosts}} Total</p>
</div>
</div>
</div>
<div class="col span-3">
<div class="banner bg-info">
<div style="width: 90px" class="banner-icon"><span class="icon icon-cpu"></span> CPU</div>
<div class="banner-message">
<p>{{model.numGhz}} Total GHz</p>
</div>
</div>
</div>
<div class="col span-3">
<div class="banner bg-info">
<div style="width: 90px" class="banner-icon"><span class="icon icon-memory"></span> Memory</div>
<div class="banner-message">
<p>{{model.numMem}} Total GB</p>
</div>
</div>
</div>
<div class="col span-3">
<div class="banner bg-info">
<div style="width: 90px" class="banner-icon"><span class="icon icon-hdd"></span> Storage</div>
<div class="banner-message">
<p>{{model.numStorage}} Total GB</p>
</div>
</div>
</div>
</div>
{{/if}}
<div class="p-20">
{{#sortable-table
tableClassNames="bordered"
bulkActions=true
rightActions=true
paging=true
pagingLabel="pagination.project"
search=true
sortBy=sortBy
descending=descending
headers=headers
body=model.projects
fullRows=true
stickyHeader=false
as |sortable kind p dt|
}}
{{#if (eq kind "row")}}
{{project-row model=p dt=dt}}
{{else if (eq kind "nomatch")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'clusterBox.noMatch'}}</td></tr>
{{else if (eq kind "norows")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'clusterBox.noData'}}</td></tr>
{{else if (eq kind "right-actions")}}
{{#link-to "authenticated.clusters.new-project" (query-params clusterId=model.id) class="btn btn-sm bg-link icon-btn"}}
<span class="darken"><i class="icon icon-folder"></i></span>
<span>{{t 'clustersPage.newProject'}}</span>
{{/link-to}}
{{/if}}
{{/sortable-table}}
</div>

View File

@ -0,0 +1,14 @@
import Ember from 'ember';
export default Ember.Component.extend({
model: null,
tagName: '',
expanded: null,
actions: {
toggle() {
this.sendAction('toggle');
},
},
});

View File

@ -0,0 +1,66 @@
<tr class="main-row">
<td>
<i role="button" {{action "toggle"}} class="icon icon-play eased text-small text-muted {{if expanded 'icon-rotate-90'}}"><span class="visually-hidden">Open accordion</span></i>
</td>
<td data-title="{{dt.state}}" class="state">
{{badge-state model=model}}
</td>
<td data-title="{{dt.name}}">
<a href="{{href-to 'authenticated.clusters.cluster' model.id}}">{{model.displayName}}</a>
</td>
{{#if (eq model.state "inactive")}}
<td colspan="4" class="text-center">
{{#if (or model.registrationToken.hostCommand model.registrationToken.clusterCommand)}}
{{#if model.registrationToken.hostCommand}}
{{#link-to "authenticated.clusters.cluster.host-templates" model.id (query-params backTo="clusters") class="btn btn-sm bg-primary icon-btn ml-20"}}
<span class="darken"><i class="icon icon-host"></i></span>
<span>{{t 'clusterRow.addHost'}}</span>
{{/link-to}}
{{/if}}
{{#if model.registrationToken.clusterCommand}}
{{#link-to "authenticated.clusters.cluster.import" model.id class="btn btn-sm bg-primary icon-btn ml-20"}}
<span class="darken"><i class="icon icon-download"></i></span>
<span>{{t 'clusterRow.importCluster'}}</span>
{{/link-to}}
{{/if}}
{{else}}
{{t 'clusterRow.noHosts'}}
{{/if}}
</td>
{{else}}
<td data-title="{{dt.hosts}}" class="text-center">
{{model.numHosts}}
</td>
<td data-title="{{dt.cpu}}" class="text-center">
{{model.numGhz}} GHz
</td>
<td data-title="{{dt.memory}}" class="text-center">
{{model.numMem}} GiB
</td>
<td data-title="{{dt.storage}}" class="text-center">
{{model.numStorage}} GiB
</td>
{{/if}}
<td data-title="{{dt.actions}} "class="actions">
{{action-menu model=model}}
</td>
</tr>
<tr class="sub-row {{unless expanded 'hide'}}">
<td>{{! expand}}</td>
<td colspan="{{sub fullColspan 2}}">
{{#if expanded}}
{{project-table
model=model.projects
bulkActions=false
search=false
}}
{{/if}}
</td>
</tr>
{{#if expanded}}
<tr class="separator-row">
<td colspan="{{fullColspan}}"></td>
</tr>
{{/if}}

View File

@ -3,7 +3,7 @@ import Ember from 'ember';
export const headersAll = [
{
name: 'state',
sort: ['stateSort','sortName','id'],
sort: ['sortState','sortName','id'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 150,

View File

@ -56,7 +56,14 @@
</td>
<td>&nbsp;</td>
<td class="pt-5 pb-5" data-title="{{t 'catalogSettings.more.kind.label'}}:">
{{t 'generic.environment'}}
{{new-select
classNames="form-control"
content=kindChoices
optionLabelPath="translationKey"
optionValuePath="value"
localizedLabel=true
value=row.kind
}}
</td>
<td>&nbsp;</td>
<td class="pt-5 pb-5" data-title="{{t 'catalogSettings.more.branch.label'}}:">

View File

@ -2,11 +2,20 @@ import Ember from 'ember';
import NewOrEdit from 'ui/mixins/new-or-edit';
export default Ember.Component.extend(NewOrEdit, {
projects: Ember.inject.service(),
editing: true,
primaryResource: Ember.computed.alias('cluster'),
cluster: null,
didInsertElement() {
let el = this.$('INPUT')[0];
if ( el ) {
el.focus();
}
},
actions: {
done() {
this.sendAction('done');
@ -25,7 +34,13 @@ export default Ember.Component.extend(NewOrEdit, {
},
},
doneSaving: function() {
didSave() {
return this.get('primaryResource').waitForTransition().then(() => {
return this.get('projects').refreshAll();
});
},
doneSaving() {
this.send('cancel');
}
});

View File

@ -1,6 +1,8 @@
<td valign="middle" class="row-check" style="padding-top: 2px;">
{{check-box nodeId=model.id}}
</td>
{{#if bulkActions}}
<td valign="middle" class="row-check" style="padding-top: 2px;">
{{check-box nodeId=model.id}}
</td>
{{/if}}
<td data-title="{{dt.state}}" class="state">
{{badge-state model=model}}
</td>

View File

@ -1,9 +1,9 @@
import Ember from 'ember';
export const headersWithCluster = [
const headersWithCluster = [
{
name: 'state',
sort: ['stateSort','name','id'],
sort: ['sortState','name','id'],
translationKey: 'generic.state',
width: 125,
},
@ -11,11 +11,13 @@ export const headersWithCluster = [
name: 'cluster',
sort: ['cluster.displayName','displayName','id'],
translationKey: 'clustersPage.cluster.label',
searchField: ['cluster.displayName'],
},
{
name: 'name',
sort: ['displayName','id'],
translationKey: 'clustersPage.environment.label',
searchField: ['displayName'],
},
{
name: 'stacks',
@ -41,17 +43,25 @@ export const headersWithCluster = [
{
name: 'default',
sort: false,
translationKey: 'clusterBox.loginDefault',
translationKey: 'clusterRow.loginDefault',
width: 60,
classNames: 'text-center',
},
];
export const headersWithoutCluster = headersWithCluster.filter(x => x.name !== 'cluster');
const headersWithoutCluster = headersWithCluster.filter(x => x.name !== 'cluster');
export default Ember.Component.extend({
classNames: ['box','mt-20','pt-0'],
tagName: '',
showCluster: false,
bulkActions: true,
search: true,
sortBy: 'name',
headers: headersWithoutCluster,
headers: Ember.computed('showCluster', function() {
if ( this.get('showCluster') ) {
return headersWithCluster;
} else {
return headersWithoutCluster;
}
}),
});

View File

@ -0,0 +1,22 @@
{{#sortable-table
tableClassNames="bordered"
bulkActions=bulkActions
paging=true
pagingLabel="pagination.project"
search=search
sortBy=sortBy
descending=descending
headers=headers
body=model
fullRows=true
stickyHeader=false
as |sortable kind p dt|
}}
{{#if (eq kind "row")}}
{{project-row model=p dt=dt showCluster=showCluster search=search bulkActions=bulkActions}}
{{else if (eq kind "nomatch")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'clusterRow.noMatch'}}</td></tr>
{{else if (eq kind "norows")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'clusterRow.noData'}}</td></tr>
{{/if}}
{{/sortable-table}}

View File

@ -41,7 +41,14 @@ export default Ember.Component.extend(NewOrEdit, {
width: '40',
},
],
stacks: null,
clusters: null,
init() {
this._super(...arguments);
this.set('clusters', this.get('userStore').all('cluster'));
},
actions: {
cancel() {

View File

@ -24,6 +24,19 @@
descriptionPlaceholder="viewEditDescription.form.description.placeholder"
}}
</section>
<section class="pt-10">
<label class="pb-5 acc-label">{{t 'viewEditProject.showEdit.cluster.label'}}</label>
{{new-select
classNames="form-control"
optionValuePath="id"
optionLabelPath="displayName"
content=clusters
value=project.clusterId
prompt="viewEditProject.showEdit.cluster.prompt"
localizedPrompt=true
}}
</section>
{{else if project.description}}
<section class="pt-10">
<label class="inline-block m-0">{{t 'viewEditProject.description'}}: </label>

View File

@ -42,7 +42,7 @@ export default Ember.Controller.extend({
storageHeaders: [
{
name: 'state',
sort: ['stateSort','displayUri','id'],
sort: ['sortState','displayUri','id'],
translationKey: 'hostsPage.hostPage.storageTab.table.header.state',
width: 125,
},

View File

@ -10,7 +10,7 @@ export const headers = [
},
{
name: 'state',
sort: ['stateSort','displayName'],
sort: ['sortState','displayName'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 120

View File

@ -30,7 +30,7 @@ export default Ember.Controller.extend({
},
{
name: 'state',
sort: ['stack.isDefault:desc','stack.displayName','stateSort','displayName'],
sort: ['stack.isDefault:desc','stack.displayName','sortState','displayName'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 120

View File

@ -16,7 +16,7 @@ export default Ember.Controller.extend(ContainerSparkStats, {
storageHeaders: [
{
name: 'state',
sort: ['stateSort','displayUri','id'],
sort: ['sortState','displayUri','id'],
translationKey: 'hostsPage.hostPage.storageTab.table.header.state',
width: 125,
},

View File

@ -44,7 +44,7 @@ export default Ember.Controller.extend({
},
{
name: 'state',
sort: ['stateSort','displayName'],
sort: ['sortState','displayName'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 120

View File

@ -112,6 +112,10 @@ export default Ember.Mixin.create({
return this.get('name') || '('+this.get('id')+')';
}.property('name','id'),
sortName: function() {
return Util.sortableNumericSuffix(this.get('displayName'));
}.property('displayName'),
isTransitioning: Ember.computed.equal('transitioning','yes'),
isError: Ember.computed.equal('transitioning','error'),
isRemoved: Ember.computed('state', () => { return !C.REMOVEDISH_STATES.includes(this.state); }),
@ -214,7 +218,7 @@ export default Ember.Mixin.create({
return this.constructor.defaultStateColor;
}.property('relevantState','isError'),
stateSort: function() {
sortState: function() {
var color = this.get('stateColor').replace('text-','');
return (stateColorSortMap[color] || stateColorSortMap['other']) + ' ' + this.get('relevantState');
}.property('stateColor','relevantState'),
@ -522,10 +526,10 @@ export default Ember.Mixin.create({
}, 'Wait for state='+state);
},
waitForNotTransitioning: function() {
waitForTransition: function() {
return this._waitForTestFn(function() {
return this.get('transitioning') !== 'yes';
}, 'Wait for not transitioning');
}, 'Wait for transition');
},
waitForAction: function(name) {

View File

@ -16,7 +16,7 @@ export default Ember.Mixin.create({
let good = 0;
let notGood = 0;
this.get(inputKey).sortBy('stateSort').forEach((inst) => {
this.get(inputKey).sortBy('sortState').forEach((inst) => {
let color = inst.get('stateBackground');
if ( color === 'bg-muted' ) {
color = 'bg-success';

View File

@ -206,10 +206,6 @@ var Container = Instance.extend(EndpointPorts, {
return (this.get('labels')||{})[C.LABEL.SCHED_GLOBAL] + '' === 'true';
}.property('labels'),
sortName: function() {
return Util.sortableNumericSuffix(this.get('displayName'));
}.property('displayName'),
isSidekick: function() {
return (this.get('labels')||{})[C.LABEL.LAUNCH_CONFIG] + '' !== C.LABEL.LAUNCH_CONFIG_PRIMARY;
}.property('labels'),

View File

@ -4,7 +4,7 @@ import PolledResource from 'ui/mixins/cattle-polled-resource';
import C from 'ui/utils/constants';
import { parseExternalId } from 'ui/utils/parse-externalid';
const builtInUi = ['amazonec2','azure','digitalocean','exoscale','packet','rackspace','vmwarevsphere','aliyunecs'];
export const builtInUi = ['amazonec2','azure','digitalocean','exoscale','packet','rackspace','vmwarevsphere','aliyunecs'];
function displayUrl(url) {
url = url||'';

View File

@ -5,7 +5,7 @@ export default Ember.Controller.extend({
headers: [
{
name: 'state',
sort: ['stateSort','displayAddress','id'],
sort: ['sortState','displayAddress','id'],
translationKey: 'registriesPage.index.table.header.state',
width: 125,
},

View File

@ -8,8 +8,8 @@ export default Ember.Controller.extend({
headers: [
{
name: 'stateSort',
sort: ['stateSort','name','id'],
name: 'state',
sort: ['sortState','name','id'],
type: 'string',
searchField: 'displayState',
translationKey: 'generic.state',

View File

@ -45,7 +45,7 @@ export default Ember.Controller.extend({
},
{
name: 'state',
sort: ['stack.isDefault:desc','stack.displayName','stateSort','displayName'],
sort: ['stack.isDefault:desc','stack.displayName','sortState','displayName'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 120
@ -79,7 +79,7 @@ export default Ember.Controller.extend({
},
{
name: 'state',
sort: ['stateSort','displayName'],
sort: ['sortState','displayName'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 120
@ -115,7 +115,7 @@ export default Ember.Controller.extend({
},
{
name: 'state',
sort: ['stateSort','displayName'],
sort: ['sortState','displayName'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 120

View File

@ -30,7 +30,7 @@ export default Ember.Controller.extend({
},
{
name: 'state',
sort: ['stateSort','displayName'],
sort: ['sortState','displayName'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 120

View File

@ -471,6 +471,16 @@ clustersPage:
label: Cluster Name
environment:
label: Environment Name
projects:
label: Environments
hosts:
label: Hosts
cpu:
label: CPU
memory:
label: RAM
storage:
label: Disk
containerPage:
header: 'Container: {name}'
@ -1299,9 +1309,10 @@ catalogSettings:
label: Branch
placeholder: 'e.g. master'
clusterBox:
clusterRow:
noMatch: No environments match the current search
noData: This cluster doesn't have any environments yet.
noHosts: External cluster has no hosts
addHost: Add Hosts
importCluster: Use existing Kubernetes
loginDefault: Login
@ -3348,6 +3359,9 @@ viewEditProject:
edit: "Edit Environment:"
add: Add Environment
template: Environment Template
cluster:
label: Cluster
prompt: Select a Cluster...
accessControl: Access Control
noMembers: Add one or more members who can use this environment.
networkPolicy: