You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1129 lines
44 KiB

//we listen to the jobResurrected event (triggered in Splunk.Search.resurrect())
//and because it passes the containing group title of the module resurrecting the job,
//we are able to map the job.getCreateTime() values into 'last refreshed:' header for each panel.
Splunk.DashboardManager = $.klass({
dateDict : {},
NOW_REFRESHED_TIME : _("<b>real-time</b>"),
TODAY_REFRESHED_TIME : _("today at %(timeText)s."),
GENERIC_REFRESHED_TIME : _("<b>%(dateText)s ago</b>"),
FULL_REFRESHED_TIME : _("refreshed: %(dateText)s"),
DISPLAY_REFLOW_EVENT: 'Splunk.Events.REDRAW',
PANEL_DROP_EVENT: 'Splunk.Events.PANEL_DROP',
windowWidth: $(window).width(),
// windowHeight: $(window).height(),
initialize: function() {
// handlers to keep the last refreshed headers updated.
$(document).bind('jobResurrected', this.onJobExists.bind(this));
$(document).bind('jobDispatched', this.onJobExists.bind(this));
$(document).bind('jobProgress', this.onJobProgress.bind(this));
var that = this;
// setup the headers to auto-truncate long titles
this.titleHeaders = $('.layoutCell .splHeader h2');
this.handlePanelResize();
var timeoutID = null;
$(window).bind('resize', function() {
if ( $(window).width() != that.windowWidth /*|| $(window).height() != that.windowHeight*/ ) {
that.windowWidth = $(window).width();
// that.windowHeight = $(window).height();
if ( timeoutID )
window.clearTimeout(that.timeoutID);
timeoutID = window.setTimeout(function(){
$(window).trigger("real_resize");
}, 100);
}
});
$(window).bind('real_resize', this.handlePanelResize.bind(this));
// $(document).bind('Splunk.Events.REDRAW', this.handlePanelResize.bind(this));
$(document).bind('allModulesLoaded', this.handlePanelResize.bind(this));
$(document).bind('jobDone', function(){
if(!this.editMode) {
setTimeout(this.equalizeHeights, 500);
}
}.bind(this));
// custom event fired by chart modules when they are resized manually by the user
$(document).bind('ChartManualResize', this.handlePanelResize.bind(this));
$(document).bind('RefreshPage', this.softRefresh.bind(this));
// $(window).bind('resize', function(){DebugUtils.trace("window.resize invoked")});
// $(window).bind('real_resize', function(){DebugUtils.trace("window.real_resize invoked")});
// $(document).bind('Splunk.Events.REDRAW', function(){DebugUtils.trace("Splunk.Events.REDRAW invoked")});
// $(document).bind('allModulesLoaded', function(){DebugUtils.trace("allModulesLoaded invoked")});
// $(document).bind('jobDone', function(){DebugUtils.trace("jobDone invoked")});
// $(document).bind('ChartManualResize', function(){DebugUtils.trace("ChartManualResize invoked")});
$(document).bind('PrintStart', this.insertPageBreakers.bind(this));
$(document).bind('PrintEnd', this.removePageBreakers.bind(this));
this.searchIdToGroupNames = {};
this.panelRowsSelector = 'div.layoutRow[class*="panel_row"]';
// this.panelRowsSelector = 'div.layoutRow[class="panel_row*"]';
this.$panelRows = $(this.panelRowsSelector);
this.$isAwesomeBrowser = ! ($.browser.msie && $.browser.version < 9);
// DebugUtils.trace( this.panelRowsSelector);
//do equal heights
this.equalizeHeights();
var dragAndDropEnabled = false;
if ( Splunk.ViewConfig && ! ($.browser.msie && $.browser.version == 6) && 0 == $(".FlashWrapperContainer").length ) {
dragAndDropEnabled = (Splunk.ViewConfig.view.nativeObjectMode == "SimpleDashboard") && Splunk.ViewConfig.view.canWrite && ! Splunk.ViewConfig.view.hasRowGrouping;
}
this.editMode = false;
$(document).bind('Splunk.Module.DashboardTitleBar.editMode', function(event, enabled){
var $paneledit = $('.paneledit');
if (enabled) {
$paneledit.show();
if(dragAndDropEnabled) {
that.dragAndDropControllerInit();
that.editMode = true;
}
} else {
$paneledit.hide();
if(dragAndDropEnabled) {
that.dragAndDropControllerDestroy();
that.editMode = false;
}
}
}.bind(this));
that.panelEditInit();
//setup panel editor and focus model
this.messenger = Splunk.Messenger.System.getInstance();
},
/**
* Reloads the existing page preserving old search jobs if they are present via the
* fragment identifier.
*
* @param {String} excludeGimpId (Optional) An optional gimpId to exclude form the soft-refresh (forces job refresh)
*/
softRefresh: function(excludeGimpId) {
var frag = {}; //Splunk.util.queryStringToProp(Splunk.util.getHash());
var gimps = $('.Gimp');
for (var i = 0; i < gimps.length; i++) {
var gimpId = gimps[i].id;
if (gimpId==excludeGimpId) {
continue;
}
var gimpModule = Splunk.Globals['ModuleLoader'].getModuleInstanceById(gimpId);
var search = gimpModule.getContext().get("search");
if (!search || !search.job) continue;
var sid = search.job.getSearchId();
if (!sid) continue;
var meta = gimpModule.container.closest('.dashboardCell').find('.paneledit').attr("data-sequence");
frag['panel_' + meta + ".sid"] = sid;
search.job.setAsAutoCancellable(false);
}
frag['edit'] = 1;
window.location.hash = Splunk.util.propToQueryString(frag);
window.location.reload();
},
// iterate on all the panels besides the one clicked on, and remove the menu.
// since this is a draggable object, the events are not propagating to the top and document.click is never triggered.
// we could manually trigger a dummy event, or a doc.click event, besides IE is garbage and it is throwing a weird error when we do so.
menusGC: function(orig){
var that = this;
$('.paneledit').each(function(){
if (this != orig){
that.hideMenu(this.actionsMenu);
}
});
},
hideMenu: function(menu){
if (menu) {
menu.getMenu().remove();
menu = null;
}
},
panelEditInit: function() {
var that = this;
$('.paneledit').click(function(event) {
that.menusGC(this);
// since events are not being propagated, we have to manually hide our menu item if it is in a visible mode.
if (this.actionsMenu && this.actionsMenu.getMenu().is(':visible')) {
that.hideMenu(this.actionsMenu);
event.stopImmediatePropagation();
return false;
}
// remove the previous menu, since our id could have been changed.
that.hideMenu(this.actionsMenu);
var meta = $(this);//.parent();
var sequence = meta.attr('data-sequence');
var intersectX = meta.attr('data-intersect-x');
var intersectY = meta.attr('data-intersect-y');
var dashboardId = meta.attr('data-dashboard-id');
var app = meta.attr('data-app');
var panelType = meta.attr('data-paneltype');
var id = $($('.Gimp')[sequence]).attr('id');
var gimpModule = Splunk.Globals['ModuleLoader'].getModuleInstanceById(id);
//shallow object of k/v pairs adapted for panel editor
var panelSettings = gimpModule.getPanelSettings(panelType, 'options.');
panelSettings.id = dashboardId;
panelSettings.panelType = panelType;
panelSettings.enable_fragment_id = 0;
panelSettings.enable_controls = 1;
//search meta data
var context = null, search = null, job = null;
context = gimpModule.getContext();
if (context) search = context.get('search');
if (search) job = search.job;
if (!job || job.areResultsTransformed())
panelSettings.is_transforming = true;
else
panelSettings.is_transforming = false;
//set the href to the panel editor
var editVisualizationHref = Splunk.util.make_url('paneleditor', app, 'edit', intersectX, intersectY)+ '?' + Splunk.util.propToQueryString(panelSettings);
var menuDict = [
{
label: _("Edit search"),
uri: Splunk.util.make_url('paneleditor', app, 'searchedit', intersectX, intersectY) + '?id=' + encodeURIComponent(dashboardId),
callback: function(event) {
$(document).trigger('SessionTimeout.Jobber');
that.showExpose(id);
var options = {
onBeforeDestroy: function() {
//restart the jobber
$(document).trigger('SessionStart.Jobber');
$(".dashboardCellEditable").removeClass("dashboardCellActive");
that.hideExpose();
},
onFrameLoad: function(popup, iframe) {
$(document).bind('panelsave', function() {
popup.destroyPopup();
that.softRefresh(id);
});
},
isModal: false,
pclass: 'panelEditorPopup'
};
Splunk.Popup.IFramer(event.target.href, _("Edit search"), options);
return false;
}
},
{
label: _("Edit visualization"),
uri: editVisualizationHref,
callback: function(event) {
$(document).trigger('SessionTimeout.Jobber');
//panel meta found on <a data-*=""/>
//gimp module lookup
var id = $($('.Gimp')[sequence]).attr('id');
that.showExpose(id);
var options = {
onBeforeDestroy: function() {
//restart the jobber
$(document).trigger('SessionStart.Jobber');
$(".dashboardCellEditable").removeClass("dashboardCellActive");
that.hideExpose();
},
onFrameLoad: function(popup, iframe) {
$(document).bind('panelsave', function() {
popup.destroyPopup();
that.softRefresh(id);
});
},
isModal: false,
pclass: 'panelEditorPopup'
};
Splunk.Popup.IFramer(event.target.href, _("Edit visualization"), options);
return false;
}
},
{
label: _("Delete"),
uri: '',
callback: function(event) {
that.showExpose(id);
setTimeout(function(){
var deletePanel = confirm(_('Are you sure you would like to delete this panel?'));
that.hideExpose();
if (deletePanel) {
var url = Splunk.util.make_url('paneleditor', app, 'delete', intersectX, intersectY)+ '?' + Splunk.util.propToQueryString({id: dashboardId});
$.ajax({
url: url,
type: 'POST',
timeout: 10000,
complete: function(jqXHR, textStatus) {
if (jqXHR.status==204) {
//delete node beacuse we are going to reset sequence
meta.closest('.layoutCell').remove();
that.resetSequence();
that.softRefresh(id);
} else {
alert(_('Sorry, the specified panel could not be deleted.'));
}
}
});
}
}, 600);
return false;
}
}
];
this.actionsMenu = new Splunk.MenuBuilder({
menuDict: menuDict,
activator: (that.$isAwesomeBrowser ? meta : meta.parent()),
menuClasses: 'splMenu-primary'
});
this.actionsMenu.showMenu();
return false;
});
},
panelRowsAddOverlayLayers: function(doBind) {
var that = this;
that.isDNDEditMode = doBind;
if(doBind) {
$(window).unbind("real_resize", doAddOverlays);
$(window).bind("real_resize", doAddOverlays);
doAddOverlays();
}
function doAddOverlays(e) {
if ( ! that.isDNDEditMode ) {
return ;
}
var start = DebugUtils.getCurrfentTime();
var mySelection = $(that.panelRowsSelector);
mySelection.find(".vmPanelDropPlaceholderOverlay").remove();
// reset z-index since IE is dumb.
if ( ! that.$isAwesomeBrowser ) {
mySelection.children().css({"z-index": "1"});
}
mySelection.find(".layoutCellInner").each(function(){
var overlayNode = $(document.createElement("div")).addClass("layoutCellInner vmPanelDropPlaceholderOverlay");
$(this).after(overlayNode);
var ieThingy = 25;
var height = ($(this).parent().height());
if( ! that.$isAwesomeBrowser )
height -= ieThingy;
height += "px";
var top = that.$isAwesomeBrowser ? "0" : ieThingy+"px";
bindAttributes(overlayNode, ($(this).parent().width() - 15) + "px", height, top);
if ( ! that.$isAwesomeBrowser ) {
overlayNode = $(document.createElement("div")).addClass("layoutCellInner vmPanelDropPlaceholderOverlay");
$(this).after(overlayNode);
bindAttributes(overlayNode, ($(this).parent().width() - 100) + "px", ieThingy + "px", 0);
}
});
function bindAttributes(element, width, height, top) {
element.css({
'width': width,
'height': height,
'z-index': 2,
// 'background-color': 'red',
'top': top
}).bind({
mouseover: function(){
var selection = $(this).parent().children().first();
selection.find(".dashboardContent, .splHeader").css("opacity", "0.6");
},
mouseout: function(){
that.dragAndDropMouseOut($(this).parent().children().first());
}
});
}
DebugUtils.trace( "doAddOverlays", start) ;
}
},
dragAndDropMouseOut: function (selection) {
if (selection) {
selection.find(".dashboardContent, .splHeader").css("opacity", "1.0");
}
else {
this.dragAndDropMouseOut($(this.panelRowsSelector).find('.layoutCellInner'));
}
},
dragAndDropControllerInit: function() {
var that = this;
var maxHeight = 250;
var newRowHeight = 20;
var sortableParameters = {
connectWith: that.panelRowsSelector,
placeholder: 'vmPanelDropPlaceholder',
opacity: 0.7,
tolerance: 'pointer',
cursor: 'move',
delay: 100,
cursorAt: { top: (maxHeight / 2) },
handle: '.vmPanelDropPlaceholderOverlay'
};
//help IE get out of class early
if(! this.$isAwesomeBrowser){
sortableParameters.helper = function(){
return $('<div style="border:4px dashed #cccccc;"></div>');
};
sortableParameters.opacity = 1;
}
$('.splLastRefreshed').hide();
$(that.panelRowsSelector).fadeOut('fast', function(){$(this).fadeIn('fast');});
// FIXME hide the "move panels" button
// this should be removed from the template once the feature is stable
$(".editmode > .splButton-tertiary.move").hide();
_removeEmptyRows();
// set max height
var selector = $(that.panelRowsSelector);
selector.find(".layoutCell").css({"max-height": (maxHeight + "px")/*, "overflow": "hidden"*/});
selector.find(".layoutCellInner").css({"min-height": "0", "max-height": ((maxHeight - 10) + "px"), "overflow": "hidden"});
selector.find(".dashboardContent").css({"max-height": ((maxHeight - 60) + "px"), "overflow": "hidden"});
that.panelRowsAddOverlayLayers(true);
_generateEmptyRows(false);
that.changeChartFlow();
/** END COMMANDS - METHODS START HERE */
function _bindEvents() {
var myRowSelection = $(that.panelRowsSelector);
myRowSelection.unbind('sortstart');
myRowSelection.unbind('sortactivate');
myRowSelection.unbind('sortover');
myRowSelection.unbind('sortstop');
myRowSelection.bind( "sortstart", _sortableStart );
myRowSelection.bind( "sortactivate", _sortableActivate );
myRowSelection.bind( "sortover", _sortableOver );
myRowSelection.bind( "sortstop", _sortableStop );
}
function _sortableStart(event, ui) {
$('.vmPanelDropPlaceholder').css("height", Math.floor( $(ui.item).height() - 15) + 'px' ); //TODO: this seems hacky
$('.vmPanelDropPlaceholder').css("width", Math.floor($(ui.item).width() - 25) + 'px');
}
function _sortableActivate(event, ui) {
// var start = DebugUtils.getCurrfentTime();
if( ! (this === ui.item.parent()[0]) ) {
if ( $(this).children().length > 2 ) { // disable rows that has 3 panels - this is a UI constrain
$(this).sortable("disable");
_sortableRefresh();
}
}
else if ( $(this).children().length == 2 ) { // for a single panel row - disable the insertion points above and below
$(this).next().sortable("disable");//.css("background-color", "red");
$(this).prev().sortable("disable");//.css("background-color", "green");
_sortableRefresh();
}
// DebugUtils.trace( "_sortableActivate", start) ;
}
/**
* handle sortable over target
*/
function _sortableOver(event, ui) {
// var start = DebugUtils.getCurrfentTime();
that.equalizeWidths(event, ui);
var numItems = $(this).children().length;
if ( $(ui.sender).context === $(this).context )
numItems--;
var width = Math.floor(96 / numItems) + "%";
$('.vmPanelDropPlaceholder').css("width",width);
// attempt to set width of helper to width of placeholder
//$(ui.helper).width($(ui.placeholder).width());
// var height = Math.max($(this).height(), $(ui.item).height()) + "px";
// // DebugUtils.trace( "_sortableOver", start) ;
}
function _sortableStop(event, ui) {
var start = DebugUtils.getCurrfentTime();
// on some rare cases you can drop the panel top a position where the mouse is not over it.
// for these cases we would like to apply the mouseout styling ann all panels, just to play safe.
that.dragAndDropMouseOut();
// hide any visible menus
that.menusGC();
DebugUtils.trace("_sortableStop invoked") ;
$(that.panelRowsSelector).sortable('destroy');
_removeEmptyRows();
that.equalizeWidths(event, ui, true);
// save the state to the system
_save();
that.changeChartFlow();
$(".vmPanelDropPlaceholderOverlay", $(that.panelRowsSelector)).remove();
_generateEmptyRows(true);
that.panelRowsAddOverlayLayers(true);
// fire off the panel drop event, passing the dropped element as extra data
$(document).trigger(that.PANEL_DROP_EVENT, {droppedElement: ui.item[0]});
DebugUtils.trace( "_sortableStop end", start) ;
}
function _sortableInit( setParams ) {
var start = DebugUtils.getCurrfentTime();
var sortable;
if (setParams )
sortable = $(that.panelRowsSelector).sortable(sortableParameters);
else
sortable = $(that.panelRowsSelector).sortable();
sortable.disableSelection();
_bindEvents();
DebugUtils.trace( "_sortableInit ("+(setParams)+") ", start) ;
return sortable;
}
function _sortableRefresh(setParams) {
var start = DebugUtils.getCurrfentTime();
var sortable = _sortableInit(setParams).sortable("refresh");
DebugUtils.trace( "_sortableRefresh", start) ;
return sortable;
}
function _generateEmptyRows(doRefresh) {
var counter = 1;
$(that.panelRowsSelector).each(function(){
_addEmptyRow($(this), "before");
});
_addEmptyRow($(that.panelRowsSelector).last(), "after", 100);
// XXX not sure what is causing this, but sometimes new rows are getting a 0 opacity.
// This ugly woraround takes care of that.
$(".layoutRow").fadeTo(0, 1);
doRefresh ? _sortableRefresh(true) : _sortableInit(true);
function _addEmptyRow(element, where, rowHeight) {
var start = DebugUtils.getCurrfentTime();
rowHeight = rowHeight ? rowHeight : newRowHeight;
var newElement = $(document.createElement("div")).addClass("layoutRow equalHeightRow splClearfix panel_row1_col").css("min-height", rowHeight + "px");
( where == "after" ) ? element.after(newElement) : element.before(newElement);
DebugUtils.trace( "_addEmptyRow", start) ;
}
}
function _removeEmptyRows() {
var start = DebugUtils.getCurrfentTime();
$(that.panelRowsSelector).each(function(){
if ( $(this).children().length == 0 )
$(this).remove();
});
// $(".vmPanelDropPlaceholderOverlay", $(that.panelRowsSelector)).css("opacity", "0.2").css("background-color", "white");
// $(".layoutCellInner", $(that.panelRowsSelector)).parent().children().first().css("box-shadow", "0 0 5px #CCCCCC");
DebugUtils.trace( "_removeEmptyRows", start) ;
}
function _save() {
// var start = DebugUtils.getCurrfentTime();
$.post(Splunk.util.make_url(['viewmaster', Splunk.util.getCurrentApp(), Splunk.ViewConfig.view.id].join('/')), {
'action': 'edit',
'view_json': JSON.stringify(_toJSON())
},
_onSaveCallback, 'json');
// DebugUtils.trace( "_save", start)
function _toJSON() {
var output = {};
output['new_panel_sequence'] = [];
$(that.panelRowsSelector).each(function() {
var rowSet = [];
$('.paneledit', this).each(function() {
var s = parseInt($(this).attr('data-sequence'), 10);
if (!isNaN(s))
rowSet.push(s);
});
output['new_panel_sequence'].push(rowSet);
});
return output;
}
function _onSaveCallback(jsonObject){
if (jsonObject.success) {
// reset the current indexing to future actions
that.resetSequence();
}
else {
for (var i=0,L=jsonObject.messages.length; i<L; i++) {
alert('dashboard failed in updating: ' + jsonObject.messages[i].message);
}
}
}
}
},
resetSequence: function() {
var sequence = 0;
var x = 0;
$(this.panelRowsSelector).each(function(){
if ( $(this).children().length == 0 )
return ;
var y = 0;
$('.paneledit', this).each(function() {
$(this).attr("data-sequence", sequence++);
$(this).attr("data-intersect-y", y++);
$(this).attr("data-intersect-x", x);
});
x++;
});
},
/**
* sets the width of panel row items based on number of children in the row
*
* @param {object} event object
* @param {object} ui jquery-ui object
* @param {boolean} fullSize set full width if true
*/
equalizeWidths: function (event, ui, fullSize) {
var start = DebugUtils.getCurrfentTime();
$(this.panelRowsSelector).each(function(index, value){
var children = $(this).children();
var numPanels = children.length;
if ( event && ($(ui.sender).context === $(this).context) )
numPanels--;
if (numPanels == 0) return; // no work to do, exit
fullSize = (!event || fullSize);
var width = (Math.floor((fullSize ? 100 : 96) / numPanels)) + "%";
children.css("width", width);
//XXX terrible hack
if (fullSize && numPanels == 3)
children.last().css("width", "34%");
// other javascript routines depend on the class name matching the number of columns in the row,
// so update these here to avoid an inconsistent state
if(numPanels == 1) {
$(this).removeClass('twoColRow threeColRow').addClass('oneColRow');
}
else if(numPanels == 2) {
$(this).removeClass('oneColRow threeColRow').addClass('twoColRow');
}
else if(numPanels == 3) {
$(this).removeClass('oneColRow twoColRow').addClass('threeColRow');
}
$(".vmPanelDropPlaceholderOverlay", children).each(function(){
$(this).css("width", $(this).parent().children().first().width() + "px");
});
});
DebugUtils.trace( "equalizeWidths", start) ;
// this.equalizeHeights();
this.changeChartFlow();
},
changeChartFlow: function() {
if ( true || this.$isAwesomeBrowser ) {
var start = DebugUtils.getCurrfentTime();
$(document).trigger(this.DISPLAY_REFLOW_EVENT);
DebugUtils.trace( "DISPLAY_REFLOW_EVENT", start) ;
}
},
dragAndDropControllerDestroy: function() {
var theSelection = $(this.panelRowsSelector);
theSelection.fadeOut('fast', function(){$(this).fadeIn('fast');});
try {
theSelection.sortable('destroy');
}
catch (e) {}
// reset visualization
$(".vmPanelDropPlaceholderOverlay").remove();
$('.splLastRefreshed').show();
// reset max height
theSelection.find(".layoutCell").css({"max-height": "none", "overflow": "none"});
theSelection.find(".layoutCellInner").css({"max-height": "none", "overflow": "none"});
theSelection.find(".dashboardContent").css({"max-height": "none", "overflow": "none"});
theSelection.each(function(){
var children = $(this).children();
var width = (Math.floor(100 / children.length)) + "%";
children.css("width", width);
if ( $(this).children().length == 0 )
$(this).remove();
});
this.equalizeWidths();
this.equalizeHeights();
this.panelRowsAddOverlayLayers(false);
this.changeChartFlow();
},
/**
* Enable the expose around a targeted panel.
*
* @param {String} id The id attribute for the targeted Gimp module.
*/
showExpose: function(id) {
//panel module lookup
var that = this;
var gimpModule = Splunk.Globals['ModuleLoader'].getModuleInstanceById(id);
var moduleContentEl = $($(gimpModule.container).attr('s:parentmodule'));
if (!moduleContentEl.hasClass("JSChart")) {
moduleContentEl = $(moduleContentEl).children()[0];
}
var content = $(moduleContentEl).closest('.SplunkModule');
content.addClass('dashboardVisActive');
content.addClass('dashboardCellExpose');
content.expose({
color: '#000',
closeOnClick: false,
opacity: 0.5,
loadSpeed: 0,
closeSpeed: 0,
onLoad: function(e) {
$('.splIcon-close').click(function(){
that.hideExpose();
});
},
onClose: function(e) {
content.removeClass('dashboardCellExpose');
}
});
content.expose().load();
},
/**
* Disable the expose around any targeted panel if present'
*/
hideExpose: function() {
$.mask.close();
$(".dashboardVisActive").removeClass("dashboardVisActive");
},
/**
* Initialize the drag'n'drop behavior.
*/
dragndropInit: function() {
var that = this;
this.$panelRows.sortable({
connectWith: this.panelRowsSelector,
update: function(event, ui) {
var rows = [];
that.isMoving = false;
that.$panelRows.each(function() {
var row = [];
$('.move a', this).each(function() {
row.push(parseInt($(this).attr('data-sequence'), 10));
});
if (row.length) {
rows.push(row);
}
});
that.save(rows);
}
}).disableSelection();
},
/**
* Save the new layout to the server.
*
* @param {Array} rows An array of arrays providing the new sequence structure (ie., [[0,2],[1]]).
*/
save: function(rows) {
var params = {
url: Splunk.util.make_url('/paneleditor/'),
type: 'POST',
timeout: 50000,
data: JSON.stringify(rows),
contentType: 'application/json',
complete: function(response) {
//if (response.status!=200) {
//alert(_('Error:\nYour panel changes could not be saved.'));
//}
}
};
$.ajax(params);
},
/**
* for the given group (aka panel title), we return a date object with the timestamp of the oldest job it contains.
*/
getEarliestCreateTimeForGroup: function(group) {
var earliest = null;
for (var i=0,l=this.dateDict[group].length; i<l; i++) {
if (!earliest) {
earliest = this.dateDict[group][i];
continue;
}
if (this.dateDict[group][i] < earliest) {
earliest = this.dateDict[group][i];
}
}
return earliest;
},
/**
* By proactively linking these two together on jobDispatched
* and jobResurrected, we preserve the linkage for the
* times like jobProgress when we have no direct linkage.
*/
linkSearchIdToGroup: function(job, group) {
var sid = job.getSearchId();
if (this.searchIdToGroupNames.hasOwnProperty(sid)) {
this.searchIdToGroupNames[sid].push(group);
} else {
this.searchIdToGroupNames[sid] = [group];
}
},
/**
* given an sid, this method returns an array of matching group names.
* Specifically returns the header string(s) of panels that contain modules
* that are currently using the given job.
* (Assumes: on corresponding jobDispatched / jobResurrected event previously
* triggered, we will have stored the link between job and group)
*/
getGroupNamesForSearchId: function(sid) {
if (this.searchIdToGroupNames.hasOwnProperty(sid)) {
return $.extend([], this.searchIdToGroupNames[sid]);
}
return [];
},
/**
* updates the 'last refreshed' time for the correct panel,
* as identified by the 'group' argument.
* NOTE: the method name uses the generic word 'exists' because this
* is a listener for both jobDispatched and jobResurrected events.
* All jobs enter the world triggering one of those two events.
*/
onJobExists: function(event, job, group) {
this.linkSearchIdToGroup(job, group);
//new job was dispatched, lets clear the old data.
this.dateDict[group] = [];
this.updateRefreshedTimeForGroup(event, job, group);
},
/**
* If the job in question is a real time search,
* then we call updateRefreshedTimeForGroup.
*/
onJobProgress: function(event, job) {
var group = this.getGroupNamesForSearchId(job.getSearchId());
if (job.isRealTimeSearch()) {
this.updateRefreshedTimeForGroup(event, job, group);
}
},
/**
* uses the given job object and group string to update the corresponding
* panel header's 'last refreshed' date and time.
*/
updateRefreshedTimeForGroup: function(event, job, group) {
// DebugUtils.trace("updateRefreshedTimeForGroup group=" + group + " sid=" + job.getSearchId());
if (!group) return;
// NOTE: if a panel contains either all real-time searches or a
// mixture of real time and historical searches, the last refreshed
// time will always display the MOST RECENT of the real time searches.
if (job.isRealTimeSearch()) {
var now = new Date();
// resistance is futile.
this.dateDict[group] = [now];
this.setLastRefreshedHeaderText(group, this.NOW_REFRESHED_TIME);
// NOTE: on the other hand if a panel contains only historical searches,
// the last refreshed time will always display the OLDEST of the historical searches that were run.
} else if (job.getCreateTime()) {
// 1 We keep a list of all of the times received per group.
if (!this.dateDict.hasOwnProperty(group)) {
this.dateDict[group] = [];
}
this.dateDict[group].push(job.getCreateTime());
// 2 we get the earliest one for this group.
var earliest = this.getEarliestCreateTimeForGroup(group);
var diff = Math.max((new Date().getTime() - earliest.getTime()) / 1000, 0);
// 3 get the correct localized string.
var dateText;
if (diff === 0) {
// this is to account for dashboards that render in 0 milliseconds, to the precision of the Date object
dateText = '1s';
} else if (diff < 60) {
dateText = '&lt; 1m';
} else if (diff < 3600) {
dateText = Math.ceil(diff / 60) + 'm';
} else if (diff < 86400) {
dateText = Math.ceil(diff / 3600) + 'h';
} else {
dateText = Math.ceil(diff / 86400) + 'd';
}
this.setLastRefreshedHeaderText(group, dateText, format_time(earliest, "medium"));
//refresh the timer every minuet for this panel
this.intSet = this.intSet || [];
var that = this;
if(!this.intSet[job.getSearchId()]){
setInterval(function(){
that.updateRefreshedTimeForGroup(event, job, group);
},60000);
that.intSet[job.getSearchId()] = true;
}
}
},
setLastRefreshedHeaderText: function(group, shortDateText, longDateText) {
var lastRefreshedSpan = $("<span>");
lastRefreshedSpan.addClass("splLastRefreshed");
lastRefreshedSpan.attr("title", sprintf(this.FULL_REFRESHED_TIME, {dateText: longDateText}));
if (shortDateText === this.NOW_REFRESHED_TIME) {
lastRefreshedSpan.html(sprintf(this.NOW_REFRESHED_TIME));
} else {
lastRefreshedSpan.html(sprintf(this.GENERIC_REFRESHED_TIME, {dateText: shortDateText}));
}
// 4 go find the correct panel title
$('h2[title="' + group + '"]').closest('.layoutCell').find('.meta').attr('title', group);
$('.meta[title="' + group + '"]').find("span.splLastRefreshed").remove();
$('.meta[title="' + group + '"]').prepend(lastRefreshedSpan);
},
/**
* Auto truncates the panel headers based on available width
*/
handlePanelResize: function() {
this.titleHeaders.each(function() {
// without this check it has the neat effect of nuking the contents of all $(".splHeader h2")
// including those in modules like ResultsHeader.
if ($(this).attr('title')) {
// this is just a trial and error calculation; could be smarter
var charWidth = parseInt(Math.pow($(this).parent().width() / 12 - 15, 1.15), 10);
//$(this).text(Splunk.util.smartTrim($(this).attr('title'), charWidth));
}
});
// set equal heights in view mode only
if(!this.editMode) {
this.equalizeHeights();
}
},
/**
* This method catches the case where the user is looking at a new simple
* dashboard that has no panels, and displays an accelerator link
*/
showDashboardPrompts: function() {
var view_config = Splunk.util.getCurrentViewConfig();
if (view_config.hasOwnProperty('view') && view_config['view']['objectMode'] != 'SimpleDashboard') {
return false;
}
var panelCount = $('.dashboardCell').length;
if (panelCount == 0) {
var link = $( _('<p class="dashboardPromptMessage">This dashboard is empty. <a href="#">Edit the dashboard</a> to add a panel.</p>')).bind('click', function() {
Splunk.Globals.Viewmaster.openDashEditForm(Splunk.util.getCurrentView());
return false;
}).appendTo($('.layoutRow.firstRow'));
}
},
/**
* This method equalizes heights of dashboard cells within the same panel
*/
equalizeHeights: function() {
var start = DebugUtils.getCurrfentTime();
$(".equalHeightRow").each(function(){
$(this).find('.layoutCellInner').css({'min-height': 0});
if ($.browser.msie && $.browser.version == 6.0) {
$(this).children().css({'height': 0});
}
var max = 0;
$(this).find('.layoutCellInner').each(function(i){
if ($(this).height() > max) { max = $(this).height(); }
});
if ($.browser.msie && $.browser.version == 6.0) { $(this).find('.layoutCellInner').css({'height': max}); }
$(this).find('.layoutCellInner').css({'min-height': max});
});
DebugUtils.trace( "equalizeHeights", start) ;
},
/**
* This method traverses the dashboard rows from top to bottom, whenever it finds one that will have a page break
* in the middle of it, inserts a page-breaking element above it
*/
insertPageBreakers: function() {
// IE9 and IE10 can handle page breaking purely in CSS
if($.browser.msie && parseFloat($.browser.version) >= 9) {
return;
}
var $row, rowHeight,
currentHeight = 0,
$pageBreaker = $('<div class="page-breaker"></div>'),
pageBreakHeight = ($.browser.msie) ? 800 : 900; // pixel height to use when breaking up the page
$('.equalHeightRow').each(function(i, row) {
$row = $(row);
// caclulate the row height, force to zero for empty elements, since some browsers will report a non-zero height
rowHeight = ($row.is(':empty')) ? 0 : $row.outerHeight(true); // true means include margin
if(i != 0 && rowHeight > 0 && currentHeight + rowHeight >= pageBreakHeight) {
// this element needs a page break before it
$pageBreaker.clone().insertBefore($row);
currentHeight = rowHeight;
}
else {
currentHeight += rowHeight;
}
});
},
removePageBreakers: function() {
if($.browser.msie && parseFloat($.browser.version) >= 9) {
return;
}
$('.page-breaker').remove();
}
});
var DebugUtils = {
traceEnabled: false,
getCurrfentTime: function() {
if(this.traceEnabled)
return (new Date()).getTime();
},
trace: function(arg, start) {
if( this.traceEnabled && window.console) {
var now = this.getCurrfentTime();
arg = this._addSpaces(arg, 30);
if (start)
arg += ["\t", (now - start)].join('');
console.log([now, "\t", arg].join(''));
}
},
_addSpaces: function(str, len) {
var newStr = str;
while(newStr.length < len)
newStr += " ";
return newStr;
}
};