Skip to content

Commit bfe4d86

Browse files
authored
✨Frontend: Personalized resource limits (#4415)
1 parent b9d9903 commit bfe4d86

File tree

10 files changed

+323
-91
lines changed

10 files changed

+323
-91
lines changed

services/static-webserver/client/source/class/osparc/component/node/BootOptionsView.js

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,14 @@
1616
************************************************************************ */
1717

1818
qx.Class.define("osparc.component.node.BootOptionsView", {
19-
extend: qx.ui.core.Widget,
20-
21-
construct: function(node) {
22-
this.base();
23-
24-
this._setLayout(new qx.ui.layout.VBox(10));
25-
26-
if (node) {
27-
this.setNode(node);
28-
}
29-
},
19+
extend: osparc.component.node.ServiceOptionsView,
3020

3121
events: {
3222
"bootModeChanged": "qx.event.type.Event"
3323
},
3424

35-
properties: {
36-
node: {
37-
check: "osparc.data.model.Node",
38-
init: null,
39-
nullable: false,
40-
apply: "__applyNode"
41-
}
42-
},
43-
4425
members: {
45-
__applyNode: function(node) {
26+
_applyNode: function(node) {
4627
if (node.hasBootModes()) {
4728
this.__populateLayout();
4829
}
@@ -65,17 +46,6 @@ qx.Class.define("osparc.component.node.BootOptionsView", {
6546

6647
const buttonsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10));
6748

68-
const stopButton = new qx.ui.form.Button().set({
69-
label: this.tr("Stop"),
70-
icon: "@FontAwesome5Solid/stop/14",
71-
enabled: false
72-
});
73-
node.getStatus().bind("interactive", stopButton, "enabled", {
74-
converter: state => state === "ready"
75-
});
76-
node.attachExecuteHandlerToStopButton(stopButton);
77-
buttonsLayout.add(stopButton);
78-
7949
const nodeMetaData = node.getMetaData();
8050
const workbenchData = node.getWorkbench().serialize();
8151
const nodeId = node.getNodeId();

services/static-webserver/client/source/class/osparc/component/node/LifeCycleView.js

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,14 @@
1616
************************************************************************ */
1717

1818
qx.Class.define("osparc.component.node.LifeCycleView", {
19-
extend: qx.ui.core.Widget,
20-
21-
construct: function(node) {
22-
this.base();
23-
24-
this._setLayout(new qx.ui.layout.VBox(10));
25-
26-
if (node) {
27-
this.setNode(node);
28-
}
29-
},
19+
extend: osparc.component.node.ServiceOptionsView,
3020

3121
events: {
3222
"versionChanged": "qx.event.type.Event"
3323
},
3424

35-
properties: {
36-
node: {
37-
check: "osparc.data.model.Node",
38-
init: null,
39-
nullable: false,
40-
apply: "__applyNode"
41-
}
42-
},
43-
4425
members: {
45-
__applyNode: function(node) {
26+
_applyNode: function(node) {
4627
if (node.isUpdatable() || node.isDeprecated() || node.isRetired()) {
4728
this.__populateLayout();
4829
}
@@ -120,17 +101,6 @@ qx.Class.define("osparc.component.node.LifeCycleView", {
120101

121102
const buttonsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
122103

123-
const stopButton = new qx.ui.form.Button().set({
124-
label: this.tr("Stop"),
125-
icon: "@FontAwesome5Solid/stop/14",
126-
enabled: false
127-
});
128-
node.getStatus().bind("interactive", stopButton, "enabled", {
129-
converter: state => state === "ready"
130-
});
131-
node.attachExecuteHandlerToStopButton(stopButton);
132-
buttonsLayout.add(stopButton);
133-
134104
const updateButton = new osparc.ui.form.FetchButton().set({
135105
label: this.tr("Update"),
136106
icon: "@MaterialIcons/update/14",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2023 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
qx.Class.define("osparc.component.node.ServiceOptionsView", {
19+
extend: qx.ui.core.Widget,
20+
type: "abstract",
21+
22+
construct: function(node) {
23+
this.base();
24+
25+
this._setLayout(new qx.ui.layout.VBox(5));
26+
27+
if (node) {
28+
this.setNode(node);
29+
}
30+
},
31+
32+
properties: {
33+
node: {
34+
check: "osparc.data.model.Node",
35+
init: null,
36+
nullable: false,
37+
apply: "_applyNode"
38+
}
39+
},
40+
41+
/**
42+
* @abstract
43+
*/
44+
members: {
45+
_applyNode: function() {
46+
throw new Error("Abstract method called!");
47+
}
48+
}
49+
});
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2023 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
qx.Class.define("osparc.component.node.UpdateResourceLimitsView", {
19+
extend: osparc.component.node.ServiceOptionsView,
20+
21+
events: {
22+
"limitsChanged": "qx.event.type.Event"
23+
},
24+
25+
members: {
26+
__resourceFields: null,
27+
__saveBtn: null,
28+
29+
_applyNode: function(node) {
30+
if (node.isComputational() || node.isDynamic()) {
31+
if (node.isComputational()) {
32+
node.getStatus().bind("interactive", this, "enabled", {
33+
converter: () => !osparc.data.model.NodeStatus.isComputationalRunning(node)
34+
});
35+
} else if (node.isDynamic()) {
36+
node.getStatus().bind("interactive", this, "enabled", {
37+
converter: interactive => interactive === "idle"
38+
});
39+
}
40+
this.__populateLayout();
41+
}
42+
},
43+
44+
__populateLayout: function() {
45+
this.__resourceFields = [];
46+
this._removeAll();
47+
48+
this._add(new qx.ui.basic.Label(this.tr("Update Service Limits")).set({
49+
font: "text-14"
50+
}));
51+
52+
const resourcesLayout = osparc.info.ServiceUtils.createResourcesInfo();
53+
const title = resourcesLayout.getChildren()[0];
54+
title.exclude();
55+
resourcesLayout.exclude();
56+
this._add(resourcesLayout);
57+
58+
const node = this.getNode();
59+
const params = {
60+
url: {
61+
studyId: node.getStudy().getUuid(),
62+
nodeId: node.getNodeId()
63+
}
64+
};
65+
osparc.data.Resources.get("nodesInStudyResources", params)
66+
.then(serviceResources => {
67+
resourcesLayout.show();
68+
const gridLayout = resourcesLayout.getChildren()[1];
69+
let row = 1;
70+
Object.entries(serviceResources).forEach(([imageName, imageInfo]) => {
71+
gridLayout.add(new qx.ui.basic.Label(imageName).set({
72+
font: "text-13"
73+
}), {
74+
row,
75+
column: 0
76+
});
77+
if ("resources" in imageInfo) {
78+
const resourcesInfo = imageInfo["resources"];
79+
Object.keys(resourcesInfo).forEach(resourceKey => {
80+
if (resourceKey === "VRAM") {
81+
return;
82+
}
83+
let column = 1;
84+
const resourceInfo = resourcesInfo[resourceKey];
85+
let label = resourceKey;
86+
if (resourceKey === "RAM") {
87+
label += " (GB)";
88+
}
89+
const resourceKeyTitle = new qx.ui.basic.Label(label).set({
90+
font: "text-13"
91+
});
92+
gridLayout.add(resourceKeyTitle, {
93+
row,
94+
column
95+
});
96+
column++;
97+
Object.keys(osparc.info.ServiceUtils.RESOURCES_INFO).forEach(resourceInfoKey => {
98+
if (resourceInfoKey in resourceInfo) {
99+
let value = resourceInfo[resourceInfoKey];
100+
if (resourceKey === "RAM") {
101+
value = osparc.utils.Utils.bytesToGB(value);
102+
}
103+
const spinner = new qx.ui.form.Spinner(0, value, 200).set({
104+
singleStep: 0.1
105+
});
106+
const nf = new qx.util.format.NumberFormat();
107+
nf.setMinimumFractionDigits(2);
108+
nf.setMaximumFractionDigits(2);
109+
spinner.setNumberFormat(nf);
110+
111+
spinner.imageName = imageName;
112+
spinner.resourceKey = resourceKey;
113+
this.__resourceFields.push(spinner);
114+
gridLayout.add(spinner, {
115+
row,
116+
column
117+
});
118+
column++;
119+
}
120+
});
121+
row++;
122+
});
123+
}
124+
});
125+
126+
const buttonsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
127+
this._add(buttonsLayout);
128+
129+
const resetBtn = new qx.ui.form.Button(this.tr("Reset"));
130+
resetBtn.addListener("execute", () => this.__populateLayout(), this);
131+
buttonsLayout.add(resetBtn);
132+
133+
const saveBtn = this.__saveBtn = new osparc.ui.form.FetchButton(this.tr("Save")).set({
134+
center: true,
135+
appearance: "strong-button"
136+
});
137+
saveBtn.addListener("execute", () => this.__saveValues(serviceResources), this);
138+
buttonsLayout.add(saveBtn);
139+
})
140+
.catch(err => console.error(err));
141+
142+
return resourcesLayout;
143+
},
144+
145+
__saveValues: function(serviceResources) {
146+
this.__saveBtn.setFetching(true);
147+
const updatedResources = osparc.utils.Utils.deepCloneObject(serviceResources);
148+
this.__resourceFields.forEach(resourceField => {
149+
if (
150+
resourceField.imageName in updatedResources &&
151+
resourceField.resourceKey in updatedResources[resourceField.imageName].resources
152+
) {
153+
let value = resourceField.getValue();
154+
if (resourceField.resourceKey === "RAM") {
155+
value = osparc.utils.Utils.gBToBytes(value);
156+
}
157+
updatedResources[resourceField.imageName].resources[resourceField.resourceKey]["limit"] = value;
158+
}
159+
});
160+
const node = this.getNode();
161+
const params = {
162+
url: {
163+
studyId: node.getStudy().getUuid(),
164+
nodeId: node.getNodeId()
165+
},
166+
data: updatedResources
167+
};
168+
osparc.data.Resources.fetch("nodesInStudyResources", "put", params)
169+
.then(() => {
170+
osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("Limits successfully updated"));
171+
this.fireEvent("limitsChanged");
172+
})
173+
.catch(err => {
174+
osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("Something went wrong Updating the limits"), "ERROR");
175+
console.error(err);
176+
})
177+
.finally(() => {
178+
this.__saveBtn.setFetching(false);
179+
this.__populateLayout();
180+
});
181+
}
182+
}
183+
});

services/static-webserver/client/source/class/osparc/data/Permissions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ qx.Class.define("osparc.data.Permissions", {
123123
"studies.template.create.all",
124124
"services.all.read",
125125
"services.all.reupdate",
126+
"services.all.updateLimits",
126127
"services.filePicker.read.all",
127128
"user.role.update",
128129
"user.clusters.create",

services/static-webserver/client/source/class/osparc/data/Resources.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,18 +213,20 @@ qx.Class.define("osparc.data.Resources", {
213213
* NODES
214214
*/
215215
"nodesInStudyResources": {
216-
idField: "nodeId",
217-
useCache: true,
216+
idField: ["studyId", "nodeId"],
218217
endpoints: {
219218
get: {
220219
method: "GET",
221220
url: statics.API + "/projects/{studyId}/nodes/{nodeId}/resources"
221+
},
222+
put: {
223+
method: "PUT",
224+
url: statics.API + "/projects/{studyId}/nodes/{nodeId}/resources"
222225
}
223226
}
224227
},
225228
"serviceResources": {
226229
idField: ["key", "version"],
227-
useCache: true,
228230
endpoints: {
229231
get: {
230232
method: "GET",

services/static-webserver/client/source/class/osparc/data/model/NodeStatus.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ qx.Class.define("osparc.data.model.NodeStatus", {
115115
return true;
116116
},
117117

118+
isComputationalRunning(node) {
119+
if (node && node.isComputational()) {
120+
return ["PUBLISHED", "PENDING", "STARTED"].includes(node.getStatus().getRunning());
121+
}
122+
return false;
123+
},
124+
118125
doesCompNodeNeedRun: function(node) {
119126
if (node && node.isComputational()) {
120127
return (

0 commit comments

Comments
 (0)