From 127eb7709595ea893c96066c64e5052b6340e84b Mon Sep 17 00:00:00 2001 From: Ziheng Sun Date: Sat, 13 Mar 2021 13:31:06 -0500 Subject: [PATCH] fix the progress bar of jupyterhub, add notes to history version of jupyter notebooks --- src/main/java/com/gw/jpa/History.java | 17 +- .../gw/server/Java2JupyterClientEndpoint.java | 3 +- src/main/java/com/gw/tools/HistoryTool.java | 98 ++++++- src/main/java/com/gw/tools/HostTool.java | 2 + src/main/java/com/gw/tools/ProcessTool.java | 10 +- src/main/java/com/gw/utils/BaseTool.java | 13 +- .../gw/utils/LoggingRequestInterceptor.java | 52 ++++ .../java/com/gw/web/GeoweaverController.java | 78 +++++- .../java/com/gw/web/JupyterController.java | 69 +++-- src/main/resources/static/css/demo.css | 35 ++- .../static/css/jquery.dataTables.min.css | 1 + .../static/js/dataTables.cellEdit.js | 264 ++++++++++++++++++ src/main/resources/static/js/gw.history.js | 88 ++++++ src/main/resources/static/js/gw.host.js | 69 ++++- src/main/resources/templates/geoweaver.html | 7 +- 15 files changed, 761 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/gw/utils/LoggingRequestInterceptor.java create mode 100644 src/main/resources/static/css/jquery.dataTables.min.css create mode 100644 src/main/resources/static/js/dataTables.cellEdit.js create mode 100644 src/main/resources/static/js/gw.history.js diff --git a/src/main/java/com/gw/jpa/History.java b/src/main/java/com/gw/jpa/History.java index 684333397..95dc007f5 100644 --- a/src/main/java/com/gw/jpa/History.java +++ b/src/main/java/com/gw/jpa/History.java @@ -5,10 +5,7 @@ import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; import javax.persistence.Id; -import javax.persistence.Lob; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -36,6 +33,10 @@ public class History { @Temporal(TemporalType.TIMESTAMP) private Date history_end_time; + @Column(columnDefinition = "TEXT") + private String history_notes; + + private String history_process; private String host_id; @@ -46,6 +47,16 @@ public class History { /** end of history section **/ /**********************************************/ + + public String getHistory_notes() { + return this.history_notes; + } + + public void setHistory_notes(String history_notes) { + this.history_notes = history_notes; + } + + public String getIndicator() { return indicator; } diff --git a/src/main/java/com/gw/server/Java2JupyterClientEndpoint.java b/src/main/java/com/gw/server/Java2JupyterClientEndpoint.java index e359eea33..a6019f759 100644 --- a/src/main/java/com/gw/server/Java2JupyterClientEndpoint.java +++ b/src/main/java/com/gw/server/Java2JupyterClientEndpoint.java @@ -130,7 +130,8 @@ public void beforeRequest(Map> nativeheaders) { List values = mapElement.getValue(); // if("Sec-WebSocket-Key".equals(newkey)) { - if("Host".equals(newkey) || "Origin".equals(newkey) || "Sec-WebSocket-Key".equals(newkey)) { + // if("Host".equals(newkey) || "Origin".equals(newkey) ) { + if("Host".equals(newkey) || "Origin".equals(newkey) || "Sec-WebSocket-Key".equals(newkey)) { continue; diff --git a/src/main/java/com/gw/tools/HistoryTool.java b/src/main/java/com/gw/tools/HistoryTool.java index 9a251f798..1ff5d7414 100644 --- a/src/main/java/com/gw/tools/HistoryTool.java +++ b/src/main/java/com/gw/tools/HistoryTool.java @@ -4,8 +4,9 @@ import java.sql.SQLException; import java.util.List; import java.util.Optional; - +import java.util.Collection; import javax.annotation.PostConstruct; +import java.util.Iterator; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -398,7 +399,102 @@ public String process_all_history(String pid) { // return resp.toString(); } + + public String deleteAllHistoryByHost(String hostid){ + + String resp = null; + + try{ + + Collection historylist = historyrepository.findRecentHistory(hostid, 1000); + + Iterator hisint = historylist.iterator(); + + StringBuffer idlist = new StringBuffer(); + + while(hisint.hasNext()) { + + History h = hisint.next(); + + idlist.append(h.getHistory_id()).append(","); + + historyrepository.delete(h); + + } + + resp = "{ \"removed_history_ids\": \"" + idlist.toString() + "\""; + + }catch(Exception e){ + + e.printStackTrace(); + + } + + return resp; + + } + + public String deleteNoNotesHistoryByHost(String hostid){ + + String resp = null; + + try{ + + Collection historylist = historyrepository.findRecentHistory(hostid, 1000); + + Iterator hisint = historylist.iterator(); + + StringBuffer idlist = new StringBuffer(); + + while(hisint.hasNext()) { + + History h = hisint.next(); + + if(bt.isNull(h.getHistory_notes())){ + + idlist.append(h.getHistory_id()).append(","); + + historyrepository.delete(h); + + } + + } + + resp = "{ \"removed_history_ids\": \"" + idlist.toString() + "\""; + + }catch(Exception e){ + + e.printStackTrace(); + + } + + return resp; + + } + /** + * Update the notes of a history + */ + public void updateNotes(String hisid, String notes){ + + try{ + + logger.info("Updating history: " + hisid + " - " + notes); + + History h = this.getHistoryById(hisid); + + h.setHistory_notes(notes); + + this.saveHistory(h); + + }catch(Exception e){ + + e.printStackTrace(); + + } + + } + /** * Save Jupyter Notebook Checkpoints into the GW database */ diff --git a/src/main/java/com/gw/tools/HostTool.java b/src/main/java/com/gw/tools/HostTool.java index b72419f19..92fab84ed 100644 --- a/src/main/java/com/gw/tools/HostTool.java +++ b/src/main/java/com/gw/tools/HostTool.java @@ -153,6 +153,8 @@ public String recent(String hostid, int limit) { resp.append("\"name\": \"").append(h.getHistory_process()).append("\", "); resp.append("\"end_time\": \"").append(h.getHistory_end_time()).append("\", "); + + resp.append("\"notes\": \"").append(h.getHistory_notes()).append("\", "); resp.append("\"status\": \"").append(h.getIndicator()).append("\", "); diff --git a/src/main/java/com/gw/tools/ProcessTool.java b/src/main/java/com/gw/tools/ProcessTool.java index 8ffc276b2..67d13c22d 100644 --- a/src/main/java/com/gw/tools/ProcessTool.java +++ b/src/main/java/com/gw/tools/ProcessTool.java @@ -879,7 +879,9 @@ public String recent(int limit) { resp.append("{ \"id\": \"").append(process_obj[0]).append("\", "); - resp.append("\"name\": \"").append(process_obj[11]).append("\", "); + resp.append("\"name\": \"").append(process_obj[12]).append("\", "); + + resp.append("\"notes\": \"").append(process_obj[8]).append("\", "); resp.append("\"end_time\": \"").append(process_obj[2]).append("\", "); @@ -950,7 +952,9 @@ public String one_history(String hid) { resp.append("\"id\": \"").append(first_obj[5]).append("\", "); - resp.append("\"name\": \"").append(first_obj[11]).append("\", "); + resp.append("\"name\": \"").append(first_obj[12]).append("\", "); + + resp.append("\"notes\": \"").append(first_obj[8]).append("\", "); resp.append("\"begin_time\":\"").append(first_obj[1]).append("\", "); @@ -1024,6 +1028,8 @@ public String all_active_process() { resp.append("\", \"status\": \"").append(escape(String.valueOf(row[4]))); + resp.append("\", \"notes\": \"").append(escape(String.valueOf(row[8]))); + resp.append("\", \"host\": \"").append(escape(String.valueOf(row[5]))); resp.append("\"}"); diff --git a/src/main/java/com/gw/utils/BaseTool.java b/src/main/java/com/gw/utils/BaseTool.java index 49b0ab244..62dba8735 100644 --- a/src/main/java/com/gw/utils/BaseTool.java +++ b/src/main/java/com/gw/utils/BaseTool.java @@ -20,6 +20,8 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.text.ParseException; @@ -116,7 +118,16 @@ public String removeLob(String lob) { } - + public String encodeValue(String value) { + + try { + value = URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return value; + } /** * Get the temp folder for file transfer diff --git a/src/main/java/com/gw/utils/LoggingRequestInterceptor.java b/src/main/java/com/gw/utils/LoggingRequestInterceptor.java new file mode 100644 index 000000000..5ac9078c4 --- /dev/null +++ b/src/main/java/com/gw/utils/LoggingRequestInterceptor.java @@ -0,0 +1,52 @@ +package com.gw.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor { + + final static Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class); + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + traceRequest(request, body); + ClientHttpResponse response = execution.execute(request, body); + traceResponse(response); + return response; + } + + private void traceRequest(HttpRequest request, byte[] body) throws IOException { + log.info("===========================request begin================================================"); + log.info("URI : {}", request.getURI()); + log.info("Method : {}", request.getMethod()); + log.info("Headers : {}", request.getHeaders() ); + log.info("Request body: {}", new String(body, "UTF-8")); + log.info("==========================request end================================================"); + } + + private void traceResponse(ClientHttpResponse response) throws IOException { + StringBuilder inputStringBuilder = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8")); + String line = bufferedReader.readLine(); + while (line != null) { + inputStringBuilder.append(line); + inputStringBuilder.append('\n'); + line = bufferedReader.readLine(); + } + log.info("============================response begin=========================================="); + log.info("Status code : {}", response.getStatusCode()); + log.info("Status text : {}", response.getStatusText()); + log.info("Headers : {}", response.getHeaders()); + log.info("Response body: {}", inputStringBuilder.toString()); + log.info("=======================response end================================================="); + } + +} diff --git a/src/main/java/com/gw/web/GeoweaverController.java b/src/main/java/com/gw/web/GeoweaverController.java index 5d2341274..7e7421977 100644 --- a/src/main/java/com/gw/web/GeoweaverController.java +++ b/src/main/java/com/gw/web/GeoweaverController.java @@ -11,17 +11,29 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import com.gw.search.GWSearchTool; +import com.gw.ssh.RSAEncryptTool; +import com.gw.ssh.SSHSession; +import com.gw.tools.FileTool; +import com.gw.tools.HistoryTool; +import com.gw.tools.HostTool; +import com.gw.tools.ProcessTool; +import com.gw.tools.SessionManager; +import com.gw.tools.WorkflowTool; +import com.gw.utils.BaseTool; +import com.gw.utils.RandomString; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.stereotype.Repository; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.PathVariable; @@ -31,18 +43,6 @@ import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.context.request.WebRequest; -import com.gw.search.GWSearchTool; -import com.gw.ssh.RSAEncryptTool; -import com.gw.ssh.SSHSession; -import com.gw.tools.FileTool; -import com.gw.tools.HistoryTool; -import com.gw.tools.HostTool; -import com.gw.tools.ProcessTool; -import com.gw.tools.SessionManager; -import com.gw.tools.WorkflowTool; -import com.gw.utils.BaseTool; -import com.gw.utils.RandomString; - /** * * Controller for SSH related activities, including all the handlers for Geoweaver. @@ -104,6 +104,48 @@ public void destroy() { sessionManager.closeAll(); } + + @RequestMapping(value="/delAllHistory", method= RequestMethod.POST) + public @ResponseBody String delAllHistory(ModelMap model, WebRequest request){ + + String resp = null; + + try{ + + String hostid = request.getParameter("id"); + + resp = hist.deleteAllHistoryByHost(hostid); + + }catch(Exception e){ + + throw new RuntimeException("failed " + e.getLocalizedMessage()); + + } + + return resp; + + } + + @RequestMapping(value="/delNoNotesHistory", method = RequestMethod.POST) + public @ResponseBody String delNoNotesHistory(ModelMap model, WebRequest request){ + + String resp = null; + + try{ + + String hostid = request.getParameter("id"); + + resp = hist.deleteNoNotesHistoryByHost(hostid); + + }catch(Exception e){ + + throw new RuntimeException("failed " + e.getLocalizedMessage()); + + } + + return resp; + + } @RequestMapping(value = "/del", method = RequestMethod.POST) public @ResponseBody String del(ModelMap model, WebRequest request){ @@ -898,6 +940,16 @@ public ResponseEntity fileGetter(ModelMap model, @PathVariable(value=" resp = "{\"id\" : \"" + wid + "\"}"; + }else if(type.equals("history")){ + + String hisid = request.getParameter("id"); + + String notes = request.getParameter("notes"); + + hist.updateNotes(hisid, notes); + + resp = "{\"id\" : \"" + hisid + "\"}"; + } }catch(Exception e) { diff --git a/src/main/java/com/gw/web/JupyterController.java b/src/main/java/com/gw/web/JupyterController.java index 760ce3f2e..f249a0b9f 100644 --- a/src/main/java/com/gw/web/JupyterController.java +++ b/src/main/java/com/gw/web/JupyterController.java @@ -4,6 +4,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -31,7 +32,10 @@ import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.util.LinkedMultiValueMap; @@ -49,6 +53,7 @@ import com.gw.tools.HistoryTool; import com.gw.tools.HostTool; import com.gw.utils.BaseTool; +import com.gw.utils.LoggingRequestInterceptor; @Controller //@RequestMapping("/Geoweaver/web") @@ -869,7 +874,8 @@ private ResponseEntity processGET(RequestEntity reqentity, HttpMethod met String contenttype = getHeaderProperty(responseEntity.getHeaders(), "Content-Type"); // - if(bt.isNull(newbody)||(!bt.isNull(contenttype)&&(contenttype.contains("image")||contenttype.contains("font")))) { + if(bt.isNull(newbody)|| ( !bt.isNull(contenttype) && (contenttype.contains("image") + || contenttype.contains("font")) )) { // HttpHeaders headers = updateHeader(responseEntity.getHeaders(), newbody, hostid); // @@ -966,7 +972,11 @@ HttpHeaders updateHeader(HttpHeaders oldheaders, String returnbody, String hosti newheaders.set(key, "/Geoweaver/jupyter-proxy/" + hostid + value.get(0)); - }else if (key.toLowerCase().equals("content-length")){ + }else if(key.toLowerCase().equals("transfer-encoding") && value.get(0).equals("chunked")){ + + logger.info("skip the header property of transfer encoding and value is chunked"); + + }else if (key.toLowerCase().equals("content-length")){ // logger.debug("Old Content Length: " + value); @@ -1153,6 +1163,15 @@ public ResponseEntity jupyterhub_login( HttpMethod method, @PathVariable("hostid } + /** + * Login Jupyter Notebook + * @param method + * @param hostid + * @param httpheaders + * @param request + * @return + * @throws URISyntaxException + */ @RequestMapping(value="/jupyter-proxy/{hostid}/login", method = RequestMethod.POST) public ResponseEntity jupyter_login( HttpMethod method, @PathVariable("hostid") String hostid, @RequestHeader HttpHeaders httpheaders, HttpServletRequest request) throws URISyntaxException @@ -1174,11 +1193,11 @@ public ResponseEntity jupyter_login( HttpMethod method, @PathVariable("hostid") logger.debug("Request URI: " + request.getRequestURI()); -// logger.info("Query String: " + request.getQueryString()); + logger.info("Query String: " + request.getQueryString()); -// logger.info("Original Request String: " + request.getParameterMap()); + logger.info("Original Request String: " + request.getParameterMap()); -// logger.info("Old Headers: " + httpheaders); + logger.info("Old Headers: " + httpheaders); // String realurl = this.getRealRequestURL(request.getRequestURI()); // @@ -1192,11 +1211,11 @@ public ResponseEntity jupyter_login( HttpMethod method, @PathVariable("hostid") // // logger.info("URL: " + uri.toString()); // -// logger.info("HTTP Method: " + method.toString()); + logger.info("HTTP Method: " + method.toString()); HttpHeaders newheaders = getHeaders(httpheaders, method, request, hostid); - MultiValueMap map= new LinkedMultiValueMap(); +// MultiValueMap map= new LinkedMultiValueMap(); Iterator hmIterator = request.getParameterMap().entrySet().iterator(); @@ -1208,28 +1227,42 @@ public ResponseEntity jupyter_login( HttpMethod method, @PathVariable("hostid") Map.Entry mapElement = (Map.Entry)hmIterator.next(); - map.add((String)mapElement.getKey(), ((String[])(mapElement.getValue()))[0]); - - if(!bt.isNull(reqstr.toString())) { + String key = (String)mapElement.getKey(); + + String value = (((String[])(mapElement.getValue()))[0]); + + if(!bt.isNull(reqstr.toString())) { reqstr.append("&"); } - - reqstr.append((String)mapElement.getKey()).append("=").append(((String[])(mapElement.getValue()))[0]); + + if(key.equals("_xsrf")) { + + newheaders.set("cookie", "_xsrf="+value); + + logger.info("Cookie XSRF: " + value); + + } + + reqstr.append(key).append("=").append(value); } - - + // HttpHeaders newheaders = this.updateHeaderReferer(httpheaders, h, realurl, request.getQueryString()); HttpEntity requestentity = new HttpEntity(reqstr.toString(), newheaders); -// logger.info("Body: " + requestentity.getBody()); + logger.info("Body: " + requestentity.getBody()); + + logger.info("New Headers: " + requestentity.getHeaders()); -// logger.info("New Headers: " + requestentity.getHeaders()); + RestTemplate restTemplate1 = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory())); + List interceptors = new ArrayList<>(); + interceptors.add(new LoggingRequestInterceptor()); + restTemplate1.setInterceptors(interceptors); - ResponseEntity responseEntity = restTemplate.exchange(getRealTargetURL(newheaders.get("referer").get(0)), method, requestentity, String.class); + ResponseEntity responseEntity = restTemplate1.exchange(getRealTargetURL(newheaders.get("referer").get(0)), method, requestentity, String.class); HttpHeaders respheaders = responseEntity.getHeaders(); @@ -1273,7 +1306,7 @@ public ResponseEntity jupyter_login( HttpMethod method, @PathVariable("hostid") // Set ent = respheaders.entrySet(); -// logger.info(respheaders.toString()); + logger.info(respheaders.toString()); }else if(responseEntity.getStatusCode()==HttpStatus.UNAUTHORIZED) { diff --git a/src/main/resources/static/css/demo.css b/src/main/resources/static/css/demo.css index 6e9351c4e..ba92b83f7 100644 --- a/src/main/resources/static/css/demo.css +++ b/src/main/resources/static/css/demo.css @@ -139,4 +139,37 @@ a{ .no-csstransforms3d .support-note span.no-csstransforms3d, .no-csstransitions .support-note span.no-csstransitions{ display: block; -} \ No newline at end of file +} + +.my-input-class { + padding: 3px 6px; + border: 1px solid #ccc; + border-radius: 4px; +} + +.my-confirm-class { + padding: 3px 6px; + font-size: 12px; + color: white; + text-align: center; + vertical-align: middle; + border-radius: 4px; + background-color: #337ab7; + text-decoration: none; +} + +.my-cancel-class { + padding: 3px 6px; + font-size: 12px; + color: white; + text-align: center; + vertical-align: middle; + border-radius: 4px; + background-color: #a94442; + text-decoration: none; +} + +.error { + border: solid 1px; + border-color: #a94442; +} diff --git a/src/main/resources/static/css/jquery.dataTables.min.css b/src/main/resources/static/css/jquery.dataTables.min.css new file mode 100644 index 000000000..9ec6ca31f --- /dev/null +++ b/src/main/resources/static/css/jquery.dataTables.min.css @@ -0,0 +1 @@ +table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc{cursor:pointer;*cursor:hand}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("../images/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("../images/sort_asc.png")}table.dataTable thead .sorting_desc{background-image:url("../images/sort_desc.png")}table.dataTable thead .sorting_asc_disabled{background-image:url("../images/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("../images/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#ffffff}table.dataTable tbody tr.selected{background-color:#B0BED9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table,.dataTables_wrapper.no-footer div.dataTables_scrollBody table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}} diff --git a/src/main/resources/static/js/dataTables.cellEdit.js b/src/main/resources/static/js/dataTables.cellEdit.js new file mode 100644 index 000000000..974810c3f --- /dev/null +++ b/src/main/resources/static/js/dataTables.cellEdit.js @@ -0,0 +1,264 @@ +/*! CellEdit 1.0.19 + * ©2016 Elliott Beaty - datatables.net/license + */ + +/** + * @summary CellEdit + * @description Make a cell editable when clicked upon + * @version 1.0.19 + * @file dataTables.editCell.js + * @author Elliott Beaty + * @contact elliott@elliottbeaty.com + * @copyright Copyright 2016 Elliott Beaty + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license/mit + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + +jQuery.fn.dataTable.Api.register('MakeCellsEditable()', function (settings) { + var table = this.table(); + + jQuery.fn.extend({ + // UPDATE + updateEditableCell: function (callingElement) { + // Need to redeclare table here for situations where we have more than one datatable on the page. See issue6 on github + var table = $(callingElement).closest("table").DataTable().table(); + var row = table.row($(callingElement).parents('tr')); + var cell = table.cell($(callingElement).parents('td, th')); + var columnIndex = cell.index().column; + var inputField =getInputField(callingElement); + + // Update + var newValue = inputField.val(); + if (!newValue && ((settings.allowNulls) && settings.allowNulls != true)) { + // If columns specified + if (settings.allowNulls.columns) { + // If current column allows nulls + if (settings.allowNulls.columns.indexOf(columnIndex) > -1) { + _update(newValue); + } else { + _addValidationCss(); + } + // No columns allow null + } else if (!newValue) { + _addValidationCss(); + } + //All columns allow null + } else if (newValue && settings.onValidate) { + if (settings.onValidate(cell, row, newValue)) { + _update(newValue); + } else { + _addValidationCss(); + } + } + else { + _update(newValue); + } + function _addValidationCss() { + // Show validation error + if (settings.allowNulls.errorClass) { + $(inputField).addClass(settings.allowNulls.errorClass); + } else { + $(inputField).css({ "border": "red solid 1px" }); + } + } + function _update(newValue) { + var oldValue = cell.data(); + cell.data(newValue); + //Return cell & row. + settings.onUpdate(cell, row, oldValue); + } + // Get current page + var currentPageIndex = table.page.info().page; + + //Redraw table + table.page(currentPageIndex).draw(false); + }, + // CANCEL + cancelEditableCell: function (callingElement) { + var table = $(callingElement.closest("table")).DataTable().table(); + var cell = table.cell($(callingElement).parents('td, th')); + // Set cell to it's original value + cell.data(cell.data()); + + // Redraw table + table.draw(); + } + }); + + // Destroy + if (settings === "destroy") { + $(table.body()).off("click", "td"); + table = null; + } + + if (table != null) { + // On cell click + $(table.body()).on('click', 'td', function () { + + var currentColumnIndex = table.cell(this).index().column; + + // DETERMINE WHAT COLUMNS CAN BE EDITED + if ((settings.columns && settings.columns.indexOf(currentColumnIndex) > -1) || (!settings.columns)) { + var row = table.row($(this).parents('tr')); + editableCellsRow = row; + + var cell = table.cell(this).node(); + var oldValue = table.cell(this).data(); + // Sanitize value + oldValue = sanitizeCellValue(oldValue); + + // Show input + if (!$(cell).find('input').length && !$(cell).find('select').length && !$(cell).find('textarea').length) { + // Input CSS + var input = getInputHtml(currentColumnIndex, settings, oldValue); + $(cell).html(input.html); + if (input.focus) { + $('#ejbeatycelledit').focus(); + } + } + } + }); + } + +}); + +function getInputHtml(currentColumnIndex, settings, oldValue) { + var inputSetting, inputType, input, inputCss, confirmCss, cancelCss, startWrapperHtml = '', endWrapperHtml = '', listenToKeys = false; + + input = {"focus":true,"html":null}; + + if(settings.inputTypes){ + $.each(settings.inputTypes, function (index, setting) { + if (setting.column == currentColumnIndex) { + inputSetting = setting; + inputType = inputSetting.type.toLowerCase(); + } + }); + } + + if (settings.inputCss) { inputCss = settings.inputCss; } + if (settings.wrapperHtml) { + var elements = settings.wrapperHtml.split('{content}'); + if (elements.length === 2) { + startWrapperHtml = elements[0]; + endWrapperHtml = elements[1]; + } + } + + if (settings.confirmationButton) { + if (settings.confirmationButton.listenToKeys) { listenToKeys = settings.confirmationButton.listenToKeys; } + confirmCss = settings.confirmationButton.confirmCss; + cancelCss = settings.confirmationButton.cancelCss; + inputType = inputType + "-confirm"; + } + switch (inputType) { + case "list": + input.html = startWrapperHtml + "" + endWrapperHtml; + input.focus = false; + break; + case "list-confirm": // List w/ confirm + input.html = startWrapperHtml + " Confirm Cancel" + endWrapperHtml; + input.focus = false; + break; + case "datepicker": //Both datepicker options work best when confirming the values + case "datepicker-confirm": + // Makesure jQuery UI is loaded on the page + if (typeof jQuery.ui == 'undefined') { + alert("jQuery UI is required for the DatePicker control but it is not loaded on the page!"); + break; + } + jQuery(".datepick").datepicker("destroy"); + input.html = startWrapperHtml + "  Confirm Cancel" + endWrapperHtml; + setTimeout(function () { //Set timeout to allow the script to write the input.html before triggering the datepicker + var icon = "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif"; + // Allow the user to provide icon + if (typeof inputSetting.options !== 'undefined' && typeof inputSetting.options.icon !== 'undefined') { + icon = inputSetting.options.icon; + } + var self = jQuery('.datepick').datepicker( + { + showOn: "button", + buttonImage: icon, + buttonImageOnly: true, + buttonText: "Select date" + }); + },100); + break; + case "text-confirm": // text input w/ confirm + input.html = startWrapperHtml + " Confirm Cancel" + endWrapperHtml; + break; + case "undefined-confirm": // text input w/ confirm + input.html = startWrapperHtml + " Confirm Cancel" + endWrapperHtml; + break; + case "textarea": + input.html = startWrapperHtml + "" + endWrapperHtml; + break; + case "textarea-confirm": + input.html = startWrapperHtml + "Confirm Cancel" + endWrapperHtml; + break; + case "number-confirm" : + input.html = startWrapperHtml + " Confirm Cancel" + endWrapperHtml; + break; + default: // text input + input.html = startWrapperHtml + "" + endWrapperHtml; + break; + } + return input; +} + +function getInputField(callingElement) { + // Update datatables cell value + var inputField; + switch ($(callingElement).prop('nodeName').toLowerCase()) { + case 'a': // This means they're using confirmation buttons + if ($(callingElement).siblings('input').length > 0) { + inputField = $(callingElement).siblings('input'); + } + if ($(callingElement).siblings('select').length > 0) { + inputField = $(callingElement).siblings('select'); + } + if ($(callingElement).siblings('textarea').length > 0) { + inputField = $(callingElement).siblings('textarea'); + } + break; + default: + inputField = $(callingElement); + } + return inputField; +} + +function sanitizeCellValue(cellValue) { + if (typeof (cellValue) === 'undefined' || cellValue === null || cellValue.length < 1) { + return ""; + } + + // If not a number + if (isNaN(cellValue)) { + // escape single quote + cellValue = cellValue.replace(/'/g, "'"); + } + return cellValue; +} diff --git a/src/main/resources/static/js/gw.history.js b/src/main/resources/static/js/gw.history.js new file mode 100644 index 000000000..058835f66 --- /dev/null +++ b/src/main/resources/static/js/gw.history.js @@ -0,0 +1,88 @@ +/** + * + * author: Z.S. + * date: Mar 12 2021 + * + */ + +GW.history = { + + deleteAllJupyter: function(hostid, callback){ + + if(confirm("WARNING: Are you sure to remove all the history? This is permanent and cannot be recovered.")){ + + $.ajax({ + + url: "delAllHistory", + + method: "POST", + + data: { id: hostid} + + }).done(function(msg){ + + console.log("All the history has been deleted, refresh the history table"); + + callback(hostid); + + }).fail(function(jxr, status){ + + console.error(status + " failed to update notes, the server may lose connection. Try again. "); + + }); + + } + + }, + + deleteNoNotesJupyter: function(hostid, callback){ + + if(confirm("WARNING: Are you sure to remove all the history without notes? This is permanent and cannot be recovered.")){ + + $.ajax({ + + url: "delNoNotesHistory", + + method: "POST", + + data: { id: hostid} + + }).done(function(msg){ + + console.log("history without notes are deleted, refresh the history table"); + + callback(hostid); + + }).fail(function(jxr, status){ + + console.error(status + " failed to update notes, the server may lose connection. Try again. "); + + }); + + } + + }, + + updateNotesOfAHistory: function(hisid, notes){ + + $.ajax({ + + url: "edit", + + method: "POST", + + data: { type: "history", id: hisid, notes: notes} + + }).done(function(msg){ + + console.log("notes is updated"); + + }).fail(function(jxr, status){ + + console.error(status + " failed to update notes, the server may lose connection. Try again. "); + + }); + }, + + +} diff --git a/src/main/resources/static/js/gw.host.js b/src/main/resources/static/js/gw.host.js index 9ffd28d5b..c5021702e 100644 --- a/src/main/resources/static/js/gw.host.js +++ b/src/main/resources/static/js/gw.host.js @@ -1337,6 +1337,7 @@ GW.host = { }, + deleteSelectedJupyter: function(){ if(confirm("Are you sure to remove all the selected history? This is permanent.")){ @@ -1459,6 +1460,27 @@ GW.host = { }, + historyTableCellUpdateCallBack: function(updatedCell, updatedRow, oldValue){ + + console.log("The new value for the cell is: " + updatedCell.data()); + console.log("The old value for that cell was: " + oldValue); + console.log("The values for each cell in that row are: " + updatedRow.data()); + + // The values for each cell in that row are: ,http://localhost:8888/api/contents/work/GMU%20workspace/COVID/covid_win_laptop.ipynb,xyz,2021-03-03 22:00:32.913,View Download Delete + + var thecheckbox = updatedRow.data()[0] + + var hisid = $(thecheckbox).attr("id").substring(9) + + console.log("history id: " + hisid) + + var newvalue = updatedRow.data()[2] + + GW.history.updateNotesOfAHistory(hisid, newvalue); + + }, + + recent: function(hid){ console.log("Show the history of all previously executed scripts/jupyter notebok"); @@ -1486,15 +1508,18 @@ GW.host = { var content = "

Recent History

"+ "
"+ - " "+ + "
"+ + " "+ + " "+ " "+ - " "+ + "
"+ - " "+ + "
"+ " "+ " "+ " "+ " "+ + " "+ " "+ // " "+ " "+ @@ -1512,8 +1537,9 @@ GW.host = { msg[i].id+"')\">Delete "; content += " "+ - " "+ + " "+ " "+ + " "+ " "+ // status_col + detailbtn + @@ -1525,6 +1551,29 @@ GW.host = { $("#host-history-browser").html(content); + // initialize the tab with editable cells + + var table = $('.host_history_table').DataTable(); + + table.MakeCellsEditable({ + "onUpdate": GW.host.historyTableCellUpdateCallBack, + "columns": [2], + "allowNulls": { + "columns": [2], + "errorClass": 'error' + }, + "confirmationButton": { // could also be true + "confirmCss": 'my-confirm-class', + "cancelCss": 'my-cancel-class' + }, + "inputTypes": [ + { + "column": 2, + "type": "text", + "options": null + }] + }); + // $("#all-selected").on("click", function(){}); $('#all-selected').change(function(){ @@ -1550,6 +1599,18 @@ GW.host = { }); + $("#deleteHostHistoryNoNoteBtn").on("click", function(){ + + GW.history.deleteNoNotesJupyter(hid, GW.host.recent); + + }) + + $("#deleteHostHistoryAllBtn").on("click", function(){ + + GW.history.deleteAllJupyter(hid, GW.host.recent); + + }) + $("#compareHistoryBtn").on("click", function(){ GW.comparison.show(); diff --git a/src/main/resources/templates/geoweaver.html b/src/main/resources/templates/geoweaver.html index c4e1a07d1..6278e005a 100644 --- a/src/main/resources/templates/geoweaver.html +++ b/src/main/resources/templates/geoweaver.html @@ -7,7 +7,7 @@ - + @@ -109,6 +109,10 @@ + + + +
ProcessNotes (Click to Edit)Begin TimeStatusAction
"+msg[i].name+""+msg[i].notes+""+msg[i].begin_time+"