Merge pull request #1013 from KinashYurii/DATALAB-2174
[DATALAB-2174] : added BE for admin page
diff --git a/services/billing-azure/billing.yml b/services/billing-azure/billing.yml
index 188bbe6..a9e46f4 100644
--- a/services/billing-azure/billing.yml
+++ b/services/billing-azure/billing.yml
@@ -1,92 +1,188 @@
# *****************************************************************************
#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
#
# ******************************************************************************
-spring:
- main:
- allow-bean-definition-overriding: true
- data:
- mongodb:
- username: admin
- password: MONGO_PASSWORD
- database: datalabdb
- port: MONGO_PORT
- host: MONGO_HOST
+<#include "ssn.yml">
+# Minimum and maximum number of slave EMR instances than could be created
+minEmrInstanceCount: 2
+maxEmrInstanceCount: 14
+# Minimum and maximum percentage cost for slave EMR spot instances biding
+minEmrSpotInstanceBidPct: 20
+maxEmrSpotInstanceBidPct: 90
+
+# Maximum length for gcp user name (due to gcp restrictions)
+maxUserNameLength: 10
+# Minimum and maximum number of slave Dataproc instances that could be created
+minInstanceCount: 3
+maxInstanceCount: 15
+minDataprocPreemptibleCount: 0
+gcpOuauth2AuthenticationEnabled: false
+
+# Boundaries for Spark cluster creation
+minSparkInstanceCount: 2
+maxSparkInstanceCount: 14
+
+# Timeout for check the status of environment via provisioning service
+checkEnvStatusTimeout: 5m
+
+# Restrict access to DataLab features using roles policy
+rolePolicyEnabled: true
+# Default access to DataLab features using roles policy
+roleDefaultAccess: false
+
+# Set to true to enable the scheduler of billing report.
+billingSchedulerEnabled: false
+billingPort: 8088
+# Set to true to enable audit
+auditEnabled: true
+ # Name of configuration file for billing report.
+ <#if DEV_MODE == "true">
+billingConfFile: ${sys['user.dir']}/../billing/billing.yml
+ <#else>
+billingConfFile: ${DATALAB_CONF_DIR}/billing.yml
+ </#if>
+
+ssnInstanceSize: <SSN_INSTANCE_SIZE>
+
+serviceBaseName: SERVICE_BASE_NAME
+os: OPERATION_SYSTEM
server:
- port: 8088
- servlet:
- contextPath: /api/billing
+ requestLog:
+ appenders:
+ - type: file
+ currentLogFilename: ${LOG_ROOT_DIR}/ssn/request-selfservice.log
+ archive: true
+ archivedLogFilenamePattern: ${LOG_ROOT_DIR}/ssn/request-selfservice-%d{yyyy-MM-dd}.log.gz
+ archivedFileCount: 10
+ rootPath: "/api"
+ applicationConnectors:
+ # - type: http
+ # port: 8080
+ - type: https
+ port: 8443
+ certAlias: ssn
+ validateCerts: false
+ keyStorePath: ${KEY_STORE_PATH}
+ keyStorePassword: ${KEY_STORE_PASSWORD}
+ trustStorePath: ${TRUST_STORE_PATH}
+ trustStorePassword: ${TRUST_STORE_PASSWORD}
+ adminConnectors:
+ # - type: http
+ # port: 8081
+ - type: https
+ port: 8444
+ certAlias: ssn
+ validateCerts: false
+ keyStorePath: ${KEY_STORE_PATH}
+ keyStorePassword: ${KEY_STORE_PASSWORD}
+ trustStorePath: ${TRUST_STORE_PATH}
+ trustStorePassword: ${TRUST_STORE_PASSWORD}
-server.ssl.key-store-type: JKS
-server.ssl.key-store: /home/OS_USER/keys/ssn.keystore.jks
-server.ssl.key-store-password: KEY_STORE_PASSWORD
-server.ssl.key-alias: ssn
+mongoMigrationEnabled: false
logging:
- file: /var/opt/datalab/log/ssn/billing.log
- level:
- com:
- epam: trace
+ level: INFO
+ loggers:
+ com.epam: INFO
+ org.apache.guacamole: DEBUG
+ com.novemberain: ERROR
+ appenders:
+ <#if DEV_MODE == "true">
+ - type: console
+ </#if>
+ - type: file
+ currentLogFilename: ${LOG_ROOT_DIR}/ssn/selfservice.log
+ archive: true
+ archivedLogFilenamePattern: ${LOG_ROOT_DIR}/ssn/selfservice-%d{yyyy-MM-dd}.log.gz
+ archivedFileCount: 10
-keycloak:
+mavenSearchService:
+ protocol: http
+ host: search.maven.org
+ port: 80
+ jerseyClient:
+ timeout: 5s
+ connectionTimeout: 5s
+
+schedulers:
+ inactivity:
+ enabled: false
+ cron: "0 0 0/2 ? * * *"
+ checkInfrastructureStatusScheduler:
+ enabled: true
+ cron: "0 0/15 * ? * *"
+ startComputationalScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ stopComputationalScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ startExploratoryScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ stopExploratoryScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ terminateComputationalScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ checkQuoteScheduler:
+ enabled: true
+ cron: "0 2/15 * ? * *"
+ checkUserQuoteScheduler:
+ enabled: false
+ cron: "0 0 * ? * * *"
+ checkProjectQuoteScheduler:
+ enabled: true
+ cron: "0 4/15 * ? * *"
+ checkEndpointStatusScheduler:
+ enabled: true
+ cron: "0 6/15 * ? * *"
+ billingScheduler:
+ enabled: true
+ cron: "0 0/15 * ? * *"
+
+
+guacamole:
+ connectionProtocol: ssh
+ serverPort: 4822
+ port: 22
+ username: datalab-user
+
+keycloakConfiguration:
+ redirectUri: https://localhost:8443/
+ realm: DLAB_bhliva
bearer-only: true
- realm: datalab
- resource: KEYCLOAK_CLIENT_ID
- credentials.secret: KEYCLOAK_CLIENT_SECRET
+ auth-server-url: http://52.11.45.11:8080/auth
ssl-required: none
- auth-server-url: KEYCLOAK_AUTH_SERVER_URL
+ register-node-at-startup: true
+ register-node-period: 600
+ resource: sss
+ credentials:
+ secret: cf5a484b-039b-4161-8707-ad65c0f25962
-datalab:
- sbn: SERVICE_BASE_NAME
- billingEnabled: true
-
- # Authentication info
-
- # Explicit azure authentication parameters
- clientId: CLIENT_ID
- clientSecret: CLIENT_SECRET
- tenantId: TENANT_ID
- subscriptionId: SUBSCRIPTION_ID
-
- # Contains authentication info (clientId, clientSecret, tenantId, subscriptionId) received after Azure CLI authentication
- # Overrides explicit azure authentication parameters above
- authenticationFile: AUTHENTICATION_FILE
- # Billing configuration for RateCard API. For more details please see https://msdn.microsoft.com/en-us/library/mt219004.aspx
- offerNumber: OFFER_NUMBER
- currency: CURRENCY
- locale: LOCALE
- regionInfo: REGION_INFO
-
- # Azure provides with aggregated data by date. Scheduler tries to retrieve data every <period> with <initialDelay> from
- # application startup in minutes.
- # Scheduler retrieves data only for the past period of time from midnight of start day to midnight of end date.
- # Scheduler does not retrieve data for the current date
- initialDelay: 10
- period: 60
- aggregationOutputMongoDataSource:
- host: MONGO_HOST
- port: MONGO_PORT
- username: admin
- password: MONGO_PASSWORD
- database: datalabdb
- ssnStorageAccountTagName: <AZURE_SSN_STORAGE_ACCOUNT_TAG>
- sharedStorageAccountTagName: <AZURE_SHARED_STORAGE_ACCOUNT_TAG>
- datalakeTagName: <AZURE_DATALAKE_TAG>
\ No newline at end of file
+jerseyClient:
+ minThreads: 1
+ maxThreads: 128
+ workQueueSize: 8
+ gzipEnabled: true
+ gzipEnabledForRequests: false
+ chunkedEncodingEnabled: true
\ No newline at end of file
diff --git a/services/common/src/main/java/com/epam/datalab/exceptions/DynamicChangePropertiesException.java b/services/common/src/main/java/com/epam/datalab/exceptions/DynamicChangePropertiesException.java
new file mode 100644
index 0000000..68550ae
--- /dev/null
+++ b/services/common/src/main/java/com/epam/datalab/exceptions/DynamicChangePropertiesException.java
@@ -0,0 +1,8 @@
+package com.epam.datalab.exceptions;
+
+public class DynamicChangePropertiesException extends DatalabException {
+
+ public DynamicChangePropertiesException(String message) {
+ super(message);
+ }
+}
diff --git a/services/datalab-webapp-common/src/main/java/com/epam/datalab/rest/mappers/DynamicChangePropertiesExceptionMapper.java b/services/datalab-webapp-common/src/main/java/com/epam/datalab/rest/mappers/DynamicChangePropertiesExceptionMapper.java
new file mode 100644
index 0000000..6408b16
--- /dev/null
+++ b/services/datalab-webapp-common/src/main/java/com/epam/datalab/rest/mappers/DynamicChangePropertiesExceptionMapper.java
@@ -0,0 +1,20 @@
+package com.epam.datalab.rest.mappers;
+
+import com.epam.datalab.exceptions.DynamicChangePropertiesException;
+import com.epam.datalab.rest.dto.ErrorDTO;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+public class DynamicChangePropertiesExceptionMapper implements ExceptionMapper<DynamicChangePropertiesException> {
+
+ @Override
+ public Response toResponse(DynamicChangePropertiesException e) {
+ final Response.Status status = Response.Status.NO_CONTENT;
+ return Response.status(status)
+ .type(MediaType.APPLICATION_JSON)
+ .entity(new ErrorDTO(status.getStatusCode(), e.getMessage()))
+ .build();
+ }
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/SelfServiceApplication.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/SelfServiceApplication.java
index 65059f8..9c5f166 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/SelfServiceApplication.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/SelfServiceApplication.java
@@ -46,6 +46,7 @@
import com.epam.datalab.backendapi.resources.UserGroupResource;
import com.epam.datalab.backendapi.resources.UserRoleResource;
import com.epam.datalab.backendapi.resources.UserSettingsResource;
+import com.epam.datalab.backendapi.resources.admin.ChangePropertiesResource;
import com.epam.datalab.backendapi.resources.callback.BackupCallback;
import com.epam.datalab.backendapi.resources.callback.CheckInactivityCallback;
import com.epam.datalab.backendapi.resources.callback.ComputationalCallback;
@@ -198,6 +199,7 @@
jersey.register(injector.getInstance(ProjectCallback.class));
jersey.register(injector.getInstance(OdahuResource.class));
jersey.register(injector.getInstance(OdahuCallback.class));
+ jersey.register(injector.getInstance(ChangePropertiesResource.class));
}
private void disableGzipHandlerForGuacamoleServlet(Server server) {
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/admin/ChangePropertiesResource.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/admin/ChangePropertiesResource.java
new file mode 100644
index 0000000..2386b86
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/admin/ChangePropertiesResource.java
@@ -0,0 +1,116 @@
+package com.epam.datalab.backendapi.resources.admin;
+
+import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.backendapi.resources.dto.YmlDTO;
+import com.epam.datalab.backendapi.roles.UserRoles;
+import com.epam.datalab.backendapi.service.impl.DynamicChangeProperties;
+import io.dropwizard.auth.Auth;
+import lombok.NoArgsConstructor;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Path("admin")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@NoArgsConstructor
+public class ChangePropertiesResource {
+
+ @GET
+ @Path("/self-service")
+ public Response getSelfServiceProperties(@Auth UserInfo userInfo) {
+ if (UserRoles.isAdmin(userInfo)) {
+ return Response
+ .ok(DynamicChangeProperties.getSelfServiceProperties())
+ .build();
+ } else {
+ return Response
+ .status(Response.Status.FORBIDDEN)
+ .build();
+ }
+ }
+
+ @GET
+ @Path("/provisioning-service")
+ public Response getProvisioningServiceProperties(@Auth UserInfo userInfo) {
+ if (UserRoles.isAdmin(userInfo)) {
+ return Response
+ .ok(DynamicChangeProperties.getProvisioningServiceProperties())
+ .build();
+ } else {
+ return Response
+ .status(Response.Status.FORBIDDEN)
+ .build();
+ }
+ }
+
+ @GET
+ @Path("/billing")
+ public Response getBillingServiceProperties(@Auth UserInfo userInfo) {
+ if (UserRoles.isAdmin(userInfo)) {
+ return Response
+ .ok(DynamicChangeProperties.getBillingServiceProperties())
+ .build();
+ } else {
+ return Response
+ .status(Response.Status.FORBIDDEN)
+ .build();
+ }
+ }
+
+ @POST
+ @Path("/self-service")
+ public Response overwriteSelfServiceProperties(@Auth UserInfo userInfo, YmlDTO ymlDTO) {
+ if (UserRoles.isAdmin(userInfo)) {
+ DynamicChangeProperties.overwriteSelfServiceProperties(ymlDTO.getYmlString());
+ return Response.ok().build();
+ } else {
+ return Response
+ .status(Response.Status.FORBIDDEN)
+ .build();
+ }
+ }
+
+ @POST
+ @Path("/provisioning-service")
+ public Response overwriteProvisioningServiceProperties(@Auth UserInfo userInfo, YmlDTO ymlDTO) {
+ if (UserRoles.isAdmin(userInfo)) {
+ DynamicChangeProperties.overwriteProvisioningServiceProperties(ymlDTO.getYmlString());
+ return Response.ok().build();
+ } else {
+ return Response
+ .status(Response.Status.FORBIDDEN)
+ .build();
+ }
+ }
+
+ @POST
+ @Path("/billing")
+ public Response overwriteBillingServiceProperties(@Auth UserInfo userInfo, YmlDTO ymlDTO) {
+ if (UserRoles.isAdmin(userInfo)) {
+ DynamicChangeProperties.overwriteBillingServiceProperties(ymlDTO.getYmlString());
+ return Response.ok().build();
+ } else {
+ return Response
+ .status(Response.Status.FORBIDDEN)
+ .build();
+ }
+ }
+
+ @POST
+ @Path("/restart")
+ public Response restart(@Auth UserInfo userInfo,
+ @QueryParam("billing") boolean billing,
+ @QueryParam("provserv") boolean provserv,
+ @QueryParam("ui") boolean ui) {
+ if (UserRoles.isAdmin(userInfo)) {
+ DynamicChangeProperties.restart(billing, provserv, ui);
+ return Response.ok().build();
+ } else {
+ return Response
+ .status(Response.Status.FORBIDDEN)
+ .build();
+ }
+ }
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/YmlDTO.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/YmlDTO.java
new file mode 100644
index 0000000..5317064
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/YmlDTO.java
@@ -0,0 +1,9 @@
+package com.epam.datalab.backendapi.resources.dto;
+
+import lombok.Data;
+
+@Data
+public class YmlDTO {
+
+ private String ymlString;
+}
\ No newline at end of file
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/DynamicChangeProperties.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/DynamicChangeProperties.java
new file mode 100644
index 0000000..9a7d349
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/DynamicChangeProperties.java
@@ -0,0 +1,180 @@
+package com.epam.datalab.backendapi.service.impl;
+
+import com.epam.datalab.exceptions.DynamicChangePropertiesException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+public class DynamicChangeProperties {
+
+ private static final String SELF_SERVICE = "self-service.yml";
+ private static final String SELF_SERVICE_PROP_PATH = "/opt/datalab/conf/self-service.yml";
+ private static final String SELF_SERVICE_SUPERVISORCTL_RUN_NAME = " ui ";
+ private static final String PROVISIONING_SERVICE = "provisioning.yml";
+ private static final String PROVISIONING_SERVICE_PROP_PATH = "/opt/datalab/conf/provisioning.yml";
+ private static final String PROVISIONING_SERVICE_SUPERVISORCTL_RUN_NAME = " provserv ";
+ private static final String BILLING_SERVICE = "billing.yml";
+ private static final String BILLING_SERVICE_PROP_PATH = "/opt/datalab/conf/billing.yml";
+ private static final String BILLING_SERVICE_SUPERVISORCTL_RUN_NAME = " billing ";
+ private static final String SECRET_REGEX = "(.*)[sS]ecret(.*): (.*)";
+ private static final String SECRET_REPLACEMENT_FORMAT = " ***********";
+ private static final String SH_COMMAND = "sudo supervisorctl restart";
+
+ private static final String LICENCE =
+ "# *****************************************************************************\n" +
+ "#\n" +
+ "# Licensed to the Apache Software Foundation (ASF) under one\n" +
+ "# or more contributor license agreements. See the NOTICE file\n" +
+ "# distributed with this work for additional information\n" +
+ "# regarding copyright ownership. The ASF licenses this file\n" +
+ "# to you under the Apache License, Version 2.0 (the\n" +
+ "# \"License\"); you may not use this file except in compliance\n" +
+ "# with the License. You may obtain a copy of the License at\n" +
+ "#\n" +
+ "# http://www.apache.org/licenses/LICENSE-2.0\n" +
+ "#\n" +
+ "# Unless required by applicable law or agreed to in writing,\n" +
+ "# software distributed under the License is distributed on an\n" +
+ "# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n" +
+ "# KIND, either express or implied. See the License for the\n" +
+ "# specific language governing permissions and limitations\n" +
+ "# under the License.\n" +
+ "#\n" +
+ "# ******************************************************************************";
+
+ private static final int DEFAULT_VALUE_PLACE = 1;
+ private static final int DEFAULT_NAME_PLACE = 0;
+
+ public static String getSelfServiceProperties() {
+ return readFileAsString(SELF_SERVICE_PROP_PATH, SELF_SERVICE);
+ }
+
+ public static String getProvisioningServiceProperties() {
+ return readFileAsString(PROVISIONING_SERVICE_PROP_PATH, PROVISIONING_SERVICE);
+ }
+
+ public static String getBillingServiceProperties() {
+ return readFileAsString(BILLING_SERVICE_PROP_PATH, BILLING_SERVICE);
+ }
+
+ public static void overwriteSelfServiceProperties(String ymlString) {
+ writeFileFromString(ymlString, SELF_SERVICE, SELF_SERVICE_PROP_PATH);
+ }
+
+ public static void overwriteProvisioningServiceProperties(String ymlString) {
+ writeFileFromString(ymlString, PROVISIONING_SERVICE, PROVISIONING_SERVICE_PROP_PATH);
+ }
+
+ public static void overwriteBillingServiceProperties(String ymlString) {
+ writeFileFromString(ymlString, BILLING_SERVICE, BILLING_SERVICE_PROP_PATH);
+ }
+
+ public static void restart(boolean billing, boolean provserv, boolean ui) {
+ try {
+ String shCommand = buildSHCommand(billing, provserv, ui);
+ log.info("Tying to restart ui: {}, provserv: {}, billing: {}, with command: {}", ui,
+ provserv, billing, shCommand);
+ Runtime.getRuntime().exec(shCommand).waitFor();
+
+
+ } catch (IOException | InterruptedException e) {
+ log.error(e.getMessage());
+ }
+ }
+
+ private static String buildSHCommand(boolean billing, boolean provserv, boolean ui) {
+ StringBuilder stringBuilder = new StringBuilder(SH_COMMAND);
+ if (billing) stringBuilder.append(BILLING_SERVICE_SUPERVISORCTL_RUN_NAME);
+ if (provserv) stringBuilder.append(PROVISIONING_SERVICE_SUPERVISORCTL_RUN_NAME);
+ if (ui) stringBuilder.append(SELF_SERVICE_SUPERVISORCTL_RUN_NAME);
+ return stringBuilder.toString();
+ }
+
+ private static String readFileAsString(String selfServicePropPath, String serviceName) {
+ try {
+ log.trace("Trying to read self-service.yml, file from path {} :", selfServicePropPath);
+ String currentConf = FileUtils.readFileToString(new File(selfServicePropPath), Charset.defaultCharset());
+ return hideSecretsAndRemoveLicence(currentConf);
+ } catch (IOException e) {
+ log.error(e.getMessage());
+ throw new DynamicChangePropertiesException(String.format("Failed while read file %s", serviceName));
+ }
+ }
+
+ private static String hideSecretsAndRemoveLicence(String currentConf) {
+ Matcher m = Pattern.compile(SECRET_REGEX).matcher(currentConf);
+ List<String> secrets = new ArrayList<>();
+ String confWithReplacedSecretConf = removeLicence(currentConf);
+ while (m.find()) {
+ secrets.add(m.group().split(":")[DEFAULT_VALUE_PLACE]);
+ }
+ for (String secret : secrets) {
+ confWithReplacedSecretConf = confWithReplacedSecretConf.replace(secret, SECRET_REPLACEMENT_FORMAT);
+ }
+ return confWithReplacedSecretConf;
+ }
+
+ private static String removeLicence(String conf) {
+ return conf.substring(LICENCE.length() + 7);
+ }
+
+ private static void writeFileFromString(String newPropFile, String serviceName, String servicePath) {
+ try {
+ String oldFile = FileUtils.readFileToString(new File(servicePath), Charset.defaultCharset());
+ BufferedWriter writer = new BufferedWriter(new FileWriter(servicePath));
+ log.trace("Trying to overwrite {}, file for path {} :", serviceName, servicePath);
+ writer.write(addLicence());
+ writer.write(checkAndReplaceSecretIfEmpty(newPropFile, oldFile));
+ log.info("{} overwritten successfully", serviceName);
+ writer.close();
+ } catch (IOException e) {
+ log.error("Failed during overwriting {}", serviceName);
+ throw new DynamicChangePropertiesException(String.format("Failed during overwriting %s", serviceName));
+ }
+ }
+
+ private static String addLicence() {
+ return LICENCE + "\n\n";
+ }
+
+ private static String checkAndReplaceSecretIfEmpty(String newPropFile, String oldProf) {
+ Map<String, String> emptySecrets = findEmptySecret(newPropFile);
+ return emptySecrets.isEmpty() ? newPropFile : replaceEmptySecret(newPropFile, oldProf, emptySecrets);
+ }
+
+ private static String replaceEmptySecret(String newPropFile, String oldProf, Map<String, String> emptySecrets) {
+ String fileWithReplacedEmptySecrets = newPropFile;
+ Matcher oldProfMatcher = Pattern.compile(SECRET_REGEX).matcher(oldProf);
+ while (oldProfMatcher.find()) {
+ String[] s = oldProfMatcher.group().split(":");
+ if (emptySecrets.containsKey(s[DEFAULT_NAME_PLACE])) {
+ fileWithReplacedEmptySecrets = fileWithReplacedEmptySecrets.replace(emptySecrets.get(s[DEFAULT_NAME_PLACE]), oldProfMatcher.group());
+ }
+ }
+ return fileWithReplacedEmptySecrets;
+ }
+
+ private static Map<String, String> findEmptySecret(String newPropFile) {
+ Matcher newPropFileMatcher = Pattern.compile(SECRET_REGEX).matcher(newPropFile);
+ Map<String, String> emptySecrets = new HashMap<>();
+ while (newPropFileMatcher.find()) {
+ String[] s = newPropFileMatcher.group().split(":");
+ if (s[DEFAULT_VALUE_PLACE].equals(SECRET_REPLACEMENT_FORMAT)) {
+ emptySecrets.put(s[DEFAULT_NAME_PLACE], newPropFileMatcher.group());
+ }
+ }
+ return emptySecrets;
+ }
+}
diff --git a/services/self-service/src/main/resources/webapp/package-lock.json b/services/self-service/src/main/resources/webapp/package-lock.json
index a4d8a2f..89cca19 100644
--- a/services/self-service/src/main/resources/webapp/package-lock.json
+++ b/services/self-service/src/main/resources/webapp/package-lock.json
@@ -2002,6 +2002,11 @@
"negotiator": "0.6.2"
}
},
+ "ace-builds": {
+ "version": "1.4.12",
+ "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.12.tgz",
+ "integrity": "sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg=="
+ },
"acorn": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
@@ -2602,6 +2607,11 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
+ "brace": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz",
+ "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg="
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -7092,6 +7102,15 @@
"date-fns": "^1.29.0"
}
},
+ "ng2-ace-editor": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/ng2-ace-editor/-/ng2-ace-editor-0.3.9.tgz",
+ "integrity": "sha512-e8Q4YCirlL/OEiekewmzupG+zV3prYsiYmQnRzQzd0wNgsPjOLOdb0it7cCbzFfIXKGyIIHKTW5584WxPr2LnQ==",
+ "requires": {
+ "ace-builds": "^1.4.2",
+ "brace": "^0.11.1"
+ }
+ },
"ngx-toastr": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-12.1.0.tgz",
diff --git a/services/self-service/src/main/resources/webapp/package.json b/services/self-service/src/main/resources/webapp/package.json
index 6baa7a2..bd320b3 100644
--- a/services/self-service/src/main/resources/webapp/package.json
+++ b/services/self-service/src/main/resources/webapp/package.json
@@ -33,6 +33,7 @@
"moment": "^2.24.0",
"moment-timezone": "^0.5.31",
"ng-daterangepicker": "^1.1.0",
+ "ng2-ace-editor": "^0.3.9",
"ngx-toastr": "^12.1.0",
"rxjs": "^6.6.3",
"rxjs-compat": "6.5.3",
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts b/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts
index 6d5b6d0..dfbbbf7 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts
@@ -23,11 +23,12 @@
import { ManagenementModule } from './management';
import { ProjectModule } from './project';
import { RolesModule } from './roles';
+import {ConfigurationModule} from './configuration';
import {OdahuModule} from './odahu';
@NgModule({
- imports: [CommonModule, ManagenementModule, ProjectModule, RolesModule, OdahuModule],
+ imports: [CommonModule, ManagenementModule, ProjectModule, RolesModule, ConfigurationModule, OdahuModule],
declarations: [],
- exports: [ManagenementModule, ProjectModule, RolesModule, OdahuModule]
+ exports: [ManagenementModule, ProjectModule, RolesModule, ConfigurationModule, OdahuModule]
})
export class AdministrationModule { }
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.html
new file mode 100644
index 0000000..f5c19bb
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.html
@@ -0,0 +1,110 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<div class="base-retreat">
+ <div class="sub-nav">
+ <button mat-raised-button class="butt"
+ (click)="action('save')"
+ [disabled]="provisioningSource === serverConfigs.provisioningSource
+ && serverConfigs.selvServiceSource === selvServiceSource
+ && serverConfigs.billingSource === billingSource
+ "
+ >
+ Save
+ </button>
+ <button mat-raised-button class="butt"
+ (click)="action('discard')"
+ [disabled]="provisioningSource === serverConfigs.provisioningSource
+ && serverConfigs.selvServiceSource === selvServiceSource
+ && serverConfigs.billingSource === billingSource"
+ >
+ Discard changes
+ </button>
+ <button mat-raised-button class="butt" (click)="refresh()">
+ <i class="material-icons refresh-icon">autorenew</i>Refresh
+ </button>
+ </div>
+ <mat-divider></mat-divider>
+ <div class="configuration-wrapper">
+ <mat-tab-group animationDuration="0.5ms" (selectedTabChange)="tabChanged($event)">
+ <mat-tab label="Main"
+ [disabled]="!(provisioningSource === serverConfigs.provisioningSource
+ && serverConfigs.selvServiceSource === selvServiceSource
+ && serverConfigs.billingSource === billingSource
+ ) && activeTab !== 0"
+ >
+ <h4>Main settings</h4>
+ <div class="main-wrapper">
+ <section class="section">
+ <p class="section-title">Restart services</p>
+ <div class="section-content">
+ <ul class="list-menu selection-list">
+ <li *ngFor="let service of services">
+ <p class="list-item" role="menuitem">
+ <span (click)="toggleSetings(service);$event.stopPropagation()" class="d-flex">
+ <span class="empty-checkbox" [ngClass]="{'checked': service.selected}">
+ <span class="checked-checkbox" *ngIf="service.selected"></span>
+ </span>
+ {{service.label}}
+ </span>
+ </p>
+ </li>
+ </ul>
+ <button mat-raised-button type="button" class="butt action" (click)="restartServices()">Restart</button>
+ </div>
+ </section>
+ </div>
+ </mat-tab>
+ <mat-tab label="Provisioning"
+ [disabled]="!(provisioningSource === serverConfigs.provisioningSource
+ && serverConfigs.selvServiceSource === selvServiceSource
+ && serverConfigs.billingSource === billingSource
+ ) && activeTab !== 1
+">
+ <h4>Edit provisioning.yml</h4>
+ <div class="editor-wrap">
+ <div ace-editor [(text)]="provisioningSource" ></div>
+ </div>
+ </mat-tab>
+ <mat-tab label="Self service"
+ [disabled]="!(provisioningSource === serverConfigs.provisioningSource
+ && serverConfigs.selvServiceSource === selvServiceSource
+ && serverConfigs.billingSource === billingSource
+ ) && activeTab !== 2
+"
+ >
+ <h4>Edit self-service.yml</h4>
+ <div class="editor-wrap">
+ <div ace-editor [(text)]="selvServiceSource" ></div>
+ </div>
+ </mat-tab>
+ <mat-tab label="Billing"
+ [disabled]="!(provisioningSource === serverConfigs.provisioningSource
+ && serverConfigs.selvServiceSource === selvServiceSource
+ && serverConfigs.billingSource === billingSource
+ ) && activeTab !== 3"
+ >
+ <h4>Edit billing.yml</h4>
+ <div class="editor-wrap">
+ <div ace-editor [(text)]="billingSource" ></div>
+ </div>
+ </mat-tab>
+ </mat-tab-group>
+ </div>
+</div>
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.scss b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.scss
new file mode 100644
index 0000000..40f533a
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.scss
@@ -0,0 +1,92 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+.configuration-wrapper{
+ box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
+ padding-top: 0;
+ height: calc(100vh - 130px);
+
+ .sub-nav{
+ justify-content: flex-end;
+ padding: 0 30px;
+ }
+
+ h4{
+ padding: 10px 30px;
+ color: rgba(0,0,0,.87);
+ }
+
+ .editor-wrap{
+ height: calc(100% - 60px);
+ box-shadow: 0 2px 24px 0 rgba(73,93,112,0.3);
+ margin: 0 30px;
+ }
+
+ .main-wrapper{
+ padding: 10px 30px;
+
+ .section{
+
+ &-title{
+ font-size: 17px;
+ }
+
+ &-content {
+
+ .list-menu {
+ width: 100%;
+ max-height: 450px;
+ left: 0;
+ padding: 10px 0;
+ margin: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ li {
+ padding: 0;
+ margin: 0;
+ }
+
+ .list-item{
+ padding: 5px 10px;
+ display: flex;
+
+ span{
+ cursor: pointer;
+ }
+
+ .empty-checkbox{
+ display: block;
+ margin-right: 5px;
+ margin-top: 0;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+.sub-nav{
+ justify-content: flex-end;
+
+ .butt{
+ margin-left: 10px;
+ }
+
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.ts
new file mode 100644
index 0000000..0d02230
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/configuration.component.ts
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {Component, OnInit, Output, EventEmitter, Inject, ViewChild, HostListener} from '@angular/core';
+import { ValidatorFn, FormControl } from '@angular/forms';
+import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { ToastrService } from 'ngx-toastr';
+import {RolesGroupsService, HealthStatusService, ApplicationSecurityService, AppRoutingService} from '../../core/services';
+import {CheckUtils, SortUtils} from '../../core/util';
+import { DICTIONARY } from '../../../dictionary/global.dictionary';
+import {ProgressBarService} from '../../core/services/progress-bar.service';
+import {ConfirmationDialogComponent, ConfirmationDialogType} from '../../shared/modal-dialog/confirmation-dialog';
+import {MatTabChangeEvent} from '@angular/material/tabs';
+import {Router} from '@angular/router';
+import {ConfigurationService} from '../../core/services/configutration.service';
+import {NotificationDialogComponent} from '../../shared/modal-dialog/notification-dialog';
+import {logger} from 'codelyzer/util/logger';
+
+@Component({
+ selector: 'datalab-configuration',
+ templateUrl: './configuration.component.html',
+ styleUrls: ['./configuration.component.scss']
+})
+export class ConfigurationComponent implements OnInit {
+ private healthStatus: any;
+ editorOptions = {theme: 'vs-dark', language: 'javascript'};
+ code: string = 'function x() {console.log("Hello world!");}';
+ text: any;
+ public activeTab: number = 0;
+ selvServiceSource;
+ provisioningSource;
+ billingSource;
+ serverConfigs = {
+ selvServiceSource: {},
+ provisioningSource: {},
+ billingSource: {},
+ };
+ public services = [
+ {label: 'Self service', selected: false},
+ {label: 'Provisioning', selected: false},
+ {label: 'Billing', selected: false},
+ ];
+
+ private confirmMessages = {
+ restartService: 'Restarting services will make DataLab unavailable for some time.',
+ discardChanges: 'Discard all unsaved changes.',
+ saveChanges: 'After you save changes you need to restart service.',
+ };
+
+ @HostListener('window:keydown', ['$event'])
+ onKeyDown(event: KeyboardEvent) {
+ if ((event.metaKey || event.ctrlKey) && event.key === 's' && this.activeTab !== 0 && this.router.url === '/configuration') {
+ this.action('save');
+ event.preventDefault();
+ }
+ }
+
+ constructor(
+ private healthStatusService: HealthStatusService,
+ private appRoutingService: AppRoutingService,
+ private configurationService: ConfigurationService,
+ private router: Router,
+ public dialog: MatDialog
+ ) { }
+
+ ngOnInit() {
+ this.getEnvironmentHealthStatus();
+ this.getSettings();
+ }
+ private getEnvironmentHealthStatus() {
+ this.healthStatusService.getEnvironmentHealthStatus()
+ .subscribe((result: any) => {
+ this.healthStatus = result;
+ if (!this.healthStatus.admin && !this.healthStatus.projectAdmin) {
+ this.appRoutingService.redirectToHomePage();
+ } else {
+
+ }
+ }
+ );
+ }
+
+ refresh() {
+ console.log('Refresh');
+ }
+
+ action(action) {
+ this.dialog.open(SettingsConfirmationDialogComponent, { data: {
+ action: action, message: action === 'discard' ? this.confirmMessages.discardChanges : this.confirmMessages.saveChanges
+ }, panelClass: 'modal-sm' })
+ .afterClosed().subscribe(result => {
+ console.log(action, this.activeTab);
+ });
+ }
+
+ getSettings() {
+ this.configurationService.getSelvServiceSettings().subscribe(v => {
+ this.serverConfigs.billingSource = v;
+ this.serverConfigs.selvServiceSource = v;
+ this.serverConfigs.provisioningSource = v;
+ this.selvServiceSource = v;
+ this.provisioningSource = v;
+ this.billingSource = v;
+ }
+ );
+ }
+
+ public tabChanged(tabChangeEvent: MatTabChangeEvent): void {
+ this.activeTab = tabChangeEvent.index;
+ if (this.selvServiceSource !== this.serverConfigs.selvServiceSource) {
+ this.dialog.open(SettingsConfirmationDialogComponent, { data: {
+ action: 'Was changed'
+ }, panelClass: 'modal-sm' })
+ .afterClosed().subscribe(result => {
+ if (result) {
+ this.serverConfigs.selvServiceSource = this.selvServiceSource;
+ } else {
+ this.selvServiceSource = this.serverConfigs.selvServiceSource;
+ }
+ });
+ }
+ }
+
+ toggleSetings(service) {
+ service.selected = !service.selected
+ }
+
+ restartServices() {
+ this.dialog.open(SettingsConfirmationDialogComponent, { data: {
+ action: 'Restart services', message: this.confirmMessages.restartService
+ }, panelClass: 'modal-sm' })
+ .afterClosed().subscribe(result => {
+ });
+ }
+}
+
+@Component({
+ selector: 'confirm-dialog',
+ template: `
+ <div id="dialog-box">
+ <div class="dialog-header">
+ <h4 class="modal-title"><span class="capitalize">{{ data.action }}</span> <span *ngIf="data.action === 'save' || data.action === 'discard'"> changes</span></h4>
+ <button type="button" class="close" (click)="dialogRef.close()">×</button>
+ </div>
+
+ <div mat-dialog-content class="content">
+ {{data.message}}
+ </div>
+ <div class="text-center ">
+ <p class="strong">Do you want to proceed?</p>
+ </div>
+ <div class="text-center m-top-20 pb-25">
+ <button type="button" class="butt" mat-raised-button (click)="dialogRef.close()">No</button>
+ <button type="button" class="butt butt-success" mat-raised-button (click)="dialogRef.close(true)">Yes</button>
+ </div>
+ </div>
+ `,
+ styles: [
+ `
+ .content { color: #718ba6; padding: 20px 50px; font-size: 14px; font-weight: 400; margin: 0; }
+ .info { color: #35afd5; }
+ .info .confirm-dialog { color: #607D8B; }
+ header { display: flex; justify-content: space-between; color: #607D8B; }
+ header h4 i { vertical-align: bottom; }
+ header a i { font-size: 20px; }
+ header a:hover i { color: #35afd5; cursor: pointer; }
+ .content{padding: 35px 30px 30px 30px;}
+ .plur { font-style: normal; }
+ .scrolling-content{overflow-y: auto; max-height: 200px; }
+ .cluster { width: 50%; text-align: left;}
+ .status { width: 50%;text-align: left;}
+ .label { font-size: 15px; font-weight: 500; font-family: "Open Sans",sans-serif;}
+ .node { font-weight: 300;}
+ .resource-name { width: 40%;text-align: left; padding: 10px 0;line-height: 26px;}
+ .clusters-list { width: 60%;text-align: left; padding: 10px 0;line-height: 26px;}
+ .clusters-list-item { width: 100%;text-align: left;display: flex}
+ .resource-list{max-width: 100%; margin: 0 auto;margin-top: 20px; }
+ .resource-list-header{display: flex; font-weight: 600; font-size: 16px;height: 48px; border-top: 1px solid #edf1f5; border-bottom: 1px solid #edf1f5; padding: 0 20px;}
+ .resource-list-row{display: flex; border-bottom: 1px solid #edf1f5;padding: 0 20px;}
+ .confirm-resource-terminating{text-align: left; padding: 10px 20px;}
+ .confirm-message{color: #ef5c4b;font-size: 13px;min-height: 18px; text-align: center; padding-top: 20px}
+ .checkbox{margin-right: 5px;vertical-align: middle; margin-bottom: 3px;}
+ label{cursor: pointer}
+ .bottom-message{padding-top: 15px;}
+ .table-header{padding-bottom: 10px;}`
+ ]
+})
+
+export class SettingsConfirmationDialogComponent {
+ constructor(
+ public dialogRef: MatDialogRef<SettingsConfirmationDialogComponent>,
+ @Inject(MAT_DIALOG_DATA) public data: any
+ ) {
+
+ }
+}
+
+
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/configuration/index.ts b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/index.ts
new file mode 100644
index 0000000..d38f2c5
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/configuration/index.ts
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MaterialModule } from '../../shared/material.module';
+import { FormControlsModule } from '../../shared/form-controls';
+import {InformMessageModule} from '../../shared/inform-message';
+import {ConfigurationComponent, SettingsConfirmationDialogComponent} from './configuration.component';
+import {AceEditorModule} from 'ng2-ace-editor';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ FormsModule,
+ ReactiveFormsModule,
+ MaterialModule,
+ FormControlsModule,
+ InformMessageModule,
+ AceEditorModule
+ ],
+ declarations: [ConfigurationComponent, SettingsConfirmationDialogComponent],
+ entryComponents: [SettingsConfirmationDialogComponent],
+ exports: [ConfigurationComponent]
+})
+export class ConfigurationModule { }
diff --git a/services/self-service/src/main/resources/webapp/src/app/app.module.ts b/services/self-service/src/main/resources/webapp/src/app/app.module.ts
index 9559cf1..e6e887c 100644
--- a/services/self-service/src/main/resources/webapp/src/app/app.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/app.module.ts
@@ -41,6 +41,7 @@
import { SwaggerAPIModule } from './swagger';
import {ReportsModule} from './reports/reports.module';
import {LocalizationService} from './core/services/localization.service';
+import {AceEditorModule} from 'ng2-ace-editor';
LocalizationService.registerCulture(window.navigator.language);
@@ -66,7 +67,8 @@
RouterModule,
AppRoutingModule,
CoreModule.forRoot(),
- ToastrModule.forRoot({ timeOut: 10000 })
+ ToastrModule.forRoot({ timeOut: 10000 }),
+ AceEditorModule
],
providers: [{
provide: LocationStrategy,
diff --git a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
index 461c5f7..4b7d06c 100644
--- a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
@@ -34,6 +34,7 @@
import { SwaggerComponent } from './swagger/swagger.component';
import { AuthorizationGuard, CheckParamsGuard, CloudProviderGuard, AdminGuard, AuditGuard } from './core/services';
import {AuditComponent} from './reports/audit/audit.component';
+import {ConfigurationComponent} from './administration/configuration/configuration.component';
import {OdahuComponent} from './administration/odahu/odahu.component';
const routes: Routes = [{
@@ -74,6 +75,11 @@
component: ManagementComponent,
canActivate: [AuthorizationGuard, AdminGuard]
}, {
+ path: 'configuration',
+ component: ConfigurationComponent,
+ canActivate: [AuthorizationGuard, AdminGuard]
+ },
+ {
path: 'swagger',
component: SwaggerComponent,
canActivate: [AuthorizationGuard]
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
index ae6221a..685d0ef 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
@@ -48,6 +48,9 @@
import { ErrorInterceptor } from './interceptors/error.interceptor';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+
+import {ConfigurationService} from './services/configutration.service';
+
import {AuditGuard, OdahuDeploymentService} from './services';
@NgModule({
@@ -85,8 +88,8 @@
ProjectService,
EndpointService,
UserAccessKeyService,
+ ConfigurationService,
OdahuDeploymentService,
-
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: [] },
{
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/configutration.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/configutration.service.ts
new file mode 100644
index 0000000..8cc8406
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/core/services/configutration.service.ts
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import {Observable, of} from 'rxjs';
+import { map, catchError } from 'rxjs/operators';
+
+import { ApplicationServiceFacade } from './applicationServiceFacade.service';
+import { ErrorUtils } from '../util';
+
+@Injectable()
+export class ConfigurationService {
+ constructor(private applicationServiceFacade: ApplicationServiceFacade) { }
+
+ public getSelvServiceSettings(): Observable<{}> {
+ return of(`# *****************************************************************************
+ #
+ # Licensed to the Apache Software Foundation (ASF) under one
+ # or more contributor license agreements. See the NOTICE file
+ # distributed with this work for additional information
+ # regarding copyright ownership. The ASF licenses this file
+ # to you under the Apache License, Version 2.0 (the
+ # "License"); you may not use this file except in compliance
+ # with the License. You may obtain a copy of the License at
+ #
+ # http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing,
+ # software distributed under the License is distributed on an
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ # KIND, either express or implied. See the License for the
+ # specific language governing permissions and limitations
+ # under the License.
+ #
+ # ******************************************************************************
+
+ <#include "/infrastructure-provisioning/src/ssn/templates/ssn.yml">
+
+ # Minimum and maximum number of slave EMR instances than could be created
+ minEmrInstanceCount: 2
+ maxEmrInstanceCount: 14
+ # Minimum and maximum percentage cost for slave EMR spot instances biding
+ minEmrSpotInstanceBidPct: 20
+ maxEmrSpotInstanceBidPct: 90
+
+ # Maximum length for gcp user name (due to gcp restrictions)
+ maxUserNameLength: 10
+ # Minimum and maximum number of slave Dataproc instances that could be created
+ minInstanceCount: 3
+ maxInstanceCount: 15
+ minDataprocPreemptibleCount: 0
+ gcpOuauth2AuthenticationEnabled: false
+
+ # Boundaries for Spark cluster creation
+ minSparkInstanceCount: 2
+ maxSparkInstanceCount: 14
+
+ # Timeout for check the status of environment via provisioning service
+ checkEnvStatusTimeout: 5m
+
+ # Restrict access to DataLab features using roles policy
+ rolePolicyEnabled: true
+ # Default access to DataLab features using roles policy
+ roleDefaultAccess: false
+
+ # Set to true to enable the scheduler of billing report.
+ billingSchedulerEnabled: true
+ billingPort: 8088
+ # Set to true to enable audit
+ auditEnabled: true
+ # Name of configuration file for billing report.
+ <#if DEV_MODE == "true">
+ billingConfFile: \${sys['user.dir']}/../billing/billing.yml
+ <#else>
+ billingConfFile: \${DATALAB_CONF_DIR}/billing.yml
+ </#if>
+
+ ssnInstanceSize: <SSN_INSTANCE_SIZE>
+
+ serviceBaseName: SERVICE_BASE_NAME
+ os: OPERATION_SYSTEM
+ server:
+ requestLog:
+ appenders:
+ - type: file
+ currentLogFilename: \${LOG_ROOT_DIR}/ssn/request-selfservice.log
+ archive: true
+ archivedLogFilenamePattern: \${LOG_ROOT_DIR}/ssn/request-selfservice-%d{yyyy-MM-dd}.log.gz
+ archivedFileCount: 10
+ rootPath: "/api"
+ applicationConnectors:
+ # - type: http
+ # port: 8080
+ - type: https
+ port: 8443
+ certAlias: ssn
+ validateCerts: false
+ keyStorePath: \${KEY_STORE_PATH}
+ keyStorePassword: \${KEY_STORE_PASSWORD}
+ trustStorePath: \${TRUST_STORE_PATH}
+ trustStorePassword: \${TRUST_STORE_PASSWORD}
+ adminConnectors:
+ # - type: http
+ # port: 8081
+ - type: https
+ port: 8444
+ certAlias: ssn
+ validateCerts: false
+ keyStorePath: \${KEY_STORE_PATH}
+ keyStorePassword: \${KEY_STORE_PASSWORD}
+ trustStorePath: \${TRUST_STORE_PATH}
+ trustStorePassword: \${TRUST_STORE_PASSWORD}
+
+ mongoMigrationEnabled: false
+
+ logging:
+ level: INFO
+ loggers:
+ com.epam: INFO
+ org.apache.guacamole: DEBUG
+ com.novemberain: ERROR
+ appenders:
+ <#if DEV_MODE == "true">
+ - type: console
+ </#if>
+ - type: file
+ currentLogFilename: \${LOG_ROOT_DIR}/ssn/selfservice.log
+ archive: true
+ archivedLogFilenamePattern: \${LOG_ROOT_DIR}/ssn/selfservice-%d{yyyy-MM-dd}.log.gz
+ archivedFileCount: 10
+
+ mavenSearchService:
+ protocol: http
+ host: search.maven.org
+ port: 80
+ jerseyClient:
+ timeout: 5s
+ connectionTimeout: 5s
+
+ schedulers:
+ inactivity:
+ enabled: false
+ cron: "0 0 0/2 ? * * *"
+ checkInfrastructureStatusScheduler:
+ enabled: true
+ cron: "0 0/15 * ? * *"
+ startComputationalScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ stopComputationalScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ startExploratoryScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ stopExploratoryScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ terminateComputationalScheduler:
+ enabled: true
+ cron: "*/20 * * ? * * *"
+ checkQuoteScheduler:
+ enabled: true
+ cron: "0 2/15 * ? * *"
+ checkUserQuoteScheduler:
+ enabled: false
+ cron: "0 0 * ? * * *"
+ checkProjectQuoteScheduler:
+ enabled: true
+ cron: "0 4/15 * ? * *"
+ checkEndpointStatusScheduler:
+ enabled: true
+ cron: "0 6/15 * ? * *"
+ billingScheduler:
+ enabled: true
+ cron: "0 0/15 * ? * *"
+
+
+ guacamole:
+ connectionProtocol: ssh
+ serverPort: 4822
+ port: 22
+ username: datalab-user
+
+ keycloakConfiguration:
+ redirectUri: http://localhost:4200/
+ realm: DLAB_bhliva
+ bearer-only: true
+ auth-server-url: http://52.11.45.11:8080/auth
+ ssl-required: none
+ register-node-at-startup: true
+ register-node-period: 600
+ resource: sss
+ credentials:
+ secret: cf5a484b-039b-4161-8707-ad65c0f25962
+
+ jerseyClient:
+ minThreads: 1
+ maxThreads: 128
+ workQueueSize: 8
+ gzipEnabled: true
+ gzipEnabledForRequests: false
+ chunkedEncodingEnabled: true
+ `)
+ }
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
index aab2b07..27e1e90 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/form-controls/multi-level-select-dropdown/multi-level-select-dropdown.component.scss
@@ -120,9 +120,11 @@
padding: 0;
margin: 0;
}
+
.role-item{
padding-left: 30px;
}
+
.role-cloud-item{
padding-left: 60px;
}
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
index 89de5e0..8a64a8c 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
@@ -99,6 +99,12 @@
<span *ngIf="isExpanded; else env">Environment Management</span>
<ng-template #env><i class="material-icons">settings</i></ng-template>
</a>
+ <a class="sub-nav-item" [style.margin-left.px]="isExpanded ? '30' : '0'"
+ [routerLink]="['/configuration']" [routerLinkActive]="['active']"
+ [routerLinkActiveOptions]="{exact:true}">
+ <span *ngIf="isExpanded; else env">Configuration</span>
+ <ng-template #env><i class="material-icons">settings</i></ng-template>
+ </a>
</a>
<a class="nav-item has-children" *ngIf="healthStatus?.billingEnabled || healthStatus?.auditEnabled">
<span *ngIf="isExpanded">Reports</span>
diff --git a/services/self-service/src/main/resources/webapp/src/assets/styles/_general.scss b/services/self-service/src/main/resources/webapp/src/assets/styles/_general.scss
index 9234c6a..e27d33f 100644
--- a/services/self-service/src/main/resources/webapp/src/assets/styles/_general.scss
+++ b/services/self-service/src/main/resources/webapp/src/assets/styles/_general.scss
@@ -39,6 +39,7 @@
.pr-3{padding-right: 3px}
.pb-50 {padding-bottom: 50px;}
+.pb-25 {padding-bottom: 25px;}
.pb-10 {padding-bottom: 10px;}
.txt-r {text-align: right }
diff --git a/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss b/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
index fc3887c..73ed337 100644
--- a/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
+++ b/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
@@ -1026,6 +1026,28 @@
}
}
}
+.configuration-wrapper{
+ box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
+ .mat-tab-header{
+ margin: 0 30px;
+ }
+ .mat-tab-label{
+ .mat-tab-label-content{
+ font-family: "Open Sans", sans-serif;
+ font-weight: 600;
+ font-size: 13px;
+ }
+ }
+
+ .ace-monokai{
+ height: calc(100%);
+ }
+
+ .ace_scrollbar{
+
+ }
+}
+
diff --git a/services/self-service/src/main/resources/webapp/src/styles.scss b/services/self-service/src/main/resources/webapp/src/styles.scss
index c09ccd9..3c5d948 100644
--- a/services/self-service/src/main/resources/webapp/src/styles.scss
+++ b/services/self-service/src/main/resources/webapp/src/styles.scss
@@ -382,12 +382,13 @@
text-align: center;
}
}
-#scrolling, .scrolling{
+#scrolling, .scrolling, ace_scrollbar{
scrollbar-width: thin;
}
#scrolling::-webkit-scrollbar,
.scrolling::-webkit-scrollbar,
+.ace_scrollbar::-webkit-scrollbar,
.list-selected mat-chip-list .mat-chip-list-wrapper::-webkit-scrollbar {
width: 5px;
height: 5px;
@@ -395,6 +396,7 @@
#scrolling::-webkit-scrollbar-track,
.scrolling::-webkit-scrollbar-track,
+.ace_scrollbar::-webkit-scrollbar-track,
.list-selected mat-chip-list .mat-chip-list-wrapper::-webkit-scrollbar-track {
box-shadow: none;
-webkit-box-shadow: none;
@@ -403,6 +405,7 @@
#scrolling::-webkit-scrollbar-thumb,
.scrolling::-webkit-scrollbar-thumb,
+.ace_scrollbar::-webkit-scrollbar-thumb,
.list-selected mat-chip-list .mat-chip-list-wrapper::-webkit-scrollbar-thumb {
background-color: #f6fafe;
background-color: rgba(0, 0, 0, 0.4);