Skip to content

Instantly share code, notes, and snippets.

@petterik
Last active March 1, 2016 07:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save petterik/5f6d95fdd3138188ae16 to your computer and use it in GitHub Desktop.
Save petterik/5f6d95fdd3138188ae16 to your computer and use it in GitHub Desktop.
A Splunk dashboard for an Image Search demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>ma new image search | Splunk</title>
<link rel="shortcut icon" href="{{SPLUNKWEB_URL_PREFIX}}/static/img/favicon.ico" />
<link rel="stylesheet" type="text/css" href="{{SPLUNKWEB_URL_PREFIX}}/static/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" media="all" href="{{SPLUNKWEB_URL_PREFIX}}/static/css/pages/dashboard-simple-bootstrap.min.css" />
<link rel="stylesheet" type="text/css" media="all" href="{{SPLUNKWEB_URL_PREFIX}}/static/app/search/dashboard.css" />
<!--[if IE 7]><link rel="stylesheet" href="{{SPLUNKWEB_URL_PREFIX}}/static/css/sprites-ie7.css" /><![endif]-->
</head>
<body class="simplexml preload">
<!--
BEGIN LAYOUT
This section contains the layout for the dashboard. Splunk uses proprietary
styles in <div> tags, similar to Bootstrap's grid system.
-->
<div class="header">
<div id="placeholder-splunk-bar">
<a href="{{SPLUNKWEB_URL_PREFIX}}/app/launcher/home" class="brand" title="splunk &gt; listen to your data">splunk<strong>&gt;</strong></a>
</div>
<div id="placeholder-app-bar"></div>
</div>
<div class="dashboard-body container-fluid main-section-body" data-role="main">
<div class="dashboard-header clearfix">
<h2>ma new image search</h2>
</div>
<div class="dashboard-row dashboard-row1">
<div class="dashboard-cell" style="width: 100%;">
<div class="dashboard-panel clearfix">
<div class="panel-element-row">
<div class="dashboard-element event" id="element1" style="width: 100%">
<div class="panel-body"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="footer"></div>
<!--
END LAYOUT
-->
<script src="{{SPLUNKWEB_URL_PREFIX}}/config?autoload=1"></script>
<script src="{{SPLUNKWEB_URL_PREFIX}}/static/js/i18n.js"></script>
<script src="{{SPLUNKWEB_URL_PREFIX}}/static/js/build/simplexml.min/config.js"></script>
<script type="text/javascript">
require.config({
baseUrl: "{{SPLUNKWEB_URL_PREFIX}}/static/js",
waitSeconds: 0 // Disable require.js load timeout
});
//
// LIBRARY REQUIREMENTS
//
// In the require function, we include the necessary libraries and modules for
// the HTML dashboard. Then, we pass variable names for these libraries and
// modules as function parameters, in order.
//
// When you add libraries or modules, remember to retain this mapping order
// between the library or module and its function parameter. You can do this by
// adding to the end of these lists, as shown in the commented examples below.
require([
"splunkjs/mvc",
"splunkjs/mvc/utils",
"splunkjs/mvc/tokenutils",
"underscore",
"jquery",
"splunkjs/mvc/simplexml",
"splunkjs/mvc/headerview",
"splunkjs/mvc/footerview",
"splunkjs/mvc/simplexml/dashboard",
"splunkjs/mvc/simplexml/element/chart",
"splunkjs/mvc/simplexml/element/event",
"splunkjs/mvc/simplexml/element/html",
"splunkjs/mvc/simplexml/element/list",
"splunkjs/mvc/simplexml/element/map",
"splunkjs/mvc/simplexml/element/single",
"splunkjs/mvc/simplexml/element/table",
"splunkjs/mvc/simpleform/input/dropdown",
"splunkjs/mvc/simpleform/input/radiogroup",
"splunkjs/mvc/simpleform/input/text",
"splunkjs/mvc/simpleform/input/timerange",
"splunkjs/mvc/simpleform/input/submit",
"splunkjs/mvc/searchmanager",
"splunkjs/mvc/savedsearchmanager",
"splunkjs/mvc/postprocessmanager",
"splunkjs/mvc/simplexml/urltokenmodel"
// Add comma-separated libraries and modules manually here, for example:
// ..."splunkjs/mvc/simplexml/urltokenmodel",
// "splunkjs/mvc/checkboxview"
],
function(
mvc,
utils,
TokenUtils,
_,
$,
DashboardController,
HeaderView,
FooterView,
Dashboard,
ChartElement,
EventElement,
HtmlElement,
ListElement,
MapElement,
SingleElement,
TableElement,
DropdownInput,
RadioGroupInput,
TextInput,
TimeRangeInput,
SubmitButton,
SearchManager,
SavedSearchManager,
PostProcessManager,
UrlTokenModel
// Add comma-separated parameter names here, for example:
// ...UrlTokenModel,
// CheckboxView
) {
// AUTO-COMPILED JAVASCRIPT
//
// Import required dependencies
//
var DashboardController = require("splunkjs/mvc/simplexml/controller");
var HeaderView = require("splunkjs/mvc/headerview");
var FooterView = require("splunkjs/mvc/footerview");
var Dashboard = require("splunkjs/mvc/simplexml/dashboard");
var ChartElement = require("splunkjs/mvc/simplexml/element/chart");
var EventElement = require("splunkjs/mvc/simplexml/element/event");
var HtmlElement = require("splunkjs/mvc/simplexml/element/html");
var ListElement = require("splunkjs/mvc/simplexml/element/list");
var MapElement = require("splunkjs/mvc/simplexml/element/map");
var SingleElement = require("splunkjs/mvc/simplexml/element/single");
var TableElement = require("splunkjs/mvc/simplexml/element/table");
var SearchManager = require("splunkjs/mvc/searchmanager");
var SavedSearchManager = require("splunkjs/mvc/savedsearchmanager");
var UrlTokenModel = require("splunkjs/mvc/simplexml/urltokenmodel");
var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
//
// Create form token namespaces
//
var defaultTokenModel = mvc.Components.getInstance('default', {create: true});
var submittedTokenModel = new UrlTokenModel();
mvc.Components.registerInstance('submitted', submittedTokenModel);
submittedTokenModel.on('url:navigate', function() {
submittedTokenModel.disableAutosave();
defaultTokenModel.set(submittedTokenModel.toJSON());
submittedTokenModel.enableAutosave();
});
// Initialize non-input tokens
defaultTokenModel.set(submittedTokenModel.toJSON());
var submitTokens = _.debounce(function() {
// Submit form, even if no values have changed since the last submit.
submittedTokenModel.set(defaultTokenModel.toJSON());
});
//
// Create searches
//
function getKey(e, k) {
return e.context.attributes.getNamedItem(k).nodeValue;
}
function createSearch(id) {
var search = "index=images AND colors";
var spath = "";
var sumString = "score=";
var checkedBuckets = 0;
$(".bucket").each(function(i) {
var self = $(this);
if (self.attr("checked")) {
checkedBuckets += 1;
var colorId = "colors{" + getKey(self, "b") + "}{" + getKey(self,"s") + "}{" + getKey(self, "h") + "}";
spath += " | spath ";
var color = "color" + i;
spath += "path=" + colorId + " output=" + color;
sumString += "(1 + " + color + ")*";
}
});
sumString += "1";
var n = checkedBuckets;
var scorePctEval = "score_pct=if(" + n + "<2, score, 100*(log(score," + n + "))/log(pow((floor(100/" + n + "))+1," + n + ")," + n + "))";
search += spath;
search += " | eval " + sumString;
search += " | eval " + scorePctEval;
search += " | stats sum(score_pct) as relevance by image, source";
search += " | sort -relevance"
console.log("da search: " + search);
return new SearchManager({
"id": "search" + id,
"status_buckets": 0,
"earliest_time": "$earliest$",
"search": search,
"latest_time": "$latest$",
"cancelOnUnload": true,
"app": utils.getCurrentApp(),
"auto_cancel": 90,
"autostart":false,
"preview": true
}, {tokens: true, tokenNamespace: "submitted"});
}
//
// Create header and footer Splunk UI
//
new HeaderView({
id: 'header',
section: 'dashboards',
el: $('.header'),
acceleratedAppNav: true
}, {tokens: true}).render();
new FooterView({
id: 'footer',
el: $('.footer')
}, {tokens: true}).render();
new Dashboard({
id: 'dashboard',
el: $('.dashboard-body')
}, {tokens: true}).render();
//
// Create components
//
function createElement(id) {
return new MySplunkView({
"id": "element" + id,
"resizable": true,
"managerid": "search" + id,
"el": $("#element" + id)
}, {tokens: true});
}
DashboardController.ready();
submitTokens();
// END OF AUTO-COMPILED JAVASCRIPT
// HSB -> RGB conversion
function hsv2rgb(h,s,v) {
// Adapted from http://www.easyrgb.com/math.html
// hsv values = 0 - 1, rgb values = 0 - 255
var r, g, b;
var RGB = new Array();
if(s==0){
RGB['red']=RGB['green']=RGB['blue']=Math.round(v*255);
}else{
// h must be < 1
var var_h = h * 6;
if (var_h==6) var_h = 0;
//Or ... var_i = floor( var_h )
var var_i = Math.floor( var_h );
var var_1 = v*(1-s);
var var_2 = v*(1-s*(var_h-var_i));
var var_3 = v*(1-s*(1-(var_h-var_i)));
if(var_i==0){
var_r = v;
var_g = var_3;
var_b = var_1;
}else if(var_i==1){
var_r = var_2;
var_g = v;
var_b = var_1;
}else if(var_i==2){
var_r = var_1;
var_g = v;
var_b = var_3
}else if(var_i==3){
var_r = var_1;
var_g = var_2;
var_b = v;
}else if (var_i==4){
var_r = var_3;
var_g = var_1;
var_b = v;
}else{
var_r = v;
var_g = var_1;
var_b = var_2
}
//rgb results = 0 ÷ 255
RGB['red']=Math.round(var_r * 255);
RGB['green']=Math.round(var_g * 255);
RGB['blue']=Math.round(var_b * 255);
}
return RGB;
};
// START OWN STUFF
var hBuckets = 10;
var sBuckets = 3;
var bBuckets = 2;
var checkBoxesDiv = $("<div/>", {
class: 'row span11',
id: 'checkBoxes'
});
var loop = 0;
for (var i = bBuckets-1; i >= 0; i--) {
loop++;
for (var j = (loop == 1? 0 : sBuckets-1);
(loop == 1 && j < sBuckets) || (loop == 2 && j >= 0);
loop == 1 ? j++ : j--) {
for (var k = 0; k < hBuckets; k++) {
var hue = (1/(1.1*hBuckets)) + (k == 0? 0 : (1/hBuckets)*k);
var sat = (1/(1.1*sBuckets)) + (j == 0? 0 : (1/sBuckets)*j);
var bri = (1/(1.1*bBuckets)) + (i == 0? 0 : (1/bBuckets)*i);
var rgb = hsv2rgb(hue, sat, bri);
var checkBoxWrap = $("<div/>", {
class: "span1",
style: "background-color: rgb(" + rgb['red'] + "," + rgb['green'] + "," + rgb['blue'] + ")"
});
var checkBox = $("<input/>", {
type: "checkbox",
class: "bucket",
h: k,
s: j,
b: i
});
checkBoxWrap.append(checkBox);
checkBoxesDiv.append(checkBoxWrap);
}
}
}
var dashBody = $('.dashboard-row');
dashBody.prepend(checkBoxesDiv);
// search
var searchButton = $("<button/>", {
class: "btn btn-large btn-primary",
type: "button",
text: "Search!",
});
var currentSearchId = 0;
var search = null;
var element = null;
$(searchButton).click(function() {
if (element != null) {
element.off();
}
if (search != null) {
search.cancel();
search.finalize();
search.off();
}
$("#element" + currentSearchId).remove();
currentSearchId++;
var searchId = currentSearchId;
search = createSearch(searchId);
attachNewElement(searchId);
element = createElement(searchId);
element.render();
search.startSearch();
});
var resetButton = $("<button/>", {
class: "btn btn-small btn-default",
type: "button",
text: "Reset selection",
});
$(resetButton).click(function() {
$('#checkBoxes').find('input[type=checkbox]:checked').removeAttr('checked');
});
dashBody.append(searchButton);
dashBody.append(resetButton);
function attachNewElement(id) {
var elementDiv = $("<div/>", {
class: "dashboard-element chart",
id: "element" + id,
style: "width: 100%"
});
$("<div/>", {
class: "panel-body"
}).appendTo(elementDiv);
$(".panel-element-row").append(elementDiv);
}
// My own view
var MySplunkView = SimpleSplunkView.extend({
className: "my-splunk-view",
outputMode: "json",
n_images: 6,
height: 800,
visible: false,
cache: {},
createView: function() {
console.log("In createView:");
$(".splunk-message-container").remove();
this.setSize();
for (var i = 0; i < this.n_images; i++) {
this.createImageContainer();
}
return this;
},
createImageContainer: function() {
$("<div/>", {
style: "position: relative; width: 400px; height: 400px; float: left;"
}).appendTo(this.$el.selector);
},
formatData: function(data) {
return data;
},
updateView: function(viz, data) {
this.visible = true;
this.setSize();
if (this._isJobDone)
console.log("Job is done!");
console.log("In updateView:");
console.log(data);
var currentImages = {};
for (var i = 0; i < this.n_images && i < data.length; i++) {
fetchImage(i, data, viz, this.cache);
currentImages[getImageKey(data, i)] = true;
}
for (var key in this.cache) {
if (!(key in currentImages)) {
delete this.cache[key];
}
}
},
setSize: function() {
$(this.$el.selector).css({
width: "1200px",
height: this.height + "px"
});
},
grow: function() {
if(this.visible && this._data.length > this.n_images) {
this.height += 400;
var increment = Math.min(3, this._data.length - this.n_images);
this.n_images += increment;
for (var i = 0; i < increment; i++) {
this.createImageContainer();
}
this.render();
}
}
});
function getImageKey(data, i) {
return data[i].source + data[i].image;
}
function fetchImage(i, data, viz, cache) {
var imageKey = getImageKey(data, i);
var itsDiv = $('.my-splunk-view').children()[i];
if ($(itsDiv).attr("imageKey") === imageKey) {
return;
} else {
$(itsDiv).attr("imageKey", imageKey);
}
var matchSplit = data[i].relevance.split(".");
var match = "";
if (matchSplit.length == 1) {
match = matchSplit[0];
} else {
match = matchSplit[0] + "." + matchSplit[1].substring(0, 1);
}
if (imageKey in cache) {
populateImageDiv(cache[imageKey], match, itsDiv);
} else {
$.ajax({
url: "http://localhost:5123/image",
cache: false,
type: "GET",
data: {
path: data[i].source,
filename: data[i].image
},
success: function(image) {
if ($(itsDiv).attr("imageKey") === imageKey) {
cache[imageKey] = image;
populateImageDiv(image, match, itsDiv);
}
},
error: function(x) {
alert("Error: " + x);
}});
}
}
function populateImageDiv(image, match, div) {
var theDiv = $(div);
theDiv.empty();
var img = $("<img/>", {
width: "400",
height: "400",
style: "float: left; width: 400px; height: 400px;",
src: "data:image/jpg;base64," + image
});
img.appendTo(theDiv);
img.load(function() {
});
var h2style = "position: absolute; top: 280px; left: 0; width: 380px;";
h2style += " color: white; font: bold 24px/45px Helvetica, Sans-Serif;";
h2style += " letter-spacing: -1px; background: rgb(0, 0, 0);";
h2style += " background: rgba(0, 0, 0, 0.7); padding: 10px;";
$("<h2/>", {
style: h2style,
text: "Relevance: " + match + ""
}).appendTo(theDiv);
}
// Scrolling stuff
function isAtTheBottom() {
return $(window).scrollTop() == $(document).height() - $(window).height();
}
$(window).scroll(function() {
if(isAtTheBottom() && element != null) {
element.grow();
}
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment