Metric_Nmon

Pushed by: admin
License: TA9O64YS7EPT (Professional)
Timestamp: 2026-02-21T22:16:26.040137
masterdev
Splunk Git Pusher 2 months ago
parent df7922d0e6
commit 2abf928c2e

@ -0,0 +1,7 @@
# Metricator for Nmon
Copyright 2017-2018 Octamis limited - Copyright 2017-2018 Guilhem Marchand
All rights reserved.
https://www.octamis.com/services/metricator

@ -0,0 +1,63 @@
{
"dependencies": null,
"incompatibleApps": null,
"info": {
"author": [
{
"company": "Octamis",
"email": "support@octamis.com",
"name": "Octamis"
}
],
"classification": {
"categories": [
"Unix",
"Linux",
"Performance",
"Monitoring",
"Capacity planning",
"System administration",
"Benchmarking"
],
"developmentStatus": "Production/Stable",
"intendedAudience": "IT Professionals"
},
"commonInformationModels": null,
"description": "Metricator for Nmon provides rich and efficient monitoring and capacity planning for Linux, IBM AIX and Oracle Solaris",
"id": {
"group": null,
"name": "metricator-for-nmon",
"version": "2.0.2"
},
"license": {
"name": "Octamis",
"text": "./license.txt",
"uri": ""
},
"privacyPolicy": {
"name": null,
"text": null,
"uri": null
},
"releaseDate": "6 September 2021",
"releaseNotes": {
"name": "version 2.0.1, please consult online documentation",
"text": "./README.md",
"uri": ""
},
"title": "Metricator for Nmon"
},
"inputGroups": null,
"platformRequirements": null,
"schemaVersion": "2.0.0",
"supportedDeployments": [
"_standalone",
"_distributed",
"_search_head_clustering"
],
"targetWorkloads": [
"_search_heads",
"_indexers"
],
"tasks": null
}

@ -0,0 +1,20 @@
.html h1 {
color: royalblue;
}
.html h2 {
color: #29547f;
}
/* margin left required for Firefox */
.list li {
/*margin-left: 4px; */
margin-left: 10px;
padding-left: 10px;
}
.list {
display: inline-block;
margin-right: 50px;
}

@ -0,0 +1,374 @@
/* Logo subtitle */
.logosubtitle h2 {
font-size: 16px;
margin: 10px 0;
font-weight: normal;
color: darkslategrey;
text-align: center;
}
/* links color */
a {
color: #adbacd;
}
.mainbutton_container {
text-align: center;
margin-top: 25px;
margin-bottom: 50px;
}
.mainbutton_container_nomargin {
text-align: center;
}
.mainbutton {
display: inline-block;
margin-left: 30px;
margin-right: 30px;
}
.mainbutton_highmargin {
display: inline-block;
margin-left: 90px;
margin-right: 90px;
}
/* main category icons and titles */
.imgheader {
margin-top: 10px;
margin-bottom: 40px;
}
.imgheader img {
float: left;
width: 48px;
height: 48px;
}
.imgheader h1 {
color: lightslategrey;
text-align: left;
position: relative;
top: 9px;
left: 10px;
}
.imgheader h2 {
position: relative;
top: 18px;
left: 10px;
}
.imgminiheader img {
position: relative;
top: -4px;
}
/* Hide the view title as an App Home Page */
/* Now disabled
.dashboard-header h2 {
visibility: hidden;
}
*/
/* margin left required for Firefox */
.ui_list li {
margin-left: 3px;
}
.custom h1 {
color: lightslategrey;
text-align: center;
}
.customleft h1 {
color: lightslategrey;
text-align: left;
}
.customright h1 {
color: lightslategrey;
text-align: right;
}
.custom img {
text-align: center;
}
/* Home Button */
.round-button {
width: 3%;
height: 0;
padding-bottom: 3%;
border-radius: 50%;
border: 2px solid #f5f5f5;
overflow: hidden;
background: #464646;
box-shadow: 0 0 3px gray;
}
.round-button:hover {
background: #262626;
}
.round-button img {
display: block;
width: 76%;
padding: 12%;
height: auto;
}
.cat_title {
color: #5379af;
margin-bottom: 10px;
}
.bootstrap_title {
color: #5379af;
margin-bottom: 5px;
margin-top: 5px;
}
span:hover {
position: relative;
font-weight: bolder;
}
/* Home page button links, inspired from http://www.w3schools.com */
a.tryitbtn,
a.tryitbtn:link,
a.tryitbtn:visited,
a.showbtn,
a.showbtn:link,
a.showbtn:visited {
display: inline-block;
color: #469496;
background-color: #1f1f1f;
font-weight: bold;
font-size: 12px;
text-align: center;
padding-left: 10px;
padding-right: 10px;
padding-top: 3px;
padding-bottom: 4px;
text-decoration: none;
margin-left: 0;
/* margin-left: 5px; */
margin-right: 10px;
margin-top: 0px;
margin-bottom: 5px;
border: 1px solid #aaaaaa;
border: 1px solid #469496;
border-radius: 5px;
white-space: nowrap;
min-width: 60px;
}
a.tryitbtn:hover,
a.tryitbtn:active,
a.tryitbtn:hover,
a.tryitbtn:active {
background-color: #469496;
color: #1f1f1f;
}
a.tryitbtnxl,
a.tryitbtnxl:link,
a.tryitbtnxl:visited,
a.showbtnxl,
a.showbtnxl:link,
a.showbtnxl:visited {
display: inline-block;
color: #469496;
background-color: #1f1f1f;
font-weight: bold;
font-size: 12px;
text-align: center;
padding-left: 10px;
padding-right: 10px;
padding-top: 3px;
padding-bottom: 4px;
text-decoration: none;
margin-left: 0;
/* margin-left: 5px; */
margin-right: 10px;
margin-top: 5px;
margin-bottom: 5px;
border: 1px solid #aaaaaa;
border: 1px solid #469496;
border-radius: 5px;
white-space: nowrap;
min-width: 150px;
}
a.tryitbtnxl:hover,
a.tryitbtnxl:active,
a.tryitbtnxl:hover,
a.tryitbtnxl:active {
background-color: #469496;
color: #1f1f1f;
}
a.tryitbtnxxl,
a.tryitbtnxxl:link,
a.tryitbtnxxl:visited,
a.showbtnxxl,
a.showbtnxxl:link,
a.showbtnxxl:visited {
display: inline-block;
color: #469496;
background-color: #1f1f1f;
font-weight: bold;
font-size: 12px;
text-align: center;
padding-left: 10px;
padding-right: 10px;
padding-top: 3px;
padding-bottom: 4px;
text-decoration: none;
margin-left: 0;
/* margin-left: 5px; */
margin-right: 10px;
margin-top: 10px;
margin-bottom: 10px;
border: 1px solid #aaaaaa;
border: 1px solid #469496;
border-radius: 5px;
white-space: nowrap;
min-width: 220px;
}
a.tryitbtnxxl:hover,
a.tryitbtnxxl:active,
a.tryitbtnxxl:hover,
a.tryitbtnxxl:active {
background-color: #469496;
color: #1f1f1f;
}
a.tryitbtn-alt,
a.tryitbtn-alt:link,
a.tryitbtn-alt:visited,
a.tryitbtn-alt,
a.tryitbtn-alt:link,
a.tryitbtn-alt:visited {
display: inline-block;
color: #c171b1;
background-color: #1f1f1f;
font-weight: bold;
font-size: 12px;
text-align: center;
padding-left: 10px;
padding-right: 10px;
padding-top: 3px;
padding-bottom: 4px;
text-decoration: none;
margin-left: 0;
/* margin-left: 5px; */
margin-right: 10px;
margin-top: 0px;
margin-bottom: 5px;
border: 1px solid #aaaaaa;
border: 1px solid #c171b1;
border-radius: 5px;
white-space: nowrap;
min-width: 60px;
}
a.tryitbtn-alt:hover,
a.tryitbtn-alt:active,
a.tryitbtn-alt:hover,
a.tryitbtn-alt:active {
background-color: #c171b1;
color: #1f1f1f;
}
a.rt {
margin-left: 10px;
}
a.rt {
-webkit-transition: all 1s ease; /* Safari and Chrome */
-moz-transition: all 1s ease; /* Firefox */
-ms-transition: all 1s ease; /* IE 9 */
-o-transition: all 1s ease; /* Opera */
transition: all 1s ease;
}
a.rt:hover img {
-webkit-transform: scale(1.1); /* Safari and Chrome */
-moz-transform: scale(1.1); /* Firefox */
-ms-transform: scale(1.1); /* IE 9 */
-o-transform: scale(1.1); /* Opera */
transform: scale(1.1);
}
/* Fix single label trouble with Splunk 6.3.0 */
.before-label {
font-size: medium !important;
}
.after-label {
font-size: medium !important;
}
.single-result-unit {
font-size: medium !important;
}
/* Prevents modal window from generating troubles within Splunk interfaces */
.modal {
display: none;
}
/* Custom Modals widths */
.modal[class^="custom-modal"] {
left: 50%;
border: 1px solid green;
}
.custom-modal-30 {
width: 30%;
margin-left: -15%;
}
.custom-modal-50 {
width: 50%;
margin-left: -25%;
}
.custom-modal-60 {
width: 60%;
margin-left: -30%;
}
.custom-modal-70 {
width: 70%;
margin-left: -35%;
}
.custom-modal-80 {
width: 80%;
margin-left: -40%;
}
.custom-modal-96 {
width: 96%;
margin-left: -48%;
}
/* Panels title bar customization */
.dashboard-row .dashboard-panel h2.panel-title {
/* text-align: center; */
font-size: 20px;
font-weight: 500;
background-color: #444444;
padding: 5px 5px 5px 5px;
}

@ -0,0 +1,46 @@
(function() {
require([
"underscore",
"jquery",
"splunkjs/mvc",
"appUtils",
"splunkjs/ready!",
"splunkjs/mvc/simplexml/ready!",
], function(_, $, mvc, appUtils) {
/////////////////////////////////////////
/// Start Main Code Here
/////////////////////////////////////////
var ref = appUtils.getTokenModels();
var defaultTokenModel = ref[0];
var submittedTokenModel = ref[1];
appUtils.checkEmptyTokenFocus("host1", appUtils.getToken("host1"));
appUtils.checkEmptyTokenFocus("host2", appUtils.getToken("host2"));
appUtils.checkEmptyTokenFocus("metric_name", appUtils.getToken("metric_name"));
defaultTokenModel.on("change:host1", function(model, value, options) {
appUtils.checkEmptyTokenFocus("host1", value);
if (typeof value !== 'undefined' && value.toString().trim() === "") {
appUtils.setToken("form.host1", undefined, true);
}
});
defaultTokenModel.on("change:host2", function(model, value, options) {
appUtils.checkEmptyTokenFocus("host2", value);
if (typeof value !== 'undefined' && value.toString().trim() === "") {
appUtils.setToken("form.host2", undefined, true);
}
});
defaultTokenModel.on("change:metric_name", function(model, value, options) {
appUtils.checkEmptyTokenFocus("metric_name", value);
if (typeof value !== 'undefined' && value.toString().trim() === "") {
appUtils.setToken("form.metric_name", undefined, true);
}
});
appUtils.submitTokens();
});
}).call(this);

@ -0,0 +1,38 @@
(function() {
require([
"underscore",
"jquery",
"splunkjs/mvc",
"appUtils",
"splunkjs/ready!",
"splunkjs/mvc/simplexml/ready!",
], function(_, $, mvc, appUtils) {
/////////////////////////////////////////
/// Start Main Code Here
/////////////////////////////////////////
var ref = appUtils.getTokenModels();
var defaultTokenModel = ref[0];
var submittedTokenModel = ref[1];
appUtils.checkEmptyTokenFocus("metric_name", appUtils.getToken("metric_name"));
appUtils.checkEmptyTokenFocus("host", appUtils.getToken("host"));
defaultTokenModel.on("change:metric_name", function(model, value, options) {
appUtils.checkEmptyTokenFocus("metric_name", value);
if (typeof value !== 'undefined' && value.toString().trim() === "") {
appUtils.setToken("form.metric_name", undefined, true);
}
});
defaultTokenModel.on("change:host", function(model, value, options) {
appUtils.checkEmptyTokenFocus("host", value);
if (typeof value !== 'undefined' && value.toString().trim() === "") {
appUtils.setToken("form.host", undefined, true);
}
});
appUtils.submitTokens();
});
}).call(this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

@ -0,0 +1,74 @@
require(['splunkjs/mvc/simplexml/ready!'], function(){
require(['splunkjs/ready!', 'splunkjs/mvc'], function(mvc){
/*
--------------------------------------------------------------
Multi depends buttons - Written by François Toulouse, thanks !
--------------------------------------------------------------
Usage: Add an html bootstrap button
<button class="btn" data-token-name="foo" data-token-value="1">Activate foo token</button>
<button class="btn" data-token-name="bar" data-token-value="1">Activate bar token</button>
*/
var defaultTokenModel = mvc.Components.getInstance('default', {create: true});
var submittedTokenModel = mvc.Components.getInstance('submitted', {create: true});
function setToken(name, value) {
defaultTokenModel.set(name, value);
submittedTokenModel.set(name, value);
}
function getToken(name) {
var ret = null;
if(defaultTokenModel.get(name) != undefined){
ret = defaultTokenModel.get(name);
}
else if(submittedTokenModel.get(name) != undefined){
ret = submittedTokenModel.get(name);
}
return ret;
}
function unsetToken(name) {
defaultTokenModel.unset(name);
submittedTokenModel.unset(name);
}
// For each button with the class "custom-sub-nav"
$('.custom-sub-nav').each(function(){
var $btn_group = $(this);
/* for each button in this nav:
- Cliking on the button: create the token "data-token-name" with attribute value "data-token-value"
- Button has been clicked already and the user click on it again: removes the token "data-token-name"
*/
$btn_group.find('button').on('click', function(){
var $btn = $(this);
var btn_current_label = $btn.html();
var btn_alt_label = $btn.attr('data-alt-label');
var tk_name = $btn.attr('data-token-name');
var tk_value = $btn.attr('data-token-value');
if( getToken(tk_name) == null){
setToken(tk_name, tk_value);
$btn.addClass('active');
}
else{
unsetToken(tk_name);
$btn.removeClass('active');
}
// Manage button label
$btn.html(btn_alt_label);
$btn.attr('data-alt-label', btn_current_label);
});
});
});
});

@ -0,0 +1,42 @@
/* blue color for current rendering */
td.data-bar-cell {
padding: 4px 8px;
}
td.data-bar-cell .data-bar-wrapper .data-bar {
height: 16px;
min-width: 1px;
background-color: #5479AF;
font-weight: bold;
}
.data-bar-over { color: #FFFFFF; }
.data-bar-under { color: #000000; }
.data-bar-wrapper {
border-style: solid;
border-width: 1px;
}
/* red color for laerting */
td.red-data-bar-cell {
padding: 4px 8px;
}
td.red-data-bar-cell .red-data-bar-wrapper .red-data-bar {
height: 16px;
min-width: 1px;
background-color: #CD5C5C;
font-weight: bold;
}
.red-data-bar-over { color: #FFFFFF; }
.red-data-bar-under { color: #000000; }
.red-data-bar-wrapper {
border-style: solid;
border-width: 1px;
}

@ -0,0 +1,53 @@
require([
'jquery',
'underscore',
'splunkjs/mvc',
'views/shared/results_table/renderers/BaseCellRenderer',
'splunkjs/mvc/simplexml/ready!'
], function($, _, mvc, BaseCellRenderer) {
// blue rendering for current
var DataBarCellRenderer = BaseCellRenderer.extend({
canRender: function(cell) {
return (cell.field === 'current_used_percent');
},
render: function($td, cell) {
var pColor="data-bar-under"
if(cell.value > 15){ pColor="data-bar-over" }
$td.addClass('data-bar-cell').html(_.template('<div class="data-bar-wrapper"><div class="data-bar <%- pColor %>" style="width:<%- percent %>%">&nbsp;<%- ppp %>%</div></div>', {
percent: Math.min(Math.max(parseFloat(cell.value), 0), 100),
ppp: parseFloat(cell.value).toFixed(2),
pColor: pColor
}));
}
});
mvc.Components.get('element_table_show_lookup_inventory').getVisualization(function(tableView) {
tableView.table.addCellRenderer(new DataBarCellRenderer());
tableView.table.render();
});
// red rendering for alerting
var DataBarCellRenderer2 = BaseCellRenderer.extend({
canRender: function(cell) {
return (cell.field === 'max_used_percent_alert');
},
render: function($td, cell) {
var pColor="red-data-bar-under"
if(cell.value > 15){ pColor="red-data-bar-over" }
$td.addClass('red-data-bar-cell').html(_.template('<div class="red-data-bar-wrapper"><div class="red-data-bar <%- pColor %>" style="width:<%- percent %>%">&nbsp;<%- ppp %>%</div></div>', {
percent: Math.min(Math.max(parseFloat(cell.value), 0), 100),
ppp: parseFloat(cell.value).toFixed(2),
pColor: pColor
}));
}
});
mvc.Components.get('element_table_show_lookup_content').getVisualization(function(tableView) {
tableView.table.addCellRenderer(new DataBarCellRenderer2());
tableView.table.render();
});
});

@ -0,0 +1,21 @@
/* red color for alerting */
td.red-data-bar-cell {
padding: 4px 8px;
}
td.red-data-bar-cell .red-data-bar-wrapper .red-data-bar {
height: 16px;
min-width: 1px;
background-color: #CD5C5C;
font-weight: bold;
}
.red-data-bar-over { color: #FFFFFF; }
.red-data-bar-under { color: #000000; }
.red-data-bar-wrapper {
border-style: solid;
border-width: 1px;
}

@ -0,0 +1,71 @@
require([
'jquery',
'underscore',
'splunkjs/mvc',
'views/shared/results_table/renderers/BaseCellRenderer',
'splunkjs/mvc/simplexml/ready!'
], function($, _, mvc, BaseCellRenderer) {
// red rendering for alerting
var DataBarCellRenderer1 = BaseCellRenderer.extend({
canRender: function(cell) {
return (cell.field === 'max_cpu_percent');
},
render: function($td, cell) {
var pColor="red-data-bar-under"
if(cell.value > 15){ pColor="red-data-bar-over" }
$td.addClass('red-data-bar-cell').html(_.template('<div class="red-data-bar-wrapper"><div class="red-data-bar <%- pColor %>" style="width:<%- percent %>%">&nbsp;<%- ppp %>%</div></div>', {
percent: Math.min(Math.max(parseFloat(cell.value), 0), 100),
ppp: parseFloat(cell.value).toFixed(2),
pColor: pColor
}));
}
});
mvc.Components.get('element_table_show_lookup_content').getVisualization(function(tableView) {
tableView.table.addCellRenderer(new DataBarCellRenderer1());
tableView.table.render();
});
var DataBarCellRenderer2 = BaseCellRenderer.extend({
canRender: function(cell) {
return (cell.field === 'max_phy_percent');
},
render: function($td, cell) {
var pColor="red-data-bar-under"
if(cell.value > 15){ pColor="red-data-bar-over" }
$td.addClass('red-data-bar-cell').html(_.template('<div class="red-data-bar-wrapper"><div class="red-data-bar <%- pColor %>" style="width:<%- percent %>%">&nbsp;<%- ppp %>%</div></div>', {
percent: Math.min(Math.max(parseFloat(cell.value), 0), 100),
ppp: parseFloat(cell.value).toFixed(2),
pColor: pColor
}));
}
});
mvc.Components.get('element_table_show_lookup_content').getVisualization(function(tableView) {
tableView.table.addCellRenderer(new DataBarCellRenderer2());
tableView.table.render();
});
var DataBarCellRenderer3 = BaseCellRenderer.extend({
canRender: function(cell) {
return (cell.field === 'max_vir_percent');
},
render: function($td, cell) {
var pColor="red-data-bar-under"
if(cell.value > 15){ pColor="red-data-bar-over" }
$td.addClass('red-data-bar-cell').html(_.template('<div class="red-data-bar-wrapper"><div class="red-data-bar <%- pColor %>" style="width:<%- percent %>%">&nbsp;<%- ppp %>%</div></div>', {
percent: Math.min(Math.max(parseFloat(cell.value), 0), 100),
ppp: parseFloat(cell.value).toFixed(2),
pColor: pColor
}));
}
});
mvc.Components.get('element_table_show_lookup_content').getVisualization(function(tableView) {
tableView.table.addCellRenderer(new DataBarCellRenderer3());
tableView.table.render();
});
});

@ -0,0 +1,31 @@
require([
'jquery',
'underscore',
'splunkjs/mvc',
'views/shared/results_table/renderers/BaseCellRenderer',
'splunkjs/mvc/simplexml/ready!'
], function($, _, mvc, BaseCellRenderer) {
// red rendering for alerting
var DataBarCellRenderer1 = BaseCellRenderer.extend({
canRender: function(cell) {
return (cell.field === 'max_fs_percent');
},
render: function($td, cell) {
var pColor="red-data-bar-under"
if(cell.value > 15){ pColor="red-data-bar-over" }
$td.addClass('red-data-bar-cell').html(_.template('<div class="red-data-bar-wrapper"><div class="red-data-bar <%- pColor %>" style="width:<%- percent %>%">&nbsp;<%- ppp %>%</div></div>', {
percent: Math.min(Math.max(parseFloat(cell.value), 0), 100),
ppp: parseFloat(cell.value).toFixed(2),
pColor: pColor
}));
}
});
mvc.Components.get('element_table_show_lookup_content').getVisualization(function(tableView) {
tableView.table.addCellRenderer(new DataBarCellRenderer1());
tableView.table.render();
});
});

@ -0,0 +1,12 @@
require.config({
paths: {
"app": "../app"
}
});
require(['splunkjs/mvc/simplexml/ready!'], function(){
require(['splunkjs/ready!'], function(){
// The splunkjs/ready loader script will automatically instantiate all elements
// declared in the dashboard's HTML.
});
});

@ -0,0 +1,10 @@
{
"name": "bubblechart",
"version": "1.0.0",
"main": "bubblechart.js",
"ignore": [],
"dependencies": {
"d3": "3.3.x"
},
"devDependencies": {}
}

@ -0,0 +1,29 @@
.splunk-toolkit-bubble-chart {
font-family: arial;
position: relative;
height: 100%;
width: 100%;
}
.splunk-toolkit-bubble-chart svg {
display: block;
margin: 0px auto;
}
.splunk-toolkit-bubble-chart g {
display: block;
margin: 0px auto;
}
.bubble-chart-tooltip {
position: absolute;
background-color: #424242;
border-radius: 3px 3px 3px 3px;
padding: 7px;
font-size: 1.0em;
color: white;
opacity:0;
}
.node:hover{
opacity: .7;
}

@ -0,0 +1,236 @@
// Bubble Chart
// this displays information as different 'bubbles,' their unique values represented with
// the size of the bubble.
// supports drilldown clicks
// available settings:
// - nameField: the field to use as the label on each bubble
// - valueField: the field to use as the value of each bubble (also dictates size)
// - categoryField: the field to use for grouping similar data (usually the same field as nameField)
// ---expected data format---
// a splunk search like this: source=foo | stats count by artist_name, track_name
define(function(require, exports, module) {
var _ = require('underscore');
var d3 = require("../d3/d3");
var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
require("css!./bubblechart.css");
var BubbleChart = SimpleSplunkView.extend({
className: "splunk-toolkit-bubble-chart",
options: {
managerid: null,
data: "preview",
nameField: null,
valueField: 'count',
categoryField: null
},
output_mode: "json",
initialize: function() {
_.extend(this.options, {
formatName: _.identity,
formatTitle: function(d) {
return (d.source.name + ' -> ' + d.target.name +
': ' + d.value);
}
});
SimpleSplunkView.prototype.initialize.apply(this, arguments);
this.settings.enablePush("value");
// in the case that any options are changed, it will dynamically update
// without having to refresh. copy the following line for whichever field
// you'd like dynamic updating on
this.settings.on("change:valueField", this.render, this);
this.settings.on("change:nameField", this.render, this);
this.settings.on("change:categoryField", this.render, this);
// Set up resize callback. The first argument is a this
// pointer which gets passed into the callback event
$(window).resize(this, _.debounce(this._handleResize, 20));
},
_handleResize: function(e){
// e.data is the this pointer passed to the callback.
// here it refers to this object and we call render()
e.data.render();
},
createView: function() {
// Here we wet up the initial view layout
var margin = {top: 0, right: 0, bottom: 0, left: 0};
var availableWidth = parseInt(this.settings.get("width") || this.$el.width());
var availableHeight = parseInt(this.settings.get("height") || this.$el.height());
this.$el.html("");
var svg = d3.select(this.el)
.append("svg")
.attr("width", availableWidth)
.attr("height", availableHeight)
.attr("pointer-events", "all");
var tooltip = d3.select(this.el).append("div")
.attr("class", "bubble-chart-tooltip");
// The returned object gets passed to updateView as viz
return { container: this.$el, svg: svg, margin: margin, tooltip: tooltip};
},
// making the data look how we want it to for updateView to do its job
formatData: function(data) {
// getting settings
var nameField = this.settings.get('nameField');
var valueField = this.settings.get('valueField');
var categoryField = this.settings.get('categoryField');
var collection = data;
var bubblechart = { 'name': nameField+"s", 'children': [ ] }; // how we want it to look
// making the children formatted array
for (var i=0; i < collection.length; i++) {
var Idx = -1;
$.each(bubblechart.children, function(idx, el) {
if (el.name == collection[i][categoryField]) {
Idx = idx;
}
});
if (Idx == -1) {
bubblechart.children.push({ 'name': collection[i][categoryField], children: [ ] });
Idx = bubblechart.children.length - 1;
}
bubblechart.children[Idx].children.push({ 'name': collection[i][nameField], 'size': collection[i][valueField] || 1 });
}
return bubblechart; // this is passed into updateView as 'data'
},
updateView: function(viz, data) {
var that = this;
// Clear svg
var svg = $(viz.svg[0]);
svg.empty();
var tooltip = viz.tooltip;
// Add the graph group as a child of the main svg
var graph = viz.svg
.append("g")
.attr("class", "bubble")
.attr("transform", "translate(" + viz.margin.left + "," + viz.margin.top + ")");
// Set format and color
var format = d3.format(",d");
var color = d3.scale.category20c();
// We have two phases in layout. We tell the
// d3 lout how much room it has, then set
// the sizes of it's containers to match
// the size it returns.
var containerHeight = this.$el.height();
var containerWidth = this.$el.width();
var diameter = Math.min(containerWidth, containerHeight);
// Tell the layout to layout
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
// Set containers' sizes to match actual layout
var width = bubble.size()[0];
var height = bubble.size()[1];
graph.attr("width", width)
.attr("height", height);
svg.height(height);
svg.width(width);
var node = graph.selectAll(".node")
.data(bubble.nodes(classes(data))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
// NOTE: this is taken out because we have a custom tooltip.
// It may need to be put back for accessibility
// node.append("title")
// .text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
// ensure the text is truncated if the bubble is tiny
.text(function(d) { return (d.className + " " + format(d.value)).substring(0, d.r / 3); });
// Re-flatten the child array
function classes(data) {
var classes = [];
function recurse(name, node) {
if (node.children)
node.children.forEach(function(child) {
recurse(node.name, child);
});
else
classes.push({packageName: name || "", className: node.name || "", value: node.size});
}
recurse(null, data);
return {children: classes};
}
// Tooltips
function doMouseEnter(d){
var text;
if(d.className === undefined || d.className === ""){
text = "Event: " + d.value;
} else {
text = d.className+": " + d.value;
}
tooltip
.text(text)
.style("opacity", function(){
if(d.value !== undefined) { return 1; }
return 0;
})
.style("left", (d3.mouse(that.el)[0]) + "px")
.style("top", (d3.mouse(that.el)[1]) + "px");
}
// More tooltips
function doMouseOut(d){
tooltip.style("opacity", 1e-6);
}
node.on("mouseover", doMouseEnter);
node.on("mouseout", doMouseOut);
// Drilldown clickings. edit this in order to change the search token that
// is set to 'value' (a token in bubbles django), this will change the drilldown
// search.
node.on('click', function(e) {
var clickEvent = {
name: e.className,
category: e.packageName,
value: e.value
};
that.settings.set("value", e.className);
that.trigger("click", clickEvent);
});
}
});
return BubbleChart;
});

@ -0,0 +1,10 @@
{
"name": "calendarheatmap",
"version": "1.0.0",
"main": "calendarheatmap.js",
"ignore": [],
"dependencies": {
"d3": "3.3.x"
},
"devDependencies": {}
}

@ -0,0 +1,136 @@
.splunk-toolkit-cal-heatmap {
margin-left: 20px;
margin-right: 20px;
margin-top: 20px;
}
.splunk-toolkit-cal-heatmap .heatmap-container {
overflow: hidden;
}
.splunk-toolkit-cal-heatmap .heatmap-container hr {
margin-top: 0px;
}
.splunk-toolkit-cal-heatmap .heatmap-series-title {
margin-bottom: 5px;
display: inline-block;
}
.splunk-toolkit-cal-heatmap .heatmap-buttons {
margin-bottom: 5px;
float: right;
display: inline;
}
/* Cal-HeatMap CSS */
.splunk-toolkit-cal-heatmap .heatmap-container .graph
{
clear: both;
display: block;
font-family: "Lucida Grande", Lucida, Verdana, sans-serif;
overflow: hidden;
}
.splunk-toolkit-cal-heatmap .heatmap-container .graph-label
{
fill: #999;
font-size: 10px
}
.splunk-toolkit-cal-heatmap .heatmap-container .graph, .splunk-toolkit-cal-heatmap .heatmap-container .graph-legend rect {
shape-rendering: crispedges
}
.splunk-toolkit-cal-heatmap .heatmap-container .graph-rect
{
fill: #ededed;
}
.splunk-toolkit-cal-heatmap .heatmap-container .graph rect:hover
{
stroke: #000;
stroke-width: 1px
}
.splunk-toolkit-cal-heatmap .heatmap-container .subdomain-text {
font-size: 8px;
fill: #999;
pointer-events: none;
}
.splunk-toolkit-cal-heatmap .heatmap-container .hover_cursor:hover {
cursor: pointer;
}
.splunk-toolkit-cal-heatmap .heatmap-container .qi {
background-color: #999;
fill: #999;
}
.splunk-toolkit-cal-heatmap .heatmap-container .q0
{
background-color: #fff;
fill: #fff;
stroke: #ededed
}
.splunk-toolkit-cal-heatmap .heatmap-container .q1
{
background-color: #89dae2 !important;
fill: #89dae2 !important;
}
.splunk-toolkit-cal-heatmap .heatmap-container .q2
{
background-color: #9ccedb !important;
fill: #699cc0 !important;
}
.splunk-toolkit-cal-heatmap .heatmap-container .q3
{
background-color: #6bb5cf !important;
fill: #45669d !important;
}
.splunk-toolkit-cal-heatmap .heatmap-container .q4
{
background-color: #396379 !important;
fill: #396379 !important;
}
.splunk-toolkit-cal-heatmap .heatmap-container .q5
{
background-color: #273b64 !important;
fill: #273b64 !important;
}
.splunk-toolkit-cal-heatmap .heatmap-container rect.highlight
{
stroke:#444;
stroke-width:1;
}
.splunk-toolkit-cal-heatmap .heatmap-container text.highlight
{
fill: #444;
}
.splunk-toolkit-cal-heatmap .heatmap-container rect.now
{
stroke: white !important;
}
.splunk-toolkit-cal-heatmap .heatmap-container text.now
{
fill: white !important;
font-weight: 800;
}
.splunk-toolkit-cal-heatmap .heatmap-container .domain-background {
fill: none;
shape-rendering: crispedges;
}

@ -0,0 +1,263 @@
// calheat!
// shows a cool looking heatmap based on different time signatures
// requires a timechart search. it dynamically guesses how to set up the
// way to show the time, but you can define any settings you want in the html
// docs: http://kamisama.github.io/cal-heatmap
// ---settings---
// domain: (hour, day, week, month, year)
// subDomain: (min, x_min, hour, x_hour, day, x_day, week, x_week, month, x_month)
// -- x_ variants are used to rotate the reading order to left to right, then top to bottom.
// start: set to 'current' for current time or 'earliest' for your earliest data point
// TODO:
// add a setting for each option at http://kamisama.github.io/cal-heatmap/#options
// rather than using the JS method in the HTML like i'm doing now.
// the data is expected in this format after formatData (epoch time: event count):
// {
// "timestamps":[
// {
// "1378225500":"8",
// "1378225560":"8",
// "1378225620":"8",
// },
// {
// "1378230300":"4",
// "1378230360":"4",
// "1378230660":"2"
// },
// {
// "1378225500":"7",
// "1378225560":"7",
// },
// {
// "1378225500":"6",
// "1378225560":"6",
// "1378225620":"7",
// },
// {
// "1378225500":"41",
// "1378225560":"41",
// },
// {
// "1378225500":"22",
// "1378225560":"22",
// }
// ],
// -- we add this part onto the actual data --
// "start":"2013-09-03T16:25:00.000Z",
// "domain":"hour",
// "subDomain":"min"
// }
define(function(require, exports, module) {
var _ = require('underscore');
var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
var d3 = require("../d3/d3");
var CalHeatMap = require("./contrib/cal-heatmap");
require("css!./calendarheatmap.css");
var CalendarHeatMap = SimpleSplunkView.extend({
moduleId: module.id,
className: "splunk-toolkit-cal-heatmap",
heatmapOptionNames: [
'cellRadius', 'domainMargin', 'maxDate', 'dataType',
'considerMissingDataAsZero', 'verticalOrientation',
'domainDynamicDimension', 'label', 'legendCellSize',
'legendCellPadding', 'legendMargin', 'legendVerticalPosition',
'legendHorizontalPosition', 'domainLabelFormat',
'subDomainDateFormat', 'subDomainTextFormat', 'nextSelector',
'previousSelector', 'itemNamespace', 'onMaxDomainReached',
'onMinDomainReached', 'width', 'height'],
options: {
managerid: "search1", // your MANAGER ID
data: "preview", // Results type
domain: 'hour', // the largest unit it will differentiate by in squares
subDomain: 'min', // the smaller unit the calheat goes off of
uID: null,
range: 4
},
validDomains: {
'min': ['hour'],
'hour': ['day', 'week'],
'day': ['week', 'month', 'year'],
'week': ['month', 'year'],
'month': ['year']
},
output_mode: "json_rows",
initialize: function() {
var that = this;
SimpleSplunkView.prototype.initialize.apply(this, arguments);
this.settings.enablePush("value");
// whenever domain or subDomain are changed, we will re-render.
this.settings.on("change:domain", this.onDomainChange, this);
this.settings.on("change:subDomain", this.onDomainChange, this);
this.settings.on("change", this._onSettingsChange, this);
var uniqueID=Math.floor(Math.random()*1000001);
this.settings.set("uID", uniqueID);
},
onDomainChange: function() {
var dom = this.settings.get('domain');
var sd = this.settings.get('subDomain');
// Knock off the prefix cause it doesnt matter here
var sdShort = sd.replace("x_", "");
// If the current domain is valid for this subdomain
if (_.contains(this.validDomains[sdShort], dom)){
this.render();
}
else{
console.log(sd + " is and invalid subDomain for " + dom);
}
},
_onSettingsChange: function(changed) {
// Route heatmap visualization changes to the renderer
if ((_.intersection(_.keys(changed.changed), this.heatmapOptionNames)).length > 0) {
this.render();
}
},
createView: function() {
return true;
},
// making the data look how we want it to for updateView to do its job
// in this case, it looks like this:
// {timestamp1: count, timestamp2: count, ... }
formatData: function(data) {
var rawFields = this.resultsModel.data().fields;
var domain = this.settings.get('domain');
var subDomain = this.settings.get('subDomain');
var filteredFields = _.filter(rawFields, function(d){ return d[0] !== "_"; });
var objects = _.map(data, function(row) {
return _.object(rawFields, row);
});
var series = [];
for(var i = 0; i < filteredFields.length; i++) {
series.push({ name: filteredFields[i], timestamps: {}, min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY });
}
_.each(objects, function(object) {
// Get the timestamp for this object
var time = new Date(object['_time']);
var timeValue = time.valueOf() / 1000;
// For each actual value, store it in the timestamp object
_.each(filteredFields, function(field, i) {
var value = object[field];
series[i].timestamps[timeValue] = parseInt(value, 10) || 0;
series[i].min = Math.min(series[i].min, value);
series[i].max = Math.max(series[i].max, value);
});
});
_.each(series, function(serie) {
});
return {
series: series,
domain: domain,
subDomain: subDomain,
start: new Date(objects[0]['_time']),
min: new Date(objects[0]['_time']),
max: new Date(objects[objects.length - 1]['_time'])
};
},
updateView: function(viz, data) {
var that = this;
// Options that can be set externally after instantiation
// that affect the display. Ensure that any "empty" values
// are set to null (use default). Some controls hand back
// empty strings, which result in nothing being shown.
// Not what is wanted.
var vizOptions = _.chain(this.settings.toJSON())
.pairs()
.filter(function(kv) { return _.contains(that.heatmapOptionNames, kv[0]); })
.filter(function(kv) { return ! (_.isNull(kv[1]) || _.isUndefined(kv[1])) || (kv[1] !== ""); })
.object()
.value();
this.$el.html('');
_.each(data.series, function(series, idx) {
var scale = d3.scale.quantile()
.domain([series.min, series.max])
.range([0,1,2,3,4]);
var legend = _.map(scale.quantiles(), function(x) { return Math.round(x); });
var title = series.name;
var $el = $("<div class='heatmap-container'/>").appendTo(that.el);
var $title = $("<h4 class='heatmap-series-title'>Heatmap for: " + series.name + "</h4>").appendTo($el);
var $buttons = $("<div class='heatmap-buttons'/>").appendTo($el);
var $prev = $("<a class='heatmap-prev btn-pill icon-triangle-left'></a>").appendTo($buttons);
var $next = $("<a class='heatmap-next btn-pill icon-triangle-right'></a>").appendTo($buttons);
var options = _.extend({
itemSelector: $el[0],
previousSelector: $prev[0],
nextSelector: $next[0],
data: series.timestamps,
domain: data.domain,
subDomain: data.subDomain,
start: data.start,
range: 4,
cellSize: 20,
cellPadding: 3,
domainGutter: 10,
highlight: ['now', new Date()],
legend: legend,
legendMargin: [0, 0, 20, 0],
legendCellSize: 14,
minDate: data.min,
maxDate: data.max,
onMinDomainReached: function(hit) {
$prev.attr("disabled", hit ? "disabled" : false);
},
onMaxDomainReached: function(hit) {
$next.attr("disabled", hit ? "disabled" : false);
},
onClick: function(date, value, title) {
that.trigger('click', {
date: date,
value: value,
series: series.name
});
that.settings.set('value', date.valueOf());
}
}, vizOptions);
var cal = new CalHeatMap();
cal.init(options); // create the calendar using either default or user defined options */
if (idx < data.series.length - 1) {
$("<hr/>").appendTo($el);
}
});
}
});
return CalendarHeatMap;
});

@ -0,0 +1,22 @@
Copyright (c) 2012 Tyler Kellen, contributors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,110 @@
/* Cal-HeatMap CSS */
.graph
{
clear: both;
display: block;
font-family: "Lucida Grande", Lucida, Verdana, sans-serif;
overflow: hidden;
}
.graph-label
{
fill: #999;
font-size: 10px
}
.graph, .graph-legend rect {
shape-rendering: crispedges
}
.graph-rect
{
fill: #ededed;
}
.graph rect:hover
{
stroke: #000;
stroke-width: 1px
}
.subdomain-text {
font-size: 8px;
fill: #999;
pointer-events: none;
}
.hover_cursor:hover {
cursor: pointer;
}
.qi {
background-color: #999;
fill: #999;
}
.q0
{
background-color: #fff;
fill: #fff;
stroke: #ededed
}
.q1
{
background-color: #dae289;
fill: #dae289
}
.q2
{
background-color: #cedb9c;
fill: #9cc069
}
.q3
{
background-color: #b5cf6b;
fill: #669d45
}
.q4
{
background-color: #637939;
fill: #637939
}
.q5
{
background-color: #3b6427;
fill: #3b6427
}
rect.highlight
{
stroke:#444;
stroke-width:1;
}
text.highlight
{
fill: #444;
}
rect.now
{
stroke: red;
}
text.now
{
fill: red;
font-weight: 800;
}
.domain-background {
fill: none;
shape-rendering: crispedges;
}

@ -0,0 +1,26 @@
Copyright (c) 2013, Michael Bostock
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name Michael Bostock may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,8 @@
{
"name": "d3",
"version": "3.3.5",
"main": "d3.js",
"ignore": [],
"dependencies": {},
"devDependencies": {}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,13 @@
.node circle {
stroke-width: 1.5px;
}
.node text {
font-size: 12px;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}

@ -0,0 +1,361 @@
// Cluster Dendrogram D3.js code taken and modified from http://bl.ocks.org/mbostock/4063570 by Mike Bostock
define(function (require, exports, module) {
var d3 = require("../d3/d3.layout");
var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
var _ = require("underscore");
require("css!./dendrogram.css");
var Dendrogram = SimpleSplunkView.extend({
className: "splunk-toolkit-dendrogram",
options: {
managerid: null,
data: "preview",
root_label: "root_label not set",
height: "auto",
node_outline_color: "#509DDD",
node_close_color: "#e7969c",
node_open_color: "#ffffff",
label_size_color: "#509DDD",
label_count_color: "#1f77b4",
has_size: true,
initial_open_level: 1,
margin_left: 100,
margin_right: 400,
},
output_mode: "json_rows",
initialize: function () {
_(this.options).extend({
height_px: 500,
width_px: 2000,
});
SimpleSplunkView.prototype.initialize.apply(this, arguments);
this.settings.on("change:order", this.render, this);
$(window).resize(this, _.debounce(this._handleResize, 20));
},
_handleResize: function (e) {
// e.data is the this pointer passed to the callback.
// here it refers to this object and we call render()
e.data.render();
},
createView: function () {
return true;
},
// Making the data look how we want it to for updateView to do its job
formatData: function (data) {
var height = this.settings.get("height");
var height_px = this.settings.get("height_px");
var width = this.settings.get("width");
var width_px = this.settings.get("width_px");
var root_label = this.settings.get("root_label");
var has_size = this.settings.get("has_size");
this.settings.set(
"height_px",
height === "auto" ? Math.max(data.length * 30, height_px) : height
);
data = _(data).map(function (row) {
return _(row).map(function (item, i) {
// Convert the string value to number
return has_size && i + 1 === row.length ? parseFloat(item) : item;
});
});
var get_sum = function (list) {
return _(list)
.pluck(list[0].length - 1)
.reduce(function (memo, num) {
return memo + num;
}, 0);
};
var nest = function (list) {
var groups = _(list).groupBy(0);
return _(groups).map(function (value, key) {
var children = _(value)
.chain()
.map(function (v) {
return _(v).rest();
})
.compact()
.value();
if (has_size) {
var sum = get_sum(children);
var count = children.length;
return children.length == 1 && children[0].length === 1
? { name: key, size: children[0][0] }
: { name: key, sum: sum, count: count, children: nest(children) };
} else {
return children.length == 1 && children[0].length === 0
? { name: key }
: { name: key, children: nest(children) };
}
});
};
var formatted_data = {
name: root_label,
children: nest(data),
};
if (has_size) {
_(formatted_data).extend({
sum: get_sum(data),
count: data.length,
});
}
return formatted_data;
},
updateView: function (viz, data) {
this.$el.html("");
//this.$el.append('<button id="open_all">Open all</button>');
//this.$el.append('<button id="close_all">Close all</button>');
//$("#open_all").on("click", function() {
// $("g.node_close").click();
//});
//$("#close_all").on("click", function() {
// $("g.node_open").click();
//});
var has_size = this.settings.get("has_size");
var node_outline_color = this.settings.get("node_outline_color");
var node_close_color = this.settings.get("node_close_color");
var node_open_color = this.settings.get("node_open_color");
var label_size_color = this.settings.get("label_size_color");
var label_count_color = this.settings.get("label_count_color");
var width = this.settings.get("width_px");
var height = this.settings.get("height_px");
var m = [
20,
this.settings.get("margin_right"),
20,
this.settings.get("margin_left"),
],
w = width - m[1] - m[3],
h = height - m[0] - m[2],
i = 0;
var tree = d3.layout.tree().size([h, w]);
var diagonal = d3.svg.diagonal().projection(function (d) {
return [d.y, d.x];
});
var vis = d3
.select(this.el)
.append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
data.x0 = h / 2;
data.y0 = 0;
function toggle_children(tree, level) {
if (tree.children) {
_(tree.children).each(function (child) {
toggle_children(child, level + 1);
});
if (level >= initial_open_level) {
toggle(tree);
}
}
}
var initial_open_level = this.settings.get("initial_open_level");
if (initial_open_level >= 0) {
toggle_children(data, 0);
}
var duration = 0;
update(data);
duration = d3.event && d3.event.altKey ? 5000 : 500;
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(data).reverse();
// Normalize for fixed-depth.
nodes.forEach(function (d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = vis.selectAll("g.node").data(nodes, function (d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node
.enter()
.append("svg:g")
//.attr("class", "node")
.attr("class", function (d) {
return d._children ? "node node_close" : "node node_open";
})
.attr("transform", function (d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", function (d) {
toggle(d);
update(d);
});
nodeEnter
.append("svg:circle")
.attr("r", 1e-6)
.style("fill", function (d) {
return d._children ? node_close_color : node_open_color;
})
.style("cursor", function (d) {
return d.children || d._children ? "pointer" : "default";
})
.style("stroke", node_outline_color);
nodeEnter
.append("svg:text")
.attr("x", function (d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "start";
})
.style("cursor", function (d) {
return d.children || d._children ? "pointer" : "default";
})
.style("fill-opacity", 1e-6)
.html(function (d) {
if (has_size) {
var sum = Number(d.sum).toLocaleString("en");
var size = Number(d.size).toLocaleString("en");
var long_label =
d.name +
' - <tspan fill="' +
label_size_color +
'">' +
sum +
'</tspan> - <tspan fill="' +
label_count_color +
'">' +
d.count +
"<tspan>";
var short_label =
d.name +
' - <tspan fill="' +
label_size_color +
'">' +
size +
"<tspan>";
return d.children || d._children ? long_label : short_label;
} else {
return d.name;
}
});
// Transition nodes to their new position.
var nodeUpdate = node
.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate
.select("circle")
.attr("r", 4.5)
.style("fill", function (d) {
return d._children ? node_close_color : node_open_color;
});
nodeUpdate.select("text").style("fill-opacity", 1);
nodeUpdate.select("text").style("fill", "white");
// Transition exiting nodes to the parent's new position.
var nodeExit = node
.exit()
.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle").attr("r", 1e-6);
nodeExit.select("text").style("fill-opacity", 1e-6);
// Update the links…
var link = vis
.selectAll("path.link")
.data(tree.links(nodes), function (d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link
.enter()
.insert("svg:path", "g")
.attr("class", "link")
.attr("d", function (d) {
var o = { x: source.x0, y: source.y0 };
return diagonal({ source: o, target: o });
})
.transition()
.duration(duration)
.attr("d", diagonal);
// Transition links to their new position.
link.transition().duration(duration).attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link
.exit()
.transition()
.duration(duration)
.attr("d", function (d) {
var o = { x: source.x, y: source.y };
return diagonal({ source: o, target: o });
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children.
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
},
});
return Dendrogram;
});

@ -0,0 +1,10 @@
{
"name": "forcedirected",
"version": "1.0.0",
"main": "forcedirected.js",
"ignore": [],
"dependencies": {
"d3": "3.3.x"
},
"devDependencies": {}
}

@ -0,0 +1,86 @@
.splunk-toolkit-force-directed {
overflow: hidden;
font-family: arial;
}
.splunk-toolkit-force-directed circle.node {
stroke: #fff;
stroke-width: 1.5px;
}
.splunk-toolkit-force-directed .link, .splunk-toolkit-force-directed #arrowEnd {
stroke: #999;
stroke-opacity: .6;
fill: none;
}
.splunk-toolkit-force-directed #arrowEnd {
fill: #999;
}
.splunk-toolkit-force-directed circle.node {
stroke: #fff;
stroke-width: 1.5px;
}
.splunk-toolkit-force-directed circle.nodeHighlight,
.splunk-toolkit-force-directed circle.highlight {
stroke-width: 2px;
stroke: #E89595;
}
.linkHighlight {
stroke: red !important;
}
.splunk-toolkit-force-directed circle.nodeHighlight.highlight {
stroke-width: 3px;
}
.splunk-toolkit-force-directed line.link {
stroke: #999;
stroke-opacity: .6;
}
.splunk-toolkit-force-directed #chart {
width: 100%;
height: 100%;
}
.splunk-toolkit-force-directed #tooltipContainer {
border: 1px solid hsl(0, 0%, 80%);
position: absolute;
min-width: 200px;
min-height: 50px;
border-radius:3px;
z-index:100;
background: #3A3A3A;
padding: 10px;
color: white;
top:50px;
}
.splunk-toolkit-force-directed .group-swatch {
width:20px;
height:20px;
float:left;
margin:2px;
margin-right: 10px
}
.splunk-toolkit-force-directed .group-name {
padding-top: 5px;
}
.splunk-toolkit-force-directed .tooltipLabel {
font-weight:bold;
padding-right:5px;
}
.splunk-toolkit-force-directed .tooltipRow {
margin-bottom:10px;
}
.splunk-toolkit-force-directed .panCursor {
cursor: move;
}

@ -0,0 +1,546 @@
// Force Directed Graphs!
// these require an input of (at least) 3 fields in the format
// 'stats count by field1 field2 field3'
// ---- settings ----
// height, width
// panAndZoom: the ability to zoom (true, false)
// directional: true, false
// valueField: what field to count by
// charges, gravity: change the look of the graph, play around with these!
// linkDistance: the distance between each node
// ---- expected data format ----
// a splunk search like this: source=*somedata* | stats count by artist_name track_name device
// each group is an artist/song pairing
// {
// "nodes":[
// {
// "source":"Bruno Mars",
// "group":0
// },
// {
// "source":"It Will Rain",
// "group":0
// },
// {
// "source":"Cobra Starship",
// "group":1
// },
// {
// "source":"You Make Me Feel",
// "group":1
// },
// {
// "source":"Gym Class Heroes",
// "group":2
// },
// {
// "source":"Stereo Hearts",
// "group":2
// },
// ],
// "links":[
// {
// "source":0,
// "target":1,
// "value":null
// },
// {
// "source":2,
// "target":3,
// "value":null
// },
// {
// "source":4,
// "target":5,
// "value":null
// },
// ],
// - we add this part -
// "groupNames":{
// "iphone":49,
// "android":53,
// "blackberry":48,
// "ipad":52,
// "ipod":50
// },
// "groupLookup":[
// "iphone",
// "android",
// "blackberry",
// "ipad",
// "ipod"
// ]
// }
define(function(require, exports, module) {
var _ = require('underscore');
var d3 = require("../d3/d3");
var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
var ForceDirected = SimpleSplunkView.extend({
moduleId: module.id,
className: "splunk-toolkit-force-directed",
options: {
managerid: null,
data: 'preview',
panAndZoom: true,
directional: true,
valueField: 'count',
charges: -500,
gravity: 0.2,
linkDistance: 15,
swoop: false,
isStatic: true
},
output_mode: "json_rows",
initialize: function() {
SimpleSplunkView.prototype.initialize.apply(this, arguments);
// in the case that any options are changed, it will dynamically update
// without having to refresh.
this.settings.on("change:charges", this.render, this);
this.settings.on("change:gravity", this.render, this);
this.settings.on("change:linkDistance", this.render, this);
this.settings.on("change:directional", this.render, this);
this.settings.on("change:panAndZoom", this.render, this);
this.settings.on("change:swoop", this.render, this);
this.settings.on("change:isStatic", this.render, this);
},
createView: function() {
var margin = {top: 10, right: 10, bottom: 10, left: 10};
var availableWidth = parseInt(this.settings.get("width") || this.$el.width(), 10);
var availableHeight = parseInt(this.settings.get("height") || this.$el.height(), 10);
this.$el.html("");
var svg = d3.select(this.el)
.append("svg")
.attr("width", availableWidth)
.attr("height", availableHeight)
.attr("pointer-events", "all");
return { container: this.$el, svg: svg, margin: margin };
},
// making the data look how we want it to for updateView to do its job
formatData: function(data) {
var nodes = {};
var links = [];
data.forEach(function(link) {
var sourceName = link[0];
var targetName = link[1];
var groupName = link[2];
var newLink = {};
newLink.source = nodes[sourceName] ||
(nodes[sourceName] = {name: sourceName, group: groupName, value: 0});
newLink.target = nodes[targetName] ||
(nodes[targetName] = {name: targetName, group: groupName, value: 0});
newLink.value = +link[3];
newLink.source.value += newLink.value;
newLink.target.value += newLink.value;
links.push(newLink);
});
return {nodes: d3.values(nodes), links: links};
},
updateView: function(viz, data) {
var that = this;
var containerHeight = this.$el.height();
var containerWidth = this.$el.width();
// Clear svg
var svg = $(viz.svg[0]);
svg.empty();
svg.height(containerHeight);
svg.width(containerWidth);
// Add the graph group as a child of the main svg
var graphWidth = containerWidth - viz.margin.left - viz.margin.right;
var graphHeight = containerHeight - viz.margin.top - viz.margin.bottom;
var graph = viz.svg
.append("g")
.attr("width", graphWidth)
.attr("height", graphHeight)
.attr("transform", "translate(" + viz.margin.left + "," + viz.margin.top + ")");
// Get settings
this.charge = this.settings.get('charges');
this.gravity = this.settings.get('gravity');
this.linkDistance = this.settings.get('linkDistance');
this.zoomable = this.settings.get('panAndZoom');
this.swoop = this.settings.get('swoop');
this.isStatic = this.settings.get('isStatic');
this.isDirectional = this.settings.get('directional');
this.zoomFactor = 0.5;
this.groupNameLookup = data.groupLookup;
// Set up graph
var r = 6;
var height = graphHeight;
var width = graphWidth;
var force = d3.layout.force()
.gravity(this.gravity)
.charge(this.charge)
.linkDistance(this.linkDistance)
.size([width, height]);
this.color = d3.scale.category20();
this.tooltips = new Tooltips(graph);
if (this.zoomable) {
initPanZoom.call(this, viz.svg);
}
graph.style("opacity", 1e-6)
.transition()
.duration(1000)
.style("opacity", 1);
graph.append("svg:defs").selectAll("marker")
.data(["arrowEnd"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 0)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("markerUnits", "userSpaceOnUse")
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var link = graph.selectAll("line.link")
.data(data.links)
.enter().append('path')
.attr("class", "link")
.attr("marker-end", function(d) {
if (that.isDirectional) {
return "url(#" + "arrowEnd" + ")";
}
})
.style("stroke-width", function(d) {
var num = Math.max(Math.round(Math.log(d.value)), 1);
return _.isNaN(num) ? 1 : num;
});
link
.on('click', function(d) {
that.trigger('click:link', {
source: d.source.name,
sourceGroup: d.source.group,
target: d.target.name,
targetGroup: d.target.group,
value: d.value
});
})
.on('mouseover', function(d) {
d3.select(this).classed('linkHighlight', true);
openLinkTooltip(d, this);
})
.on('mouseout', function(d) {
d3.select(this).classed('linkHighlight', false);
that.tooltips.close(this);
});
var node = graph.selectAll("circle.node")
.data(data.nodes)
.enter().append("svg:circle")
.attr("class", "node")
.attr("r", r - 1)
.style("fill", function(d) {
return that.color(d.group);
})
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
node
.on('click', function(d) {
that.trigger('click:node', {
name: d.name,
group: d.group,
value: d.value
});
})
.on('mouseover', function(d) {
d3.select(this).classed('nodeHighlight', true);
openNodeTooltip(d, this);
})
.on('mouseout', function(d) {
d3.select(this).classed('nodeHighlight', false);
that.tooltips.close(this);
});
force.nodes(data.nodes)
.links(data.links)
.on("tick", function() {
link.attr("d", function(d) {
var diffX = d.target.x - d.source.x;
var diffY = d.target.y - d.source.y;
// Length of path from center of source node to center of target node
var pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
// x and y distances from center to outside edge of target node
var offsetX = (diffX * (r * 2)) / pathLength;
var offsetY = (diffY * (r * 2)) / pathLength;
if (!that.swoop) {
pathLength = 0;
}
return "M" + d.source.x + "," + d.source.y + "A" + pathLength + "," + pathLength + " 0 0,1 " + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
});
node.attr("cx", function(d) {
d.x = Math.max(r, Math.min(width - r, d.x));
return d.x;
})
.attr("cy", function(d) {
d.y = Math.max(r, Math.min(height - r, d.y));
return d.y;
});
}).start();
if (this.isStatic) {
forwardAlpha(force, 0.005, 1000);
}
function forwardAlpha(layout, alpha, max) {
alpha = alpha || 0;
max = max || 1000;
var i = 0;
while (layout.alpha() > alpha && i++ < max) {
layout.tick();
}
}
// draggin'
function initPanZoom(svg) {
var that = this;
svg.on('mousedown.drag', function() {
if (that.zoomable) {
svg.classed('panCursor', true);
}
// console.log('drag start');
});
svg.on('mouseup.drag', function() {
svg.classed('panCursor', false);
// console.log('drag stop');
});
svg.call(d3.behavior.zoom().on("zoom", function() {
panZoom();
}));
}
// zoomin'
function panZoom() {
graph.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
function openNodeTooltip(d, node) {
var groupName;
if (that.groupNameLookup !== undefined) {
groupName = that.groupNameLookup[d.group];
} else {
groupName = d.group;
}
that.tooltips.open('nodes', {
slots: {
val: d.name,
group: groupName
},
swatch: that.color(d.group)
}, node);
}
function openLinkTooltip(d, node) {
that.tooltips.open('links', {
slots: {
source: d.source.name,
target: d.target.name
}
}, node);
}
//TODO: This doesn't seem to be used in this file
function getSafeVal(getobj, name) {
var retVal;
if (getobj.hasOwnProperty(name) && getobj[name] !== null) {
retVal = getobj[name];
} else {
retVal = name;
}
return retVal;
}
function highlightNodes(val) {
var self = this, groupName;
if (val !== ' ' && val !== '') {
graph.selectAll('circle')
.filter(function(d, i) {
groupName = self.groupNameLookup[d.group];
if (d.source.indexOf(val) >= 0 || groupName.indexOf(val) >= 0) {
d3.select(this).classed('highlight', true);
} else {
d3.select(this).classed('highlight', false);
}
});
} else {
graph.selectAll('circle').classed('highlight', false);
}
}
/////////////////////// formerly known as tooltips.js /////////////////////////////
function Tooltips(svg) {
var tooltipTimer = null,
tooltipOpenCoords = {},
tooltipIsOpen = false,
tooltipContents,
$tooltipContainer,
isReady = false,
layouts;
setup(svg, viz.container);
function setup(svg, $container) {
var self = this,
data = [0],
$nodeVal, $nodeGroup, $nodeContainer,
$linkSource, $linkTarget, $linkContainer;
$tooltipContainer = $("<div id='tooltipContainer'></div>");
$nodeContainer = $("<div class='nodeContainer'></div>");
$nodeVal = $("<div class='node-value tooltipRow'><span class='tooltipLabel'>Value: </span><span class='field1-val'></span></div>");
$nodeGroup = $("<div class='node-group tooltipRow'></div><div class='group-swatch'></div><div class='group-name'><span class='tooltipLabel'>Group: </span><span class='group-val'></span></div>");
$nodeContainer.append($nodeVal);
$nodeContainer.append($nodeGroup);
$tooltipContainer.append($nodeContainer);
$linkContainer = $("<div class='linkContainer'></div>");
$linkSource = $("<div class='source tooltipRow'><span class='tooltipLabel'>Source: </span><span class='source-val'></span></div>");
$linkTarget = $("<div class='target tooltipRow'><span class='tooltipLabel'>Target: </span><span class='target-val'></span></div>");
$linkContainer.append($linkSource);
$linkContainer.append($linkTarget);
$tooltipContainer.append($linkContainer);
$tooltipContainer.find('.group-swatch').hide();
$container.prepend($tooltipContainer);
$tooltipContainer.hide();
layouts = {
'nodes': {
"container": $nodeContainer,
"slots": {
"val": $nodeVal.find('.field1-val'),
"group": $nodeGroup.find('.group-val')
},
"swatch": $nodeContainer.find('.group-swatch')
},
'links': {
"container": $linkContainer,
"slots": {
"source": $linkSource.find('.source-val'),
"target": $linkTarget.find('.target-val')
}
}
};
isReady = true;
}
function clearTooltips() {
if (isReady) {
$.each(layouts, function(k, layout) {
$.each(layout.slots, function(k, v) {
// this isnt really neccesary because it's either hidden or shown with newly-replaced content
v.empty();
});
layout.container.hide();
if (layout.swatch !== undefined) {
layout.swatch.hide();
}
});
}
}
this.close = function(node) {
// return false;
var self = this,
dx, dy;
var mouseCoords = d3.mouse(node);
if (tooltipTimer !== null) {
window.clearTimeout(tooltipTimer);
}
dx = Math.abs(tooltipOpenCoords.x - mouseCoords[0]);
dy = Math.abs(tooltipOpenCoords.y - mouseCoords[1]);
/*
only close the tooltip when the user has moved a certain distance away
this helps when an element is very small and the user might have
difficulty keeping their mouse directly over it
*/
if (dy > 10 || dx > 10) {
tooltipIsOpen = false;
tooltipTimer = window.setTimeout(function() {
$tooltipContainer.fadeOut(400);
}, 500);
}
};
this.open = function(layout, data, node) {
var mouseCoords = d3.mouse(node);
tooltipIsOpen = true;
tooltipOpenCoords = {
x: mouseCoords[0] + 6 * 2,
y: mouseCoords[1] + 6 * 3
};
clearTooltips();
$.each(data.slots, function(k, v) {
layouts[layout]['slots'][k].append(v);
});
layouts[layout]['container'].show();
if (layouts[layout]['swatch'] !== undefined) {
layouts[layout]['swatch'].show().css('background-color', data.swatch);
}
$tooltipContainer
.css("left", tooltipOpenCoords.x)
.css("top", tooltipOpenCoords.y);
$tooltipContainer.fadeIn(400);
};
}
}
});
return ForceDirected;
});

@ -0,0 +1,53 @@
// This code has been imported from the following nice work: https://splunkbase.splunk.com/app/3171/
// Author: Ryan Thibodeaux
//
//
// Options module for passing
// parameters between modules/functions.
(function() {
define([
"module",
], function(module) {
var appOptions;
var options = {};
var config = module.config();
if (typeof config !== 'undefined' && config !== null) {
options = config.options;
}
return appOptions = (function() {
function appOptions() {} // empty constructor
// check if appOptions contains parameter 'name'
appOptions.hasOption = function(name) {
if (typeof options === 'undefined' ||
typeof name === 'undefined' ||
options.hasOwnProperty(name) !== true) {
return false;
}
return true;
};
// return value stored in parameter 'name'
appOptions.getOptionValue = function(name) {
return (appOptions.hasOption(name) ? options[name] : undefined);
};
// set parameter 'name' to value
appOptions.setOptionValue = function(name, value) {
if (!appOptions.hasOption(name)) {
return false;
}
options[name] = value;
return true;
};
return appOptions;
})();
});
}).call(this);

@ -0,0 +1,740 @@
// This code has been imported from the following nice work: https://splunkbase.splunk.com/app/3171/
// Author: Ryan Thibodeaux
//
//
// Utility functions used throughout
// other modules and funcitons in the app.
(function() {
define([
"jquery",
"underscore",
"appOptions",
"splunkjs/mvc",
"splunkjs/mvc/simplexml/ready!",
], function($, _, appOptions, mvc) {
var appUtils;
var footerRemovalTimerOn = 0;
var submittedTokenModel = mvc.Components.get('submitted');
var defaultTokenModel = mvc.Components.get('default');
if (typeof mvc === 'undefined' || !submittedTokenModel || !defaultTokenModel) {
var str = "Failed to load Splunk components. " +
"This is probably a symptom of a bigger problem.";
alert(str);
console.error(str);
}
return appUtils = (function() {
function appUtils() {} // empty constructor
// Initializes app tokens and set footer removal timer
appUtils.initiliazeApp = function(submit) {
// make sure appName and pageName are set, and set
// the 'my_app' and 'my_view' tokens accordingly
var myApp = appOptions.getOptionValue('appName');
var myView = appOptions.getOptionValue('pageName');
if (typeof myApp === 'undefined' || myApp.toString().trim().length < 1 ||
typeof myView === 'undefined' || myView.toString().trim().length < 1) {
var comps = (location.pathname.split('?')[0]).split('/');
var idx = comps.indexOf('app');
myApp = comps[idx + 1];
myView = comps[idx + 2];
appOptions.setOptionValue('appName', myApp);
appOptions.setOptionValue('pageName', myView);
}
appUtils.setToken('my_app', myApp);
appUtils.setToken('my_view', myView);
if (!!submit) { //!!undefined is false
appUtils.submitTokens();
}
// make sure pageStartTime is set
var startTime = appOptions.getOptionValue('pageStartTime');
if (typeof startTime === 'undefined' || startTime <= 0) {
startTime = new Date().valueOf();
appOptions.setOptionValue('pageStartTime', startTime);
}
// set timer to remove footer instead of relying on
// page 'load' event (that was unreliable)
appUtils.setFooterEditTimer(200);
};
// return token model objects
appUtils.getTokenModels = function() {
return ([defaultTokenModel, submittedTokenModel]);
}
// set generic wildcard tooltip on passed element name(s) where
// inputs can be an array or a comma-delimited list
appUtils.setWildCardTooltip = function(inputElements) {
if (typeof inputElements === 'undefined' || inputElements.length < 1) {
return;
}
var newArray = inputElements;
// test if not an Array - turn it into one if it is not
if (Object.prototype.toString.call(inputElements) !== '[object Array]') {
newArray = inputElements.replace(/^,+|,+$/gm, '').split(",");
}
var len = newArray.length;
for (var i = 0; i < len; i++) {
appUtils.setTooltip(newArray[i], 'Use \"*\" as a wildcard');
}
};
// set the 'tip' string as the tooltip for element 'name'
appUtils.setTooltip = function(name, tip) {
if (typeof name === 'undefined' || name.length < 1 || typeof tip === 'undefined') {
return;
}
var eleID = (name[0] === '#' ? name : '#' + name);
var e = $(eleID);
// element exists, so add tooltips
if (e.length) {
// add tooltips to text inputs
var textChild = e.children('div.splunk-textinput');
if (textChild.length) {
textChild.attr('title', tip);
var textChildInputs = textChild.find('input');
if (textChildInputs.length) {
textChildInputs.attr('placeholder', tip);
}
}
// add tooltips to multiselect and dropdown inputs
var msChild = e.children('div.splunk-choice-input');
if (msChild.length) {
msChild.attr('title', tip);
}
}
};
// remove links from Splunk footer
appUtils.hideFooterLinks = function() {
footerRemovalTimerOn = 0;
var footer = $('#footer');
if (footer.length > 0) {
links = footer.find('a');
if (links.length > 0) {
links.hide();
// hide the "Hide Filters" link on top of the
// dashboard if it is present
var hideLink = $('.hide-global-filters');
if (hideLink.length > 0) {
hideLink.hide();
}
} else {
appUtils.setFooterEditTimer();
}
}
};
// setup timer to remove links from Splunk footer, where
// the footer is checked 'delayMS' milliseconds from now
appUtils.setFooterEditTimer = function(delayMS) {
// set default delay for when to check for footer
// if delayMS was not set
if (Math.floor(delayMS) > 1) {} else {
delayMS = 1000;
}
// don't allow setting this timer after page has been loaded
// for more than 60 seconds
if (appUtils.getPageLoadedSecs() > 60) {
return;
}
// only allow assessing the footer if the footer is on
// the dashboard, i.e., hideFooter is not true
if ($('#footer').length > 0) {
if (!footerRemovalTimerOn) {
setTimeout(appUtils.hideFooterLinks, delayMS);
footerRemovalTimerOn = 1;
}
}
};
// number of seconds the page has been loaded
appUtils.getPageLoadedSecs = function() {
return ((new Date().valueOf() - appOptions.getOptionValue('pageStartTime')) / 1000);
};
// Sets token 'name' to 'value' in submittedTokenModel and
// defaultTokenModel unless excludeDefault is set to true
appUtils.setSubmittedToken = function(name, value, excludeDefault) {
if (typeof name === 'undefined' || !submittedTokenModel) {
return;
}
if (!excludeDefault) {
appUtils.setDefaulToken(name, value);
}
submittedTokenModel.set(name, value);
};
// Sets token 'name' to 'value' in defaultTokenModel.
appUtils.setDefaulToken = function(name, value) {
if (typeof name === 'undefined' || !defaultTokenModel) {
return;
}
defaultTokenModel.set(name, value);
};
// Sets token 'name' to 'value' in defaultTokenModel and
// submit all tokens if set
appUtils.setToken = function(name, value, submit) {
appUtils.setDefaulToken(name, value);
if (!!submit) {
appUtils.submitTokens();
}
};
// Returns value of token 'name' in defaultTokenModel and
appUtils.getDefaultToken = function(name) {
if (typeof name === 'undefined' || !defaultTokenModel) {
return undefined;
}
return defaultTokenModel.get(name);
};
// Returns value of token 'name' in submittedTokenModel and
appUtils.getSubmittedToken = function(name) {
if (typeof name === 'undefined' || !submittedTokenModel) {
return undefined;
}
return submittedTokenModel.get(name);
};
// Returns value of token 'name' in token model 'model'
appUtils.getToken = function(name, model) {
var tokens = (typeof model === 'undefined') ? defaultTokenModel : model;
if (typeof name === 'undefined' || !tokens) {
return undefined;
}
return tokens.get(name);
};
// Copy defaultTokenModel values into submittedTokenModels
appUtils.submitTokens = function() {
if (submittedTokenModel && defaultTokenModel) {
submittedTokenModel.set(defaultTokenModel.toJSON());
}
};
// Return boolean answer to if 'checkval' matches 'newval'
appUtils.checkTokenValue = function(checkval, newval) {
return (newval === checkval ? true : false);
};
// Jumps to div element eleID
appUtils.scrollIntoView = function(eleID, setting) {
var e = document.getElementById(eleID);
if (!!e && e.scrollIntoView) {
e.scrollIntoView(setting);
}
};
// return if a token is set or not, where "lax" determines
// if the token is checked if it is an empty value as well
appUtils.checkEmptyValue = function(value, lax) {
if (!!lax) {
return (typeof value === 'undefined')
} else {
return (typeof value === 'undefined' || value.length < 1)
}
};
// Toggles visibility of HTML elements of a dashboard
appUtils.hideHtmlElement = function(eleID, hide) {
if (appUtils.checkEmptyValue(eleID)) {
return;
}
var e = (eleID[0] === '#' ? eleID : '#' + eleID);
if ($(e).length) {
if (!!hide) {
$(e).hide();
} else {
$(e).show();
}
}
};
// loop through input elements and evaluate each one for focus
appUtils.checkEmptyTokenFocusForDashboard = function(inputs) {
if (typeof inputs === 'undefined') {
return;
}
var len = inputs.length;
var currentValue = undefined;
for (var i = 0; i < len; i++) {
currentValue = (defaultTokenModel.attributes.hasOwnProperty('form.' + inputs[i]) ? appUtils.getToken('form.' + inputs[i]) : appUtils.getToken(inputs[i]));
appUtils.checkEmptyTokenFocus(inputs[i], currentValue);
}
};
// set border style based on state of 'value'
appUtils.checkEmptyTokenFocus = function(name, value) {
var id = (name[0] === '#' ? name : '#' + name);
var p = $(id);
if (p.length) {
if (typeof value === 'undefined' || value.length < 1) {
appUtils.setInputFocus(p);
return true;
} else {
appUtils.clearInputFocus(p);
return false;
}
}
return false;
};
// set the focus effects on the based element
appUtils.setInputFocus = function(el) {
if (el.hasClass('input-text') || el.hasClass('splunk-textinput')) {
el.find('input[type="text"]').css("border-color", "red").css("box-shadow", "0px 1px 1px rgba(0, 0, 0, 0.075) inset, 0px 0px 8px rgba(222, 79, 79, 0.6");
} else if (el.hasClass('input-dropdown')) {
el.find('.select2-choice').css("border-color", "red").css("box-shadow", "0px 1px 1px rgba(0, 0, 0, 0.075) inset, 0px 0px 8px rgba(222, 79, 79, 0.6");
} else if (el.hasClass('input-multiselect')) {
el.find('.select2-choices').css("border-color", "red").css("box-shadow", "0px 1px 1px rgba(0, 0, 0, 0.075) inset, 0px 0px 8px rgba(222, 79, 79, 0.6");
} else {
el.css("border-style", "double");
}
};
// clear the focus effects on the based element
appUtils.clearInputFocus = function(el) {
if (el.hasClass('input-text') || el.hasClass('splunk-textinput')) {
el.find('input[type="text"]').css("border-color", "").css("box-shadow", "");
} else if (el.hasClass('input-dropdown')) {
el.find('.select2-choice').css("border-color", "").css("box-shadow", "");
} else if (el.hasClass('input-multiselect')) {
el.find('.select2-choices').css("border-color", "").css("box-shadow", "");
} else {
el.css("border-style", "none");
}
};
// add event listener of type to object using the assigned callback
appUtils.addEvent = function(object, type, callback) {
if (typeof object === 'undefined') {
return;
}
if (object.addEventListener) {
object.addEventListener(type, callback, false);
} else if (object.attachEvent) {
object.attachEvent("on" + type, callback);
} else {
object["on" + type] = callback;
}
};
// redirects to a new page in the current app where urlSegment
// starts with the new page to go to and newTab indicates if
// we want to open a new tab or not
appUtils.drilldownRedirect = function(urlSegment, newTab) {
if (typeof urlSegment === 'undefined' || urlSegment.toString().trim().length < 1) {
return;
}
// make sure the new segment starts with a '/'
var segment = urlSegment.toString().trim();
segment = (segment[0] === '/' ? segment : '/' + segment);
// get strip everything in the current URL and strip it down
// to what comes before the current page, including the last '/'
var uri = window.location.toString();
var currentPage = appUtils.getToken('my_view');
var path = uri.substr(0, uri.indexOf(currentPage)).replace(/\/+$/i, '');
// go to new URL
if (!!newTab) {
window.open(path + segment, "_blank");
} else {
window.location = path + segment;
}
};
// generate html button in parent element
// id: id and name to use on the html button
// label: label/span to apply to the button
// parent: id of parent html element in which to place the button
// append?: should append or prepend in parent list of children
// submit?: should it be a submit button type or not
// vertical?: is the button used in a vertical list of items
appUtils.generateButton = function(id, label, parent, append, submit, vertical) {
var btn = document.createElement('button');
var span;
// apply id field
if (typeof id !== 'undefined' && id.length > 0) {
btn.id = id;
btn.name = id;
}
// apply label
if (typeof label !== 'undefined' && label.length > 0) {
span = document.createElement('span');
span.innerHTML = label;
btn.appendChild(span);
}
// assign styling and insert if parent is set
if (typeof parent !== 'undefined' && parent.length > 0) {
var parentID = (parent[0] === '#' ? parent : '#' + parent);
var p = $(parentID);
if (p.length) {
// set button in its place of the parent
var t = p.find('.fieldset');
if (t.length) {
t = $(t[0]);
if (!!append) {
t.append(btn);
} else {
t.prepend(btn);
}
}
}
}
// set button type classes and CSS
if (!!submit) {
btn.className = 'btn btn-primary';
} else {
btn.className = 'btn-info btn-app-info';
}
// set button CSS based on it being in a
// vertical stack of items or not
if (!!vertical) {
btn.style.verticalAlign = 'middle';
btn.style.margin = "5px 10px 5px 0px";
} else {
btn.style.verticalAlign = 'top';
btn.style.marginTop = "21px";
btn.style.marginRight = " 10px";
}
return $(btn);
};
// strip value of dangerous characters in Splunk and trim the result
appUtils.cleanTxtString = function(value) {
if (typeof value === 'undefined' || value.length < 1) {
return "";
}
return value.replace(/%|\||\=|\[|\]|\(|\)/g, "").trim();
};
// clean raw input text elements
// value: current value to clean
// defaultVal: default value to return if cleaned version is empty/undefined
// post: function to use to clean passed value
appUtils.cleanTextInputElement = function(value, defaultVal, post) {
var cleanedValue = defaultVal;
if (typeof value !== 'undefined') {
if (!!post) {
cleanedValue = post(value.toString());
} else {
cleanedValue = appUtils.cleanTxtString(value.toString());
}
if (cleanedValue.length < 1) {
cleanedValue = defaultVal;
}
}
return cleanedValue;
};
// Forces the strict ordering of the values in the token of a checkbox
// group identified by the argument "name". The ordering is determined
// by the order of the individual checkboxes in the group. The current
// values of the token is passed via the "value" argument.
appUtils.enforceCheckboxOrdering = function(name, value) {
var cb = mvc.Components.getInstance(name);
if (typeof cb !== 'undefined') {
var preferred_values_order = [];
var new_field_list = [];
var matched = [];
var choices = cb.options.choices;
// set list of preferred order based on value ordering in checkbox
for (var i = 0; i < choices.length; i++) {
preferred_values_order.push(choices[i]['value']);
}
// values that do match entries in ordered_preference
matched = value.filter(function(x) { return preferred_values_order.indexOf(x) >= 0 });
// loop through preferred_values_order and add them if they are present in matched
for (var j = 0; j < preferred_values_order.length; j++) {
if (matched.indexOf(preferred_values_order[j]) >= 0) {
new_field_list.push(preferred_values_order[j]);
}
}
appUtils.setToken("form." + name, new_field_list);
}
};
// Setup the modal search tool button and event listeners
// for the passed instance in "modalObject"
appUtils.setupModalSearchTool = function(modalObject) {
if (typeof modalObject !== 'undefined') {
// Create a button on the top fieldset that will open the modal window
var modalButton = appUtils.generateButton('btn_modal_open', 'Open Search Tool');
modalButton.click(function() {
appUtils.setToken('dd_modal_search_time.earliest', appUtils.getToken('earliest'));
appUtils.setToken('dd_modal_search_time.latest', appUtils.getToken('latest'));
modalObject.show();
});
// add modal button to end of top fieldset after the last input / submit button
var topFieldset = $('.dashboard-body').find('.fieldset').first();
if (topFieldset.length > 0) {
var topFieldsetChildren = topFieldset.children();
if (topFieldsetChildren.length > 0) {
var i = topFieldsetChildren.length - 1;
for(i; i >= 0 ; i--) {
var lastChild = $(topFieldsetChildren[i]);
if (!lastChild.hasClass('form-submit') && !lastChild.hasClass('input')) {
// continue, go to next previous child
} else {
lastChild.after(modalButton);
break;
}
}
if (i < 0) {
topFieldset.append(modalButton);
}
} else {
topFieldset.append(modalButton);
}
}
defaultTokenModel.on("change:dd_modal_search_value", function(model, value, options) {
appUtils.checkEmptyTokenFocus("dd_modal_search_value", value);
});
submittedTokenModel.on("change:dd_modal_search_value", function(model, value, options) {
appUtils.setToken("dd_modal_search_value_internal", appUtils.parseModalSearchTerm("dd_modal_search_value", value), true);
});
}
};
// Parse and clean the search input string from modal
// window search tool.
// Returns the cleaned value.
// name: name of the token used for the search input text box
// value: value obtained from the text box
appUtils.parseModalSearchTerm = function(name, value) {
if (typeof value === 'undefined') {
return undefined;
} else if (value.toString().trim() === "") {
appUtils.setToken(name, undefined, true);
return undefined;
}
var valueCleaned = value.toString().replace(/\'|\"|\||%|\[|\]|\(|\)|\=/g, '');
if (valueCleaned !== value) {
alert('Search string contained disallowed characters (\'\"%|[]()=). They have been stripped in the applied search value.');
}
value = valueCleaned.trim();
if (value === "") {
alert("Applied search string is empty. Please enter a valid search string.");
appUtils.setToken(name, undefined, true);
return undefined;
}
return value;
};
// Parse and clean selected host. Valid inputs values
// will be separated into a domain and user token.
// Returns boolean value indicating if the tokens have changed.
// name: name of the token used for the input text box
// value: value obtained from the text box
appUtils.parseDashboardHostTokens = function(name, value) {
var newHostValue = undefined;
var currentHostValue = appUtils.getSubmittedToken('dd_target_host_internal');
var submit = false;
if (typeof value !== 'undefined') {
// trim whitespace
var cleanedValue = value.trim();
// reset initial value if nothing is left after cleaning,
// but don't call submit
if (cleanedValue.length < 1) {
appUtils.setToken(name, undefined);
} else {
// alert the host if there are disallowed characters
if (typeof cleanedValue !== 'undefined') {
var cleanedUser = cleanedValue.replace(/\*|\'|\"|\||%|\[|\]|\(|\)|\=/g, '');
if (cleanedUser !== cleanedValue) {
alert("The passed Host value contained disallowed characters (*\'\"%|[]()=). They have been stripped from the applied search.");
}
newHostValue = cleanedUser.trim();
}
// set user value to undefined if an empty string
if (typeof newHostValue === 'undefined' || newHostValue.length < 1) {
alert("The passed Host value is incomplete. Use the Search Tool to choose a Host.");
newHostValue = undefined;
}
}
}
// set host token value if different than the current value
if (newHostValue !== currentHostValue) {
appUtils.setToken('dd_target_host_internal', newHostValue);
submit = true;
}
return submit;
};
// Parse and clean selected user. Valid inputs values
// will be separated into a domain and user token.
// Returns boolean value indicating if the tokens have changed.
// name: name of the token used for the input text box
// value: value obtained from the text box
appUtils.parseDashboardUserTokens = function(name, value) {
var newUserValue = undefined;
var currentUserValue = appUtils.getSubmittedToken('dd_target_user_internal');
var newDomainValue = undefined;
var currentDomainValue = appUtils.getSubmittedToken('dd_target_domain_internal');
var submit = false;
if (typeof value !== 'undefined') {
// trim whitespace
var cleanedValue = value.trim();
// reset initial value if nothing is left after cleaning,
// but don't call submit
if (cleanedValue.length < 1) {
appUtils.setToken(name, undefined);
} else {
// inspect the data for validity
var regex = /([^\x5c]*)(?:\x5c+)?([^\x5c]*)?/g;
var match = regex.exec(cleanedValue);
var dom = match.length > 1 ? match[1] : undefined;
var user = match.length > 2 ? match[2] : undefined;
// alert the user if there are disallowed characters
if (typeof user !== 'undefined') {
var cleanedUser = user.replace(/\*|\'|\"|\||%|\[|\]|\(|\)|\=/g, '');
if (cleanedUser !== user) {
alert("The passed User value contained disallowed characters (*\'\"%|[]()=). They have been stripped from the applied search.");
}
newUserValue = cleanedUser.trim();
}
// alert the user if there are disallowed characters
if (typeof dom !== 'undefined') {
var cleanedDomain = dom.replace(/\*|\'|\"|\||%|\[|\]|\(|\)|\=/g, '');
if (cleanedDomain !== dom) {
alert("The passed Domain value contained disallowed characters (*\'\"%|[]()=). They have been stripped from the applied search.");
}
newDomainValue = cleanedDomain.trim();
}
// set user value to undefined if an empty string
if (typeof newUserValue === 'undefined' || newUserValue.length < 1) {
alert("The passed User value is incomplete. Use the Search Tool to choose a User.");
newUserValue = undefined;
}
// set domain value to undefined if an empty string
if (typeof newDomainValue === 'undefined' || newDomainValue.length < 1) {
alert("The passed Domain value is incomplete. Use the Search Tool to choose a Domain.");
newDomainValue = undefined;
}
}
}
// set user token value if different than the current value
if (newUserValue !== currentUserValue) {
appUtils.setToken('dd_target_user_internal', newUserValue);
submit = true;
}
// set domain token value if different than the current value
if (newDomainValue !== currentDomainValue) {
appUtils.setToken('dd_target_domain_internal', newDomainValue);
submit = true;
}
return submit;
};
return appUtils;
})();
});
}).call(this);

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2017, Ryan Thibodeaux
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,101 @@
/*
* Copyright (c) 2017, Ryan Thibodeaux. All Rights Reserved
* see included LICENSE file (BSD 3-clause)
*/
/* top-level entity */
.modal-text-msg {
position: fixed;
top: 40%;
left: 50%;
width: 450px;
margin-left: -225px;
z-index: 200000;
}
.modal-text-msg-unactivated {
display: none !important;
}
.modal-text-msg-activated {}
.modal-text-msg > .modal-body {
padding: 15px 20px 10px 20px;
max-height: 800px;
}
/* background */
.modal-text-msg-backdrop {
opacity: 0.5;
background-color: black;
z-index: 100000;
cursor: pointer;
}
.modal-text-msg-backdrop-clear {
opacity: 0.0 !important;
}
/* header items */
.modal-text-msg > .modal-header {
padding: 7px 0px 7px 20px
}
.modal-text-msg > .modal-header {
background: #90b5ea;
-webkit-border-top-left-radius: 5px;
-moz-border-radius-topleft: 5px;
border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
-moz-border-radius-topright: 5px;
border-top-right-radius: 5px;
border-radius: 5px 5px 0 0;
}
.modal-text-msg > .info {
background: #90b5ea;
}
.modal-text-msg > .debug {
background: #f5f5f5;
}
.modal-text-msg > .warn {
background: #dd9754;
}
.modal-text-msg > .error {
background: indianred;
}
.modal-text-msg > .modal-header > .modal-title {
line-height: 22px;
overflow-wrap: break-word;
padding-right: 30px;
font-size: 20px;
font-weight: bold;
margin: 0;
}
/* Text content elements */
.modal-text-msg p {
font-size:14px;
line-height: 20px;
}
/* footer items */
.modal-text-msg > .modal-footer {
padding: 5px 10px 5px 10px;
background-color: #f5f5f5;
border-radius: 0 0 5px 5px;
-webkit-border-bottom-left-radius: 5px;
-moz-border-radius-bottomleft: 5px;
border-bottom-left-radius: 5px;
-webkit-border-bottom-right-radius: 5px;
-moz-border-radius-bottomright: 5px;
border-bottom-right-radius: 5px;
}
.modal-text-msg-close:before {
color: #000000;
font-size: 18px;
font-weight: bold;
}
.modal-text-msg-close:hover:before {
color: #000000;
}

@ -0,0 +1,157 @@
/**
* @fileoverview Class definition for Modal Text Message
* @author Ryan Thibodeaux
* @version 1.0.1
*/
/*
* Copyright (c) 2017, Ryan Thibodeaux. All Rights Reserved
* see included LICENSE file (BSD 3-clause)
*/
define(function(require, exports, module) {
"use strict";
var _ = require('underscore');
var $ = require('jquery');
var Backbone = require('backbone');
require("css!/static/app/metricator-for-nmon/components/modaltextmsg/modaltextmsg.css");
// escapes any HTML passed into string
function escapeHTML(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
/**
* Creates a new ModalTextMsg object.
* @class
* @classdesc Modal Text Message class that displays messages as a modal box.
* @param {Object} options
* @param {String} options.type Type of text message to show
* @param {String} options.title Title for the text message
* @param {String} options.message Message content string
* @param {String} options.id HTML ID to use
*/
var ModalTextMsg = Backbone.View.extend({
className: 'ModalTextMsg',
content: undefined,
defaults: {
title: "", // title to show on top of the modal window
type: "info", // the type of modal message [info, debug, warn, error]
message: "", // the message to display in the modal window
id: "ModalTextMsgID", // the html ID to use for the modal text message
},
// initialize ModalTextMsg object
initialize: function(options) {
this.options = options;
this.options.title = (typeof this.options.title === 'undefined' ? this.defaults.title : escapeHTML(this.options.title).trim());
this.options.type = (typeof this.options.type === 'undefined' ? this.defaults.type : escapeHTML(this.options.type).trim().toLowerCase());
this.options.message = (typeof this.options.message === 'undefined' ? this.defaults.message : escapeHTML(this.options.message).trim());
this.options.id = (typeof this.options.id === 'undefined' ? this.defaults.id : escapeHTML(this.options.id).trim());
this.template = _.template(this.template);
// enforce the type to be of a specific value
switch(this.options.type) {
case "error":
this.options.title = (this.options.title === "" ? "Error" : this.options.title);
break;
case "warn":
this.options.title = (this.options.title === "" ? "Warning" : this.options.title);
break;
case "debug":
this.options.title = (this.options.title === "" ? "Debug" : this.options.title);
break;
case "info":
default:
this.options.type = "info";
this.options.title = (this.options.title === "" ? "Info" : this.options.title);
break;
}
// setup content div by breaking up message into
// paragraphs for every <br/> tag found
var c = document.createElement('div');
c.id = "modal-text-msg-content";
var msgParts = this.options.message.split("&lt;br/&gt;");
msgParts.forEach( function(str) {
var para = document.createElement("p");
var t = document.createTextNode(str.trim());
para.appendChild(t);
c.appendChild(para);
});
this.content = c;
// render the content and add it to the HTML body but don't show it
(this.render()).$el.addClass('modal-text-msg-unactivated');
$(document.body).append(this.el);
},
// click listeners
events: {
'click .modal-text-msg-close' : 'close',
'click .modal-text-msg-backdrop' : 'close'
},
// render the content based on the template
render: function() {
this.$el.html(this.template({
id : this.options.id,
title : this.options.title,
type : this.options.type
}));
this.$el.find(".modal-body").append(this.content);
return this;
},
// show the modal text message window
show: function() {
if (this.$el.hasClass('modal-text-msg-unactivated')) {
this.$el.removeClass('modal-text-msg-unactivated').addClass('modal-text-msg-activated');
}
this.updateVisibility();
return this;
},
// close the modal window and destroy the content
close: function() {
this.unbind();
this.remove();
this.updateVisibility();
return this;
},
// update visibility of modal windows that
// have been activated
updateVisibility: function() {
// make the last window visible and all others invisible
var modals = $(".modal-text-msg-activated");
if (modals.length > 0) {
modals.each(function(i,m) {
if (i == modals.length - 1) {
$(m).show()
} else {
$(m).hide();
}
});
}
},
// html template
template: '<div id="<%- id %>" class="modal modal-text-msg" role="dialog">' +
'<div class="modal-header <%- type %>">' +
'<div class="modal-title"><%- title %></div>' +
'</div>' +
'<div class="modal-body"></div>' +
'<div class="modal-footer"><button class="close modal-text-msg-close"/></div>' +
'</div>' +
'<div class="modal-backdrop modal-text-msg-backdrop"></div>'
});
return ModalTextMsg;
});

@ -0,0 +1,138 @@
/**
* @fileoverview Controlling logic for the Modal Text Message feature
* @author Ryan Thibodeaux
* @version 1.0.1
*/
/*
* Copyright (c) 2017, Ryan Thibodeaux. All Rights Reserved
* see included LICENSE file (BSD 3-clause)
*/
(function() {
require([
"jquery",
"ModalTextMsg",
"splunkjs/mvc",
"splunkjs/ready!",
"splunkjs/mvc/simplexml/ready!"
], function($, ModalTextMsg, mvc) {
"use strict";
// get token models and setup modifier functions
var defaultTokenModel = mvc.Components.get('default');
var submittedTokenModel = mvc.Components.get('submitted');
var urlTokenModel = mvc.Components.get('url');
function setToken(name, value, submit) {
defaultTokenModel.set(name, value);
if (!!submit) {
submitTokens();
}
}
function submitTokens() {
if (submittedTokenModel && defaultTokenModel) {
submittedTokenModel.set(defaultTokenModel.toJSON());
}
}
// parse passed modal text message title
function parseMessageTitle(token, title) {
var value = title;
if (typeof value !== 'undefined') {
if (Object.prototype.toString.call(title) === '[object Array]') {
value = title[0];
}
value = value.trim()
if (value.length > 0) {
next_title = value;
}
}
}
// parse passed modal text message content and trigger the display
function parseMessageToken(token, msg) {
var value = msg;
if (typeof value !== 'undefined') {
if (Object.prototype.toString.call(msg) === '[object Array]') {
value = msg[0];
}
value = value.trim();
if (value.length > 0) {
next_msg = value;
next_msg_type = token.split("_").pop();
triggerMsgDisplay();
}
}
}
// show the modal text message
function triggerMsgDisplay() {
var k = new ModalTextMsg({
title : next_title,
type : next_msg_type,
message : next_msg
});
k.show();
next_title = undefined;
next_msg = undefined;
next_msg_type = undefined;
}
/////////////////////////////////////////
/// Start Main Code Here
/////////////////////////////////////////
// array of message tokens in increasing order of priority
const MESSAGE_TOKENS = ["modal_msg_title", "modal_msg_debug", "modal_msg_info", "modal_msg_warn", "modal_msg_error"];
const MESSAGE_URL_TOKENS = ["modal_msg_url_title", "modal_msg_url_debug", "modal_msg_url_info", "modal_msg_url_warn", "modal_msg_url_error"];
var next_title = undefined;
var next_msg = undefined;
var next_msg_type = undefined;
// parse and display messages passed via URL tokens
var urlTokensSet = urlTokenModel.keys();
for (var i = 0; i < MESSAGE_URL_TOKENS.length; i++) {
if (urlTokensSet.indexOf(MESSAGE_URL_TOKENS[i]) >= 0) {
if (MESSAGE_URL_TOKENS[i] === "modal_msg_url_title") {
parseMessageTitle(MESSAGE_URL_TOKENS[i], urlTokenModel.get(MESSAGE_URL_TOKENS[i]));
} else {
parseMessageToken(MESSAGE_URL_TOKENS[i], urlTokenModel.get(MESSAGE_URL_TOKENS[i]));
}
}
}
// listen for changes to the message type tokens
MESSAGE_TOKENS.forEach(function(str) {
submittedTokenModel.on("change:" + str, function(model, value, options) {
if (str === "modal_msg_title") {
parseMessageTitle(str, value);
} else {
parseMessageToken(str, value);
}
if (typeof value !== 'undefined') {
setToken(str, undefined, true);
}
});
// see if the value was already set at page load via quick changes that
// may not be registered in the code block above since it isn't loaded
// quickly enough in 6.5+
var currentValue = submittedTokenModel.get(str);
var urlValue = urlTokenModel.get(str);
if (typeof currentValue !== "undefined" && currentValue.length > 0) {
if (typeof urlValue === "undefined" || urlValue.length < 1) {
setToken(str, currentValue + " ", true);
}
}
});
},function(err) {
// error callback
// the error has a list of modules that failed
var failedId = err.requireModules && err.requireModules[0];
console.error("Error when loading dependencies in Modal Text Message wrapper: ", err);
});
}).call(this);

@ -0,0 +1,10 @@
{
"name": "parallelcoords",
"version": "1.0.0",
"main": "parallelcoords.js",
"ignore": [],
"dependencies": {
"d3": "3.3.x"
},
"devDependencies": {}
}

@ -0,0 +1,26 @@
Copyright (c) 2012, Kai Chang
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name Kai Chang may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,34 @@
.parcoords > svg, .parcoords > canvas {
font: 14px sans-serif;
position: absolute;
}
.parcoords > canvas {
pointer-events: none;
}
.parcoords rect.background {
fill: transparent;
}
.parcoords rect.background:hover {
fill: rgba(120,120,120,0.2);
}
.parcoords .resize rect {
fill: rgba(0,0,0,0.1);
}
.parcoords rect.extent {
fill: rgba(255,255,255,0.25);
stroke: rgba(0,0,0,0.6);
}
.parcoords .axis line, .parcoords .axis path {
fill: none;
stroke: #222;
shape-rendering: crispEdges;
}
.parcoords canvas {
opacity: 1;
-moz-transition: opacity 0.3s;
-webkit-transition: opacity 0.3s;
-o-transition: opacity 0.3s;
}
.parcoords canvas.faded {
opacity: 0.25;
}

@ -0,0 +1,598 @@
define(function(require, exports, module) {
var d3 = require("../../d3/d3");
require("css!./d3-parcoords.css");
/// BEGIN LIBRARY CODE
//
d3.parcoords = function(config) {
var __ = {
data: [],
dimensions: [],
dimensionTitles: {},
types: {},
brushed: false,
mode: "default",
rate: 20,
width: 600,
height: 300,
margin: { top: 24, right: 0, bottom: 12, left: 0 },
color: "#069",
composite: "source-over",
alpha: 0.7
};
extend(__, config);
var pc = function(selection) {
selection = pc.selection = d3.select(selection);
__.width = selection[0][0].clientWidth;
__.height = selection[0][0].clientHeight;
// canvas data layers
["shadows", "marks", "foreground", "highlight"].forEach(function(layer) {
canvas[layer] = selection
.append("canvas")
.attr("class", layer)[0][0];
ctx[layer] = canvas[layer].getContext("2d");
});
// svg tick and brush layers
pc.svg = selection
.append("svg")
.attr("width", __.width)
.attr("height", __.height)
.append("svg:g")
.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
return pc;
};
var events = d3.dispatch.apply(this,["render", "resize", "highlight", "brush"].concat(d3.keys(__))),
w = function() { return __.width - __.margin.right - __.margin.left; },
h = function() { return __.height - __.margin.top - __.margin.bottom },
flags = {
brushable: false,
reorderable: false,
axes: false,
interactive: false,
shadows: false,
debug: false
},
xscale = d3.scale.ordinal(),
yscale = {},
dragging = {},
line = d3.svg.line(),
axis = d3.svg.axis().orient("left").ticks(5),
g, // groups for axes, brushes
ctx = {},
canvas = {};
// side effects for setters
var side_effects = d3.dispatch.apply(this,d3.keys(__))
.on("composite", function(d) { ctx.foreground.globalCompositeOperation = d.value; })
.on("alpha", function(d) { ctx.foreground.globalAlpha = d.value; })
.on("width", function(d) { pc.resize(); })
.on("height", function(d) { pc.resize(); })
.on("margin", function(d) { pc.resize(); })
.on("rate", function(d) { rqueue.rate(d.value); })
.on("data", function(d) {
if (flags.shadows) paths(__.data, ctx.shadows);
})
.on("dimensions", function(d) {
xscale.domain(__.dimensions);
if (flags.interactive) pc.render().updateAxes();
});
// expose the state of the chart
pc.state = __;
pc.flags = flags;
// create getter/setters
getset(pc, __, events);
// expose events
d3.rebind(pc, events, "on");
// tick formatting
d3.rebind(pc, axis, "ticks", "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat");
// getter/setter with event firing
function getset(obj,state,events) {
d3.keys(state).forEach(function(key) {
obj[key] = function(x) {
if (!arguments.length) return state[key];
var old = state[key];
state[key] = x;
side_effects[key].call(pc,{"value": x, "previous": old});
events[key].call(pc,{"value": x, "previous": old});
return obj;
};
});
};
function extend(target, source) {
for (key in source) {
target[key] = source[key];
}
return target;
};
pc.autoscale = function() {
// yscale
var defaultScales = {
"date": function(k) {
return d3.time.scale()
.domain(d3.extent(__.data, function(d) {
return d[k] ? d[k].getTime() : null;
}))
.range([h()+1, 1])
},
"number": function(k) {
return d3.scale.linear()
.domain(d3.extent(__.data, function(d) { return +d[k]; }))
.range([h()+1, 1])
},
"string": function(k) {
return d3.scale.ordinal()
.domain(__.data.map(function(p) { return p[k]; }))
.rangePoints([h()+1, 1])
}
};
__.dimensions.forEach(function(k) {
yscale[k] = defaultScales[__.types[k]](k);
});
// hack to remove ordinal dimensions with many values
pc.dimensions(pc.dimensions().filter(function(p,i) {
var uniques = yscale[p].domain().length;
if (__.types[p] == "string" && (uniques > 60 || uniques < 2)) {
return false;
}
return true;
}));
// xscale
xscale.rangePoints([0, w()], 1);
// canvas sizes
pc.selection.selectAll("canvas")
.style("margin-top", __.margin.top + "px")
.style("margin-left", __.margin.left + "px")
.attr("width", w()+2)
.attr("height", h()+2)
// default styles, needs to be set when canvas width changes
ctx.foreground.strokeStyle = __.color;
ctx.foreground.lineWidth = 1.4;
ctx.foreground.globalCompositeOperation = __.composite;
ctx.foreground.globalAlpha = __.alpha;
ctx.highlight.lineWidth = 3;
ctx.shadows.strokeStyle = "#dadada";
return this;
};
pc.detectDimensions = function() {
pc.types(pc.detectDimensionTypes(__.data));
pc.dimensions(d3.keys(pc.types()));
return this;
};
// a better "typeof" from this post: http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
pc.toType = function(v) {
return ({}).toString.call(v).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
};
// try to coerce to number before returning type
pc.toTypeCoerceNumbers = function(v) {
if ((parseFloat(v) == v) && (v != null)) return "number";
return pc.toType(v);
};
// attempt to determine types of each dimension based on first row of data
pc.detectDimensionTypes = function(data) {
var types = {}
d3.keys(data[0])
.forEach(function(col) {
types[col] = pc.toTypeCoerceNumbers(data[0][col]);
});
return types;
};
pc.render = function() {
// try to autodetect dimensions and create scales
if (!__.dimensions.length) pc.detectDimensions();
if (!(__.dimensions[0] in yscale)) pc.autoscale();
pc.render[__.mode]();
events.render.call(this);
return this;
};
pc.render.default = function() {
pc.clear('foreground');
if (__.brushed) {
__.brushed.forEach(path_foreground);
} else {
__.data.forEach(path_foreground);
}
};
var rqueue = d3.renderQueue(path_foreground)
.rate(50)
.clear(function() { pc.clear('foreground'); });
pc.render.queue = function() {
if (__.brushed) {
rqueue(__.brushed);
} else {
rqueue(__.data);
}
};
pc.shadows = function() {
flags.shadows = true;
if (__.data.length > 0) paths(__.data, ctx.shadows);
return this;
};
// draw little dots on the axis line where data intersects
pc.axisDots = function() {
var ctx = pc.ctx.marks;
ctx.globalAlpha = d3.min([1/Math.pow(data.length, 1/2), 1]);
__.data.forEach(function(d) {
__.dimensions.map(function(p,i) {
ctx.fillRect(position(p)-0.75,yscale[p](d[p])-0.75,1.5,1.5);
});
});
return this;
};
// draw single polyline
function color_path(d, ctx) {
ctx.strokeStyle = d3.functor(__.color)(d);
ctx.beginPath();
__.dimensions.map(function(p,i) {
if (i == 0) {
ctx.moveTo(position(p),yscale[p](d[p]));
} else {
ctx.lineTo(position(p),yscale[p](d[p]));
}
});
ctx.stroke();
};
// draw many polylines of the same color
function paths(data, ctx) {
ctx.clearRect(-1,-1,w()+2,h()+2);
ctx.beginPath();
data.forEach(function(d) {
__.dimensions.map(function(p,i) {
if (i == 0) {
ctx.moveTo(position(p),yscale[p](d[p]));
} else {
ctx.lineTo(position(p),yscale[p](d[p]));
}
});
});
ctx.stroke();
};
function path_foreground(d) {
return color_path(d, ctx.foreground);
};
function path_highlight(d) {
return color_path(d, ctx.highlight);
};
pc.clear = function(layer) {
ctx[layer].clearRect(0,0,w()+2,h()+2);
return this;
};
pc.createAxes = function() {
if (g) pc.removeAxes();
// Add a group element for each dimension.
g = pc.svg.selectAll(".dimension")
.data(__.dimensions, function(d) { return d; })
.enter().append("svg:g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + xscale(d) + ")"; })
// Add an axis and title.
g.append("svg:g")
.attr("class", "axis")
.attr("transform", "translate(0,0)")
.each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
.append("svg:text")
.attr({
"text-anchor": "middle",
"y": 0,
"transform": "translate(0,-12)",
"x": 0,
"class": "label"
})
.text(function(d) {
return d in __.dimensionTitles ? __.dimensionTitles[d] : d; // dimension display names
})
flags.axes= true;
return this;
};
pc.removeAxes = function() {
g.remove();
return this;
};
pc.updateAxes = function() {
var g_data = pc.svg.selectAll(".dimension")
.data(__.dimensions, function(d) { return d; })
g_data.enter().append("svg:g")
.attr("class", "dimension")
.attr("transform", function(p) { return "translate(" + position(p) + ")"; })
.style("opacity", 0)
.append("svg:g")
.attr("class", "axis")
.attr("transform", "translate(0,0)")
.each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
.append("svg:text")
.attr({
"text-anchor": "middle",
"y": 0,
"transform": "translate(0,-12)",
"x": 0,
"class": "label"
})
.text(String);
g_data.exit().remove();
g = pc.svg.selectAll(".dimension");
g.transition().duration(1100)
.attr("transform", function(p) { return "translate(" + position(p) + ")"; })
.style("opacity", 1)
if (flags.shadows) paths(__.data, ctx.shadows);
return this;
};
pc.brushable = function() {
if (!g) pc.createAxes();
// Add and store a brush for each axis.
g.append("svg:g")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(
yscale[d].brush = d3.svg.brush()
.y(yscale[d])
.on("brushstart", function() {
d3.event.sourceEvent.stopPropagation();
})
.on("brush", pc.brush)
);
})
.selectAll("rect")
.style("visibility", null)
.attr("x", -15)
.attr("width", 30)
flags.brushable = true;
return this;
};
// Jason Davies, http://bl.ocks.org/1341281
pc.reorderable = function() {
if (!g) pc.createAxes();
g.style("cursor", "move")
.call(d3.behavior.drag()
.on("dragstart", function(d) {
dragging[d] = this.__origin__ = xscale(d);
})
.on("drag", function(d) {
dragging[d] = Math.min(w(), Math.max(0, this.__origin__ += d3.event.dx));
__.dimensions.sort(function(a, b) { return position(a) - position(b); });
xscale.domain(__.dimensions);
pc.render();
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("dragend", function(d) {
delete this.__origin__;
delete dragging[d];
d3.select(this).transition().attr("transform", "translate(" + xscale(d) + ")");
pc.render();
}));
flags.reorderable = true;
return this;
};
// pairs of adjacent dimensions
pc.adjacent_pairs = function(arr) {
var ret = [];
for (var i = 0; i < arr.length-1; i++) {
ret.push([arr[i],arr[i+1]]);
};
return ret;
};
pc.interactive = function() {
flags.interactive = true;
return this;
};
// Get data within brushes
pc.brush = function() {
__.brushed = selected();
events.brush.call(pc,__.brushed);
pc.render();
};
// expose a few objects
pc.xscale = xscale;
pc.yscale = yscale;
pc.ctx = ctx;
pc.canvas = canvas;
pc.g = function() { return g; };
pc.brushReset = function(dimension) {
if (g) {
g.selectAll('.brush')
.each(function(d) {
d3.select(this).call(
yscale[d].brush.clear()
);
})
pc.brush();
}
return this;
};
// rescale for height, width and margins
// TODO currently assumes chart is brushable, and destroys old brushes
pc.resize = function() {
// selection size
pc.selection.select("svg")
.attr("width", __.width)
.attr("height", __.height)
pc.svg.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
// scales
pc.autoscale();
// axes, destroys old brushes. the current brush state should pass through in the future
if (g) pc.createAxes().brushable();
events.resize.call(this, {width: __.width, height: __.height, margin: __.margin});
return this;
};
// highlight an array of data
pc.highlight = function(data) {
pc.clear("highlight");
d3.select(canvas.foreground).classed("faded", true);
data.forEach(path_highlight);
events.highlight.call(this,data);
return this;
};
// clear highlighting
pc.unhighlight = function(data) {
pc.clear("highlight");
d3.select(canvas.foreground).classed("faded", false);
return this;
};
// calculate 2d intersection of line a->b with line c->d
// points are objects with x and y properties
pc.intersection = function(a, b, c, d) {
return {
x: ((a.x * b.y - a.y * b.x) * (c.x - d.x) - (a.x - b.x) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)),
y: ((a.x * b.y - a.y * b.x) * (c.y - d.y) - (a.y - b.y) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x))
};
};
function is_brushed(p) {
return !yscale[p].brush.empty();
};
// data within extents
function selected() {
var actives = __.dimensions.filter(is_brushed),
extents = actives.map(function(p) { return yscale[p].brush.extent(); });
// test if within range
var within = {
"date": function(d,p,dimension) {
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
},
"number": function(d,p,dimension) {
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
},
"string": function(d,p,dimension) {
return extents[dimension][0] <= yscale[p](d[p]) && yscale[p](d[p]) <= extents[dimension][1]
}
};
return __.data
.filter(function(d) {
return actives.every(function(p, dimension) {
return within[__.types[p]](d,p,dimension);
});
});
};
function position(d) {
var v = dragging[d];
return v == null ? xscale(d) : v;
}
pc.toString = function() { return "Parallel Coordinates: " + __.dimensions.length + " dimensions (" + d3.keys(__.data[0]).length + " total) , " + __.data.length + " rows"; };
pc.version = "0.2.2";
return pc;
};
d3.renderQueue = (function(func) {
var _queue = [], // data to be rendered
_rate = 10, // number of calls per frame
_clear = function() {}, // clearing function
_i = 0; // current iteration
var rq = function(data) {
if (data) rq.data(data);
rq.invalidate();
_clear();
rq.render();
};
rq.render = function() {
_i = 0;
var valid = true;
rq.invalidate = function() { valid = false; };
function doFrame() {
if (!valid) return true;
if (_i > _queue.length) return true;
var chunk = _queue.slice(_i,_i+_rate);
_i += _rate;
chunk.map(func);
}
d3.timer(doFrame);
};
rq.data = function(data) {
rq.invalidate();
_queue = data.slice(0);
return rq;
};
rq.rate = function(value) {
if (!arguments.length) return _rate;
_rate = value;
return rq;
};
rq.remaining = function() {
return _queue.length - _i;
};
// clear the canvas
rq.clear = function(func) {
if (!arguments.length) {
_clear();
return rq;
}
_clear = func;
return rq;
};
rq.invalidate = function() {};
return rq;
});
/// END LIBRARY CODE
return d3.parcoords;
});

@ -0,0 +1,117 @@
// parallel coords!
// a visualisation technique for multidimensional categorical data
// you can drag the vertical axis for each section to filter things (try it out for yourself)
// --- settings ---
// none for the time being.
// TODO: add settings to choose which data goes where
// --- expected data format ---
// a splunk search like this: index=_internal sourcetype=splunkd_access | table method status
define(function(require, exports, module) {
var _ = require('underscore');
var d3 = require("../d3/d3");
var parcoords = require("./contrib/d3-parcoords");
var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
var ParCoords = SimpleSplunkView.extend({
className: "splunk-toolkit-parcoords",
options: {
managerid: null, // your MANAGER ID
data: "preview", // Results type
},
output_mode: "json_rows",
initialize: function() {
SimpleSplunkView.prototype.initialize.apply(this, arguments);
this.settings.enablePush("value");
// Set up resize callback. The first argument is a this
// pointer which gets passed into the callback event
$(window).resize(this, _.debounce(this._handleResize, 20));
},
_handleResize: function(e){
// e.data is the this pointer passed to the callback.
// here it refers to this object and we call render()
e.data.render();
},
createView: function() {
this.$el.html(''); // clearing all prior junk from the view (eg. 'waiting for data...')
return true;
},
// making the data look how we want it to for updateView to do its job
formatData: function(data) {
// Decide what fields we want
// TODO: this should be specifialbe
var fields = _.filter(this.resultsModel.data().fields, function(d){return d[0] !== "_" });
var objects = _.map(data, function(row) {
var obj = {};
_.each(fields, function(field, idx) {
if (row[idx] !== null) {
obj[field] = row[idx];
}
else {
obj[field] = "";
}
});
return obj;
});
data = {
'results': objects,
'fields': fields
}
return data;
},
updateView: function(viz, data) {
var that = this;
var availableHeight = parseInt(this.settings.get("height") || this.$el.height());
this.$el.html('');
var fields = data.fields;
viz = $("<div id='"+this.id+"_parallelcoords' class='parcoords'>").appendTo(this.el)
.css("height", availableHeight)
var colorgen = d3.scale.category20();
var colors = {};
_(data.results).chain()
.pluck(fields[0])
.uniq()
.each(function(d,i) {
colors[d] = colorgen(i);
});
var color = function(d) {return colors[d[fields[0]]]; };
var pc_progressive = d3.parcoords()('#' + this.id + '_parallelcoords')
.data(data.results)
.color(color)
.alpha(0.4)
.margin({ top: 24, left: 150, bottom: 12, right: 0 })
.mode("queue")
.render()
.brushable() // enable brushing
.interactive() // command line mode
.on("brush", function(selected) {
that.trigger("select", {selected: selected});
});
pc_progressive.svg.selectAll("text")
.style("font", "10px sans-serif");
}
});
return ParCoords;
});

@ -0,0 +1,10 @@
{
"name": "sankey",
"version": "1.0.0",
"main": "sankey.js",
"ignore": [],
"dependencies": {
"d3": "3.3.x"
},
"devDependencies": {}
}

@ -0,0 +1,26 @@
Copyright (c) 2013, Michael Bostock
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name Michael Bostock may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,585 @@
define(function(require, exports, module) {
var d3 = require('../../d3/d3');
/// BEGIN LIBRARY CODE
// A modified d3 sankey plugin
// This is taken verbatim from:
// https://github.com/kunalb/d3-plugins/blob/sankey/sankey/sankey.js
// Referenced from pull request:
// https://github.com/d3/d3-plugins/pull/39
d3.sankey = function() {
var sankey = {},
nodeWidth = 24,
nodePadding = 8,
size = [1, 1],
nodes = [],
links = [],
components = [];
sankey.nodeWidth = function(_) {
if (!arguments.length) return nodeWidth;
nodeWidth = +_;
return sankey;
};
sankey.nodePadding = function(_) {
if (!arguments.length) return nodePadding;
nodePadding = +_;
return sankey;
};
sankey.nodes = function(_) {
if (!arguments.length) return nodes;
nodes = _;
return sankey;
};
sankey.links = function(_) {
if (!arguments.length) return links;
links = _;
return sankey;
};
sankey.size = function(_) {
if (!arguments.length) return size;
size = _;
return sankey;
};
sankey.layout = function(iterations) {
computeNodeLinks();
computeNodeValues();
computeNodeStructure();
computeNodeBreadths();
computeNodeDepths(iterations);
computeLinkDepths();
return sankey;
};
sankey.relayout = function() {
computeLinkDepths();
return sankey;
};
// A more involved path generator that requires 3 elements to render --
// It draws a starting element, intermediate and end element that are useful
// while drawing reverse links to get an appropriate fill.
//
// Each link is now an area and not a basic spline and no longer guarantees
// fixed width throughout.
//
// Sample usage:
//
// linkNodes = this._svg.append("g").selectAll(".link")
// .data(this.links)
// .enter().append("g")
// .attr("fill", "none")
// .attr("class", ".link")
// .sort(function(a, b) { return b.dy - a.dy; });
//
// linkNodePieces = [];
// for (var i = 0; i < 3; i++) {
// linkNodePieces[i] = linkNodes.append("path")
// .attr("class", ".linkPiece")
// .attr("d", path(i))
// .attr("fill", ...)
// }
sankey.reversibleLink = function() {
var curvature = .5;
// Used when source is behind target, the first and last paths are simple
// lines at the start and end node while the second path is the spline
function forwardLink(part, d) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy,
y1 = d.target.y + d.ty,
y2 = d.source.y + d.sy + d.dy,
y3 = d.target.y + d.ty + d.dy;
switch (part) {
case 0:
return "M" + x0 + "," + y0 + "L" + x0 + "," + (y0 + d.dy);
case 1:
return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0 + " " + x3 + "," + y1 + " " + x1 + "," + y1
+ "L" + x1 + "," + y3
+ "C" + x3 + "," + y3 + " " + x2 + "," + y2 + " " + x0 + "," + y2
+ "Z";
case 2:
return "M" + x1 + "," + y1 + "L" + x1 + "," + (y1 + d.dy);
}
}
// Used for self loops and when the source is actually in front of the
// target; the first element is a turning path from the source to the
// destination, the second element connects the two twists and the last
// twists into the target element.
//
//
// /--Target
// \----------------------\
// Source--/
//
function backwardLink(part, d) {
var curveExtension = 30;
var curveDepth = 15;
function getDir(d) {
return d.source.y + d.sy > d.target.y + d.ty ? -1 : 1;
}
function p(x, y) {
return x + "," + y + " ";
}
var dt = getDir(d) * curveDepth,
x0 = d.source.x + d.source.dx,
y0 = d.source.y + d.sy,
x1 = d.target.x,
y1 = d.target.y + d.ty;
switch (part) {
case 0:
return "M" + p(x0, y0) +
"C" + p(x0, y0) +
p(x0 + curveExtension, y0) +
p(x0 + curveExtension, y0 + dt) +
"L" + p(x0 + curveExtension, y0 + dt + d.dy) +
"C" + p(x0 + curveExtension, y0 + d.dy) +
p(x0, y0 + d.dy) +
p(x0, y0 + d.dy) +
"Z";
case 1:
return "M" + p(x0 + curveExtension, y0 + dt) +
"C" + p(x0 + curveExtension, y0 + 3 * dt) +
p(x1 - curveExtension, y1 - 3 * dt) +
p(x1 - curveExtension, y1 - dt) +
"L" + p(x1 - curveExtension, y1 - dt + d.dy) +
"C" + p(x1 - curveExtension, y1 - 3 * dt + d.dy) +
p(x0 + curveExtension, y0 + 3 * dt + d.dy) +
p(x0 + curveExtension, y0 + dt + d.dy) +
"Z";
case 2:
return "M" + p(x1 - curveExtension, y1 - dt) +
"C" + p(x1 - curveExtension, y1) +
p(x1, y1) +
p(x1, y1) +
"L" + p(x1, y1 + d.dy) +
"C" + p(x1, y1 + d.dy) +
p(x1 - curveExtension, y1 + d.dy) +
p(x1 - curveExtension, y1 + d.dy - dt) +
"Z";
}
}
return function(part) {
return function(d) {
if (d.source.x < d.target.x) {
return forwardLink(part, d);
} else {
return backwardLink(part, d);
}
}
}
};
// The standard link path using a constant width spline that needs a
// single path element.
sankey.link = function() {
var curvature = .5;
function link(d) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy + d.dy / 2,
y1 = d.target.y + d.ty + d.dy / 2;
return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0
+ " " + x3 + "," + y1
+ " " + x1 + "," + y1;
}
link.curvature = function(_) {
if (!arguments.length) return curvature;
curvature = +_;
return link;
};
return link;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks() {
nodes.forEach(function(node) {
node.sourceLinks = [];
node.targetLinks = [];
});
links.forEach(function(link) {
var source = link.source,
target = link.target;
if (typeof source === "number") source = link.source = nodes[link.source];
if (typeof target === "number") target = link.target = nodes[link.target];
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues() {
nodes.forEach(function(node) {
node.value = Math.max(
d3.sum(node.sourceLinks, value),
d3.sum(node.targetLinks, value)
);
});
}
// Take the list of nodes and create a DAG of supervertices, each consisting
// of a strongly connected component of the graph
//
// Based off:
// http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
function computeNodeStructure() {
var nodeStack = [],
index = 0;
nodes.forEach(function(node) {
if (!node.index) {
connect(node);
}
});
function connect(node) {
node.index = index++;
node.lowIndex = node.index;
node.onStack = true;
nodeStack.push(node);
if (node.sourceLinks) {
node.sourceLinks.forEach(function(sourceLink){
var target = sourceLink.target;
if (!target.hasOwnProperty('index')) {
connect(target);
node.lowIndex = Math.min(node.lowIndex, target.lowIndex);
} else if (target.onStack) {
node.lowIndex = Math.min(node.lowIndex, target.index);
}
});
if (node.lowIndex === node.index) {
var component = [], currentNode;
do {
currentNode = nodeStack.pop()
currentNode.onStack = false;
component.push(currentNode);
} while (currentNode != node);
components.push({
root: node,
scc: component
});
}
}
}
components.forEach(function(component, i){
component.index = i;
component.scc.forEach(function(node) {
node.component = i;
});
});
}
// Assign the breadth (x-position) for each strongly connected component,
// followed by assigning breadth within the component.
function computeNodeBreadths() {
layerComponents();
components.forEach(function(component, i){
bfs(component.root, function(node){
var result = node.sourceLinks
.filter(function(sourceLink){
return sourceLink.target.component == i;
})
.map(function(sourceLink){
return sourceLink.target;
});
return result;
});
});
var max = 0;
var componentsByBreadth = d3.nest()
.key(function(d) { return d.x; })
.sortKeys(d3.ascending)
.entries(components)
.map(function(d) { return d.values; });
var max = -1, nextMax = -1;
componentsByBreadth.forEach(function(c){
c.forEach(function(component){
component.x = max + 1;
component.scc.forEach(function(node){
node.x = component.x + node.x;
nextMax = Math.max(nextMax, node.x);
});
});
max = nextMax;
});
nodes
.filter(function(node) {
var outLinks = node.sourceLinks.filter(function(link){ return link.source.name != link.target.name; });
return (outLinks.length == 0);
})
.forEach(function(node) { node.x = max; })
scaleNodeBreadths((size[0] - nodeWidth) / Math.max(max, 1));
function flatten(a) {
return [].concat.apply([], a);
}
function layerComponents() {
var remainingComponents = components,
nextComponents,
visitedIndex,
x = 0;
while (remainingComponents.length) {
nextComponents = [];
visitedIndex = {};
remainingComponents.forEach(function(component) {
component.x = x;
component.scc.forEach(function(n) {
n.sourceLinks.forEach(function(l) {
if (!visitedIndex.hasOwnProperty(l.target.component) &&
l.target.component != component.index) {
nextComponents.push(components[l.target.component]);
visitedIndex[l.target.component] = true;
}
})
});
});
remainingComponents = nextComponents;
++x;
}
}
function bfs(node, extractTargets) {
var queue = [node], currentCount = 1, nextCount = 0;
var x = 0;
while(currentCount > 0) {
var currentNode = queue.shift();
currentCount--;
if (!currentNode.hasOwnProperty('x')) {
currentNode.x = x;
currentNode.dx = nodeWidth;
var targets = extractTargets(currentNode);
queue = queue.concat(targets);
nextCount += targets.length;
}
if (currentCount == 0) { // level change
x++;
currentCount = nextCount;
nextCount = 0;
}
}
}
}
function moveSourcesRight() {
nodes.forEach(function(node) {
if (!node.targetLinks.length) {
node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
}
});
}
function moveSinksRight(x) {
nodes.forEach(function(node) {
if (!node.sourceLinks.length) {
node.x = x - 1;
}
});
}
function scaleNodeBreadths(kx) {
nodes.forEach(function(node) {
node.x *= kx;
});
}
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function(d) { return d.x; })
.sortKeys(d3.ascending)
.entries(nodes)
.map(function(d) { return d.values; });
//
initializeNodeDepth();
resolveCollisions();
for (var alpha = 1; iterations > 0; --iterations) {
relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function(nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
});
nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
node.y = i;
node.dy = node.value * ky;
});
});
links.forEach(function(link) {
link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
nodesByBreadth.forEach(function(nodes, breadth) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedSource(link) {
return center(link.source) * link.value;
}
}
function relaxRightToLeft(alpha) {
nodesByBreadth.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedTarget(link) {
return center(link.target) * link.value;
}
}
function resolveCollisions() {
nodesByBreadth.forEach(function(nodes) {
var node,
dy,
y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y0 - node.y;
if (dy > 0) node.y += dy;
y0 = node.y + node.dy + nodePadding;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y0 - nodePadding - size[1];
if (dy > 0) {
y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y + node.dy + nodePadding - y0;
if (dy > 0) node.y -= dy;
y0 = node.y;
}
}
});
}
function ascendingDepth(a, b) {
return a.y - b.y;
}
}
function computeLinkDepths() {
nodes.forEach(function(node) {
node.sourceLinks.sort(ascendingTargetDepth);
node.targetLinks.sort(ascendingSourceDepth);
});
nodes.forEach(function(node) {
var sy = 0, ty = 0;
node.sourceLinks.forEach(function(link) {
link.sy = sy;
sy += link.dy;
});
node.targetLinks.forEach(function(link) {
link.ty = ty;
ty += link.dy;
});
});
function ascendingSourceDepth(a, b) {
return a.source.y - b.source.y;
}
function ascendingTargetDepth(a, b) {
return a.target.y - b.target.y;
}
}
function center(node) {
return node.y + node.dy / 2;
}
function value(link) {
return link.value;
}
return sankey;
};
/// END LIBRARY CODE
return d3.sankey;
});

@ -0,0 +1,28 @@
.sankey-diagram .node rect {
cursor: move;
fill-opacity: 0.9;
shape-rendering: crispEdges;
}
.sankey-diagram .node text {
pointer-events: none;
text-shadow: 0 1px 0 white;
}
.sankey-diagram .link {
fill: none;
stroke: black;
stroke-opacity: 0.2;
}
.sankey-diagram .link:hover, .sankey-diagram .link.hovering {
stroke-opacity: 0.5;
}
.sankey-diagram .link.my-selected {
stroke: yellow;
}
.sankey-diagram scrollable {
overflow-y: auto;
}

@ -0,0 +1,277 @@
// AppFramework Sankey Plug-In
// ---------------------------
//
// Provide an easy-to-use plug-in that takes data that relates a
// many-to-many relationship with scores into a Sankey display, a form
// of flow display. Any relationship between two fields can be
// illustrated, although the most common is
// "stats count by field1, field2"
define(function(require, exports, module) {
var _ = require("underscore");
var $ = require("jquery");
var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
var d3 = require("../d3/d3");
// Load D3 Sankey plugin
require("./contrib/d3-sankey");
// Import CSS for the sankey chart.
require("css!./sankey.css");
var SankeyChart = SimpleSplunkView.extend({
moduleId: module.id,
className: "sankey-diagram",
options: {
managerid: null,
data: "preview",
formatLabel: _.identity,
height: 300,
formatTooltip: function(d) {
return (d.source.name + ' -> ' + d.target.name + ': ' + d.value);
}
},
// This is how we extend the SimpleSplunkView's options value for
// this object, so that these values are available when
// SimpleSplunkView initializes.
initialize: function() {
SimpleSplunkView.prototype.initialize.apply(this, arguments);
this.settings.on("change:formatLabel change:formatTooltip", this.render, this);
// Set up resize callback.
$(window).resize(_.debounce(_.bind(this._handleResize, this), 20));
},
_handleResize: function() {
this.render();
},
// The object this method returns will be passed to the
// updateView() method as the first argument, to be
// manipulated according to the data and the visualization's
// needs.
createView: function() {
var margin = {top: 10, right: 10, bottom: 10, left: 10};
var availableWidth = parseInt(this.settings.get("width") || this.$el.width());
var availableHeight = parseInt(this.settings.get("height") || this.$el.height());
this.$el.html("");
var svg = d3.select(this.el)
.append("svg")
.attr("width", availableWidth)
.attr("height", availableHeight)
.attr("pointer-events", "all");
return { svg: svg, margin: margin};
},
// Where the data and the visualization meet. Both 'viz' and
// 'data' are the data structures returned from their
// respective construction methods, createView() above and
// onData(), below.
updateView: function(viz, data) {
var that = this;
var containerHeight = this.$el.height();
var containerWidth = this.$el.width();
// Clear svg
var svg = $(viz.svg[0]);
svg.empty();
svg.height(containerHeight);
svg.width(containerWidth);
// Add the graph group as a child of the main svg
var graphWidth = containerWidth - viz.margin.left - viz.margin.right;
var graphHeight = containerHeight - viz.margin.top - viz.margin.bottom;
var graph = viz.svg
.append("g")
.attr("width", graphWidth)
.attr("height", graphHeight)
.attr("transform", "translate(" + viz.margin.left + "," + viz.margin.top + ")");
var formatLabel = this.settings.get('formatLabel') || _.identity;
var formatTooltip = this.settings.get('formatTooltip');
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([graphWidth, graphHeight]);
var path = sankey.link();
sankey.nodes(data.nodes)
.links(data.links)
.layout(1);
var link = graph.append("g").selectAll(".link")
.data(data.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) {
return Math.max(1, d.dy);
})
.sort(function(a, b) {
return b.dy - a.dy;
});
link.append("title")
.text(function(d) {
return formatTooltip(d);
});
var node = graph.append("g").selectAll(".node")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
var color = d3.scale.category20();
// Draw the rectangles at each end of the link that
// correspond to a given node, and then decorate the chart
// with the names for each node.
node.append("rect")
.attr("height", function(d) {
return d.dy;
})
.attr("width", sankey.nodeWidth())
.style("fill", function(d) {
d.color = color(d.name.replace(/ .*/, ""));
return d.color;
})
.style("stroke", function(d) {
return d3.rgb(d.color).darker(2);
})
.on("mouseover", function(node) {
var linksToHighlight = link.filter(function(d) {
return d.source.name === node.name || d.target.name === node.name;
});
linksToHighlight.classed('hovering', true);
})
.on("mouseout", function(node) {
var linksToHighlight = link.filter(function(d) {
return d.source.name === node.name || d.target.name === node.name;
});
linksToHighlight.classed('hovering', false);
})
.append("title")
.text(function(d) {
return formatLabel(d.name) + "\n" + d.value;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(d3.behavior.drag()
.origin(function(d) {
return d;
})
.on("dragstart", function() {
this.parentNode.appendChild(this);
})
.on("drag", dragmove));
node.append("text")
.attr("x", -6)
.attr("y", function(d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) {
return formatLabel(d.name);
})
.filter(function(d) {
return d.x < graphWidth / 2;
})
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// This view publishes the 'click:link' event that
// other Splunk views can then use to drill down
// further into the data. We return the source and target
// names as values to be used in further Splunk searches.
// This allows us to accept events from the visualization
// library and provide them consistently to other Splunk
// views.
var format_event_data = function(e) {
return {
source: e.source.name,
target: e.target.name,
value: e.value
};
};
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(graphHeight - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
link.on('click', function(e) {
that.trigger('click:link', format_event_data(e));
});
node.on('mousedown', function(e) {
var linksToNodes = function(links, type) {
return _.map(links, function(link) {
return {
name: link[type].name,
value: link[type].value
};
});
};
var clickEvent = {
name: e.name,
value: e.value,
incomingLinks: linksToNodes(e.targetLinks, "source"),
outgoingLinks: linksToNodes(e.sourceLinks, "target")
};
that.trigger('click:node', clickEvent);
});
},
// This function turns the three expected data items into data
// structures Sankey understands, and then calls
// updateView(). This is the function that is called when
// new data is available, and triggers the actual rendering of
// the visualization above. The data passed here corresponds
// to the basic format requested by the view.
formatData: function(data) {
var nodeList = _.uniq(_.pluck(data, 0).concat(_.pluck(data, 1)));
var links = _.map(data, function(item) {
return {
source: nodeList.indexOf(item[0]),
target: nodeList.indexOf(item[1]),
value: parseInt(item[2], 10)
};
});
var nodes = _.map(nodeList, function(node) {
return {
name: node
};
});
return { nodes: nodes, links: links };
},
render: function() {
this.$el.height(this.settings.get('height'));
return SimpleSplunkView.prototype.render.apply(this, arguments);
}
});
return SankeyChart;
});

@ -0,0 +1,70 @@
/*
--------------
SccTakeTheTour
--------------
*/
/* Navigation*/
.scc-takethetour-nav {
margin-bottom: 2em;
}
.scc-takethetour-nav button,
.scc-takethetour-nav div {
display: inline-block;
}
.scc-takethetour-nav div button{
border-radius: 0;
border-right: none;
}
.scc-takethetour-nav button.prev{
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
}
.scc-takethetour-nav button.next{
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.scc-takethetour-nav button.prev .caret {
transform: rotate(90deg);
padding: 0;
}
.scc-takethetour-nav button.next .caret {
transform: rotate(270deg);
padding: 0;
}
/* Slides */
.scc-takethetour-slides {
margin: 0;
}
.scc-takethetour-slides li{
list-style-type: none;
}
/* Various */
.scc-takethetour .credits {
font-size: 9px;
text-align: center;
}
.scc-takethetour.modal .modal-footer {
padding-left: 20px;
padding-right: 20px;
}
.scc-takethetour .never-show-again {
float: left;
}
.scc-takethetour .never-show-again input {
margin-top: 0;
margin-right: 0.5em;
}

@ -0,0 +1,226 @@
/*
--------------
SccTakeTheTour
--------------
*/
define(function(require, exports, module) {
var $ = require('jquery');
var SimpleSplunkView = require('splunkjs/mvc/simplesplunkview');
var SplunkUtils = require('splunkjs/mvc/utils');
require("css!./scc_takethetour.css");
var SccTakeTheTour = SimpleSplunkView.extend({
className: "scc-takethetour",
options: {
title: "Take the tour",
width: 60,
close_btn_label: "Close",
no_more_label: "Do not show this tour again",
hide_container: false,
show_credits: false,
cookie_name_suffix: "",
backdrop_close: true,
keyboard_close: true
},
initialize: function() {
SimpleSplunkView.prototype.initialize.apply(this, arguments);
// Treat "hide_container" setting
if(this.settings.get('hide_container') == true){
this.$el.parent().css({
'padding':0,
'margin':0,
'height':'0px',
'width':'0px'
})
}
return this;
},
render: function() {
this.$el.html('');
// Cookie stuff
var cookie_name_suffix = this.settings.get('cookie_name_suffix') == "" ? "" : '_' + this.settings.get('cookie_name_suffix');
var ck_name = 'scc_takethetour_' + SplunkUtils.getCurrentApp() + cookie_name_suffix;
// set expiration for 1 year
var d = new Date();
d.setTime(d.getTime() + (365*24*60*60*1000));
var ck_expires = "expires="+d.toUTCString();
function getCookie(ck_name) {
var oRegex = new RegExp("(?:; )?" + ck_name + "=([^;]*);?");
return oRegex.test(document.cookie) ? decodeURIComponent(RegExp["$1"]) : null;
}
function setCookie(ck_name) {
document.cookie = ck_name + "= scc-takethetour" + ";" + ck_expires;
}
// GENERATE BOOTSTRAP MODAL
// ------------------------
if(typeof this.settings.get('user_slides') !== 'undefined'
&& $('#'+this.settings.get('user_slides')).length == 1
&& getCookie(ck_name) == null){
var html_tpl = '<div class="scc-takethetour modal fade" id="scc-takethetour-modal" tabindex="-1" role="dialog" aria-labelledby="scc-takethetour-modal">'
+ '<div class="modal-dialog" role="document">'
+ '<div class="modal-content">'
+ '<div class="modal-header">'
+ '<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>'
+ '<h4 class="modal-title" id="myModalLabel">' + this.settings.get('title') + '</h4>'
+ '</div>'
+ '<div class="modal-body">'
+ '<div class="scc-takethetour-nav">'
+ '<button class="prev btn btn-default disabled"><span class="caret"></span></button>'
+ '<div></div>'
+ '<button class="next btn btn-default"><span class="caret"></span></button>'
+ '</div>'
+ '<ul class="scc-takethetour-slides"></ul>'
+ '</div>'
+ '<div class="modal-footer">'
+ '<label class="never-show-again"><input type="checkbox">' + this.settings.get('no_more_label') + '</label>'
+ '<button type="button" class="btn btn-default" data-dismiss="modal">' + this.settings.get('close_btn_label') + '</button>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>';
this.$el.html(html_tpl);
var $user_slides = $('#'+this.settings.get('user_slides')+' li');
var $modal = this.$el.find('#scc-takethetour-modal');
$modal.css({width:this.settings.get('width') + "%", "margin-left": "-" + this.settings.get('width')/2 + "%"});
var $modal_body = this.$el.find('#scc-takethetour-modal .modal-body');
var $slides = this.$el.find('.scc-takethetour-slides');
var $slides_nav = this.$el.find('.scc-takethetour-nav');
var $slides_nav_prev = $slides_nav.find('.prev');
var $slides_nav_next = $slides_nav.find('.next');
var $slides_nav_links = $slides_nav.find('div');
var $checkbox = this.$el.find('.never-show-again input');
var active_slide_id = 0;
var max_slide_id = $user_slides.length - 1;
// Treat "show_credits" setting
if(this.settings.get('show_credits') === true){
var html_credits = '<div class="credits">'
+ 'Powered by <a href="http://www.splunk.com" target="_blank">Splunk</a> and <a href="https://github.com/ftoulouse/splunk-components-collection" target="_blank">Scc</a>'
+ '</div>';
$modal.find('.modal-footer').append(html_credits);
}
$user_slides.each(function(idx){
$slides_nav_links.append('<button class="btn btn-default" data-slide-id="' + idx + '">' + (idx + 1) + '</button>');
$slides.append($user_slides.eq(idx));
idx == 0 ? $slides_nav_links.find('button').eq(idx).addClass('btn-primary') : $slides.find('li').eq(idx).addClass('hide');
});
$('#'+this.settings.get('user_slides')).remove();
// EVENTS
// ------
$slides_nav_prev.on('click', function(){
if(!$(this).hasClass('disabled')){
slidesManagr(active_slide_id - 1);
}
});
$slides_nav_next.on('click', function(){
if(!$(this).hasClass('disabled')){
slidesManagr(active_slide_id + 1);
}
});
$slides_nav_links.on('click', function(e){
slidesManagr($(e.target).attr('data-slide-id'));
});
// Add / remove vertical scrollbar on page resize
function isScrollNeeded(){
var overflow_y = $slides_nav.outerHeight(true) + $slides.outerHeight() > $modal_body.height() ? 'scroll' : 'none';
$modal_body.css('overflow-y', overflow_y);
};
// Hide / show slides and slides navigation
function slidesManagr(slide_id){
slide_id = parseInt(slide_id);
// Manage slides
$slides.find('li').eq(active_slide_id).addClass('hide');
$slides_nav_links.find('button').eq(active_slide_id).removeClass('btn-primary')
$slides.find('li').eq(slide_id).removeClass('hide');
$slides_nav_links.find('button').eq(slide_id).addClass('btn-primary');
// Manage nav
if(slide_id == 0){
if(!$slides_nav_prev.hasClass('disabled')){
$slides_nav_prev.addClass('disabled');
}
if($slides_nav_next.hasClass('disabled')){
$slides_nav_next.removeClass('disabled');
}
}
else if(slide_id == max_slide_id){
if(!$slides_nav_next.hasClass('disabled')){
$slides_nav_next.addClass('disabled');
}
if($slides_nav_prev.hasClass('disabled')){
$slides_nav_prev.removeClass('disabled');
}
}
else{
if($slides_nav_prev.hasClass('disabled')){
$slides_nav_prev.removeClass('disabled');
}
if($slides_nav_next.hasClass('disabled')){
$slides_nav_next.removeClass('disabled');
}
}
active_slide_id = slide_id;
isScrollNeeded();
}
$(window).resize(function(){
isScrollNeeded();
});
// Treat "backdrop_close" and "keyboard_close" settings
var modal_backdrop = this.settings.get('backdrop_close') === true ? true : 'static';
var modal_keyboard = this.settings.get('keyboard_close') === true ? true : false;
$modal
.on('shown.bs.modal', function(e){
// Prevents page body scrolling if modal content is scrollable
$('body').css({overflow:"hidden", position:"fixed", width: "100%"});
isScrollNeeded();
})
.on('hide.bs.modal', function(e){
// Give back inherited overflow attributes to the body
$('body').css({overflow:"inherit", position:"inherit", width: "inherit"});
// Never show the tour again until navigator cookie is not deleted by user
if($checkbox.is(':checked')){
setCookie(ck_name);
}
})
.modal({
backdrop: modal_backdrop,
keyboard: modal_keyboard
});
}
return this;
}
});
return SccTakeTheTour;
});

@ -0,0 +1,61 @@
require.config({
paths: {
prettify: '../app/simple_xml_examples/components/srcviewer/contrib/prettify'
}
})
define([
'underscore',
'jquery',
'backbone',
'prettify',
'css!contrib/google-code-prettify/prettify.css'
], function(_, $, Backbone, prettify) {
var CodeView = Backbone.View.extend({
options: {
stripI18n: true
},
initialize: function() {
this.listenTo(this.model, 'change:content', this.render, this);
},
getContent: function() {
var content = this.model.get('content');
if (content && this.options.stripI18n) {
content = this.stripInjectedI18n(content);
}
return content;
},
stripInjectedI18n: function(source) {
var lines = source.split(/\r\n|\r|\n/);
var i = 0, start = 0;
while (i < lines.length) {
if (lines[i].indexOf('i18n_register') === 0) {
i++;
while (!lines[i]) {
i++;
}
start = i;
break;
}
i++;
}
return lines.slice(start).join('\n');
},
render: function() {
this.$el.html(this.template({
content: this.getContent(),
lang: this.model.get('lang')
}));
this.$el.attr({ "class": "tab-pane", id: this.model.get("id") });
prettify(function(){}, this.el);
return this;
},
template: _.template(
'<pre class="prettyprint linenums <% if(lang) { %>lang-<%-lang%><% } %>">' +
'<code><%- content %></code>' +
'</pre>'
)
});
return CodeView;
});

@ -0,0 +1,106 @@
require.config({
paths: {
showdown: '../app/simple_xml_examples/components/srcviewer/contrib/showdown'
}
});
define([
'underscore',
'jquery',
'backbone',
'./codeview',
'showdown',
'bootstrap.tab'
], function(_, $, Backbone, CodeView, Showdown) {
var markdown = new Showdown.converter();
var SourceCodeViewer = Backbone.View.extend({
className: 'sourcecode-viewer',
options: {
title: 'Dashboard Source Code'
},
initialize: function(){
this.items = [];
this.listenTo(this.collection, 'reset remove', this.render, this);
this.listenTo(this.collection, 'add', this.addItem, this);
this.listenTo(this.model, 'change', this.render, this);
},
events: {
'click .nav>li>a': function(e){
e.preventDefault();
$(e.currentTarget).tab('show');
}
},
addItem: function(model){
if(!model.has('id')) {
model.set('id', _.uniqueId('tab_'));
}
var id = model.get('id');
var filename = model.get('name');
var fileUrl = model.get('url') || '';
var link = $('<a class="tab-title-text"></a>').text(filename).attr('href', fileUrl + '#' + id)
var li = $('<li></li>').append(link);
this.$('.nav-tabs').append(li);
var item = new CodeView({
model: model,
el: $('<div></div>').appendTo(this.$('.tab-content'))
});
item.render();
if(this.items.length == 0) {
// Activate the tab, if it's the first item
li.find('a').click();
}
this.items.push(item);
return item;
},
render: function(){
_(this.items).invoke('remove');
this.items.length = 0; // Clear items array
var model = _.extend({
_: _,
description: '',
shortDescription: '',
related_links: null
}, this.model.toJSON(), this.options);
if(model.description) {
model.description = markdown.makeHtml(model.description);
}
this.$el.addClass(this.className);
this.$el.html(this.template(model));
this.collection.map(_(this.addItem).bind(this));
return this;
},
template: _.template(
'<div class="dashboard-description">' +
'<div class="description-title"><h3><%= _("Description").t() %></h3></div>' +
'<div class="example-info">' +
'<p class="description"><%= description %></p>' +
'<% if(related_links && related_links.length) { %>' +
'<h5><%= _("Related examples:").t() %></h5>' +
'<ul class="related-links">' +
'<% _.each(related_links, function(link) { %>' +
'<li><a href="<%- link.href %>"><%- link.label %></a></li>' +
'<% }); %>' +
'</ul>' +
'<% } %>' +
'</div>' +
'</div>' +
'<div class="showsource-container">' +
'<ul class="nav nav-tabs">' +
'<li class="nav-title">Source Code</li>' +
'</ul>' +
'<div class="tab-content source-content"></div>' +
'</div>'
)
});
return SourceCodeViewer;
});

@ -0,0 +1,12 @@
.tagcloud-viz {
text-align: center;
margin: 20px auto;
max-width: 60%;
line-height: 18px;
}
.tagcloud-viz .link {
padding: 2px 3px;
display: inline-block;
vertical-align: middle;
}

@ -0,0 +1,72 @@
/*
* Simple TagCloud visualization
* This view is an example for a simple visualization based on search results
*/
define(function(require, module) {
var _ = require('underscore'), $ = require('jquery');
var SimpleSplunkView = require('splunkjs/mvc/simplesplunkview');
var Drilldown = require('splunkjs/mvc/drilldown');
require('css!./tagcloud.css');
var TagCloud = SimpleSplunkView.extend({
moduleId: module.id,
className: 'tagcloud-viz',
options: {
labelField: 'label',
valueField: 'count',
minFontSize: 8,
maxFontSize: 36,
data: 'preview'
},
output_mode: 'json',
events: {
'click a': function(e) {
e.preventDefault();
// Perform automatic drilldown on click on a tag
Drilldown.handleDrilldown({
name: this.settings.get('labelField'),
value: $.trim($(e.target).text())
}, 'row', this.manager);
}
},
initialize: function() {
SimpleSplunkView.prototype.initialize.apply(this, arguments);
// Make sure we re-render the visualization when our settings change
this.listenTo(this.settings, 'change:labelField change:valueField change:minFontSize change:maxFontSize', this._updateView);
},
createView: function() {
return true;
},
updateView: function(viz, data) {
var labelField = this.settings.get('labelField');
var valueField = this.settings.get('valueField');
var minFontSize = parseFloat(this.settings.get('minFontSize'));
var maxFontSize = parseFloat(this.settings.get('maxFontSize'));
// Clear the current view
var el = this.$el.empty().css('line-height', Math.ceil(maxFontSize * 0.55) + 'px');
var minMagnitude = Infinity, maxMagnitude = -Infinity;
_(data).chain().map(function(result) {
// Extract and convert the magnitude field value
var magnitude = parseFloat(result[valueField]);
// Find the maximum and minimum of the magnitude field values
minMagnitude = magnitude < minMagnitude ? magnitude : minMagnitude;
maxMagnitude = magnitude > maxMagnitude ? magnitude : maxMagnitude;
return {
label: result[labelField],
magnitude: magnitude
};
}).each(function(result) {
// Calculate relative size of each tag
var size = minFontSize + ((result.magnitude - minMagnitude) / maxMagnitude * (maxFontSize - minFontSize));
// Render the tag
$('<a class="link" href="#" /> ').text(result.label + ' ').css({
'font-size': size
}).appendTo(el);
});
}
});
return TagCloud;
});

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2016-2017, OctoInsight Inc., All rights reserved.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,45 @@
/*
* Copyright (c) 2016-2017, OctoInsight Inc., All rights reserved.
* Authored by Ryan Thibodeaux
* see included LICENSE file (BSD 3-clause)
*/
.toggledopen {
font-family: Arial, Helvetica, Arial, sans-serif;
padding: 9px 6px 6px 6px;
cursor: pointer;
display: inline;
float: left;
font-size: 20px;
font-weight: bolder;
-webkit-border-radius: 1px;
-moz-border-radius: 1px;
border-radius: 1px;
}
.toggledopen:before {
color: #d3d3d3;
content: "\25B2";
}
.toggledclosed {
font-family: Arial, Helvetica, Arial, sans-serif;
padding: 9px 6px 6px 6px;
cursor: pointer;
display: inline;
float: left;
font-size: 20px;
font-weight: bolder;
-webkit-border-radius: 1px;
-moz-border-radius: 1px;
border-radius: 1px;
}
.toggledclosed:before {
color: #d3d3d3;
content: "\25Bc";
}
.togglepanel-hidden {
display: none !important;
}

@ -0,0 +1,263 @@
/**
* @fileoverview Class definition for TogglePanel or Accordion panel feature
* @author Ryan Thibodeaux
* @version 1.0.0
*/
/*
* Copyright (c) 2016-2017, OctoInsight Inc., All rights reserved.
* Authored by Ryan Thibodeaux
* see included LICENSE file (BSD 3-clause)
*/
/*
* Definition of custom TogglePanel class.
* This turns a Splunk "panel" element into
* an Accordion panel or toggle-able panel.
*
* NOTE: this is not an extension of the panel
* base class; instead, it allows one to retroactively
* turn an existing panel into a toggle-able one. This
* makes managing panels easier in Simple XML dashboards
* because the Simple XML dashboards don't have to know
* about this class, i.e., it is done in JS for the
* dashboard
*/
define(function(require, exports, module) {
var $ = require('jquery');
var mvc = require("splunkjs/mvc");
// The require-css plugin is inconsistent at determining the path
// for loading CSS files. We have to hardcode for now,
require('css!/static/app/metricator-for-nmon/components/togglepanel/togglepanel.css');
// default settings for TogglePanel object
var defaults = {
openWidth : undefined, // width of toggle panel when open
closeWidth : undefined, // width of toggle panel when closed
toggleSpeed : 500, // toggle animation duration in milliseconds
};
// constructor of TogglePanel object
// parent can be an HTML id or jquery selector
function TogglePanel(parent, openWidth, closeWidth, toggleSpeed) {
if (!(this instanceof TogglePanel)) {
throw new TypeError("TogglePanel constructor cannot be called as a function.");
}
// handle parent parameter passed in as HTML id or jquery selector.
// this.parent should point to jquery selector of TogglePanel object
// this.parentId should contain the parent's HTML id
if (typeof parent === 'undefined') {
throw new TypeError("TogglePanel constructor cannot be called without a parent.");
} else if (typeof parent === 'string' || parent instanceof String) {
this.parentId = parent.replace(/^\#+/g, '');
this.parent = $('#' + parent);
if (!this.parentId.length) {
throw new TypeError("TogglePanel constructor cannot find specified parent.");
}
} else {
this.parentId = $(parent).attr('id');
this.parent = $(parent);
}
// set TogglePanel parameters
this.openWidth = (typeof openWidth !== 'undefined' ? openWidth : defaults.openWidth);
this.closeWidth = (typeof closeWidth !== 'undefined' ? closeWidth : defaults.closeWidth);
this.toggleSpeed = (typeof toggleSpeed !== 'undefined' ? toggleSpeed : defaults.toggleSpeed);
this.fieldset = undefined;
this.childrenMvc = undefined;
}
// create() wrapper for Toggle Panel constructor
TogglePanel.create = function(parent, openWidth, closeWidth, toggleSpeed) {
return (new TogglePanel(parent, openWidth, closeWidth, toggleSpeed));
};
// static CSS class names to use for the open and close
// state of the TogglePanel
TogglePanel.OPENED_CLASS = 'toggledopen';
TogglePanel.CLOSED_CLASS = 'toggledclosed';
// define the TogglePanel class, which is a wholly new class
// and not an extension of another
TogglePanel.prototype = {
constructor: TogglePanel,
// toggle child state
toggleChild: function(el, wait) {
if (!!wait) {
el.slideToggle(this.toggleSpeed, function(){return;});
} else {
el.slideToggle(this.toggleSpeed);
}
el.resize();
},
// animate hiding of child
slideUpChild: function(el, wait) {
if (!!wait) {
el.slideUp(this.toggleSpeed, function(){return;});
} else {
el.slideUp(this.toggleSpeed);
}
},
// animate showing of child
slideDownChild: function(el, wait) {
if (!!wait) {
el.slideDown(this.toggleSpeed, function(){return;});
} else {
el.slideDown(this.toggleSpeed);
}
},
// resize Splunk MVC element
resizeChild: function(el) {
el.resize();
},
// call toggleChild for all known child elements
toggleAllChildren: function() {
var self = this;
this.childrenMvc.forEach(function(child) {
self.toggleChild(child.$el);
});
return this;
},
// call slideUp for all known child elements
hideAllChildren: function() {
var self = this;
this.childrenMvc.forEach(function(child) {
self.slideUpChild(child.$el);
});
return this;
},
// call slideDown for all known child elements
// where we let the animation complete before moving
// to the next and then we resize after all done
showAllChildren: function() {
var self = this;
this.childrenMvc.forEach(function(child) {
self.slideDownChild(child.$el, true);
});
this.childrenMvc.forEach(function(child) {
self.resizeChild(child.$el);
});
return this;
},
// high-level function to start the toggle process on,
// element el which should toggle all dashboard elements inside
// of the toggle panel (el) and its fieldset element
toggle: function(el) {
if (!el.data('parent')) {
return;
}
// set the toggled state on the contained fieldset element
// and set the appropriate width of the TogglePanel object el
var parent = $('#' + el.data('parent'));
if (parent.length) {
var fieldset = this.fieldset;
// if toggling open, set width, toggle the fieldset,
// and then toggle the elements
if (el.attr("class") === TogglePanel.CLOSED_CLASS) {
if (typeof this.openWidth !== 'undefined') {
parent.css('width', this.openWidth);
}
// toggle the fieldset
if (fieldset.length > 0) {
fieldset.removeClass("togglepanel-hidden");
fieldset.slideDown(this.toggleSpeed);
}
// call toggle open on all children
this.showAllChildren();
// set new toggle icon
el.attr('class', TogglePanel.OPENED_CLASS);
} else {
// if toggling closed, toggle elements,
// and then toggle the fieldset
// call slideUp on all children
this.hideAllChildren();
// hide the fieldset, where we use slideUp but
// we also add a class after the animation is complete
// where this class will force it to be hidden
if (fieldset.length > 0) {
fieldset.slideUp(this.toggleSpeed, function() {
fieldset.addClass("togglepanel-hidden");
});
}
// set the close width
if (typeof this.closeWidth !== 'undefined') {
parent.css('width', this.closeWidth);
}
// set new toggle icon
el.attr('class', TogglePanel.CLOSED_CLASS);
}
}
return this;
},
// initial function to setup the TogglePanel object
// by inserting the toggle element at the beginning
// of the panel title and setting the appropriate state
// of the TogglePanel based on the hide parameter
setup: function(hide) {
// setup html element and its attributes for the toggle icon
var title = this.parent.find('.panel-title')
var toggleDiv = $('<div> &nbsp; </div>');
toggleDiv.attr('class', TogglePanel.OPENED_CLASS);
toggleDiv.attr('id', "toggle_panel_div_" + this.parentId);
this.parent.children('.dashboard-panel').prepend(toggleDiv);
toggleDiv.attr('alt', '#' + this.parentId).data('parent', this.parentId);
this.$el = toggleDiv
// save fieldset selector if the panel has one
this.fieldset = this.parent.find('.fieldset');
// find all MVC elements and save them in childrenMvc
var children = [];
this.parent.find('.dashboard-element').each(function() {
var k = mvc.Components.get(this.id);
if (typeof k !== 'undefined') {
children.push(k);
}
});
this.childrenMvc = children;
// hide all children if "hide" is true
if (!!hide) {
this.toggle(this.$el);
}
// setup click listener on toggle switch and panel title
toggleDiv.on("click", $.proxy(this.toggle, this, this.$el));
if (title.length > 0) {
title.first().on("click", $.proxy(this.toggle, this, this.$el));
title.first().css("cursor", "pointer");
}
return this;
},
};
return TogglePanel;
});

@ -0,0 +1,41 @@
/*
* Copyright (c) 2016-2017, OctoInsight Inc., All rights reserved.
* Authored by Ryan Thibodeaux
* see included LICENSE file (BSD 3-clause)
*/
/*
* This file implements the autodiscover function
* that finds panels in SimpleXML that should
* be turned into Toggle Panels based on their
* HTML IDs matching a specific pattern.
*/
(function() {
require([
"underscore",
"jquery",
"splunkjs/mvc",
"TogglePanel",
], function(_, $, mvc, TogglePanel) {
const regex = /_togglepanel/i;
const regexHide = /_togglepanel_true/i;
_(mvc.Components.toJSON())
.chain()
.filter(function(el) {
var id = $(el).attr("id");
var dom = $(el).attr("$el");
if (typeof id !== "undefined" && typeof dom !== "undefined") {
if (id.match(regex) !== null && dom.hasClass('dashboard-cell')) {
return el;
}
}
}).each(function(el) {
var id = $(el).attr("id");
var hide = (id.match(regexHide) !== null ? true : false);
new TogglePanel(id).setup(hide);
});
});
}).call(this);

@ -0,0 +1,10 @@
require(['jquery', 'splunkjs/mvc/simplexml/ready!'], function($) {
$("[id*=setWidth]").each(function() {
var match = /setWidth_(\d+(?:_\d+)?)/.exec($(this).attr('id'));
if (match[1]) {
$(this).closest(".dashboard-cell").css('width', match[1].replace("_", ".") + '%');
}
});
// Force visualizations (esp. charts) to be redrawn with their new size
$(window).trigger('resize');
});

@ -0,0 +1,31 @@
/* darks css theme from: http://www.brainfold.net/2016/04/splunk-dashboards-looks-more-beautiful.html */
body,.dashboard-body,.footer,.header,.dashboard-cell {
background: #1C1E23 !important;
}
.dashboard-panel {
background: #292C33 !important;
}
svg text {
fill: #fff !important;
}
.single-value .single-result {
color: #fff;
}
.dashboard-header h2, p.description, .nav-footer>li>a {
color: #ddd;
text-shadow: none;
}
.dashboard-row .dashboard-panel h2.panel-title {
color: #fff;
}
button, input, label, select, textarea {
color: #808080 !important;
}

@ -0,0 +1,10 @@
/*
*
* This is the default CSS for all
* pages in the app.
*
* This file will automatically be loaded
* for all dashboards.
*
*/

@ -0,0 +1,77 @@
/////////////////////////////////////////////////
//
// This is the default entry point for all
// pages in the app.
//
// This file will automatically be loaded
// for all dashboards.
//
/////////////////////////////////////////////////
(function() {
var appName, appPath;
var pageOptions, pageName;
var urlAppComponents, requireRoot;
// anonymous function to breakdown the URL into app name and page name
urlAppComponents = (function() {
var comps = (location.pathname.split('?')[0]).split('/');
var idx = comps.indexOf('app');
var app = comps[idx + 1];
var page = comps[idx + 2];
return [app, page];
})();
// obtain values from previous anonymous function
appName = urlAppComponents[0];
pageName = urlAppComponents[1];
// save global entities
pageOptions = {
"pageStartTime": new Date().valueOf(), // save time of when page is loaded
"appName" : appName, // app name
"pageName" : pageName, // dashboard page name
};
// setup paths for rest of the configuration
requireRoot = "../app";
appPath = requireRoot + "/" + appName;
// configure RequrieJS Paths and Options
require.config({
paths: {
"app" : requireRoot,
"appOptions" : appPath + "/components/lib/options",
"appUtils" : appPath + "/components/lib/utils",
},
config: {
"appOptions": {
"options": pageOptions
}
}
});
// load the important modules for the dashboards, where we load CSS first
// and than everything else
require([], function() {
require([
"appOptions",
"appUtils",
], function(ignored, appUtils) {
// call initialization routine
appUtils.initiliazeApp(true);
}, function(err) {
// error callback
// the error has a list of modules that failed
var failedId = err.requireModules && err.requireModules[0];
requirejs.undef(failedId);
console.error("Error when loading dependency", err);
});
}, function(err) {
// error callback
// the error has a list of modules that failed
var failedId = err.requireModules && err.requireModules[0];
requirejs.undef(failedId);
console.error("Error when loading CSS dependency", err);
});
}).call(this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -0,0 +1,3 @@
.dashboard-header h2 {
visibility: hidden !important;
}

@ -0,0 +1,5 @@
/* completely hide panel footers */
.dashboard-row .dashboard-panel .dashboard-element .panel-footer {
display: none;
}

@ -0,0 +1,7 @@
.dashboard-row .dashboard-panel .refresh-time-indicator {
font-size: 11px;
color: #555555;
cursor: default;
padding: 10px 10px 6px 10px;
visibility: hidden !important;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,66 @@
.html h2 {
color: #adbacd !important;
}
a.tryitbtn-blue,
a.tryitbtn-blue:link,
a.tryitbtn-blue:visited,
a.tryitbtn-blue,
a.tryitbtn-blue:link,
a.tryitbtn-blue:visited {
display: inline-block;
color: #71b1c1;
background-color: #1f1f1f;
font-weight: bold;
font-size: 12px;
text-align: center;
padding-left: 10px;
padding-right: 10px;
padding-top: 3px;
padding-bottom: 4px;
text-decoration: none;
margin-left: 0;
/* margin-left: 5px; */
margin-right: 10px;
margin-top: 0px;
margin-bottom: 5px;
border: 1px solid #aaaaaa;
border: 1px solid #71b1c1;
border-radius: 5px;
white-space: nowrap;
}
a.tryitbtn-blue:hover,
a.tryitbtn-blue:active,
a.tryitbtn-blue:hover,
a.tryitbtn-blue:active {
background-color: #71b1c1;
color: #1f1f1f;
}
/* main category icons and titles */
.imgheader img {
float: left;
width: 48px;
height: 48px;
}
.imgheader h1 {
color: #adbacd;
text-align: left;
position: relative;
top: 9px;
left: 10px;
}
.imgheader h2 {
position: relative;
top: 18px;
left: 10px;
}
.imgminiheader img {
position: relative;
top: -4px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save