diff --git a/cws-service/src/main/java/jpl/cws/controller/MvcCore.java b/cws-service/src/main/java/jpl/cws/controller/MvcCore.java index 222c46e1..72cd360a 100644 --- a/cws-service/src/main/java/jpl/cws/controller/MvcCore.java +++ b/cws-service/src/main/java/jpl/cws/controller/MvcCore.java @@ -115,7 +115,6 @@ protected ModelAndView buildLogsModel(String message) { // model.addObject("procDefs", cwsExecutionService.listProcessDefinitions()); model.addObject("workerIds", cwsConsoleService.getAllWorkerIds()); - model.addObject("procInstIds", cwsConsoleService.getProcInstIds()); log.trace("MODEL for Logs page: "+model.getModel()); } diff --git a/cws-service/src/main/java/jpl/cws/controller/RestService.java b/cws-service/src/main/java/jpl/cws/controller/RestService.java index 915a0372..04fa5c35 100644 --- a/cws-service/src/main/java/jpl/cws/controller/RestService.java +++ b/cws-service/src/main/java/jpl/cws/controller/RestService.java @@ -228,6 +228,23 @@ public RestService() {} // Success! return buildModel("login", "updated initiator enabled to " + enabled); } + + @RequestMapping(value = "/initiators/all/enabled", method = POST) + public @ResponseBody ModelAndView setAllInitiatorsEnabled( + @RequestParam("enabled") boolean enabled) { + + try { + if (enabled) { + cwsInitiatorsService.enableAndStartAllInitiators(); + } else { + cwsInitiatorsService.disableAndStopAllInitiators(); + } + } catch (Exception e) { + log.error("A problem occured when setting enabled status to " + enabled, e); + return buildModel("initiators", e.getMessage()); + } + return buildModel("login", "updated initiator enabled to " + enabled); + } /** @@ -252,6 +269,32 @@ public RestService() {} } return "error"; } + + /** + * Gets all process initiators enabled flag. + * + */ + @RequestMapping(value = "initiators/all/enabled", method = GET) + public @ResponseBody Map areAllInitiatorsEnabled () { + try { + log.trace("REST::areAllInitiatorsEnabled"); + List initiators = cwsConsoleService.getAllProcessInitiators(); + Map statusMap = new HashMap<>(); + for (int i = 0; i < initiators.size(); i++) { + String status = ""; + if (initiators.get(i).isEnabled()) { + status = "true"; + } else { + status = "false"; + } + statusMap.put(initiators.get(i).getInitiatorId(), status); + } + return statusMap; + } catch (Exception e) { + log.error("areAllInitiatorsEnabled exception", e); + } + return null; + } /** @@ -1436,8 +1479,45 @@ public GsonUTCDateAdapter() { return "success"; } - - + + /** + * Suspends a process definition given its procDefId + * + */ + @RequestMapping(value = "/deployments/suspend/{procDefId}", method = POST) + public @ResponseBody String suspendProcDefId( + @PathVariable String procDefId) { + log.info("*** REST CALL *** suspendProcDefId (procDefId=" + procDefId + ")"); + String result = cwsConsoleService.suspendProcDefId(procDefId); + return result; + } + + /** + * Activates a suspended process definition given its procDefId + * + */ + @RequestMapping(value = "/deployments/activate/{procDefId}", method = POST) + public @ResponseBody String activateProcDefId( + @PathVariable String procDefId ) { + log.info ("*** REST CALL *** activateProcDefId (procDefId" + procDefId + ")"); + String result = cwsConsoleService.activateProcDefId(procDefId); + return result; + } + + /** + * Method that deletes a running process instance. + * + * Accepts an array of procInstIds and expects all of them to be running. + */ + @RequestMapping(value = "/processes/delete", method = POST) + public @ResponseBody String deleteRunningProcInsts( + final HttpSession session, + @RequestBody List procInstIds) { + log.debug("*** REST CALL *** deleteRunningProcInsts"); + String result = cwsConsoleService.deleteRunningProcInst(procInstIds); + return result; + } + /** * * @@ -1635,4 +1715,5 @@ public Message createMessage(Session session) throws JMSException { } return restCallResult.getResponse(); } + } \ No newline at end of file diff --git a/cws-service/src/main/java/jpl/cws/process/initiation/InitiatorsService.java b/cws-service/src/main/java/jpl/cws/process/initiation/InitiatorsService.java index 2748c7ba..dd48ff84 100644 --- a/cws-service/src/main/java/jpl/cws/process/initiation/InitiatorsService.java +++ b/cws-service/src/main/java/jpl/cws/process/initiation/InitiatorsService.java @@ -617,15 +617,78 @@ public synchronized void disableAndStopInitiator(String initiatorId) throws Exce disableAndStopInitiator(cwsConsoleService.getProcessInitiatorById(initiatorId)); } + public synchronized void disableAndStopAllInitiators() throws Exception { + disableAndStopAllInitiators(cwsConsoleService.getAllProcessInitiators()); + } + // Synchronized to prevent race conditions in refreshing the spring context public synchronized void enableAndStartInitiator(String initiatorId) throws Exception { enableAndStartInitiator(cwsConsoleService.getProcessInitiatorById(initiatorId)); } + + public synchronized void enableAndStartAllInitiators() throws Exception { + enableAndStartAllInitiators(cwsConsoleService.getAllProcessInitiators()); + } public CwsProcessInitiator getInitiator(String initiatorId) { return cwsConsoleService.getProcessInitiatorById(initiatorId); } + + private void disableAndStopAllInitiators(List initiators) throws Exception { + log.debug("disableAndStopAllInitiators"); + if (initiators == null) { + throw new IllegalArgumentException("Null initiator list!"); + } + + //Check if any initiators are currently enabled + for (CwsProcessInitiator initiator : initiators) { + if (initiator == null) { + throw new IllegalArgumentException("null initiator!"); + } + + // Is the initiator currently enabled? + // + boolean isEnabled = initiator.isEnabled(); + log.trace("before updating initiator, enabled = " + isEnabled); + + if (isEnabled) { + // Disable the initiator + // + initiator.setEnabled(false); + } + + // Stop initiator, if necessary + // + if (initiator.isAlive()) { + log.trace("Interrupting (stopping) initiator: " + initiator + " ..."); + + // Interrupt the initiator... + // + initiator.interrupt(); + + // Wait for initiator to die... + // + int maxWaits = 0; + while (initiator.isAlive() && maxWaits++ < 10) { + log.warn("initiator still alive... (poll #" + maxWaits + ")"); + try { Thread.sleep(200); } catch (InterruptedException e) { } + } + if (initiator.isAlive()) { + log.error("initiator " + initiator + " (procDefKey=" +initiator.getProcDefKey()+") still alive after interruption!"); + } + else { + log.trace("initator is NOT alive."); + } + } + else { + log.trace("initiator was already dead. No need to interrupt it."); + } + + initiator.cleanUp(); + log.trace("Done with disableAndStopInitiator of " + initiator); + } + } /** * @@ -677,6 +740,52 @@ private void disableAndStopInitiator(CwsProcessInitiator initiator) throws Excep initiator.cleanUp(); log.trace("Done with disableAndStopInitiator of " + initiator); } + + /** + * + * + */ + private void enableAndStartAllInitiators(List initiators) throws Exception { + log.debug("enableAndStartAllInitiators"); + if (initiators == null) { + throw new IllegalArgumentException("Null initiator list!"); + } + + //Check if any initiators are currently enabled + for (CwsProcessInitiator initiator : initiators) { + if (initiator == null) { + throw new IllegalArgumentException("null initiator!"); + } + + // If this initiator is not being enabled for the first time, then + // we must re-initialize it.. + // + if (initiator.getState() != Thread.State.NEW) { + disableAndStopInitiator(initiator); + cwsConsoleService.replaceInitiatorBean(initiator.getInitiatorId(), initiator); + initiator = cwsConsoleService.getProcessInitiatorById(initiator.getInitiatorId()); + } + + // Enable the initiator + // + initiator.setEnabled(true); + + // Start up initiator if necessary + // + try { + if (initiator.isAlive()) { + log.error("initiator should not already be alive!"); + } + else { + log.debug("Starting initiator: " + initiator + " ..."); + initiator.start(); + } + } + catch (Exception e) { + log.error("problem while starting up a new initiator", e); + } + } + } /** diff --git a/cws-service/src/main/java/jpl/cws/scheduler/CwsProcessInstance.java b/cws-service/src/main/java/jpl/cws/scheduler/CwsProcessInstance.java index a7bf35e1..7bed1f46 100644 --- a/cws-service/src/main/java/jpl/cws/scheduler/CwsProcessInstance.java +++ b/cws-service/src/main/java/jpl/cws/scheduler/CwsProcessInstance.java @@ -40,7 +40,8 @@ public CwsProcessInstance( String startedByWorker, Timestamp procStartTime, Timestamp procEndTime, - Map inputVariables) { + Map inputVariables, + Map outputVariables) { super(); this.uuid = uuid; this.procDefKey = procDefKey; @@ -55,6 +56,7 @@ public CwsProcessInstance( this.procStartTime = procStartTime; this.procEndTime = procEndTime; this.inputVariables = inputVariables; + this.outputVariables = outputVariables; } public String getUuid() { @@ -161,5 +163,13 @@ public Map getInputVariables() { return inputVariables; } + public void setOutputVariables(Map input) { + this.outputVariables = input; + } + + public Map getOutputVariables() { + return outputVariables; + } + } diff --git a/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java b/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java index 4723bb3a..f0824545 100644 --- a/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java +++ b/cws-service/src/main/java/jpl/cws/service/CwsConsoleService.java @@ -418,6 +418,16 @@ public CwsProcessInitiator getProcessInitiatorById(String initiatorId) { return initiator; } + public List getAllProcessInitiators() { + Map initiatorMap; + initiatorMap = SpringApplicationContext.getBeansOfType(CwsProcessInitiator.class); + ArrayList initiators= new ArrayList(); + for (Map.Entry initiator : initiatorMap.entrySet()) { + initiators.add(initiator.getValue()); + } + return initiators; + } + public void replaceInitiatorBean(String springBeanKey, CwsProcessInitiator newInitiator) { log.debug("replaceInitiatorBean initiator: " + springBeanKey + " ..."); springApplicationContext.replaceBean(springBeanKey, null, newInitiator.getPropertyValues(), @@ -1134,10 +1144,13 @@ public List getFilteredProcessInstancesCamunda(String superP Timestamp procStartTime = (Timestamp) row.get("proc_start_time"); Timestamp procEndTime = (Timestamp) row.get("proc_end_time"); Map inputVars; + Map outputVars; if (procInstIdObj != null) { inputVars = getInputVariablesForProcess(procInstIdObj.toString()); + outputVars = getOutputVariablesForProcess(procInstIdObj.toString()); } else { inputVars = new HashMap(); + outputVars = new HashMap(); } CwsProcessInstance instance = new CwsProcessInstance(uuidObj == null ? null : uuidObj.toString(), procDefKeyObj == null ? null : procDefKeyObj.toString(), @@ -1149,7 +1162,7 @@ public List getFilteredProcessInstancesCamunda(String superP updatedTimestampObj == null ? null : updatedTimestampObj, claimedByWorker == null ? null : claimedByWorker, startedByWorker == null ? null : startedByWorker, procStartTime == null ? null : procStartTime, procEndTime == null ? null : procEndTime, - inputVars); + inputVars, outputVars); instances.add(instance); } @@ -1218,4 +1231,45 @@ public Message createMessage(Session session) throws JMSException { throw e; } } + + /** + * Suspend a procDefKey given a procDefId + */ + public String suspendProcDefId(String procDefId) { + try { + repositoryService.updateProcessDefinitionSuspensionState().byProcessDefinitionId(procDefId).suspend(); + return "Success"; + } catch (Exception e) { + log.error("*** ERROR *** Could not suspend procDefId: " + e); + return "Error"; + } + } + + /** + * Activate a suspended procDefKey given a procDefId + */ + public String activateProcDefId(String procDefId) { + try { + repositoryService.updateProcessDefinitionSuspensionState().byProcessDefinitionId(procDefId).activate(); + return "Success"; + } catch (Exception e) { + log.error("*** ERROR *** Could not activate procDefId: " + e); + return "Error"; + } + } + + /** + * Delete a running process instance given an array of procInstIds + */ + public String deleteRunningProcInst(List procInstIds) { + try { + for (String procInstId : procInstIds) { + runtimeService.deleteProcessInstance(procInstId, "User requested delete from processes page.", true, true, true); + log.debug("DELETED PROCESS INSTANCE"); + } + return "Success"; + } catch (Exception e) { + return "Error: " + e; + } + } } diff --git a/cws-test/src/test/java/jpl/cws/test/integration/ui/HistoryTestIT.java b/cws-test/src/test/java/jpl/cws/test/integration/ui/HistoryTestIT.java index e02107b4..6b8a545c 100644 --- a/cws-test/src/test/java/jpl/cws/test/integration/ui/HistoryTestIT.java +++ b/cws-test/src/test/java/jpl/cws/test/integration/ui/HistoryTestIT.java @@ -11,6 +11,7 @@ import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.Select; +import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -81,6 +82,13 @@ public void runResultsTest() throws IOException { findOnPage("CWS - History"); + WebElement hideLineCheckbox = findElByXPath("//input[@id='hide-log-lines-checkbox']"); + waitForElement(hideLineCheckbox); + + sleep(10000); + + hideLineCheckbox.click(); + if (findOnPage("History Page.") && findOnPage("Command 'mkdir Test' exit code: 0") && findOnPage("Command 'ls' exit code: 0") diff --git a/cws-ui/src/main/webapp/css/deployments.css b/cws-ui/src/main/webapp/css/deployments.css new file mode 100644 index 00000000..9891bb67 --- /dev/null +++ b/cws-ui/src/main/webapp/css/deployments.css @@ -0,0 +1,214 @@ +.dataTables_wrapper .filter .dataTables_filter { + float: right; + padding-top: 15px; + display: inline; + margin-right: 15px; +} + +.dataTables_wrapper .mylength .dataTables_length { + float: right +} + +.dataTables_wrapper .download-button { + padding-top: 15px; +} + +.above-table-div { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: flex-end; + gap: 10px; + margin-bottom: 5px; + margin-top: 15px; +} + +.above-table-buttons { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-end; + align-items: flex-end; + gap: 10px; +} + +.btn-icon { + margin-right: 5px; +} + +.below-table-div { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: flex-start; + gap: 10px; + margin-bottom: 5px; +} + +.status-div { + padding: 0px 6px 6px 6px; +} + +#suspend-div { + height: 50px; + float: right; +} + +.bar-failedToStart { + background-color: #D8860B; +} + +.bar-incident { + background-color: #C347ED; + /*#F142F4;*/ +} + +#workers-div { + overflow: auto; + max-height: 500px; + margin-left: 20px; +} + +.stat-txt { + font-size: 0.7em; + font-family: monospace; +} + +.progress-bar-warning { + background-color: #E7B814; +} + +.progress-bar-disabled { + background-color: #CCCCCC; +} + +.progress-bar-info { + background-color: #4363CF; +} + +#selAll-label { + cursor: pointer; +} + +#workers-div div label { + margin: 0 5px; + cursor: pointer; +} + +*/ #deploy-table td { + padding: 10px; +} + +#process-table { + margin-top: 2rem; +} + +#process-table tr td { + vertical-align: middle; + padding: 4px 8px; + min-width: 70px; +} + +#process-table tr td:nth-child(1) { + min-width: 200px; + overflow: hidden; + text-wrap: none +} + +#process-table tr td:nth-child(3) { + text-align: center; + width: 70px; +} + +#process-table tr td:nth-child(4) { + text-align: center; + width: 70px; +} + +#process-table tr td:nth-child(5) { + text-align: center; + width: 70px; +} + +.progress { + margin: 0px; +} + +.w-down { + color: #bbb; +} + +#deleting-message-container { + margin-top: 30px; + display: none; + justify-content: center; + align-items: center; + +} + +.loader { + border: 10px solid #f3f3f3; + border-radius: 50%; + border-top: 10px solid #3498db; + width: 40px; + height: 40px; + -webkit-animation: spin 1s linear infinite; + /* Safari */ + animation: spin 1s linear infinite; +} + +/* Safari */ +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +.proc-name-flex { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-content: space-between; + align-items: center; + gap: 15px; +} + +.proc-name-btns { + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; + align-content: space-between; + gap: 5px; +} + +.status-div-flex { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: baseline; + align-content: center; + gap: 10px; +} + +.glyphicon-pause { + scale: 1.5; +} \ No newline at end of file diff --git a/cws-ui/src/main/webapp/css/history.css b/cws-ui/src/main/webapp/css/history.css new file mode 100644 index 00000000..71e7be2a --- /dev/null +++ b/cws-ui/src/main/webapp/css/history.css @@ -0,0 +1,60 @@ +.dataTables_wrapper .filter .dataTables_filter{float:right; padding-top: 15px; display: inline; margin-right: 15px;} +.dataTables_wrapper .download-button {padding-top: 15px;} +.dataTables_wrapper .button {float:left; display: inline; margin-top: 15px; margin-right: 15px;} +.dataTables_wrapper {margin-left: 5px; margin-right: -10px;} +summary { + width: 100px; +} +.historyLimitSize { + max-height: 150px; +} +.thumbnail { + margin-top: 5px !important; + margin-bottom: 0px !important; +} +.above-table-div { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: flex-end; + gap: 10px; + margin-bottom: 5px; +} + +.above-table-buttons { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-end; + align-items: flex-end; + gap: 10px; +} + +.above-table-filler { + flex-grow: 1; +} + +#logData{ + font-size:95%; +} +#logData tr th{ + white-space: nowrap; +} +/* log level header*/ +#logData tr th:nth-child(5){ + padding-right: 25px; +} +#logData tr td{ + line-height: 1; +} +#logData tr td:nth-child(4){ + /*overflow: auto;*/ + word-wrap: break-word; + word-break: break-all; + font-family: courier,consolas; + /*white-space: normal !important;*/ +} +summary { + display: list-item; +} \ No newline at end of file diff --git a/cws-ui/src/main/webapp/css/processes.css b/cws-ui/src/main/webapp/css/processes.css index 7c226b9a..784abc61 100644 --- a/cws-ui/src/main/webapp/css/processes.css +++ b/cws-ui/src/main/webapp/css/processes.css @@ -118,4 +118,22 @@ summary { align-items: flex-start; gap: 10px; margin-bottom: 5px; +} + +.table-cell-flex { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: flex-start; + gap: 10px; +} + +.table-cell-main-flex { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + justify-content: space-between; + align-items: flex-start; + gap: 0px; } \ No newline at end of file diff --git a/cws-ui/src/main/webapp/js/cws.js b/cws-ui/src/main/webapp/js/cws.js index ffecebb2..b771a1b5 100644 --- a/cws-ui/src/main/webapp/js/cws.js +++ b/cws-ui/src/main/webapp/js/cws.js @@ -22,6 +22,29 @@ function getQueryString(){ return params; } +//parses query string. returns null if empty +function parseQueryString(qstring){ + /* + * PARSE THE QUERY STRING + */ + qstring = qstring.substring(1); + + var keyValPair = qstring.split('&'); + + if(keyValPair.length == 1 && keyValPair[0] == ""){ + return null; + } + + var params = {}; + for(entry in keyValPair){ + var key = keyValPair[entry].split("=")[0]; + var val = keyValPair[entry].split("=")[1]; + params[key] = val; + } + + return params; +} + //Thank you to sobyte.net for the following function: function base64ToBlob(base64Data, mime) { mime = mime || "image/png"; @@ -70,6 +93,19 @@ function checkForURL(potentialURL) { } } +function isEqual (a, b) { + for (const key in a) { + if (b[key] !== undefined) { + if (a[key] !== b[key]) { + return false; + } + } else { + return false; + } + } + return true; +} + function autocomplete(inp, arr) { var currentFocus; inp.addEventListener("input", function(e) { diff --git a/install/cws-ui/deployments.ftl b/install/cws-ui/deployments.ftl index e49fb03c..1e1ed7dc 100644 --- a/install/cws-ui/deployments.ftl +++ b/install/cws-ui/deployments.ftl @@ -11,56 +11,8 @@ - - - @@ -667,11 +755,6 @@
-
- -