diff --git a/assets/static/unsee.js b/assets/static/unsee.js index a8d2b79f4..476874033 100644 --- a/assets/static/unsee.js +++ b/assets/static/unsee.js @@ -1,318 +1,325 @@ -/* globals Raven */ // raven.js -/* globals moment */ // moment.js +const $ = require("jquery"); +const moment = require("moment"); +const Raven = require("raven-js"); -/* globals Alerts, Autocomplete, Colors, Config, Counter, Grid, Filters, Progress, Silence, Summary, Templates, UI, Unsilence, Watchdog */ +const alerts = require("./alerts"); +const autocomplete = require("./autocomplete"); +const colors = require("./colors"); +const config = require("./config"); +const counter = require("./counter"); +const grid = require("./grid"); +const filters = require("./filters"); +const progress = require("./progress"); +const silence = require("./silence"); +const summary = require("./summary"); +const templates = require("./templates"); +const ui = require("./ui"); +const unsilence = require("./unsilence"); +const watchdog = require("./watchdog"); -/* exported Unsee */ -var Unsee = (function() { +var timer = false; +var version = false; +var refreshInterval = 15; +var hiddenAt = false; - var timer = false; - var version = false; - var refreshInterval = 15; - var hiddenAt = false; +var selectors = { + refreshButton: "#refresh", + errors: "#errors", + instanceErrors: "#instance-errors", +}; - var selectors = { - refreshButton: "#refresh", - errors: "#errors", - instanceErrors: "#instance-errors", - }; +function parseAJAXError(xhr, textStatus) { + // default to textStatus, it's usually just "error" string + var err = textStatus; + if (xhr.readyState === 0) { + // ajax() completed but request wasn't send + err = "Connection to the remote endpoint failed"; + } else if (xhr.responseJSON && xhr.responseJSON.error) { + // there's response JSON and an error key in it + err = xhr.responseJSON.error; + } else if (xhr.responseText) { + // else check response as a string + err = xhr.responseText; + } + return err; +} - // when user switches to a different tab but keeps unsee tab open in the background - // some browsers (like Chrome) will try to apply some forms of throttling for the JS - // code, to ensure that there are no visual artifacts (like state alerts not removed from the page) - // redraw all alerts if we detect that the user switches from a different tab to unsee - var setupPageVisibilityHandler = function() { - // based on https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API - if (typeof document.hidden !== "undefined" && typeof document.addEventListener !== "undefined") { - document.addEventListener("visibilitychange", function() { - if (document.hidden) { - // when tab is hidden set a timestamp of that event - hiddenAt = moment().utc().unix(); - } else { - // when user switches back check if we have a timestamp - // and if autorefresh is enable - if (hiddenAt && Config.GetOption("autorefresh").Get()) { - // get the diff to see how long tab was hidden - var diff = moment().utc().unix() - hiddenAt; - if (diff > refreshInterval) { - // if it was hidden for more than one refresh cycle - // then manually refresh alerts to ensure everything - // is up to date - Unsee.Reload(); - } - } - hiddenAt = false; - } - }, false); - } - }; +function getRefreshRate() { + return refreshInterval; +} - var parseAJAXError = function(xhr, textStatus) { - // default to textStatus, it's usually just "error" string - var err = textStatus; - if (xhr.readyState === 0) { - // ajax() completed but request wasn't send - err = "Connection to the remote endpoint failed"; - } else if (xhr.responseJSON && xhr.responseJSON.error) { - // there's response JSON and an error key in it - err = xhr.responseJSON.error; - } else if (xhr.responseText) { - // else check response as a string - err = xhr.responseText; - } - return err; - }; - - var init = function() { - Progress.Init(); - - Config.Init({ - CopySelector: "#copy-settings-with-filter", - SaveSelector: "#save-default-filter", - ResetSelector: "#reset-settings" - }); - Config.Load(); - - Counter.Init(); - Summary.Init(); - Grid.Init(); - Autocomplete.Init(); - Filters.Init(); - Watchdog.Init(30, 60*15); // set watchdog to 15 minutes - - $(selectors.refreshButton).click(function() { - if (!$(selectors.refreshButton).prop("disabled")) { - Unsee.Reload(); - } - return false; - }); - - setupPageVisibilityHandler(); - }; - - var getRefreshRate = function() { - return refreshInterval; - }; - - var setRefreshRate = function(seconds) { - var rate = parseInt(seconds); +function setRefreshRate(seconds) { + var rate = parseInt(seconds); + if (isNaN(rate) || rate === null) { + // if passed rate is incorrect use select value + rate = config.getOption("refresh").Get(); if (isNaN(rate) || rate === null) { - // if passed rate is incorrect use select value - rate = Config.GetOption("refresh").Get(); - if (isNaN(rate) || rate === null) { - // if that's also borked use default 15 - rate = 15; - } + // if that's also borked use default 15 + rate = 15; } - // don't allow setting refresh rate lower than 1s - if (rate < 1) { - rate = 1; - } - refreshInterval = rate; - Progress.Reset(); - }; + } + // don't allow setting refresh rate lower than 1s + if (rate < 1) { + rate = 1; + } + refreshInterval = rate; + progress.resetTimer(); +} - var needsUpgrade = function(responseVersion) { - if (version === false) { - version = responseVersion; - return false; - } - return version != responseVersion; - }; +function needsUpgrade(responseVersion) { + if (version === false) { + version = responseVersion; + return false; + } + return version != responseVersion; +} - var updateIsReady = function() { - Progress.Complete(); - $(selectors.refreshButton).prop("disabled", true); - Counter.Hide(); - }; +function updateIsReady() { + progress.complete(); + $(selectors.refreshButton).prop("disabled", true); + counter.hide(); +} - var updateCompleted = function() { - Counter.Show(); - Filters.UpdateCompleted(); - Progress.Complete(); - $(selectors.refreshButton).prop("disabled", false); - // hack for fixing padding since input can grow and change height - $("body").css("padding-top", $(".navbar").height()); - }; +function updateCompleted() { + counter.show(); + filters.updateDone(); + progress.complete(); + $(selectors.refreshButton).prop("disabled", false); + // hack for fixing padding since input can grow and change height + $("body").css("padding-top", $(".navbar").height()); +} - var renderError = function(template, context) { - Counter.Error(); - Grid.Clear(); - Grid.Hide(); - $(selectors.errors).html(Templates.Render(template, context)); - $(selectors.errors).show(); - Counter.Unknown(); - Summary.Update({}); - document.title = "(◕ O ◕)"; - updateCompleted(); - }; +function renderError(template, context) { + counter.markError(); + grid.clear(); + grid.hide(); + $(selectors.errors).html(templates.renderTemplate(template, context)); + $(selectors.errors).show(); + counter.markUnknown(); + summary.update({}); + document.title = "(◕ O ◕)"; + updateCompleted(); +} - var handleError = function(err) { - Raven.captureException(err); - if (window.console) { - console.error(err.stack); - } - renderError("internalError", { - name: err.name, - message: err.message, - raw: err - }); - setTimeout(function() { - Unsee.WaitForNextReload(); - }, 500); - }; +function resume() { + if (config.getOption("autorefresh").Get()) { + filters.updateDone(); + } else { + filters.setPause(); + return false; + } + progress.resetTimer(); + if (timer !== false) { + clearInterval(timer); + } + timer = setTimeout(triggerReload, getRefreshRate() * 1000); +} - var upgrade = function() { - renderError("reloadNeeded", {}); - setTimeout(function() { - location.reload(); - }, 3000); - }; +function handleError(err) { + Raven.captureException(err); + if (window.console) { + console.error(err.stack); + } + renderError("internalError", { + name: err.name, + message: err.message, + raw: err + }); + setTimeout(function() { + resume(); + }, 500); +} - var triggerReload = function() { - updateIsReady(); - $.ajax({ - url: "alerts.json?q=" + Filters.GetFilters().join(","), - success: function(resp) { - Counter.Success(); - if (needsUpgrade(resp.version)) { - upgrade(); - } else { - if (resp.upstreams.counters.total === 0) { - // no upstream to use fail hard - Counter.Unknown(); - $(selectors.instanceErrors).html(""); - renderError("updateError", { - error: "Fatal error", - messages: [ "No working Alertmanager server found" ], - lastTs: Watchdog.GetLastUpdate() +function upgrade() { + renderError("reloadNeeded", {}); + setTimeout(function() { + location.reload(); + }, 3000); +} + +function triggerReload() { + updateIsReady(); + $.ajax({ + url: "alerts.json?q=" + filters.getFilters().join(","), + success: function(resp) { + counter.markSuccess(); + if (needsUpgrade(resp.version)) { + upgrade(); + } else { + if (resp.upstreams.counters.total === 0) { + // no upstream to use fail hard + counter.markUnknown(); + $(selectors.instanceErrors).html(""); + renderError("updateError", { + error: "Fatal error", + messages: [ "No working Alertmanager server found" ], + lastTs: watchdog.getLastUpdate() + }); + resume(); + } else if (resp.upstreams.counters.healthy > 0 ) { + // we have some healthy upstreams, check for failed ones + if (resp.upstreams.counters.failed > 0) { + var instances = []; + resp.upstreams.instances.sort(function(a, b){ + if(a.name < b.name) return -1; + if(a.name > b.name) return 1; + return 0; }); - Unsee.WaitForNextReload(); - } else if (resp.upstreams.counters.healthy > 0 ) { - // we have some healthy upstreams, check for failed ones - if (resp.upstreams.counters.failed > 0) { - var instances = []; - resp.upstreams.instances.sort(function(a, b){ - if(a.name < b.name) return -1; - if(a.name > b.name) return 1; - return 0; - }); - $.each(resp.upstreams.instances, function(i, instance){ - if (instance.error !== "") { - instances.push(instance); - } - }); - $(selectors.instanceErrors).html( - Templates.Render("instanceError", { - instances: instances - }) - ); - } else { - $(selectors.instanceErrors).html(""); - } - // update_alerts() is cpu heavy so it will block browser from applying css changes - // inject tiny delay between addClass() above and update_alerts() so that the browser - // have a chance to reflect those updates - setTimeout(function() { - try { - Summary.Update({}); - Filters.ReloadBadges(resp.filters); - Colors.Update(resp.colors); - Alerts.Update(resp); - updateCompleted(); - Watchdog.Pong(moment(resp.timestamp)); - Unsee.WaitForNextReload(); - if (!Watchdog.IsFatal()) { - $(selectors.errors).html(""); - $(selectors.errors).hide(""); - } - } catch (err) { - Counter.Unknown(); - handleError(err); - Unsee.WaitForNextReload(); - } - }, 50); - } else { - // we have upstreams but none is working, fail hard - Counter.Unknown(); - $(selectors.instanceErrors).html(""); - var failedInstances = []; - $.each(resp.upstreams.instances, function(i, instance) { + $.each(resp.upstreams.instances, function(i, instance){ if (instance.error !== "") { - failedInstances.push(instance); + instances.push(instance); } }); - renderError("configError", { - instances: failedInstances - }); - Unsee.WaitForNextReload(); + $(selectors.instanceErrors).html( + templates.renderTemplate("instanceError", { + instances: instances + }) + ); + } else { + $(selectors.instanceErrors).html(""); + } + // update_alerts() is cpu heavy so it will block browser from applying css changes + // inject tiny delay between addClass() above and update_alerts() so that the browser + // have a chance to reflect those updates + setTimeout(function() { + try { + summary.update({}); + filters.reloadBadges(resp.filters); + colors.update(resp.colors); + alerts.update(resp); + updateCompleted(); + watchdog.pong(moment(resp.timestamp)); + resume(); + if (!watchdog.isFatal()) { + $(selectors.errors).html(""); + $(selectors.errors).hide(""); + } + } catch (err) { + counter.markUnknown(); + handleError(err); + resume(); + } + }, 50); + } else { + // we have upstreams but none is working, fail hard + counter.markUnknown(); + $(selectors.instanceErrors).html(""); + var failedInstances = []; + $.each(resp.upstreams.instances, function(i, instance) { + if (instance.error !== "") { + failedInstances.push(instance); + } + }); + renderError("configError", { + instances: failedInstances + }); + resume(); + } + } + }, + error: function(xhr, textStatus) { + counter.markUnknown(); + $(selectors.instanceErrors).html(""); + // if fatal error was already triggered we have error message + // so don't add new one + if (!watchdog.isFatal()) { + var err = parseAJAXError(xhr, textStatus); + renderError("updateError", { + error: "Backend error", + messages: [ err ], + lastTs: watchdog.getLastUpdate() + }); + } + resume(); + } + }); +} + +function pause() { + progress.pause(); + filters.setPause(); + if (timer !== false) { + clearInterval(timer); + timer = false; + } +} + +function flash() { + var bg = $("#flash").css("background-color"); + $("#flash").css("display", "block").animate({ + backgroundColor: "#fff" + }, 300, function() { + $(this).animate({ + backgroundColor: bg + }, 100).css("display", "none"); + }); +} + +// when user switches to a different tab but keeps unsee tab open in the background +// some browsers (like Chrome) will try to apply some forms of throttling for the JS +// code, to ensure that there are no visual artifacts (like state alerts not removed from the page) +// redraw all alerts if we detect that the user switches from a different tab to unsee +function setupPageVisibilityHandler() { + // based on https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API + if (typeof document.hidden !== "undefined" && typeof document.addEventListener !== "undefined") { + document.addEventListener("visibilitychange", function() { + if (document.hidden) { + // when tab is hidden set a timestamp of that event + hiddenAt = moment().utc().unix(); + } else { + // when user switches back check if we have a timestamp + // and if autorefresh is enable + if (hiddenAt && config.getOption("autorefresh").Get()) { + // get the diff to see how long tab was hidden + var diff = moment().utc().unix() - hiddenAt; + if (diff > refreshInterval) { + // if it was hidden for more than one refresh cycle + // then manually refresh alerts to ensure everything + // is up to date + triggerReload(); } } - }, - error: function(xhr, textStatus) { - Counter.Unknown(); - $(selectors.instanceErrors).html(""); - // if fatal error was already triggered we have error message - // so don't add new one - if (!Watchdog.IsFatal()) { - var err = Unsee.ParseAJAXError(xhr, textStatus); - renderError("updateError", { - error: "Backend error", - messages: [ err ], - lastTs: Watchdog.GetLastUpdate() - }); - } - Unsee.WaitForNextReload(); + hiddenAt = false; } - }); - }; + }, false); + } +} - var pause = function() { - Progress.Pause(); - Filters.Pause(); - if (timer !== false) { - clearInterval(timer); - timer = false; +function init() { + progress.init(); + + config.init({ + CopySelector: "#copy-settings-with-filter", + SaveSelector: "#save-default-filter", + ResetSelector: "#reset-settings" + }); + config.loadFromCookies(); + + counter.init(); + summary.init(); + grid.init(); + autocomplete.init(); + filters.init(); + watchdog.init(30, 60*15); // set watchdog to 15 minutes + + $(selectors.refreshButton).click(function() { + if (!$(selectors.refreshButton).prop("disabled")) { + triggerReload(); } - }; + return false; + }); - var resume = function() { - if (Config.GetOption("autorefresh").Get()) { - Filters.UpdateCompleted(); - } else { - Filters.Pause(); - return false; - } - Progress.Reset(); - if (timer !== false) { - clearInterval(timer); - } - timer = setTimeout(Unsee.Reload, Unsee.GetRefreshRate() * 1000); - }; + setupPageVisibilityHandler(); +} - var flash = function() { - var bg = $("#flash").css("background-color"); - $("#flash").css("display", "block").animate({ - backgroundColor: "#fff" - }, 300, function() { - $(this).animate({ - backgroundColor: bg - }, 100).css("display", "none"); - }); - }; - - return { - Init: init, - Pause: pause, - WaitForNextReload: resume, - Reload: triggerReload, - GetRefreshRate: getRefreshRate, - SetRefreshRate: setRefreshRate, - Flash: flash, - ParseAJAXError: parseAJAXError - }; - -})(); +exports.init = init; +exports.pause = pause; +exports.resume = resume; +exports.triggerReload = triggerReload; +exports.getRefreshRate = getRefreshRate; +exports.setRefreshRate = setRefreshRate; +exports.flash = flash; +exports.parseAJAXError = parseAJAXError; $(document).ready(function() { @@ -326,15 +333,16 @@ $(document).ready(function() { trigger: "hover" }); - Templates.Init(); - UI.Init(); - Silence.Init(); - Unsilence.Init(); - Unsee.Init(); + colors.init($("#alerts").data("static-color-labels").split(" ")); + templates.init(); + ui.init(); + silence.init(); + unsilence.init(); + init(); // delay initial alert load to allow browser finish rendering setTimeout(function() { - Filters.SetFilters(); + filters.setFilters(); }, 100); } catch (error) { Raven.captureException(error); diff --git a/assets/static/unsee.test.js b/assets/static/unsee.test.js new file mode 100644 index 000000000..a67741f67 --- /dev/null +++ b/assets/static/unsee.test.js @@ -0,0 +1,5 @@ +test("unsee getRefreshRate()", () => { + window.jQuery = require("jquery"); + const unsee = require("./unsee"); + unsee.getRefreshRate(); +});