// 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( "
"+ "
"+ "
"+ "
"+ "
"+ "
"+ "
"+ "
"+ "
"+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ ""+ //""+ ""+ ""+ ""+ ""+ "
"+ "
"+ "
"+ "
"+ "
"+ ""+ "
"+ "
"+ "
"+ "
"+ ""+ "
"+ "
"+ "
"+ "
"+ ""+ "
Config Explorer for
"+ "
by Chris Younger
"+ "
"+ "Settings Theme: "+ "light"+ "dark"+ "high contrast"+ "Change log"+ "
"+ "
"+ ""+ "
"+ "
"+ "
"+ "
Saved
"+ "
"+ "
"+ ""); // 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 = $("
Sort by:"+ "
Name
"+ "
Size
"+ "
Modified date
").appendTo($dashboard_body); $menu.find(".ce_sortby_" + sortMode + "." + (sortAsc ? 'icon-chevron-down' : 'icon-chevron-up')).addClass("ce_sortby_selected"); $menu.css({right: $(window).width() - parseInt($ce_tree_pane.css("width")) + 2, top: 41}); $menu.on("click", ".ce_clickable_icon", function(){ var $t = $(this); sortMode = $t.hasClass("ce_sortby_name") ? "name" : $t.hasClass("ce_sortby_size") ? "size" : "time"; sortAsc = !!($t.hasClass("icon-chevron-down")); readFolderLoad(inFolder); }); $ce_context_menu_overlay.removeClass("ce_hidden"); $(document).one("click contextmenu", function(e2) { $ce_context_menu_overlay.addClass("ce_hidden"); $menu.remove(); e2.preventDefault(); }); } else if (elem.hasClass('ce_sort_files_rest')) { $menu = $("
Display:
"+ "
Title (URL)
"+ "
URL (Title)
"+ "
Title
"+ "
URL
"+ "
Sort by:
"+ "
Label
"+ "
Last updated
").appendTo($dashboard_body); $menu.find(".ce_sortby_" + sortModeRest + "." + (sortAscRest==="1" ? 'icon-chevron-down' : 'icon-chevron-up')).addClass("ce_sortby_selected"); $menu.find(".ce_display_" + displayModeRest).addClass("ce_sortby_selected"); $menu.css({right: $(window).width() - parseInt($ce_tree_pane.css("width")) + 2, top: 41}); $menu.on("click", ".ce_clickable_icon", function(){ var $t = $(this); if ($t.hasClass("icon-string")) { displayModeRest = $t.hasClass("ce_display_ln") ? "ln" : $t.hasClass("ce_display_nl") ? "nl" : $t.hasClass("ce_display_l") ? "l" : "n"; localStorage.setItem('ce_display_mode_rest', displayModeRest); } else { sortModeRest = $t.hasClass("ce_sortby_name") ? "name" : $t.hasClass("ce_sortby_label") ? "label" : "time"; sortAscRest = $t.hasClass("icon-chevron-down") ? "1" : "0"; localStorage.setItem('ce_sort_mode_rest', sortModeRest); localStorage.setItem('ce_sort_asc_rest', sortAscRest); } leftPaneRestList(); }); $ce_context_menu_overlay.removeClass("ce_hidden"); $(document).one("click contextmenu", function(e2) { $ce_context_menu_overlay.addClass("ce_hidden"); $menu.remove(); e2.preventDefault(); }); } }); $('.ce_treesearch_input').on("input", function(){ if ($(".ce_tree_icons .ce_show_filesystem.ce_selected").length) { leftPaneFileList($(this).val().toLowerCase()); } if ($(".ce_tree_icons .ce_recent_files.ce_selected").length) { leftPaneRecentList($(this).val().toLowerCase()); } if ($(".ce_tree_icons .ce_show_confs.ce_selected").length) { leftPaneConfList($(this).val().toLowerCase()); } if ($(".ce_tree_icons .ce_show_rest.ce_selected").length) { leftPaneRestList($(this).val().toLowerCase()); } }).on("focus", function(e){ if ($(".ce_leftnav_keyboard_selected").length == 0) { $ce_file_wrap.children(".ce_leftnav").first().addClass("ce_leftnav_keyboard_selected"); } treeSearchFocus = true; }).on("blur", function(e){ $(".ce_leftnav_keyboard_selected").removeClass("ce_leftnav_keyboard_selected"); treeSearchFocus = false; }).on("keydown", function(e){ // select the top item var $keyFocused = $(".ce_leftnav_keyboard_selected"); if (e.which === 13) { $keyFocused.click(); } else if (e.which === 38) { // up e.preventDefault(); if ($keyFocused && $keyFocused.index() > 0) { $keyFocused.prev().addClass("ce_leftnav_keyboard_selected"); $keyFocused.removeClass("ce_leftnav_keyboard_selected"); } } else if (e.which === 40) { // down e.preventDefault(); if ($keyFocused && $keyFocused.index() < $keyFocused.parent().children().length - 1) { $keyFocused.next().addClass("ce_leftnav_keyboard_selected"); $keyFocused.removeClass("ce_leftnav_keyboard_selected"); } // If user hits backspace with nothing in box then navigate back } else if (e.which === 8 && $(this).val() === "" && $(".ce_tree_icons .ce_show_filesystem.ce_selected").length) { // navigate back $(".ce_folder_up").click(); } else if (e.which === 8 && $(this).val() === "" && $(".ce_tree_icons .ce_show_rest.ce_selected").length) { // navigate back $(".ce_folder_up_rest").click(); } }); function filterModeReset(){ $(".ce_treesearch_input").val(""); } function addHookActionToTree(hook, file, actions, matchtype) { if (hook._match.test(file) && hook.matchtype == matchtype && ! (hook.hasOwnProperty("showInPane") && hook.showInPane === "editor")) { actions.push($("
").text(replaceTokens(hook.label, file)).on("click", function(){ runAction(hook.action, file, false); })); } } // add folder hooks to the path display at the top of the left pane $ce_file_path.on("contextmenu", function (e) { // only allow right clicking on the "path" when in filesystem mode if ($(".ce_tree_icons .ce_show_filesystem.ce_selected").length) { var actions = []; for (var j = 0; j < hooksActive.length; j++) { addHookActionToTree(hooksActive[j], inFolder, actions, "folder"); } // todo check: git_autocommit is working or not if (confIsTrue('git_autocommit', false) && conf.git_autocommit_work_tree === "") { actions.push($("
Show change log
").on("click", function(){ showChangeLog(); })); } buildLeftContextMenu(actions, e); } }); // Click handler for left pane items $ce_file_list.on("click", ".ce_leftnav", function(){ var elem = $(this); // click on a conf file if (elem.hasClass("ce_conf")) { runBToolList($(this).attr('file'), 'btool'); // click on file } else if (elem.hasClass("ce_is_report")) { readFile(elem.attr('file')); // recent files list } else if (elem.hasClass("ce_leftnav_reopen")) { hooksCfg[elem.attr('type')](elem.attr('file')); // rest app } else if (elem.hasClass("ce_leftnav_restapp")) { inFolderRestApp = elem.attr('restapp'); leftPaneRestList(); // rest type } else if (elem.hasClass("ce_leftnav_resttype")) { inFolderRestType = elem.attr('resttype'); leftPaneRestList(); // rest file } else if (elem.hasClass("ce_leftnav_restfile")) { readFileRest(elem.attr('restfile')); // Folder } else { if (! leftpane_ignore) { readFolder(elem.attr('file'), 'fwd'); } } // Right click menu for left pane }).on("contextmenu", ".ce_leftnav", function (e) { var $leftnavElem = $(this); var isFile = $leftnavElem.hasClass("ce_is_report"); var thisFile = $leftnavElem.attr('file'); var actions = []; var j; if (isFile) { // Add the custom hook actions for (j = 0; j < hooksActive.length; j++) { addHookActionToTree(hooksActive[j], thisFile, actions, "file"); } // gate behind flag if (confIsTrue('dashboard_xml_file_experimental_actions', false)) { var file_parts_for_openurl = thisFile.match(regex_file_path_to_dashboard_parts); if (file_parts_for_openurl) { actions.push($("
Attempt view in browser
").on("click", function(){ hooksCfg.openurl("/app/" + file_parts_for_openurl[2] + "/" + file_parts_for_openurl[3]); })); actions.push($("
Attempt edit via REST API
").on("click", 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 ($leftnavElem.hasClass("ce_is_folder")) { for (j = 0; j < hooksActive.length; j++) { addHookActionToTree(hooksActive[j], thisFile, actions, "folder"); } } if ($leftnavElem.hasClass("ce_leftnav_editable") && confIsTrue('write_access', false)) { // can rename, can trash actions.push($("
Rename
").on("click", function(){ fileSystemRename(thisFile); })); actions.push($("
Delete
").on("click", function(){ filesystemDelete(thisFile); })); } if ($leftnavElem.hasClass("ce_conf")) { for (j = 0; j < hooksActive.length; j++) { addHookActionToTree(hooksActive[j], thisFile, actions, "conf"); } actions.push($("
Show btool (hide paths)
").on("click", function(){ runBToolList(thisFile, 'btool-hidepaths'); })); actions.push($("
Show btool (hide all defaults)
").on("click", function(){ runBToolList(thisFile, 'btool-hidedefaults'); })); actions.push($("
Show btool (hide system defaults)
").on("click", function(){ runBToolList(thisFile, 'btool-hidesystemdefaults'); })); if (conf.btool_dir_for_master_apps) { actions.push($("
[master-apps] Show btool
").on("click", function(){ runBToolList(thisFile + ":" + conf.btool_dir_for_master_apps, 'btool', conf.btool_dir_for_master_apps, "/splunk/etc/master-apps/"); })); actions.push($("
[master-apps] Show btool (hide all defaults)
").on("click", function(){ runBToolList(thisFile + ":" + conf.btool_dir_for_master_apps, 'btool-hidedefaults', conf.btool_dir_for_master_apps, "/splunk/etc/master-apps/"); })); } if (conf.btool_dir_for_manager_apps) { actions.push($("
[manager-apps] Show btool
").on("click", function(){ runBToolList(thisFile + ":" + conf.btool_dir_for_manager_apps, 'btool', conf.btool_dir_for_manager_apps, "/splunk/etc/manager-apps/"); })); actions.push($("
[manager-apps] Show btool (hide all defaults)
").on("click", function(){ runBToolList(thisFile + ":" + conf.btool_dir_for_manager_apps, 'btool-hidedefaults', conf.btool_dir_for_manager_apps, "/splunk/etc/manager-apps/"); })); } if (conf.btool_dir_for_deployment_apps) { actions.push($("
[deployment-apps] Show btool
").on("click", function(){ runBToolList(thisFile + ":" + conf.btool_dir_for_deployment_apps, 'btool', conf.btool_dir_for_deployment_apps, "/splunk/etc/deployment-apps/"); })); actions.push($("
[deployment-apps] Show btool (hide all defaults)
").on("click", function(){ runBToolList(thisFile + ":" + conf.btool_dir_for_deployment_apps, 'btool-hidedefaults', conf.btool_dir_for_deployment_apps, "/splunk/etc/deployment-apps/"); })); } if (conf.btool_dir_for_shcluster_apps) { actions.push($("
[shcluster-apps] Show btool
").on("click", function(){ runBToolList(thisFile + ":" + conf.btool_dir_for_shcluster_apps, 'btool'); })); actions.push($("
[shcluster-apps] Show btool (hide all defaults)
").on("click", function(){ runBToolList(thisFile + ":" + conf.btool_dir_for_shcluster_apps, 'btool-hidedefaults'); })); } actions.push($("
Show .spec file
").on("click", function(){ displaySpecFile(thisFile); })); actions.push($("
Show live (running) config
").on("click", function(){ runningVsLayered(thisFile, false); })); actions.push($("
Compare live config against btool output
").on("click", function(){ runningVsLayered(thisFile, true); })); } if (isFile) { if (confIsTrue('git_autocommit', false)) { // can show history actions.push($("
View file history
").on("click", function(){ getFileHistory(thisFile, inFolder); })); } // download file actions.push($("
Download
").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($("
Mark for comparison
").on("click", function(){ comparisonLeftFile = thisFile; })); if (comparisonLeftFile && comparisonLeftFile !== thisFile) { actions.push($("
Compare to " + htmlEncode(dodgyBasename(comparisonLeftFile)) + "
").on("click", function(){ compareFiles(thisFile, comparisonLeftFile); })); } } if ($leftnavElem.hasClass("ce_leftnav_restfile")) { var file_parts_for_openurl_rest = $leftnavElem.attr('restfile').match(/([^\/]+)\/data\/ui\/views\/(.*)$/); if (file_parts_for_openurl_rest) { actions.push($("
Attempt view in browser
").on("click", function(){ hooksCfg.openurl("/app/" + file_parts_for_openurl_rest[1] + "/" + file_parts_for_openurl_rest[2]); })); } } if (actions.length) { buildLeftContextMenu(actions, e, $leftnavElem); } }); // Event handlers for the editor tabs $ce_tabs.on("click", ".ce_close_tab", function(e){ e.stopPropagation(); closeTabWithConfirmation($(this).parent().index()); // Middle click to close tab }).on("auxclick", ".ce_tab", function(e){ if (e.which !== 3) { e.stopPropagation(); closeTabWithConfirmation($(this).index()); } // Clicking tab }).on("click", ".ce_tab", function(){ activateTab($(this).index()); // On hover show the cross }).on("mouseenter", ".ce_tab", function(){ $(this).append(""); }).on("mouseleave", ".ce_tab", function(){ $(this).find('.ce_close_tab').remove(); }); function buildLeftContextMenu(actions, e, $t) { e.preventDefault(); // To prevent the default context menu. if (! actions.length) { return; } e.stopPropagation(); var $menu = $(".ce_context_menu_wrap"); $menu.empty().append(actions); $(".ce_leftnav_highlighted").removeClass("ce_leftnav_highlighted"); if ($t) { $t.addClass("ce_leftnav_highlighted"); } var windowHeight = $(window).height(); if((e.clientY + 200) > windowHeight) { $menu.css({opacity:1, left:30 + e.clientX, bottom:$(window).height()-e.clientY, right:"auto", top:"auto"}); } else { $menu.css({opacity:1, left:30 + e.clientX, bottom:"auto", right:"auto", top: e.clientY - 30}); } $ce_context_menu_overlay.removeClass("ce_hidden"); $(document).one("click contextmenu", function(e2) { $menu.removeAttr("style"); $ce_context_menu_overlay.addClass("ce_hidden"); if ($t) { $t.removeClass("ce_leftnav_highlighted"); } e2.preventDefault(); }); } function compareFiles(rightFile, leftFile) { var ecfg = createTab('diff', rightFile + " " + leftFile, "diff: " + rightFile + " " + leftFile); ecfg.diffFileRight = rightFile; ecfg.diffFileLeft = leftFile; // get both files Promise.all([ serverActionWithoutFlicker({action: 'read', path: leftFile}), serverActionWithoutFlicker({action: 'read', path: rightFile}), ]).then(function(contents){ updateTabAsDiffer(ecfg, leftFile + "\n" + contents[0], rightFile + "\n" + contents[1]); }).catch(function(){ closeTabByCfg(ecfg); }); } function debugRefreshBumpHook(endpoint, embeddedMode){ // get localisation url var url = "/" + document.location.pathname.split("/")[1] + "/"; var label = ""; var ecfg; if (typeof embeddedMode === "undefined") { embeddedMode = false; } if (endpoint && endpoint === "?") { debugRefreshEndpointSelection(); return; } if (endpoint && endpoint !== "all") { if (endpoint === "bump") { url += "_bump"; label = "bump"; } else { url += "debug/refresh?entity=" + endpoint; label = "debug/refresh: " + endpoint; } } else { label = "debug/refresh: all"; url += "debug/refresh"; endpoint = "all"; } if (embeddedMode) { ecfg = createTabSidecar(label); } else { // Load as a stand alone tab ecfg = createTab('refresh', endpoint, label); } $.post(url, function(data){ if (endpoint === "bump") { data = $('
').html(data).text(); } else { data = data.replace(/'''[\s\S]*'''/,""); } if (embeddedMode) { updateSidecarAsEditor(ecfg, data); } else { updateTabAsEditor(ecfg, data, 'plaintext'); } }).fail(function(jqXHR, textStatus, errorThrown) { if (embeddedMode) { updateSidecarAsEditor(ecfg, "Status code: " + jqXHR.status + "\n\n" + errorThrown); } else { closeTabByCfg(ecfg); showModal({ title: "Error", body: "
" + label + " - Error occurred!

Status code: " + jqXHR.status + "

" + htmlEncode(errorThrown) + "
", }); } }); } function changeDirectory(dir){ if (dir) { $(".ce_show_filesystem ").click(); readFolder(dir.replace(/[\/\\]$/g, ""), 'fwd'); } } function replaceTokens(str, file){ var basefile = dodgyBasename(file); var dirname = dodgyDirname(file); return str.replace(/\$\{(FILE|BASEFILE|DIRNAME)(?:,(\-?\d+))?(?:,(\-?\d+))?\}/g,function(all, g1, g2, g3){ var ret, l; if (g1=="FILE") { ret = file; } if (g1=="BASEFILE") { ret = basefile; } if (g1=="DIRNAME") { ret = dirname; } l = ret.length; if (typeof g3 !== "undefined") { g2 = parseInt(g2); g3 = parseInt(g3); return ret.substr(g2 < 0 ? l + g2 : g2, g3 < 0 ? l + g3 : g3); } if (typeof g2 !== "undefined") { g2 = parseInt(g2); return ret.substr(g2 < 0 ? l + g2 : g2); } return ret; }); } function runAction(actionStr, file, embeddedMode) { var parts = actionStr.split(":"); var action = parts.shift(); var args = parts.join(":"); if (file !== undefined) { args = replaceTokens(args, file); } hooksCfg[action](args, undefined, embeddedMode); } // Keep track of what tabs are open in local storage. function openTabsListChanged(){ var t = []; for (var j = 0; j < editors.length; j++){ editors[j].position = editors[j].tab.index(); } editors.sort(function(a,b){ if (a.position > b.position) { return 1; } else if (b.position > a.position) { return -1; } else { return 0; } }); activeTab = $ce_tabs.children(".ce_active").index(); for (var i = 0; i < editors.length; i++){ if (tabCfg[editors[i].type].can_reopen) { t.push({label: editors[i].label, type: editors[i].type, file: editors[i].file}); } } localStorage.setItem('ce_open_tabs', JSON.stringify(t)); updateUrlHash(); } function updateUrlHash(){ var hashparts = [inFolder], last_used_idx, newest; for (var i = 0; i < editors.length; i++){ if (tabCfg[editors[i].type].can_reopen) { if (! newest || newest < editors[i].last_opened) { newest = editors[i].last_opened; last_used_idx = (hashparts.length - 1) / 2; } hashparts.push(editors[i].type); hashparts.push(editors[i].file); } } hashparts.unshift(last_used_idx); if (history.replaceState) { history.replaceState(null, null, '#' + hashparts.join("|")); } else { location.hash = '#' + encodeURIComponent(hashparts.join("|")); } } function readUrlHash(){ var parts = decodeURIComponent(document.location.hash.substr(1)).split("|"); if (parts.length > 1) { inFolder = parts[1]; for (var i = 2; (i + 1) < parts.length; i+=2) { // check to make sure its allowed first! This protects against someone crafting a bad url (e.g. a "run" command) and sending it to a victim if (tabCfg[parts[i]].can_reopen) { var file_parts = parts[i+1].split("@"); if (file_parts.length > 1) { doLineHighlight = file_parts[1]; hooksCfg[parts[i]](file_parts[0]); } else { hooksCfg[parts[i]](parts[i+1]); } } } var tabIdx = parseInt(parts[0],10); if (parts.length > 2 && !isNaN(tabIdx)) { activateTab(tabIdx); } } } // This can be used like so: // var bc = new BroadcastChannel("config_explorer"); bc.postMessage({action:"open",type:"rest",path:"/servicesNS/admin/search/data/ui/views/aaa_dump"}) function handleBroadcastMessage(event) { console.log("Received broadcast message", event); if (event.data && event.data.hasOwnProperty("action") && event.data.action==="open") { if (tabCfg[event.data.type].can_reopen) { hooksCfg[event.data.type](event.data.path); broadcastChannel.postMessage({ action:"opened", type: event.data.type, path: event.data.path }); } } } function getPreferences() { var preferences; try { preferences = JSON.parse(localStorage.getItem('ce_preferences')); } catch(e) {} if (! preferences || $.isEmptyObject(preferences)) { preferences = { "cursorBlinking": "blink", "cursorSmoothCaretAnimation": false, "minimap": { "renderCharacters": true, "showSlider": "mouseover" } }; } return preferences; } // This is an action that will occur after saving the file function openPreferences() { showModal({ title: "Preferences", size: 500, body: "
Preferences will only affect sessions from this browser."+ "

Enable word-wrap "+ "
Enable visible whitespace "+ //"
Reuse tabs for post-save actions "+ "
Hover tooltip "+ "
Show full file path in tab (refresh required)"+ "

Advanced editor options (See here)
"+ "
"+ "
", 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 = "

Working directory:

"+ "
"+ "

"; } showModal({ title: "Run ", size: 600, body: "
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: "
Select server class to reload.

All server classes
", onShow: function(){ var $serverClasses = $(".ce-server-classes"); $ce_spinner.clone().appendTo($serverClasses); service.get('/services/deployment/server/serverclasses', {count:0, f:"name"}, function(err, r) { if (err) { console.error(err); showModal({ title: "Error", body: "
Unexpected error occurred retreiving serverclasses list

" + htmlEncode(JSON.stringify(err)) + "
", }); } var str = ""; //console.log("resp",r.data); if (r && r.data && r.data.entry) { for (var i = 0; i < r.data.entry.length; i++) { str += "" + htmlEncode(r.data.entry[i].name) + ""; } } $serverClasses.html(str); $(".ce-deployserver").on("click","a",function(e){ e.preventDefault(); e.stopPropagation(); var $this = $(this); $(".modal").modal('hide'); if ($this.hasClass("ce-server-classes-all")) { runReloadDeployServerNow(); } else { runReloadDeployServerNow($this.text()); //runShellCommandNow("./bin/splunk reload deploy-server -class \"" + $this.text() + "\""); } }); }); }, actions: [{ onClick: function(){ $(".modal").modal('hide'); }, label: "Cancel" }] }); } function runReloadDeployServerNow(srvclass) { if (typeof srvclass === undefined) { srvclass = ""; } var command = "reload deploy-server"; if (tabAlreadyOpen('deployserver', "")) { closeTabByName('deployserver', ""); } var ecfg = createTab('deployserver', "", '$ ' + htmlEncode(command)); serverActionWithoutFlicker({action: 'deployserver', path: srvclass}).then(function(contents){ updateTabAsEditor(ecfg, contents, 'plaintext'); }).catch(function(){ closeTabByCfg(ecfg); }); } function runShellCommandNow(command, fromFolder, embeddedMode){ if (typeof embeddedMode === "undefined") { embeddedMode = false; } var ecfg; var label = '$ ' + htmlEncode(command); var timer = $("
"); if (embeddedMode) { ecfg = createTabSidecar(label); timer.appendTo(ecfg.sidecar_editor_container); } else { ecfg = createTab('run', command, label); timer.appendTo(ecfg.editor_container); } //var cancel = $("").appendTo(ecfg.container); var started = Date.now(); var interval = setInterval(function() { var tt = Math.round((Date.now() - started) / 1000); if (tt > 2) { timer.html(tt + " sec"); } },1000); // trim length if (run_history.length > 50) { run_history.shift(); } // only save if the command is different to what was last run if (command !== run_history[(run_history.length - 1)]) { run_history.push(command); } // save to localstorage localStorage.setItem('ce_run_history', JSON.stringify(run_history)); if (typeof fromFolder === "undefined") { fromFolder = ""; } ecfg.fromFolder = fromFolder; serverActionWithoutFlicker({action: 'run', path: command, param1: fromFolder}).then(function(contents){ clearInterval(interval); if (embeddedMode) { updateSidecarAsEditor(ecfg, contents); } else { if (/git +diff/.test(command)) { updateTabAsEditor(ecfg, contents, "git-diff"); } else if (/git +log/.test(command)) { updateTabAsEditor(ecfg, contents, "git-log"); } else { updateTabAsEditor(ecfg, contents, 'plaintext'); } } // refresh left pane refreshFolder(); }).catch(function(){ clearInterval(interval); if (embeddedMode) { updateSidecarAsEditor(ecfg, "An unexpected error occured."); } else { closeTabByCfg(ecfg); } }); } // Check config function runBToolCheck() { var ecfg = createTab('btool-check', 'btool-check', 'btool check '); serverActionWithoutFlicker({action: 'btool-check'}).then(function(contents){ contents = contents.replace(/^(No spec file for|Checking):.*\r?\n/mg,'').replace(/^\t\t/mg,'').replace(/\n{2,}/g,'\n\n'); if ($.trim(contents)) { updateTabAsEditor(ecfg, contents, 'plaintext'); } else { closeTabByCfg(ecfg); showModal({ title: "Info", body: "
No configuration errors found
", size: 300 }); } }).catch(function(){ closeTabByCfg(ecfg); }); } function getRest(path, args) { return new Promise(function(resolve, reject){ service.get(path, args, function(err, r) { if (err) { reject(err); } if (r && r.data && r.data.entry) { // for (var i = 0; i < r.data.entry.length; i++) { // str += "[" + r.data.entry[i].name + "]\n"; // var props = Object.keys(r.data.entry[i].content); // props.sort(); // for (var j = 0; j < props.length; j++) { // if (props[j].substr(0,4) !== "eai:" && !(props[j] === "disabled" && ! r.data.entry[i].content[props[j]])) { // str += props[j] + " = " + r.data.entry[i].content[props[j]] + "\n"; // } // } // } //if (str) { resolve(r.data.entry); //} else { // reject(); //} } else { reject(); } }); }); } function getRunningConfig(path) { path = path.replace(/\.conf$/i,""); return new Promise(function(resolve, reject){ service.get('/servicesNS/-/-/configs/conf-' + path, null, function(err, r) { if (err) { reject(err); } var str = ""; if (r && r.data && r.data.entry) { for (var i = 0; i < r.data.entry.length; i++) { str += "[" + r.data.entry[i].name + "]\n"; var props = Object.keys(r.data.entry[i].content); props.sort(); for (var j = 0; j < props.length; j++) { if (props[j].substr(0,4) !== "eai:" && !(props[j] === "disabled" && ! r.data.entry[i].content[props[j]])) { str += props[j] + " = " + r.data.entry[i].content[props[j]] + "\n"; } } } if (str) { resolve(str); } else { reject(); } } else { reject(); } }); }); } function runningVsLayered(path, compare){ path = path.replace(/\.conf$/i,""); var type = 'live'; var tab_path_fmt = 'live: ' + path; if (compare) { type = 'live-diff'; tab_path_fmt = 'live/btool: ' + path; } if (! tabAlreadyOpen(type, path)) { var ecfg = createTab(type, path, tab_path_fmt); serverActionWithoutFlicker({action: 'btool-list', path: path}).then(function(contents){ var c = formatBtoolList(contents, "btool-hidepaths"); if ($.trim(c)) { getRunningConfig(path).then(function(contents_running){ if (compare) { updateTabAsDiffer( ecfg, "# Filesystem config\n" + formatLikeRunningConfig(contents), "# Running config\n" + contents_running ); } else { updateTabAsEditor(ecfg, contents_running, 'ini'); } }).catch(function(){ closeTabByCfg(ecfg); showModal({ title: "Warning", body: "
Could not get retreieve running config for " + htmlEncode(path) + "
", size: 300 }); }); } else { closeTabByCfg(ecfg); showModal({ title: "Error", body: "
No btool data returned for " + htmlEncode(path) + "
", size: 300 }); } }).catch(function(){ closeTabByCfg(ecfg); }); } } function runBToolList(inPath, type, origPath, replacementPath){ var parts = inPath.split(":"); var conf_file = parts.shift().replace(/\.conf$/i, ""); var path = parts.join(":"); // There are three special "paths" which we replace with the dynamic value if (path === "deployment-apps"){ if (conf.btool_dir_for_deployment_apps) { path = conf.btool_dir_for_deployment_apps; origPath = conf.btool_dir_for_deployment_apps; replacementPath = "/splunk/etc/deployment-apps/"; } else { path = ""; } } if (path === "master-apps") { if (conf.btool_dir_for_master_apps) { path = conf.btool_dir_for_master_apps; origPath = conf.btool_dir_for_master_apps; replacementPath = "/splunk/etc/master-apps/"; } else { path = ""; } } if (path === "manager-apps") { if (conf.btool_dir_for_manager_apps) { path = conf.btool_dir_for_manager_apps; origPath = conf.btool_dir_for_manager_apps; replacementPath = "/splunk/etc/manager-apps/"; } else { path = ""; } } if (path === "shcluster") { if (conf.btool_dir_for_shcluster_apps) { path = conf.btool_dir_for_shcluster_apps; } else { path = ""; } } var tab_path_fmt = 'btool: ' + conf_file + (type === "btool" ? "" : " (" + type.substr(6) + ")"); if (tabAlreadyOpen(type, conf_file + ":" + path)) { closeTabByName(type, conf_file + ":" + path); } var ecfg = createTab(type, conf_file + ":" + path, tab_path_fmt); serverActionWithoutFlicker({action: 'btool-list', path: conf_file, param1: path}).then(function(contents){ if (typeof origPath !== "undefined") { contents = adjustBtoolPaths(contents, origPath, replacementPath); } var c = formatBtoolList(contents, type); if ($.trim(c)) { updateTabAsEditor(ecfg, c, 'ini'); ecfg.btoollist = contents; serverAction({action: 'spec-hinting', path: conf_file}).then(function(h){ ecfg.hinting = buildHintingLookup(conf_file, h); }); } else { closeTabByCfg(ecfg); showModal({ title: "Warning", body: "
No contents for \"" + htmlEncode(tab_path_fmt) + "\"
", size: 300 }); } }).catch(function(){ console.error(arguments); closeTabByCfg(ecfg); }); } function displaySpecFile(path) { path = path.replace(/\.conf$/i,""); var tab_path_fmt = 'spec: ' + path; if (! tabAlreadyOpen('spec', path)) { var ecfg = createTab('spec', path, tab_path_fmt); serverActionWithoutFlicker({action: 'spec', path: path}).then(function(contents) { if ($.trim(contents)) { updateTabAsEditor(ecfg, contents, 'ini'); } else { closeTabByCfg(ecfg); showModal({ title: "Error", body: "
No spec file found!
", 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(); $("
Error loading folder contents
(was folder deleted?)

Retry or Go to SPLUNK_HOME
").appendTo($ce_file_wrap); $ce_file_wrap.find(".ce_tree_retry").on("click", function(){ return readFolderFromServer(path, direction); }); $ce_file_wrap.find(".ce_tree_reset").on("click", function(){ return readFolderFromServer("."); }); }); } function readFolderLoad(path){ inFolder = path; updateUrlHash(); localStorage.setItem('ce_current_path', inFolder); folderContents.sort(function(a, b) { if (a.dir === b.dir) { if (sortMode === "size" || sortMode === "time") { if (sortAsc) { return a[sortMode] > b[sortMode] ? -1 : a[sortMode] < b[sortMode] ? 1 : a.sortkey > b.sortkey ? 1 : a.sortkey < b.sortkey ? -1 : 0; } return a[sortMode] > b[sortMode] ? 1 : a[sortMode] < b[sortMode] ? -1 : a.sortkey > b.sortkey ? 1 : a.sortkey < b.sortkey ? -1 : 0; } // sort by name if (sortAsc) { return a.sortkey > b.sortkey ? 1 : a.sortkey < b.sortkey ? -1 : 0; } return a.sortkey > b.sortkey ? -1 : a.sortkey < b.sortkey ? 1 : 0; } return b.dir ? 1 : -1; }); leftPaneFileList(); } function filePathRTLCheck() { var span = $ce_file_path.find("span"); if (span.attr("title")) { if (span.width() > $ce_file_path.width()) { $ce_file_path.addClass('ce_rtl'); } else { $ce_file_path.removeClass('ce_rtl'); } } } function leftPaneRemoveSpinner(){ if (leftpane_ignore) { if (leftpane_ignore === 'fwd') { $ce_file_wrap.css({transition: "all 0ms", transform: "translate(200px, 0px)", opacity: 0}); } else if (leftpane_ignore === 'back') { $ce_file_wrap.css({transition: "all 0ms", transform: "translate(-200px, 0px)", opacity: 0}); } else { $ce_file_wrap.css({transition: "all 0ms", transform: "", opacity: 0}); } $ce_file_wrap.removeClass("ce_move_none ce_move_fwd ce_move_back"); $ce_tree_pane.find(".ce_spinner, .ce_fs_slow_message").remove(); setTimeout(function(){ $ce_file_wrap.css({transition: "", transform: "", opacity: ""}); },0); leftpane_ignore = false; } } function formatSize(bytes) { if (bytes === 0) return '0 B'; var k = 1024; var dm = 2; var sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; var i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] + ''; } function formatDate(val) { var d = new Date(val*1000); return d.toLocaleString(); } function leftPaneFileList(filter){ leftPaneRemoveSpinner(); $ce_file_wrap.empty(); $ce_file_path.empty(); $ce_tree_icons.find('i').removeClass("ce_disabled"); if (inFolder === ".") { $(".ce_folder_up").addClass("ce_disabled"); } else { $(".ce_folder_up").removeClass("ce_disabled"); } $("").appendTo($ce_file_path); var files = 0; var filter_re; if (filter) { filter_re = new RegExp(escapeRegExp(filter), 'gi'); } for (var i = 0; i < folderContents.length; i++) { var item = folderContents[i].name; if (! filter || item.toLowerCase().indexOf(filter) > -1) { var icon = (folderContents[i].dir) ? "folder" : "report"; var text = htmlEncode(item); if (filter) { text = text.replace(filter_re, "$&"); } $("
"+ " " + "
" + text + "
"+ "
" + (folderContents[i].size > -1 && folderContents[i].hasOwnProperty("size") ? formatSize(folderContents[i].size) : "") + "
"+ "
" + (folderContents[i].time > -1 && folderContents[i].hasOwnProperty("time") ? formatDate(folderContents[i].time) : "") + "
"+ "
").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, "$&"); } $("
"+ " " + "
" + appListArray[i].label + " " + appListArray[i].count + "
"+ "
").attr("restapp", appListArray[i].app).appendTo($ce_file_wrap); } } } else { // sort views / etc restTypes[type].cache.sort(function(a, b) { if (sortModeRest==="label"){ if (displayModeRest==="l" || displayModeRest==="ln") { return sortAscRest==="1" ? a.label.localeCompare(b.label) : b.label.localeCompare(a.label); } else { return sortAscRest==="1" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name); } } else if ( sortModeRest==="time"){ return sortAscRest==="1" ? a.date - b.date : b.date - a.date; } }); for (i = 0; i < restTypes[type].cache.length; i++) { if (restTypes[type].cache[i].app === inFolderRestApp) { if (! filter || restTypes[type].cache[i].name.toLowerCase().indexOf(filter) > -1 || restTypes[type].cache[i].label.toLowerCase().indexOf(filter) > -1) { files = true; if (! restTypes[type].cache[i].label || displayModeRest==="n") { label = htmlEncode(restTypes[type].cache[i].name); } else if (displayModeRest==="l") { label = htmlEncode(restTypes[type].cache[i].label); } else if (displayModeRest==="ln") { label = htmlEncode(restTypes[type].cache[i].label) + " (" + htmlEncode(restTypes[type].cache[i].name) + ")"; } else { //nl label = htmlEncode(restTypes[type].cache[i].name) + " (" + htmlEncode(restTypes[type].cache[i].label) + ")"; } if (filter) { label = label.replace(filter_re, "$&"); } $("
"+ "
" + label + "
"+ "
").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) + "
", size: 300 }); closeTabByCfg(ecfg); } else if (restData.length === 1) { updateTabAsEditor(ecfg, restData[0].content['eai:data'], "xml"); } else { showModal({ title: "Warning", body: "
unexpectedly returned too many items from REST API at: " + htmlEncode(path) + "
", size: 300 }); closeTabByCfg(ecfg); } }).catch(function(){ showModal({ title: "Warning", body: "
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: "
" + $warn_msg.attr("title").replace(/\..*/,"") + "
", size: 600, actions: [{ label: "Save anyway", onClick: function(){ $(".modal").modal('hide'); // set a flag and resave ecfg.modified_on_disk_ignored = true; saveActiveTab(cb); } },{ label: "Cancel and diff", onClick: function(){ $(".modal").modal('hide'); openDiffOfUnsavedChanges(ecfg); } },{ label: "Cancel", onClick: function(){ $(".modal").modal('hide'); } }] }); return; } saved_value = ecfg.editor.getValue(); ecfg.saving = true; ecfg.tab.append(""); serverAction({action: 'save', path: ecfg.file, file: saved_value}).then(function(){ // After file has been saved, delete its filemod time and get a new filemod time delete ecfg.filemod; // remove unsaved changes icon from tab ecfg.tab.find('.ce_modified_on_disk').remove(); ecfg.tab.css("color",""); clearTimeout(fileModsCheckTimer); fileModsCheckTimer = setTimeout(function(){ checkFileMods(); }, 100); ecfg.saving = false; ecfg.modified_on_disk_ignored = false; ecfg.tab.find('.ce_tab_saving_icon').remove(); showToast('Saved'); ecfg.server_content = saved_value; ecfg.tab.find('.icon-alert-circle').remove(); ecfg.hasChanges = false; highlightBadConfig(ecfg); if (ecfg.file === "") { loadPermissionsAndConfList(); } if (cb) { cb(); } // Run any post-save hook var p = getPostSave(ecfg.file); if (p !== null) { if (p[0] === "run" && ! approvedPostSaveHooks.hasOwnProperty(p[0] + "|" + p[1])) { showModal({ title: "Confirm post-save action ", body: "
The following action is configured to run after every save:

" + htmlEncode(p[0]) + ":" + htmlEncode(p[1]) + "
", size: 600, actions: [{ onClick: function(){ $(".modal").modal('hide'); approvedPostSaveHooks[p[0] + "|" + p[1]] = true; runPostSaveAction(p); }, cssClass: 'btn-primary', label: "Approve" },{ onClick: function(){ $(".modal").modal('hide'); }, label: "Cancel" }] }); } else { runPostSaveAction(p); } } }, function(){ ecfg.saving = false; ecfg.tab.find('.ce_tab_saving_icon').remove(); }); } return null; } else if (ecfg.canBeSavedRest) { if (! ecfg.saving) { saved_value = ecfg.editor.getValue(); ecfg.saving = true; ecfg.tab.append(""); service.post(ecfg.file, {"eai:data": saved_value}, function(err, r) { if (err) { showModal({ title: "Warning", body: "
Error while saving:
" + htmlEncode(err) + "
", // err.data.messages["0"].text size: 500 }); console.log(err); ecfg.saving = false; ecfg.tab.find('.ce_tab_saving_icon').remove(); } if (r && r.data) { //console.log("ok saving to rest", r.data); ecfg.tab.find('.ce_modified_on_disk').remove(); ecfg.tab.css("color",""); clearTimeout(fileModsCheckTimer); fileModsCheckTimer = setTimeout(function(){ checkFileMods(); }, 100); ecfg.saving = false; ecfg.tab.find('.ce_tab_saving_icon').remove(); showToast('Saved'); ecfg.server_content = saved_value; ecfg.tab.find('.icon-alert-circle').remove(); ecfg.hasChanges = false; // send broadcast message: broadcastChannel.postMessage({ action:"saved", type: ecfg.type, path: ecfg.file }); } else { console.error("unexpected error when saving", r.data); ecfg.saving = false; ecfg.tab.find('.ce_tab_saving_icon').remove(); showModal({ title: "Warning", body: "
Unpexpected error while saving!
", // err.data.messages["0"].text size: 500 }); } }); } return null; } else { showModal({ title: "Warning", body: "
This file cannot be saved
", size: 300 }); } } function runPostSaveAction(parts) { closeTabByHookDetails(parts[0], parts[1]); hooksCfg[parts[0]](parts[1], undefined, true); } function updateTabHTML(ecfg, contents) { ecfg.editor_container.html(contents).css("overflow", "auto"); ecfg.tab.trigger("ce_loaded"); } function updateTabAsDiffer(ecfg, left, right) { var originalModel = monaco.editor.createModel(left); var modifiedModel = monaco.editor.createModel(right); ecfg.editor_container.empty(); ecfg.editor = monaco.editor.createDiffEditor(ecfg.editor_container[0],{ automaticLayout: true, }); ecfg.editor.setModel({ original: originalModel, modified: modifiedModel }); ecfg.tab.trigger("ce_loaded"); if (ecfg.hasOwnProperty("diffFileRight")) { ecfg.editor.addAction({ id: 'updatediffer', contextMenuOrder: 0.3, contextMenuGroupId: 'navigation', label: 'Rerun diff', run: function() { compareFiles(ecfg.diffFileRight, ecfg.diffFileLeft); closeTabByCfg(ecfg); } }); } } // Make sure the server action that results in a tab open, takes a minimum amount of time so as not to flicker and look dumb function serverActionWithoutFlicker(postData, customErrorCB) { var promise = serverAction(postData, customErrorCB); var promiseTimeout = new Promise(function(resolve) { setTimeout(resolve, 800); }); var promiseCombined = Promise.all([promise, promiseTimeout]); return promiseCombined.then(function(values) { return values[0]; }); } // Make a rest call to our backend python script function serverAction(postData, customErrorCB) { //console.log("CBY SERVERACTION:", postData); return new Promise(function(resolve, reject) { inFlightRequests++; $('.ce_saving_icon').removeClass('ce_hidden'); //console.log(postData); service.post('/services/config_explorer', postData, function(err, r) { inFlightRequests--; if (inFlightRequests === 0) { $('.ce_saving_icon').addClass('ce_hidden'); } var errText = ''; //console.log(err, r); if (err) { if (err.data.hasOwnProperty('messages')) { errText = "
" + htmlEncode(err.data.messages["0"].text) + "
"; } else { errText = "
" + htmlEncode(JSON.stringify(err)) + "
"; } } else { if (! r.data) { errText = "
Error communicating with Splunk
"; } else if (! (r.data.hasOwnProperty('result') || r.data.hasOwnProperty('reason'))) { errText = "
" + htmlEncode(r.data) + "
"; } else if (r.data.reason === "missing_perm_read") { errText = "

To use this application you must be have the capability \"admin_all_objects\" via a role.

"; } else if (r.data.reason === "missing_perm_run") { errText = "

You must enable the run_commands setting

"; } else if (r.data.reason === "missing_perm_write") { errText = "

You must enable the write_access setting

"; } else if (r.data.reason === "config_locked") { errText = "

Unable to write to the settings file becuase it is locked and must be edited externally: etc/apps/config_explorer/local/config_explorer.conf

"; } else if (r.data.reason === "binary_file") { errText = "

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: "
An error occurred!

" + htmlEncode(postData.action) + ": " + htmlEncode(postData.hasOwnProperty("path") ? postData.path : "") + "


" + errText + "
", }); } 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:

"; for (var j = 0; j < r.data.git.length; j++) { git_output += "
" + htmlEncode($.trim(r.data.git[j].content)) + "
"; } var ecfg = createTab('git', '', 'git output'); updateTabHTML(ecfg, "
" + git_output + "
"); } } 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; } 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; } 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 = $('').appendTo('body'); self.$modal.on('shown.bs.modal', function (e) { self.onShow.call(this, e); }); self.$modal.on('hidden.bs.modal', function (e) { self.onHide.call(this, e); }); } self.$modal.css({'width': options.size + "px", 'margin-left': -1 * (options.size / 2) + "px"}); self.$modal.data('bs.modal', false); self.$modal.find('.modal-dialog').removeClass().addClass('modal-dialog '); self.$modal.find('.modal-content').html(''.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.

" + htmlEncode(ecfg.file) + "
", size: 600 }); } else if (restData.length === 1) { if (ecfg.server_content !== restData[0].content['eai:data']) { ecfg.tab.css("color","orange").append(""); showModal({ title: "Warning", body: "
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('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.' + "
", onShow: function(){ $('.ce_quicksettings').on("click",function(e){ e.preventDefault(); readFile(""); $(".modal").modal('hide'); }); }, actions: [{ onClick: function(){ $(".modal").modal('hide'); }, cssClass: 'btn-primary', label: "OK" }] }); } // Build the left pane showTreePaneSpinner(); if (Number(conf.cache_file_depth) > 0) { var fsCompleted = false; serverAction({action:'fs'}).then(function(contents){ filecache = contents; readFolder(inFolder); fsCompleted = true; }); setTimeout(function(){ if (!fsCompleted) { $("
Taking too long?
Reduce cache_file_depth in Settings
").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(); } }); }); });