Function.prototype.bind = function(object) { var method = this; return function() { return method.apply(object, arguments); }; }; var viewModel = { graphTitle: ko.observable(""), timeWindow: ko.observable("1.hour"), clusters: ko.observableArray([]), servers: ko.observableArray([]), metrics: ko.observableArray([]), selectedCluster: ko.observable(""), selectedServer: ko.observable(""), selectedMetric: ko.observable(""), metricsLoading: ko.observable(false), currentGraph: ko.observableArray([]), savedGraphs: ko.observableArray([]), savingSavedGraphs: ko.observable(false), selectSavedGraph: function(i) { var graph = this.savedGraphs()[i]; this.clearGraph(); this.graphTitle(graph.name); _.each(graph.metrics, function(metric) { this.add(metric); }.bind(this)); }, saveCurrentGraph: function() { this.savedGraphs.push({name: this.graphTitle(), metrics: this.currentGraph()}); this.drawGraphIfReady(); }, removeSavedGraph: function(i) { if (confirm("Are you sure?")) { var graph = this.savedGraphs()[i]; this.savedGraphs.remove(graph); } }, clearGraph: function() { for(var i = 0; i < this.currentGraph().length; i++) this.remove(i); }, scale: function(i) { var metric = this.currentGraph()[i]; metric.scale = !metric.scale; this.currentGraph.valueHasMutated(); this.drawGraphIfReady(); }, remove: function(i) { var metric = this.currentGraph()[i]; this.currentGraph.remove(metric); this.drawGraphIfReady(); }, fetchClusters: function() { $.getJSON("/clusters.json", function(data) { $.each(data, function(i, e) { this.clusters.push(e); }.bind(this)); }.bind(this)); }, clusterSelected: function() { var cluster = this.selectedCluster(); $.getJSON("/" + cluster + "/servers.json", function(data) { $.each(data, function(i, e) { this.servers.push(e); }.bind(this)); }.bind(this)); }, serverSelected: function() { var cluster = this.selectedCluster(); var server = this.selectedServer(); _.each(this.metrics(), function(m) { this.metrics.remove(m); }.bind(this)); this.metricsLoading(true); $.getJSON("/" + cluster + "/" + server + "/metrics.json", function(data) { $.each(data, function(i, e) { this.metrics.push(e); }.bind(this)); this.metricsLoading(false); }.bind(this)); }, addToGraph: function() { var name = [this.selectedCluster(), this.selectedServer(), this.selectedMetric()].join("/"); this.add({name: name}); }, getMetricByName: function(name) { return _.detect(this.currentGraph(), function(metric) { return metric.name == metric; }); }, add: function(metric) { if (!this.getMetricByName(metric.name)) { this.currentGraph.push(metric); this.fetchData(metric); } }, fetchData: function(metric) { if (metric.data) { this.drawGraph(); } else { $.getJSON("/" + metric.name + ".json?time_window=" + this.timeWindow(), function(data) { metric.data = data; this.drawGraphIfReady(); }.bind(this)); } }, timeChanged: function() { this.resetChartIfExists(); _.each(this.currentGraph(), function(metric) { delete metric.data; this.fetchData(metric); }.bind(this)); }, readyToDrawGraph: function() { return _.all(this.currentGraph(), function(metric) { return metric.data; }.bind(this)); }, drawGraphIfReady: function() { if (this.readyToDrawGraph()) { this.drawGraph(); } }, resetChartIfExists: function() { if (this.chart) { this.chart.destroy(); this.chart = null; } }, drawGraph: function() { this.resetChartIfExists(); if (this.currentGraph().length == 0) return; var series = []; _.each(this.currentGraph(), function(metric) { var data = metric.data; var interval = data[1][0] - data[0][0]; if (metric.scale) { data = $.map(data, function(point) { return [[point[0], point[1] * 60]]; }); } series.push({ type: 'area', name: metric.name, pointInterval: interval * 1000, pointStart: data[0][0] * 1000, data: $.map(data, function(e) { return e[1]; }) }); }); this.chart = new Highcharts.Chart({ chart: { renderTo: 'chart', zoomType: 'x', spacingRight: 20 }, title: { text: this.graphTitle() }, subtitle: { text: document.ontouchstart === undefined ? 'Click and drag in the plot area to zoom in' : 'Drag your finger over the plot to zoom in' }, xAxis: { type: 'datetime', maxZoom: 360, title: { text: null } }, yAxis: { title: { text: '' }, startOnTick: false, showFirstLabel: false }, tooltip: { shared: true }, legend: { enabled: false }, series: series }); }, savedGraphsChanged: function() { this.savingSavedGraphs(true); var graphs = _.map(this.savedGraphs(), function(graph) { return {name: graph.name, metrics: _.map(graph.metrics, function(metric) { return { name: metric.name, scale: metric.scale } })}; }); $.ajax({ type: "post", dataType: "json", url: "/graphs.json", data: {"json": JSON.stringify(graphs)}, success: function() { this.savingSavedGraphs(false); }.bind(this) }); } }; $(function() { ko.applyBindings(viewModel); viewModel.selectedCluster.subscribe(viewModel.clusterSelected, viewModel); viewModel.selectedServer.subscribe(viewModel.serverSelected, viewModel); viewModel.timeWindow.subscribe(viewModel.timeChanged, viewModel); viewModel.savedGraphs.subscribe(viewModel.savedGraphsChanged, viewModel); $.getJSON("/graphs.json", function(graphs) { _.each(graphs, function(graph) { viewModel.savedGraphs.push(graph); }); }); viewModel.fetchClusters(); });