diff options
author | Jorge Hernandez <jh1730@att.com> | 2018-07-13 14:01:05 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@onap.org> | 2018-07-13 14:01:05 +0000 |
commit | 92eb2fc652a408819b27a3209bedda23db96bda4 (patch) | |
tree | 978d67840ccd01bc7475761f592224292ead547e /client/client-deployment/src/main | |
parent | 2e32896a4e98fe2047b2e6356d4e9d7dbd3a0252 (diff) | |
parent | c6d74e6cee17405b9d26506b06259ccb2f38c737 (diff) |
Merge "Adding client deployment module to apex-pdp"
Diffstat (limited to 'client/client-deployment/src/main')
21 files changed, 1830 insertions, 0 deletions
diff --git a/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRest.java b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRest.java new file mode 100644 index 000000000..b6ed37d80 --- /dev/null +++ b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRest.java @@ -0,0 +1,84 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.apex.client.deployment.rest; + +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.onap.policy.apex.model.utilities.Assertions; +import org.slf4j.ext.XLogger; +import org.slf4j.ext.XLoggerFactory; + +/** + * This class is used to launch the services. It creates a Grizzly embedded web server and runs the services. + */ +public class ApexDeploymentRest { + // Logger for this class + private static final XLogger logger = XLoggerFactory.getXLogger(ApexDeploymentRest.class); + + // The HTTP server exposing JAX-RS resources defined in this application. + private HttpServer server; + + /** + * Starts the HTTP server for the Apex services client on the default base URI and with the default REST packages + */ + public ApexDeploymentRest() { + this(new ApexDeploymentRestParameters()); + } + + /** + * Starts the HTTP server for the Apex services client + * + * @param parameters: The Apex parameters to use to start the server + */ + public ApexDeploymentRest(final ApexDeploymentRestParameters parameters) { + Assertions.argumentNotNull(parameters, "parameters may not be null"); + + logger.debug("Apex services RESTful client starting . . ."); + + // Create a resource configuration that scans for JAX-RS resources and providers + // in org.onap.policy.apex.client.deployment.rest package + final ResourceConfig rc = new ResourceConfig().packages(parameters.getRESTPackages()); + + // Add MultiPartFeature class for jersey-media-multipart + rc.register(MultiPartFeature.class); + + // create and start a new instance of grizzly http server + // exposing the Jersey application at BASE_URI + server = GrizzlyHttpServerFactory.createHttpServer(parameters.getBaseURI(), rc); + + // Add static content + server.getServerConfiguration().addHttpHandler(new org.glassfish.grizzly.http.server.CLStaticHttpHandler( + ApexDeploymentRestMain.class.getClassLoader(), "/webapp/"), parameters.getStaticPath()); + + logger.debug("Apex services RESTful client started"); + } + + /** + * Shut down the web server + */ + public void shutdown() { + logger.debug("Apex services RESTful client shutting down . . ."); + server.shutdown(); + logger.debug("Apex services RESTful client shut down"); + } +} diff --git a/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestMain.java b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestMain.java new file mode 100644 index 000000000..6e7220487 --- /dev/null +++ b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestMain.java @@ -0,0 +1,192 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.apex.client.deployment.rest; + +import java.io.PrintStream; + +/** + * User: ewatkmi Date: 31 Jul 2017 + */ +public class ApexDeploymentRestMain { + // Services state + public enum ServicesState { + STOPPED, READY, INITIALIZING, RUNNING + }; + + private ServicesState state = ServicesState.STOPPED; + + // The parameters for the client + private ApexDeploymentRestParameters parameters = null; + + // Output and error streams for messages + private final PrintStream outStream; + + // The Apex services client this class is running + private ApexDeploymentRest apexDeploymentRest = null; + + /** + * Main method, main entry point for command + * + * @param args The command line arguments for the client + */ + public static void main(final String[] args) { + try { + final ApexDeploymentRestMain restMain = new ApexDeploymentRestMain(args, System.out); + restMain.init(); + } catch (final Exception e) { + System.err.println(e.getMessage()); + } + } + + /** + * Constructor, kicks off the rest service + * + * @param args The command line arguments for the RESTful service + * @param outStream The stream for output messages + */ + public ApexDeploymentRestMain(final String[] args, final PrintStream outStream) { + // Save the streams for output and error + this.outStream = outStream; + + // Client parameter parsing + final ApexDeploymentRestParameterParser parser = new ApexDeploymentRestParameterParser(); + + try { + // Get and check the parameters + parameters = parser.parse(args); + } catch (final ApexDeploymentRestParameterException e) { + throw new ApexDeploymentRestParameterException( + "Apex Services REST endpoint (" + this.toString() + ") parameter error, " + e.getMessage() + '\n' + + parser.getHelp(ApexDeploymentRestMain.class.getCanonicalName())); + } + + if (parameters.isHelpSet()) { + throw new ApexDeploymentRestParameterException( + parser.getHelp(ApexDeploymentRestMain.class.getCanonicalName())); + } + + // Validate the parameters + final String validationMessage = parameters.validate(); + if (validationMessage.length() > 0) { + throw new ApexDeploymentRestParameterException( + "Apex Services REST endpoint (" + this.toString() + ") parameters invalid, " + validationMessage + + '\n' + parser.getHelp(ApexDeploymentRestMain.class.getCanonicalName())); + } + + state = ServicesState.READY; + } + + /** + * Initialize the rest service + */ + public void init() { + outStream.println("Apex Services REST endpoint (" + this.toString() + ") starting at " + + parameters.getBaseURI().toString() + " . . ."); + + try { + state = ServicesState.INITIALIZING; + + // Start the REST service + apexDeploymentRest = new ApexDeploymentRest(parameters); + + // Add a shutdown hook to shut down the rest services when the process is exiting + Runtime.getRuntime().addShutdownHook(new Thread(new ApexServicesShutdownHook())); + + state = ServicesState.RUNNING; + + if (parameters.getTimeToLive() == ApexDeploymentRestParameters.INFINITY_TIME_TO_LIVE) { + outStream.println("Apex Services REST endpoint (" + this.toString() + ") started at " + + parameters.getBaseURI().toString()); + } else { + outStream.println("Apex Services REST endpoint (" + this.toString() + ") started"); + } + + // Find out how long is left to wait + long timeRemaining = parameters.getTimeToLive(); + while (timeRemaining == ApexDeploymentRestParameters.INFINITY_TIME_TO_LIVE || timeRemaining > 0) { + // decrement the time to live in the non-infinity case + if (timeRemaining > 0) { + timeRemaining--; + } + + // Wait for a second + Thread.sleep(1000); + } + } catch (final Exception e) { + outStream.println( + "Apex Services REST endpoint (" + this.toString() + ") failed at with error: " + e.getMessage()); + } finally { + if (apexDeploymentRest != null) { + apexDeploymentRest.shutdown(); + apexDeploymentRest = null; + } + state = ServicesState.STOPPED; + } + + } + + /** + * Get services state. + * + * @return the service state + */ + public ServicesState getState() { + return state; + } + + @Override + public String toString() { + final StringBuilder ret = new StringBuilder(); + ret.append(this.getClass().getSimpleName()).append(": Config=[").append(this.parameters).append("], State=") + .append(this.getState()); + return ret.toString(); + } + + /** + * Explicitly shut down the services + */ + public void shutdown() { + if (apexDeploymentRest != null) { + outStream.println("Apex Services REST endpoint (" + this.toString() + ") shutting down"); + apexDeploymentRest.shutdown(); + } + state = ServicesState.STOPPED; + outStream.println("Apex Services REST endpoint (" + this.toString() + ") shut down"); + } + + /** + * This class is a shutdown hook for the Apex services command + */ + private class ApexServicesShutdownHook implements Runnable { + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + if (apexDeploymentRest != null) { + apexDeploymentRest.shutdown(); + } + } + } + +} diff --git a/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameterException.java b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameterException.java new file mode 100644 index 000000000..87785b7f3 --- /dev/null +++ b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameterException.java @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.apex.client.deployment.rest; + +/** + * A run time exception used to report parsing and parameter input errors. + * + * User: ewatkmi Date: 31 Jul 2017 + */ +public class ApexDeploymentRestParameterException extends IllegalArgumentException { + private static final long serialVersionUID = 6520231162404452427L; + + /** + * Create an ApexServicesRestParameterException with a message + * + * @param message the message + */ + public ApexDeploymentRestParameterException(final String message) { + super(message); + } + + /** + * Create an ApexServicesRestParameterException with a message and an exception. + * + * @param message the message + * @param throwable The exception that caused the exception + */ + public ApexDeploymentRestParameterException(final String message, final Throwable throwable) { + super(message, throwable); + } +} diff --git a/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameterParser.java b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameterParser.java new file mode 100644 index 000000000..35ad9cdde --- /dev/null +++ b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameterParser.java @@ -0,0 +1,116 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.apex.client.deployment.rest; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * This class reads and handles command line parameters to the Apex RESTful services + * + * User: ewatkmi Date: 31 Jul 2017 + */ +public class ApexDeploymentRestParameterParser { + // Apache Commons CLI options + Options options; + + /** + * Construct the options for the CLI RESTful services + */ + public ApexDeploymentRestParameterParser() { + options = new Options(); + options.addOption("h", "help", false, "outputs the usage of this command"); + options.addOption(Option.builder("p").longOpt("port").desc("port to use for the Apex Services REST calls") + .hasArg().argName("PORT").required(false).type(Number.class).build()); + options.addOption(Option.builder("t").longOpt("time-to-live") + .desc("the amount of time in seconds that the server will run for before terminating").hasArg() + .argName("TIME_TO_LIVE").required(false).type(Number.class).build()); + } + + /** + * Parse the command line options + * + * @param args the arguments + * @return the parsed arguments + */ + public ApexDeploymentRestParameters parse(final String[] args) { + CommandLine commandLine = null; + try { + commandLine = new DefaultParser().parse(options, args); + } catch (final ParseException e) { + throw new ApexDeploymentRestParameterException( + "invalid command line arguments specified : " + e.getMessage()); + } + + final ApexDeploymentRestParameters parameters = new ApexDeploymentRestParameters(); + final String[] remainingArgs = commandLine.getArgs(); + + if (commandLine.getArgs().length > 0) { + throw new ApexDeploymentRestParameterException( + "too many command line arguments specified : " + Arrays.toString(remainingArgs)); + } + + if (commandLine.hasOption('h')) { + parameters.setHelp(true); + } + try { + if (commandLine.hasOption('p')) { + parameters.setRESTPort(((Number) commandLine.getParsedOptionValue("port")).intValue()); + } + } catch (final ParseException e) { + throw new ApexDeploymentRestParameterException("error parsing argument \"port\" :" + e.getMessage(), e); + } + try { + if (commandLine.hasOption('t')) { + parameters.setTimeToLive(((Number) commandLine.getParsedOptionValue("time-to-live")).longValue()); + } + } catch (final ParseException e) { + throw new ApexDeploymentRestParameterException("error parsing argument \"time-to-live\" :" + e.getMessage(), + e); + } + + return parameters; + } + + /** + * Get help information + * + * @param mainClassName the main class name for the help output + * @return help string + */ + public String getHelp(final String mainClassName) { + final StringWriter stringWriter = new StringWriter(); + final PrintWriter stringPrintWriter = new PrintWriter(stringWriter); + + final HelpFormatter helpFormatter = new HelpFormatter(); + helpFormatter.printHelp(stringPrintWriter, 120, mainClassName + " [options...] ", "", options, 0, 0, ""); + + return stringWriter.toString(); + } +} diff --git a/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameters.java b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameters.java new file mode 100644 index 000000000..6151506b8 --- /dev/null +++ b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestParameters.java @@ -0,0 +1,115 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.apex.client.deployment.rest; + +import java.net.URI; + +/** + * This class reads and handles command line parameters to the Apex RESTful services + * + * User: ewatkmi Date: 31 Jul 2017 + */ +public class ApexDeploymentRestParameters { + public static final int DEFAULT_REST_PORT = 18989; + public static final int INFINITY_TIME_TO_LIVE = -1; + + // Base URI the HTTP server will listen on + private static final String DEFAULT_SERVER_URI_ROOT = "http://localhost:"; + private static final String DEFAULT_REST_PATH = "/apexservices/"; + private static final String DEFAULT_STATIC_PATH = "/"; + + // Package that will field REST requests + public static final String[] DEFAULT_PACKAGES = + new String[] { "com.ericsson.apex.services.client.deployment.rest" }; + + // The services parameters + private boolean helpSet = false; + private int restPort = DEFAULT_REST_PORT; + private long timeToLive = INFINITY_TIME_TO_LIVE; + + public String validate() { + String validationMessage = ""; + validationMessage += validatePort(); + validationMessage += validateTimeToLive(); + + return validationMessage; + } + + public URI getBaseURI() { + return URI.create(DEFAULT_SERVER_URI_ROOT + restPort + DEFAULT_REST_PATH); + } + + public String[] getRESTPackages() { + return DEFAULT_PACKAGES; + } + + public String getStaticPath() { + return DEFAULT_STATIC_PATH; + } + + private String validatePort() { + if (restPort < 1024 || restPort > 65535) { + return "port must be greater than 1023 and less than 65536\n"; + } else { + return ""; + } + } + + private String validateTimeToLive() { + if (timeToLive < -1) { + return "time to live must be greater than -1 (set to -1 to wait forever)\n"; + } else { + return ""; + } + } + + public boolean isHelpSet() { + return helpSet; + } + + public void setHelp(final boolean helpSet) { + this.helpSet = helpSet; + } + + public int getRESTPort() { + return restPort; + } + + public void setRESTPort(final int restPort) { + this.restPort = restPort; + } + + public long getTimeToLive() { + return timeToLive; + } + + public void setTimeToLive(final long timeToLive) { + this.timeToLive = timeToLive; + } + + @Override + public String toString() { + final StringBuilder ret = new StringBuilder(); + ret.append(this.getClass().getSimpleName()).append(": URI=").append(this.getBaseURI()).append(", TTL=") + .append(this.getTimeToLive()).append("sec"); + return ret.toString(); + } +} diff --git a/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestResource.java b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestResource.java new file mode 100644 index 000000000..810b59cc1 --- /dev/null +++ b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ApexDeploymentRestResource.java @@ -0,0 +1,146 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.apex.client.deployment.rest; + +import com.google.gson.JsonObject; + +import java.io.InputStream; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.onap.policy.apex.core.deployment.ApexDeploymentException; +import org.onap.policy.apex.core.deployment.EngineServiceFacade; +import org.slf4j.ext.XLogger; +import org.slf4j.ext.XLoggerFactory; + +/** + * The class represents the root resource exposed at the base URL<br> + * The url to access this resource would be in the form {@code <baseURL>/rest/....} <br> + * For example: a GET request to the following URL + * {@code http://localhost:18989/apexservices/rest/?hostName=localhost&port=12345} + * + * <b>Note:</b> An allocated {@code hostName} and {@code port} query parameter must be included in all requests. + * Datasets for different {@code hostName} are completely isolated from one another. + * + */ +@Path("deployment/") +@Produces({ MediaType.APPLICATION_JSON }) +@Consumes({ MediaType.APPLICATION_JSON }) + +public class ApexDeploymentRestResource { + // Get a reference to the logger + private static final XLogger LOGGER = XLoggerFactory.getXLogger(ApexDeploymentRestResource.class); + + /** + * Constructor, a new resource director is created for each request. + */ + public ApexDeploymentRestResource() {} + + /** + * Query the engine service for data + * + * @param hostName the host name of the engine service to connect to. + * @param port the port number of the engine service to connect to. + * @return a Response object containing the engines service, status and context data in JSON + */ + @GET + public Response createSession(@QueryParam("hostName") final String hostName, @QueryParam("port") final int port) { + final String host = hostName + ":" + port; + final EngineServiceFacade engineServiceFacade = new EngineServiceFacade(hostName, port); + + try { + engineServiceFacade.init(); + } catch (final ApexDeploymentException e) { + final String errorMessage = "Error connecting to Apex Engine Service at " + host; + LOGGER.warn(errorMessage + "<br>", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage()) + .build(); + } + + final JsonObject responseObject = new JsonObject(); + + // Engine Service data + responseObject.addProperty("engine_id", engineServiceFacade.getKey().getID()); + responseObject.addProperty("model_id", + engineServiceFacade.getApexModelKey() != null ? engineServiceFacade.getApexModelKey().getID() + : "Not Set"); + responseObject.addProperty("server", hostName); + responseObject.addProperty("port", Integer.toString(port)); + + return Response.ok(responseObject.toString(), MediaType.APPLICATION_JSON).build(); + } + + /** + * Upload a model. + * + * @param hostName the host name of the engine service to connect to. + * @param port the port number of the engine service to connect to. + * @param uploadedInputStream input stream + * @param fileDetail details on the file + * @param ignoreConflicts conflict policy + * @param forceUpdate update policy + * @return a response object in plain text confirming the upload was successful + */ + @POST + @Path("modelupload/") + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Response modelUpload(@FormDataParam("hostName") final String hostName, @FormDataParam("port") final int port, + @FormDataParam("file") final InputStream uploadedInputStream, + @FormDataParam("file") final FormDataContentDisposition fileDetail, + @FormDataParam("ignoreConflicts") final boolean ignoreConflicts, + @FormDataParam("forceUpdate") final boolean forceUpdate) { + final EngineServiceFacade engineServiceFacade = new EngineServiceFacade(hostName, port); + + try { + engineServiceFacade.init(); + } catch (final ApexDeploymentException e) { + final String errorMessage = "Error connecting to Apex Engine Service at " + hostName + ":" + port; + LOGGER.warn(errorMessage + "<br>", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage()) + .build(); + } + + try { + engineServiceFacade.deployModel(fileDetail.getFileName(), uploadedInputStream, ignoreConflicts, + forceUpdate); + } catch (final Exception e) { + LOGGER.warn("Error updating model on engine service " + engineServiceFacade.getKey().getID(), e); + final String errorMessage = + "Error updating model on engine service " + engineServiceFacade.getKey().getID(); + LOGGER.warn(errorMessage + "<br>", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage + "\n" + e.getMessage()) + .build(); + } + + return Response.ok("Model " + fileDetail.getFileName() + " deployed on engine service " + + engineServiceFacade.getKey().getID()).build(); + } + +} diff --git a/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ParameterCheck.java b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ParameterCheck.java new file mode 100644 index 000000000..87e76cdf7 --- /dev/null +++ b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/ParameterCheck.java @@ -0,0 +1,211 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.apex.client.deployment.rest; + +import java.util.Map; + +import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey; +import org.slf4j.ext.XLogger; +import org.slf4j.ext.XLoggerFactory; + +/** + * The Class ParameterCheck is used to check parameters passed to the servlet. + * + * @author Liam Fallon (liam.fallon@ericsson.com) + */ +public final class ParameterCheck { + private static final int MAX_PORT = 65535; + + /** + * private constructor to prevent subclassing of this utility class. + */ + private ParameterCheck() {} + + /** + * The Enum StartStop is used to hold . + * + * @author Liam Fallon (liam.fallon@ericsson.com) + */ + public enum StartStop { + /** Start of an Apex engine has been ordered. */ + START, + /** Stop of an Apex engine has been ordered. */ + STOP + }; + + private static final XLogger LOGGER = XLoggerFactory.getXLogger(ParameterCheck.class); + + private static final String HOSTNAME_PAR = "hostname"; + private static final String PORT_PAR = "port"; + private static final String AXARTIFACTKEY_PAR = "AxArtifactKey"; + + /** + * Gets the host name. + * + * @param parameterMap the parameter map + * @return the host name + */ + public static String getHostName(final Map<String, String[]> parameterMap) { + if (!parameterMap.containsKey(HOSTNAME_PAR)) { + LOGGER.warn("parameter \"" + HOSTNAME_PAR + "\" not found"); + return null; + } + + final String[] hostNameValue = parameterMap.get(HOSTNAME_PAR); + + if (hostNameValue.length == 0 || hostNameValue[0].trim().length() == 0) { + LOGGER.warn("value of parameter \"" + HOSTNAME_PAR + "\" not found"); + return null; + } + + return hostNameValue[0]; + } + + /** + * Gets the port. + * + * @param parameterMap the parameter map + * @return the port + */ + public static int getPort(final Map<String, String[]> parameterMap) { + if (!parameterMap.containsKey(PORT_PAR)) { + LOGGER.warn("parameter \"" + PORT_PAR + "\" not found"); + return -1; + } + + final String[] portValue = parameterMap.get(PORT_PAR); + + if (portValue.length == 0 || portValue[0].trim().length() == 0) { + LOGGER.warn("value of parameter \"" + PORT_PAR + "\" not found"); + return -1; + } + + int port = -1; + try { + port = Integer.parseInt(portValue[0]); + } catch (final Exception e) { + LOGGER.warn("value \"" + portValue[0] + "\"of parameter \"" + PORT_PAR + "\" not a valid integer", e); + return -1; + } + + if (port <= 0 || port > MAX_PORT) { + LOGGER.warn("value \"" + portValue[0] + "\"of parameter \"" + PORT_PAR + + "\" not a valid port between 0 and 65535"); + return -1; + } + + return port; + } + + /** + * Gets the engine key. + * + * @param parameterMap the parameter map + * @return the engine key + */ + public static AxArtifactKey getEngineKey(final Map<String, String[]> parameterMap) { + String artifactKeyParameter = null; + for (final String parameter : parameterMap.keySet()) { + // Check for an AxArtifactKey parameter + if (parameter.startsWith(AXARTIFACTKEY_PAR)) { + artifactKeyParameter = parameter; + break; + } + } + if (artifactKeyParameter == null) { + LOGGER.warn("parameter \"" + AXARTIFACTKEY_PAR + "\" not found"); + return null; + } + + final String[] axArtifactKeyArray = artifactKeyParameter.split("#"); + + if (axArtifactKeyArray.length != 2) { + LOGGER.warn("value \"" + artifactKeyParameter + "\" of parameter \"" + AXARTIFACTKEY_PAR + "\" not valid"); + return null; + } + + return new AxArtifactKey(axArtifactKeyArray[1]); + } + + /** + * Gets the start stop. + * + * @param parameterMap the parameter map + * @param engineKey the engine key + * @return the start stop + */ + public static ParameterCheck.StartStop getStartStop(final Map<String, String[]> parameterMap, + final AxArtifactKey engineKey) { + final String startStopPar = AXARTIFACTKEY_PAR + '#' + engineKey.getID(); + if (!parameterMap.containsKey(startStopPar)) { + LOGGER.warn("parameter \"" + startStopPar + "\" not found"); + return null; + } + + final String[] startStopValue = parameterMap.get(startStopPar); + + if (startStopValue.length == 0 || startStopValue[0].trim().length() == 0) { + LOGGER.warn("value of parameter \"" + startStopPar + "\" not found"); + return null; + } + + ParameterCheck.StartStop startStop; + if (startStopValue[0].equalsIgnoreCase("start")) { + startStop = ParameterCheck.StartStop.START; + } else if (startStopValue[0].equalsIgnoreCase("stop")) { + startStop = ParameterCheck.StartStop.STOP; + } else { + LOGGER.warn("value \"" + startStopValue[0] + "\"of parameter \"" + startStopPar + + "\" not \"start\" or \"stop\""); + return null; + } + + return startStop; + } + + /** + * Find and return a long value with the given name. + * + * @param parameterMap The parameter map containing the value + * @param longName The name of the long parameter + * @return The long value + */ + public static long getLong(final Map<String, String[]> parameterMap, final String longName) { + if (!parameterMap.containsKey(longName)) { + LOGGER.warn("parameter \"" + longName + "\" not found"); + return -1; + } + + final String[] longValue = parameterMap.get(longName); + + if (longValue.length == 0 || longValue[0].trim().length() == 0) { + LOGGER.warn("value of parameter \"" + longName + "\" not found"); + return -1; + } + + try { + return Long.parseLong(longValue[0]); + } catch (final Exception e) { + LOGGER.warn("value \"" + longValue[0] + "\"of parameter \"" + longName + "\" not a valid long", e); + return -1; + } + } +} diff --git a/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/package-info.java b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/package-info.java new file mode 100644 index 000000000..997e411a6 --- /dev/null +++ b/client/client-deployment/src/main/java/org/onap/policy/apex/client/deployment/rest/package-info.java @@ -0,0 +1,27 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +/** + * Implements the RESTful deployment for Apex. + * + * @author Liam Fallon (liam.fallon@ericsson.com) + */ + +package org.onap.policy.apex.client.deployment.rest; diff --git a/client/client-deployment/src/main/resources/webapp/WEB-INF/web.xml b/client/client-deployment/src/main/resources/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..b79cf362a --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/WEB-INF/web.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ============LICENSE_START======================================================= + Copyright (C) 2016-2018 Ericsson. All rights reserved. + ================================================================================ + Licensed 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. + + SPDX-License-Identifier: Apache-2.0 + ============LICENSE_END========================================================= +--> + +<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee + http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + id="WebApp_ID" version="3.0"> + + <servlet> + <servlet-name>apex-services.rest</servlet-name> + <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> + <init-param> + <param-name>jersey.config.server.provider.packages</param-name> + <param-value>org.onap.policy.apex.client.deployment.rest</param-value> + </init-param> + <init-param> + <param-name>jersey.config.server.provider.classnames</param-name> + <param-value>org.glassfish.jersey.media.multipart.MultiPartFeature</param-value> + </init-param> + <load-on-startup>1</load-on-startup> + </servlet> + <servlet-mapping> + <servlet-name>apex-services.rest</servlet-name> + <url-pattern>/apexservices/*</url-pattern> + </servlet-mapping> + +</web-app>
\ No newline at end of file diff --git a/client/client-deployment/src/main/resources/webapp/index.html b/client/client-deployment/src/main/resources/webapp/index.html new file mode 100644 index 000000000..2ae25f901 --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/index.html @@ -0,0 +1,87 @@ +<!-- + ============LICENSE_START======================================================= + Copyright (C) 2016-2018 Ericsson. All rights reserved. + ================================================================================ + Licensed 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. + + SPDX-License-Identifier: Apache-2.0 + ============LICENSE_END========================================================= +--> + +<html> +<head> +<meta charset="UTF-8"> + +<title>Apex Deployment</title> + +<!-- interface style --> +<link rel="stylesheet" type="text/css" href="css/interfaceAssets.css"> +<!-- ApexChartLib style --> +<link rel="stylesheet" type="text/css" href="css/apexChartLib.css"> +<!-- Apex services client style --> +<link rel="stylesheet" type="text/css" href="css/styles.css"> + +<script type="text/javascript" src="js/ApexUtils.js"></script> + +</head> +<body> + + <!-- interface styled System Bar --> + <div class="ebSystemBar"> + <div class="ebSystemBar-topMenuName">Apex</div> + <div class="ebSystemBar-config"></div> + </div> + + <div class="layoutWrapper"> + + <div class="ebBreadcrumbs"> + <div class="ebBreadcrumbs-item"> + <a href="javascript:getHomepageURL();" class="ebBreadcrumbs-link">Apex</a> + </div> + <div class="ebBreadcrumbs-item"> + <a href="" class="ebBreadcrumbs-link">Deployment</a> + </div> + </div> + + <div class="appHeading"> + <h1 class="title">Apex Deployment</h1> + </div> + + <div class="search ebQuickActionBar"></div> + + <!-- Main content div --> + <div id="content" class="content"> + + <!-- Engine Service --> + <h2>Engine Service</h2> + <div class="engineService"></div> + + <!-- Apex Model Loading --> + <h2>Apex Model Loading</h2> + <div class="modelLoading"></div> + + </div> + </div> + + <!-- jQuery --> + <script src="jquery/jquery-1.12.4.js"></script> + + <!-- Apex services client JS files --> + <script type="text/javascript" src="js/ApexAjax.js"></script> + <script type="text/javascript" src="js/ApexTable.js"></script> + <script type="text/javascript" src="js/ApexEngineService.js"></script> + <script type="text/javascript" src="js/ApexModelLoading.js"></script> + <script type="text/javascript" src="js/ApexServicesMain.js"></script> + +</body> +</html> diff --git a/client/client-deployment/src/main/resources/webapp/js/ApexAjax.js b/client/client-deployment/src/main/resources/webapp/js/ApexAjax.js new file mode 100644 index 000000000..beb2cb11d --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/js/ApexAjax.js @@ -0,0 +1,84 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +/* + * Send a GET request + */ +function ajax_get(requestURL, callback, hostName, port, params, errorCallback) { + var data = { + hostName : hostName, + port : port + }; + for ( var p in params) { + data[p] = params[p]; + } + return $.ajax({ + type : 'GET', + url : requestURL, + dataType : "json", + data : data, + success : function(data, textStatus, jqXHR) { + if (callback) { + callback(data); + } + }, + error : function(jqXHR, textStatus, errorThrown) { + if (jqXHR.status == 500 || jqXHR.status == 404) { + if (jqXHR.responseText.indexOf("cound not handshake with server") !== -1 || jqXHR.status == 404) { + clearEngineURL(); + getEngineURL(jqXHR.responseText); + } else { + apexErrorDialog_activate(document.body, jqXHR.responseText); + } + } + if (errorCallback) { + errorCallback(jqXHR, textStatus, errorThrown); + } + } + }); +} + +/* + * Send a POST request and add a file to its payload + */ +function ajax_upload(requestURL, callback, hostName, port, fileUrl, ignoreConflicts, forceUpdate) { + var formData = new FormData(); + formData.append("hostName", hostName); + formData.append("port", port); + formData.append("file", fileUrl); + formData.append("ignoreConflicts", ignoreConflicts); + formData.append("forceUpdate", forceUpdate); + return $.ajax({ + url : requestURL, + type : "POST", + contentType : false, + dataType : "text", + processData : false, + data : formData, + success : function(data, textStatus, jqXHR) { + callback(data); + }, + error : function(jqXHR, textStatus, errorThrown) { + if (jqXHR.status == 500) { + apexErrorDialog_activate(document.body, jqXHR.responseText); + } + } + }); +} diff --git a/client/client-deployment/src/main/resources/webapp/js/ApexEngineService.js b/client/client-deployment/src/main/resources/webapp/js/ApexEngineService.js new file mode 100644 index 000000000..f47c98f07 --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/js/ApexEngineService.js @@ -0,0 +1,101 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +/* + * Create the Engine Service Table + */ +function createEngineServiceTable() { + var tableId = config.engineService.tableId; + var headers = config.engineService.headers; + var table = createEngineTable($("." + config.engineService.parent), tableId, headers.map(function(a) { + return a.title; + })); + var tableRow = document.createElement("tr"); + var tableData = ""; + for ( var h in headers) { + tableData += "<td id=" + tableId + "_" + headers[h].id + "></td>"; + } + tableRow.innerHTML = tableData; + var actionTD = $(tableRow).find("#" + tableId + "_periodic_events"); + actionTD + .html('<input type="text" name="period" id="period" style="display:inline-block"><label class="ebSwitcher"><input type="checkbox" class="ebSwitcher-checkbox" /><div class="ebSwitcher-body"><div class="ebSwitcher-onLabel">Stopped</div><div class="ebSwitcher-switch"></div><div class="ebSwitcher-offLabel">Started</div></div></label>'); + var period = actionTD.find("#period"); + var switcher = actionTD.find(".ebSwitcher"); + switcher.css('display', 'inline-block'); + switcher.css('margin-left', '5px'); + switcher.css('vertical-align', 'middle'); + var checkbox = $(actionTD).find('input:checkbox:first'); + checkbox.change(function(event) { + var startstop; + if (checkbox.prop('checked')) { + startstop = "Stop"; + } else { + startstop = "Start"; + } + this.servicesCall.abort(); + ajax_get(restRootURL + "periodiceventstartstop", startStopCallback, this.engineURL.hostname, + this.engineURL.port, { + engineId : this.engineId, + startstop : startstop, + period : period.val() + }, resetPeriodicEvents); + }.bind(this)); + $(table).children("#engineTableBody").append(tableRow); +} + +/* + * Check for any changes in the Engine Service Table data and update only where + * necessary + */ +function setEngineServiceData(engineId, modelId, server, port, periodicEvents) { + this.engineId = engineId; + var tableId = config.engineService.tableId; + var headers = config.engineService.headers.map(function(a) { + return a.id; + }); + var data = [ engineId, server + ":" + port, modelId ]; + + var engineServiceTable = $("#engineServicesTable"); + + for ( var h in headers) { + var td = engineServiceTable.find("#" + tableId + "_" + headers[h]); + if (td.html() !== data[h]) { + engineServiceTable.find("#" + tableId + "_" + headers[h]).html(data[h]); + } + } + + var actionTD = engineServiceTable.find("#" + tableId + "_periodic_events"); + var checkbox = $(actionTD).find('input:checkbox:first'); + if (checkbox.is(":checked") === periodicEvents) { + checkbox.prop("checked", !checkbox.prop("checked")); + } +} + +/* + * Resets the switcher for Periodic Events in the Engine Service Table + */ +function resetPeriodicEvents() { + var engineServiceTable = $("#engineServicesTable"); + var periodicEventsTD = $(engineServiceTable).find("#engineServicesTable_periodic_events"); + var checkbox = $(periodicEventsTD).find('input:checkbox:first'); + if (checkbox.is(":checked")) { + checkbox.prop("checked", false); + } +} diff --git a/client/client-deployment/src/main/resources/webapp/js/ApexModelLoading.js b/client/client-deployment/src/main/resources/webapp/js/ApexModelLoading.js new file mode 100644 index 000000000..8bd051f0f --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/js/ApexModelLoading.js @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +/* + * Create the div for uploading Apex models + */ +function createModelLoadingDiv() { + var fileLoader = document.createElement("input"); + fileLoader.setAttribute("type", "file"); + fileLoader.setAttribute("name", "apexModelFile"); + fileLoader.setAttribute("label", "Load Apex Model XML file"); + $('.modelLoading').append(fileLoader); + + var ignoreConflictsCheckbox = document.createElement("input"); + ignoreConflictsCheckbox.setAttribute("type", "checkbox"); + ignoreConflictsCheckbox.setAttribute("name", "ignoreContextConflicts"); + $('.modelLoading').append(ignoreConflictsCheckbox); + + ignoreConflictsLabel = document.createElement("label"); + ignoreConflictsLabel.setAttribute("class", "ignoreConflictsLabel"); + ignoreConflictsLabel.innerHTML = "Ignore Context Conflicts"; + $('.modelLoading').append(ignoreConflictsLabel); + + var forceUpdateCheckbox = document.createElement("input"); + forceUpdateCheckbox.setAttribute("type", "checkbox"); + forceUpdateCheckbox.setAttribute("name", "forceUpdate"); + $('.modelLoading').append(forceUpdateCheckbox); + + forceUpdateLabel = document.createElement("label"); + forceUpdateLabel.setAttribute("class", "ignoreConflictsLabel"); + forceUpdateLabel.innerHTML = "Force Update"; + $('.modelLoading').append(forceUpdateLabel); + + var submitButton = document.createElement("button"); + submitButton.setAttribute("class", "ebBtn"); + submitButton.innerHTML = "Load Apex Model XML file"; + $(submitButton).click( + function() { + var file = fileLoader.files[0]; + var ignoreConflicts = $(ignoreConflictsCheckbox).is(":checked"); + var forceUpdate = $(forceUpdateCheckbox).is(":checked"); + ajax_upload(restRootURL + "modelupload/", uploadCallback, this.engineURL.hostname, this.engineURL.port, + file, ignoreConflicts, forceUpdate); + }.bind(this)); + $('.modelLoading').append(submitButton); +}
\ No newline at end of file diff --git a/client/client-deployment/src/main/resources/webapp/js/ApexServicesMain.js b/client/client-deployment/src/main/resources/webapp/js/ApexServicesMain.js new file mode 100644 index 000000000..a54815ab3 --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/js/ApexServicesMain.js @@ -0,0 +1,147 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +var restRootURL; + +var config = { + refresh : 5000, + engineService : { + parent : "engineService", + tableId : "engineServicesTable", + headers : [ { + title : "Engine Service ID", + id : "engine_id" + }, { + title : "server:port", + id : "server_port" + }, { + title : "Model ID", + id : "model_id" + } ] + } +} + +/* + * Callback for showing model info + */ +function servicesCallback(data) { + // If engine url in cookie has not been cleared + if (localStorage.getItem("apex-monitor-services")) { + setEngineServiceData(data.engine_id, data.model_id, data.server, data.port, data.periodic_events); + + // Make content visible after data has been returned for the first time + if (!$(".content").is(':visible')) { + $(".content").fadeIn(); + } + + // Repeat the same request + setTimeout(function() { + this.servicesCall = ajax_get(restRootURL, servicesCallback, this.engineURL.hostname, this.engineURL.port); + }, config.refresh); + } +} + +/* + * Callback for uploading a model + */ +function uploadCallback(response) { + // Open a dialog showing the response + apexSuccessDialog_activate(document.body, response); +} + +/* + * Clears and resets all content on the page + */ +function setUpPage() { + // Clear each div + $('#content > div').each(function() { + $(this).empty(); + }); + + // Set up content div's + createEngineServiceTable(); + createModelLoadingDiv(); +} + +/* + * Retrieves the engine URL from the cookie. If it has not been set yet, then a + * dialog is shown asking for it + */ +function getEngineURL(message) { + // The engine URL is stored in a cookie using the key + // "apex-monitor-services" + var engineURL = localStorage.getItem("apex-monitor-services"); + + // This url is used to store the last known engine URL so that the user + // doesn't have to retype it every time + var oldEngineURL = localStorage.getItem("apex-monitor-services_old"); + + // If an engine URL is stored in the cookie + if (engineURL) { + // Parse the engine URL + this.engineURL = JSON.parse(engineURL); + + // Send a request with that engine URL + this.servicesCall = ajax_get(restRootURL, servicesCallback, this.engineURL.hostname, this.engineURL.port); + } else { + // Prompt for engine URL + apexDialogForm_activate(document.body, message); + } +} + +/* + * Clears the cookie and reset the page + */ +function clearEngineURL() { + // Remove engine URL from cookie + localStorage.removeItem("apex-monitor-services"); + + // Reset the page + setUpPage(); +} + +/* + * Called after the DOM is ready + */ +$(document).ready( + function() { + restRootURL = location.protocol + + "//" + + window.location.hostname + + (location.port ? ':' + location.port : '') + + (location.pathname.endsWith("/deployment/") ? location.pathname.substring(0, location.pathname + .indexOf("deployment/")) : location.pathname) + "apexservices/deployment/"; + + // Set up the structure of the page + setUpPage(); + + // Check cookies for engine URL + getEngineURL(); + + // Add click event to config icon for clearing engine URL + $(".ebSystemBar-config").click(function() { + // Clear the engine URL + clearEngineURL(); + + // Request the engine URL + getEngineURL(); + }); + + });
\ No newline at end of file diff --git a/client/client-deployment/src/main/resources/webapp/js/ApexTable.js b/client/client-deployment/src/main/resources/webapp/js/ApexTable.js new file mode 100644 index 000000000..20e3d08bd --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/js/ApexTable.js @@ -0,0 +1,59 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +/* + * Create a table with given headers + */ +function createEngineTable(parent, id, tableHeaders) { + var table = createTable(id); + + var tableHead = document.createElement("thead"); + table.appendChild(tableHead); + tableHead.setAttribute("id", "engineTableHeader"); + + var tableHeaderRow = document.createElement("tr"); + tableHead.appendChild(tableHeaderRow); + tableHeaderRow.setAttribute("id", "engineTableHeaderRow"); + + for ( var t in tableHeaders) { + var tableHeader = document.createElement("th"); + tableHeaderRow.appendChild(tableHeader); + tableHeader.setAttribute("id", "engineTableHeader"); + tableHeader.appendChild(document.createTextNode(tableHeaders[t])); + } + + var tableBody = document.createElement("tbody"); + tableBody.setAttribute("id", "engineTableBody"); + table.appendChild(tableBody); + + parent.append(table); + + return table; +} + +/* + * Create a table and apply UISDK styles to it + */ +function createTable(id) { + var table = document.createElement("table"); + table.setAttribute("id", id); + table.setAttribute("class", "apexTable ebTable elTablelib-Table-table ebTable_striped"); + return table; +}
\ No newline at end of file diff --git a/client/client-deployment/src/main/resources/webapp/js/ApexUtils.js b/client/client-deployment/src/main/resources/webapp/js/ApexUtils.js new file mode 100644 index 000000000..0f5e689fd --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/js/ApexUtils.js @@ -0,0 +1,212 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2016-2018 Ericsson. All rights reserved. + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +/* + * Crate a dialog with input, attach it to a given parent and show an optional message + */ +function apexDialogForm_activate(formParent, message) { + apexUtils_removeElement("apexDialogDiv"); + + var contentelement = document.createElement("apexDialogDiv"); + var formDiv = document.createElement("div"); + var backgroundDiv = document.createElement("div"); + backgroundDiv.setAttribute("id", "apexDialogDivBackground"); + backgroundDiv.setAttribute("class", "apexDialogDivBackground"); + + backgroundDiv.appendChild(formDiv); + contentelement.appendChild(backgroundDiv); + formParent.appendChild(contentelement); + + formDiv.setAttribute("id", "apexDialogDiv"); + formDiv.setAttribute("class", "apexDialogDiv"); + + var headingSpan = document.createElement("span"); + formDiv.appendChild(headingSpan); + + headingSpan.setAttribute("class", "headingSpan"); + headingSpan.innerHTML = "Apex Engine Configuration"; + + var form = document.createElement("apexDialog"); + formDiv.appendChild(form); + + form.setAttribute("id", "apexDialog"); + form.setAttribute("class", "form-style-1"); + form.setAttribute("method", "post"); + + if (message) { + var messageLI = document.createElement("li"); + messageLI.setAttribute("class", "dialogMessage"); + messageLI.innerHTML = message; + form.appendChild(messageLI); + } + + var urlLI = document.createElement("li"); + form.appendChild(urlLI); + + var urlLabel = document.createElement("label"); + urlLI.appendChild(urlLabel); + + urlLabel.setAttribute("for", "apexDialogUrlInput"); + urlLabel.innerHTML = "Apex Engine rest URL:"; + + var urlLabelSpan = document.createElement("span"); + urlLabel.appendChild(urlLabelSpan); + + urlLabelSpan.setAttribute("class", "required"); + urlLabelSpan.innerHTML = "*"; + + var engineUrl = localStorage.getItem("apex-monitor-services_old"); + + var urlInput = document.createElement("input"); + urlInput.setAttribute("id", "services_url_input"); + urlInput.setAttribute("placeholder", "localhost:12345"); + urlInput.value = (engineUrl && engineUrl !== "null") ? JSON.parse(engineUrl).hostname + ":" + + JSON.parse(engineUrl).port : ""; + urlLI.appendChild(urlInput); + + var inputLI = document.createElement("li"); + form.appendChild(inputLI); + + var submitInput = document.createElement("input"); + submitInput.setAttribute("id", "submit"); + submitInput.setAttribute("class", "button ebBtn"); + submitInput.setAttribute("type", "submit"); + submitInput.setAttribute("value", "Submit"); + submitInput.onclick = apexDialogForm_submitPressed; + inputLI.appendChild(submitInput); + + // Enter key press triggers submit + $(urlInput).keyup(function(event) { + if (event.keyCode == 13) { + $(submitInput).click(); + } + }); + + urlInput.focus(); +} + +/* + * Create a dialog for displaying text + */ +function apexTextDialog_activate(formParent, message, title) { + apexUtils_removeElement("apexDialogDiv"); + + var contentelement = document.createElement("div"); + contentelement.setAttribute("id", "apexDialogDiv") + var formDiv = document.createElement("div"); + var backgroundDiv = document.createElement("div"); + backgroundDiv.setAttribute("id", "apexDialogDivBackground"); + backgroundDiv.setAttribute("class", "apexDialogDivBackground"); + + backgroundDiv.appendChild(formDiv); + contentelement.appendChild(backgroundDiv); + formParent.appendChild(contentelement); + + formDiv.setAttribute("id", "apexErrorDialogDiv"); + formDiv.setAttribute("class", "apexDialogDiv apexErrorDialogDiv"); + + var headingSpan = document.createElement("span"); + formDiv.appendChild(headingSpan); + + headingSpan.setAttribute("class", "headingSpan"); + headingSpan.innerHTML = title; + + var form = document.createElement("div"); + formDiv.appendChild(form); + + form.setAttribute("id", "apexDialog"); + form.setAttribute("class", "form-style-1"); + form.setAttribute("method", "post"); + + if (message) { + var messageLI = document.createElement("li"); + messageLI.setAttribute("class", "dialogMessage"); + messageLI.innerHTML = message; + form.appendChild(messageLI); + } + + var inputLI = document.createElement("li"); + form.appendChild(inputLI); + + var cancelInput = document.createElement("input"); + cancelInput.setAttribute("class", "button ebBtn"); + cancelInput.setAttribute("type", "submit"); + cancelInput.setAttribute("value", "Close"); + cancelInput.onclick = newModelForm_cancelPressed; + form.appendChild(cancelInput); +} + +/* + * Create a Success dialog + */ +function apexSuccessDialog_activate(formParent, message) { + apexTextDialog_activate(formParent, message, "Success"); +} + +/* + * Create an Error dialog + */ +function apexErrorDialog_activate(formParent, message) { + apexTextDialog_activate(formParent, message, "Error"); +} + +/* + * Dialog cancel callback + */ +function newModelForm_cancelPressed() { + apexUtils_removeElement("apexDialogDivBackground"); +} + +/* + * Dialog submit callback + */ +function apexDialogForm_submitPressed() { + var url = $('#services_url_input').val(); + if (url && url.length > 0) { + var engineConfig = { + hostname : url.split(":")[0], + port : url.split(":")[1] + }; + localStorage.setItem("apex-monitor-services_old", JSON.stringify(engineConfig)); + localStorage.setItem("apex-monitor-services", JSON.stringify(engineConfig)); + apexUtils_removeElement("apexDialogDivBackground"); + getEngineURL(); + } +} + +/* + * Remove an element from the page + */ +function apexUtils_removeElement(elementname) { + var element = document.getElementById(elementname); + if (element != null) { + element.parentNode.removeChild(element); + } +} + +function getHomepageURL() { + var homepageURL = location.protocol + + "//" + + window.location.hostname + + (location.port ? ':' + location.port : '') + + (location.pathname.endsWith("/deployment/") ? location.pathname.substring(0, location.pathname + .indexOf("deployment/")) : location.pathname); + location.href = homepageURL; +}
\ No newline at end of file diff --git a/client/client-deployment/src/main/resources/webapp/resources/16px/rowCollapsed_black_16px.svg b/client/client-deployment/src/main/resources/webapp/resources/16px/rowCollapsed_black_16px.svg new file mode 100644 index 000000000..6878c863c --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/resources/16px/rowCollapsed_black_16px.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/"> +]> +<svg version="1.1" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" + x="0px" y="0px" width="16px" height="16px" viewBox="13 2437.971 16 16" enable-background="new 13 2437.971 16 16" + xml:space="preserve"> +<defs> +</defs> +<rect display="none" fill="#66A19F" width="87" height="3280.97"/> +<rect x="17" y="2441.97" fill="#FFFFFF" width="7" height="7"/> +<path d="M24.5,2440.97c-0.275,0-7.725,0-8,0s-0.5,0.225-0.5,0.5s0,7.725,0,8s0.225,0.499,0.5,0.499c0.22,0,7.78,0,8,0 + c0.275,0,0.5-0.223,0.5-0.499s0-7.725,0-8S24.775,2440.97,24.5,2440.97z M24,2448.97h-7v-7.001h7V2448.97z"/> +<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="18" y1="2445.47" x2="23" y2="2445.47"/> +<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="20.5" y1="2447.97" x2="20.5" y2="2442.971"/> +<rect x="13" y="2437.97" display="none" opacity="0.2" fill="#E94E47" width="16" height="16"/> +</svg> diff --git a/client/client-deployment/src/main/resources/webapp/resources/16px/rowExpanded_black_16px.svg b/client/client-deployment/src/main/resources/webapp/resources/16px/rowExpanded_black_16px.svg new file mode 100644 index 000000000..5e3e46a66 --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/resources/16px/rowExpanded_black_16px.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/"> +]> +<svg version="1.1" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" + x="0px" y="0px" width="16px" height="16px" viewBox="13 2405.971 16 16" enable-background="new 13 2405.971 16 16" + xml:space="preserve"> +<defs> +</defs> +<rect display="none" fill="#66A19F" width="87" height="3280.97"/> +<rect x="17" y="2409.97" fill="#FFFFFF" width="7" height="7"/> +<path d="M24.5,2408.971c-0.275,0-7.725,0-8,0s-0.5,0.225-0.5,0.5s0,7.725,0,8s0.225,0.499,0.5,0.499c0.22,0,7.78,0,8,0 + c0.275,0,0.5-0.223,0.5-0.499s0-7.725,0-8S24.775,2408.971,24.5,2408.971z M24,2416.971h-7v-7.001h7V2416.971z"/> +<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="18" y1="2413.471" x2="23" y2="2413.471"/> +<rect x="13" y="2405.97" display="none" opacity="0.2" fill="#E94E47" width="16" height="16"/> +</svg> diff --git a/client/client-deployment/src/main/resources/webapp/resources/16px/settings_black_16px.svg b/client/client-deployment/src/main/resources/webapp/resources/16px/settings_black_16px.svg new file mode 100644 index 000000000..c347888e9 --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/resources/16px/settings_black_16px.svg @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ + <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/"> +]> +<svg version="1.1" + xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" + x="0px" y="0px" width="16px" height="16px" viewBox="13 101.971 16 16" enable-background="new 13 101.971 16 16" + xml:space="preserve"> +<defs> +</defs> +<rect display="none" fill="#66A19F" width="87" height="3280.97"/> +<path d="M28,110.97v-2h-2.101c-0.13-0.638-0.383-1.229-0.73-1.754l1.488-1.488l-1.414-1.414l-1.488,1.488 + c-0.524-0.347-1.117-0.601-1.755-0.731v-2.101h-2v2.101c-0.638,0.129-1.23,0.383-1.754,0.731l-1.488-1.488l-1.414,1.414l1.488,1.488 + c-0.347,0.524-0.601,1.116-0.731,1.754H14v2h2.101c0.13,0.638,0.384,1.23,0.731,1.755l-1.488,1.488l1.414,1.414l1.488-1.488 + c0.524,0.347,1.116,0.601,1.754,0.73v2.101h2v-2.101c0.638-0.129,1.23-0.383,1.754-0.731l1.489,1.489l1.414-1.414l-1.489-1.489 + c0.347-0.524,0.601-1.116,0.731-1.754H28z M21,112.47c-1.381,0-2.5-1.119-2.5-2.5s1.119-2.5,2.5-2.5s2.5,1.119,2.5,2.5 + S22.381,112.47,21,112.47z"/> +<rect x="13" y="101.97" display="none" opacity="0.2" fill="#E94E47" width="16" height="16"/> +</svg> diff --git a/client/client-deployment/src/main/resources/webapp/resources/systemBar/help_black.svg b/client/client-deployment/src/main/resources/webapp/resources/systemBar/help_black.svg new file mode 100644 index 000000000..14fdf5d35 --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/resources/systemBar/help_black.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> +<path fill="#1D1D1B" d="M8,0C3.582,0,0,3.582,0,8s3.582,8,8,8s8-3.582,8-8S12.418,0,8,0z M8.608,12.149 + c-0.205,0.183-0.442,0.273-0.711,0.273c-0.277,0-0.521-0.09-0.728-0.27c-0.207-0.181-0.311-0.433-0.311-0.756 + c0-0.287,0.101-0.528,0.3-0.724c0.201-0.196,0.447-0.295,0.738-0.295c0.287,0,0.529,0.099,0.725,0.295 + c0.196,0.195,0.295,0.437,0.295,0.724C8.917,11.717,8.813,11.967,8.608,12.149z M10.46,6.227c-0.123,0.229-0.27,0.427-0.439,0.594 + C9.85,6.986,9.545,7.267,9.104,7.661c-0.122,0.111-0.22,0.208-0.293,0.292C8.738,8.038,8.684,8.114,8.647,8.185 + c-0.036,0.069-0.063,0.14-0.083,0.209c-0.02,0.07-0.049,0.193-0.089,0.368C8.408,9.135,8.194,9.32,7.837,9.32 + c-0.187,0-0.343-0.061-0.47-0.183C7.239,9.016,7.176,8.835,7.176,8.595c0-0.301,0.047-0.561,0.14-0.782 + c0.093-0.22,0.217-0.413,0.371-0.58C7.84,7.067,8.048,6.869,8.31,6.64c0.229-0.201,0.395-0.352,0.497-0.454 + c0.102-0.103,0.188-0.216,0.257-0.341c0.07-0.125,0.105-0.261,0.105-0.408c0-0.287-0.107-0.528-0.32-0.725S8.361,4.417,8.024,4.417 + c-0.394,0-0.684,0.099-0.87,0.298C6.968,4.913,6.811,5.206,6.682,5.593C6.56,5.998,6.329,6.2,5.989,6.2 + c-0.201,0-0.37-0.071-0.508-0.212C5.344,5.846,5.274,5.693,5.274,5.528c0-0.34,0.109-0.685,0.328-1.034 + C5.82,4.146,6.14,3.856,6.559,3.627s0.907-0.344,1.466-0.344c0.52,0,0.978,0.096,1.375,0.287c0.397,0.192,0.705,0.452,0.922,0.782 + c0.216,0.33,0.324,0.688,0.324,1.074C10.646,5.73,10.584,5.998,10.46,6.227z"/> +</svg> diff --git a/client/client-deployment/src/main/resources/webapp/resources/systemBar/logout_black.svg b/client/client-deployment/src/main/resources/webapp/resources/systemBar/logout_black.svg new file mode 100644 index 000000000..af297acde --- /dev/null +++ b/client/client-deployment/src/main/resources/webapp/resources/systemBar/logout_black.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> +<g> + <g> + <path fill="#1D1D1B" d="M10.77,12.455c0,0-0.684,0.192-0.684-0.684c0-0.199,0-0.861,0-1.711H5.98 + c-0.377,0-0.684-0.308-0.684-0.684V6.639c0-0.377,0.307-0.684,0.684-0.684h4.106c0-0.818,0-1.475,0-1.732 + c0-0.919,0.684-0.662,0.684-0.662l5.133,4.447L10.77,12.455z"/> + </g> + <path fill="#1D1D1B" d="M9.85,14H3.099c-0.551,0-1-0.447-1-1V3c0-0.552,0.449-1,1-1H9.85V0H3.099c-1.656,0-3,1.344-3,3v10 + c0,1.656,1.344,3,3,3H9.85V14z"/> +</g> +</svg> |