This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tail.git
commit 7984a843cefd1855b8c36f2692eeea37fbef5c70 Author: Bertrand Delacretaz <[email protected]> AuthorDate: Fri Aug 7 13:46:15 2015 +0000 SLING-4897 - Web console log tail plugin, contributed by Varun Nagpal, thanks! git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1694685 13f79535-47bb-0310-9956-ffa450edef68 --- README.md | 5 + pom.xml | 125 ++++++ src/main/java/org/apache/sling/tail/LogFilter.java | 27 ++ .../sling/tail/impl/LogTailerWebConsolePlugin.java | 458 +++++++++++++++++++++ src/main/resources/libs/tail/css/tail.css | 121 ++++++ src/main/resources/libs/tail/js/tail.js | 425 +++++++++++++++++++ 6 files changed, 1161 insertions(+) diff --git a/README.md b/README.md new file mode 100644 index 0000000..849d6f9 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +sling-logtail +============= + +Sling bundle encapsulating a Web Console for tailing logs over a browser +Install the bundle via System Console or mvn sling:install on any running sling instance and navigate to /system/console/tail diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..26653e5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>24</version> + </parent> + + <artifactId>org.apache.sling.tail</artifactId> + <version>0.0.1-SNAPSHOT</version> + <packaging>bundle</packaging> + + <name>Apache Sling Log Tail Implementation</name> + <description> + This bundle enables a web tail view of the system log files. + </description> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Sling-Bundle-Resources> + /libs/tail/css, /libs/tail/js + </Sling-Bundle-Resources> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + <extensions>true</extensions> + <executions> + <execution> + <id>generate-scr-descriptor</id> + <goals> + <goal>scr</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd</artifactId> + <version>2.1.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- OSGi Libraries not included here --> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <version>4.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + <version>4.2.0</version> + <scope>provided</scope> + </dependency> + + <!-- servlet API for the web console plugin --> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <version>2.3</version> + </dependency> + + <!-- Required for log tail plugin --> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.webconsole</artifactId> + <version>3.1.8</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.json</artifactId> + <version>2.0.6</version> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.log</artifactId> + <version>4.0.0</version> + </dependency> + + </dependencies> + +</project> diff --git a/src/main/java/org/apache/sling/tail/LogFilter.java b/src/main/java/org/apache/sling/tail/LogFilter.java new file mode 100644 index 0000000..030cd8d --- /dev/null +++ b/src/main/java/org/apache/sling/tail/LogFilter.java @@ -0,0 +1,27 @@ +package org.apache.sling.tail; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * + */ +public interface LogFilter { + boolean eval(String input); +} diff --git a/src/main/java/org/apache/sling/tail/impl/LogTailerWebConsolePlugin.java b/src/main/java/org/apache/sling/tail/impl/LogTailerWebConsolePlugin.java new file mode 100644 index 0000000..78d8668 --- /dev/null +++ b/src/main/java/org/apache/sling/tail/impl/LogTailerWebConsolePlugin.java @@ -0,0 +1,458 @@ +package org.apache.sling.tail.impl; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.FileAppender; +import org.apache.felix.scr.annotations.*; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.webconsole.AbstractWebConsolePlugin; +import org.apache.felix.webconsole.WebConsoleConstants; +import org.apache.sling.commons.json.io.JSONWriter; +import org.apache.sling.tail.LogFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.util.*; + +/** + * + */ +@Component +@Service(value = { Servlet.class }) +@Properties({ + @Property(name=org.osgi.framework.Constants.SERVICE_DESCRIPTION, + value="Apache Sling Web Console Plugin to tail log(s) of this Sling instance"), + @Property(name= WebConsoleConstants.PLUGIN_LABEL, value=LogTailerWebConsolePlugin.LABEL), + @Property(name=WebConsoleConstants.PLUGIN_TITLE, value=LogTailerWebConsolePlugin.TITLE), + @Property(name="felix.webconsole.configprinter.modes", value={"always"}) +}) +public class LogTailerWebConsolePlugin extends AbstractWebConsolePlugin { + public static final String LABEL = "tail"; + public static final String TITLE = "Tail Logs"; + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private static final int LINES_TO_TAIL = 100; + private static final String POSITION_COOKIE = "log.tail.position"; + private static final String FILTER_COOKIE = "log.tail.filter"; + private static final String MODIFIED_COOKIE = "log.modified"; + private static final String CREATED_COOKIE = "log.created"; + + private String fileName = ""; + private File errLog; + + @Override + protected void renderContent(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(isAjaxRequest(request)) { + + parseCommand(request, response); + + RandomAccessFile randomAccessFile = null; + + try { + + try { + randomAccessFile = new RandomAccessFile(errLog, "r"); + log.debug("Tailing file " + fileName + " of length " + randomAccessFile.length()); + } catch (Exception e) { + log.error("Error reading " + fileName, e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + return; + } + + JSONWriter json = new JSONWriter(response.getWriter()); + json.setTidy(true); + json.object(); + + boolean reverse = false; + //long created = getCreatedTimestampFromCookie(request); + /*long modified = getModifiedTimestampFromCookie(request); + if(errLog.lastModified() == modified) { + json.endObject(); + return; + } + else { + persistCookie(response, MODIFIED_COOKIE, String.valueOf(errLog.lastModified())); + }*/ + long pos = getPositionFromCookie(request); + if(pos < 0) { + pos = randomAccessFile.length()-1; + reverse = true; + } + else if(pos > randomAccessFile.length()) {//file rotated + pos = 0; + } + LogFilter[] query = getQueryFromCookie(request); + + if(reverse) { + randomAccessFile.seek(pos); + if(randomAccessFile.read() == '\n') { + pos--; + randomAccessFile.seek(pos); + if(randomAccessFile.read() == '\r') { + pos--; + } + } + + json.key("content").array(); + int found = 0; + StringBuilder sb = new StringBuilder(); + String line = null; + List<String> lines = new ArrayList<String>(); + while(found != LINES_TO_TAIL && pos > 0) { + boolean eol = false; + randomAccessFile.seek(pos); + int c = randomAccessFile.read(); + if(c == '\n') { + found++; + sb = sb.reverse(); + line = sb.toString(); + sb = new StringBuilder(); + eol = true; + pos--; + if(pos > 0) { + randomAccessFile.seek(pos); + if(randomAccessFile.read() == '\r') { + pos--; + } + } + } + else { + sb.append((char)c); + pos--; + } + + if(eol) { + if(filter(line, query)){ + lines.add(line); + } + } + } + + if(pos < 0) { + if(filter(line, query)){ + lines.add(line); + } + } + for(int i=lines.size()-1; i > -1; i--) { + json.object().key("line").value(lines.get(i)).endObject(); + } + json.endArray(); + json.endObject(); + } + else { + randomAccessFile.seek(pos); + String line = null; + int lineCount = 0; + json.key("content").array(); + boolean read = true; + while(read) { + StringBuilder input = new StringBuilder(); + int c = -1; + boolean eol = false; + + while (!eol) { + switch (c = randomAccessFile.read()) { + case -1: + case '\n': + eol = true; + break; + case '\r': + eol = true; + long cur = randomAccessFile.getFilePointer(); + if ((randomAccessFile.read()) != '\n') { + randomAccessFile.seek(cur); + } + break; + default: + input.append((char)c); + break; + } + } + + if ((c == -1) && (input.length() == 0)) { + read = false; + continue; + } + line = input.toString(); + lineCount++; + if(lineCount == LINES_TO_TAIL) { + read = false; + } + pos = randomAccessFile.getFilePointer(); + + if(filter(line, query)){ + json.object().key("line").value(line).endObject(); + } + } + json.endArray(); + json.endObject(); + } + + persistCookie(response, POSITION_COOKIE, String.valueOf(randomAccessFile.getFilePointer())); + + } catch (Exception e) { + log.error("Error tailing " + fileName, e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + finally { + try { + if(randomAccessFile != null) { + randomAccessFile.close(); + } + } + catch (Exception e) { + log.error("Error closing " + fileName, e); + } + } + } + else { + PrintWriter printWriter = response.getWriter(); + printWriter.println("<script type=\"text/javascript\" src=\"/libs/tail/js/tail.js\"></script>"); + printWriter.println("<link href=\"/libs/tail/css/tail.css\" rel=\"stylesheet\" type=\"text/css\"></link>"); + printWriter.println("<div class=\"header-cont\">"); + printWriter.println(" <div class=\"header\" style=\"display:none;\">"); + printWriter.println(" <table>"); + printWriter.println(" <tr>"); + printWriter.println(" <td><button class=\"numbering\" title=\"Show Line Numbers\" data-numbers=\"false\">Show Line No.</button></td>"); + printWriter.println(" <td><button class=\"pause\" title=\"Pause\">Pause</button></td>"); + printWriter.println(" <td class=\"longer\"><label>Sync frequency(msec)</label>"); + printWriter.println(" <button class=\"faster\" title=\"Sync Faster\">-</button>"); + printWriter.println(" <input id=\"speed\" type=\"text\" value=\"3000\"/>"); + printWriter.println(" <button class=\"slower\" title=\"Sync Slower\">+</button></td>"); + printWriter.println(" <td><button class=\"tail\" title=\"Unfollow Tail\" data-following=\"true\">Unfollow</button></td>"); + printWriter.println(" <td><button class=\"highlighting\" title=\"Highlight\">Highlight</button></td>"); + printWriter.println(" <td><button class=\"clear\" title=\"Clear Display\">Clear</button></td>"); + printWriter.println(" <td class=\"longer\"><input id=\"filter\" type=\"text\"/><span class=\"filterClear ui-icon ui-icon-close\" title=\"Clear Filter\"> </span><button class=\"filter\" title=\"Filter Logs\">Filter</button></td>"); + printWriter.println(" <td><button class=\"refresh\" title=\"Reload Logs\">Reload</button></td>"); + printWriter.println(" <td><button class=\"sizeplus\" title=\"Bigger\">a->A</button></td>"); + printWriter.println(" <td><button class=\"sizeminus\" title=\"Smaller\">A->a</button></td>"); + printWriter.println(" <td><button class=\"top\" title=\"Scroll to Top\">Top</button></td>"); + printWriter.println(" <td><button class=\"bottom\" title=\"Scroll to Bottom\">Bottom</button></td>"); + printWriter.println(" </tr>"); + printWriter.println(" <tr>"); + printWriter.println(" <td class=\"loadingstatus\" colspan=\"2\" data-status=\"inactive\"><ul><li></li></ul></td>"); + printWriter.println(" <td>Tailing <select id=\"logfiles\">" + getOptions() + "</select></td>"); + printWriter.println(" </tr>"); + printWriter.println(" </table>"); + printWriter.println(" </div>"); + printWriter.println(" <div class=\"pulldown\" title=\"Click to show options\"> == </div>"); + printWriter.println("</div>"); + printWriter.println(""); + printWriter.println(" <div class=\"content\">"); + printWriter.println(""); + printWriter.println(" <div id=\"logarea\"></div>"); + printWriter.println(""); + printWriter.println(" </div>"); + } + } + + protected void parseCommand(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String cmd = request.getParameter("command"); + if(cmd == null) { + return; + } + + if(cmd.equals("reset")) { + deleteCookie(response, FILTER_COOKIE); + } + else if(cmd.startsWith("filter:")) { + String queryStr = cmd.substring(7); + if(queryStr.length()==0) { + deleteCookie(response, FILTER_COOKIE); + } + else { + persistCookie(response, FILTER_COOKIE, queryStr); + log.info("Filtering on : " + queryStr); + } + } + else if(cmd.startsWith("file:")) { + if(!fileName.equals(cmd.substring(5))) { + deleteCookie(response, FILTER_COOKIE); + deleteCookie(response, POSITION_COOKIE); + fileName = cmd.substring(5); + errLog = new File(filePathMap.get(fileName)); + if(!errLog.exists()) { + throw new ServletException("File " + fileName + " doesn't exist"); + } + if(!errLog.canRead()) { + throw new ServletException("Cannot read file " + fileName); + } + } + } + } + + @Override + public String getLabel() { + return LABEL; + } + + @Override + public String getTitle() { + return TITLE; + } + + private HashMap<String, String> filePathMap = new HashMap<String, String>(); + + private String getKey(File file) { + if(!filePathMap.containsKey(file.getName())) { + filePathMap.put(file.getName(), file.getAbsolutePath()); + } + return file.getName(); + } + + private String getOptions() { + Set<String> logFiles = new HashSet<String>(); + LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory(); + for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) { + for (Iterator<Appender<ILoggingEvent>> index = logger.iteratorForAppenders(); index.hasNext();) { + Appender<ILoggingEvent> appender = index.next(); + if(appender instanceof FileAppender) { + FileAppender fileAppender = (FileAppender) appender; + String logfilePath = fileAppender.getFile(); + logFiles.add(logfilePath); + } + } + } + + String logFilesHtml = "<option value=\"\"> - Select file - </option>"; + for(String logFile : logFiles) { + File file = new File(logFile); + logFilesHtml += "<option value=\"" + getKey(file) + "\">" + file.getName() + "</option>"; + } + return logFilesHtml; + } + + @Override + protected boolean isHtmlRequest( final HttpServletRequest request ) { + return !isAjaxRequest(request); + } + + private boolean isAjaxRequest( final HttpServletRequest request) { + return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); + } + + private boolean filter(String str, LogFilter[] query) { + if(query != null) { + for(LogFilter q : query) { + if(!q.eval(str)) { + return false; + } + } + } + return true; + } + + private void deleteCookie(HttpServletResponse response, String name) { + Cookie cookie = new Cookie(name, ""); + cookie.setMaxAge(0); + response.addCookie(cookie); + log.debug("Deleting cookie :: " + cookie.getName()); + } + + private void persistCookie(HttpServletResponse response, String name, String value) { + Cookie cookie = new Cookie(name , value); + //cookie.setPath("/system/console/" + LABEL); + response.addCookie(cookie); + log.debug("Adding cookie :: " + cookie.getName() + " " + cookie.getValue()); + } + + private LogFilter[] getQueryFromCookie(HttpServletRequest request) { + try { + for(Cookie cookie : request.getCookies()) { + if(cookie.getName().equals(FILTER_COOKIE)) { + String[] parts = cookie.getValue().split("&&"); + LogFilter[] conditions = new LogFilter[parts.length]; + for(int i=0; i<parts.length; i++) { + final String part = parts[i]; + conditions[i] = new LogFilter() { + public boolean eval(String input) { + return input.contains(part); + } + + public String toString() { + return part; + } + }; + } + return conditions; + } + } + } + catch (Exception e) { + + } + return null; + } + + private long getCreatedTimestampFromCookie(HttpServletRequest request) { + try { + for(Cookie cookie : request.getCookies()) { + if(cookie.getName().equals(CREATED_COOKIE)) { + return Long.parseLong(cookie.getValue()); + } + } + } + catch (Exception e) { + + } + return -1; + } + + private long getModifiedTimestampFromCookie(HttpServletRequest request) { + try { + for(Cookie cookie : request.getCookies()) { + if(cookie.getName().equals(MODIFIED_COOKIE)) { + return Long.parseLong(cookie.getValue()); + } + } + } + catch (Exception e) { + + } + return -1; + } + + private long getPositionFromCookie(HttpServletRequest request) { + try { + for(Cookie cookie : request.getCookies()) { + if(cookie.getName().equals(POSITION_COOKIE)) { + return Long.parseLong(cookie.getValue()); + } + } + } + catch (Exception e) { + log.debug("Position specified is invalid, Tailing from beginning of the file.", e); + } + return -1; + } +} diff --git a/src/main/resources/libs/tail/css/tail.css b/src/main/resources/libs/tail/css/tail.css new file mode 100644 index 0000000..062210e --- /dev/null +++ b/src/main/resources/libs/tail/css/tail.css @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.header-cont { + position:fixed; + top:0; + left:0; +} + +.pulldown { + height:20px; + cursor:pointer; + background:#CCCCCC; + margin: 0 auto; + border-radius: 0 0 25px 25px; + text-align: center; +} + +.header { + background:#F0F0F0; + border:1px solid #CCC; + margin:0px auto; + height:50px; + vertical-align:middle; +} + +.header table td { + width:5%; + text-align:center; + vertical-align:middle; +} + +.header table td.longer { + width:20%; +} + +.header table td button { + cursor:pointer; +} + +.content { + font:Courier-New; + font-size:9px; +} + +.criteria-item { + cursor:pointer; +} + +.criteria-item.selected { + background-color: #2B60DE; + color:white; +} + +.highlight-content-inner-div { + float:left; + clear:both; + width:95%; + margin-bottom: 10px; +} + +span.box { + min-width:50px; + text-overflow:clip; + border:1px solid black; + margin-right: 5px; + margin-left: 5px; + margin-top:5px; + margin-bottom:5px; +} + +.criteria-list { + padding: 0; + list-style:none; + margin:0; +} + +#criteria { + min-height: 200px; + border:1px solid; +} + +.lineNumberCol { + background-color: grey; + color: white; +} + +#speed { + width:50px; +} + +.loadingstatus li { + font-size:30px; + color:grey; + list-style: inherit !important; +} + +.hide { + display: none; +} + +.filterClear { + display: inline-block; + vertical-align: middle; +} \ No newline at end of file diff --git a/src/main/resources/libs/tail/js/tail.js b/src/main/resources/libs/tail/js/tail.js new file mode 100644 index 0000000..fc41594 --- /dev/null +++ b/src/main/resources/libs/tail/js/tail.js @@ -0,0 +1,425 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + var logarea; + var follow = true; + var searchCriteria = []; + var logfontsize = 9; + var refreshInterval = 3000; + var _load = false; + var _lineNum = 0; + var _isLineNumShowing = false; + + var modal; + + var showLine = function(text) { + logarea.append("<br/>"); + + if(text.indexOf("\t") == 0) { + text = "<span style='padding-left:5%;'>" + text + "</span>"; + } + else { + text = "<span>" + text + "</span>" + } + + for(var i=0; i < searchCriteria.length; i++) { + if(text.indexOf(searchCriteria[i]["string"]) >=0) { + if(searchCriteria[i]["bold"]) {text = "<b>" + text + "</b>";} + if(searchCriteria[i]["italic"]) {text = "<i>" + text + "</i>";} + var color = "red"; if(searchCriteria[i]["forecolor"]) {color = searchCriteria[i]["forecolor"];} + var bg = ""; if(searchCriteria[i]["backcolor"]) {bg = searchCriteria[i]["backcolor"];} + text = "<span style='color:" + color + ";background-color:" + bg + "'>" + text + "</span>"; + } + } + _lineNum++; + logarea.append("<a class='lineNumberCol " + (_isLineNumShowing?"":"hide") + "' name='line_" + _lineNum + "'> " + _lineNum + " </a>" + text); + + }; + + var loadTail = function() { + _load = false; + + $.ajax({ + url: "/system/console/tail", + data: {}, + dataType: "json", + method: "GET", + async: true, + success: function(s) { + if(s.content) { + $(".loadingstatus").data("status", "active"); + for(var i = 0; i < s.content.length; i++) { + var line = s.content[i].line; + showLine(line); + } + if(follow) { + $("html,body").scrollTop(logarea[0].scrollHeight); + } + } + else { + $(".loadingstatus").data("status", "inactive"); + } + }, + error: function(e) { + $(".loadingstatus").data("status", "error"); + }, + complete: function(d) { + _load = true; + } + }); + }; + + var sendCmd = function(cmd, callback) { + $.ajax({ + url: "/system/console/tail", + data: {command: cmd}, + dataType: "json", + method: "POST", + async: true, + success: function(s) {}, + error: function(e) {}, + complete: function(d) { + if(callback) { + callback(); + } + } + }); + } + + var clearAll = function() { + logarea.find("span").css({"color":"black", "background-color":"white"}); + var b = logarea[0].getElementsByTagName('b'); + + while(b.length) { + var parent = b[ 0 ].parentNode; + while( b[ 0 ].firstChild ) { + parent.insertBefore( b[ 0 ].firstChild, b[ 0 ] ); + } + parent.removeChild( b[ 0 ] ); + } + + var i = logarea[0].getElementsByTagName('i'); + + while(i.length) { + var parent = i[ 0 ].parentNode; + while( i[ 0 ].firstChild ) { + parent.insertBefore( i[ 0 ].firstChild, i[ 0 ] ); + } + parent.removeChild( i[ 0 ] ); + } + + }; + + $(document).ready(function(e){ + logarea = $("#logarea"); + + if ($("#highlighting").length === 0) { + var insertModal = $("<div>", {"class": "", "id": "highlighting", "style": "width:30rem", "title": "Highlighting"}).hide(); + $(document.body).append(insertModal); + var criteria = ""; + for(var i=0; i < searchCriteria.length; i++) { + criteria = criteria + "<li class='criteria-item'><div class='box'>" + searchCriteria[i]["string"] + "</div></li>"; + } + + var content = "<div id='criteria' class='highlight-content-inner-div'><ul class='criteria-list'>" + criteria + "</ul></div>" + + "<div class='highlight-content-inner-div'><button class='add'>Add</button><button class='delete'>Delete</button></div>" + + "<div class='highlight-content-inner-div'><input id='search'>String</input></div>" + + "<div class='highlight-content-inner-div'><input type='checkbox' id='bold' value='off'>Bold</input> <input type='checkbox' id='italic' value='off'>Italic</input></div> " + + "<div class='highlight-content-inner-div'><input type='color' id='forecolor' value='#FFFFFF'>Foreground Color</input> <input type='color' id='backcolor' value='#ff0000'>Background Color</input></div> "; + $("#highlighting").append(content); + var buttonsArr = []; + buttonsArr.push({ + text : "OK", + click : function() { + clearAll(); + if(searchCriteria.length > 0) { + var logEntries = logarea[0].getElementsByTagName("span"); + for(var j=0; j<logEntries.length; j++) { + var text = logEntries[j].innerHTML; + for(var i=0; i < searchCriteria.length; i++) { + if(text.indexOf(searchCriteria[i]["string"]) >=0) { + if(searchCriteria[i]["bold"]) {text = "<b>" + text + "</b>";} + if(searchCriteria[i]["italic"]) {text = "<i>" + text + "</i>";} + var color = "red"; if(searchCriteria[i]["forecolor"]) {color = searchCriteria[i]["forecolor"];} + var bg = ""; if(searchCriteria[i]["backcolor"]) {bg = searchCriteria[i]["backcolor"];} + logEntries[j].innerHTML = text; + logEntries[j].style.color = color; + logEntries[j].style.backgroundColor = bg; + } + } + } + } + $(this).dialog("close"); + } + }); + buttonsArr.push({ + text : "Cancel", + click : function() { + $(this).dialog("close"); + } + }); + + var modal = $("#highlighting").dialog({ + autoOpen: false, + width: "30rem", + modal: true, + buttons: buttonsArr, + open: function(event, ui) { + $(this).data("bkp-load-val", _load); + if(_load) { + $(".pause").click(); + } + }, + close: function(event, ui) { + if($(this).data("bkp-load-val") && !_load) { + $(".pause").click(); + } + $(this).removeData("bkp-load-val"); + } + }); + + $("#highlighting").find(".add").click(function(e) { + var val = $("#highlighting").find("#search").val(); + var color = $("#forecolor").val(); + var bg = $("#backcolor").val(); + var b = $("#bold").is(":checked"); + var i = $("#italic").is(":checked"); + var index = searchCriteria.length; + searchCriteria.push({"string":val, "bold":b, "italic":i, "forecolor":color, "backcolor":bg}); + $("#highlighting").find(".criteria-list").append("<li class='criteria-item' data-index='" + index + "'><span class='box' style='color:"+color+";background-color:"+bg+";font:" + (b?" bold ":"") + (i?" italic ":"") + ";'>" + val + "</span>" + val + "</li>"); + $("#highlighting").find("#search").val(""); + $("#highlighting").find("#bold")[0].checked = false; + $("#highlighting").find("#italic")[0].checked = false; + }); + + $("#highlighting").find(".delete").click(function(e) { + var $selected = $("#highlighting").find(".criteria-item.selected"); + if($selected.length == 0) return; + + var index = parseInt($(e.target).data("index")); + searchCriteria.splice(index, 1); + + $selected.remove(); + }); + + $("#highlighting").on("click", ".criteria-item", function(e) { + $(e.target).toggleClass("selected").siblings().removeClass("selected"); + }); + + } + + $(".highlighting").click(function(e) { + $("#highlighting").dialog("open"); + }); + + $(".clear").click(function(e) { + logarea.empty(); + }); + + $(".refresh").click(function(e) { + sendCmd("reset"); + _load = false; + logarea.empty(); + $("#filter").val(""); + document.cookie = "log.tail.position=0"; + _load = true; + }); + + $(".filter").click(function(e) { + var filterVal = $("#filter").val(); + sendCmd("filter:"+filterVal); + }); + + $(".filterClear").click(function(e) { + $("#filter").val(""); + $(".filter").click(); + }); + + $(".tail").click(function(e) { + var $elem = $(e.target); + var currStatus = $elem.data("following"); + $elem.data("following", !currStatus); + follow = $elem.data("following"); + if(follow) { + $elem.attr("title", "Unfollow Tail"); + $elem.html("Unfollow"); + } + else { + $elem.attr("title", "Follow Tail"); + $elem.html("Follow"); + } + }); + + $(".sizeplus").click(function(e) { + logfontsize++; + $(".content").css("font-size", logfontsize+"px"); + }); + + $(".sizeminus").click(function(e) { + logfontsize--; + $(".content").css("font-size", logfontsize+"px"); + }); + + $(".pause").click(function(e) { + var $elem = $(e.target); + if(_load) { + $elem.attr("title", "Click to Resume"); + $elem.html("Resume"); + + $(".loadingstatus").data("status", "inactive"); + _load = false; + } + else { + $elem.attr("title", "Click to Pause"); + $elem.html("Pause"); + _load = true; + } + }); + + $(".top").click(function(e) { + $("html,body").scrollTop(0); + if(follow) { + $(".tail").click(); + } + }); + + $(".bottom").click(function(e) { + $("html,body").scrollTop(logarea[0].scrollHeight); + follow = true; + }); + + $(".numbering").click(function(e) { + var $elem = $(e.target); + var currStatus = $elem.data("numbers"); + $elem.data("numbers", !currStatus); + _isLineNumShowing = $elem.data("numbers"); + if(_isLineNumShowing) { + $(".lineNumberCol").removeClass("hide"); + $elem.attr("title", "Hide Line Numbers"); + $elem.html("Hide Line No."); + } + else { + $(".lineNumberCol").addClass("hide"); + $elem.attr("title", "Show Line Numbers"); + $elem.html("Show Line No."); + } + }); + + $(".slower").click(function(e) { + refreshInterval += 500; + $("#speed").val(refreshInterval); + }); + + $(".faster").click(function(e) { + if(refreshInterval >= 1500) { + refreshInterval -= 500; + } + $("#speed").val(refreshInterval); + }); + + var timerFunc = function(){ + if(_load) { + loadTail(); + } + }; + + $("#speed").change(function(e) { + refreshInterval = parseInt($(e.target).val()); + clearInterval(intervalObj); + intervalObj = setInterval(timerFunc, refreshInterval); + }); + + var intervalObj = setInterval(timerFunc, refreshInterval); + + var getScrollbarWidth = function() { + var outer = document.createElement("div"); + outer.style.visibility = "hidden"; + outer.style.width = "100px"; + outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps + + document.body.appendChild(outer); + + var widthNoScroll = outer.offsetWidth; + // force scrollbars + outer.style.overflow = "scroll"; + + // add innerdiv + var inner = document.createElement("div"); + inner.style.width = "100%"; + outer.appendChild(inner); + + var widthWithScroll = inner.offsetWidth; + + // remove divs + outer.parentNode.removeChild(outer); + + return widthNoScroll - widthWithScroll; + }; + + var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0) - getScrollbarWidth(); + + $(".pulldown").width(w); + $(".header").width(w); + $(".pulldown").click(function() { + if($(".header").is(":visible")) { + $(".header").slideUp(); + } + else { + $(".header").slideDown(); + $(".pulldown").attr("title", "Click to hide options"); + } + }); + + var statusOpacity = 0.2; + + setInterval(function() { + var status = $(".loadingstatus").data("status"); + var color = "grey"; + switch(status) { + case "error":color="red";break; + case "inactive":color="grey";break; + case "active":color="green";break; + default:color="grey"; + } + $(".loadingstatus").find("li").css("color", color); + $(".loadingstatus").find("li").html("<span style='border-radius:10px;'>"+status+"</span>"); + + if(status == "active") { + $(".loadingstatus").fadeTo(1000, statusOpacity); + if(statusOpacity == 0.2) { + statusOpacity = 1.0; + } + else { + statusOpacity = 0.2; + } + } + else { + statusOpacity = 1.0; + $(".loadingstatus").css("opacity", statusOpacity); + } + }, 1000); + + $("#logfiles").change(function() { + var selected = $("#logfiles").val(); + if(selected != "") { + _load = false; + sendCmd("file:"+selected, function(){_load=true;}); + } + }); + }); \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
