// Converts bytes to a human readable value. function bytesToHuman(bytes) { var i = Math.floor(Math.log(bytes) / Math.log(1024)), sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; } // A timer for when messages are shown on screen to auto hide. var messageTimer = null; // Hide the message on screen. function hideMessage() { // Get the message div. var message = document.getElementById("message"); message.style.display = "none"; // Do not display. // Clear the message timer. clearTimeout(messageTimer); messageTimer = null; } // Display a standard message on screen. function displayMessage(message) { displayMessage(message, "cadetblue", true); } // Display a sucessful message with green color. function displaySuccess(message) { displayMessage(message, "green", true); } // Display an error message with red color. function displayError(message) { displayMessage(message, "red", true); } // Display a message with a timeout and color specified. function displayMessage(message, color, timeout) { // If no color defined, we use cadetblue. if (color == undefined) { color = "cadetblue"; } // Log the message to the javascript console. console.log(message); // Get the message div. var messageDiv = document.getElementById("message"); messageDiv.innerText = message; messageDiv.style.backgroundColor = color; messageDiv.style.display = "block"; // Make message visable. // If a message timer already exists, we can clear the timeout to prevent it from hiding this message. if (messageTimer!=null) { clearTimeout(messageTimer); } // If message is to timeout, set a timeout to hide in 5 seconds. if (timeout) { messageTimer = setTimeout(hideMessage, 5000); } } // Configuration Options var UIDisableSpamReporting = false; var UIDisableLogs = false; // The width calculated for the subject. var UISubjectWidth = 0; // Build custom CSS based on configuration. function rebuildCustomCSS() { var cssConfig = '"; // Add style tag to page. document.getElementById("customcss").innerHTML = cssConfig; } // Load configuration from API. function loadConfig() { // Call the API. $.ajax({ dataType: "json", type: "GET", url: "/api/config" }) .done(function(data) { // If an error ocurred. Display it. if (data.status=="error") { displayError("Unable to load configuration: "+data.error); return; } // Save configuration. UIDisableSpamReporting = data.disable_spam_reporting; UIDisableLogs = data.disable_logs; if (data.custom_brand!="") { $("#navbar_brand").text(data.custom_brand); } $("#message_count").text(data.message_count.toLocaleString()); // Rebuild CSS with new config. rebuildCustomCSS(); }) .fail(function(jqXHR, textStatus) { // On error, display a message. displayError("Unable to load configuration: "+textStatus); }); } // Storage of the currently selected email message. var selectedMessage = null; // Auto resize global resize based variable. // This variable is basically the current top offset of the message contents view, minus the message list height. // This allows us to easily determine the max height of the message contents view by taking the window height // and substracting the message height and this base height. var messageResizeBase = 0; // Handle a window resize event. function handleResize() { // Get the current message list height from either the message list container itself, or storage. var messagesH = $("#message_list_container").height(); if (localStorage && 'message_list_height' in localStorage) { messagesH = localStorage.message_list_height; $("#message_list_container").height(messagesH); } // If we don't have a resize base already calculated, calculate it. if (messageResizeBase==0) { messageResizeBase = $("#message_contents").offset().top-messagesH; } // The new message contents height should be the window height minus messages list height minus the resize base. var messageH = $(window).height()-messagesH-messageResizeBase; $("#message_contents").height(messageH); // Get width of other columns. var fromWidth = $("th.from").width(); var toWidth = $("th.to").width(); var statusWidth = 0; if ($("th.status").is(":visible")) { statusWidth = $("th.status").width(); } var receivedWidth = $("th.received").width(); // Subtract width of other volumes from windows width. UISubjectWidth = $(window).width()-(fromWidth+toWidth+statusWidth+receivedWidth+16); // 16 is padding. // Limit to width of 100 pixels. if (UISubjectWidth<100) { UISubjectWidth = 100; } // Build the css. rebuildCustomCSS(); } // This function will check to see where the active message is in the list, and determine if it is visable. // If the message is not visable in the message list, it will scroll to make it visable. function scollToActiveMessageIfNeeded() { // Get the current selection, and stop if no selection is made. var selection = $("#message_list .active"); if (selection.length<=0) { return; } // Determine the selection's position in the container list. var selectionTop = selection.position().top; var rowH = selection.height(); var container = $("#message_list_container"); // If the message is above the scroll position, we need to scroll up. if (container.scrollTop()>selectionTop-rowH) { container.animate({ scrollTop: selectionTop-rowH }, 200); } else if (container.scrollTop()+container.height()"); iframe.css("height", "100%"); iframe.css("width", "100%"); iframe.attr("src", "/api/message/"+selectedMessage.uuid+extension); // Append iframe to the message contents view. $("#message_contents").html(""); $("#message_contents").append(iframe); } else { // All other source types are handled here. // Get the message source from the API. $.ajax({ url: "/api/message/"+selectedMessage.uuid+extension }) .done(function(data) { // If an error was returned, we display it. if (data.status!=undefined) { displayError("Unable to pull message: "+data.error); return; } // We display plain text message contents in a pre-formatted element. var preFormated = $("
").text(data);

            // Append the pre-formatted element to the message contents.
            $("#message_contents").html("");
            $("#message_contents").append(preFormated);
        })
        .fail(function(jqXHR, textStatus) {
            // Om error, display a message.
            displayError("Unable to pull message: "+textStatus);
        });
    }
}

// This function is used to update the currently selected message view.
function updateSelectedMessage() {
    // Update the header information.
    $("#message_header .received").text(selectedMessage.formatted_date);
    $("#message_header .size").text(bytesToHuman(selectedMessage.size));
    $("#message_header .from").text(selectedMessage.from);
    $("#message_header .to").text(selectedMessage.to);
    $("#message_header .subject").text(selectedMessage.subject);
    $("#message_header .spam_score").text(selectedMessage.spam_score);
    $("#message_header .status").text(selectedMessage.status);
    $("#message_header .source_ip").text(selectedMessage.source_ip);

    // If no plain text, this must be a html email.
    if (!selectedMessage.plain_text) {
        // Disable plain text source selection tab.
        $("#message_header .nav-tabs .plaintext").prop("disabled", true);
        // Enable the html source selection tab.
        $("#message_header .nav-tabs .html").prop("disabled", false);
        // If the currently selected source tab is disabled, we need to select html.
        if ($("#message_header .nav-tabs .active").prop("disabled")) {
            $("#message_header .nav-tabs .html").click();
        } else {
            // Otherwise, select the active tab.
            $("#message_header .nav-tabs .active").click();
        }
    } else {
        // When plaintext is avaiable, we need to disable HTML source selection only if there is no HTML.
        $("#message_header .nav-tabs .html").prop("disabled", !selectedMessage.html);
        // We can enable the plain text source selection.
        $("#message_header .nav-tabs .plaintext").prop("disabled", false);
        // If the currently selected source tab is disabled, we need to select plain text.
        if ($("#message_header .nav-tabs .active").prop("disabled")) {
            $("#message_header .nav-tabs .plaintext").click();
        } else {
            // Otherwise, select the active tab.
            $("#message_header .nav-tabs .active").click();
        }
    }
}

// When a message is selected in the message list, this function is called.
function handleMessageListSelection() {
    // Get the selected message data.
    var selection = $(this);
    var message = JSON.parse(selection.attr("data"));
    selectedMessage = message;

    // Change the selelected message in the message list to this selection.
    $("#message_list tr.active").removeClass("active");
    selection.addClass("active");

    // Update the location hash URI to this message.
    window.location.hash = "uuid="+message.uuid;

    // Update the selected message.
    updateSelectedMessage();
}

// When the learn ham spam reporting button is clicked, this function is called.
function learnHam() {
    // If no message is selected, we display a message and stop here.
    if (selectedMessage==null) {
        displayMessage("Select a message first.");
        return;
    }
    // Confirm that this action is actually wanted to occur.
    var r = confirm("Are you sure you want to report as ham?");
    if (r != true) {
        return
    }

    // Send request to the API.
    $.ajax({
        dataType: "json",
        type: 'PUT',
        url: "/api/message/"+selectedMessage.uuid+"/learn_ham"
    })
    .done(function(data) {
        // If error, display message.
        if (data.status=="error") {
            displayError("Unable to report ham: "+data.error);
            return;
        }
        // We Successfully submitted a report.
        displaySuccess("Successfully reported as ham.");
    })
    .fail(function(jqXHR, textStatus) {
        // On error, display message.
        displayError("Unable to report ham: "+textStatus);
    });
}

// When the learn spam spam reporting button is clicked, this function is called.
function learnSpam() {
    // If no message is selected, we display a message and stop here.
    if (selectedMessage==null) {
        displayMessage("Select a message first.");
        return;
    }
    // Confirm that this action is actually wanted to occur.
    var r = confirm("Are you sure you want to report as spam?");
    if (r != true) {
        return
    }

    // Send request to the API.
    $.ajax({
        dataType: "json",
        type: "PUT",
        url: "/api/message/"+selectedMessage.uuid+"/learn_spam"
    })
    .done(function(data) {
        // If error, display message.
        if (data.status=="error") {
            displayError("Unable to report spam: "+data.error);
            return;
        }
        // We Successfully submitted a report.
        displaySuccess("Successfully reported as spam.");
    })
    .fail(function(jqXHR, textStatus) {
        // On error, display message.
        displayError("Unable to report spam: "+textStatus);
    });
}

// When the document has fully loaded, we get everything started.
$(document).ready(function() {
    // Connect to websockets if available.
    if (!window["WebSocket"]) {
        displayError("Your browser does not support websockets, auto refresh will only occur once every minute.");
        return;
    } else {
        connectToWS();
    }
    // Laod the configuration from API.
    loadConfig();

    // Make the message list resizer element work.
    makeMessageListResizable();

    // On window resize events, adjust view sizes.
    $(window).resize(handleResize);
    // Update the view sizes.
    setTimeout(handleResize, 200);

    // Load the message list.
    loadMessageList();

    // Every 5 seconds, we need to check if we need to refresh the message list.
    setInterval(checkIfRefreshNeeded, 5000);
    // Every minute, we force a refresh.
    setInterval(function() {
        shouldRefresh = true;
    }, 60000);

    // On input in the search field, handle it.
    $("#searchInput").on("input", handleSearchInput);

    // Handle clicking on items in the message list.
    $("#message_list").on("click", "tr", handleMessageListSelection);

    // Handle global document key down events.
    $(document).keydown(handleKeydownEvent);

    // Handle clicks on source selection tabs.
    $("#message_header .nav-tabs .nav-link").click(handleSourceSelection);

    // Handle a click on the email download button.
    $("#mailDownloadButton").click(function() {
        // If no message selected, stop here.
        if (selectedMessage==null) {
            return;
        }
        // Setup the download path.
        var downloadPath = "/api/message/"+selectedMessage.uuid+".eml";

        // Create an link with a download file name to allow downloading without navigating away.
        // This is done to avoid disconnection from the websocket.
        var a = document.createElement("a");
        a.href = downloadPath;
        a.download = downloadPath.substr(downloadPath.lastIndexOf('/') + 1);

        // Append the link, click it and remove it.
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    });

    // Setup handlers for spam reporting.
    $("#mailLearnHamButton").click(learnHam);
    $("#mailLearnSpamButton").click(learnSpam);

    // Check if a message uuid was provided in the location hash.
    var hashParams = new URLSearchParams(window.location.hash.slice(1));
    if (hashParams.has("uuid")) {
        // If it was, we should load that message.
        var uuid = hashParams.get("uuid");
        loadMessage(uuid);
    }

    // Register for when the hash location has changed.
    $(window).bind('hashchange', function(e) {
        // Check if the UUID is provided in the updated hash location.
        var hashParams = new URLSearchParams(window.location.hash.slice(1));
        if (hashParams.has("uuid")) {
            // If it was, we can load the message.
            var uuid = hashParams.get("uuid");
            loadMessage(uuid);
        }
    });
});