splunk-sdk
Version:
SDK for usage with the Splunk REST API
1,242 lines (1,130 loc) • 48.5 kB
JavaScript
// Copyright 2011 Splunk, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
(function() {
var Splunk = require('./splunk');
Splunk.namespace("util");
/****** DON'T CHANGE ANYTHING ABOVE THIS LINE ***********/
Splunk.util = {
/**
* Assign empty handlers for logger calls. Overriden by Splunk.Logger if it is imported.
*/
logger : {
"info":function(){},
"log":function(){},
"debug":function(){},
"warn":function(){},
"error":function(){}
},
/**
* Converts an object literal to an encoded querystring key/value string.
*
*/
propToQueryString: function(dictionary) {
var o = [];
var val;
for (var prop in dictionary) {
val = '' + dictionary[prop];
o.push(encodeURIComponent(prop) + '=' + encodeURIComponent(dictionary[prop]));
}
return o.join('&');
},
/**
* Converts a flat querystring into an object literal
*
*/
queryStringToProp: function(args) {
args = this.trim(args, '&\?#');
var parts = args.split('&');
var output = {};
var key;
var value;
var equalsSegments;
var lim = parts.length;
for (var i=0,l=lim; i<l; i++) {
equalsSegments = parts[i].split('=');
key = decodeURIComponent(equalsSegments.shift());
value = equalsSegments.join("=");
output[key] = decodeURIComponent(value);
}
return output;
},
/**
* Extracts the fragment identifier value.
*/
getHash: function(){
var hashPos = window.location.href.indexOf('#');
if (hashPos == -1) {
return "";
}
var qPos = window.location.href.indexOf('?', hashPos);
if (qPos != -1)
return window.location.href.substr(qPos);
return window.location.href.substr(hashPos);
},
/**
* This was ported, rewritten a bit and greatly simplified from the
* same method in the old Calendar object we used to use.
* TODO - it is only here temporarily, and we should continue trying to
* kill it.
*/
parseDate : function(str, fmt) {
if ((!str) || (!str.indexOf) || (str.indexOf("mm")==0)) return null;
var y = 0;
var m = -1;
var d = 0;
var a = str.split(/\W+/);
var b = fmt.match(/%./g);
var i = 0, j = 0;
var hr = 0;
var min = 0;
var sec = 0;
for (i = 0; i < a.length; ++i) {
if (!a[i])
continue;
switch (b[i]) {
case "%d":
d = parseInt(a[i], 10);
break;
case "%m":
m = parseInt(a[i], 10) - 1;
break;
case "%Y":
case "%y":
y = parseInt(a[i], 10);
(y < 100) && (y += (y > 29) ? 1900 : 2000);
break;
case "%H":
hr = parseInt(a[i], 10);
break;
case "%M":
min = parseInt(a[i], 10);
break;
case "%S":
sec = parseInt(a[i], 10);
break;
default:
break;
}
}
if (y != 0 && m != -1 && d != 0) {
var ourDate = new Date(y, m, d, hr, min, sec);
return ourDate;
} else {
//this.logger.warn('unable to parse date "' + str + '" into "' + fmt + '"');
return false;
}
},
/**
* Given a timezone offset in minutes, and a JS Date object,
* returns the delta in milliseconds, of the two timezones.
* Note that this will include the offset contributions from DST for both.
*/
getTimezoneOffsetDelta: function(serverOffsetThen, d) {
if (!Splunk.util.isInt(serverOffsetThen)) {
return 0;
}
// what JS thinks the timezone offset is at the time given by d. This WILL INCLUDE DST
var clientOffsetThen = d.getTimezoneOffset() * 60;
// what splunkd told is the actual timezone offset.
serverOffsetThen = serverOffsetThen * -60;
return 1000 * (serverOffsetThen - clientOffsetThen);
},
getEpochTimeFromISO: function(isoStr) {
// lazily init the regex so we only do it only if necessary and only once.
if (!this._isoTimeRegex) {
// Nobody doesnt like ISO.
this._isoTimeRegex = /([\+\-])?(\d{4,})(?:(?:\-(\d{2}))(?:(?:\-(\d{2}))(?:(?:[T ](\d{2}))(?:(?:\:(\d{2}))(?:(?:\:(\d{2}(?:\.\d+)?)))?)?(?:(Z)|([\+\-])(\d{2})(?:\:(\d{2}))?)?)?)?)?/;
}
var m = this._isoTimeRegex.exec(isoStr);
// put it into a string form that JS Date constructors can actually deal with.
// Being Super Careful: calling substring on undefined variable
// here throws an exception that kills the stack but doesnt
// appear in firebug nor even in the Error Console.
var seconds, milliseconds;
if (m[7]) {
seconds = m[7].substring(0,2);
// Note this includes the period. ie ".003"
millisecondsStr = m[7].substring(2);
} else {
millisecondsStr = "";
}
var offset = eval(m[9] + (60*m[10] + parseInt(m[11], 10)));
var str = sprintf("%s/%s/%s %s:%s:%s", m[3], m[4], m[2], m[5], m[6], seconds);
// its still wrong, because JS will interpret this time in localtime,
// AND if you give IE the timezone part of the string, it passes out in its own vomit.
var t = new Date(str);
// so we patch it.
t.setTime(t.getTime() + this.getTimezoneOffsetDelta(offset, t));
var startTime = t.getTime() / 1000;
return startTime + millisecondsStr;
},
getConfigValue: function(configKey, optionalDefault) {
if (window.$C && window.$C.hasOwnProperty(configKey)) return window.$C[configKey];
else {
if (typeof optionalDefault != 'undefined') { // ensure optionalDefault can be set to 'false'
// util.logger will have been swapped out by the Logger when Logger
// has already been setup, but still works when its not.
//this.logger.debug('getConfigValue - ' + configKey + ' not set, defaulting to ' + optionalDefault);
return optionalDefault;
}
throw new Error('getConfigValue - ' + configKey + ' not set, no default provided');
}
},
/**
* Returns a proper path that is relative to the current appserver location.
* This is critical to ensure that we are proxy compatible. This method
* takes 1 or more arguments, which will all be stiched together in sequence.
*
* Ex: make_url('search/job'); // "/splunk/search/job"
* Ex: make_url('/search/job'); // "/splunk/search/job"
* Ex: make_url('/search', '/job'); // "/splunk/search/job"
* Ex: make_url('/search', '/job', 1234); // "/splunk/search/job/1234"
*
* Static paths are augmented with a cache defeater
*
* Ex: make_url('/static/js/foo.js'); // "/splunk/static/@12345/js/foo.js"
* Ex: make_url('/static/js/foo.js'); // "/splunk/static/@12345.1/js/foo.js"
*
* @param path {String} The relative path to extend
*
* TODO: lots of fancy URL munging
*
*/
make_url: function() {
var output = '', seg, len;
for (var i=0,l=arguments.length; i<l; i++) {
seg = arguments[i].toString();
len = seg.length;
if (len > 1 && seg.charAt(len-1) == '/') {
seg = seg.substring(0, len-1);
}
if (seg.charAt(0) != '/') {
output += '/' + seg;
} else {
output += seg;
}
}
// augment static dirs with build number
if (output!='/') {
var segments = output.split('/');
var firstseg = segments[1];
if (firstseg=='static' || firstseg=='modules') {
var postfix = output.substring(firstseg.length+2, output.length);
output = '/'+firstseg+'/@' + window.$C['BUILD_NUMBER'];
if (window.$C['BUILD_PUSH_NUMBER']) output += '.' + window.$C['BUILD_PUSH_NUMBER'];
if (segments[2] == 'app')
output += ':'+this.getConfigValue('APP_BUILD', 0);
output += '/' + postfix;
}
}
var root = Splunk.util.getConfigValue('MRSPARKLE_ROOT_PATH', '/');
var locale = Splunk.util.getConfigValue('LOCALE', 'en-US');
if (root == '' || root == '/') {
return '/' + locale + output;
} else {
return root + '/' + locale + output;
}
},
/**
* Given a path and a dictionary of options, builds a qualified query string.
*
* @param uri {String} required; path to endpoint. eg. "search/jobs"
* @param options {Object} key / value par of query params eg. {'foo': 'bar'}
*/
make_full_url: function(url, options) {
url = this.make_url(url);
if (options) url = url + '?' + this.propToQueryString(options);
return url;
},
/**
* Redirects user to a new page.
*
* @param uri {String} required
* @param options {Object} containing parameters like:
* sid => attaches optional sid in valid format
* s => attaches optional saved search name
* q => attaches optional search string in valid format
*
* Example:
* util.redirect_to('app/core/search', {
* 'sid' : 1234,
* 'foo' : 'bar'
* });
*
* redirects to 'splunk/app/core/search?sid=1234&foo=bar'
* @param windowObj {Window Object} an optional window object to target the location change
* @param focus {Boolean} if true, focus is called on windowObj
*/
redirect_to: function(uri, options, windowObj, focus) {
uri = this.make_full_url(uri, options);
if (!windowObj) windowObj = window;
windowObj.document.location = uri;
if (focus && windowObj.focus) windowObj.focus();
return;
},
/**
* Returns the current app name (not label).
*/
getCurrentApp: function() {
return $(document.body).attr("s:app") || 'UNKNOWN_APP';
},
/**
* Returns the current view name (not label).
*/
getCurrentView: function() {
return $(document.body).attr("s:view") || 'UNKNOWN_VIEW';
},
/**
* Returns the current 'displayView' name if it differs from the view name, else returns the current view name.
*/
getCurrentDisplayView: function() {
return $(document.body).attr("s:displayview") || this.getCurrentView();
},
getAutoCancelInterval: function() {
var interval = $(document.body).attr("s:autoCancelInterval");
if (!interval) {
this.logger.error("no autoCancelInterval found. Returning 0");
interval = 0;
}
return interval;
},
/**
* Returns the current viewstate ID as requested via the URI parameter
* 'vs'. This is embedded in the <body> tag.
*
* If no viewstate has been requested, then all parameter writes will
* go to the default sticky state, keyed by the reserved token '_current'.
*
* NOTE: viewstate is also provided to the modules through context resurrection,
* And that being the case, the value of this is marginal.
*/
//getCurrentViewState: function() {
// return $(document.body).attr("s:viewstateid") || null;
//},
/**
* Returns a dictionary of all the app, view, and saved search config
* data that is specified in the current view. Ex:
* {
* 'view': {"template": "builder.html", "displayView": "report_builder_display", "refresh": null, "label": "Display Report", "viewstateId": "*:ft10i02z", "onunloadCancelJobs": false, "id": "report_builder_display"},
* 'app': {"id": "search", "label": "Search"},
* 'savedSearch': {"search": "johnvey | timechart count", "name": "jvreport3", "vsid": "*:ft10i02z", "qualifiedSearch": "search johnvey | timechart count"}
* }
*/
getCurrentViewConfig: function() {
return $.extend({}, Splunk.ViewConfig);
},
/**
* Return the path without the localization segment.
*/
getPath: function(path) {
if (path === undefined) {
path = document.location.pathname;
}
var locale = this.getConfigValue('LOCALE').toString();
// if there is no way to figure out the locale, just return pathname
if (!this.getConfigValue('LOCALE') || path.indexOf(locale) == -1) {
return path;
}
var start = locale.length + path.indexOf(locale);
return path.slice(start);
},
/**
* Get the cumulative offsetTop for an element.
*
* @param {Object} element A DOM element.
*/
getCumlativeOffsetTop: function(element){
if(!element) return 0;
return element.offsetTop + this.getCumlativeOffsetTop(element.offsetParent);
},
/**
* Get the cumulative offsetLeft for an element.
*
* @param {Object} element A DOM element.
*/
getCumlativeOffsetLeft: function(element){
if(!element) return 0;
return element.offsetLeft + this.getCumlativeOffsetLeft(element.offsetParent);
},
/**
* Retrieve the amount of content that has been hidden by scrolling down.
*
* @type Number
* @return 0-n value.
*/
getPageYOffset: function(){
var pageYOffset = 0;
if(window.pageYOffset){
pageYOffset = window.pageYOffset;
}else if(document.documentElement && document.documentElement.scrollTop){
pageYOffset = document.documentElement.scrollTop;
}
return pageYOffset;
},
/**
* Retrieve the inner dimensions of the window. This does not work in jQuery.
*
* @type Object
* @return An object literal having width and height attributes.
*/
getWindowDimensions: function(){
return {
width:(!isNaN(window.innerWidth))?window.innerWidth:document.documentElement.clientWidth||0,
height:(!isNaN(window.innerHeight))?window.innerHeight:document.documentElement.clientHeight||0
};
},
/**
* Retrieve the computed style from a specified element.
*
* @param el
* @param styleProperty
* @return The computed style value.
* @type String
*/
getComputedStyleHelper: function(el, styleProperty){
if(el.currentStyle){
return el.currentStyle[styleProperty];
}else if(window.getComputedStyle){
var cssProperty = styleProperty.replace(/([A-Z])/g, "-$1").toLowerCase();
var computedStyle = window.getComputedStyle(el, "");
return computedStyle.getPropertyValue(cssProperty);
}else{
return "";
}
},
/**
* Retrieve a GET parameter from the window.location. Type casting is not performed.
* @param {String} p The param value to retrieve.
* @param {String} s Optional string to search through instead of window.location.search
* @return {String || null} The string value or null if it does not exist.
*/
getParameter: function(p, s){
s = s || window.location.search;
if(!s){
return null;
}
if(!(s.indexOf(p+'=')+1)){
return null;
}
return s.split(p+'=')[1].split('&')[0];
},
/**
* Take an RGB value and convert to HEX equivalent.
*
* @param {String} rgb A RGB value following rgb(XXX, XXX, XXX) convention.
* @type String
* @return A HEX equivalent for a given RGB value with a leading '#' character.
*/
getHEX: function(rgb){
var parts = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
var hex = (parts[1]<<16|parts[2]<<8|parts[3]).toString(16);
return "#"+Array(6-hex.length).concat([hex]).toString().replace(/,/g, 0);
},
/**
* Take an arbitrary RGB or HEX in long or shorthand notation and normalize to standard long HEX form with leading '#' character.
*
* @param {String} color A RGB or HEX color value in long or short notation.
* @type String or null
* @return A fully qualified 6 character hexadecimal value or with leading '#' character or null if it can't be processed.
*/
normalizeColor: function(color){
normalizedColor = null;
if(color.charAt(0)==="#"){
if(color.length===4){
normalizedColor = color + color.charAt(1) + color.charAt(2) + color.charAt(3);
}else{
normalizedColor = color;
}
}else{
try{
normalizedColor = this.getHEX(color);
}catch(e){}
}
return normalizedColor;
},
/**
* innerHTML substitute when it is not fast enough.
* @param {HTMLObject} target The target DOM element to replace innerHTML content with.
* @param {String} innerHTML The innerHTML string to add.
* @return {HTMLObject} The reference to the target DOM element as it may have been cloned and removed.
*/
turboInnerHTML: function(target, innerHTML) {
/*@cc_on //innerHTML is faster for IE
target.innerHTML = innerHTML;
return target;
@*/
var targetClone = target.cloneNode(false);
targetClone.innerHTML = innerHTML;
target.parentNode.replaceChild(targetClone, target);
return targetClone;
},
normalizeBoolean: function(test, strictMode) {
if (typeof(test) == 'string') {
test = test.toLowerCase();
}
switch (test) {
case true:
case 1:
case '1':
case 'yes':
case 'on':
case 'true':
return true;
case false:
case 0:
case '0':
case 'no':
case 'off':
case 'false':
return false;
default:
if (strictMode) throw TypeError("Unable to cast value into boolean: " + test);
return test;
}
},
getCommaFormattedNumber: function(nStr) {
nStr += '';
var x = nStr.split('.');
var x1 = x[0];
var x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
},
reLTrim: /^[\s\t\r\n]+/,
reLTrimCommand: /^[\s\t\r\n\|]+/,
reRNormalize: /[\s\t\r\n]+$/,
/**
* Returns a fully qualified search string by prepending the 'search'
* command of unqualified searches. This method deems strings as unqualified
* if it does not start with a | or 'search '
*
* @param {boolean} isUserEntered Indicates if 'q' is expected to be unqualified
*/
addLeadingSearchCommand: function(q, isUserEntered) {
var workingQ = '' + q;
workingQ = workingQ.replace(this.reLTrim, '').replace(this.reRNormalize, ' ');
if (workingQ.substring(0, 1) == '|') {
return q;
}
// this is specific to the case where searchstring = 'search ',
// which we conservatively assume does not constitute a search command
if (!isUserEntered
&& (workingQ.substring(0, 7) == 'search ' && workingQ.length > 7))
{
return q;
}
return 'search ' + workingQ;
},
/**
* Returns an unqualified search string by removing any leading 'search '
* command. This method does a simple search at the beginning of the
* search.
*/
stripLeadingSearchCommand: function(q) {
var workingQ = '' + q;
workingQ = workingQ.replace(this.reLTrimCommand, '');
if (workingQ.substring(0, 7) == 'search ') {
return workingQ.substring(7).replace(this.reLTrimCommand, '');
}
return q;
},
/**
* Deserializes a string into a field list.
*/
stringToFieldList: function(strList) {
if (typeof(strList) != 'string' || !strList) return [];
var items = [];
var field_name_buffer = [];
var inquote = false;
var str = $.trim(strList);
for (var i=0,j=str.length; i<j; i++) {
if (str.charAt(i) == '\\') {
var nextidx = i+1;
if (j > nextidx && (str.charAt(nextidx) == '\\' || str.charAt(nextidx) == '"')) {
field_name_buffer.push(str.charAt(nextidx));
i++;
continue;
} else {
field_name_buffer.push(str.charAt(i));
continue;
}
}
if (str.charAt(i) == '"') {
if (!inquote) {
inquote = true;
continue;
} else {
inquote = false;
items.push(field_name_buffer.join(''));
field_name_buffer = [];
continue;
}
}
if ((str.charAt(i) == ' ' || str.charAt(i) == ',') && !inquote) {
if (field_name_buffer.length > 0) {
items.push(field_name_buffer.join(''));
}
field_name_buffer = [];
continue;
}
field_name_buffer.push(str.charAt(i));
}
if (field_name_buffer.length > 0) items.push(field_name_buffer.join(''));
return items;
},
/**
* Serializes a field list array into a string.
*/
_sflQuotable: /([\\",\s])/,
_sflEscapable: /([\\"])/g,
fieldListToString: function(fieldArray) {
if (!fieldArray) return '';
var output = [];
for (var i=0,L=fieldArray.length; i<L; i++) {
var v = $.trim(fieldArray[i]);
if (v != '') {
// Escape any char with the backslash.
if (v.search(this._sflEscapable) > -1) {
v = v.replace(this._sflEscapable, "\\$1");
}
// Quote the entire string if a backslash, comma, space
// or double quote is present.
if (v.search(this._sflQuotable) > -1) {
v = ['"', v, '"'].join('');
}
output.push(v);
}
}
return output.join(',');
},
searchEscape: function(str) {
if (!str.match(/[\s\,=|\[\]\"]/))
return str;
return '"' + str.replace(/(\"|\\)/g, "\\$1") + '"';
},
/**
* Compare the likeness of two objects. Please use with discretion.
*/
objectSimilarity: function(obj1, obj2){
if(obj1 instanceof Array && obj2 instanceof Array){
if(obj1.length!==obj2.length){
return false;
}else{
for(var i=0; i<obj1.length; i++){
if(!this.objectSimilarity(obj1[i], obj2[i])){
return false;
}
}
}
}else if(obj1 instanceof Object && obj2 instanceof Object){
if(obj1!=obj2){
for(var j in obj2){
if(!obj1.hasOwnProperty(j)){
return false;
}
}
for(var k in obj1){
if(obj1.hasOwnProperty(k)){
if(obj2.hasOwnProperty(k)){
if(!this.objectSimilarity(obj1[k], obj2[k])){
return false;
}
}else{
return false;
}
}
}
}
}else if(typeof(obj1)==="function" && typeof(obj2)==="function"){
if(obj1.toString()!==obj2.toString()){
return false;
}
}else if(obj1!==obj2){
return false;
}
return true;
},
/**
* Stop watch class.
*/
StopWatch: function(){
var self = this,
startTime = null,
stopTime = null,
times = [];
var isSet = function(prop){
return (prop==null)?false:true;
};
var isStarted = function(){
return isSet(startTime);
};
var isStopped = function(){
return isSet(stopTime);
};
var softReset = function(){
startTime = null;
stopTime = null;
};
self.start = function(){
if(isStarted()){
throw new Error("cannot call start, start already invoked.");
}
startTime = new Date();
};
self.stop = function(){
if(!isStarted()){
throw new Error("cannot call stop, start not invoked.");
}
if(isStopped()){
throw new Error("cannot call stop, stop already invoked.");
}
stopTime = new Date();
time = stopTime - startTime;
times.push(time);
};
self.pause = function(){
if(!isStarted()){
throw new Error("cannot call pause, start not invoked.");
}
if(isStopped()){
throw new Error("cannot call pause, stop already invoked.");
}
self.stop();
softReset();
};
self.reset = function(){
softReset();
times = [];
};
self.time = function(){
var total = 0;
for(i=0; i<times.length; i++){
total += times[i];
}
if(isStarted() && !isStopped()){
total += (new Date() - startTime);
}
return total/1000;
};
},
isInt: function(num) {
return num!=='' && !isNaN(parseInt(num, 10)) && parseInt(num, 10)==(num/1);
},
/**
* Returns a string trimmed to maxLength by removing characters from the
* middle of the string and replacing with ellipses.
*
* Ex: Splunk.util.smartTrim('1234567890', 5) ==> '12...890'
*
*/
smartTrim: function(string, maxLength) {
if (!string) return string;
if (maxLength < 1) return string;
if (string.length <= maxLength) return string;
if (maxLength == 1) return string.substring(0,1) + '...';
var midpoint = Math.ceil(string.length / 2);
var toremove = string.length - maxLength;
var lstrip = Math.ceil(toremove/2);
var rstrip = toremove - lstrip;
return string.substring(0, midpoint-lstrip) + '...' + string.substring(midpoint+rstrip);
},
_tokenDiscoverer : /\$([^$]+)\$/g,
/**
* Finds all instances of any string looking like "$foo$" anywhere in the given object literal.
* returns an array of all the distinct values it found, eg 'foo'.
* if a single string value in the struct has two, like "$foo$ $bar$", duplicates are removed.
* This will also discover any number of "$foo$" substrings that are found within the
* keys of object literals, not just the values.
*/
discoverReplacementTokens: function(fragment) {
var keys = [];
var tokenDiscoverer = Splunk.util._tokenDiscoverer;
var keysToAdd;
if (typeof fragment == 'string') {
if (fragment.match(tokenDiscoverer)) {
keysToAdd = fragment.match(tokenDiscoverer);
// TODO - im sure there's a way to write the re so that it doesnt include the '$' chars but im moving on.
for (var i=0; i<keysToAdd.length; i++ ) {
keysToAdd[i] = keysToAdd[i].substring(1, keysToAdd[i].length-1);
}
return keysToAdd;
}
return [];
}
else if (typeof fragment == "function") {
return [];
}
// then fragment is not a string.
for (var key in fragment) {
keysToAdd = [];
keysToAdd = Splunk.util.discoverReplacementTokens(fragment[key]);
// up until now we've only looked at values. We have to also discover keys in the key itself..
var matchesInTheKeyItself = key.match(tokenDiscoverer) || [];
for (var j=0; j<matchesInTheKeyItself.length; j++) {
// TODO - im sure there's a way to write the re so that it doesnt include the '$' chars but im moving on.
keysToAdd.push(matchesInTheKeyItself[j].substring(1, matchesInTheKeyItself[j].length-1));
}
// check against duplicates.
for (var k=0; k<keysToAdd.length; k++) {
if (keys.indexOf(keysToAdd[k]) ==-1) {
keys.push(keysToAdd[k]);
}
}
}
return keys;
},
/**
* walked through the entirety of fragment to all levels of nesting
* and will replace all matches of the given single regex with the given
* single value.
* replacement will occur in both keys and values.
*/
replaceTokens: function(fragment, reg, value) {
if (typeof fragment == 'string') {
if (fragment.match(reg)) {
fragment = fragment.replace(reg, value);
}
return fragment;
}
else if (typeof fragment == "function") {
return fragment;
}
// watch out for infinite loops. We make all changes to the array after iteration.
var keysToRename = {};
for (var key in fragment) {
// recurse
if (typeof fragment[key] == 'object') {
Splunk.util.replaceTokens(fragment[key], reg, value);
}
// we have hit a string value.
else if (typeof fragment[key] == 'string' && fragment[key].match(reg)) {
fragment[key] = fragment[key].replace(reg, value);
}
// now that the value is changed we check the key itself
if (key.match(reg)) {
// mark this to be changed after we're out of the iterator
keysToRename[key] = key.replace(reg, value);
}
}
for (oldKey in keysToRename) {
var newKey = keysToRename[oldKey];
fragment[newKey] = fragment[oldKey];
delete(fragment[oldKey]);
}
return fragment;
},
getServerTimezoneOffset: function() {
return Splunk.util.getConfigValue('SERVER_TIMEZONE_OFFSET');
},
// constants used by Modules as well as ModuleLoader, to denote runtime states
// WAITING_FOR_INITIALIZATION and WAITING_FOR_HIERARCHY mean that the Modules
// are still being loaded by ModuleLoader.
// the remaining two states are relevant BOTH during page load, and in general
// at runtime thereafter.
// whether or not the page is still loading is an orthogonal piece of information,
// and modules can check it on demand by calling Module.isPageLoadComplete().
moduleLoadStates: {
WAITING_FOR_INITIALIZATION : 1, // waiting for INITIALIZATION
WAITING_FOR_HIERARCHY : 2, // waiting for HIERARCHY
WAITING_FOR_CONTEXT: 6,
HAS_CONTEXT : 7
},
/**
* Returns a wait time (sec) based on the current time elapsed, as mapped
* onto a cubic easing function.
*
* elapsed_time: number of seconds that have elapsed since the first
* call to getRetryInterval()
*
* min_interval: minimum return value of this method; also the interval
* returned when elapsed_time = 0
*
* max_interval: maximum return value of this method; also the interval
* returned when elapsed_time >= clamp_time
*
* clamp_time: total duration over which to calculate a wait time; while
* elapsed_time < clamp_time, the return value will be less than
* max_interval; when elapsed_time >= clamp_time, the return value will
* always be max_interval
*
*/
getRetryInterval: function(elapsed_time, min_interval, max_interval, clamp_time) {
if (elapsed_time >= clamp_time) return parseFloat(max_interval);
return Math.min(max_interval * Math.pow(elapsed_time/parseFloat(clamp_time), 3) + min_interval, max_interval);
},
/**
* Returns a string with HTML entities escaped.
* NOTE: IE will not interpret ""'", opting to just render it encoded
* we use the alternate decimal version instead
*
*/
escapeHtml: function(input) {
return (""+input).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
},
/**
* Returns a string with backslashes escaped
*/
escapeBackslash: function(input) {
return (""+input).replace(/\\/g, '\\\\');
},
/**
* From http://blog.stevenlevithan.com/archives/faster-trim-javascript
* profiler shows this is much faster than the previous implementation in both IE and Firefox.
*
* @param {String} str The string to trim.
* @param {String} (Optional) delim The characters to remove from the start/end of the string.
*
* @type String
* @return A trimmed string.
*/
trim: function(str, delim) {
if (delim) return str.replace(new RegExp("^[\\s" + delim + "]+"),'').replace(new RegExp("[\\s" + delim + "]+$"), '');
else return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
},
focusFirstField: function(popup){ //this puts the focus on the first form element whether an input or select dropdown
var firstInput = $(":input:visible:enabled:first",popup),
firstSelect = $("select:visible:enabled:first",popup),
firstInputOffset = (firstInput.length) ? firstInput.offset().top : false,
firstSelectOffset = (firstSelect.length) ? firstSelect.offset().top : false,
firstElem = firstInput;
if(firstInputOffset && firstSelectOffset){
if(firstSelectOffset < firstInputOffset){
firstElem = firstSelect;
}
}
firstElem.focus();
}
};
/**
* ----------------------
* Black magic for Prototype's bind() method which we're still using.
*
*/
var $A = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) {
return iterable.toArray();
} else {
var results = [];
for (var i = 0, length = iterable.length; i < length; i++)
results.push(iterable[i]);
return results;
}
};
Function.prototype.bind = function() {
var __method = this, args = $A(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat($A(arguments)));
};
};
/**
* ----------------------
* Prototype augmentation.
* TODO - find another way.
*
*/
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
return new Array(count+1).join(this);
};
}
if (!String.prototype.reverse) {
String.prototype.reverse = function() {
return this.split('').reverse().join('');
};
}
if (!String.prototype.rsplit) {
String.prototype.rsplit = function(sep, limit) {
var sp = this.split(sep);
if (limit && sp.length > limit) {
var r = [];
for(var i=0; i<limit; i++)
r[i] = sp[sp.length-limit+i];
return r;
}
return sp;
};
}
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(search, fromIndex) {
if (!fromIndex) fromIndex = 0;
for(var i=0; i<this.length; i++) {
if (this[i] === search)
return i;
}
return -1;
};
}
if (!Array.prototype.extend) {
Array.prototype.extend = function(arr) {
for(var i=0; i<arr.length; i++)
this.push(arr[i]);
};
}
/**
* sprintf routine borrowed from http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_sprintf/
* Licensed under GPL and MIT licenses
*
* Modified by Gareth to add support for Python style argument specifiers:
* sprintf("Hi %(name)s, welcome to %(application)s", { name: 'Gareth', app: 'Splunk })
* Objects holding named arguments can also implement a python style __getitem__ method to return dynamic values
*/
exports.sprintf = sprintf;
function sprintf( ) {
// Return a formatted string
//
// + discuss at: http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_sprintf/
// + version: 810.1015
// + original by: Ash Searle (http://hexmen.com/blog/)
// + namespaced by: Michael White (http://getsprink.com)
// + tweaked by: Jack
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// * example 1: sprintf("%01.2f", 123.1);
// * returns 1: 123.10
var regex = /%%|%(\d+\$)?(\([^)]+\))?([-+#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;
var a = arguments;
var i = 0;
var format = a[i];
i++;
// pad()
var pad = function(str, len, chr, leftJustify) {
var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
return leftJustify ? str + padding : padding + str;
};
// justify()
var justify = function(value, prefix, leftJustify, minWidth, zeroPad) {
var diff = minWidth - value.length;
if (diff > 0) {
if (leftJustify || !zeroPad) {
value = pad(value, minWidth, ' ', leftJustify);
} else {
value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
}
}
return value;
};
// formatBaseX()
var formatBaseX = function(value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
// Note: casts negative numbers to positive ones
var number = value >>> 0;
prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
value = prefix + pad(number.toString(base), precision || 0, '0', false);
return justify(value, prefix, leftJustify, minWidth, zeroPad);
};
// formatString()
var formatString = function(value, leftJustify, minWidth, precision, zeroPad) {
if (precision != null) {
value = value.slice(0, precision);
}
return justify(value, '', leftJustify, minWidth, zeroPad);
};
// finalFormat()
var doFormat = function(substring, valueIndex, valueName, flags, minWidth, _, precision, type) {
if (substring == '%%') return '%';
// parse flags
var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false;
var flagsl = flags.length;
for (var j = 0; flags && j < flagsl; j++) switch (flags.charAt(j)) {
case ' ': positivePrefix = ' '; break;
case '+': positivePrefix = '+'; break;
case '-': leftJustify = true; break;
case '0': zeroPad = true; break;
case '#': prefixBaseX = true; break;
default: break;
}
// parameters may be null, undefined, empty-string or real valued
// we want to ignore null, undefined and empty-string values
if (!minWidth) {
minWidth = 0;
} else if (minWidth == '*') {
minWidth = +a[i];
i++;
} else if (minWidth.charAt(0) == '*') {
minWidth = +a[minWidth.slice(1, -1)];
} else {
minWidth = +minWidth;
}
// Note: undocumented perl feature:
if (minWidth < 0) {
minWidth = -minWidth;
leftJustify = true;
}
if (!isFinite(minWidth)) {
throw new Error('sprintf: (minimum-)width must be finite');
}
if (!precision) {
precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
} else if (precision == '*') {
precision = +a[i];
i++;
} else if (precision.charAt(0) == '*') {
precision = +a[precision.slice(1, -1)];
} else {
precision = +precision;
}
// grab value using valueIndex if required?
var value;
if (valueName) {
valueName = valueName.substr(1, valueName.length-2);
value = a[1].__getitem__ ? a[1].__getitem__(valueName) : a[1][valueName];
} else {
if (valueIndex){
value = a[valueIndex.slice(0, -1)];
}
else
{
value = a[i];
i++;
}
}
var number;
var prefix;
switch (type) {
case 's': return formatString(String(value), leftJustify, minWidth, precision, zeroPad);
case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
case 'i':
case 'd': {
number = parseInt(+value, 10);
prefix = number < 0 ? '-' : positivePrefix;
value = prefix + pad(String(Math.abs(number)), precision, '0', false);
return justify(value, prefix, leftJustify, minWidth, zeroPad);
}
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
{
number = +value;
prefix = number < 0 ? '-' : positivePrefix;
var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
value = prefix + Math.abs(number)[method](precision);
return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
}
default: return substring;
}
};
return format.replace(regex, doFormat);
}// }}}
})();