basicadmin-logmonitor.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. /**
  2. * Gemini Basic Administration
  3. * Script for real-time log monitoring
  4. * Attempts to use WebSockets and falls back to AJAX.
  5. */
  6. var logmonitor = (function() {
  7. var uid = 0,
  8. logBuffer = new Array(),
  9. dispCurr = 0,
  10. dispThreshold = 30,
  11. containReq = "",
  12. repaintTimeout = 0,
  13. capture = true,
  14. track = true,
  15. ignored = 0,
  16. $filter = $("#filter"),
  17. maxLines,
  18. channelID,
  19. channel,
  20. svUrl,
  21. ajaxProcessor,
  22. wsProcessor,
  23. activeProcessor,
  24. $logData = $("tbody#logdata"),
  25. $body = $("body"),
  26. $displayed = $("span#displayed"),
  27. $captured = $("span#captured");
  28. /** The WebSocket processor. */
  29. wsProcessor = (function() {
  30. function connect() {
  31. try {
  32. var url;
  33. if (svUrl.startsWith("http://")) {
  34. url = "ws://" + svUrl.substring(7);
  35. }
  36. else {
  37. url = "wss://" + svUrl.substring(8);
  38. }
  39. url = url + "?cmd=admin-log-monitor&act=ws&ch=" + channelID;
  40. var ws = new WebSocket(url, "logmonitor");
  41. }
  42. catch (failure) {
  43. return false;
  44. }
  45. // Receive data as a WebSocket event and decode from JSON before
  46. // calling the distributor.
  47. ws.onmessage = function(event) {
  48. process($.parseJSON(event.data));
  49. }
  50. // Send a keep-alive message to the server every 30 seconds.
  51. setInterval(function() {
  52. ws.send("keep-alive");
  53. }, 30000);
  54. return true;
  55. }
  56. function process(data) {
  57. for (var i = 0; i < data.updates.length; i++) {
  58. debug(data.updates[i].text, data.updates[i].level);
  59. }
  60. }
  61. function pause() {
  62. }
  63. function resume() {
  64. }
  65. return {
  66. connect: connect,
  67. pause: pause,
  68. resume: resume
  69. };
  70. })();
  71. /** The AJAX processor. */
  72. ajaxProcessor = (function() {
  73. var ajaxInterval = 0,
  74. since = 0;
  75. function processAjaxFetch(data) {
  76. if (data.uid) {
  77. since = data.uid;
  78. }
  79. if (data.items) {
  80. for (var i = 0; i < data.items.length; i++) {
  81. debug(data.items[i].text, data.items[i].level);
  82. }
  83. }
  84. }
  85. function fetchViaAjax() {
  86. $.ajax({
  87. url: svUrl + "?cmd=admin-log-monitor&act=ajax&ch=" + channelID + "&since=" + since,
  88. dataType: 'json',
  89. type: 'get',
  90. data: {},
  91. success: processAjaxFetch
  92. });
  93. }
  94. function connect() {
  95. resume();
  96. return true;
  97. }
  98. function pause() {
  99. window.clearInterval(ajaxInterval);
  100. }
  101. function resume() {
  102. window.clearInterval(ajaxInterval);
  103. ajaxInterval = window.setInterval(fetchViaAjax, 500);
  104. }
  105. return {
  106. connect: connect,
  107. pause: pause,
  108. resume: resume
  109. };
  110. })();
  111. /** Paint details; wire up the filter; and start fetching log items. */
  112. function init(sv, cid, channels) {
  113. svUrl = sv;
  114. channelID = cid;
  115. channel = channels[cid];
  116. maxLines = channel.size;
  117. createSeverityClasses();
  118. debug("Attaching listener to log file at " + standardDateString(new Date()) + ".", 50);
  119. displayChannelInfo();
  120. // Capture keypresses on the filter box.
  121. $filter.focus().keyup(function(event) {
  122. var curVal = containReq;
  123. containReq = this.value.toLowerCase();
  124. if (curVal != containReq) {
  125. if (repaintTimeout > 0) {
  126. window.clearTimeout(repaintTimeout);
  127. }
  128. repaintTimeout = window.setTimeout(repaintLog, 100);
  129. }
  130. });
  131. $("#togglecapture").click(toggleCapture);
  132. $("#toggletrack").click(toggleTrack);
  133. $("#levels").find(".level").each(function (idx, el) {
  134. el = $(el);
  135. el.click(function() {
  136. setThreshold(el.attr("level"));
  137. });
  138. });
  139. connect();
  140. }
  141. /** Connect to the server. Try WebSockets first and then AJAX. */
  142. function connect() {
  143. var good;
  144. good = wsProcessor.connect();
  145. if (good) {
  146. activeProcessor = wsProcessor;
  147. }
  148. else {
  149. good = ajaxProcessor.connect();
  150. activeProcessor = ajaxProcessor;
  151. }
  152. }
  153. /** Render a summary of the current channel's filter settings. */
  154. function displayChannelInfo() {
  155. var toAdd;
  156. if (channel) {
  157. toAdd = channel.name
  158. + " (levels " + channel.min
  159. + " to " + channel.max
  160. + (channel.contains ? (" containing \"" + channel.contains + "\"") : "")
  161. + (channel .regex ? (" matching regex \"" + channel.regex + "\"") : "")
  162. + ")";
  163. }
  164. else {
  165. toAdd = "Unknown log channel";
  166. }
  167. $("span#channel").html(toAdd);
  168. }
  169. /** Toggle the pause/capture mode. */
  170. function toggleCapture() {
  171. if (capture) {
  172. capture = false;
  173. $("div#banner span#controls a#togglecapture").addClass("selected").blur();
  174. activeProcessor.pause();
  175. }
  176. else {
  177. repaintLog();
  178. capture = true;
  179. $("div#banner span#controls a#togglecapture").removeClass("selected").blur();
  180. activeProcessor.resume();
  181. }
  182. }
  183. /** Toggle the scroll tracking mode. */
  184. function toggleTrack() {
  185. if (track) {
  186. track = false;
  187. $("div#banner span#controls a#toggletrack").removeClass("selected").blur();
  188. }
  189. else {
  190. jumpToBottom();
  191. track = true;
  192. $("div#banner span#controls a#toggletrack").addClass("selected").blur();
  193. }
  194. }
  195. /** Capture a log item and display the item if it meets the client-side filters. */
  196. function debug(text, level) {
  197. var logItem = { 'uid': uid++, 'level': level, 'text': text };
  198. // Remove top item from the buffer if at maximum length.
  199. if (logBuffer.length >= maxLines) {
  200. logBuffer.shift();
  201. if (capture) {
  202. var toRemove = uid - maxLines - 1;
  203. $("tr#line" + toRemove).remove();
  204. }
  205. }
  206. // Add the item to our buffer.
  207. logBuffer.push(logItem);
  208. if (meetsCriteria(logItem)) {
  209. displaySingle(logItem);
  210. }
  211. showStats();
  212. }
  213. /** Turns a log item into HTML. */
  214. function renderSingleAsHtml(logItem) {
  215. return "<tr id='line" + logItem.uid + "'><td class='content l" + logItem.level + "'>" + logItem.text + "</td></tr>";
  216. }
  217. /** Add a log item into the view assuming we're capturing. */
  218. function displaySingle(logItem) {
  219. if (capture) {
  220. var toAdd = renderSingleAsHtml(logItem);
  221. dispCurr++;
  222. $logData.append(toAdd);
  223. jumpIfFollow();
  224. }
  225. else {
  226. ignored++;
  227. }
  228. }
  229. /** If we're following the log, scroll down. */
  230. function jumpIfFollow() {
  231. if (track) {
  232. jumpToBottom();
  233. }
  234. }
  235. /** Scroll to the bottom of the page. */
  236. function jumpToBottom() {
  237. $body.scrollTop(10000000);
  238. }
  239. /* Adjust the minimum threshold and repaint. */
  240. function setThreshold(newThreshold) {
  241. dispThreshold = newThreshold;
  242. $("div#banner span#levels a").removeClass("selected").blur();
  243. $("div#banner span#levels a#t" + dispThreshold).addClass("selected");
  244. repaintLog();
  245. }
  246. /** Clear the view and re-render it entirely given current client-side settings. */
  247. function repaintLog() {
  248. dispCurr = 0;
  249. var toAdd = "";
  250. for (var i = 0; i < logBuffer.length; i++) {
  251. if (meetsCriteria(logBuffer[i])) {
  252. toAdd += renderSingleAsHtml(logBuffer[i]);
  253. dispCurr++;
  254. }
  255. }
  256. $logData.html(toAdd);
  257. showStats();
  258. jumpIfFollow();
  259. }
  260. /** Determines if a log item meets the current client-side criteria. */
  261. function meetsCriteria(logItem) {
  262. if (logItem.level >= dispThreshold) {
  263. return ( (containReq == "")
  264. || (logItem.text.toLowerCase().indexOf(containReq) >= 0)
  265. );
  266. }
  267. return false;
  268. }
  269. /** Display how many lines are visible versus captured. */
  270. function showStats() {
  271. var displayed = $logData.children().length;
  272. $displayed.text(displayed);
  273. $captured.text(logBuffer.length);
  274. }
  275. /** Create a gradient of colors for log severity. */
  276. function createSeverityClasses() {
  277. var sr = 168, sg = 168, sb = 128, offset = 0,
  278. toAdd = "";
  279. function createGradient(dr, dg, db, num) {
  280. var res = "";
  281. for (i = 0; i < num; i++) {
  282. res += ".l" + (offset++) + " { color: rgb(" + sr + "," + sg + "," + sb + "); } ";
  283. sr += dr; if (sr > 255) sr = 255; if (sr < 0) sr = 0;
  284. sg += dg; if (sg > 255) sg = 255; if (sg < 0) sg = 0;
  285. sb += db; if (sb > 255) sb = 255; if (sb < 0) sb = 0;
  286. }
  287. return res;
  288. }
  289. toAdd += createGradient(-4, -4, 4, 15); // Start with gray
  290. toAdd += createGradient(-1, 4, -6, 20); // Blend to blue
  291. toAdd += createGradient(-6, -8, -4, 20); // Blend to green
  292. toAdd += createGradient(-6, -6, -6, 5); // Blend to black
  293. toAdd += createGradient(12, 6, 0, 20); // Blend to orange
  294. toAdd += createGradient(1, -2, 0, 10); // Blend to red
  295. toAdd += createGradient(4, -10, 0, 11); // Blend to bright red
  296. $("head").append("<style>" + toAdd + "</style>");
  297. }
  298. return {
  299. init: init
  300. };
  301. })();