<commit_before>$(function() {
    function TerminalViewModel(parameters) {
        var self = this;

        self.loginState = parameters[0];
        self.settings = parameters[1];

        self.tabActive = false;

        self.log = ko.observableArray([]);
        self.log.extend({ throttle: 500 });
        self.plainLogLines = ko.observableArray([]);
        self.plainLogLines.extend({ throttle: 500 });

        self.buffer = ko.observable(300);
        self.upperLimit = ko.observable(1499);

        self.command = ko.observable(undefined);

        self.isErrorOrClosed = ko.observable(undefined);
        self.isOperational = ko.observable(undefined);
        self.isPrinting = ko.observable(undefined);
        self.isPaused = ko.observable(undefined);
        self.isError = ko.observable(undefined);
        self.isReady = ko.observable(undefined);
        self.isLoading = ko.observable(undefined);

        self.autoscrollEnabled = ko.observable(true);

        self.filters = self.settings.terminalFilters;
        self.filterRegex = ko.observable();

        self.cmdHistory = [];
        self.cmdHistoryIdx = -1;

        self.enableFancyFunctionality = ko.observable(true);
        self.disableTerminalLogDuringPrinting = ko.observable(false);

        self.acceptableFancyTime = 500;
        self.acceptableUnfancyTime = 300;
        self.reenableTimeout = 5000;

        self.forceFancyFunctionality = ko.observable(false);
        self.forceTerminalLogDuringPrinting = ko.observable(false);

        self.fancyFunctionality = ko.pureComputed(function() {
            return self.enableFancyFunctionality() || self.forceFancyFunctionality();
        });
        self.terminalLogDuringPrinting = ko.pureComputed(function() {
            return !self.disableTerminalLogDuringPrinting() || self.forceTerminalLogDuringPrinting();
        });

        self.displayedLines = ko.pureComputed(function() {
            if (!self.enableFancyFunctionality()) {
                return self.log();
            }

            var regex = self.filterRegex();
            var lineVisible = function(entry) {
                return regex == undefined || !entry.line.match(regex);
            };

            var filtered = false;
            var result = [];
            var lines = self.log();
            _.each(lines, function(entry) {
                if (lineVisible(entry)) {
                    result.push(entry);
                    filtered = false;
                } else if (!filtered) {
                    result.push(self._toInternalFormat("[...]", "filtered"));
                    filtered = true;
                }
            });

            return result;
        });

        self.plainLogOutput = ko.pureComputed(function() {
            if (self.fancyFunctionality()) {
                return;
            }
            return self.plainLogLines().join("\n");
        });

        self.lineCount = ko.pureComputed(function() {
            if (!self.fancyFunctionality()) {
                return;
            }

            var regex = self.filterRegex();
            var lineVisible = function(entry) {
                return regex == undefined || !entry.line.match(regex);
            };

            var lines = self.log();
            var total = lines.length;
            var displayed = _.filter(lines, lineVisible).length;
            var filtered = total - displayed;

            if (filtered > 0) {
                if (total > self.upperLimit()) {
                    return _.sprintf(gettext("showing %(displayed)d lines (%(filtered)d of %(total)d total lines filtered, buffer full)"), {displayed: displayed, total: total, filtered: filtered});
                } else {
                    return _.sprintf(gettext("showing %(displayed)d lines (%(filtered)d of %(total)d total lines filtered)"), {displayed: displayed, total: total, filtered: filtered});
                }
            } else {
                if (total > self.upperLimit()) {
                    return _.sprintf(gettext("showing %(displayed)d lines (buffer full)"), {displayed: displayed});
                } else {
                    return _.sprintf(gettext("showing %(displayed)d lines"), {displayed: displayed});
                }
            }
        });

        self.autoscrollEnabled.subscribe(function(newValue) {
            if (newValue) {
                self.log(self.log.slice(-self.buffer()));
            }
        });

        self.activeFilters = ko.observableArray([]);
        self.activeFilters.subscribe(function(e) {
            self.updateFilterRegex();
        });

        self.blacklist=[];
        self.settings.serial_autoUppercaseBlacklist.subscribe(function(newValue) {
            self.blacklist = splitTextToArray(newValue, ",", true);
        });

        self._reenableFancyTimer = undefined;
        self._reenableUnfancyTimer = undefined;
        self._disableFancy = function(difference) {
            log.warn("Terminal: Detected slow client (needed " + difference + "ms for processing new log data), disabling fancy terminal functionality");
            if (self._reenableFancyTimer) {
                window.clearTimeout(self._reenableFancyTimer);
                self._reenableFancyTimer = undefined;
            }
            self.enableFancyFunctionality(false);
        };
        self._reenableFancy = function(difference) {
            if (self._reenableFancyTimer) return;
            self._reenableFancyTimer = window.setTimeout(function() {
                log.info("Terminal: Client speed recovered, enabling fancy terminal functionality");
                self.enableFancyFunctionality(true);
            }, self.reenableTimeout);
        };
        self._disableUnfancy = function(difference) {
            log.warn("Terminal: Detected very slow client (needed " + difference + "ms for processing new log data), completely disabling terminal output during printing");
            if (self._reenableUnfancyTimer) {
                window.clearTimeout(self._reenableUnfancyTimer);
                self._reenableUnfancyTimer = undefined;
            }
            self.disableTerminalLogDuringPrinting(true);
        };
        self._reenableUnfancy = function() {
            if (self._reenableUnfancyTimer) return;
            self._reenableUnfancyTimer = window.setTimeout(function() {
                log.info("Terminal: Client speed recovered, enabling terminal output during printing");
                self.disableTerminalLogDuringPrinting(false);
            }, self.reenableTimeout);
        };

        self.fromCurrentData = function(data) {
            self._processStateData(data.state);

            var start = new Date().getTime();
            self._processCurrentLogData(data.logs);
            var end = new Date().getTime();
            var difference = end - start;

            if (self.enableFancyFunctionality()) {
                // fancy enabled -> check if we need to disable fancy
                if (difference >= self.acceptableFancyTime) {
                    self._disableFancy(difference);
                }
            } else if (!self.disableTerminalLogDuringPrinting()) {
                // fancy disabled, unfancy not -> check if we need to disable unfancy or re-enable fancy
                if (difference >= self.acceptableUnfancyTime) {
                    self._disableUnfancy(difference);
                } else if (difference < self.acceptableFancyTime / 2.0) {
                    self._reenableFancy(difference);
                }
            } else {
                // fancy & unfancy disabled -> check if we need to re-enable unfancy
                if (difference < self.acceptableUnfancyTime / 2.0) {
                    self._reenableUnfancy(difference);
                }
            }
        };

        self.fromHistoryData = function(data) {
            self._processStateData(data.state);
            self._processHistoryLogData(data.logs);
        };

        self._processCurrentLogData = function(data) {
            var length = self.log().length;
            if (length >= self.upperLimit()) {
                return;
            }

            if (!self.terminalLogDuringPrinting() && self.isPrinting()) {
                var last = self.plainLogLines()[self.plainLogLines().length - 1];
                var disabled = "--- client too slow, log output disabled while printing ---";
                if (last != disabled) {
                    self.plainLogLines.push(disabled);
                }
                return;
            }

            var newData = (data.length + length > self.upperLimit())
                ? data.slice(0, self.upperLimit() - length)
                : data;
            if (!newData) {
                return;
            }

            if (!self.fancyFunctionality()) {
                // lite version of the terminal - text output only
                self.plainLogLines(self.plainLogLines().concat(newData).slice(-self.buffer()));
                self.updateOutput();
                return;
            }

            var newLog = self.log().concat(_.map(newData, function(line) { return self._toInternalFormat(line) }));
            if (newData.length != data.length) {
                var cutoff = "--- too many lines to buffer, cut off ---";
                newLog.push(self._toInternalFormat(cutoff, "cut"));
            }

            if (self.autoscrollEnabled()) {
                // we only keep the last <buffer> entries
                newLog = newLog.slice(-self.buffer());
            }
            self.log(newLog);
            self.updateOutput();
        };

        self._processHistoryLogData = function(data) {
            self.plainLogLines(data);
            self.log(_.map(data, function(line) { return self._toInternalFormat(line) }));
            self.updateOutput();
        };

        self._toInternalFormat = function(line, type) {
            if (type == undefined) {
                type = "line";
            }
            return {line: escapeUnprintableCharacters(line), type: type}
        };

        self._processStateData = function(data) {
            self.isErrorOrClosed(data.flags.closedOrError);
            self.isOperational(data.flags.operational);
            self.isPaused(data.flags.paused);
            self.isPrinting(data.flags.printing);
            self.isError(data.flags.error);
            self.isReady(data.flags.ready);
            self.isLoading(data.flags.loading);
        };

        self.updateFilterRegex = function() {
            var filterRegexStr = self.activeFilters().join("|").trim();
            if (filterRegexStr == "") {
                self.filterRegex(undefined);
            } else {
                self.filterRegex(new RegExp(filterRegexStr));
            }
            self.updateOutput();
        };

        self.updateOutput = function() {
            if (self.tabActive && OctoPrint.coreui.browserTabVisible && self.autoscrollEnabled()) {
                self.scrollToEnd();
            }
        };

        self.gotoTerminalCommand = function() {
            // skip if user highlights text.
            var sel = getSelection().toString();
            if (sel) {
                return;
            }

            $("#terminal-command").focus();
        };

        self.toggleAutoscroll = function() {
            self.autoscrollEnabled(!self.autoscrollEnabled());
        };

        self.selectAll = function() {
            var container = self.fancyFunctionality() ? $("#terminal-output") : $("#terminal-output-lowfi");
            if (container.length) {
                container.selectText();
            }
        };

        self.scrollToEnd = function() {
            var container = self.fancyFunctionality() ? $("#terminal-output") : $("#terminal-output-lowfi");
            if (container.length) {
                container.scrollTop(container[0].scrollHeight);
            }
        };

        self.copyAll = function() {
            copyToClipboard(self.plainLogLines().join("\n"));
        };

        // command matching regex
        // (Example output for inputs G0, G1, G28.1, M117 test)
        // - 1: code including optional subcode. Example: G0, G1, G28.1, M117
        // - 2: main code only. Example: G0, G1, G28, M117
        // - 3: sub code, if available. Example: undefined, undefined, .1, undefined
        // - 4: command parameters incl. leading whitespace, if any. Example: "", "", "", " test"
        var commandRe = /^(([gmt][0-9]+)(\.[0-9+])?)(\s.*)?/i;

        self.sendCommand = function() {
            var command = self.command();
            if (!command) {
                return;
            }

            var commandToSend = command;
            var commandMatch = commandToSend.match(commandRe);
            if (commandMatch !== null) {
                var fullCode = commandMatch[1].toUpperCase(); // full code incl. sub code
                var mainCode = commandMatch[2].toUpperCase(); // main code only without sub code

                if (self.blacklist.indexOf(mainCode) < 0 && self.blacklist.indexOf(fullCode) < 0) {
                    // full or main code not on blacklist -> upper case the whole command
                    commandToSend = commandToSend.toUpperCase();
                } else {
                    // full or main code on blacklist -> only upper case that and leave parameters as is
                    commandToSend = fullCode + (commandMatch[4] !== undefined ? commandMatch[4] : "");
                }
            }

            if (commandToSend) {
                OctoPrint.control.sendGcode(commandToSend)
                    .done(function() {
                        self.cmdHistory.push(command);
                        self.cmdHistory.slice(-300); // just to set a sane limit to how many manually entered commands will be saved...
                        self.cmdHistoryIdx = self.cmdHistory.length;
                        self.command("");
                    });
            }
        };

        self.fakeAck = function() {
            OctoPrint.connection.fakeAck();
        };

        self.handleKeyDown = function(event) {
            var keyCode = event.keyCode;

            if (keyCode == 38 || keyCode == 40) {
                if (keyCode == 38 && self.cmdHistory.length > 0 && self.cmdHistoryIdx > 0) {
                    self.cmdHistoryIdx--;
                } else if (keyCode == 40 && self.cmdHistoryIdx < self.cmdHistory.length - 1) {
                    self.cmdHistoryIdx++;
                }

                if (self.cmdHistoryIdx >= 0 && self.cmdHistoryIdx < self.cmdHistory.length) {
                    self.command(self.cmdHistory[self.cmdHistoryIdx]);
                }

                // prevent the cursor from being moved to the beginning of the input field (this is actually the reason
                // why we do the arrow key handling in the keydown event handler, keyup would be too late already to
                // prevent this from happening, causing a jumpy cursor)
                return false;
            }

            // do not prevent default action
            return true;
        };

        self.handleKeyUp = function(event) {
            if (event.keyCode == 13) {
                self.sendCommand();
            }

            // do not prevent default action
            return true;
        };

        self.onAfterTabChange = function(current, previous) {
            self.tabActive = current == "#term";
            self.updateOutput();
        };

    }

    OCTOPRINT_VIEWMODELS.push({
        construct: TerminalViewModel,
        dependencies: ["loginStateViewModel", "settingsViewModel"],
        elements: ["#term"]
    });
});
<commit_msg>Fix "copy all" on Terminal tab<commit_after>$(function() {
    function TerminalViewModel(parameters) {
        var self = this;

        self.loginState = parameters[0];
        self.settings = parameters[1];

        self.tabActive = false;

        self.log = ko.observableArray([]);
        self.log.extend({ throttle: 500 });
        self.plainLogLines = ko.observableArray([]);
        self.plainLogLines.extend({ throttle: 500 });

        self.buffer = ko.observable(300);
        self.upperLimit = ko.observable(1499);

        self.command = ko.observable(undefined);

        self.isErrorOrClosed = ko.observable(undefined);
        self.isOperational = ko.observable(undefined);
        self.isPrinting = ko.observable(undefined);
        self.isPaused = ko.observable(undefined);
        self.isError = ko.observable(undefined);
        self.isReady = ko.observable(undefined);
        self.isLoading = ko.observable(undefined);

        self.autoscrollEnabled = ko.observable(true);

        self.filters = self.settings.terminalFilters;
        self.filterRegex = ko.observable();

        self.cmdHistory = [];
        self.cmdHistoryIdx = -1;

        self.enableFancyFunctionality = ko.observable(true);
        self.disableTerminalLogDuringPrinting = ko.observable(false);

        self.acceptableFancyTime = 500;
        self.acceptableUnfancyTime = 300;
        self.reenableTimeout = 5000;

        self.forceFancyFunctionality = ko.observable(false);
        self.forceTerminalLogDuringPrinting = ko.observable(false);

        self.fancyFunctionality = ko.pureComputed(function() {
            return self.enableFancyFunctionality() || self.forceFancyFunctionality();
        });
        self.terminalLogDuringPrinting = ko.pureComputed(function() {
            return !self.disableTerminalLogDuringPrinting() || self.forceTerminalLogDuringPrinting();
        });

        self.displayedLines = ko.pureComputed(function() {
            if (!self.enableFancyFunctionality()) {
                return self.log();
            }

            var regex = self.filterRegex();
            var lineVisible = function(entry) {
                return regex == undefined || !entry.line.match(regex);
            };

            var filtered = false;
            var result = [];
            var lines = self.log();
            _.each(lines, function(entry) {
                if (lineVisible(entry)) {
                    result.push(entry);
                    filtered = false;
                } else if (!filtered) {
                    result.push(self._toInternalFormat("[...]", "filtered"));
                    filtered = true;
                }
            });

            return result;
        });

        self.plainLogOutput = ko.pureComputed(function() {
            if (self.fancyFunctionality()) {
                return;
            }
            return self.plainLogLines().join("\n");
        });

        self.lineCount = ko.pureComputed(function() {
            if (!self.fancyFunctionality()) {
                return;
            }

            var regex = self.filterRegex();
            var lineVisible = function(entry) {
                return regex == undefined || !entry.line.match(regex);
            };

            var lines = self.log();
            var total = lines.length;
            var displayed = _.filter(lines, lineVisible).length;
            var filtered = total - displayed;

            if (filtered > 0) {
                if (total > self.upperLimit()) {
                    return _.sprintf(gettext("showing %(displayed)d lines (%(filtered)d of %(total)d total lines filtered, buffer full)"), {displayed: displayed, total: total, filtered: filtered});
                } else {
                    return _.sprintf(gettext("showing %(displayed)d lines (%(filtered)d of %(total)d total lines filtered)"), {displayed: displayed, total: total, filtered: filtered});
                }
            } else {
                if (total > self.upperLimit()) {
                    return _.sprintf(gettext("showing %(displayed)d lines (buffer full)"), {displayed: displayed});
                } else {
                    return _.sprintf(gettext("showing %(displayed)d lines"), {displayed: displayed});
                }
            }
        });

        self.autoscrollEnabled.subscribe(function(newValue) {
            if (newValue) {
                self.log(self.log.slice(-self.buffer()));
            }
        });

        self.activeFilters = ko.observableArray([]);
        self.activeFilters.subscribe(function(e) {
            self.updateFilterRegex();
        });

        self.blacklist=[];
        self.settings.serial_autoUppercaseBlacklist.subscribe(function(newValue) {
            self.blacklist = splitTextToArray(newValue, ",", true);
        });

        self._reenableFancyTimer = undefined;
        self._reenableUnfancyTimer = undefined;
        self._disableFancy = function(difference) {
            log.warn("Terminal: Detected slow client (needed " + difference + "ms for processing new log data), disabling fancy terminal functionality");
            if (self._reenableFancyTimer) {
                window.clearTimeout(self._reenableFancyTimer);
                self._reenableFancyTimer = undefined;
            }
            self.enableFancyFunctionality(false);
        };
        self._reenableFancy = function(difference) {
            if (self._reenableFancyTimer) return;
            self._reenableFancyTimer = window.setTimeout(function() {
                log.info("Terminal: Client speed recovered, enabling fancy terminal functionality");
                self.enableFancyFunctionality(true);
            }, self.reenableTimeout);
        };
        self._disableUnfancy = function(difference) {
            log.warn("Terminal: Detected very slow client (needed " + difference + "ms for processing new log data), completely disabling terminal output during printing");
            if (self._reenableUnfancyTimer) {
                window.clearTimeout(self._reenableUnfancyTimer);
                self._reenableUnfancyTimer = undefined;
            }
            self.disableTerminalLogDuringPrinting(true);
        };
        self._reenableUnfancy = function() {
            if (self._reenableUnfancyTimer) return;
            self._reenableUnfancyTimer = window.setTimeout(function() {
                log.info("Terminal: Client speed recovered, enabling terminal output during printing");
                self.disableTerminalLogDuringPrinting(false);
            }, self.reenableTimeout);
        };

        self.fromCurrentData = function(data) {
            self._processStateData(data.state);

            var start = new Date().getTime();
            self._processCurrentLogData(data.logs);
            var end = new Date().getTime();
            var difference = end - start;

            if (self.enableFancyFunctionality()) {
                // fancy enabled -> check if we need to disable fancy
                if (difference >= self.acceptableFancyTime) {
                    self._disableFancy(difference);
                }
            } else if (!self.disableTerminalLogDuringPrinting()) {
                // fancy disabled, unfancy not -> check if we need to disable unfancy or re-enable fancy
                if (difference >= self.acceptableUnfancyTime) {
                    self._disableUnfancy(difference);
                } else if (difference < self.acceptableFancyTime / 2.0) {
                    self._reenableFancy(difference);
                }
            } else {
                // fancy & unfancy disabled -> check if we need to re-enable unfancy
                if (difference < self.acceptableUnfancyTime / 2.0) {
                    self._reenableUnfancy(difference);
                }
            }
        };

        self.fromHistoryData = function(data) {
            self._processStateData(data.state);
            self._processHistoryLogData(data.logs);
        };

        self._processCurrentLogData = function(data) {
            var length = self.log().length;
            if (length >= self.upperLimit()) {
                return;
            }

            if (!self.terminalLogDuringPrinting() && self.isPrinting()) {
                var last = self.plainLogLines()[self.plainLogLines().length - 1];
                var disabled = "--- client too slow, log output disabled while printing ---";
                if (last != disabled) {
                    self.plainLogLines.push(disabled);
                }
                return;
            }

            var newData = (data.length + length > self.upperLimit())
                ? data.slice(0, self.upperLimit() - length)
                : data;
            if (!newData) {
                return;
            }

            if (!self.fancyFunctionality()) {
                // lite version of the terminal - text output only
                self.plainLogLines(self.plainLogLines().concat(newData).slice(-self.buffer()));
                self.updateOutput();
                return;
            }

            var newLog = self.log().concat(_.map(newData, function(line) { return self._toInternalFormat(line) }));
            if (newData.length != data.length) {
                var cutoff = "--- too many lines to buffer, cut off ---";
                newLog.push(self._toInternalFormat(cutoff, "cut"));
            }

            if (self.autoscrollEnabled()) {
                // we only keep the last <buffer> entries
                newLog = newLog.slice(-self.buffer());
            }
            self.log(newLog);
            self.updateOutput();
        };

        self._processHistoryLogData = function(data) {
            self.plainLogLines(data);
            self.log(_.map(data, function(line) { return self._toInternalFormat(line) }));
            self.updateOutput();
        };

        self._toInternalFormat = function(line, type) {
            if (type == undefined) {
                type = "line";
            }
            return {line: escapeUnprintableCharacters(line), type: type}
        };

        self._processStateData = function(data) {
            self.isErrorOrClosed(data.flags.closedOrError);
            self.isOperational(data.flags.operational);
            self.isPaused(data.flags.paused);
            self.isPrinting(data.flags.printing);
            self.isError(data.flags.error);
            self.isReady(data.flags.ready);
            self.isLoading(data.flags.loading);
        };

        self.updateFilterRegex = function() {
            var filterRegexStr = self.activeFilters().join("|").trim();
            if (filterRegexStr == "") {
                self.filterRegex(undefined);
            } else {
                self.filterRegex(new RegExp(filterRegexStr));
            }
            self.updateOutput();
        };

        self.updateOutput = function() {
            if (self.tabActive && OctoPrint.coreui.browserTabVisible && self.autoscrollEnabled()) {
                self.scrollToEnd();
            }
        };

        self.gotoTerminalCommand = function() {
            // skip if user highlights text.
            var sel = getSelection().toString();
            if (sel) {
                return;
            }

            $("#terminal-command").focus();
        };

        self.toggleAutoscroll = function() {
            self.autoscrollEnabled(!self.autoscrollEnabled());
        };

        self.selectAll = function() {
            var container = self.fancyFunctionality() ? $("#terminal-output") : $("#terminal-output-lowfi");
            if (container.length) {
                container.selectText();
            }
        };

        self.scrollToEnd = function() {
            var container = self.fancyFunctionality() ? $("#terminal-output") : $("#terminal-output-lowfi");
            if (container.length) {
                container.scrollTop(container[0].scrollHeight);
            }
        };

        self.copyAll = function() {
            copyToClipboard(_.map(self.log(), "line").join("\n"));
        };

        // command matching regex
        // (Example output for inputs G0, G1, G28.1, M117 test)
        // - 1: code including optional subcode. Example: G0, G1, G28.1, M117
        // - 2: main code only. Example: G0, G1, G28, M117
        // - 3: sub code, if available. Example: undefined, undefined, .1, undefined
        // - 4: command parameters incl. leading whitespace, if any. Example: "", "", "", " test"
        var commandRe = /^(([gmt][0-9]+)(\.[0-9+])?)(\s.*)?/i;

        self.sendCommand = function() {
            var command = self.command();
            if (!command) {
                return;
            }

            var commandToSend = command;
            var commandMatch = commandToSend.match(commandRe);
            if (commandMatch !== null) {
                var fullCode = commandMatch[1].toUpperCase(); // full code incl. sub code
                var mainCode = commandMatch[2].toUpperCase(); // main code only without sub code

                if (self.blacklist.indexOf(mainCode) < 0 && self.blacklist.indexOf(fullCode) < 0) {
                    // full or main code not on blacklist -> upper case the whole command
                    commandToSend = commandToSend.toUpperCase();
                } else {
                    // full or main code on blacklist -> only upper case that and leave parameters as is
                    commandToSend = fullCode + (commandMatch[4] !== undefined ? commandMatch[4] : "");
                }
            }

            if (commandToSend) {
                OctoPrint.control.sendGcode(commandToSend)
                    .done(function() {
                        self.cmdHistory.push(command);
                        self.cmdHistory.slice(-300); // just to set a sane limit to how many manually entered commands will be saved...
                        self.cmdHistoryIdx = self.cmdHistory.length;
                        self.command("");
                    });
            }
        };

        self.fakeAck = function() {
            OctoPrint.connection.fakeAck();
        };

        self.handleKeyDown = function(event) {
            var keyCode = event.keyCode;

            if (keyCode == 38 || keyCode == 40) {
                if (keyCode == 38 && self.cmdHistory.length > 0 && self.cmdHistoryIdx > 0) {
                    self.cmdHistoryIdx--;
                } else if (keyCode == 40 && self.cmdHistoryIdx < self.cmdHistory.length - 1) {
                    self.cmdHistoryIdx++;
                }

                if (self.cmdHistoryIdx >= 0 && self.cmdHistoryIdx < self.cmdHistory.length) {
                    self.command(self.cmdHistory[self.cmdHistoryIdx]);
                }

                // prevent the cursor from being moved to the beginning of the input field (this is actually the reason
                // why we do the arrow key handling in the keydown event handler, keyup would be too late already to
                // prevent this from happening, causing a jumpy cursor)
                return false;
            }

            // do not prevent default action
            return true;
        };

        self.handleKeyUp = function(event) {
            if (event.keyCode == 13) {
                self.sendCommand();
            }

            // do not prevent default action
            return true;
        };

        self.onAfterTabChange = function(current, previous) {
            self.tabActive = current == "#term";
            self.updateOutput();
        };

    }

    OCTOPRINT_VIEWMODELS.push({
        construct: TerminalViewModel,
        dependencies: ["loginStateViewModel", "settingsViewModel"],
        elements: ["#term"]
    });
});
<|endoftext|>


<commit_before>//On load
$(function(){
	//Display pizza menu
	renderPizza();
	//Handle button clicks
	$('.menu-ui .btn').click(function(){
		var menuBtn = $(this);
		var type = menuBtn.attr('data-type');
		clearMenu();
		if(type === "pizzas")
			renderPizza();
		else
			render(type);
		menuBtn.siblings().removeClass("active");
		menuBtn.addClass("active");
		//Set Menu title
		$('.menuType').text(type+":");
		$('.menu').fadeIn(500);
	});

});

//renders the pizza menu
function renderPizza(){
	var idx;
	var pizza;
	var instance;
	var template = $('.pizza');
	var meatContainer = $('.meat-pizza');
	var veggieContainer = $('.veggie-pizza');
	var pizzaContainer = $('.pizza-menu');
	//remove small id so container expands
	$('.maincontainer').removeAttr('id');
	for (idx = 0; idx < com.dawgpizza.menu.pizzas.length; ++idx) {
		pizza = com.dawgpizza.menu.pizzas[idx];
		instance = template.clone();
		instance.find('.name').html(pizza.name);
		instance.find('.description').html(pizza.description);
		instance.find('.prices').html("$"+pizza.prices[0]+" $"+pizza.prices[1]+" $"+pizza.prices[2]);
		instance.removeClass('template');
		if (pizza.vegetarian)
			var container = veggieContainer;
		else
			var container = meatContainer;
		container.append(instance);
	}
	pizzaContainer.fadeIn(500);
}

//Renders drink and dessert menus
function render(type) {
	var idx;
	var instance;
	var template = $('.template');
	var container = $('.menu');
	var item;
	//Add small id so container shrinks
	$('.maincontainer').attr('id', 'small');
	var address = com.dawgpizza.menu.drinks;
	if (type === "desserts")
		address = com.dawgpizza.menu.desserts;
	for (idx = 0; idx < address.length; ++idx) {
		item = address[idx];
		instance = template.clone();
		instance.find('.pizzaname').html(item.name);
		instance.find('.price').html("$" + item.price)
		instance.removeClass('template');
		container.append(instance);
	}
	container.fadeIn(500);
}

//Empties the menu for recreation
function clearMenu() {
	$('.pizza-menu').hide();
	$('.meat-pizza').empty();
	$('.veggie-pizza').empty();
	$('.menu').empty();
	$('.menu').hide();
	$('.menuType').hide();
	$('.menuType').fadeIn(500);
}
<commit_msg>fixed js bug<commit_after>//On load
$(function(){
	//Display pizza menu
	renderPizza();
	//Handle button clicks
	$('.menu-ui .btn').click(function(){
		var menuBtn = $(this);
		var type = menuBtn.attr('data-type');
		clearMenu();
		if(type === "pizzas")
			renderPizza();
		else
			render(type);
		menuBtn.siblings().removeClass("active");
		menuBtn.addClass("active");
		//Set Menu title
		$('.menuType').text(type+":");
		$('.menu').fadeIn(500);
	});

});

//renders the pizza menu
function renderPizza(){
	var idx;
	var pizza;
	var instance;
	var template = $('.pizza');
	var meatContainer = $('.meat-pizza');
	var veggieContainer = $('.veggie-pizza');
	var pizzaContainer = $('.pizza-menu');
	//remove small id so container expands
	$('.maincontainer').removeAttr('id');
	for (idx = 0; idx < com.dawgpizza.menu.pizzas.length; ++idx) {
		pizza = com.dawgpizza.menu.pizzas[idx];
		instance = template.clone();
		instance.find('.pizzaname').html(pizza.name);
		instance.find('.description').html(pizza.description);
		instance.find('.prices').html("$"+pizza.prices[0]+" $"+pizza.prices[1]+" $"+pizza.prices[2]);
		instance.removeClass('template');
		if (pizza.vegetarian)
			var container = veggieContainer;
		else
			var container = meatContainer;
		container.append(instance);
	}
	pizzaContainer.fadeIn(500);
}

//Renders drink and dessert menus
function render(type) {
	var idx;
	var instance;
	var template = $('.template');
	var container = $('.menu');
	var item;
	//Add small id so container shrinks
	$('.maincontainer').attr('id', 'small');
	var address = com.dawgpizza.menu.drinks;
	if (type === "desserts")
		address = com.dawgpizza.menu.desserts;
	for (idx = 0; idx < address.length; ++idx) {
		item = address[idx];
		instance = template.clone();
		instance.find('.pizzaname').html(item.name);
		instance.find('.price').html("$" + item.price)
		instance.removeClass('template');
		container.append(instance);
	}
	container.fadeIn(500);
}

//Empties the menu for recreation
function clearMenu() {
	$('.pizza-menu').hide();
	$('.meat-pizza').empty();
	$('.veggie-pizza').empty();
	$('.menu').empty();
	$('.menu').hide();
	$('.menuType').hide();
	$('.menuType').fadeIn(500);
}
<|endoftext|>


<commit_before>// Webpack config for creating the production bundle.

var path = require('path');
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var strip = require('strip-loader');

var relativeAssetsPath = '../static/dist';
var assetsPath = path.join(__dirname, relativeAssetsPath);

// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'))

module.exports = {
  devtool: 'source-map',
  context: path.resolve(__dirname, '..'),
  entry: {
    'main': './src/client.js'
  },
  output: {
    path: assetsPath,
    filename: '[name]-[chunkhash].js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath: '/dist/'
  },
  module: {
    loaders: [
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: [strip.loader('debug'), 'babel?stage=0&optional=runtime&plugins=typecheck']},
      { test: /\.json$/, loader: 'json-loader' },
      { test: /\.(otf.*|woff.*|eot.*|ttf.*|svg.*)$/, loader: 'url?limit=100000' },
      { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css?importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true') },
      { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
    ]
  },
  progress: true,
  resolve: {
    modulesDirectories: [
      'src',
      'node_modules'
    ],
    extensions: ['', '.json', '.js']
  },
  plugins: [
    new CleanPlugin([relativeAssetsPath]),

    // css files from the extract-text-plugin loader
    new ExtractTextPlugin('[name]-[chunkhash].css', {allChunks: true}),
    new webpack.DefinePlugin({
      __CLIENT__: true,
      __SERVER__: false,
      __DEVELOPMENT__: false,
      __DEVTOOLS__: false
    }),

    // ignore dev config
    new webpack.IgnorePlugin(/\.\/dev/, /\/config$/),

    // set global vars
    new webpack.DefinePlugin({
      'process.env': {
        // Useful to reduce the size of client-side libraries, e.g. react
        NODE_ENV: process.env.NODE_ENV,
        API_URL: process.env.API_URL,
        APP_DOMAIN: process.env.APP_DOMAIN
      }
    }),

    // optimizations
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
          warnings: false
        }
    }),

    webpackIsomorphicToolsPlugin
  ]
};
<commit_msg>Fix env vars setting<commit_after>// Webpack config for creating the production bundle.

var path = require('path');
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var strip = require('strip-loader');

var relativeAssetsPath = '../static/dist';
var assetsPath = path.join(__dirname, relativeAssetsPath);

// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'))

module.exports = {
  devtool: 'source-map',
  context: path.resolve(__dirname, '..'),
  entry: {
    'main': './src/client.js'
  },
  output: {
    path: assetsPath,
    filename: '[name]-[chunkhash].js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath: '/dist/'
  },
  module: {
    loaders: [
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: [strip.loader('debug'), 'babel?stage=0&optional=runtime&plugins=typecheck']},
      { test: /\.json$/, loader: 'json-loader' },
      { test: /\.(otf.*|woff.*|eot.*|ttf.*|svg.*)$/, loader: 'url?limit=100000' },
      { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css?importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true') },
      { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
    ]
  },
  progress: true,
  resolve: {
    modulesDirectories: [
      'src',
      'node_modules'
    ],
    extensions: ['', '.json', '.js']
  },
  plugins: [
    new CleanPlugin([relativeAssetsPath]),

    // css files from the extract-text-plugin loader
    new ExtractTextPlugin('[name]-[chunkhash].css', {allChunks: true}),
    new webpack.DefinePlugin({
      __CLIENT__: true,
      __SERVER__: false,
      __DEVELOPMENT__: false,
      __DEVTOOLS__: false
    }),

    // ignore dev config
    new webpack.IgnorePlugin(/\.\/dev/, /\/config$/),

    // set global vars
    new webpack.DefinePlugin({
      'process.env': {
        // Useful to reduce the size of client-side libraries, e.g. react
        NODE_ENV: JSON.stringify(process.env.NODE_ENV),
        API_URL: JSON.stringify(process.env.API_URL),
        APP_DOMAIN: JSON.stringify(process.env.APP_DOMAIN)
      }
    }),

    // optimizations
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
          warnings: false
        }
    }),

    webpackIsomorphicToolsPlugin
  ]
};
<|endoftext|>


<commit_before>import React from "react";

import { withStyles } from "@material-ui/core/styles";
import TextField from '@material-ui/core/TextField';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import FormControl from "@material-ui/core/FormControl";
import FormHelperText from "@material-ui/core/FormHelperText";
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';

import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/mode/javascript/javascript";

import FormComponent from "../../classes/FormComponent";
import Form from "../../components/Form";
import AutocompleteSelect from "../../components/AutocompleteSelect";
import NetworkServerStore from "../../stores/NetworkServerStore";
import { FormLabel } from "../../../node_modules/@material-ui/core";

const styles = {
  formLabel: {
    fontSize: 12,
  },
  codeMirror: {
    zIndex: 1,
  },
};

class DeviceProfileForm extends FormComponent {
  constructor() {
    super();
    this.state = {
      tab: 0,
    };

    this.onTabChange = this.onTabChange.bind(this);
    this.getNetworkServerOptions = this.getNetworkServerOptions.bind(this);
    this.getMACVersionOptions = this.getMACVersionOptions.bind(this);
    this.getRegParamsOptions = this.getRegParamsOptions.bind(this);
    this.getPingSlotPeriodOptions = this.getPingSlotPeriodOptions.bind(this);
    this.getPayloadCodecOptions = this.getPayloadCodecOptions.bind(this);
    this.onCodeChange = this.onCodeChange.bind(this);
  }

  getNetworkServerOptions(search, callbackFunc) {
    NetworkServerStore.list(this.props.match.params.organizationID, 999, 0, resp => {
      const options = resp.result.map((ns, i) => {return {label: ns.name, value: ns.id}});
      callbackFunc(options);
    });
  }

  getMACVersionOptions(search, callbackFunc) {
    const macVersionOptions = [
      {value: "1.0.0", label: "1.0.0"},
      {value: "1.0.1", label: "1.0.1"},
      {value: "1.0.2", label: "1.0.2"},
      {value: "1.0.3", label: "1.0.3"},
      {value: "1.1.0", label: "1.1.0"},
    ];

    callbackFunc(macVersionOptions);
  }

  getRegParamsOptions(search, callbackFunc) {
    const regParamsOptions = [
      {value: "A", label: "A"},
      {value: "B", label: "B"},
    ];

    callbackFunc(regParamsOptions);
  }

  getPingSlotPeriodOptions(search, callbackFunc) {
    const pingSlotPeriodOptions = [
      {value: 32 * 1, label: "every second"},
      {value: 32 * 2, label: "every 2 seconds"},
      {value: 32 * 4, label: "every 4 seconds"},
      {value: 32 * 8, label: "every 8 seconds"},
      {value: 32 * 16, label: "every 16 seconds"},
      {value: 32 * 32, label: "every 32 seconds"},
      {value: 32 * 64, label: "every 64 seconds"},
      {value: 32 * 128, label: "every 128 seconds"},
    ];

    callbackFunc(pingSlotPeriodOptions);
  }

  getPayloadCodecOptions(search, callbackFunc) {
    const payloadCodecOptions = [
      {value: "", label: "None"},
      {value: "CAYENNE_LPP", label: "Cayenne LPP"},
      {value: "CUSTOM_JS", label: "Custom JavaScript codec functions"},
    ];

    callbackFunc(payloadCodecOptions);
  }

  onCodeChange(field, editor, data, newCode) {
    let object = this.state.object;
    object[field] = newCode;
    this.setState({
      object: object,
    });
  }

  onTabChange(e, v) {
    this.setState({
      tab: v,
    });
  }

  onChange(e) {
    super.onChange(e);
    if (e.target.id === "factoryPresetFreqsStr") {
      let object = this.state.object;
      let freqsStr = e.target.value.split(",");
      object["factoryPresetFreqs"] = freqsStr.map((f, i) => parseInt(f, 10));
      this.setState({
        object: object,
      });
    }
  }

  render() {
    if (this.state.object === undefined) {
      return null;
    }

    let factoryPresetFreqsStr = "";
    if (this.state.object.factoryPresetFreqsStr !== undefined) {
      factoryPresetFreqsStr = this.state.object.factoryPresetFreqsStr;
    } else if (this.state.object.factoryPresetFreqs !== undefined) {
      factoryPresetFreqsStr = this.state.object.factoryPresetFreqs.join(", ");
    }

    const codeMirrorOptions = {
      lineNumbers: true,
      mode: "javascript",
      theme: "default",
    };

    let payloadEncoderScript = this.state.object.payloadEncoderScript;
    let payloadDecoderScript = this.state.object.payloadDecoderScript;

    if (payloadEncoderScript === "" || payloadEncoderScript === undefined) {
      payloadEncoderScript = `// Encode encodes the given object into an array of bytes.
//  - fPort contains the LoRaWAN fPort number
//  - obj is an object, e.g. {"temperature": 22.5}
// The function must return an array of bytes, e.g. [225, 230, 255, 0]
function Encode(fPort, obj) {
  return [];
}`;
    }

    if (payloadDecoderScript === "" || payloadDecoderScript === undefined) {
      payloadDecoderScript = `// Decode decodes an array of bytes into an object.
//  - fPort contains the LoRaWAN fPort number
//  - bytes is an array of bytes, e.g. [225, 230, 255, 0]
// The function must return an object, e.g. {"temperature": 22.5}
function Decode(fPort, bytes) {
  return {};
}`;
    }

    return(
      <Form
        submitLabel={this.props.submitLabel}
        onSubmit={this.onSubmit}
        disabled={this.props.disabled}
      >
        <Tabs value={this.state.tab} onChange={this.onTabChange} indicatorColor="primary">
          <Tab label="General" />
          <Tab label="Join (OTAA / ABP)" />
          <Tab label="Class-B" />
          <Tab label="Class-C" />
          <Tab label="Codec" />
        </Tabs>

        {this.state.tab === 0 &&

          <TextField
            id="name"
            label="Device-profile name"
            margin="normal"
            value={this.state.object.name || ""}
            onChange={this.onChange}
            helperText="A name to identify the device-profile."
            required
            fullWidth
          />
          {!this.props.update && <FormControl fullWidth margin="normal">
            <FormLabel className={this.props.classes.formLabel} required>Network-server</FormLabel>
            <AutocompleteSelect
              id="networkServerID"
              label="Select network-server"
              value={this.state.object.networkServerID || ""}
              onChange={this.onChange}
              getOptions={this.getNetworkServerOptions}
            />
            <FormHelperText>
              The network-server on which this device-profile will be provisioned. After creating the device-profile, this value can't be changed.
            </FormHelperText>
          </FormControl>}
          <FormControl fullWidth margin="normal">
            <FormLabel className={this.props.classes.formLabel} required>LoRaWAN MAC version</FormLabel>
            <AutocompleteSelect
              id="macVersion"
              label="Select LoRaWAN MAC version"
              value={this.state.object.macVersion || ""}
              onChange={this.onChange}
              getOptions={this.getMACVersionOptions}
            />
            <FormHelperText>
              The LoRaWAN MAC version supported by the device.
            </FormHelperText>
          </FormControl>
          <FormControl fullWidth margin="normal">
            <FormLabel className={this.props.classes.formLabel} required>LoRaWAN Regional Parameters revision</FormLabel>
            <AutocompleteSelect
              id="regParamsRevision"
              label="Select LoRaWAN Regional Parameters revision"
              value={this.state.object.regParamsRevision || ""}
              onChange={this.onChange}
              getOptions={this.getRegParamsOptions}
            />
            <FormHelperText>
                Revision of the Regional Parameters specification supported by the device.
            </FormHelperText>
          </FormControl>
          <TextField
            id="maxEIRP"
            label="Max EIRP"
            type="number"
            margin="normal"
            value={this.state.object.maxEIRP || 0}
            onChange={this.onChange}
            helperText="Maximum EIRP supported by the device."
            required
            fullWidth
          />

}

        {this.state.tab === 1 &&

          <FormControl fullWidth margin="normal">
            <FormControlLabel
              label="Device supports OTAA"
              control={
                <Checkbox
                  id="supportsJoin"
                  checked={!!this.state.object.supportsJoin}
                  onChange={this.onChange}
                  color="primary"
                />
              }
            />
          </FormControl>
          {!this.state.object.supportsJoin && <TextField
            id="rxDelay1"
            label="RX1 delay"
            type="number"
            margin="normal"
            value={this.state.object.rxDelay1 || 0}
            onChange={this.onChange}
            helperText="RX1 delay (valid values are 0 - 15)."
            required
            fullWidth
          />}
          {!this.state.object.supportsJoin && <TextField
            id="rxDROffset1"
            label="RX1 data-rate offset"
            type="number"
            margin="normal"
            value={this.state.object.rxDROffset1 || 0}
            onChange={this.onChange}
            helperText="Please refer the LoRaWAN Regional Parameters specification for valid values."
            required
            fullWidth
          />}
          {!this.state.object.supportsJoin && <TextField
            id="rxDataRate2"
            label="RX2 data-rate"
            type="number"
            margin="normal"
            value={this.state.object.rxDataRate2 || 0}
            onChange={this.onChange}
            helperText="Please refer the LoRaWAN Regional Parameters specification for valid values."
            required
            fullWidth
          />}
          {!this.state.object.supportsJoin && <TextField
            id="rxFreq2"
            label="RX2 channel frequency (Hz)"
            type="number"
            margin="normal"
            value={this.state.object.rxFreq2 || 0}
            onChange={this.onChange}
            required
            fullWidth
          />}
          {!this.state.object.supportsJoin && <TextField
            id="factoryPresetFreqsStr"
            label="Factory-preset frequencies (Hz)"
            margin="normal"
            value={factoryPresetFreqsStr}
            onChange={this.onChange}
            helperText="List of factory-preset frequencies (Hz), comma separated."
            required
            fullWidth
          />}

}

        {this.state.tab === 2 &&

          <FormControl fullWidth margin="normal">
            <FormControlLabel
              label="Device supports Class-B"
              control={
                <Checkbox
                  id="supportsClassB"
                  checked={!!this.state.object.supportsClassB}
                  onChange={this.onChange}
                  color="primary"
                />
              }
            />
          </FormControl>

          {this.state.object.supportsClassB && <TextField
            id="classBTimeout"
            label="Class-B confirmed downlink timeout"
            type="number"
            margin="normal"
            value={this.state.object.classBTimeout || 0}
            onChange={this.onChange}
            helperText="Class-B timeout (in seconds) for confirmed downlink transmissions."
            required
            fullWidth
          />}
          {this.state.object.supportsClassB && <FormControl
              fullWidth
              margin="normal"
            >
              <FormLabel className={this.props.classes.formLabel} required>Class-B ping-slot periodicity</FormLabel>
              <AutocompleteSelect
                id="pingSlotPeriod"
                label="Select Class-B ping-slot periodicity"
                value={this.state.object.pingSlotPeriod || ""}
                onChange={this.onChange}
                getOptions={this.getPingSlotPeriodOptions}
              />
              <FormHelperText>Class-B ping-slot periodicity.</FormHelperText>
          </FormControl>}
          {this.state.object.supportsClassB && <TextField
            id="pingSlotDR"
            label="Class-B ping-slot data-rate"
            type="number"
            margin="normal"
            value={this.state.object.pingSlotDR || 0}
            onChange={this.onChange}
            required
            fullWidth
          />}
          {this.state.object.supportsClassB && <TextField
            id="pingSlotFreq"
            label="Class-B ping-slot frequency (Hz)"
            type="number"
            margin="normal"
            value={this.state.object.pingSlotFreq || 0}
            onChange={this.onChange}
            required
            fullWidth
          />}

}

        {this.state.tab === 3 &&

          <FormControl fullWidth margin="normal">
            <FormControlLabel
              label="Device supports Class-C"
              control={
                <Checkbox
                  id="supportsClassC"
                  checked={!!this.state.object.supportsClassC}
                  onChange={this.onChange}
                  color="primary"
                />
              }
            />
          </FormControl>

          {this.state.object.supportsClassC && <TextField
            id="classCTimeout"
            label="Class-C confirmed downlink timeout"
            type="number"
            margin="normal"
            value={this.state.object.classCTimeout || 0}
            onChange={this.onChange}
            helperText="Class-C timeout (in seconds) for confirmed downlink transmissions."
            required
            fullWidth
          />}

}

        {this.state.tab === 4 &&

          <FormControl fullWidth margin="normal">
            <FormLabel className={this.props.classes.formLabel}>Payload codec</FormLabel>
            <AutocompleteSelect
              id="payloadCodec"
              label="Select payload codec"
              value={this.state.object.payloadCodec || ""}
              onChange={this.onChange}
              getOptions={this.getPayloadCodecOptions}
            />
            <FormHelperText>
              By defining a payload codec, LoRa App Server can encode and decode the binary device payload for you.
            </FormHelperText>
          </FormControl>

          {this.state.object.payloadCodec === "CUSTOM_JS" && <FormControl fullWidth margin="normal">
            <CodeMirror
              value={payloadDecoderScript}
              options={codeMirrorOptions}
              onBeforeChange={this.onCodeChange.bind(this, 'payloadDecoderScript')}
              className={this.props.classes.codeMirror}
            />
            <FormHelperText>
              The function must have the signature function Decode(fPort, bytes) and must return an object.
              LoRa App Server will convert this object to JSON.
            </FormHelperText>
          </FormControl>}
          {this.state.object.payloadCodec === "CUSTOM_JS" && <FormControl fullWidth margin="normal">
            <CodeMirror
              value={payloadEncoderScript}
              options={codeMirrorOptions}
              onBeforeChange={this.onCodeChange.bind(this, 'payloadEncoderScript')}
              className={this.props.classes.codeMirror}
            />
            <FormHelperText>
              The function must have the signature function Encode(fPort, obj) and must return an array
              of bytes.
            </FormHelperText>
          </FormControl>}

}
      </Form>
    );
  }
}

export default withStyles(styles)(DeviceProfileForm);
<commit_msg>Always display Class-C timeout field.<commit_after>import React from "react";

import { withStyles } from "@material-ui/core/styles";
import TextField from '@material-ui/core/TextField';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import FormControl from "@material-ui/core/FormControl";
import FormHelperText from "@material-ui/core/FormHelperText";
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';

import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/mode/javascript/javascript";

import FormComponent from "../../classes/FormComponent";
import Form from "../../components/Form";
import AutocompleteSelect from "../../components/AutocompleteSelect";
import NetworkServerStore from "../../stores/NetworkServerStore";
import { FormLabel } from "../../../node_modules/@material-ui/core";

const styles = {
  formLabel: {
    fontSize: 12,
  },
  codeMirror: {
    zIndex: 1,
  },
};

class DeviceProfileForm extends FormComponent {
  constructor() {
    super();
    this.state = {
      tab: 0,
    };

    this.onTabChange = this.onTabChange.bind(this);
    this.getNetworkServerOptions = this.getNetworkServerOptions.bind(this);
    this.getMACVersionOptions = this.getMACVersionOptions.bind(this);
    this.getRegParamsOptions = this.getRegParamsOptions.bind(this);
    this.getPingSlotPeriodOptions = this.getPingSlotPeriodOptions.bind(this);
    this.getPayloadCodecOptions = this.getPayloadCodecOptions.bind(this);
    this.onCodeChange = this.onCodeChange.bind(this);
  }

  getNetworkServerOptions(search, callbackFunc) {
    NetworkServerStore.list(this.props.match.params.organizationID, 999, 0, resp => {
      const options = resp.result.map((ns, i) => {return {label: ns.name, value: ns.id}});
      callbackFunc(options);
    });
  }

  getMACVersionOptions(search, callbackFunc) {
    const macVersionOptions = [
      {value: "1.0.0", label: "1.0.0"},
      {value: "1.0.1", label: "1.0.1"},
      {value: "1.0.2", label: "1.0.2"},
      {value: "1.0.3", label: "1.0.3"},
      {value: "1.1.0", label: "1.1.0"},
    ];

    callbackFunc(macVersionOptions);
  }

  getRegParamsOptions(search, callbackFunc) {
    const regParamsOptions = [
      {value: "A", label: "A"},
      {value: "B", label: "B"},
    ];

    callbackFunc(regParamsOptions);
  }

  getPingSlotPeriodOptions(search, callbackFunc) {
    const pingSlotPeriodOptions = [
      {value: 32 * 1, label: "every second"},
      {value: 32 * 2, label: "every 2 seconds"},
      {value: 32 * 4, label: "every 4 seconds"},
      {value: 32 * 8, label: "every 8 seconds"},
      {value: 32 * 16, label: "every 16 seconds"},
      {value: 32 * 32, label: "every 32 seconds"},
      {value: 32 * 64, label: "every 64 seconds"},
      {value: 32 * 128, label: "every 128 seconds"},
    ];

    callbackFunc(pingSlotPeriodOptions);
  }

  getPayloadCodecOptions(search, callbackFunc) {
    const payloadCodecOptions = [
      {value: "", label: "None"},
      {value: "CAYENNE_LPP", label: "Cayenne LPP"},
      {value: "CUSTOM_JS", label: "Custom JavaScript codec functions"},
    ];

    callbackFunc(payloadCodecOptions);
  }

  onCodeChange(field, editor, data, newCode) {
    let object = this.state.object;
    object[field] = newCode;
    this.setState({
      object: object,
    });
  }

  onTabChange(e, v) {
    this.setState({
      tab: v,
    });
  }

  onChange(e) {
    super.onChange(e);
    if (e.target.id === "factoryPresetFreqsStr") {
      let object = this.state.object;
      let freqsStr = e.target.value.split(",");
      object["factoryPresetFreqs"] = freqsStr.map((f, i) => parseInt(f, 10));
      this.setState({
        object: object,
      });
    }
  }

  render() {
    if (this.state.object === undefined) {
      return null;
    }

    let factoryPresetFreqsStr = "";
    if (this.state.object.factoryPresetFreqsStr !== undefined) {
      factoryPresetFreqsStr = this.state.object.factoryPresetFreqsStr;
    } else if (this.state.object.factoryPresetFreqs !== undefined) {
      factoryPresetFreqsStr = this.state.object.factoryPresetFreqs.join(", ");
    }

    const codeMirrorOptions = {
      lineNumbers: true,
      mode: "javascript",
      theme: "default",
    };

    let payloadEncoderScript = this.state.object.payloadEncoderScript;
    let payloadDecoderScript = this.state.object.payloadDecoderScript;

    if (payloadEncoderScript === "" || payloadEncoderScript === undefined) {
      payloadEncoderScript = `// Encode encodes the given object into an array of bytes.
//  - fPort contains the LoRaWAN fPort number
//  - obj is an object, e.g. {"temperature": 22.5}
// The function must return an array of bytes, e.g. [225, 230, 255, 0]
function Encode(fPort, obj) {
  return [];
}`;
    }

    if (payloadDecoderScript === "" || payloadDecoderScript === undefined) {
      payloadDecoderScript = `// Decode decodes an array of bytes into an object.
//  - fPort contains the LoRaWAN fPort number
//  - bytes is an array of bytes, e.g. [225, 230, 255, 0]
// The function must return an object, e.g. {"temperature": 22.5}
function Decode(fPort, bytes) {
  return {};
}`;
    }

    return(
      <Form
        submitLabel={this.props.submitLabel}
        onSubmit={this.onSubmit}
        disabled={this.props.disabled}
      >
        <Tabs value={this.state.tab} onChange={this.onTabChange} indicatorColor="primary">
          <Tab label="General" />
          <Tab label="Join (OTAA / ABP)" />
          <Tab label="Class-B" />
          <Tab label="Class-C" />
          <Tab label="Codec" />
        </Tabs>

        {this.state.tab === 0 &&

          <TextField
            id="name"
            label="Device-profile name"
            margin="normal"
            value={this.state.object.name || ""}
            onChange={this.onChange}
            helperText="A name to identify the device-profile."
            required
            fullWidth
          />
          {!this.props.update && <FormControl fullWidth margin="normal">
            <FormLabel className={this.props.classes.formLabel} required>Network-server</FormLabel>
            <AutocompleteSelect
              id="networkServerID"
              label="Select network-server"
              value={this.state.object.networkServerID || ""}
              onChange={this.onChange}
              getOptions={this.getNetworkServerOptions}
            />
            <FormHelperText>
              The network-server on which this device-profile will be provisioned. After creating the device-profile, this value can't be changed.
            </FormHelperText>
          </FormControl>}
          <FormControl fullWidth margin="normal">
            <FormLabel className={this.props.classes.formLabel} required>LoRaWAN MAC version</FormLabel>
            <AutocompleteSelect
              id="macVersion"
              label="Select LoRaWAN MAC version"
              value={this.state.object.macVersion || ""}
              onChange={this.onChange}
              getOptions={this.getMACVersionOptions}
            />
            <FormHelperText>
              The LoRaWAN MAC version supported by the device.
            </FormHelperText>
          </FormControl>
          <FormControl fullWidth margin="normal">
            <FormLabel className={this.props.classes.formLabel} required>LoRaWAN Regional Parameters revision</FormLabel>
            <AutocompleteSelect
              id="regParamsRevision"
              label="Select LoRaWAN Regional Parameters revision"
              value={this.state.object.regParamsRevision || ""}
              onChange={this.onChange}
              getOptions={this.getRegParamsOptions}
            />
            <FormHelperText>
                Revision of the Regional Parameters specification supported by the device.
            </FormHelperText>
          </FormControl>
          <TextField
            id="maxEIRP"
            label="Max EIRP"
            type="number"
            margin="normal"
            value={this.state.object.maxEIRP || 0}
            onChange={this.onChange}
            helperText="Maximum EIRP supported by the device."
            required
            fullWidth
          />

}

        {this.state.tab === 1 &&

          <FormControl fullWidth margin="normal">
            <FormControlLabel
              label="Device supports OTAA"
              control={
                <Checkbox
                  id="supportsJoin"
                  checked={!!this.state.object.supportsJoin}
                  onChange={this.onChange}
                  color="primary"
                />
              }
            />
          </FormControl>
          {!this.state.object.supportsJoin && <TextField
            id="rxDelay1"
            label="RX1 delay"
            type="number"
            margin="normal"
            value={this.state.object.rxDelay1 || 0}
            onChange={this.onChange}
            helperText="RX1 delay (valid values are 0 - 15)."
            required
            fullWidth
          />}
          {!this.state.object.supportsJoin && <TextField
            id="rxDROffset1"
            label="RX1 data-rate offset"
            type="number"
            margin="normal"
            value={this.state.object.rxDROffset1 || 0}
            onChange={this.onChange}
            helperText="Please refer the LoRaWAN Regional Parameters specification for valid values."
            required
            fullWidth
          />}
          {!this.state.object.supportsJoin && <TextField
            id="rxDataRate2"
            label="RX2 data-rate"
            type="number"
            margin="normal"
            value={this.state.object.rxDataRate2 || 0}
            onChange={this.onChange}
            helperText="Please refer the LoRaWAN Regional Parameters specification for valid values."
            required
            fullWidth
          />}
          {!this.state.object.supportsJoin && <TextField
            id="rxFreq2"
            label="RX2 channel frequency (Hz)"
            type="number"
            margin="normal"
            value={this.state.object.rxFreq2 || 0}
            onChange={this.onChange}
            required
            fullWidth
          />}
          {!this.state.object.supportsJoin && <TextField
            id="factoryPresetFreqsStr"
            label="Factory-preset frequencies (Hz)"
            margin="normal"
            value={factoryPresetFreqsStr}
            onChange={this.onChange}
            helperText="List of factory-preset frequencies (Hz), comma separated."
            required
            fullWidth
          />}

}

        {this.state.tab === 2 &&

          <FormControl fullWidth margin="normal">
            <FormControlLabel
              label="Device supports Class-B"
              control={
                <Checkbox
                  id="supportsClassB"
                  checked={!!this.state.object.supportsClassB}
                  onChange={this.onChange}
                  color="primary"
                />
              }
            />
          </FormControl>

          {this.state.object.supportsClassB && <TextField
            id="classBTimeout"
            label="Class-B confirmed downlink timeout"
            type="number"
            margin="normal"
            value={this.state.object.classBTimeout || 0}
            onChange={this.onChange}
            helperText="Class-B timeout (in seconds) for confirmed downlink transmissions."
            required
            fullWidth
          />}
          {this.state.object.supportsClassB && <FormControl
              fullWidth
              margin="normal"
            >
              <FormLabel className={this.props.classes.formLabel} required>Class-B ping-slot periodicity</FormLabel>
              <AutocompleteSelect
                id="pingSlotPeriod"
                label="Select Class-B ping-slot periodicity"
                value={this.state.object.pingSlotPeriod || ""}
                onChange={this.onChange}
                getOptions={this.getPingSlotPeriodOptions}
              />
              <FormHelperText>Class-B ping-slot periodicity.</FormHelperText>
          </FormControl>}
          {this.state.object.supportsClassB && <TextField
            id="pingSlotDR"
            label="Class-B ping-slot data-rate"
            type="number"
            margin="normal"
            value={this.state.object.pingSlotDR || 0}
            onChange={this.onChange}
            required
            fullWidth
          />}
          {this.state.object.supportsClassB && <TextField
            id="pingSlotFreq"
            label="Class-B ping-slot frequency (Hz)"
            type="number"
            margin="normal"
            value={this.state.object.pingSlotFreq || 0}
            onChange={this.onChange}
            required
            fullWidth
          />}

}

        {this.state.tab === 3 &&

          <FormControl fullWidth margin="normal">
            <FormControlLabel
              label="Device supports Class-C"
              control={
                <Checkbox
                  id="supportsClassC"
                  checked={!!this.state.object.supportsClassC}
                  onChange={this.onChange}
                  color="primary"
                />
              }
            />
            <FormHelperText>Select this option when the device will operate as Class-C device immediately after activation. In case it sends a DeviceModeInd mac-command when it changes to Class-C, do not select this option.</FormHelperText>
          </FormControl>

          <TextField
            id="classCTimeout"
            label="Class-C confirmed downlink timeout"
            type="number"
            margin="normal"
            value={this.state.object.classCTimeout || 0}
            onChange={this.onChange}
            helperText="Class-C timeout (in seconds) for confirmed downlink transmissions."
            required
            fullWidth
          />

}

        {this.state.tab === 4 &&

          <FormControl fullWidth margin="normal">
            <FormLabel className={this.props.classes.formLabel}>Payload codec</FormLabel>
            <AutocompleteSelect
              id="payloadCodec"
              label="Select payload codec"
              value={this.state.object.payloadCodec || ""}
              onChange={this.onChange}
              getOptions={this.getPayloadCodecOptions}
            />
            <FormHelperText>
              By defining a payload codec, LoRa App Server can encode and decode the binary device payload for you.
            </FormHelperText>
          </FormControl>

          {this.state.object.payloadCodec === "CUSTOM_JS" && <FormControl fullWidth margin="normal">
            <CodeMirror
              value={payloadDecoderScript}
              options={codeMirrorOptions}
              onBeforeChange={this.onCodeChange.bind(this, 'payloadDecoderScript')}
              className={this.props.classes.codeMirror}
            />
            <FormHelperText>
              The function must have the signature function Decode(fPort, bytes) and must return an object.
              LoRa App Server will convert this object to JSON.
            </FormHelperText>
          </FormControl>}
          {this.state.object.payloadCodec === "CUSTOM_JS" && <FormControl fullWidth margin="normal">
            <CodeMirror
              value={payloadEncoderScript}
              options={codeMirrorOptions}
              onBeforeChange={this.onCodeChange.bind(this, 'payloadEncoderScript')}
              className={this.props.classes.codeMirror}
            />
            <FormHelperText>
              The function must have the signature function Encode(fPort, obj) and must return an array
              of bytes.
            </FormHelperText>
          </FormControl>}

}
      </Form>
    );
  }
}

export default withStyles(styles)(DeviceProfileForm);
<|endoftext|>


<commit_before>const _ = require('underscore');

// Given an Error (eg, 'new Error'), return the stack associated with
// that error as an array. More recently called functions appear first
// and each element is an object with keys:
// - file: filename as it appears in the stack
// - line: 1-indexed line number in file, as a Number
// - column: 1-indexed column in line, as a Number
// - func: name of the function in the frame (maybe null)
//
// Accomplishes this by parsing the text representation of the stack
// with regular expressions. Unlikely to work anywhere but v8.
//
// If a function on the stack has been marked with mark(), don't
// return anything past that function. We call this the "user portion"
// of the stack.
export function parse(err) {
  var frames = err.stack.split('\n');

  frames.shift(); // at least the first line is the exception

  // longjohn adds lines of the form '---' (45 times) to separate
  // the trace across async boundaries. It's not clear if we need to
  // separate the trace in the same way we do for future boundaries below
  // (it's not clear that that code is still useful either)
  // so for now, we'll just remove such lines
  frames = _.filter(frames, f => !f.match(/^\-{45}$/));

  // "    - - - - -"
  // This is something added when you throw an Error through a Future. The
  // stack above the dashes is the stack of the 'wait' call; the stack below
  // is the stack inside the fiber where the Error is originally
  // constructed.
  // XXX This code assumes that the stack trace can only be split once. It's not
  // clear whether this can happen multiple times.
  const indexOfFiberSplit = frames.indexOf('    - - - - -');

  if (indexOfFiberSplit === -1) {
    // This is a normal stack trace, not a split fiber stack trace
    return {
      outsideFiber: parseStackFrames(frames)
    }
  }

  // If this is a split stack trace from a future, parse the frames above and
  // below the split separately.
  const outsideFiber = parseStackFrames(frames);
  const insideFiber = parseStackFrames(frames.slice(indexOfFiberSplit + 1));

  return {
    insideFiber,
    outsideFiber
  };
}

// Decorator. Mark the point at which a stack trace returned by
// parse() should stop: no frames earlier than this point will be
// included in the parsed stack. Confusingly, in the argot of the
// times, you'd say that frames "higher up" than this or "above" this
// will not be returned, but you'd also say that those frames are "at
// the bottom of the stack". Frames below the bottom are the outer
// context of the framework running the user's code.
export function markBottom(f) {
  /* eslint-disable camelcase */
  return function __bottom_mark__() {
    return f.apply(this, arguments);
  };
  /* eslint-enable camelcase */
}

// Decorator. Mark the point at which a stack trace returned by
// parse() should begin: no frames later than this point will be
// included in the parsed stack. The opposite of markBottom().
// Frames above the top are helper functions defined by the
// framework and executed by user code whose internal behavior
// should not be exposed.
export function markTop(f) {
  /* eslint-disable camelcase */
  return function __top_mark__() {
    return f.apply(this, arguments);
  };
  /* eslint-enable camelcase */
}

function parseStackFrames(frames) {
  let stop = false;
  let ret = [];
  frames.forEach((frame) => {
    if (stop) {
      return;
    }

    let m;

    /* eslint-disable max-len */
    if (m = frame.match(/^\s*at\s*((new )?.+?)\s*(\[as\s*([^\]]*)\]\s*)?\((.*?)(:(\d+))?(:(\d+))?\)\s*$/)) {
      // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
      // "    at My.Function (/path/to/myfile.js:532:39)"
      // "    at Array.forEach (native)"
      // "    at new My.Class (file.js:1:2)"
      // "    at [object Object].main.registerCommand.name [as func] (meteor/tools/commands.js:1225:19)"
      // "    at __top_mark__ [as matchErr] (meteor/tools/parse-stack.js:82:14)"
      //
      // In that last example, it is not at all clear to me what the
      // 'as' stanza refers to, but it is in m[3] if you find a use for it.
      if (m[1].match(/(?:^|\.)__top_mark__$/)) {
        // m[1] could be Object.__top_mark__ or something like that
        // depending on where exactly you put the function returned by
        // markTop
        ret = [];
        return;
      }
      if (m[1].match(/(?:^|\.)__bottom_mark__$/)) {
        stop = true;
        return;
      }
      ret.push({
        func: m[1],
        file: m[5],
        line: m[7] ? +m[7] : undefined,
        column: m[9] ? +m[9] : undefined
      });
      return;
    }
    /* eslint-enable max-len */

    if (m = frame.match(/^\s*at\s+(.+?)(:(\d+))?(:(\d+))?\s*$/)) {
      // "    at /path/to/myfile.js:532:39"
      ret.push({
        file: m[1],
        line: m[3] ? +m[3] : undefined,
        column: m[5] ? +m[5] : undefined
      });
      return;
    }

    if (m = frame.match(/^\s*-\s*-\s*-\s*-\s*-\s*$/)) {
      // Stop parsing if we reach a stack split from a Future
      stop = true;
      return;
    }

    if (_.isEmpty(ret)) {
      // We haven't found any stack frames, so probably we have newlines in the
      // error message. Just skip this line.
      return;
    }

    throw new Error("Couldn't parse stack frame: '" + frame + "'");
  });

  return ret;
}
<commit_msg>Use longjohn to repair stacktraces on self-test<commit_after>const _ = require('underscore');

// Given an Error (eg, 'new Error'), return the stack associated with
// that error as an array. More recently called functions appear first
// and each element is an object with keys:
// - file: filename as it appears in the stack
// - line: 1-indexed line number in file, as a Number
// - column: 1-indexed column in line, as a Number
// - func: name of the function in the frame (maybe null)
//
// Accomplishes this by parsing the text representation of the stack
// with regular expressions. Unlikely to work anywhere but v8.
//
// If a function on the stack has been marked with mark(), don't
// return anything past that function. We call this the "user portion"
// of the stack.
export function parse(err) {
  let frames = err.stack.split('\n');

  frames.shift(); // at least the first line is the exception

  // longjohn adds lines of the form '---' (45 times) to separate
  // the trace across async boundaries. It's not clear if we need to
  // separate the trace in the same way we do for future boundaries below
  // (it's not clear that that code is still useful either)
  // so for now, we'll just remove such lines
  frames = _.filter(frames, f => !f.match(/^\-{45}$/));

  // "    - - - - -"
  // This is something added when you throw an Error through a Future. The
  // stack above the dashes is the stack of the 'wait' call; the stack below
  // is the stack inside the fiber where the Error is originally
  // constructed.
  // XXX This code assumes that the stack trace can only be split once. It's not
  // clear whether this can happen multiple times.
  const indexOfFiberSplit = frames.indexOf('    - - - - -');

  if (indexOfFiberSplit === -1) {
    // This is a normal stack trace, not a split fiber stack trace
    return {
      outsideFiber: parseStackFrames(frames)
    }
  }

  // If this is a split stack trace from a future, parse the frames above and
  // below the split separately.
  const outsideFiber = parseStackFrames(frames);
  const insideFiber = parseStackFrames(frames.slice(indexOfFiberSplit + 1));

  return {
    insideFiber,
    outsideFiber
  };
}

// Decorator. Mark the point at which a stack trace returned by
// parse() should stop: no frames earlier than this point will be
// included in the parsed stack. Confusingly, in the argot of the
// times, you'd say that frames "higher up" than this or "above" this
// will not be returned, but you'd also say that those frames are "at
// the bottom of the stack". Frames below the bottom are the outer
// context of the framework running the user's code.
export function markBottom(f) {
  /* eslint-disable camelcase */
  return function __bottom_mark__() {
    return f.apply(this, arguments);
  };
  /* eslint-enable camelcase */
}

// Decorator. Mark the point at which a stack trace returned by
// parse() should begin: no frames later than this point will be
// included in the parsed stack. The opposite of markBottom().
// Frames above the top are helper functions defined by the
// framework and executed by user code whose internal behavior
// should not be exposed.
export function markTop(f) {
  /* eslint-disable camelcase */
  return function __top_mark__() {
    return f.apply(this, arguments);
  };
  /* eslint-enable camelcase */
}

function parseStackFrames(frames) {
  let stop = false;
  let ret = [];
  frames.forEach((frame) => {
    if (stop) {
      return;
    }

    let m;

    /* eslint-disable max-len */
    if (m = frame.match(/^\s*at\s*((new )?.+?)\s*(\[as\s*([^\]]*)\]\s*)?\((.*?)(:(\d+))?(:(\d+))?\)\s*$/)) {
      // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
      // "    at My.Function (/path/to/myfile.js:532:39)"
      // "    at Array.forEach (native)"
      // "    at new My.Class (file.js:1:2)"
      // "    at [object Object].main.registerCommand.name [as func] (meteor/tools/commands.js:1225:19)"
      // "    at __top_mark__ [as matchErr] (meteor/tools/parse-stack.js:82:14)"
      //
      // In that last example, it is not at all clear to me what the
      // 'as' stanza refers to, but it is in m[3] if you find a use for it.
      if (m[1].match(/(?:^|\.)__top_mark__$/)) {
        // m[1] could be Object.__top_mark__ or something like that
        // depending on where exactly you put the function returned by
        // markTop
        ret = [];
        return;
      }
      if (m[1].match(/(?:^|\.)__bottom_mark__$/)) {
        stop = true;
        return;
      }
      ret.push({
        func: m[1],
        file: m[5],
        line: m[7] ? +m[7] : undefined,
        column: m[9] ? +m[9] : undefined
      });
      return;
    }
    /* eslint-enable max-len */

    if (m = frame.match(/^\s*at\s+(.+?)(:(\d+))?(:(\d+))?\s*$/)) {
      // "    at /path/to/myfile.js:532:39"
      ret.push({
        file: m[1],
        line: m[3] ? +m[3] : undefined,
        column: m[5] ? +m[5] : undefined
      });
      return;
    }

    if (m = frame.match(/^\s*-\s*-\s*-\s*-\s*-\s*$/)) {
      // Stop parsing if we reach a stack split from a Future
      stop = true;
      return;
    }

    if (_.isEmpty(ret)) {
      // We haven't found any stack frames, so probably we have newlines in the
      // error message. Just skip this line.
      return;
    }

    throw new Error("Couldn't parse stack frame: '" + frame + "'");
  });

  return ret;
}
<|endoftext|>


<commit_before>// Handles CRUD requests for reports.
'use strict';

var express = require('express');
var Report = require('../../../models/report');
var batch = require('../../../models/batch');
var ReportQuery = require('../../../models/query/report-query');
var _ = require('underscore');

module.exports = function(app, user) {
  app = app || express();
  user = user || require('../authorization')(app);

  // Get a list of all Reports
  app.get('/api/v1/report', user.can('view data'), function(req, res) {
    var page = req.query.page;

    // Parse query string
    var queryData = parseQueryData(req.query);
    if (queryData) {
      var query = new ReportQuery(queryData);

      // Query for reports using fti
      Report.queryReports(query, page, function(err, reports) {
        if (err) res.send(err.status, err.message);
        else res.send(200, reports);
      });
    } else {
      // Return all reports using pagination
      Report.findSortedPage({}, page, function(err, reports) {
        if (err) res.send(err.status, err.message);
        else res.send(200, reports);
      });
    }
  });

  // Load batch
  app.get('/api/v1/report/batch', user.can('view data'), function(req, res) {
    batch.load(req.session.user._id, function(err, reports) {
      if (err) res.send(err.status, err.message);
      else res.send(200, { results: reports, total: reports.length });
    });
  });

  // Checkout new batch
  app.patch('/api/v1/report/batch', user.can('edit data'), function(req, res) {
    var query = new ReportQuery(req.body);
    batch.checkout(req.session.user._id, query, function(err, reports) {
      if (err) res.send(err.status, err.message);
      else res.send(200, { results: reports, total: reports.length });
    });
  });

  // Cancel batch
  app.put('/api/v1/report/batch', user.can('edit data'), function(req, res) {
    batch.cancel(req.session.user._id, function(err) {
      if (err) res.send(err.status, err.message);
      else res.send(200);
    });
  });

  // Get Report by id
  app.get('/api/v1/report/:_id', function(req, res) {
    Report.findById(req.params._id, function(err, report) {
      if (err) res.send(err.status, err.message);
      else if (!report) res.send(404);
      else res.send(200, report);
    });
  });

  // Update Report data
  app.put('/api/v1/report/:_id', user.can('edit data'), function(req, res) {
    // Find report to update
    Report.findById(req.params._id, function(err, report) {
      if (err) return res.send(err.status, err.message);
      if (!report) return res.send(404);
      // Update the actual value
      _.each(_.pick(req.body, ['flagged', '_incident', 'read']), function(val, key) {
        report[key] = val;
      });
      // Save report
      report.save(function(err, numberAffected) {
        if (err) res.send(err.status, err.message);
        else if (!numberAffected) res.send(404);
        else res.send(200);
      });
    });
  });

  // Delete all reports
  app.delete('/api/v1/report/_all', user.can('edit data'), function(req, res) {
    Report.remove(function(err) {
      if (err) res.send(err.status, err.message);
      else res.send(200);
    });
  });

  // Mark selected reports as read
  app.patch('/api/v1/report/_read', user.can('edit data'), function(req, res) {
    if (!req.body.ids || !req.body.ids.length) return res.send(200);

    Report.find({ _id: { $in: req.body.ids } }, function(err, reports) {
      if (err) return res.send(err.status, err.message);
      if (reports.length === 0) return res.send(200);
      var remaining = reports.length;
      reports.forEach(function(report) {
        // Mark each report as read only to catch it in model
        report.toggleRead(req.body.read);
        report.save(function(err) {
          if (err) return res.send(err.status, err.message);
          if (--remaining === 0) return res.send(200);
        });
      });
    });
  });

  // Flag selected reports
  app.patch('/api/v1/report/_flag', user.can('edit data'), function(req, res) {
    if (!req.body.ids || !req.body.ids.length) return res.send(200);

    Report.find({ _id: { $in: req.body.ids } }, function(err, reports) {
      if (err) return res.send(err.status, err.message);
      if (reports.length === 0) return res.send(200);
      var remaining = reports.length;
      reports.forEach(function(report) {
        // Mark each report as flagged to catch it in model
        report.toggleFlagged(req.body.flagged);
        report.save(function(err) {
          if (err) return res.send(err.status, err.message);
          if (--remaining === 0) return res.send(200);
        });
      });
    });
  });

  // Link selected reports to one incident
  app.patch('/api/v1/report/_link', user.can('edit data'), function(req, res) {
    if (!req.body.ids || !req.body.ids.length) return res.send(200);

    Report.find({ _id: { $in: req.body.ids } }, function(err, reports) {
      if (err) return res.send(err.status, err.message);
      if (reports.length === 0) return res.send(200);
      var remaining = reports.length;
      reports.forEach(function(report) {
        report.read = true;
        report._incident = req.body.incident;
        report.save(function(err) {
          if (err) return res.send(err.status, err.message);
          if (--remaining === 0) return res.send(200);
        });
      });
    });
  });

  return app;
};

// Determine the search keywords
function parseQueryData(queryData) {
  if (!queryData) return;

  // Data passed through URL parameters
  return _.pick(queryData, ['keywords', 'status', 'after', 'before', 'media',
                            'sourceId', 'incidentId', 'author']);
}
<commit_msg>Added logging statements for report-controller.js<commit_after>// Handles CRUD requests for reports.
'use strict';

var express = require('express');
var Report = require('../../../models/report');
var batch = require('../../../models/batch');
var ReportQuery = require('../../../models/query/report-query');
var _ = require('underscore');
var logger = require('../../logger');

module.exports = function(app, user) {
  app = app || express();
  user = user || require('../authorization')(app);

  // Get a list of all Reports
  app.get('/api/v1/report', user.can('view data'), function(req, res) {
    var page = req.query.page;

    // Parse query string
    var queryData = parseQueryData(req.query);
    if (queryData) {
      var query = new ReportQuery(queryData);

      // Query for reports using fti
      Report.queryReports(query, page, function(err, reports) {
        if (err) res.send(err.status, err.message);
        else res.send(200, reports);
      });
    } else {
      // Return all reports using pagination
      Report.findSortedPage({}, page, function(err, reports) {
        if (err) res.send(err.status, err.message);
        else res.send(200, reports);
      });
    }
  });

  // Load batch
  app.get('/api/v1/report/batch', user.can('view data'), function(req, res) {
    batch.load(req.session.user._id, function(err, reports) {
      if (err) res.send(err.status, err.message);
      else res.send(200, { results: reports, total: reports.length });
    });
  });

  // Checkout new batch
  app.patch('/api/v1/report/batch', user.can('edit data'), function(req, res) {
    var query = new ReportQuery(req.body);
    batch.checkout(req.session.user._id, query, function(err, reports) {
      if (err) res.send(err.status, err.message);
      else res.send(200, { results: reports, total: reports.length });
    });
  });

  // Cancel batch
  app.put('/api/v1/report/batch', user.can('edit data'), function(req, res) {
    batch.cancel(req.session.user._id, function(err) {
      if (err) res.send(err.status, err.message);
      else res.send(200);
    });
  });

  // Get Report by id
  app.get('/api/v1/report/:_id', function(req, res) {
    Report.findById(req.params._id, function(err, report) {
      if (err) res.send(err.status, err.message);
      else if (!report) res.send(404);
      else {
        var logDetails = {
          userID: req.user._id,
          username: req.user.username,
          action: 'view',
          actionRef: {
            reportContent: report.content,
            reporttID: report._id
          }
        };
        logger.logActivity(logDetails);
        res.send(200, report);
      }
    });
  });

  // Update Report data
  app.put('/api/v1/report/:_id', user.can('edit data'), function(req, res) {
    // Find report to update
    Report.findById(req.params._id, function(err, report) {
      if (err) return res.send(err.status, err.message);
      if (!report) return res.send(404);
      // Update the actual value
      _.each(_.pick(req.body, ['flagged', '_incident', 'read']), function(val, key) {
        report[key] = val;
      });
      // console.log(report);
      // Save report
      report.save(function(err, numberAffected) {
        if (err) res.send(err.status, err.message);
        else if (!numberAffected) res.send(404);
        else {
          var logDetails = {
            userID: req.user._id,
            username: req.user.username,
            action: 'flag/unflag/removeFromIncident',
            actionRef: {
              reportContent: report.content,
              reporttID: report._id
            }
          };
          logger.logActivity(logDetails);
          res.send(200);
        }
      });
    });
  });

  // Delete all reports
  app.delete('/api/v1/report/_all', user.can('edit data'), function(req, res) {
    Report.remove(function(err) {
      if (err) res.send(err.status, err.message);
      else {
        var logDetails = {
          userID: req.user._id,
          username: req.user.username,
          action: 'deleteAllReports',
          actionRef: {}
        };
        logger.logActivity(logDetails);
        res.send(200);
      }
    });
  });

  // Mark selected reports as read
  app.patch('/api/v1/report/_read', user.can('edit data'), function(req, res) {
    if (!req.body.ids || !req.body.ids.length) return res.send(200);

    Report.find({ _id: { $in: req.body.ids } }, function(err, reports) {
      if (err) return res.send(err.status, err.message);
      if (reports.length === 0) return res.send(200);
      var remaining = reports.length;
      reports.forEach(function(report) {
        // Mark each report as read only to catch it in model
        report.toggleRead(req.body.read);
        report.save(function(err) {
          if (err) return res.send(err.status, err.message);
          var logDetails = {
            userID: req.user._id,
            username: req.user.username,
            action: 'markAsRead',
            actionRef: {
              reportContent: report.content,
              reporttID: report._id
            }
          };
          logger.logActivity(logDetails);
          if (--remaining === 0) return res.send(200);
        });
      });
    });
  });

  // Flag selected reports
  app.patch('/api/v1/report/_flag', user.can('edit data'), function(req, res) {
    if (!req.body.ids || !req.body.ids.length) return res.send(200);

    Report.find({ _id: { $in: req.body.ids } }, function(err, reports) {
      if (err) return res.send(err.status, err.message);
      if (reports.length === 0) return res.send(200);
      var remaining = reports.length;
      reports.forEach(function(report) {
        // Mark each report as flagged to catch it in model
        report.toggleFlagged(req.body.flagged);
        report.save(function(err) {
          if (err) return res.send(err.status, err.message);
          var logDetails = {
            userID: req.user._id,
            username: req.user.username,
            action: 'flag',
            actionRef: {
              reportContent: report.content,
              reporttID: report._id
            }
          };
          logger.logActivity(logDetails);
          if (--remaining === 0) return res.send(200);
        });
      });
    });
  });

  // Link selected reports to one incident
  app.patch('/api/v1/report/_link', user.can('edit data'), function(req, res) {
    if (!req.body.ids || !req.body.ids.length) return res.send(200);

    Report.find({ _id: { $in: req.body.ids } }, function(err, reports) {
      if (err) return res.send(err.status, err.message);
      if (reports.length === 0) return res.send(200);
      var remaining = reports.length;
      reports.forEach(function(report) {
        report.read = true;
        report._incident = req.body.incident;
        report.save(function(err) {
          if (err) return res.send(err.status, err.message);
          // TODO Add incident title here somehow, if we can access it from within here
          var logDetails = {
            userID: req.user._id,
            username: req.user.username,
            action: 'addToIncident',
            actionRef: {
              reportContent: report.content,
              reporttID: report._id,
              incidentID: report._incident
            }
          };
          logger.logActivity(logDetails);
          if (--remaining === 0) return res.send(200);
        });
      });
    });
  });

  return app;
};

// Determine the search keywords
function parseQueryData(queryData) {
  if (!queryData) return;

  // Data passed through URL parameters
  return _.pick(queryData, ['keywords', 'status', 'after', 'before', 'media',
                            'sourceId', 'incidentId', 'author']);
}
<|endoftext|>