Skip to content

Commit 3b3fcf2

Browse files
authored
Merge pull request #916 from ITISFoundation/master
Ueli Steck 07.03.2019
2 parents 9e7e84d + 2b06c9f commit 3b3fcf2

33 files changed

+435
-1091
lines changed

.env-devel

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
#
2+
# - Keep it alfphabetical order
3+
# - To expose: export $(grep -v '^#' .env | xargs -0)
4+
#
5+
6+
BF_API_KEY=none
7+
BF_API_SECRET=none
8+
19
DOCKER_IMAGE_TAG=latest
210

311
PUBLISHED_HOST_NAME=localhost
@@ -35,7 +43,5 @@ SMTP_PORT=25
3543

3644
VENV2=.venv27/
3745

38-
BF_API_KEY=none
39-
BF_API_SECRET=none
40-
41-
LOGIN_REGISTRATION_CONFIRMATION_REQUIRED=1
46+
# python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key())"
47+
WEBSERVER_SESSION_SECRET_KEY=REPLACE ME with a key of at least length 32.

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ down-swarm:
161161
# Cache
162162

163163
.PHONY: pull-cache
164-
pull-cache:
164+
pull-cache: .env
165165
${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.cache.yml pull
166166

167167
.PHONY: build-cache
@@ -204,7 +204,7 @@ push:
204204
${DOCKER_COMPOSE} -f services/docker-compose.yml push ${SERVICES_LIST}
205205

206206
# target: pull – Pulls images from a registry
207-
pull:
207+
pull: .env
208208
${DOCKER_COMPOSE} -f services/docker-compose.yml pull ${SERVICES_LIST}
209209

210210
# target: create-stack-file – use as 'make create-stack-file output_file=stack.yaml'

api/specs/webserver/v0/openapi-projects.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ paths:
4949
schema:
5050
type: string
5151
description: 'Option to create a project from existing template: from_template={template_uuid}'
52+
- name: as_template
53+
in: query
54+
schema:
55+
type: string
56+
description: 'Option to create a template from existing project: as_template={study_uuid}'
5257
requestBody:
5358
content:
5459
application/json:

ops/travis/system-testing/tests/test_swarm_runs.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,14 @@ def test_all_services_up(docker_client, services_docker_compose, tools_docker_co
118118
"""
119119
running_services = docker_client.services.list()
120120

121-
assert (len(services_docker_compose["services"]) + len(tools_docker_compose["services"])) == len(running_services)
121+
service_names = []
122+
service_names += services_docker_compose["services"]
123+
service_names += tools_docker_compose["services"]
124+
125+
assert len(service_names) == len(running_services)
122126

123-
# TODO: check names instead
127+
for name in service_names:
128+
assert any( name in s.name for s in running_services ), f"{name} not in {running_services}"
124129

125130

126131
async def test_core_service_running(core_service_name, docker_client, loop):
@@ -187,4 +192,3 @@ async def test_check_serve_root(docker_client, services_docker_compose, tools_do
187192
pytest.fail("The server could not fulfill the request.\nError code {}".format(err.code))
188193
except urllib.error.URLError as e:
189194
pytest.fail("Failed reaching the server..\nError reason {}".format(err.reason))
190-

services/docker-compose.yml

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,25 +114,8 @@ services:
114114
- DIRECTOR_PORT=8080
115115
- STORAGE_HOST=storage
116116
- STORAGE_PORT=8080
117-
- LOGIN_REGISTRATION_CONFIRMATION_REQUIRED=${LOGIN_REGISTRATION_CONFIRMATION_REQUIRED:-1}
118-
- POSTGRES_ENDPOINT=${POSTGRES_ENDPOINT}
119-
- POSTGRES_USER=${POSTGRES_USER}
120-
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
121-
- POSTGRES_DB=${POSTGRES_DB}
122-
- POSTGRES_HOST=${POSTGRES_HOST}
123-
- POSTGRES_PORT=${POSTGRES_PORT}
124-
- RABBIT_HOST=${RABBIT_HOST}
125-
- RABBIT_PORT=${RABBIT_PORT}
126-
- RABBITMQ_USER=${RABBITMQ_USER}
127-
- RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD}
128-
- RABBITMQ_PROGRESS_CHANNEL=${RABBITMQ_PROGRESS_CHANNEL}
129-
- RABBITMQ_LOG_CHANNEL=${RABBITMQ_LOG_CHANNEL}
130-
- S3_ENDPOINT=${S3_ENDPOINT}
131-
- S3_ACCESS_KEY=${S3_ACCESS_KEY}
132-
- S3_SECRET_KEY=${S3_SECRET_KEY}
133-
- S3_BUCKET_NAME=${S3_BUCKET_NAME}
134-
- SMTP_HOST=${SMTP_HOST}
135-
- SMTP_PORT=${SMTP_PORT}
117+
env_file:
118+
- ../.env
136119
stdin_open: true
137120
tty: true
138121
depends_on:

services/web/client/source/class/qxapp/data/Permissions.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ qx.Class.define("qxapp.data.Permissions", {
8383

8484
members: {
8585
__userRole: null,
86+
__userLogin: null,
8687

8788
getRole() {
8889
return this.__userRole;
@@ -95,6 +96,10 @@ qx.Class.define("qxapp.data.Permissions", {
9596
this.__userRole = role;
9697
},
9798

99+
getLogin() {
100+
return this.__userLogin;
101+
},
102+
98103
getChildrenRoles(role) {
99104
role = role.toLowerCase();
100105
const childrenRoles = [];
@@ -151,7 +156,10 @@ qx.Class.define("qxapp.data.Permissions", {
151156
"preferences.role.update",
152157
"study.nodestree.uuid.read",
153158
"study.filestree.uuid.read",
154-
"study.logger.debug.read"
159+
"study.logger.debug.read",
160+
"studies.template.create",
161+
"studies.template.update",
162+
"studies.template.delete"
155163
],
156164
"admin": []
157165
};
@@ -215,6 +223,7 @@ qx.Class.define("qxapp.data.Permissions", {
215223
profile.addListenerOnce("getSuccess", e => {
216224
let profileData = e.getRequest().getResponse().data;
217225
this.__userRole = profileData.role;
226+
this.__userLogin = profileData.login;
218227
this.fireDataEvent("userProfileRecieved", true);
219228
}, this);
220229
profile.addListenerOnce("getError", e => {

services/web/client/source/class/qxapp/desktop/StudyBrowser.js

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -389,13 +389,19 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
389389
return list;
390390
},
391391

392+
__uuidToNumber: function(uuid) {
393+
const nThumbnails = 25;
394+
const lastCharacters = uuid.substr(uuid.length-10);
395+
const aNumber = parseInt(lastCharacters, 16);
396+
return aNumber%nThumbnails;
397+
},
398+
392399
/**
393400
* Delegates appearance and binding of each study item
394401
*/
395402
__getDelegate: function(fromTemplate, list) {
396403
const thumbnailWidth = 200;
397404
const thumbnailHeight = 120;
398-
const nThumbnails = 25;
399405
let that = this;
400406
let delegate = {
401407
// Item's Layout
@@ -433,12 +439,15 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
433439
},
434440
// Item's data binding
435441
bindItem: function(controller, item, id) {
436-
controller.bindProperty("uuid", "icon", {
442+
controller.bindProperty("uuid", "model", null, item, id);
443+
controller.bindProperty("thumbnail", "icon", {
437444
converter: function(data) {
438-
if (data) {
439-
const lastCharacters = data.substr(data.length-10);
440-
const aNumber = parseInt(lastCharacters, 16);
441-
const thumbnailId = aNumber%nThumbnails;
445+
const uuid = item.getModel();
446+
if (uuid) {
447+
if (data) {
448+
return data;
449+
}
450+
const thumbnailId = that.__uuidToNumber(uuid); // eslint-disable-line no-underscore-dangle
442451
return "qxapp/img"+ thumbnailId +".jpg";
443452
}
444453
return "@FontAwesome5Solid/plus-circle/80";
@@ -459,11 +468,6 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
459468
return data ? new Date(data) : null;
460469
}
461470
}, item, id);
462-
controller.bindProperty("uuid", "model", {
463-
converter: function(data) {
464-
return data;
465-
}
466-
}, item, id);
467471
},
468472
configureItem: item => {
469473
item.getChildControl("icon").set({
@@ -509,13 +513,19 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
509513
});
510514
},
511515

512-
__createForm: function(studyData, fromTemplate) {
516+
__createForm: function(studyData, isTemplate) {
513517
while (this.__editStudyLayout.getChildren().length > 1) {
514518
this.__editStudyLayout.removeAt(1);
515519
}
516520

517-
const itemsToBeDisplayed = ["name", "description", "prjOwner", "creationDate", "lastChangeDate"];
518-
const itemsToBeModified = fromTemplate ? [] : ["name", "description"];
521+
const canCreateTemplate = qxapp.data.Permissions.getInstance().canDo("studies.template.create");
522+
const canUpdateTemplate = qxapp.data.Permissions.getInstance().canDo("studies.template.update");
523+
const canDeleteTemplate = qxapp.data.Permissions.getInstance().canDo("studies.template.delete");
524+
const isMyTemplate = studyData["prjOwner"] === qxapp.data.Permissions.getInstance().getLogin();
525+
526+
const itemsToBeDisplayed = ["name", "description", "thumbnail", "prjOwner", "creationDate", "lastChangeDate"];
527+
const itemsToBeModified = (isTemplate && !(canUpdateTemplate && isMyTemplate)) ? [] : ["name", "description", "thumbnail"];
528+
519529
let form = new qx.ui.form.Form();
520530
let control;
521531
for (const dataId in studyData) {
@@ -529,6 +539,10 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
529539
control = new qx.ui.form.TextField();
530540
form.add(control, this.tr("Description"));
531541
break;
542+
case "thumbnail":
543+
control = new qx.ui.form.TextField();
544+
form.add(control, this.tr("Thumbnail"));
545+
break;
532546
case "prjOwner":
533547
control = new qx.ui.form.TextField();
534548
form.add(control, this.tr("Owner"));
@@ -563,7 +577,7 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
563577
// buttons
564578
let saveButton = new qx.ui.form.Button(this.tr("Save"));
565579
saveButton.setMinWidth(70);
566-
saveButton.setEnabled(!fromTemplate);
580+
saveButton.setEnabled(!isTemplate || (canUpdateTemplate && isMyTemplate));
567581
saveButton.addListener("execute", e => {
568582
for (let i=0; i<itemsToBeModified.length; i++) {
569583
const key = itemsToBeModified[i];
@@ -574,7 +588,11 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
574588
let resource = this.__studyResources.project;
575589

576590
resource.addListenerOnce("putSuccess", ev => {
577-
this.reloadUserStudies();
591+
if (isTemplate) {
592+
this.reloadTemplateStudies();
593+
} else {
594+
this.reloadUserStudies();
595+
}
578596
}, this);
579597

580598
resource.put({
@@ -585,6 +603,35 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
585603
}, this);
586604
form.addButton(saveButton);
587605

606+
if (!isTemplate && canCreateTemplate) {
607+
const saveAsButton = new qx.ui.form.Button(this.tr("Save As Template"));
608+
saveAsButton.setMinWidth(70);
609+
610+
saveAsButton.addListener("execute", e => {
611+
for (let i=0; i<itemsToBeModified.length; i++) {
612+
const key = itemsToBeModified[i];
613+
let getter = "get" + qx.lang.String.firstUp(key);
614+
let newVal = model[getter]();
615+
studyData[key] = newVal;
616+
}
617+
618+
const resources = this.__studyResources.projects;
619+
620+
resources.addListenerOnce("postSaveAsTemplateSuccess", ev => {
621+
console.log(ev);
622+
this.reloadTemplateStudies();
623+
}, this);
624+
resources.addListenerOnce("postSaveAsTemplateError", ev => {
625+
console.error(ev);
626+
});
627+
resources.postSaveAsTemplate({
628+
"study_id": studyData["uuid"]
629+
}, studyData);
630+
}, this);
631+
632+
form.addButton(saveAsButton);
633+
}
634+
588635
let cancelButton = new qx.ui.form.Button(this.tr("Cancel"));
589636
cancelButton.setMinWidth(70);
590637
cancelButton.addListener("execute", e => {
@@ -594,14 +641,14 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
594641

595642
let deleteButton = new qx.ui.form.Button(this.tr("Delete"));
596643
deleteButton.setMinWidth(70);
597-
deleteButton.setEnabled(!fromTemplate);
644+
deleteButton.setEnabled(!isTemplate || (canDeleteTemplate && isMyTemplate));
598645
deleteButton.addListener("execute", e => {
599646
let win = this.__createConfirmWindow();
600647
win.center();
601648
win.open();
602649
win.addListener("close", () => {
603650
if (win["value"] === 1) {
604-
this.__deleteStudy(studyData);
651+
this.__deleteStudy(studyData, isTemplate);
605652
}
606653
}, this);
607654
}, this);
@@ -610,13 +657,17 @@ qx.Class.define("qxapp.desktop.StudyBrowser", {
610657
this.__editStudyLayout.add(new qx.ui.form.renderer.Single(form));
611658
},
612659

613-
__deleteStudy: function(studyData) {
660+
__deleteStudy: function(studyData, isTemplate = false) {
614661
this.__stopInteractiveServicesInStudy(studyData);
615662

616663
let resource = this.__studyResources.project;
617664

618665
resource.addListenerOnce("delSuccess", ev => {
619-
this.reloadUserStudies();
666+
if (isTemplate) {
667+
this.reloadTemplateStudies();
668+
} else {
669+
this.reloadUserStudies();
670+
}
620671
}, this);
621672

622673
resource.del({

services/web/client/source/class/qxapp/io/rest/ResourceFactory.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ qx.Class.define("qxapp.io.rest.ResourceFactory", {
126126
postFromTemplate: {
127127
method: "POST",
128128
url: basePath+"/projects?from_template={template_id}"
129+
},
130+
131+
postSaveAsTemplate: {
132+
method: "POST",
133+
url: basePath+"/projects?as_template={study_id}"
129134
}
130135
});
131136

services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,5 @@ storage:
5656
host: storage
5757
port: 11111
5858
session:
59-
secret_key: "TODO: Replace with a key of at least length 32"
59+
secret_key: ${WEBSERVER_SESSION_SECRET_KEY}
60+
...

services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ rabbit:
3737
login:
3838
enabled: True
3939
registration_invitation_required: False
40-
registration_confirmation_required: ${LOGIN_REGISTRATION_CONFIRMATION_REQUIRED}
40+
registration_confirmation_required: True
4141
smtp:
4242
sender: 'OSPARC support <[email protected]>'
4343
host: ${SMTP_HOST}
@@ -56,5 +56,5 @@ storage:
5656
port: ${STORAGE_PORT}
5757
version: v0
5858
session:
59-
secret_key: "TODO: Replace with a key of at least length 32"
59+
secret_key: ${WEBSERVER_SESSION_SECRET_KEY}
6060
...

0 commit comments

Comments
 (0)