diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI.Web/Views/LogParser/Index.cshtml | 769 |
1 files changed, 769 insertions, 0 deletions
diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index cd47d687..830cfe47 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -12,3 +12,772 @@ <h2>Parsed log</h2> <em>TODO</em> + + + +<!DOCTYPE html> +<html> +<head> + <title>SMAPI log parser</title> + <style type="text/css"> + body { + font-size: 10pt; + background: #fff; + } + + .mod-repeat { + font-size: 8pt; + } + + input[type="button"] { + cursor: pointer; + } + + .template { + display: none; + } + + .popup, #uploader { + position: fixed; + top: 0px; + left: 0px; + right: 0px; + bottom: 0; + background-color: rgba(0, 0, 0, .33); + z-index: 2; + display: none; + padding: 5px; + } + + #uploader:after { + content: attr(data-text); + display: block; + width: 100px; + height: 24px; + line-height: 25px; + border: 1px solid #000; + background: #fff; + position: absolute; + top: 50%; + left: 50%; + margin: -12px -50px 0 0; + font-size: 18px; + font-weight: bold; + text-align: center; + border-radius: 5px; + } + + .popup h1 { + position: absolute; + top: 10%; + left: 50%; + margin-left: -150px; + text-align: center; + width: 300px; + border: 1px solid #008; + border-radius: 5px; + background: #fff; + font-family: sans-serif; + font-size: 40px; + margin-top: -25px; + } + + .frame { + margin: auto; + margin-top: 25px; + position: absolute; + top: 10%; + left: 10%; + right: 10%; + bottom: 10%; + padding-bottom: 30px; + } + + .buttons { + position: absolute; + display: inline-block; + bottom: -10px; + right: 0px; + padding: 5px; + border: 1px solid #008; + border-radius: 5px; + background: #fff; + } + + #cancel, #closeraw { + font-size: 20px; + border-radius: 5px; + border: 1px solid #880000; + background-color: #fcc; + outline: none; + box-shadow: inset 0px 0px 1px 1px rgba(0, 0, 0, .2); + } + + #cancel:hover, #closeraw:hover { + background-color: #fee; + } + + #submit { + font-size: 20px; + border-radius: 5px; + border: 1px solid #008800; + background-color: #cfc; + outline: none; + box-shadow: inset 0px 0px 1px 1px rgba(0, 0, 0, .2); + } + + #submit:hover { + background-color: #efe; + } + + #input, #dataraw { + width: 100%; + height: 100%; + max-width: 100%; + max-height: 100%; + margin: auto; + box-sizing: border-box; + border-radius: 5px; + border: 1px solid #000088; + outline: none; + box-shadow: inset 0px 0px 1px 1px rgba(0, 0, 192, .2); + } + + .color-red { + color: red; + } + + .color-green { + color: green; + } + + #tabs { + top: 0px; + left: 0px; + right: 0px; + border-bottom: 0; + display: block; + position: fixed; + margin: 0; + padding: 0; + background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(210, 235, 249, 1) 100%); + } + + #tabs li { + margin: 5px 1px 0 0; + height: 25px; + float: left; + display: inline-block; + width: 75px; + border: 1px solid #000000; + border-bottom: 0; + border-radius: 5px 5px 0 0; + text-align: center; + font-family: monospace; + font-size: 18px; + cursor: pointer; + font-weight: bold; + color: #000; + text-shadow: 0px 0px 2px #fff; + border-color: #880000; + background-color: #fcc; + box-shadow: inset 0px 0px 1px 1px rgba(0, 0, 0, .2); + } + + #tabs li:hover { + background-color: #fee; + } + + #tabs li:first-child { + margin-left: 5px; + } + + #tabs li.active { + background: #cfc; + border-color: #008800; + } + + #tabs li.active:hover { + background: #efe; + } + + #tabs li.upload { + float: right; + background: #ccf; + border-color: #000088; + margin-right: 5px; + } + + #tabs li.upload:hover { + background: #eef; + } + + #tabs li.notice { + color: #000000; + background: transparent; + border: 0; + padding-top: 1px; + font-size: 13px; + font-weight: normal; + width: auto; + margin-left: 5px; + cursor: default; + box-shadow: none; + font-style: italic; + } + + #output { + border-top: 1px solid #888; + position: fixed; + top: 30px; + left: 0px; + right: 0px; + bottom: 0px; + padding: 10px; + overflow: auto; + font-family: monospace; + } + + #output > * { + display: block; + } + + #output.trace .trace, + #output.debug .debug, + #output.info .info, + #output.alert .alert, + #output.warn .warn, + #output.error .error { + display: none; + } + + #output .trace { + color: #999; + } + + #output .debug { + color: #595959; + } + + #output .info { + color: #000 + } + + #output .alert { + color: #b0b; + } + + #output .warn { + color: #f80 + } + + #output .error { + color: #f00 + } + + #output .always { + font-weight: bold; + border-bottom: 1px dashed #888888; + padding-bottom: 10px; + margin-bottom: 5px; + } + + caption { + text-align: left; + padding-top: 2px; + } + + #log { + border-spacing: 0; + } + + #log tr { + background: #fff; + } + + #log td { + padding: 0 1px; + background: inherit; + border-bottom: 1px dotted #ccc; + border-top: 2px solid #fff; + vertical-align: top; + } + + #log td:not(:last-child) { + max-width: 175px; + padding: 0 4px; + overflow: hidden; + white-space: nowrap; + } + + #log td[data-title]:hover { + font-size: 1px; + overflow: inherit; + position: relative; + } + + #log td:nth-child(3):hover:after { + content: attr(data-title); + display: block; + position: absolute; + border-radius: 4px; + box-shadow: 1px 1px 2px #ccc; + background: inherit; + border: 1px solid #ccc; + background: #efefef; + padding: 1px 1px 0 1px; + font-size: 10pt; + top: -2px; + left: 2px; + color: #000; + } + + #log td:last-child { + width: 100%; + } + + table#gameinfo, + table#modslist { + border: 1px solid #000000; + background: #ffffff; + border-radius: 5px; + border-spacing: 1px; + overflow: hidden; + cursor: default; + box-shadow: 1px 1px 1px 1px #dddddd; + } + + #modslist { + min-width: 400px; + } + + #gameinfo td:first-child { + padding-right: 5px; + } + + #gameinfo tr, + #modslist tr { + background: #eee + } + + #gameinfo tr:nth-child(even), + #modslist tr:nth-child(even) { + background: #fff + } + + #modslist tr { + cursor: pointer; + } + + span.notice { + font-weight: normal; + font-size: 11px; + position: relative; + top: -1px; + display: none; + } + + span.notice.btn { + cursor: pointer; + border: 1px solid #000; + border-radius: 5px; + position: relative; + top: -1px; + padding: 0 2px; + background: #eee; + } + + #output:not(.modfilter) span.notice.txt { + display: inline-block; + } + + #output.modfilter span.notice.btn { + display: inline-block; + } + </style> + <style type="text/css" id="modflags"></style> +</head> +<body> + <ul id="tabs"> + <li>TRACE</li> + <li>DEBUG</li> + <li class="active">INFO</li> + <li class="active">ALERT</li> + <li class="active">WARN</li> + <li class="active">ERROR</li> + <li class="notice">Click tabs to toggle message visibility</li> + <li class="upload" id="upload">UPLOAD</li> + </ul> + <div id="output" class="trace debug"></div> + <script class="template" id="template-body" type="text/html"> + <div class="always"> + <table id="gameinfo"> + <caption>Game info:</caption> + <tr> + <td>SMAPI Version</td> + <td>{0}</td> + </tr> + <tr> + <td>Game Version</td> + <td>{1}</td> + </tr> + <tr> + <td>Platform</td> + <td>{2}</td> + </tr> + <tr> + <td>Mods path</td> + <td>{4}</td> + </tr> + <tr> + <td>Log started</td> + <td>{3}</td> + </tr> + </table> + <br /> + <table id="modslist"> + <caption>Installed Mods: <span id="modlink-r" class="notice btn">Remove all mod filters</span><span class="notice txt"><i>Click any mod to filter</i></span> <span id="modlink-a" class="notice btn txt">Select all</span></caption> + </table> + </div> + <table id="log"></table> + </script> + <script class="template" id="template-css" type="text/html"> + #output.modfilter:not(.mod-{0}) .mod-{0} { display:none; } #output.modfilter.mod-{0} #modslist tr { background:#ffeeee; } #output.modfilter.mod-{0} #modslist tr#modlink-{0} { background:#eeffee; } + </script> + <script class="template" id="template-modentry" type="text/html"> + <tr id="modlink-{0}"> + <td>{1}</td> + <td>{2}</td> + <td>{3}</td> + <td class={4}>{5}</td> + </tr> + </script> + <script class="template" id="template-logentry" type="text/html"> + <tr class="{0} mod mod-{1}"> + <td>{2}</td> + <td>{3}</td> + <td data-title="{4}">{4}</td> + <td>{5}</td> + </tr> + </script> + <script class="template" id="template-lognotice" type="text/html"> + <tr class="{0} mod-repeat mod mod-{1}"> + <td colspan="3"></td> + <td><i>repeats [{2}] times.</i></td> + </tr> + </script> + <div id="popup-upload" class="popup"> + <h1>Upload log file</h1> + <div class="frame"> + <textarea id="input" placeholder="Paste or drag a text file here to upload"></textarea> + <div class="buttons"> + <input type="button" id="submit" value="Parse" /> + <input type="button" id="cancel" value="Cancel" /> + </div> + </div> + </div> + <div id="popup-raw" class="popup"> + <h1>Raw log file</h1> + <div class="frame"> + <textarea id="dataraw"></textarea> + <div class="buttons"> + <input type="button" id="closeraw" value="Close" /> + </div> + </div> + </div> + <div id="uploader"></div> + <script src="https://code.jquery.com/jquery-3.2.1.min.js" + integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" + crossorigin="anonymous"></script> + <script> + $(function() { + function modClicked(evt) { + var id = $(evt.currentTarget).attr("id").split('-')[1], + cls = "mod-" + id;; + if (output.hasClass(cls)) + filters--; + else + filters++; + output.toggleClass(cls); + if (filters == 0) { + output.removeClass("modfilter"); + } else { + output.addClass("modfilter"); + } + } + + function removeFilter() { + for (var c = 0; c < modInfo.length; c++) { + output.removeClass("mod-" + c); + } + filters = 0; + output.removeClass("modfilter"); + } + + function selectAll() { + for (var c = 0; c < modInfo.length; c++) { + output.addClass("mod-" + c); + } + filters = modInfo.length; + output.addClass("modfilter"); + } + + function parseData() { + Stage = "parseData.pre"; + var data = $("#input").val(); + if (!data) { + Stage = "parseData.checkNullData"; + throw new Error("Field `data` is null"); + + } + var dataInfo = regexInfo.exec(data) || regexInfo.exec(data) || regexInfo.exec(data), + dataMods = regexMods.exec(data) || regexMods.exec(data) || regexMods.exec(data), + dataDate = regexDate.exec(data) || regexDate.exec(data) || regexDate.exec(data), + dataPath = regexPath.exec(data) || regexPath.exec(data) || regexPath.exec(data), + match; + console.log("dataInfo:", dataInfo); + console.log("dataMods:", dataMods); + console.log("dataDate:", dataDate); + console.log("dataPath:", dataPath); + Stage = "parseData.doNullCheck"; + if (!dataInfo) + throw new Error("Field `dataInfo` is null"); + if (!dataMods) + throw new Error("Field `dataMods` is null"); + if (!dataPath) + throw new Error("Field `dataPath` is null"); + dataMods = dataMods[0]; + Stage = "parseData.setupDefaults"; + modMap = { + "SMAPI": 0 + }; + modErrors = { + "SMAPI": 0, + "Console.Out": 0 + }; + logInfo = []; + modInfo = [ + ["SMAPI", dataInfo[1], "Zoryn, CLxS & Pathoschild"] + ]; + Stage = "parseData.parseInfo"; + if (dataDate) + var date = new Date(dataDate[1] + "Z"); + versionInfo = [dataInfo[1], dataInfo[2], dataInfo[3], dataDate ? date.getFullYear() + "-" + ("0" + date.getMonth().toString()).substr(-2) + "-" + ("0" + date.getDay().toString()).substr(-2) + " at " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() + " " + date.toLocaleTimeString('en-us', { timeZoneName: 'short' }).split(' ')[2] : "No timestamp found", dataPath[1]]; + Stage = "parseData.parseMods"; + while (match = regexMod.exec(dataMods)) { + modErrors[match[1]] = 0; + modMap[match[1]] = modInfo.length; + modInfo.push([match[1], match[2], match[3] ? ("by " + match[3]) : "Unknown author"]); + } + Stage = "parseData.parseLog"; + while (match = regexLog.exec(data)) { + if (match[2] == "ERROR") + modErrors[match[3]]++; + logInfo.push([match[1], match[2], match[3], match[4]]); + } + Stage = "parseData.post"; + modMap["Console.Out"] = modInfo.length; + modInfo.push(["Console.Out", "", ""]); + } + + function renderData() { + Stage = "renderData.pre"; + output.html(prepare(templateBody, versionInfo)); + var modslist = $("#modslist"), log = $("#log"), modCache = [], y = 0 + for (; y < modInfo.length; y++) { + var errors = modErrors[modInfo[y][0]], + err, cls = "color-red"; + if (errors == 0) { + err = "No Errors"; + cls = "color-green"; + } else if (errors == 1) + err = "1 Error"; + else + err = errors + " Errors"; + modCache.push(prepare(templateModentry, [y, modInfo[y][0], modInfo[y][1], modInfo[y][2], cls, err])); + } + modslist.append(modCache.join("")); + for (var z = 0; z < modInfo.length; z++) + $("#modlink-" + z).on("click", modClicked); + var flagCache = []; + for (var c = 0; c < modInfo.length; c++) + flagCache.push(prepare(templateCss, [c])); + flags.html(flagCache.join("")); + var logCache = [], dupeCount = 0, dupeMemory = "|||"; + for (var x = 0; x < logInfo.length; x++) { + var dm = logInfo[x][1] + "|" + logInfo[x][2] + "|" + logInfo[x][3]; + if (dupeMemory != dm) { + if (dupeCount > 0) + logCache.push(prepare(templateLognotice, [logInfo[x - 1][1].toLowerCase(), modMap[logInfo[x - 1][2]], dupeCount])); + dupeCount = 0; + dupeMemory = dm; + logCache.push(prepare(templateLogentry, [logInfo[x][1].toLowerCase(), modMap[logInfo[x][2]], logInfo[x][0], logInfo[x][1], logInfo[x][2], logInfo[x][3].split(" ").join("  ").replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<br />")])); + } + else + dupeCount++; + } + log.append(logCache.join("")); + $("#modlink-r").on("click", removeFilter); + $("#modlink-a").on("click", selectAll); + } + + function prepare(str, arr) { + var regex = /\{(\d)\}/g, + match; + while (match = regex.exec(str)) + str = str.replace(match[0], arr[match[1]]); + return str; + } + function loadData() { + try { + Stage = "loadData.Pre"; + var start = performance.now(); + parseData(); + renderData(); + var end = performance.now(); + $(".always").prepend('<div>Log processed in: ' + (Math.round((end - start) * 100) / 100) + ' ms (<a id="viewraw" href="#">View raw</a>)</div><br />'); + $("#viewraw").on("click", function() { + $("#dataraw").val($("#input").val()); + $("#popup-raw").fadeIn(); + }) + Stage = "loadData.Post"; + } + catch (err) { + $("#output").html('<div id="log" class="color-red"><h1>Parsing failed!</h1>Parsing of the log failed, details follow.<br /> <p>Stage: ' + Stage + '</p>' + err + '<hr /><div id="rawlog"></div></div>'); + $("#rawlog").text($("#input").val()); + } + } + function getData() { + $("#uploader").attr("data-text", "Loading..."); + $("#uploader").fadeIn(); + $.get("https://cors-anywhere.herokuapp.com/pastebin.com/raw/" + location.search.substring(1) + "/?nocache=" + Math.random(), function(data, state, xhr) { + if (data.substring(0, 9) == "<!DOCTYPE") { + $("#output").html('<div id="log" class="color-red"><h1>Captcha required!</h1>The pastebin server is asking for a captcha, but their API doesnt let us show it to you directly.<br />Instead, to finish saving the log, you need to <a href="https://pastebin.com/' + location.search.substring(1) + '" target="_blank">solve the captcha in a new tab</a>, once you have done so, reload this page.</div>'); + } + else { + $("#input").val(LZString.decompressFromUTF16(data) || data); + loadData(); + } + $("#uploader").fadeOut(); + }); + } + var Stage, + flags = $("#modflags"), + output = $("#output"), + filters = 0, + memory = "", + versionInfo, + modInfo, + modMap, + modErrors, + logInfo, + templateBody = $("#template-body").text(), + templateModentry = $("#template-modentry").text(), + templateCss = $("#template-css").text(), + templateLogentry = $("#template-logentry").text(), + templateLognotice = $("#template-lognotice").text(), + regexInfo = /\[[\d\:]+ INFO SMAPI] SMAPI (.*?) with Stardew Valley (.*?) on (.*?)\n/g, + regexMods = /\[[^\]]+\] Loaded \d+ mods:(?:\n\[[^\]]+\] .+)+/g, + regexLog = /\[([\d\:]+) (TRACE|DEBUG|INFO|WARN|ALERT|ERROR) ? ([^\]]+)\] ?((?:\n|.)*?)(?=(?:\[\d\d:|$))/g, + regexMod = /\[(?:.*?)\] *(.*?) (\d+\.?(?:.*?))(?: by (.*?))? \|(?:.*?)$/gm, + regexDate = /\[\d{2}:\d{2}:\d{2} TRACE SMAPI\] Log started at (.*?) UTC/g, + regexPath = /\[\d{2}:\d{2}:\d{2} DEBUG SMAPI\] Mods go here: (.*?)(?:\n|$)/g + ; + $("#tabs li:not(.notice)").on("click", function(evt) { + var t = $(evt.currentTarget) + t.toggleClass("active"); + $("#output").toggleClass(t.text().toLowerCase()); + }) + $("#upload").on("click", function() { + memory = $("#input").val() || ""; + $("#input").val(""); + $("#popup-upload").fadeIn(); + }) + var proxies = [ + "https://cors-anywhere.herokuapp.com/", + "https://galvanize-cors-proxy.herokuapp.com/" + ]; + $('#popup-upload').on({ + 'dragover dragenter': function(e) { + e.preventDefault(); + e.stopPropagation(); + }, + 'drop': function(e) { + $("#uploader").attr("data-text", "Reading...") + $("#uploader").show(); + var dataTransfer = e.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.files.length) { + e.preventDefault(); + e.stopPropagation(); + var file = dataTransfer.files[0]; + var reader = new FileReader(); + reader.onload = $.proxy(function(file, $input, event) { + $input.val(event.target.result); + $("#uploader").fadeOut(); + $("#submit").click(); + }, this, file, $("#input")); + reader.readAsText(file); + } + } + }); + function logSize(id, str) { + console.log(id + ":", str.length * 2, "bytes", Math.round(str.length / 5.12) / 100, "kb"); + } + $("#submit").on("click", function() { + $("#popup-upload").fadeOut(); + if ($("#input").val()) { + memory = ""; + var raw = $("#input").val(); + var paste = LZString.compressToUTF16(raw); + logSize("Raw", raw); + logSize("Compressed", paste); + if (paste.length * 2 > 524288) { + $("#output").html('<div id="log" class="color-red"><h1>Unable to save!</h1>This log cannot be saved due to its size.<hr />' + $("#input").val() + '</div>'); + return; + } + console.log("paste:", paste); + var packet = { + api_dev_key: "b8219d942109d1e60ebb14fbb45f06f9", + api_option: "paste", + api_paste_private: 1, + api_paste_code: paste, + api_paste_expire_date: "1W" + }; + $("#uploader").attr("data-text", "Saving..."); + $("#uploader").fadeIn(); + var uri = proxies[Math.floor(Math.random() * proxies.length)] + "pastebin.com/api/api_post.php"; + console.log(packet, uri); + $.post(uri, packet, function(data, state, xhr) { + $("#uploader").fadeOut(); + console.log("Result: ", data); + if (data.substring(0, 15) == "Bad API request") + $("#output").html('<div id="log" class="color-red"><h1>Parsing failed!</h1>Parsing of the log failed, details follow.<br /> <p>Stage: Upload</p>Error: ' + data + '<hr />' + $("#input").val() + '</div>'); + else if (data) + location.href = "?" + data.split('/').pop(); + else + $("#output").html('<div id="log" class="color-red"><h1>Parsing failed!</h1>Parsing of the log failed, details follow.<br /> <p>Stage: Upload</p>Error: Received null response<hr />' + $("#input").val() + '</div>'); + }) + } else { + alert("Unable to parse log, the input is empty!"); + $("#uploader").fadeOut(); + } + }) + $("#cancel").on("click", function() { + $("#popup-upload").fadeOut(400, function() { + $("#input").val(memory); + memory = ""; + }); + }); + $("#closeraw").on("click", function() { + $("#popup-raw").fadeOut(400); + }) + if (location.search) { + getData(); + } + else + $("#popup-upload").fadeIn(); + }) + </script> + <script type="text/javascript" src="lz-string.min.js"></script> +</body> +</html> |