From 83266a570af528e45f51b78091426f32942028bc Mon Sep 17 00:00:00 2001
From: Julian Vennen <julian@aternos.org>
Date: Tue, 17 Sep 2024 18:43:08 +0200
Subject: [PATCH] Truncate the center of log files

---
 core/config/filter.php        |  2 +-
 core/src/Filter/Pre/Lines.php | 20 +++++++++++++++++-
 web/public/js/mclogs.js       | 40 ++++++++++++++++++++++++++++++++---
 3 files changed, 57 insertions(+), 5 deletions(-)

diff --git a/core/config/filter.php b/core/config/filter.php
index 0ca22bf..b5c2b88 100644
--- a/core/config/filter.php
+++ b/core/config/filter.php
@@ -9,8 +9,8 @@
      */
     'pre' => [
         '\\Filter\\Pre\\Trim',
-        '\\Filter\\Pre\\Length',
         '\\Filter\\Pre\\Lines',
+        '\\Filter\\Pre\\Length',
         '\\Filter\\Pre\\Ip',
         '\\Filter\\Pre\\Username',
         '\\Filter\\Pre\\AccessToken'
diff --git a/core/src/Filter/Pre/Lines.php b/core/src/Filter/Pre/Lines.php
index 01eb5fb..f175fa5 100644
--- a/core/src/Filter/Pre/Lines.php
+++ b/core/src/Filter/Pre/Lines.php
@@ -15,6 +15,24 @@ class Lines implements PreFilterInterface {
     public static function Filter(string $data): string
     {
         $config = \Config::Get('storage');
-        return implode("\n", array_slice(explode("\n", $data), 0, $config["maxLines"]));
+        $limit = $config["maxLines"];
+
+        $lines = explode("\n", $data);
+        $count = count($lines);
+
+        if ($count <= $limit) {
+            return $data;
+        }
+
+        $removed = $count - $limit + 3;
+        $message = "Truncated " . $removed . " line" . ($removed > 1 ? "s" : "");
+
+        array_splice($lines, $limit / 2, $removed, [
+            str_repeat("=", strlen($message)),
+            $message,
+            str_repeat("=", strlen($message)),
+        ]);
+
+        return implode("\n", $lines);
     }
 }
\ No newline at end of file
diff --git a/web/public/js/mclogs.js b/web/public/js/mclogs.js
index 78b3f42..1375c26 100644
--- a/web/public/js/mclogs.js
+++ b/web/public/js/mclogs.js
@@ -46,6 +46,40 @@ document.addEventListener('keydown', event => {
     return true;
 })
 
+/**
+ * Limit the number of characters in a string by removing the end
+ * @param {string} string
+ * @param {number} limit
+ * @returns {string}
+ */
+function limitLength(string, limit = parseInt(pasteArea.dataset.maxLength)) {
+    return string.substring(0, limit);
+}
+
+/**
+ * Limit the number of lines in a string by truncating the middle
+ * @param {string} string
+ * @param {number} limit
+ * @returns {string}
+ */
+function limitLines(string, limit = parseInt(pasteArea.dataset.maxLines)) {
+    let lines = string.split('\n');
+    if (lines.length <= limit) {
+        return string;
+    }
+
+    let removed = lines.length - limit + 3;
+    let message = 'Truncated ' + removed + ' line' + (removed > 1 ? 's' : '');
+
+    lines.splice(limit / 2, removed,
+        "=".repeat(message.length),
+        message,
+        "=".repeat(message.length)
+    );
+
+    return lines.join('\n')
+}
+
 /**
  * Save the log to the API
  * @returns {Promise<void>}
@@ -58,9 +92,9 @@ async function sendLog() {
     pasteSaveButtons.forEach(button => button.classList.add("btn-working"));
 
     try {
-        let log = pasteArea.value
-            .substring(0, parseInt(pasteArea.dataset.maxLength))
-            .split('\n').slice(0, parseInt(pasteArea.dataset.maxLines)).join('\n');
+        let log = pasteArea.value;
+        log = limitLines(log);
+        log = limitLength(log);
 
         const response = await fetch(`${location.protocol}//api.${location.host}/1/log`, {
             method: "POST",