// Copyright (C) 2018 Chris Younger
/* jslint bitwise: true */
/* globals require, monaco, BroadcastChannel, Promise */
/*
TODO
- Option to open a blank diffing window
- ability to stream results from a run window
- ability to see running config explorer commands and kill them?
- if you hit save when a post-save action is already running, two things run at once. Seems its not too bad anyway.
- the sidecar should allow rerun
*/
// Loading monaco from the CDN
/*
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.15.0/min/vs' }});
window.MonacoEnvironment = {
getWorkerUrl: function(workerId, label) {
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
self.MonacoEnvironment = { baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.15.0/min/' };
importScripts('https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.15.0/min/vs/base/worker/workerMain.js');`
)}`;
}
}*/
// The splunk webserver prepends all scripts with a call to i18n_register() for internationalisation. This fails for web-workers becuase they dont know about this function yet.
// The options are patch the function on-the-fly like so, or to edit the file on the filesystem (which makes upgrading monaco harder)
(function() {
var mode = "min"; // or dev
require.config({
paths: {
'vs': '../app/config_explorer/node_modules/monaco-editor/'+mode+'/vs',
}
});
window.MonacoEnvironment = {
getWorkerUrl: function(workerId, label) {
return "data:text/javascript;charset=utf-8," + encodeURIComponent(
//"console.log('shimming i18n_register for worker'); "+
"function i18n_register(){/*console.log('i18n_register shimmed');*/} "+
"self.MonacoEnvironment = { baseUrl: '" + window.location.origin + "/static/app/config_explorer/node_modules/monaco-editor/"+mode+"/' }; "+
"importScripts('" + window.location.origin + "/static/app/config_explorer/node_modules/monaco-editor/"+mode+"/vs/base/worker/workerMain.js');"
);
}
};
})();
require([
"splunkjs/mvc",
"jquery",
"vs/editor/editor.main",
"app/config_explorer/sortable.min",
"app/config_explorer/OverlayScrollbars"
], function(
mvc,
$,
wat,
Sortable,
OverlayScrollbars
) {
// Inject all the HTML contents
$(".dashboard-body").html(
"
"+
""+
""+
"");
// Mapping of all the tab types
// can_rerun - has a right click open in the editor for "rerun". This will close and reopen the tab. If this is "true" it must also exist in the hooksCfg list with the same name!
// reopen - tracked through hash and in "recent files". If this is "true" it must also exist in the hooksCfg list with the same name!
var tabCfg = {
'btool': { can_rerun: true, can_reopen: true, },
'btool-hidepaths': { can_rerun: true, can_reopen: true, },
'btool-hidedefaults': { can_rerun: true, can_reopen: true, },
'btool-hidesystemdefaults': { can_rerun: true, can_reopen: true, },
'btool-check': { can_rerun: true, can_reopen: false, },
'spec': { can_rerun: false, can_reopen: true, },
'run': { can_rerun: true, can_reopen: false, },
'deployserver': { can_rerun: true, can_reopen: false, },
'read': { can_rerun: false, can_reopen: true, },
'settings': { can_rerun: false, can_reopen: false, },
'change-log': { can_rerun: false, can_reopen: false, },
'diff': { can_rerun: false, can_reopen: false, },
'history': { can_rerun: false, can_reopen: false, },
'git': { can_rerun: false, can_reopen: false, },
'live': { can_rerun: false, can_reopen: false, },
'live-diff': { can_rerun: false, can_reopen: false, },
'refresh': { can_rerun: true, can_reopen: false, },
'internal': { can_rerun: false, can_reopen: false, },
'rest': { can_rerun: false, can_reopen: true, },
};
// This is what will be executed in the following circumstances:
// - the URL hash on load (can_reopen=true above)
// - opening from the Recent file list (can_reopen=true above)
// - Editor>RightClick>Rerun option is chosen (can_rerun=true above)
// - custom [hook:] right-click option
var hooksCfg = {
'btool': function(arg1, arg2, embeddedMode){
runBToolList(arg1, 'btool');
},
'btool-hidepaths': function(arg1, arg2, embeddedMode){
runBToolList(arg1, 'btool-hidepaths');
},
'btool-hidedefaults':function(arg1, arg2, embeddedMode){
runBToolList(arg1, 'btool-hidedefaults');
},
'btool-hidesystemdefaults':function(arg1, arg2, embeddedMode){
runBToolList(arg1, 'btool-hidesystemdefaults');
},
'btool-check': function(arg1, arg2, embeddedMode){
runBToolCheck();
},
'spec': function(arg1, arg2, embeddedMode){
displaySpecFile(arg1);
},
'run': function(arg1, arg2, embeddedMode){
runShellCommandNow(arg1, arg2, embeddedMode);
},
'run-safe': function(arg1, arg2, embeddedMode){
runShellCommand(arg1, true);
},
'deployserver': function(arg1, arg2, embeddedMode){
runReloadDeployServer(arg1);
},
'read': function(arg1, arg2, embeddedMode){
readFile(arg1);
},
'live': function(arg1, arg2, embeddedMode){
runningVsLayered(arg1, false);
},
'live-diff': function(arg1, arg2, embeddedMode){
runningVsLayered(arg1, true);
},
'bump': function(arg1, arg2, embeddedMode){
debugRefreshBumpHook("bump", embeddedMode);
},
'refresh': function(arg1, arg2, embeddedMode){
debugRefreshBumpHook(arg1, embeddedMode);
},
'cd': function(arg1, arg2, embeddedMode){
changeDirectory(arg1);
},
'clipboard': function(arg1, arg2, embeddedMode){
copyTextToClipboard(arg1);
},
'rest': function(arg1, arg2, embeddedMode){
readFileRest(arg1);
},
'openurl': function(arg1, arg2, embeddedMode){
// set the target to the base64 representation of the URL so we can keep opening in the same tab for the same url
window.open(arg1, btoa(arg1));
}
};
// globals
var debug_gutters = false;
var service = mvc.createService({owner: "nobody"});
//var username = Splunk.util.getConfigValue("USERNAME");
var editors = [];
var inFolder = (localStorage.getItem('ce_current_path') || './etc/apps');
var inFolderRestApp = (localStorage.getItem('ce_current_path_restapp') || '');
var inFolderRestType = "views";
var regex_file_path_to_dashboard_parts = /.*\/(apps|users\/[^\/]+)\/([^\/]+)\/(?:local|default)\/data\/ui\/views\/(.*)\.xml$/;
var openingFolder;
var folderContents;
var run_history = (JSON.parse(localStorage.getItem('ce_run_history')) || []);
var closed_tabs = (JSON.parse(localStorage.getItem('ce_closed_tabs')) || []);
var preferences = getPreferences();
var activeTab = -1;
var filecache = {};
var conf = {};
var confFiles = {};
var confFilesSorted = [];
var inFlightRequests = 0;
var comparisonLeftFile = null;
var tabid = 0;
var leftpane_ignore = false;
var max_recent_files_show = 50;
var hooksActive = [];
var tabCreationCount = 0;
var approvedPostSaveHooks = {};
var fileModsCheckTimer;
var fileModsCheckTimerPeriod = 10000;
var fileModsCheckTimerInProgress = false;
var sortMode = "name";
var sortAsc = true;
var treeSearchFocus = false;
var sortModeRest = (localStorage.getItem('ce_sort_mode_rest') || 'label');
var sortAscRest = (localStorage.getItem('ce_sort_asc_rest') || '1');
var displayModeRest = (localStorage.getItem('ce_display_mode_rest') || 'ln');
var doLineHighlight = "";
var $dashboard_body = $('.dashboard-body');
var $ce_tree_pane = $(".ce_tree_pane");
var $ce_container = $('.ce_container');
var $ce_resize_column = $('.ce_resize_column');
var $ce_file_list = $(".ce_file_list");
var $ce_file_wrap = $(".ce_file_wrap");
var $ce_file_path = $(".ce_file_path");
var $ce_tree_icons = $(".ce_tree_icons");
var $ce_contents = $(".ce_contents");
var $ce_contents_home = $(".ce_contents_home");
var $ce_spinner = $(".ce_spinner");
var $ce_tabs = $(".ce_tabs");
var $ce_home_tab = $(".ce_home_tab");
var $ce_context_menu_overlay = $(".ce_context_menu_overlay");
var broadcastChannel = new BroadcastChannel("config_explorer");
broadcastChannel.onmessage = handleBroadcastMessage;
// Set the "save" hotkey at a global level instead of on the editor, this way the editor doesnt need to have focus.
$(window).on('keydown', function(event) {
// CTRL-SHIFT-/ focus the bottom left filter. can use just forward slash if nothing is currently focused.
if (((event.ctrlKey || event.metaKey) && event.shiftKey && event.which===191) || (event.which===191 && document.activeElement.tagName === "BODY")) {
// if the editor is focused, then the keydown event wont bubble up to the window
$(".ce_treesearch_input").focus();
}
// CTRL-S to save active tab
if ((event.ctrlKey || event.metaKey) && event.which===83) {
event.preventDefault();
saveActiveTab();
}
// Prevent people from navigating away when they have unsaved changes
}).on("beforeunload", function() {
for (var i = 0; i < editors.length; i++) {
if (editors[i].hasChanges) {
return "Unsaved changes will be lost.";
}
}
});
function setLeftPathWidth(size) {
size = Math.min(Math.max(size, 0), $(window).width() * 0.8);
$ce_tree_pane.css("width", size + "px");
$ce_resize_column.css("left", size + "px");
$ce_container.css("left", (size + 3) + "px");
filePathRTLCheck();
}
// Handler for resizing the tree pane/editor divider
$ce_resize_column.on("mousedown", function(e) {
e.preventDefault();
$(document).on("mousemove.colresize", function(e) {
setLeftPathWidth(e.pageX);
localStorage.setItem('ce_lwidth', e.pageX);
});
});
$(document).on("mouseup",function(e) {
$(document).off('mousemove.colresize');
});
$('.ce_app_errors').on('click', function(){
runBToolCheck();
});
$('.ce_app_settings').on('click', function(){
readFile("");
});
$(".ce_theme").on('click', function(){
setThemeMode($(this).attr("data-theme"));
});
$('.ce_app_changelog .btn').on('click', function(){
showChangeLog();
});
$(".ce_splunk_reload").on("click", function(){
debugRefreshBumpHook( $(this).attr("data-endpoint") );
});
$(".ce_splunk_reload_specific").on("click", function(){
debugRefreshEndpointSelection();
});
$ce_home_tab.on("click", function(){
activateTab(-1);
});
// Click handlers for New File/New Folder buttons
$ce_tree_icons.on("click", "i", function(e){
e.stopPropagation();
var elem = $(this);
var $menu;
if (elem.hasClass("ce_disabled")) {
return;
}
if (elem.hasClass("ce_upload_file")) {
fileSystemUpload(inFolder);
} else if (elem.hasClass("ce_refresh_tree")) {
refreshFolder();
} else if (elem.hasClass("ce_refresh_tree_rest")) {
delete restTypes.views.cache;
delete restTypes.apps.cache;
leftPaneRestList();
} else if (elem.hasClass("ce_folder_up")) {
readFolder(inFolder.replace(/[\/\\][^\/\\]*$/,''), 'back');
} else if (elem.hasClass("ce_folder_up_rest")) {
if (inFolderRestApp !== "") {
inFolderRestApp = "";
} else {
inFolderRestType = "";
}
leftPaneRestList();
// TODO [low] it should remember which file system tab you are on
} else if (elem.hasClass("ce_show_filesystem")) {
if (! elem.hasClass("ce_selected")) {
filterModeReset();
$ce_tree_icons.find(".ce_clickable_icon").removeClass("ce_selected ce_tree_btn_show");
elem.addClass("ce_selected");
$(".ce_folder_up, .ce_refresh_tree, .ce_filter, .ce_add_folder, .ce_app_run, .ce_upload_file, .ce_sort_files").addClass("ce_tree_btn_show");
leftPaneFileList();
}
} else if (elem.hasClass("ce_recent_files")) {
if (! elem.hasClass("ce_selected")) {
filterModeReset();
$ce_tree_icons.find(".ce_clickable_icon").removeClass("ce_selected ce_tree_btn_show");
elem.addClass("ce_selected");
leftPaneRecentList();
}
} else if (elem.hasClass("ce_show_confs")) {
if (! elem.hasClass("ce_selected")) {
filterModeReset();
$ce_tree_icons.find(".ce_clickable_icon").removeClass("ce_selected ce_tree_btn_show");
elem.addClass("ce_selected");
leftPaneConfList();
}
} else if (elem.hasClass("ce_show_rest")) {
if (! elem.hasClass("ce_selected")) {
filterModeReset();
$ce_tree_icons.find(".ce_clickable_icon").removeClass("ce_selected ce_tree_btn_show");
elem.addClass("ce_selected");
$(".ce_folder_up_rest, .ce_refresh_tree_rest, .ce_sort_files_rest").addClass("ce_tree_btn_show");
leftPaneRestList();
}
} else if (elem.hasClass('ce_app_run')) {
runShellCommand();
} else if (elem.hasClass('ce_sort_files')) {
$menu = $("
").on("click", function(){
runningVsLayered(thisFile, true);
}));
}
if (isFile) {
if (confIsTrue('git_autocommit', false)) {
// can show history
actions.push($("
").on("click", function(){
serverAction({action: 'filedownload', path: inFolder, param1: thisFile}).then(function(b64data){
var byteCharacters = atob(b64data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += 512) {
var slice = byteCharacters.slice(offset, offset + 512);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: "application/octet-stream"});
var filename = dodgyBasename(thisFile);
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
});
}));
// can compare
actions.push($("
",
onShow: function(){
// On load set the fields to the current values
var preferences = getPreferences();
if (preferences.hasOwnProperty("wordWrap") && preferences.wordWrap === "on") {
$(".ce_pref_item .ce_wordWrap").prop('checked', true);
}
if (preferences.hasOwnProperty("renderWhitespace") && preferences.renderWhitespace === "all") {
$(".ce_pref_item .ce_renderWhitespace").prop('checked', true);
}
//if (preferences.hasOwnProperty("ce_reuseWindow") && preferences.ce_reuseWindow) {
// $(".ce_pref_item .ce_reuseWindow").prop('checked', true);
//}
if (!(preferences.hasOwnProperty("hover") && preferences.hover.hasOwnProperty("enabled") && ! preferences.hover.enabled)) {
$(".ce_pref_item .ce_hideSpecTooltip").prop('checked', true);
}
if (preferences.hasOwnProperty("ce_fullPathTab") && preferences.ce_fullPathTab) {
$(".ce_pref_item .ce_fullPathTab").prop('checked', true);
}
delete preferences.wordWrap;
delete preferences.renderWhitespace;
delete preferences.ce_reuseWindow;
delete preferences.ce_fullPathTab;
if (preferences.hasOwnProperty("hover")) {
delete preferences.hover.enabled;
if ($.isEmptyObject(preferences.hover)) {
delete preferences.hover;
}
}
$(".ce_pref_item .ce_pref_advanced").val(JSON.stringify(preferences,null,3));
},
actions: [{
onClick: function(){
try {
preferences = JSON.parse($(".ce_pref_item .ce_pref_advanced").val());
} catch (e) {
preferences = {};
}
preferences.wordWrap = ($(".ce_pref_item input.ce_wordWrap:checked").length > 0) ? "on" : "off";
preferences.renderWhitespace = ($(".ce_pref_item input.ce_renderWhitespace:checked").length > 0) ? "all" : "none";
//preferences.ce_reuseWindow = ($(".ce_pref_item input.ce_reuseWindow:checked").length > 0);
preferences.ce_fullPathTab = ($(".ce_pref_item input.ce_fullPathTab:checked").length > 0);
if (! preferences.hasOwnProperty("hover")) {
preferences.hover = {};
}
if ($(".ce_pref_item input.ce_hideSpecTooltip:checked").length === 0) {
preferences.hover.enabled = false;
} else {
preferences.hover.enabled = true;
}
localStorage.setItem('ce_preferences', JSON.stringify(preferences));
// Update all currently open windows
for (var i = 0; i < editors.length; i++) {
if (editors[i].hasOwnProperty("editor")) {
editors[i].editor.updateOptions(preferences);
}
}
$(".modal").modal('hide');
},
cssClass: 'btn-primary',
label: "Save"
},{
onClick: function(){ $(".modal").modal('hide'); },
label: "Cancel"
}]
});
}
// This is an action that will occur after saving the file
function setPostSaveAction() {
var ecfg = editors[activeTab];
var suggestions = [];
for (var j = 0; j < hooksActive.length; j++) {
if (hooksActive[j]._match.test(ecfg.file) && hooksActive[j].matchtype == "file" && isTrueValue(hooksActive[j].showWithSave)) {
suggestions.push("Suggest: " + htmlEncode(replaceTokens(hooksActive[j].action, ecfg.file)) + " ");
}
}
showModal({
title: "Set post-save action",
size: 900,
body: "
Enter a command to automatically run after successful save of this file only. This action will be saved in your browser local storage "+
"(it will not affect other users or other browsers you use, but it will be remembered after browser refresh). "+
"Run commands will be executed from the SPLUNK_HOME directory.
"+
"File: " + htmlEncode(ecfg.file) + ""+
"
"+
suggestions.join('') +
"
" +
""+
"
"+
"
"+
//"
Run in background tab:
"+
//"
The following options will check the returned content from a post-save action for a specific string and will show a success or failure icon on the tab. Only one value is required for the icon to be displayed.
"+
//"
Content match for success:
"+
//"
Content match for failure:
"+
"",
onShow: function(){
// On load set the fields to the current values
var p = getPostSave(ecfg.file);
if (p !== null) {
$(".ce_postsave_arg0").val(p[0]);
$(".ce_postsave_arg1").val(p[1]);
//$(".ce_postsave_background").val(p[2]);
//$(".ce_postsave_strmatch_good").val(p[3]);
//$(".ce_postsave_strmatch_bad").val(p[4]);
}
$(".ce_postsave_suggestion").on("click", function(){
var s = $(this).html();
var sparts = s.split(":");
$(".ce_postsave_arg0").val(sparts.shift());
$(".ce_postsave_arg1").val(sparts.join(":"));
});
},
actions: [{
onClick: function(){
var postsave = (JSON.parse(localStorage.getItem('ce_postsave')) || {});
var arg0 = $(".ce_postsave_arg0").val();
var arg1 = $(".ce_postsave_arg1").val();
//var background = $(".ce_postsave_background").val();
//var strmatch_good = $(".ce_postsave_strmatch_good").val();
//var strmatch_bad = $(".ce_postsave_strmatch_bad").val();
if (arg0 === "nothing") {
delete postsave[ecfg.file];
} else {
postsave[ecfg.file] = [arg0, arg1]; //, background, strmatch_good, strmatch_bad];
}
localStorage.setItem('ce_postsave', JSON.stringify(postsave));
$(".modal").modal('hide');
},
cssClass: 'btn-primary',
label: "Save"
},{
onClick: function(){ $(".modal").modal('hide'); },
label: "Cancel"
}]
});
}
// Read local storage to get any post-save action that exists for this file
function getPostSave(file) {
var parts;
var postsave = (JSON.parse(localStorage.getItem('ce_postsave')) || {});
if (postsave.hasOwnProperty(file)){
// Handle legacy format
if (typeof postsave[file] === "string") {
parts = postsave[file].split(":");
var action = parts.shift();
var args = replaceTokens(parts.join(":"), file);
return [action,args];
} else {
parts = postsave[file];
parts[1] = replaceTokens(parts[1], file);
return parts;
}
}
return null;
}
// Run button
function runShellCommand(contents, useSHOME) {
var history_idx = run_history.length,
in_progress_cmd = '',
$input,
cwd = "";
if (inFolder !== "."){
cwd = "
Enter a command to run on the server. Warning: Be careful with commands that do not exit as they will become orphaned processes. This does not have a timeout.
" +
cwd +
""+
"
",
onShow: function(){
$input = $('.ce_prompt_input');
if (contents) {
$input.val(contents);
}
// Provide a history of run commands
$input.focus().on('keydown', function(e) {
// on enter, submit form
if (e.which === 13) {
$('.modal').find('button:first-child').click();
} else if (e.which === 38) {// up arrow
if (history_idx === run_history.length) {
in_progress_cmd = $input.val();
}
history_idx--;
if (history_idx < 0) {history_idx = 0;}
$input.val(run_history[history_idx]);
} else if (e.which === 40) { // down arrow
if (history_idx === run_history.length) { return; }
history_idx++;
if (history_idx === run_history.length) {
$input.val(in_progress_cmd);
} else {
$input.val(run_history[history_idx]);
}
}
});
},
actions: [{
onClick: function(){
var runDir = $(".ce_run_cwd_radio_shome:checked").length;
$('.modal').one('hidden.bs.modal', function() {
var command = $input.val();
if (command) {
if (runDir) {
runShellCommandNow(command, "", false);
} else {
runShellCommandNow(command, inFolder, false);
}
}
}).modal('hide');
},
cssClass: 'btn-primary',
label: "Run"
},{
onClick: function(){ $(".modal").modal('hide'); },
label: "Cancel"
}]
});
}
function debugRefreshEndpointSelection() {
showModal({
title: "Debug/refresh endpoints",
size: 1000,
body: "
Select endpoint to debug/refresh. This list of endpoints is configured in Settings (config_explorer.conf) property: debug_refresh_endpoints
",
onShow: function(){
var $debugrefreshEndpoints = $(".ce-debugrefresh-endpoints");
var str ="";
for (var i = 0; i < conf._debug_refresh_endpoints.length; i++) {
var epoint = $.trim(conf._debug_refresh_endpoints[i]);
if (epoint) {
str += "" + htmlEncode(epoint) + "";
}
}
$debugrefreshEndpoints
.html(str)
.on("click","a",function(e){
e.preventDefault();
e.stopPropagation();
var $this = $(this);
$(".modal").modal('hide');
debugRefreshBumpHook( $this.text() );
});
},
actions: [{
onClick: function(){ $(".modal").modal('hide'); },
label: "Cancel"
}]
});
}
// Run button
function runReloadDeployServer() {
showModal({
title: "Reload deploy-server",
size: 1000,
body: "
",
size: 300
});
}
}).catch(function(){
closeTabByCfg(ecfg);
});
}
}
// Update and display the left pane in filesystem mode
function showTreePaneSpinner(direction) {
if (! leftpane_ignore) {
leftpane_ignore = direction || "none";
$ce_file_wrap.addClass("ce_move_" + leftpane_ignore);
$ce_spinner.clone().appendTo($ce_tree_pane);
$ce_tree_icons.find('i').addClass("ce_disabled");
}
}
function refreshFolder(){
readFolderFromServer(inFolder);
}
function getTreeCache(path, depth) {
var patharray = path.split("/");
if (typeof depth === "undefined") {
depth = patharray.length;
} else {
// make sure we arent attempting to go deeper than the length of the path
depth = Math.min(patharray.length, depth);
}
var base = filecache;
if (filecache === null) {
return base;
}
for (var i = 1; i < depth; i++) {
if (base.hasOwnProperty(patharray[i])) {
base = base[patharray[i]];
} else {
base = null;
break;
}
}
return base;
}
// Read the folder from the cache
function readFolder(path, direction) {
//path = path.replace(/\/{2,}/,"/");
path = path.replace(/(?:\/\.|\/)+$/g,"");
filterModeReset();
var base = getTreeCache(path);
if (base === null || ! base.hasOwnProperty(".")){
readFolderFromServer(path, direction);
} else {
folderContents = [];
for (var key in base) {
if (base.hasOwnProperty(key) && key !== ".") {
folderContents.push({
sortkey: key.toLocaleLowerCase(),
dir: true,
name: key
});
}
}
if (base.hasOwnProperty(".")) {
for (var j = 0; j < base["."].length; j++) {
folderContents.push({
sortkey: base["."][j].toLocaleLowerCase(),
dir: false,
name: base["."][j]
});
}
}
readFolderLoad(path);
// Even though we just loaded the folder contents from cache, we still request data from the server which will be updated once it is ready.
// this keeps the UI fast and makes sure that stuff hasnt changed after the cache was generated. It also allows for sorting by size/timestamp
readFolderFromServerWithoutSpinner(path, direction);
}
}
function readFolderFromServer(path, direction) {
showTreePaneSpinner(direction);
openingFolder = path;
return readFolderFromServerActual(path, direction);
}
function readFolderFromServerWithoutSpinner(path, direction) {
openingFolder = path;
return readFolderFromServerActual(path, direction);
}
function readFolderFromServerActual(path, direction) {
return serverAction({action: 'read', path: path}).then(function(contents){
// Make sure the user didnt navigate to another folder while waiting for the server query
if (openingFolder !== path) { return; }
var i;
// if filecache is null it means user turned it off by setting conf to -1
if (filecache !== null) {
var base = getTreeCache(path);
// base can be null if the screen was opened when we were already deep in the uncached zone
if (base !== null) {
var folders = ["."];
base["."] = [];
for (i = 0; i < contents.length; i++) {
if (!contents[i][0]) {
base["."].push(contents[i][1]);
} else {
folders.push(contents[i][1]);
if (! base.hasOwnProperty(contents[i][1])) {
base[contents[i][1]] = {};
}
}
}
// Go through and delete any keys that might no longer exist
for (var key in base) {
if (base.hasOwnProperty(key) && folders.indexOf(key) === -1) {
delete base[key];
}
}
}
}
folderContents = [];
for (i = 0; i < contents.length; i++) {
folderContents.push({
sortkey: contents[i][1].toLocaleLowerCase(),
dir: !!(contents[i][0]),
name: contents[i][1],
time: contents[i][2],
size: contents[i][3],
});
}
readFolderLoad(path);
}).catch(function(msg){
leftPaneRemoveSpinner();
$ce_file_wrap.empty();
$ce_file_path.empty();
$("
").attr("file", inFolder + "/" + item).appendTo($ce_file_wrap);
files++;
}
}
if (files===0) {
if (filter) {
$("
Not found: " + htmlEncode(filter) + "
").appendTo($ce_file_wrap);
} else {
$("
Folder empty
").appendTo($ce_file_wrap);
}
} else {
if (treeSearchFocus && $(".ce_leftnav_keyboard_selected").length == 0) {
$ce_file_wrap.children(".ce_leftnav").first().addClass("ce_leftnav_keyboard_selected");
}
}
$ce_file_path.find("span, bdi").attr("title", inFolder).text(inFolder + '/' + (files > 1 ? '\xa0\xa0\xa0\xa0\xa0' + files : ""));
filePathRTLCheck();
}
function leftPaneRestList(filter){
//leftPaneRemoveSpinner();
$ce_file_wrap.empty();
$ce_file_path.empty();
$ce_tree_icons.find('i').removeClass("ce_disabled");
var crumbs = "";
if (inFolderRestType === "") {
$(".ce_folder_up_rest").addClass("ce_disabled");
//leftPaneRestListGetTypes();
} else if (inFolderRestApp === "") {
$(".ce_folder_up_rest").addClass("ce_disabled");
crumbs = "."; //inFolderRestType;
leftPaneRestListGet("apps", filter);
} else {
crumbs = "./" + inFolderRestApp; //inFolderRestType +
leftPaneRestListGet(inFolderRestType, filter);
}
$("").appendTo($ce_file_path);
$ce_file_path.find("span, bdi").attr("title", crumbs).text(crumbs + '/');
filePathRTLCheck();
localStorage.setItem('ce_current_path_restapp', inFolderRestApp);
}
var restTypes = {
"views": {
endpoint: "/data/ui/views",
args: {
count:0,
f: "label",
search: "(isDashboard=1 AND (rootNode=\"dashboard\" OR rootNode=\"form\" OR rootNode=\"view\" OR rootNode=\"html\") AND isVisible=1)"
}
},
"apps": {
endpoint: "/services/apps/local",
},
};
function leftPaneRestListGet(type, filter) {
var label;
var files = false;
var i;
var filter_re;
if (filter) {
filter_re = new RegExp(escapeRegExp(filter), 'gi');
}
// if app's cache doesnt exist, do that
if (! restTypes.apps.hasOwnProperty("cache")) {
getRest(restTypes.apps.endpoint, {count:0, f:"label"}).then(function(restData) {
//console.log(restData);
restTypes.apps.cache = {};
for (var i = 0; i < restData.length; i++) {
restTypes.apps.cache[restData[i].name] = restData[i].content.label;
}
// try again
leftPaneRestListGet(type, filter);
}).catch(function(){
showModal({
title: "Warning",
body: "
Could not get rest data for apps
",
size: 300
});
});
// if we dont have a cached copy of the data we need e.g. views, then get it now
} else if (! restTypes[inFolderRestType].hasOwnProperty("cache") ) {
getRest(restTypes[inFolderRestType].endpoint, restTypes[inFolderRestType].args).then(function(restData) {
//console.log(restData);
restTypes[inFolderRestType].cache = [];
for (var i = 0; i < restData.length; i++) {
var item = {
date: new Date(restData[i].updated).valueOf(),
link: restData[i].links.alternate,
app: restData[i].acl.app,
label: restData[i].content.hasOwnProperty("label") ? restData[i].content.label : "",
name: restData[i].name,
};
restTypes[inFolderRestType].cache.push(item);
}
// try again
leftPaneRestListGet(type, filter);
}).catch(function(){
showModal({
title: "Warning",
body: "
Could not get rest data for " + htmlEncode(inFolderRestType) + "
",
size: 300
});
});
// we have the apps list and the data list
} else {
//console.log("using " + inFolderRestType + " cache", restTypes[inFolderRestType].cache);
if (type === "apps") {
var appList = {};
var appListArray = [];
for (i = 0; i < restTypes[inFolderRestType].cache.length; i++) {
var app = restTypes[inFolderRestType].cache[i].app;
if (! appList.hasOwnProperty(app)) {
appList[app] = {
count:0,
latest:0,
app: app,
label_orig: restTypes.apps.cache[app]
};
appListArray.push(appList[app]);
if (! restTypes.apps.cache[app] || displayModeRest==="n") {
appList[app].label = htmlEncode(app);
} else if (displayModeRest==="l") {
appList[app].label = htmlEncode(restTypes.apps.cache[app]);
} else if (displayModeRest==="ln") {
appList[app].label = htmlEncode(restTypes.apps.cache[app]) + " (" + htmlEncode(app) + ")";
} else { //nl
appList[app].label = htmlEncode(app) + " (" + htmlEncode(restTypes.apps.cache[app]) + ")";
}
}
appList[app].count++;
if (appList[app].latest < restTypes[inFolderRestType].cache[i].date) {
appList[app].latest = restTypes[inFolderRestType].cache[i].date;
}
}
// sort apps
appListArray.sort(function(a, b) {
if (sortModeRest==="label"){
return sortAscRest==="1" ? a.label.localeCompare(b.label) : b.label.localeCompare(a.label);
} else if ( sortModeRest==="time"){
return sortAscRest==="1" ? a.latest - b.latest : b.latest - a.latest;
}
});
for (i = 0; i < appListArray.length; i++) {
if (! filter || appListArray[i].app.toLowerCase().indexOf(filter) > -1 || appListArray[i].label_orig.toLowerCase().indexOf(filter) > -1) {
files = true;
if (filter) {
appListArray[i].label = appListArray[i].label.replace(filter_re, "$&");
}
$("
").attr("restfile", restTypes[type].cache[i].link).appendTo($ce_file_wrap);
}
}
}
}
if (!files) {
$("
Nothing found
").appendTo($ce_file_wrap);
} else {
if (treeSearchFocus && $(".ce_leftnav_keyboard_selected").length == 0) {
$ce_file_wrap.children(".ce_leftnav").first().addClass("ce_leftnav_keyboard_selected");
}
}
}
}
// Handle clicking a rest link in the left pane, to open it in the right pane
function readFileRest(path){
var label = preferences.ce_fullPathTab ? "" + dodgyRemoveRelPath(dodgyDirname(path)) + "" + dodgyBasename(path) : dodgyBasename(path);
var type = "rest";
if (! tabAlreadyOpen(type, path)) {
var ecfg = createTab(type, path, label);
getRest(path, null).then(function(restData) {
//console.log(restData); //?output_mode=json&count=0&f=title&f=label
if (restData.length === 0) {
showModal({
title: "Warning",
body: "
Nothing returned from REST API at: " + htmlEncode(path) + "
Unexpected error getting data from Splunk REST api: " + htmlEncode(path) + "
",
size: 300
});
closeTabByCfg(ecfg);
});
}
}
// The conf file list
function leftPaneConfList(filter) {
$ce_file_wrap.empty();
$ce_file_path.removeClass('ce_rtl').empty();
var filter_re;
var files = false;
if (filter) {
filter_re = new RegExp(escapeRegExp(filter), 'gi');
}
$("Splunk conf files").appendTo($ce_file_path);
for (var i = 0; i < confFilesSorted.length; i++) {
if (! filter) {
$("").text(confFilesSorted[i]).attr("file", confFilesSorted[i]).prepend(" ").appendTo($ce_file_wrap);
files = true;
} else if (confFilesSorted[i].toLowerCase().indexOf(filter) > -1) {
var text = htmlEncode(confFilesSorted[i]);
text = text.replace(filter_re, "$&");
$("").html(text).attr("file", confFilesSorted[i]).prepend(" ").appendTo($ce_file_wrap);
files = true;
}
}
if (!files){
$("
Not found: " + htmlEncode(filter) + "
").appendTo($ce_file_wrap);
} else {
if (treeSearchFocus && $(".ce_leftnav_keyboard_selected").length == 0) {
$ce_file_wrap.children(".ce_leftnav").first().addClass("ce_leftnav_keyboard_selected");
}
}
}
// Click handler for Recent Files button in top right
// TODO [low] can we enhance this so it shows files by how often you use them?
function leftPaneRecentList(filter) {
$ce_file_wrap.empty();
$ce_file_path.removeClass('ce_rtl').empty();
var filter_re;
if (filter) {
filter_re = new RegExp(escapeRegExp(filter), 'gi');
}
$("Recent files").appendTo($ce_file_path);
var counter = 0;
var openlabels = [];
for (var j = 0; j < editors.length; j++) {
openlabels.push(editors[j].label);
}
for (var i = closed_tabs.length - 1; i >= 0 ; i--) {
if (counter > max_recent_files_show) {
break;
}
// hide item if they are actually open at the moment
if (openlabels.indexOf(closed_tabs[i].label) === -1) {
var text = closed_tabs[i].label.replace(/^read:\s/,"");
if (! filter || text.toLowerCase().indexOf(filter) > -1) {
text = htmlEncode(text);
if (filter) {
text = text.replace(filter_re, "$&");
}
var icon = "report";
if (closed_tabs[i].type !== "read") {
icon = "bulb";
}
counter++;
$("
" + text + "
").attr("file", closed_tabs[i].file).attr("title", closed_tabs[i].file).attr("type", closed_tabs[i].type).appendTo($ce_file_wrap);
}
}
}
if (counter === 0) {
if (filter) {
$("
Not found: " + htmlEncode(filter) + "
").appendTo($ce_file_wrap);
} else {
$("
Nothing here
").appendTo($ce_file_wrap);
}
} else {
if (treeSearchFocus && $(".ce_leftnav_keyboard_selected").length == 0) {
$ce_file_wrap.children(".ce_leftnav").first().addClass("ce_leftnav_keyboard_selected");
}
}
}
// Handle clicking an file or folder in the left pane
function readFile(path, customErrorCB){
var label = preferences.ce_fullPathTab ? "" + dodgyRemoveRelPath(dodgyDirname(path)) + "" + dodgyBasename(path) : dodgyBasename(path);
var type = "read";
if (path === "") {
label = "Settings";
type = "settings";
}
if (! tabAlreadyOpen(type, path)) {
var ecfg = createTab(type, path, label);
serverActionWithoutFlicker({action: 'read', path: path}, customErrorCB).then(function(contents){
updateTabAsEditor(ecfg, contents);
if (ecfg.hasOwnProperty('matchedConf')) {
highlightBadConfig(ecfg);
if (confFiles.hasOwnProperty(ecfg.matchedConf)) {
serverAction({action: 'spec-hinting', path: ecfg.matchedConf}).then(function(c){
ecfg.hinting = buildHintingLookup(ecfg.matchedConf, c);
});
}
}
}).catch(function(){
closeTabByCfg(ecfg);
});
}
}
function fileSystemUpload(parentPath){
showModal({
title: "Create file, create folder or upload a file",
size: 580,
body: "
"+
"
New file name:
"+
"
New folder name:
"+
"
Select upload file:
"+
"
",
onShow: function(){
$('.ce_prompt_input_file').focus().on('keydown', function(e) {
// submit form on enter key
if (e.which === 13) {
$('.ce_prompt_btn_file').click();
}
});
$('.ce_prompt_btn_file').on("click", function(){
$('.modal').one('hidden.bs.modal', function() {
var fname = $('.ce_prompt_input_file').val();
if (fname) {
showTreePaneSpinner();
serverAction({action: 'newfile', path: parentPath, param1: fname}).then(function(){
refreshFolder();
showToast('Success');
}).catch(function(){
refreshFolder();
});
}
}).modal('hide');
});
$('.ce_prompt_input_folder').on('keydown', function(e) {
// submit form on enter key
if (e.which === 13) {
$('.ce_prompt_btn_folder').click();
}
});
$('.ce_prompt_btn_folder').on("click", function(){
$('.modal').one('hidden.bs.modal', function() {
var fname = $('.ce_prompt_input_folder').val();
if (fname) {
showTreePaneSpinner();
serverAction({action: 'newfolder', path: parentPath, param1: fname}).then(function(){
refreshFolder();
showToast('Success');
}).catch(function(){
refreshFolder();
});
}
}).modal('hide');
});
$('.ce_prompt_btn_upload').on("click", function(){
$('.modal').one('hidden.bs.modal', function() {
var file = $('.ce_file_upload_input')[0].files[0];
var reader = new FileReader();
var extract = "";
var ecfg;
if ($('.ce_file_upload_extract:checked').length) {
extract = "e";
ecfg = createTab('run', "", 'Extracted files: ' + htmlEncode(file.name));
}
reader.onloadend = function() {
var upFileB64 = reader.result;
showTreePaneSpinner();
serverAction({action: 'fileupload' + extract, path: parentPath, param1: file.name, file: upFileB64}).then(function(contents){
if (ecfg) {
updateTabAsEditor(ecfg, contents, "plaintext");
} else {
showToast('Success');
}
refreshFolder();
}).catch(function(){
if (ecfg) {
closeTabByCfg(ecfg);
}
refreshFolder();
});
};
reader.readAsDataURL(file);
}).modal('hide');
});
},
actions: []
});
}
// Rename a file or folder with a prompt window
function fileSystemRename(parentPath) {
if (fileIsOpenAndHasChanges(parentPath)) { return; }
var bn = dodgyBasename(parentPath);
showModal({
title: "Rename",
size: 400,
body: "
Enter new name for " + bn + "
",
onShow: function(){
$('.ce_prompt_input').focus().on('keydown', function(e) {
// submit form on enter key
if (e.which === 13) {
$('.modal').find('button:first-child').click();
}
});
},
actions: [{
onClick: function(){
$('.modal').one('hidden.bs.modal', function() {
var newname = $('.ce_prompt_input').val();
if (newname && newname !== bn) {
showTreePaneSpinner();
serverAction({action: 'rename', path: parentPath, param1: newname}).then(function(){
refreshFolder();
showToast('Success');
// if "path" is open in an editor, it needs to be closed without warning
closeTabByName("read", parentPath);
}).catch(function(){
refreshFolder();
});
}
}).modal('hide');
},
cssClass: 'btn-primary',
label: "Rename"
},{
onClick: function(){ $(".modal").modal('hide'); },
label: "Cancel"
}]
});
}
// Delete a file or folder with a popup windows
function filesystemDelete(file) {
if (fileIsOpenAndHasChanges(file)) { return; }
showModal({
title: "Delete",
size: 550,
body: "
Are you sure you want to delete: " + file + "
To confirm type 'yes':
",
onShow: function(){
$('.ce_prompt_input').focus().on("keyup blur", function(){
if ($('.ce_prompt_input').val().toLowerCase() === "yes") {
$('.modal').find(".btn-danger").removeClass('btn-disabled');
} else {
$('.modal').find(".btn-danger").addClass('btn-disabled');
}
}).on('keydown', function(e) {
if (e.which === 13) {
$('.modal').find('button:first-child').click();
}
});
$('.modal').find('.ce_type_yes').on("click",function(){
$('.ce_prompt_input').val("yes").blur();
});
},
actions: [{
onClick: function(){
if ($('.ce_prompt_input').val().toLowerCase() !== "yes") {
return;
}
$('.modal').one('hidden.bs.modal', function() {
showTreePaneSpinner();
serverAction({action: 'delete', path: file}).then(function(){
refreshFolder();
showToast('Success');
// if "path" is open in an editor, it needs to be closed without warning
closeTabByName("read", file);
}).catch(function(){
refreshFolder();
});
}).modal('hide');
},
cssClass: 'btn-danger btn-disabled',
label: "Delete"
},{
onClick: function(){ $(".modal").modal('hide'); },
label: "Cancel"
}]
});
}
function showChangeLog() {
var ecfg = createTab('change-log', "", "Change log");
serverActionWithoutFlicker({action: 'git-log', path: inFolder}).then(function(contents){
updateTabAsEditor(ecfg, contents, "git-log");
}).catch(function(){
closeTabByCfg(ecfg);
});
}
// Git history of a specific file optionally between two commit tags
function getFileHistory(file, folder){
var ecfg = createTab('history', file, "history: " + dodgyBasename(file));
serverActionWithoutFlicker({action: 'git-history', path: file, param1: folder}).then(function(contents){
contents = $.trim(contents);
if (! contents) {
showModal({
title: "Warning",
body: "
No change history found for:
" + htmlEncode(file) + "
",
size: 400
});
closeTabByCfg(ecfg);
return;
}
updateTabAsEditor(ecfg, contents, "git-diff");
}).catch(function(){
closeTabByCfg(ecfg);
});
}
function activateTab(idx){
if (idx < -1 || idx > (editors.length - 1) || (activeTab === idx && activeTab > -1)) {
return;
}
$ce_contents.children().addClass("ce_hidden");
$ce_contents_home.addClass("ce_hidden");
$ce_tabs.children().removeClass("ce_active");
$ce_home_tab.removeClass("ce_active");
activeTab = idx;
if (idx !== -1) {
editors[idx].tab.addClass('ce_active');
editors[idx].tab_container.removeClass('ce_hidden');
editors[idx].last_opened = Date.now();
} else {
$ce_home_tab.addClass('ce_active');
$ce_contents_home.removeClass('ce_hidden');
}
doPipeTabSeperators();
updateUrlHash();
if (editors[idx] && editors[idx].hasOwnProperty("editor")) {
editors[idx].editor.focus();
}
// switching tabs. check for changes
if (idx > -1) {
clearTimeout(fileModsCheckTimer);
fileModsCheckTimer = setTimeout(function(){ checkFileMods(); }, 1000);
}
}
// The pipe seperators are between active tabs but not on the currently active tab or the one to its left.
function doPipeTabSeperators(){
$(".ce_pipe, .ce_pipe_left").remove();
for (var i = 0; i < editors.length; i++) {
if ((activeTab - 1) !== i && activeTab !== i) {
editors[i].tab.append('');
}
}
if (activeTab >= 0) {
$ce_home_tab.append('');
if (activeTab > 0) {
$ce_home_tab.append('');
}
}
}
// Check if tab is open with unsaved changes
function fileIsOpenAndHasChanges(file) {
for (var i = 0; i < editors.length; i++) {
if (editors[i].file === file) {
if (editors[i].hasChanges) {
showModal({
title: "Warning",
body: "
Cannot rename or delete file becuase it is currently open with unsaved changes.
",
size: 350
});
return true;
}
}
}
return false;
}
// A tab was opened but there was nothing to put in it, so it is closed.
function closeTabByCfg(ecfg) {
for (var i = 0; i < editors.length; i++) {
if (editors[i].id === ecfg.id) {
closeTabNow(i);
return;
}
}
}
function closeTabByName(type, file) {
for (var i = 0; i < editors.length; i++) {
if (editors[i].file === file && editors[i].type === type) {
closeTabNow(i);
return;
}
}
}
function closeTabByHookDetails(arg0, arg1) {
//if (preferences.ce_reuseWindow) {
if (arg0 === "bump") {
closeTabByName("refresh", "bump");
} else if (arg0 === "run-safe") {
closeTabByName("run", arg1);
} else {
closeTabByName(arg0, arg1);
}
//}
}
// Check if tab is already open, and if so, active it instead.
function tabAlreadyOpen(type, file) {
// check if file is already open
for (var i = 0; i < editors.length; i++) {
if (editors[i].type === type && editors[i].file === file) {
activateTab(i);
return true;
}
}
return false;
}
function closeTabWithConfirmation(idx){
if (editors[idx].hasChanges) {
showModal({
title: "Unsaved changes",
body: "
Discard unsaved changes?
",
size: 300,
actions: [{
onClick: function(){
$(".modal").modal('hide');
closeTabNow(idx);
},
cssClass: 'btn-danger',
label: "Discard"
},{
onClick: function(){ $(".modal").modal('hide'); },
label: "Cancel"
}]
});
} else {
closeTabNow(idx);
}
}
// when tabs are closed, remember in local storage for recent files list
function logClosedTab(ecfg){
// make sure unique items only appear once
var splicy = -1;
for (var j = 0; j < closed_tabs.length; j++) {
if (ecfg.label === closed_tabs[j].label) {
splicy = j;
}
}
if (splicy > -1){
closed_tabs.splice(splicy, 1);
}
if (tabCfg.hasOwnProperty(ecfg.type) && tabCfg[ecfg.type].can_reopen) {
closed_tabs.push({label: ecfg.label, type: ecfg.type, file: ecfg.file});
}
// trim length
// There is a buffer of 10 so we can have things to show if the tabs are opened and thus removed from the list
if (closed_tabs.length > (max_recent_files_show + 10)) {
closed_tabs.shift();
}
//persist to localstorage
localStorage.setItem('ce_closed_tabs', JSON.stringify(closed_tabs));
}
function closeTabNow(idx) {
logClosedTab(editors[idx]);
if (editors[idx].hasOwnProperty("editor")) {
editors[idx].editor.dispose();
}
if (editors[idx].hasOwnProperty("model")) {
editors[idx].model.dispose();
}
if (editors[idx].hasOwnProperty("sidecar_editor")) {
editors[idx].sidecar_editor.dispose();
}
if (editors[idx].hasOwnProperty("sidecar_model")) {
editors[idx].sidecar_model.dispose();
}
editors[idx].tab.remove();
editors[idx].tab_container.remove();
editors.splice(idx, 1);
openTabsListChanged();
// if there are still tabs open, find the most recently used tab and activate that one
if ($ce_tabs.children().length === 0) {
activateTab(-1);
// if there is already a tab selected
} else if ($ce_tabs.children(".ce_active").length === 0) {
var last_used_idx,
newest;
for (var i = 0; i < editors.length; i++) {
if (! newest || newest < editors[i].last_opened) {
newest = editors[i].last_opened;
last_used_idx = i;
}
}
activateTab(last_used_idx);
}
}
function createTabSidecar(label) {
var ecfg = editors[activeTab];
if (! ecfg.sidecar_container) {
ecfg.sidecar_container = $("").appendTo(ecfg.tab_container);
$("").appendTo(ecfg.sidecar_container)
.on("click", function(){
if (ecfg.hasOwnProperty("sidecar_editor")) {
ecfg.sidecar_editor.dispose();
}
if (ecfg.hasOwnProperty("sidecar_model")) {
ecfg.sidecar_model.dispose();
}
ecfg.sidecar_container.remove();
ecfg.sidecar_container = null;
});
$("").appendTo(ecfg.sidecar_container)
.on("click", function(){
if(ecfg.sidecar_container.hasClass("ce_editor_sidecar_autohide_on")) {
ecfg.sidecar_container.removeClass("ce_editor_sidecar_autohide_on");
} else {
ecfg.sidecar_container.addClass("ce_editor_sidecar_autohide_on");
}
});
ecfg.sidecar_title = $("").appendTo(ecfg.sidecar_container).on("click", function(){
//console.log("ecfg.sidecar_container.css.transform=", ecfg.sidecar_container.css("transform"));
// if the sidecar is collapsed
//if (ecfg.sidecar_container.css("transform") === "matrix(1, 0, 0, 1, 0, 467)") {
// if sidecar is expanded
//} else
if (ecfg.sidecar_container.css("transform") === "" || ecfg.sidecar_container.css("transform") === "none" || ecfg.sidecar_container.css("transform") === "matrix(1, 0, 0, 1, 0, 0)"){
ecfg.sidecar_container.css({transform: "translateY(" + (ecfg.sidecar_container.height() - 35) + "px)"});
} else {
ecfg.sidecar_container.css({transform: "translateY(0)"});
}
});
ecfg.sidecar_resizer = $("").appendTo(ecfg.sidecar_container);
ecfg.sidecar_autohide = $("").appendTo(ecfg.sidecar_container);
ecfg.sidecar_editor_container = $("").appendTo(ecfg.sidecar_container);
// // Handler for resizing the tree pane/editor divider
ecfg.sidecar_resizer.on("mousedown", function(e) {
e.preventDefault();
var was_autohide_on = !! ecfg.sidecar_container.hasClass("ce_editor_sidecar_autohide_on");
ecfg.sidecar_container.removeClass("ce_editor_sidecar_autohide_on");
$(document).one("mouseup",function(e) {
if (was_autohide_on) {
ecfg.sidecar_container.addClass("ce_editor_sidecar_autohide_on");
}
$(document).off('mousemove.sidecar_resize');
});
$(document).on("mousemove.sidecar_resize", function(e) {
ecfg.sidecar_container.css({
"transform": "translateY(0)",
"width": Math.min($ce_contents.width()-158, Math.max(360, (window.innerWidth - 150 - e.pageX))) + "px",
"height": Math.min($ce_contents.height(), Math.max(190, (window.innerHeight - e.pageY))) + "px"
});
});
});
}
ecfg.sidecar_title.html(label);
ecfg.sidecar_editor_container.empty().append($ce_spinner.clone());
setTimeout(function(){
if (ecfg.sidecar_container.hasClass("ce_editor_sidecar_autohide_on")) {
ecfg.sidecar_container.css({transform: "translateY(0)", opacity: "1"});
}
}, 100);
ecfg.sidecar_autohide.css({"border-left-width":"1px"});
return ecfg;
}
function updateSidecarAsEditor(ecfg, contents) {
ecfg.sidecar_editor_container.empty();
// The monaco URL must be unique or it will silently close. We use the same file when running different versions of btool and in other circumstances so need to prefix with a unique id.
var url = "T" + (tabCreationCount++) + "/" + ecfg.file;
ecfg.sidecar_model = monaco.editor.createModel(contents, "plaintext", monaco.Uri.file(url));
var options = {
automaticLayout: true,
model: ecfg.sidecar_model,
minimap: {
enabled: false
},
lineNumbers: 'off',
wordWrap: 'on',
folding: false,
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
glyphMargin: false,
hover: { delay: 700 }
};
ecfg.sidecar_editor = monaco.editor.create(ecfg.sidecar_editor_container[0], options);
//ecfg.server_content = ecfg.editor.getValue();
var autohideTimeout = setTimeout(function(){
if (ecfg.sidecar_container.hasClass("ce_editor_sidecar_autohide_on")) {
ecfg.sidecar_container.css({transform: "translateY(" + (ecfg.sidecar_container.height() - 35) + "px)"});
}
ecfg.sidecar_autohide.css({"border-left-color":"transparent", "border-left-width":"1px"});
},5000);
setTimeout(function(){
ecfg.sidecar_autohide.css({"border-left-width":(ecfg.sidecar_container.width() - 4) + "px", "border-left-color":"rgba(255,255,255,0.5)"});
}, 10);
if (ecfg.sidecar_container.hasClass("ce_editor_sidecar_autohide_on")) {
ecfg.sidecar_container.css({transform: "translateY(0)"});
}
ecfg.sidecar_editor.onMouseDown(function(e) {
clearTimeout(autohideTimeout);
ecfg.sidecar_autohide.css({"border-left-color":"transparent", "border-left-width":"1px"});
});
}
function createTab(type, file, label){
var ecfg = {
type: type,
file: file,
label: type + ": " + file,
id: tabid++
};
editors.push(ecfg);
ecfg.tab_container = $("").appendTo($ce_contents);
ecfg.editor_container = $("").appendTo(ecfg.tab_container);
ecfg.editor_container.append($ce_spinner.clone());
// Remove the "restore session" link
$(".ce_restore_session").remove();
ecfg.tab = $("
" + label + "
").attr("title", ecfg.label).data({"tab": ecfg}).appendTo($ce_tabs);
ecfg.hasChanges = false;
ecfg.server_content = '';
activateTab(editors.length-1);
openTabsListChanged();
return ecfg;
}
function addHookActionToEditor(hook, ecfg) {
if (hook._match.test(ecfg.file) && hook.matchtype == "file" && ! (hook.hasOwnProperty("showInPane") && hook.showInPane === "tree")) {
var lab = replaceTokens(hook.label, ecfg.file);
if (isTrueValue(hook.showWithSave) && ecfg.canBeSavedFile) {
ecfg.editor.addAction({
id: "Save and " + lab,
contextMenuOrder: 0.2,
contextMenuGroupId: 'navigation',
label: "Save and " + lab,
run: function() {
saveActiveTab(function(){
runAction(hook.action, ecfg.file, true);
});
}
});
}
ecfg.editor.addAction({
id: lab,
contextMenuOrder: 0.3,
contextMenuGroupId: 'navigation',
label: lab,
run: function() {
runAction(hook.action, ecfg.file, true);
}
});
}
}
function updateTabAsEditor(ecfg, contents, language) {
// uses the built-in language detection where possible
if (! language) {
if (/\.(?:conf|meta|spec)/.test(ecfg.file)) {
language = "ini";
}
}
var re = /([^\/\\]+).conf$/;
var found = ecfg.file.match(re);
ecfg.canBeSavedFile = (ecfg.type === "read" || ecfg.type === "settings");
ecfg.canBeSavedRest = (ecfg.type === "rest");
if (found && ecfg.canBeSavedFile && found[1] !== 'app') {
ecfg.matchedConf = found[1];
} else if (ecfg.type === "settings"){
ecfg.matchedConf = "config_explorer";
}
if (ecfg.canBeSavedFile || ecfg.canBeSavedRest) {
// Start the process of checking filemodtimes
// A filemodcheck might be about to occur, but delay it another 100ms in case its the first load a bunch of tabs are opening at once.
clearTimeout(fileModsCheckTimer);
fileModsCheckTimer = setTimeout(function(){ checkFileMods(); }, 100);
}
ecfg.saving = false;
ecfg.decorations = [];
ecfg.editor_container.empty();
// The monaco URL must be unique or it will silently close. We use the same file when running different versions of btool and in other circumstances so need to prefix with a unique id.
var url = "T" + (tabCreationCount++) + "/" + ecfg.file;
ecfg.model = monaco.editor.createModel(contents, language, monaco.Uri.file(url));
// Default things to be ini syntax highlighting rather than none
if (ecfg.model.getModeId() === "plaintext" && ! language) {
monaco.editor.setModelLanguage(ecfg.model, "ini");
}
var options = $.extend({}, preferences, {
automaticLayout: true,
model: ecfg.model,
lineNumbersMinChars: 3,
ariaLabel: ecfg.file,
//readOnly: ! ecfg.canBeSavedFile,
glyphMargin: true,
hover: { delay: 700 }
});
ecfg.editor = monaco.editor.create(ecfg.editor_container[0], options);
ecfg.server_content = ecfg.editor.getValue();
// check if we need to scroll to a particular location and highlight a line
if (doLineHighlight !== "") {
var line_nums = doLineHighlight.split(",");
//console.log(doLineHighlight, line_nums);
if (line_nums.length > 1) {
ecfg.editor.setSelection(new monaco.Selection(Number(line_nums[0]),1,(Number(line_nums[1]) + 1),1));
ecfg.editor.revealLineInCenter(Number(line_nums[0]), 0);
ecfg.editor.focus();
}
doLineHighlight = "";
}
if (ecfg.canBeSavedFile || ecfg.canBeSavedRest) {
ecfg.editor.onDidChangeModelContent(function() {
// check against saved copy
if (ecfg.editor.getValue() !== ecfg.server_content) {
if (!ecfg.hasChanges) {
ecfg.tab.append("");
ecfg.hasChanges = true;
}
} else {
if (ecfg.hasChanges) {
ecfg.tab.find('.icon-alert-circle').remove();
ecfg.hasChanges = false;
}
}
// Turn off the glyphs until next save
ecfg.decorations = ecfg.editor.deltaDecorations(ecfg.decorations, []);
});
}
ecfg.editor.addAction({
id: 'prev-tab',
label: 'Switch tab to left of current',
keybindings: [monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.LeftArrow],
run: function() {
if (editors.length > 1) {
activateTab((activeTab === 0) ? editors.length - 1 : activeTab - 1);
}
return null;
}
});
ecfg.editor.addAction({
id: 'next-tab',
label: 'Switch tab to right of current',
keybindings: [monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.RightArrow],
run: function() {
if (editors.length > 1) {
activateTab((activeTab === editors.length - 1) ? 0 : activeTab + 1);
}
return null;
}
});
ecfg.editor.addAction({
id: 'last-used-tab',
label: 'Switch tab to last active',
keybindings: [monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.UpArrow],
run: function() {
var last_used_idx,
newest;
if (editors.length > 1) {
for (var i = 0; i < editors.length; i++){
if (activeTab !== i) {
if (! newest || newest < editors[i].last_opened) {
newest = editors[i].last_opened;
last_used_idx = i;
}
}
}
activateTab(last_used_idx);
}
return null;
}
});
ecfg.editor.addAction({
id: 'close-tab',
label: 'Close tab',
keybindings: [monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.KEY_W],
run: function() {
closeTabByCfg(ecfg);
return null;
}
});
if (ecfg.canBeSavedFile || ecfg.canBeSavedRest) {
ecfg.editor.addAction({
id: 'save-file',
contextMenuOrder: 0.1,
contextMenuGroupId: 'navigation',
label: 'Save file',
run: function() {
saveActiveTab();
}
});
}
if (ecfg.canBeSavedFile) {
ecfg.editor.addAction({
id: 'save-file-action',
contextMenuOrder: 1,
contextMenuGroupId: '99_prefs',
label: 'Set post-save action',
run: function() {
setPostSaveAction();
}
});
}
if (ecfg.canBeSavedFile) {
ecfg.editor.addAction({
id: 'link-to-highlight',
contextMenuOrder: 3,
contextMenuGroupId: '99_prefs',
label: 'Create link to line/selection',
run: function() {
var editor_selection = ecfg.editor.getSelection();
var hashparts = window.location.href.replace(/#.*/,"") + "#0|" + inFolder + "|" + ecfg.type + "|" + ecfg.file + "@" + editor_selection.startLineNumber + "," + editor_selection.endLineNumber;
console.log(hashparts);
copyTextToClipboard(hashparts);
}
});
}
if (ecfg.type !== "rest") {
ecfg.editor.addAction({
id: 'attempt-open-file',
contextMenuOrder: 0.4,
contextMenuGroupId: 'navigation',
label: 'Attempt open',
run: function(ed) {
var position = ed.getPosition();
var text = ed.getValue(position);
var splitedText=text.split("\n");
var line = splitedText[position.lineNumber-1];
var replace = "(.{" + position.column + "}[^\\s\'\":]+).*";
var re = new RegExp(replace,"g");
var filename_string = line.replace(re, "$1");
filename_string = filename_string.replace(/.*[\s\"\']/,"");
if (filename_string.length <= 3) {
showModal({
title: "Warning",
body: "
Use the 'Attempt Open' menu option on editor text for a file/folder path
",
size: 350
});
return;
}
var proposedPaths = {};
// we check a few different ways of finding a legitimate path from the highlighted text
proposedPaths[dodgyRemoveRelPath(filename_string)] = 2;
// when looking in etc/ it needs to be 3 folders deep
proposedPaths["etc/" + dodgyRemoveRelPath(filename_string)] = 3;
// if its a "run" editor tab, then the fromFolder will be set.
if (ecfg.hasOwnProperty("fromFolder")) {
proposedPaths[dodgyRemoveRelPath(ecfg.fromFolder + "/" + filename_string)] = 2;
} else {
proposedPaths[dodgyRemoveRelPath(dodgyDirname(ecfg.file) + filename_string)] = 2;
}
var success = false;
var errorFunction = function(){
showModal({
title: "Warning",
body: "
Unable to find file or folder path to open: " + htmlEncode(filename_string) + "
(Looked in $SPLUNK_HOME, $SPLUNK_HOME/etc and relative to current file)
",
size: 450
});
};
for (var proposedPath in proposedPaths) {
if (proposedPaths.hasOwnProperty(proposedPath)) {
//console.log("attempting path ==> " + proposedPath);
if (filecache !== null) {
// we only check if the first 2 parts of the path are legit. If the path is less than two folders deep, then it wont open
var base = getTreeCache("./" + proposedPath, 1 + proposedPaths[proposedPath]);
//console.log("checking file cache for path ", "./" + proposedPath, ":", filecache, base);
if (base !== null) {
// this doesnt handle the case where its a bare folder with no trailing slash, but its good enough
if (proposedPath.slice(-1) === "/") {
readFolder("./" + proposedPath);
} else {
readFile("./" + proposedPath, errorFunction);
}
success = true;
break;
}
}
}
}
if (!success) {
errorFunction();
}
}
});
}
ecfg.editor.addAction({
id: 'open-prefs-action',
contextMenuOrder: 2,
contextMenuGroupId: '99_prefs',
label: 'Preferences',
run: function() {
openPreferences();
}
});
if (ecfg.type === "settings") {
ecfg.editor.addAction({
id: "settings_spec",
contextMenuOrder: 0.3,
contextMenuGroupId: 'navigation',
label: "Open documentation (.spec file)",
run: function() {
runAction("spec:config_explorer.conf", "", true);
}
});
ecfg.editor.addAction({
id: "settings_examples",
contextMenuOrder: 0.3,
contextMenuGroupId: 'navigation',
label: "Show default config",
run: function() {
runAction("read:./etc/apps/config_explorer/default/config_explorer.conf", "", true);
}
});
}
if (ecfg.type === "read") {
for (var j = 0; j < hooksActive.length; j++) {
var hook = hooksActive[j];
addHookActionToEditor(hook, ecfg);
}
}
if (ecfg.type === "read") {
ecfg.editor.addAction({
id: 'reload',
contextMenuOrder: 0.2,
contextMenuGroupId: 'navigation',
label: 'Reload from disk',
run: function() {
closeTabByCfg(ecfg);
hooksCfg[ecfg.type](ecfg.file, ecfg.fromFolder);
}
});
ecfg.editor.addAction({
id: 'editorchanges',
contextMenuOrder: 0.3,
contextMenuGroupId: 'navigation',
label: 'Diff unsaved changes',
run: function() {
openDiffOfUnsavedChanges(ecfg);
}
});
// If this is an XML file, provide an option to try opening it
// warning: There are plenty of situations where this wont work, becuase of permissions layering etc
if (confIsTrue('dashboard_xml_file_experimental_actions', false)) {
var file_parts_for_openurl = ecfg.file.match(regex_file_path_to_dashboard_parts);
if (file_parts_for_openurl) {
ecfg.editor.addAction({
id: 'opendashboard',
contextMenuOrder: 0.31,
contextMenuGroupId: 'navigation',
label: 'Attempt view in browser',
run: function() {
hooksCfg.openurl("/app/" + file_parts_for_openurl[2] + "/" + file_parts_for_openurl[3]);
}
});
ecfg.editor.addAction({
id: 'editrestdashboard',
contextMenuOrder: 0.32,
contextMenuGroupId: 'navigation',
label: 'Attempt edit via REST API',
run: function() {
if (file_parts_for_openurl[1]=="apps") {
hooksCfg.rest("/servicesNS/nobody/" + file_parts_for_openurl[2] + "/data/ui/views/" + file_parts_for_openurl[3]);
} else {
hooksCfg.rest("/servicesNS/" + file_parts_for_openurl[1].substr(6) + "/" + file_parts_for_openurl[2] + "/data/ui/views/" + file_parts_for_openurl[3]);
}
}
});
}
}
}
if (ecfg.type === "rest") {
// reload rest
ecfg.editor.addAction({
id: 'reload',
contextMenuOrder: 0.2,
contextMenuGroupId: 'navigation',
label: 'Reload from REST API',
run: function() {
closeTabByCfg(ecfg);
hooksCfg.rest(ecfg.file);
}
});
var file_parts_for_openurl_rest = ecfg.file.match(/([^\/]+)\/data\/ui\/views\/(.*)$/);
if (file_parts_for_openurl_rest) {
ecfg.editor.addAction({
id: 'opendashboard',
contextMenuOrder: 0.31,
contextMenuGroupId: 'navigation',
label: 'Attempt view in browser',
run: function() {
hooksCfg.openurl("/app/" + file_parts_for_openurl_rest[1] + "/" + file_parts_for_openurl_rest[2]);
}
});
}
}
// Add a right-click option
if (tabCfg[ecfg.type].can_rerun) {
ecfg.editor.addAction({
id: 'rerun',
contextMenuOrder: 0.1,
contextMenuGroupId: 'navigation',
label: 'Rerun',
run: function() {
closeTabByCfg(ecfg);
hooksCfg[ecfg.type](ecfg.file, ecfg.fromFolder);
}
});
}
openTabsListChanged();
ecfg.tab.trigger("ce_loaded");
}
function openDiffOfUnsavedChanges(ecfg) {
var ecfgDiff = createTab('diff', ecfg.file, "diff: " + ecfg.file);
var saved_value = ecfg.editor.getValue();
serverActionWithoutFlicker({action: 'read', path: ecfg.file}).then(function(contents){
updateTabAsDiffer(ecfgDiff, ecfg.file + " (on disk)\n" + contents, "(Unsaved changes)\n" + saved_value);
}).catch(function(){
closeTabByCfg(ecfgDiff);
});
}
function saveActiveTab(cb){
var saved_value;
if (activeTab === null || activeTab === -1) {
return;
}
var ecfg = editors[activeTab];
if (ecfg.canBeSavedFile) {
if (! ecfg.saving) {
/// Warn again if file has changed
var $warn_msg = ecfg.tab.find(".ce_modified_on_disk");
if ($warn_msg.length > 0 && ! ecfg.modified_on_disk_ignored) {
showModal({
title: "Warning",
body: "
Unable to open binary file (right-click and 'download' file instead)
";
} else if (r.data.reason !== "") {
errText = "
" + htmlEncode(r.data.reason) + "
";
}
}
if (errText) {
// dont show the error for background requests such as filemods
if (postData.action !== "filemods") {
if (typeof customErrorCB === "undefined") {
showModal({
title: "Error",
body: "
",
});
} else {
customErrorCB();
}
}
reject(Error("error"));
} else {
// if there was some unexpected git output, then open a window to display it
if (r.data.git && r.data.git_status !== -1) {
var git_autocommit_show_output = "auto";
if (conf.hasOwnProperty("git_autocommit_show_output")) {
git_autocommit_show_output = conf.git_autocommit_show_output.toLowerCase();
}
if (git_autocommit_show_output === "true" || (git_autocommit_show_output === "auto" && r.data.git_status > 0)) {
var git_output = "
";
if (git_autocommit_show_output === "auto") {
git_output = "Warning: Unexpected return code when attempting to autocommit changes to version control. ";
}
git_output = "Git output is below:
");
}
}
resolve(r.data.result);
}
});
});
}
// Try to build a conf file from calling the rest services. turns out this is pretty unreliable.
function formatLikeRunningConfig(contents) {
return contents.replace(/^.+?splunk[\/\\]etc[\/\\].*?\.conf\s+(?:(.+) = (.*)|(\[.+))\r?\n/img,function(all, g1, g2, g3){
if (g3 !== undefined) { return g3 + "\n"; }
if (g2.toLowerCase() == "true") { return g1 + " = 1\n";}
if (g2.toLowerCase() == "false") { return g1 + " = 0\n";}
return g1 + " = " + g2 + "\n";
});
}
function adjustBtoolPaths(contents, inPath, outPath) {
return contents.replace(new RegExp(regexEscape(inPath) + "[\\/\\\\]apps[\\/\\\\]", 'g'), outPath);
}
// Formats the output of "btool list" depending on what checkboxes are selected in the left pane
function formatBtoolList(contents, type) {
var indent = 80;
return contents.replace(/\\/g,'/').replace(/^.+?splunk[\/]etc[\/](.*?\.conf)\s+(.+)(\r?\n)/img,function(all, g1, g2, g3){
var path = '';
// I am pretty sure that stanzas cant be set to default when containing a child that isnt
if (type === "btool-hidesystemdefaults" && /system[\/\\]default[\/\\]/.test(g1)) {
return '';
}
if (type === "btool-hidedefaults" && /[\/\\]default[\/\\]/.test(g1)) {
return '';
}
if (type !== "btool-hidepaths") {
path = (" ".repeat(Math.max(1, (indent - g2.length)))) + " " + g1;
}
return g2 + path + g3;
});
}
function addGutter(newdecorations, i, className, message) {
newdecorations.push({ range: new monaco.Range((1+i),1,(1+i),1), options: { isWholeLine: true, glyphMarginClassName: className, glyphMarginHoverMessage: [{value: message }] }});
}
function regexEscape(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
// After loading a .conf file or after saving and before any changes are made, red or green colour will
// be shown in the gutter about if the current line can be found in the output of btool list.
function highlightBadConfig(ecfg){
if (! confIsTrue('conf_validate_on_save', true)) {
return;
}
if (! ecfg.hasOwnProperty('matchedConf') || ecfg.file === "") {
return;
}
if (conf._conf_validate_on_save_exclusions.test(ecfg.matchedConf)) {
console.log("Not doing btool validation in code gutter for [" + ecfg.matchedConf + "] becuase it matches conf_validate_on_save_exclusions.");
return;
}
var run_path = "";
var run_path_parts = ecfg.file.split(/[\/\\]/);
if (run_path_parts[0] === ".") {
run_path_parts.shift();
}
if (run_path_parts.length > 1) {
run_path = run_path_parts[1];
}
// console.log("runpath is ",run_path);
// possible locations you can run btool: etc/master-apps, etc/slave-apps, etc/apps, etc/system/local, etc/user, etc/shcluster/apps
// can also run btool on slave-apps, but who has config explorer installed on an indexer anyway?
if (run_path === "apps" || run_path === "system") {
serverAction({action: 'btool-list', path: ecfg.matchedConf}).then(function(btoolcontents){
highlightBadConfigContinue(ecfg, btoolcontents, run_path);
});
} else if (run_path === "deployment-apps" && conf.btool_dir_for_deployment_apps) {
serverAction({action: 'btool-list', path: ecfg.matchedConf, param1: conf.btool_dir_for_deployment_apps}).then(function(btoolcontents){
if (btoolcontents) {
// nix only paths. this feature wont work with win becuase you cant symlink
var btoolcontents2 = adjustBtoolPaths(btoolcontents, conf.btool_dir_for_deployment_apps, "/splunk/etc/deployment-apps/");
if (btoolcontents2 !== btoolcontents) {
highlightBadConfigContinue(ecfg, btoolcontents2, run_path);
return;
}
}
highlightBadConfigContinue(ecfg, "", run_path);
});
} else if (run_path === "master-apps" && conf.btool_dir_for_master_apps) {
serverAction({action: 'btool-list', path: ecfg.matchedConf, param1: conf.btool_dir_for_master_apps}).then(function(btoolcontents){
if (btoolcontents) {
var btoolcontents2 = adjustBtoolPaths(btoolcontents, conf.btool_dir_for_master_apps, "/splunk/etc/master-apps/");
// if the replace did nothing, then the btool gutters are probably going to be all wrong anyway
if (btoolcontents2 !== btoolcontents) {
highlightBadConfigContinue(ecfg, btoolcontents2, run_path);
return;
}
}
highlightBadConfigContinue(ecfg, "", run_path);
});
} else if (run_path === "manager-apps" && conf.btool_dir_for_manager_apps) {
serverAction({action: 'btool-list', path: ecfg.matchedConf, param1: conf.btool_dir_for_manager_apps}).then(function(btoolcontents){
if (btoolcontents) {
var btoolcontents2 = adjustBtoolPaths(btoolcontents, conf.btool_dir_for_manager_apps, "/splunk/etc/manager-apps/");
// if the replace did nothing, then the btool gutters are probably going to be all wrong anyway
if (btoolcontents2 !== btoolcontents) {
highlightBadConfigContinue(ecfg, btoolcontents2, run_path);
return;
}
}
highlightBadConfigContinue(ecfg, "", run_path);
});
} else if (run_path === "shcluster" && conf.btool_dir_for_shcluster_apps) {
serverAction({action: 'btool-list', path: ecfg.matchedConf, param1: conf.btool_dir_for_shcluster_apps}).then(function(btoolcontents){
//console.log("btoolcontents",btoolcontents);
highlightBadConfigContinue(ecfg, btoolcontents, run_path);
});
} else {
highlightBadConfigContinue(ecfg, "", run_path);
}
}
function highlightBadConfigContinue(ecfg, btoolcontents, run_path){
var rexSplunkHome = /^(.+?)[\\\/]etc[\\\/]/;
var foundSplunkHome;
var lookup = {};
var splunk_home = "";
var seenStanzas = {};
var seenProps = {};
var normalBtoolChecks = true;
var masterappsPropsTransformsChecks = false;
var masterappsPropsSourcetypeChecks = false;
var allConfigsAreUnnecisary = false;
// There is some special logic if we are in the master-apps directory
if (run_path === "master-apps" || run_path === "manager-apps") {
if (conf.hasOwnProperty("_master_apps_gutter_useful_props_and_transforms") && (ecfg.matchedConf === "props" || ecfg.matchedConf === "transforms")) {
masterappsPropsTransformsChecks = true;
}
// split the master_apps_allowed_config by commas, then split each by full colon. ecfg.matchedConf
if (conf.hasOwnProperty("_master_apps_gutter_used_sourcetypes") && ecfg.matchedConf === "props") {
masterappsPropsSourcetypeChecks = true;
}
if (conf.hasOwnProperty("_master_apps_gutter_unnecissary_config_files") && conf._master_apps_gutter_unnecissary_config_files.test(ecfg.matchedConf)) {
allConfigsAreUnnecisary = true;
}
}
if (! $.trim(btoolcontents)) {
console.log("no btool contents for ", ecfg.matchedConf);
normalBtoolChecks = false;
}
//console.log("hinting file for [" + ecfg.matchedConf + "] is MB long: " + Math.round(btoolcontents.length / 1024) / 1024);
if (normalBtoolChecks) {
btoolcontents = btoolcontents.replace(/\\/g,'/');
// Build lookup of btool output
lookup = buildBadCodeLookup(btoolcontents);
if (debug_gutters) {
console.log("Btool:", lookup);
if (ecfg.hasOwnProperty('hinting')) {
console.log("Spec:", ecfg.hinting);
}
}
// try to figure out the SPLUNK_HOME value
// Its common that in inputs.conf, some stanzas are defined with $SPLUNK_HOME which btool always expands
foundSplunkHome = btoolcontents.match(rexSplunkHome);
splunk_home = "";
if (foundSplunkHome && foundSplunkHome.length == 2) {
splunk_home = foundSplunkHome[1];
}
}
// Go through everyline of the editor
var contents = ecfg.editor.getValue(),
rows = contents.split(/\r?\n/),
currentStanza = "",
currentStanzaTrimmed = "",
// This regex is complex becuase sometimes properties have a unique string on the end of them
// e.g "EVAL-src = whatever"
// found[1] will be "EVAL-src"
// found[2] will be "EVAL"
reProps = /^\s*((\w+)[^=\s]*)\s*=/,
newdecorations = [],
currentStanzaAsExpectedInBtool,
extraProp,
extraStanz;
for (var i = 0; i < rows.length; i++) {
if (rows[i].substr(0,1) === "[") {
if (rows[i].substr(0,9) === "[default]") {
currentStanza = "";
} else {
currentStanza = $.trim(rows[i]);
}
currentStanzaTrimmed = currentStanza.replace(/^(\[\w+).*$/,"$1");
// Stanzas that have $SPLUNK_HOME in them will be expanded by btool (stanzas in inputs.conf often have $SPLUNK_HOME in them)
currentStanzaAsExpectedInBtool = currentStanza.replace(/\$SPLUNK_HOME/i, splunk_home);
// Stanzas with windows path seperators are converted to unix seperators by btool
currentStanzaAsExpectedInBtool = currentStanzaAsExpectedInBtool.replace(/\\/g, '/');
// Stanzas that use relative paths, will be expanded by btool. (e.g. inputs.conf [script://./bin/go.sh] from current script location)
currentStanzaAsExpectedInBtool = currentStanzaAsExpectedInBtool.replace(/\/\/\.\//, "//" + splunk_home + ecfg.file.substr(1).replace(/[^\/\\]*\/[^\/\\]*$/,''));
if (seenStanzas.hasOwnProperty(currentStanza)) {
addGutter(newdecorations, i, 'ceOrangeLine', "Duplicate Stanza in this file");
} else if (masterappsPropsSourcetypeChecks) {
// This does does not work with source:: or host:: stanzas, or those that look like they might be a regular expression (not perfect)
if (! conf._master_apps_gutter_used_sourcetypes.test(currentStanza) && currentStanza.substr(0,9) !== "[source::" && currentStanza.substr(0,7) !== "[host::" && currentStanza.substr(0,2) !== "[(") {
addGutter(newdecorations, i, 'ceBlueLine', "This stanza \"" + currentStanza + "\" does not match a sourcetype in master_apps_gutter_used_sourcetypes (which is " + conf._master_apps_gutter_used_sourcetypes_date + " days old)");
}
} else if (allConfigsAreUnnecisary) {
addGutter(newdecorations, i, 'ceBlueLine', "In most environments, the properties in this file are not needed on indexers.");
}
seenStanzas[currentStanza] = 1;
seenProps = {};
} else {
var found = rows[i].match(reProps);
if (found) {
if (found[1].substr(0,1) !== "#") {
var g_sev = 2;
var g_messages = [];
var prop = found[1];
// Check for duplicated key in stanza
if (seenProps.hasOwnProperty(found[1])) {
//addGutter(newdecorations, i, 'ceOrangeLine', "Duplicate key in stanza");
g_sev = Math.max(g_sev, 5);
g_messages.push("Duplicate key in stanza");
} else {
seenProps[found[1]] = 1;
if (normalBtoolChecks) {
// Look if stanza/property exists in btool
if (! lookup.hasOwnProperty(currentStanzaAsExpectedInBtool)){
//addGutter(newdecorations, i, 'ceRedLine', "Not found in \"btool\" output (btool does not list the stanza \"" + currentStanzaAsExpectedInBtool +"\")");
g_sev = Math.max(g_sev, 6);
g_messages.push("Not found in \"btool\" output (btool does not list the stanza " + currentStanzaAsExpectedInBtool +")");
} else if (! lookup[currentStanzaAsExpectedInBtool].hasOwnProperty(found[1])){
// [default] is a special case and is reflected through all other stanzas in the file
if (currentStanzaAsExpectedInBtool !== "") {
//addGutter(newdecorations, i, 'ceRedLine', "Not found in \"btool\" output (btool with stanza [" + currentStanzaAsExpectedInBtool +"] does not have property \"" + found[1] + "\")");
g_sev = Math.max(g_sev, 6);
g_messages.push("Not found in \"btool\" output (could not find property in stanza " + currentStanzaAsExpectedInBtool +"");
}
} else if (lookup[currentStanzaAsExpectedInBtool][found[1]] !== ecfg.file && lookup[currentStanzaAsExpectedInBtool][found[1]].substr(2) !== ecfg.file) {
//addGutter(newdecorations, i, 'ceRedLine', "Not found in \"btool\" output (set in :" + lookup[currentStanzaAsExpectedInBtool][found[1]] + ")");
g_sev = Math.max(g_sev, 6);
g_messages.push("Not found in \"btool\" output (set in :" + lookup[currentStanzaAsExpectedInBtool][found[1]] + " and i am :" + ecfg.file + ")");
} else {
// addGutter(newdecorations, i, 'ceGreeenLine', "Found in \"btool\" output");
g_messages.push("Found in \"btool\" output");
}
}
// If a spec file exists
if (ecfg.hasOwnProperty('hinting') && found.length > 2) {
// Look in the unstanzaed part of the spec
if (ecfg.hinting[""].hasOwnProperty(found[2])) {
//addGutter(newdecorations, i, 'ceGreeenLine', "Found in \"btool\" output and spec file. (Stanza=\"\" Property=\"" + found[2] + "\")");
g_messages.push("Exists in spec file");
prop = found[2];
// Look in the stanzaed part of the spec
} else if (ecfg.hinting.hasOwnProperty(currentStanza) && ecfg.hinting[currentStanza].hasOwnProperty(found[2])) {
//addGutter(newdecorations, i, 'ceGreeenLine', "Found in \"btool\" output and spec file. (Stanza=\"" + currentStanza + "\" Property=\"" + found[2] + "\")");
g_messages.push("Exists in spec file (Stanza=" + currentStanza + ")");
prop = found[2];
// Look for a trimmed version of the stanza in the spec. e.g. [endpoint:blah_rest] might be in the spec as [endpoint]
} else if (ecfg.hinting.hasOwnProperty(currentStanzaTrimmed) && ecfg.hinting[currentStanzaTrimmed].hasOwnProperty(found[2])) {
//addGutter(newdecorations, i, 'ceGreeenLine', "Found in \"btool\" output and spec file. (Stanza=\"" + currentStanzaTrimmed + "\" Property=\"" + found[2] + "\")");
g_messages.push("Exists in spec file (Stanza=" + currentStanzaTrimmed + ")");
prop = found[2];
// Now go through those same three checks, but look for the whole thing. For Example in web.conf found[2] is "tools" where as found[1] is "tools.sessions.timeout"
} else if (ecfg.hinting[""].hasOwnProperty(found[1])) {
//addGutter(newdecorations, i, 'ceGreeenLine', "Found in \"btool\" output and spec file. (Stanza=\"\" Property=\"" + found[1] + "\")");
g_messages.push("Exists in spec file");
// Look in the stanzaed part of the spec
} else if (ecfg.hinting.hasOwnProperty(currentStanza) && ecfg.hinting[currentStanza].hasOwnProperty(found[1])) {
//addGutter(newdecorations, i, 'ceGreeenLine', "Found in \"btool\" output and spec file. (Stanza=\"" + currentStanza + "\" Property=\"" + found[1] + "\")");
g_messages.push("Exists in spec file (Stanza=" + currentStanza + ")");
// Look for a trimmed version of the stanza in the spec. e.g. [endpoint:blah_rest] might be in the spec as [endpoint]
} else if (ecfg.hinting.hasOwnProperty(currentStanzaTrimmed) && ecfg.hinting[currentStanzaTrimmed].hasOwnProperty(found[1])) {
//addGutter(newdecorations, i, 'ceGreeenLine', "Found in \"btool\" output and spec file. (Stanza=\"" + currentStanzaTrimmed + "\" Property=\"" + found[1] + "\")");
g_messages.push("Exists in spec file (Stanza=" + currentStanzaTrimmed + ")");
} else {
if (found[2] === found[1]) {
extraProp = "Looked for property ";
} else {
extraProp = "Looked for property (or \"" + found[2] + "\" ";
}
if (currentStanza === currentStanzaTrimmed) {
extraStanz = "in stanzas \"\" or " + currentStanza + "";
} else {
extraStanz = "in stanzas \"\", " + currentStanza + " and " + currentStanzaTrimmed + "";
}
//addGutter(newdecorations, i, 'ceDimGreeenLine', "Found in \"btool\" output, but not found in spec file. "+ extraProp + extraStanz);
g_sev = Math.max(g_sev, 3);
g_messages.push("Not found in spec file ("+ extraProp + extraStanz + ")");
}
}
if (masterappsPropsTransformsChecks) {
if (! conf._master_apps_gutter_useful_props_and_transforms.test(found[2])) {
g_sev = Math.max(g_sev, 4);
g_messages.push("In most environments, this property is not needed on indexers (there is no harm in having it there though).");
}
}
if (allConfigsAreUnnecisary) {
g_sev = Math.max(g_sev, 4);
g_messages.push("In most environments, the properties in this file are not needed on indexers.");
}
}
// add gutters now
if (g_messages.length) {
var g_color = g_sev == 6 ? "ceRedLine" : g_sev == 5 ? "ceOrangeLine" : g_sev == 4 ? "ceBlueLine" : g_sev == 3 ? "ceDimGreeenLine" : "ceGreeenLine";
addGutter(newdecorations, i, g_color, "`" + prop + "`: " + g_messages.join(". "));
}
}
}
}
}
ecfg.decorations = ecfg.editor.deltaDecorations(ecfg.decorations, newdecorations);
}
// The bad code lookup builds a structure of the btool list output so it can be quickly referenced to see what config
// from the current editor is being recognised by btool or not.
function buildBadCodeLookup(contents){
//(conf file path)(property)(stanza)
var rex = /^.+?splunk([\/\\]etc[\/\\].*?\.conf)\s+(?:([^=\s\[]+)\s*=|(\[[^\]]+\]))/gim;
var res;
var currentStanza = "";
var currentField = "";
var ret = {"": {"":""}};
while(res = rex.exec(contents)) {
if (res[2]) {
currentField = res[2];
ret[currentStanza][currentField] = '.' + res[1];
} else if (res[3]) {
if (res[3].substr(0,9) === "[default]") {
currentStanza = "";
} else {
currentStanza = res[3];
}
currentField = "";
ret[currentStanza] = {"":""}; // dont care about stanzas and where they come from
} //else {
//console.log("unexpected row:", res[0]);
//}
}
return ret;
}
// parse the spec file and build a lookup to use for code completion
function buildHintingLookup(conf, contents){
// left side is for properties, right size for stanzas
var rex = /^(?:([\w\.]+).*=|(\[\w+))?.*$/gm;
var res;
var currentStanza = "";
var currentField = "";
confFiles[conf] = {"": {"":{t:"", c:""}}};
while(res = rex.exec(contents)) {
// need this because our rex can match a zero length string
if (res.index == rex.lastIndex) {
rex.lastIndex++;
}
if (res[1] || res[2]) {
if (res[1]) {
currentField = res[1];
confFiles[conf][currentStanza][currentField] = {t:"", c:""};
} else {
if (res[2].substr(0,9) === "[default]") {
currentStanza = "";
} else {
currentStanza = res[2];
}
currentField = "";
if (! confFiles[conf].hasOwnProperty(currentStanza)) {
confFiles[conf][currentStanza] = {"":{t:"", c:""}};
}
}
confFiles[conf][currentStanza][currentField].t = res[0];
} else {
confFiles[conf][currentStanza][currentField].c += res[0] + "\n";
}
}
return confFiles[conf];
}
// When hovering lines in a .conf file, Monaco will lookup the current property in the README/*.conf.spec files.
// Becuase the README/*.conf.spec files are not perfect, neither is this documentation!
monaco.languages.registerHoverProvider('ini', {
provideHover: function(model, position, token) {
return new Promise(function(resolve, reject) {
// do somthing
if (editors[activeTab].hasOwnProperty('hinting')) {
// get all text up to hovered line becuase we need to find what stanza we are in
var contents = model.getValueInRange(new monaco.Range(1, 1, position.lineNumber, model.getLineMaxColumn(position.lineNumber)));
var rex = /^(?:([\w\.]+)|(\[\w+))?.*$/gm;
var currentStanza = "";
var currentField = "";
var hintdata;
var res;
while(res = rex.exec(contents)) {
// need this because our rex can match a zero length string
if (res.index == rex.lastIndex) {
rex.lastIndex++;
}
if (res[1]) {
currentField = res[1];
} else if (res[2]) {
if (res[2].substr(0,9) === "[default]") {
currentStanza = "";
} else {
currentStanza = res[2];
}
currentField = "";
}
}
if (editors[activeTab].hinting.hasOwnProperty(currentStanza) && editors[activeTab].hinting[currentStanza].hasOwnProperty(currentField)) {
hintdata = editors[activeTab].hinting[currentStanza][currentField];
} else if (editors[activeTab].hinting[""].hasOwnProperty(currentField)) {
hintdata = editors[activeTab].hinting[""][currentField];
} else {
resolve();
return;
}
resolve({
// This is what will be highlighted
range: new monaco.Range(position.lineNumber, 1, position.lineNumber, model.getLineMaxColumn(position.lineNumber)),
contents: [
{ value: '**' + hintdata.t + '**' },
{ value: '\n' + hintdata.c.replace(/^#/mg,'') + '\n' }
]
});
} else {
resolve();
}
});
}
});
// When hitting CTRL-SPACE in .conf files, monaco will suggest all valid keys - with doco!
// Becuase the README/*.conf.spec files are not perfect, neither is this hinting!
monaco.languages.registerCompletionItemProvider('ini', {
provideCompletionItems: function(model, position) {
if (editors[activeTab].hasOwnProperty('hinting')) {
// get all text up to hovered line becuase we need to find what stanza we are in
var contents = model.getValueInRange(new monaco.Range(1, 1, position.lineNumber, model.getLineMaxColumn(position.lineNumber)));
var ret = [];
var rex = /[\s\S]*\n\s*(\[\w+)/;
var currentStanza = "";
var found = contents.match(rex);
if (found && editors[activeTab].hinting.hasOwnProperty(found[1])) {
if (found[1].substr(0,9) === "[default]") {
currentStanza = "";
} else {
currentStanza = found[1];
}
}
for (var key in editors[activeTab].hinting[currentStanza]) {
if (editors[activeTab].hinting[currentStanza].hasOwnProperty(key) && key) {
ret.push({
label: key,
insertText: key + " = ",
kind: monaco.languages.CompletionItemKind.Property,
documentation: "" + editors[activeTab].hinting[currentStanza][key].t + "\n\n" + editors[activeTab].hinting[currentStanza][key].c + "\n",
});
}
}
return { suggestions: ret };
}
return { suggestions: [] };
}
});
// Register a new simple language for prettying up git diffs
monaco.languages.register({ id: 'git-diff' });
monaco.languages.setMonarchTokensProvider('git-diff', {
tokenizer: {
root: [
[/^diff.*/, "constant"],
[/^\+[^\n]*/, "comment"], // additions
[/^\-[^\n]*/, "metatag"], // deletions
[/^@@.+?@@/, "regexp"],
[/^\w[^\n]+/, "white"],
[/\s[^\n]+/, "delimiter.xml"], // other lines
]
}
});
// Register a new simple language for prettying up git log
monaco.languages.register({ id: 'git-log' });
monaco.languages.setMonarchTokensProvider('git-log', {
tokenizer: {
root: [
[/^(commit|Author:|Date:)[^\n]+/, "constant"], // additions
[/(?:\++(?=[\-\s]*$)|\(\+\))/, "comment"], // plus (?:\++(?=[\-\s]*$)|(?<=\()\+(?=\)))
[/(?:(\-+)\s*$|\(\-\))/, "metatag"], // minus (?:(\-+)\s*$|(?<=\()\-(?=\)))
]
}
});
// dubious
function dodgyBasename(f) {
return f.replace(/.*[\/\\]/,'');
}
function dodgyDirname(f) {
return f.replace(/[^\/\\]*$/,'');
}
function dodgyRemoveRelPath(f) {
return f.replace(/^\.?[\/\\]/,'');
}
//create a in-memory div, set it's inner text(which jQuery automatically encodes)
//then grab the encoded contents back out. The div never exists on the page.
function htmlEncode(value){
return $('').text(value).html();
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
function confIsTrue(param, defaultValue) {
if (!conf.hasOwnProperty(param)) {
return defaultValue;
}
return isTrueValue(conf[param]);
}
function isTrueValue(param) {
return (["1", "true", "yes", "t", "y"].indexOf($.trim(param.toLowerCase())) > -1);
}
function copyTextToClipboard(text) {
if (!navigator.clipboard) {
fallbackCopyTextToClipboard(text);
} else {
navigator.clipboard.writeText(text).then(function() {
showToast('Copied to clipboard!');
}, function (err) {
console.error('Async: Could not copy to clipboard.', err);
});
}
}
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
if (successful) {
showToast('Copied to clipboard!');
} else {
console.error('Fallback2: Could not copy to clipboard.');
}
} catch (err) {
console.error('Fallback: Could not copy to clipboard.', err);
}
document.body.removeChild(textArea);
}
function showToast(message) {
var t = $('.ce_toaster');
t.find('span').text(message);
t.addClass('ce_show');
setTimeout(function(){
t.removeClass('ce_show');
},3000);
}
var showModal = function self(o) {
var options = $.extend({
title : '',
body : '',
remote : false,
backdrop : true,
size : 500,
onShow : false,
onHide : false,
actions : false
}, o);
self.onShow = typeof options.onShow == 'function' ? options.onShow : function () {};
self.onHide = typeof options.onHide == 'function' ? options.onHide : function () {};
if (self.$modal === undefined) {
self.$modal = $('
'.replace('${title}', options.title).replace('${body}', options.body));
var footer = self.$modal.find('.modal-footer');
if (Object.prototype.toString.call(options.actions) == "[object Array]") {
for (var i = 0, l = options.actions.length; i < l; i++) {
options.actions[i].onClick = typeof options.actions[i].onClick == 'function' ? options.actions[i].onClick : function () {};
$('').appendTo(footer).on('click', options.actions[i].onClick);
}
} else {
$('').appendTo(footer);
}
self.$modal.modal(options);
};
// "vs" | "vs-dark" (default) | "hc-black"
function setThemeMode(mode){
// Remove existing theme class from parent
$dashboard_body.removeClass(function (index, className) {
return (className.match (/(^|\s)ce_theme_\S+/g) || []).join(' ');
});
$dashboard_body.addClass("ce_theme_" + mode);
// Set theme for editors
monaco.editor.setTheme(mode);
// save to local storage
localStorage.setItem('ce_theme', mode);
}
function checkFileMods() {
var files = {};
var problems = [];
var hasFiles = false;
var ecfg;
// check if change detection is disabled
if (!confIsTrue('detect_changed_files', true)) {
return;
}
clearTimeout(fileModsCheckTimer);
fileModsCheckTimer = setTimeout(checkFileMods, fileModsCheckTimerPeriod);
if (fileModsCheckTimerInProgress) {
return;
}
// if current focused tab is "rest" then check it to see if it has changed
if (activeTab > 0 && editors[activeTab].type === "rest") {
ecfg = editors[activeTab];
if (ecfg.tab.find(".ce_modified_on_disk").length === 0 ) {
//console.log("check for REST changes in ", ecfg);
fileModsCheckTimerInProgress = true;
getRest(ecfg.file, null).then(function(restData) {
fileModsCheckTimerInProgress = false;
//console.log(restData); //?output_mode=json&count=0&f=title&f=label
if (restData.length === 0) {
ecfg.tab.css("color","orange").append("");
showModal({
title: "Warning",
body: "
Warning: Unable to check for changes. File might have been deleted, moved or had its permissions changed.
Warning: File has unexpectedly changed while open.
" + htmlEncode(ecfg.file) + "
",
size: 600
});
}
} else {
console.error("Unexpeted response when checking if rest dashboard contents has changed while open - returned dashboards: " + restData.length);
}
}).catch(function(){
fileModsCheckTimerInProgress = false;
console.error("Unexpeted response when checking if rest dashboard contents has changed while open");
});
}
} else {
for (var j = 0; j < editors.length; j++){
if (editors[j].type === "read") {
files[editors[j].file] = 0;
hasFiles = true;
}
}
if (hasFiles && ! fileModsCheckTimerInProgress) {
//console.log("check for FILE changes");
fileModsCheckTimerInProgress = true;
serverAction({action: 'filemods', paths: JSON.stringify(files)}).then(function(filemods) {
fileModsCheckTimerInProgress = false;
var editorMap = {};
for (var i = 0; i < editors.length; i++) {
if (editors[i].type === "read") {
editorMap[editors[i].file] = editors[i];
}
}
for (var file in files) {
if (files.hasOwnProperty(file)) {
if (filemods.hasOwnProperty(file)) {
if (filemods[file] === "") {
// file was deleted
if (editorMap[file].tab.find(".ce_modified_on_disk").length === 0) {
problems.push("Deleted: " + htmlEncode(file) + "");
editorMap[file].tab.css("color","orange").append("");
}
} else if (! editorMap[file].hasOwnProperty("filemod")) {
// first time we have checked filemod on this file. so store the response
editorMap[file].filemod = filemods[file];
} else if (filemods[file] > editorMap[file].filemod + 10) { // add a buffer of 10 seconds
// file has updated behind the scenes
if (editorMap[file].tab.find(".ce_modified_on_disk").length === 0) {
problems.push("Changed: " + htmlEncode(file) + "");
editorMap[file].tab.css("color","orange").append("");
}
}
}
}
}
if (problems.length) {
showModal({
title: "Warning",
body: "
Warning: An open file has unexpectedly changed on disk. You should right-click in the affected editor and select either: 'Diff unsaved changes' to see what might have changed, or 'Reload from disk'.
" + problems.join(" ") + "
",
size: 600
});
}
}).catch(function(){
fileModsCheckTimerInProgress = false;
});
}
}
}
// Build the list of config files,
// This function is also called after settings are changed.
function loadPermissionsAndConfList(){
return serverAction({action:'init'}).then(function(data) {
var rex = /^Checking: .*[\/\\]([^\/\\]+?).conf\s*$/gmi;
var res;
var stanza;
conf = data.conf.global;
if (! conf.hasOwnProperty('git_autocommit_work_tree')) {
conf.git_autocommit_work_tree = "";
} else {
conf.git_autocommit_work_tree = $.trim(conf.git_autocommit_work_tree);
}
$dashboard_body.addClass('ce_no_write_access ce_no_run_access ce_no_settings_access ce_no_git_access ');
if (confIsTrue('write_access', false)) {
$dashboard_body.removeClass('ce_no_write_access');
}
if (confIsTrue('run_commands', false)) {
$dashboard_body.removeClass('ce_no_run_access');
}
if (! confIsTrue('hide_settings', false)) {
$dashboard_body.removeClass('ce_no_settings_access');
}
if (confIsTrue('git_autocommit', false) && conf.git_autocommit_work_tree !== "") {
$dashboard_body.removeClass('ce_no_git_access');
}
if (conf.btool_dir_for_deployment_apps) {
conf.btool_dir_for_deployment_apps = $.trim(conf.btool_dir_for_deployment_apps).replace(/\/$/, "");
}
if (conf.btool_dir_for_master_apps) {
conf.btool_dir_for_master_apps = $.trim(conf.btool_dir_for_master_apps).replace(/\/$/, "");
}
if (conf.btool_dir_for_shcluster_apps) {
conf.btool_dir_for_shcluster_apps = $.trim(conf.btool_dir_for_shcluster_apps).replace(/\/$/, "");
}
if (conf.hasOwnProperty('conf_validate_on_save_exclusions')) {
conf.conf_validate_on_save_exclusions = $.trim(conf.conf_validate_on_save_exclusions);
if (conf.conf_validate_on_save_exclusions !== "") {
try {
conf._conf_validate_on_save_exclusions = new RegExp(conf.conf_validate_on_save_exclusions, '');
} catch (e) {
console.error("Config file property: \"conf_validate_on_save_exclusions\" has bad regular expression and will be ignored.");
}
}
}
if (typeof conf._conf_validate_on_save_exclusions !== "object") {
conf._conf_validate_on_save_exclusions = new RegExp("savedsearches", '');
}
if (conf.hasOwnProperty('master_apps_gutter_useful_props_and_transforms')) {
conf.master_apps_gutter_useful_props_and_transforms = $.trim(conf.master_apps_gutter_useful_props_and_transforms);
if (conf.master_apps_gutter_useful_props_and_transforms !== "") {
try {
conf._master_apps_gutter_useful_props_and_transforms = new RegExp(conf.master_apps_gutter_useful_props_and_transforms, '');
} catch (e) {
console.error("Config file property: \"master_apps_gutter_useful_props_and_transforms\" has bad regular expression and will be ignored.");
}
}
}
if (conf.hasOwnProperty('master_apps_gutter_used_sourcetypes')) {
conf.master_apps_gutter_used_sourcetypes = $.trim(conf.master_apps_gutter_used_sourcetypes);
if (conf.master_apps_gutter_used_sourcetypes !== "") {
try {
conf._master_apps_gutter_used_sourcetypes = new RegExp(conf.master_apps_gutter_used_sourcetypes, '');
// attempt to load the date
if (! conf.hasOwnProperty('master_apps_gutter_used_sourcetypes_date')) {
conf.master_apps_gutter_used_sourcetypes_date = "";
}
var date_set = new Date(conf.master_apps_gutter_used_sourcetypes_date).valueOf();
if (isNaN(date_set)) {
conf._master_apps_gutter_used_sourcetypes_date = "Unknown";
} else {
conf._master_apps_gutter_used_sourcetypes_date = Math.floor(((new Date().valueOf()) - date_set) / 86400000);
}
} catch (e) {
console.error("Config file property: \"master_apps_gutter_used_sourcetypes\" has bad regular expression and will be ignored.");
}
}
}
if (conf.hasOwnProperty('master_apps_gutter_unnecissary_config_files')) {
conf.master_apps_gutter_unnecissary_config_files = $.trim(conf.master_apps_gutter_unnecissary_config_files);
if (conf.master_apps_gutter_unnecissary_config_files !== "") {
try {
conf._master_apps_gutter_unnecissary_config_files = new RegExp(conf.master_apps_gutter_unnecissary_config_files, '');
} catch (e) {
console.error("Config file property: \"master_apps_gutter_unnecissary_config_files\" has bad regular expression and will be ignored.");
}
}
}
if (conf.hasOwnProperty('debug_refresh_endpoints')) {
conf._debug_refresh_endpoints = conf.debug_refresh_endpoints.split("|");
} else {
conf._debug_refresh_endpoints = [];
}
if (confIsTrue('rest_api_dashboard_list', false)) {
$(".ce_show_rest").css("display","");
} else {
$(".ce_show_rest").css("display","none");
}
// Build the quick access hooksActive object
hooksActive = [];
var hookDefaults = data.conf.hook || {};
for (stanza in data.conf) {
if (data.conf.hasOwnProperty(stanza)) {
if (stanza.substr(0,5) === "hook:") {
data.conf[stanza] = $.extend({}, hookDefaults, data.conf[stanza]);
if (! isTrueValue(data.conf[stanza].disabled)) {
var action = data.conf[stanza].action.split(":")[0];
if (! hooksCfg.hasOwnProperty(action)) {
console.error("Stanza: [" + stanza + "] has unknown action value and will be ignored.");
continue;
}
if (action.substr(0,3) === "run") {
if (! confIsTrue('run_commands', false)) {
//console.error("Stanza: [" + stanza + "] has 'run' action but run_commands is false");
continue;
}
if (action === "run") {
data.conf[stanza].label = "$" + data.conf[stanza].label;
}
}
try {
data.conf[stanza]._match = new RegExp(data.conf[stanza].match, 'i');
hooksActive.push(data.conf[stanza]);
} catch (e) {
console.error("Stanza: [" + stanza + "] has bad regular expression and will be ignored.");
}
}
}
}
}
hooksActive.sort(function(a, b) {
if (a.order < b.order)
return -1;
if (a.order > b.order)
return 1;
return 0;
});
var actions = [];
var actionDefaults = data.conf.action || {};
var ce_custom_actions = $(".ce_custom_actions");
// Build the actions buttons on the home tab
ce_custom_actions.css("display","block");
for (stanza in data.conf) {
if (data.conf.hasOwnProperty(stanza)) {
if (stanza.substr(0,7) === "action:") {
var act = $.extend({}, actionDefaults, data.conf[stanza]);
if (! isTrueValue(act.disabled)) {
actions.push(act);
}
}
}
}
actions.sort(function(a, b) {
if (a.order < b.order)
return -1;
if (a.order > b.order)
return 1;
return 0;
});
ce_custom_actions.empty();
for (var i = 0; i < actions.length; i++) {
if (actions[i].action === "heading") {
$("").text(actions[i].label).appendTo(ce_custom_actions);
} else if (actions[i].action === "text") {
$("").text(actions[i].label).appendTo(ce_custom_actions);
} else if (actions[i].action === "br") {
$(" ").appendTo(ce_custom_actions);
} else {
// add to the home screen
(function(a, i, l){
var button = $("").text(a.label).attr("title",a.description).on("click", function(){
runAction(a.action, undefined, false);
});
button.appendTo(ce_custom_actions);
})(actions[i], i, actions.length);
}
}
confFiles = {};
confFilesSorted = [];
while((res = rex.exec(data.files)) !== null) {
if (! confFiles.hasOwnProperty(res[1])) {
confFiles[res[1]] = null;
confFilesSorted.push(res[1]);
}
}
confFilesSorted.sort();
});
}
// First load after init has occcured, setup the page
loadPermissionsAndConfList().then(function(){
$ce_spinner.detach();
$dashboard_body.removeClass("ce_loading");
setThemeMode(localStorage.getItem('ce_theme') || "vs-dark");
setLeftPathWidth(localStorage.getItem('ce_lwidth') || 280);
// on page load, log that tabs that were open previously
var ce_open_tabs = (JSON.parse(localStorage.getItem('ce_open_tabs')) || []);
if (ce_open_tabs.length) {
// move any previously open tabs into the close tabs list
for (var i = 0; i < ce_open_tabs.length; i++){
logClosedTab(ce_open_tabs[i]);
}
var $restore = $("Restore " + (ce_open_tabs.length === 1 ? "1 tab" : ce_open_tabs.length + " tabs") + "").appendTo($ce_tabs);
$restore.on("click", function(){
for (var j = 0; j < ce_open_tabs.length; j++) {
hooksCfg[ce_open_tabs[j].type](ce_open_tabs[j].file);
}
});
}
// Allow tabs to be rearranged
Sortable.create($ce_tabs[0], {
draggable: ".ce_tab",
animation: 150,
onEnd: function () {
// figure out how things moved and reorder list
openTabsListChanged();
doPipeTabSeperators();
}
});
// Add tooltips
$ce_tree_icons.find('i').tooltip({delay: 100, placement: 'bottom'});
$("body").css("overflow","");
readUrlHash();
// Left pane styled scrollbar
OverlayScrollbars($ce_file_list[0],{ className : "os-theme-light", overflowBehavior : { x: "hidden"} });
// Show a warning the first time someone opens the app
if (! localStorage.getItem('ce_seen_warning')) {
localStorage.setItem('ce_seen_warning', "1");
showModal({
title: "Dragons ahead!",
size: 600,
body:
"
Warning: This is designed for advanced users.
This app can allow you to change Splunk files on the "+
"filesystem. When you change files, if you don't know what you are doing, then you may break your Splunk environment.
" +
(!(confIsTrue('write_access', false) || confIsTrue('hide_settings', false)) ? "By default, write_access=false so files cannot be saved. Open the 'Settings' screen to enable.
" : "") +
'
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.' +
"
").appendTo($ce_tree_pane);
}
}, 3000);
} else {
if (Number(conf.cache_file_depth) === -1) {
filecache = null;
}
readFolder(inFolder);
}
// Keep track if the page is visible or not. This will affect how often we check for changes to open files
$(document).on('visibilitychange', function(){
if (document.hidden) {
fileModsCheckTimerPeriod = 60000;
clearTimeout(fileModsCheckTimer);
setTimeout(function(){ checkFileMods(); }, fileModsCheckTimerPeriod);
} else {
fileModsCheckTimerPeriod = 10000;
// When the tab gains focus, immidiatly check for updated files
checkFileMods();
}
});
});
});