Skip to content

Commit b322ab5

Browse files
authored
Merge pull request eclipse-che#2465 from eclipse/CHE-2206
CHE-2206: add stacks list, edit and creation
2 parents 996581b + 0fe481b commit b322ab5

23 files changed

Lines changed: 1283 additions & 9 deletions

dashboard/src/app/index.module.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {NavbarConfig} from './navbar/navbar-config';
2828
import {ProjectsConfig} from './projects/projects-config';
2929
import {ProxySettingsConfig} from './proxy/proxy-settings.constant';
3030
import {WorkspacesConfig} from './workspaces/workspaces-config';
31+
import {StacksConfig} from './stacks/stacks-config';
3132

3233

3334
// init module
@@ -360,3 +361,4 @@ new NavbarConfig(instanceRegister);
360361
new ProjectsConfig(instanceRegister);
361362
new WorkspacesConfig(instanceRegister);
362363
new DashboardConfig(instanceRegister);
364+
new StacksConfig(instanceRegister);

dashboard/src/app/navbar/navbar.controller.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export class CheNavBarCtrl {
4242
administration: '#/administration',
4343
// subsections
4444
plugins: '#/admin/plugins',
45-
account: '#/account'
45+
account: '#/account',
46+
stacks: '#/stacks'
4647
};
4748

4849
// highlight navbar menu item

dashboard/src/app/navbar/navbar.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@
4141
</div>
4242
</md-button>
4343
</md-list-item>
44+
<md-list-item flex class="navbar-subsection-item">
45+
<md-button nav-bar-selected flex che-reload-href
46+
ng-href="{{navbarCtrl.menuItemUrl.stacks}}" layout-align="left">
47+
<div class="navbar-item" layout="row" layout-align="start center">
48+
<md-icon md-font-icon="navbar-icon material-design icon-ic_inbox_24px"></md-icon>
49+
<span>Stacks</span>
50+
</div>
51+
</md-button>
52+
</md-list-item>
4453
<md-list-item flex class="navbar-subsection-item">
4554
<md-button nav-bar-selected flex che-reload-href
4655
ng-href="{{navbarCtrl.menuItemUrl.administration}}" layout-align="left">
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
* Copyright (c) 2015-2016 Codenvy, S.A.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Codenvy, S.A. - initial API and implementation
10+
*/
11+
'use strict';
12+
13+
/**
14+
* @ngdoc controller
15+
* @name stacks.list.controller:ListStacksCtrl
16+
* @description This class is handling the controller for listing the stacks
17+
* @author Ann Shumilova
18+
*/
19+
export class ListStacksController {
20+
21+
/**
22+
* Default constructor that is using resource
23+
* @ngInject for Dependency injection
24+
*/
25+
constructor(cheStack, $log, $mdDialog, cheNotification, $rootScope, lodash, $q) {
26+
this.cheStack = cheStack;
27+
this.$log = $log;
28+
this.$mdDialog = $mdDialog;
29+
this.cheNotification = cheNotification;
30+
this.lodash = lodash;
31+
this.$q = $q;
32+
33+
$rootScope.showIDE = false;
34+
35+
this.stackFilter = {name: ''};
36+
this.stackOrderBy = 'stack.name';
37+
38+
this.stackSelectionState = {};
39+
this.isAllSelected = false;
40+
this.isNoSelected = true;
41+
42+
this.stacks = [];
43+
this.getStacks();
44+
}
45+
46+
/**
47+
* Gets the list of stacks.
48+
*/
49+
getStacks() {
50+
this.loading = true;
51+
this.updateSelectionState();
52+
53+
let promise = this.cheStack.fetchStacks();
54+
promise.then(() => {
55+
this.loading = false
56+
this.stacks = this.cheStack.getStacks();
57+
},
58+
(error) => {
59+
if (error.status === 304) {
60+
this.stacks = this.cheStack.getStacks();
61+
}
62+
this.state = 'error';
63+
this.loading = false;
64+
});
65+
}
66+
67+
/**
68+
* Performs confirmation and deletion of pointed stack.
69+
*
70+
* @param stack stack to delete
71+
*/
72+
deleteStack(stack) {
73+
let confirmationPromise = this.confirmStacksDeletion(1, stack.name);
74+
confirmationPromise.then(() => {
75+
this.loading = true;
76+
this.cheStack.deleteStack(stack.id).then(() => {
77+
delete this.stackSelectionState[stack.id];
78+
this.cheNotification.showInfo('Stack ' + stack.name + ' has been successfully removed.');
79+
this.getStacks();
80+
}, (error) => {
81+
this.loading = false;
82+
let message = 'Failed to delete ' + stack.name + 'stack.' + (error && error.message) ? error.message : "";
83+
this.cheNotification.showError(message);
84+
});
85+
});
86+
87+
}
88+
89+
/**
90+
* Make a copy of pointed stack with new name.
91+
*
92+
* @param stack stack to make copy of
93+
*/
94+
duplicateStack(stack) {
95+
let newStack = angular.copy(stack);
96+
delete newStack.links;
97+
delete newStack.creator;
98+
delete newStack.id;
99+
newStack.name = this.generateStackName(stack.name + ' - Copy');
100+
this.loading = true;
101+
this.cheStack.createStack(newStack).then(() => {
102+
this.getStacks();
103+
}, (error) => {
104+
this.loading = false;
105+
let message = 'Failed to create ' + newStack.name + 'stack.' + (error && error.message) ? error.message : "";
106+
this.cheNotification.showError(message);
107+
});
108+
}
109+
110+
/**
111+
* Generate new stack name - based on the provided one and existing.
112+
*
113+
* @param name
114+
* @returns {*}
115+
*/
116+
generateStackName(name) {
117+
if (this.stacks.size === 0) {
118+
return name;
119+
}
120+
let existingNames = this.lodash.pluck(this.stacks, 'name');
121+
122+
if (existingNames.indexOf(name) < 0) {
123+
return name;
124+
}
125+
126+
let generatedName = name;
127+
let counter = 1;
128+
while (existingNames.indexOf(generatedName) >= 0) {
129+
generatedName = name + counter++;
130+
}
131+
return generatedName;
132+
}
133+
134+
/**
135+
* Returns whether all stacks are selected.
136+
*
137+
* @returns {*} <code>true</code> if all stacks are selected
138+
*/
139+
isAllStacksSelected() {
140+
return this.isAllSelected;
141+
}
142+
143+
/**
144+
* Returns whether there are no selected stacks.
145+
*
146+
* @returns {*} <code>true</code> if no stacks are selected
147+
*/
148+
isNoStacksSelected() {
149+
return this.isNoSelected;
150+
}
151+
152+
/**
153+
* Make all stacks selected.
154+
*/
155+
selectAllStacks() {
156+
this.stacks.forEach((stack) => {
157+
this.stackSelectionState[stack.id] = true;
158+
});
159+
}
160+
161+
/**
162+
* Make all stacks deselected.
163+
*/
164+
deselectAllStacks() {
165+
this.stacks.forEach((stack) => {
166+
this.stackSelectionState[stack.id] = false;
167+
});
168+
}
169+
170+
/**
171+
* Change the state of the stacks selection,
172+
*/
173+
changeSelectionState() {
174+
if (this.isAllSelected) {
175+
this.deselectAllStacks();
176+
} else {
177+
this.selectAllStacks();
178+
}
179+
this.updateSelectionState();
180+
}
181+
182+
/**
183+
* Update stack selection state.
184+
*/
185+
updateSelectionState() {
186+
this.isNoSelected = true;
187+
this.isAllSelected = true;
188+
189+
Object.keys(this.stackSelectionState).forEach((key) => {
190+
if (this.stackSelectionState[key]) {
191+
this.isNoSelected = false;
192+
} else {
193+
this.isAllSelected = false;
194+
}
195+
});
196+
}
197+
198+
/**
199+
* Delete all selected stacks.
200+
*/
201+
deleteSelectedStacks() {
202+
let selectedStackIds = [];
203+
204+
205+
Object.keys(this.stackSelectionState).forEach((key) => {
206+
if (this.stackSelectionState[key]) {
207+
selectedStackIds.push(key);
208+
}
209+
});
210+
211+
if (!selectedStackIds.length) {
212+
this.cheNotification.showError('No selected stacks.');
213+
return;
214+
}
215+
216+
let confirmationPromise = this.confirmStacksDeletion(selectedStackIds.length);
217+
confirmationPromise.then(() => {
218+
let deleteStackPromises = [];
219+
220+
selectedStackIds.forEach((stackId) => {
221+
this.stackSelectionState[stackId] = false;
222+
deleteStackPromises.push(this.cheStack.deleteStack(stackId));
223+
});
224+
225+
this.$q.all(deleteStackPromises).then(() => {
226+
this.cheNotification.showInfo('Selected stacks have been successfully removed.');
227+
})
228+
.catch(() => {
229+
this.cheNotification.showError('Failed to delete selected stack(s).');
230+
})
231+
.finally(() => {
232+
this.getStacks();
233+
});
234+
});
235+
}
236+
237+
/**
238+
* Show confirm dialog for stacks deletion.
239+
*
240+
* @param numberToDelete number of stacks to be deleted
241+
* @param stackName name of stack to confirm (can be null)
242+
* @returns {*}
243+
*/
244+
confirmStacksDeletion(numberToDelete, stackName) {
245+
let confirmTitle = 'Would you like to delete ';
246+
if (numberToDelete > 1) {
247+
confirmTitle += 'these ' + numberToDelete + ' stacks?';
248+
} else {
249+
confirmTitle += stackName ? stackName + '?' : 'this selected stack?';
250+
}
251+
let confirm = this.$mdDialog.confirm()
252+
.title(confirmTitle)
253+
.ariaLabel('Remove stacks')
254+
.ok('Delete!')
255+
.cancel('Cancel')
256+
.clickOutsideToClose(true);
257+
258+
return this.$mdDialog.show(confirm);
259+
}
260+
261+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<!--
2+
3+
Copyright (c) 2015 Codenvy, S.A.
4+
All rights reserved. This program and the accompanying materials
5+
are made available under the terms of the Eclipse Public License v1.0
6+
which accompanies this distribution, and is available at
7+
http://www.eclipse.org/legal/epl-v10.html
8+
9+
Contributors:
10+
Codenvy, S.A. - initial API and implementation
11+
12+
-->
13+
<che-toolbar che-title="Stacks" border-none></che-toolbar>
14+
<che-description che-link-title="Learn more." che-link="https://eclipse-che.readme.io/docs/stack">
15+
A stack is environment configuration for workspace defined by its runtime recipe. Create workspaces from stacks that define projects, runtimes
16+
and commands.
17+
</che-description>
18+
<md-content md-scroll-y flex layout="column" md-theme="maincontent-theme">
19+
<md-progress-linear md-mode="indeterminate" class="stacks-list-progress"
20+
ng-show="listStacksController.isLoading"></md-progress-linear>
21+
<md-content flex class="stacks-list-content" ng-hide="listStacksController.isLoading">
22+
<che-list-header che-input-placeholder="Search"
23+
che-search-model="listStacksController.stackFilter.name"
24+
che-hide-search="listStacksController.stacks.length === 0"
25+
che-add-button-title="Add Stack"
26+
che-add-button-href="#stack/create"
27+
che-delete-button-title="Delete"
28+
che-on-delete="listStacksController.deleteSelectedStacks()"
29+
che-hide-delete="listStacksController.isNoSelected"
30+
che-hide-header="(listStacksController.stacks | filter:listStacksController.stackFilter).length === 0">
31+
<div flex="100"
32+
layout="row"
33+
layout-align="start stretch"
34+
class="che-list-item-row">
35+
<div layout="column" layout-gt-xs="row"
36+
layout-align="start center"
37+
class="che-checkbox-area">
38+
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
39+
<md-checkbox class="che-list-item-checkbox"
40+
aria-label="Stack list"
41+
ng-checked="listStacksController.isAllChecked"
42+
ng-click="listStacksController.changeSelectionState()"></md-checkbox>
43+
</div>
44+
</div>
45+
<div flex hide-xs layout-gt-xs="row"
46+
layout-align="start center"
47+
class="che-list-item-details">
48+
<che-list-header-column flex-gt-xs="20"
49+
che-sort-value='listStacksController.stackOrderBy'
50+
che-sort-item='name'
51+
che-column-title='Name'></che-list-header-column>
52+
<che-list-header-column flex-gt-xs="35"
53+
che-column-title='Description'></che-list-header-column>
54+
<che-list-header-column flex-gt-xs="30"
55+
che-column-title='Components'></che-list-header-column>
56+
<che-list-header-column flex-gt-xs="18"
57+
che-column-title='Actions'></che-list-header-column>
58+
</div>
59+
</div>
60+
</che-list-header>
61+
<che-list ng-show="(listStacksController.stacks | filter:listStacksController.stackFilter).length > 0" class="stack-list">
62+
<stack-item
63+
ng-repeat="stack in listStacksController.stacks | orderBy:[listStacksController.stackOrderBy, 'name'] | filter:listStacksController.stackFilter"
64+
ng-model="listStacksController.stackSelectionState[stack.id]"
65+
che-selectable="true"
66+
che-on-checkbox-click="listStacksController.updateSelectionState()"
67+
che-on-delete="listStacksController.deleteStack(stack)"
68+
che-on-duplicate="listStacksController.duplicateStack(stack)"
69+
che-stack="stack"></stack-item>
70+
</che-list>
71+
<div class="che-list-empty">
72+
<span ng-show="listStacksController.stacks.length > 0 && (listStacksController.stacks | filter:listStacksController.stackFilter).length === 0">
73+
No stacks found.
74+
</span>
75+
<span ng-show="listStacksController.stacks.length === 0">There are no stacks yet</span>
76+
</div>
77+
</md-content>
78+
</md-content>
79+

0 commit comments

Comments
 (0)