Compare commits

..

17 Commits

Author SHA1 Message Date
240635d30c Merge branch 'entry-content-visibility'
Some checks failed
Publish Docker Image / build-and-push (push) Failing after 3s
2026-05-06 19:39:01 +00:00
47303d6812 perf: skip render of off-screen log cells via content-visibility
With logs up to ~25 000 entries, eagerly painting every grid cell
caused multi-second freezes on page load. Add content-visibility:
auto to the line-number and content cells so the browser defers
their layout / paint until they scroll into view.

The rule lives on the cells (not on .entry itself) because .entry
uses display: contents and produces no box of its own.
contain-intrinsic-size: auto 1.5em lets the browser remember
measured heights after first paint and uses ~one line tall as the
initial placeholder for never-seen cells.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:39:01 +00:00
aeef6bc0cd Merge branch 'auto-fold-on-load'
Some checks failed
Publish Docker Image / build-and-push (push) Failing after 4s
2026-05-06 19:31:31 +00:00
415324f340 feat: apply smart fold automatically on page load
Logs with errors now open already smart-folded — every entry within
±25 of an error stays visible, gaps collapse into draggable bars.
Folds can only be expanded individually via per-bar click or
drag; the previous "toggle to unfold everything" path is gone. The
header error-count chip becomes informational only (cursor and
pointer-events stripped so it no longer reads as interactive).

Removed the dead toggleErrors / uncollapseAllErrors / "toggled"
state plumbing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:31:31 +00:00
21aaeac884 Merge branch 'errors-fold-drag'
Some checks failed
Publish Docker Image / build-and-push (push) Failing after 4s
Smart fold-around-errors with drag-to-reveal handle. Default ±25
context per error, draggable bars between gaps, click-to-reveal-25
fallback.
2026-05-06 19:27:59 +00:00
4d0d98c604 feat: smart fold around errors with drag-to-reveal handle
Replaces the all-or-nothing "Errors only" collapse with a contextual
fold: ±25 entries around every error stay visible, and runs of
non-error entries with no nearby error collapse into a fold bar.

Each fold bar is draggable. Vertical pointer drag on the bar reveals
or re-hides lines from the top of the hidden range, ~6 px per line.
A click without drag reveals the next 25 lines. When the run is
fully revealed, the bar removes itself. Buttons / scroll behaviour
are unchanged; the existing "X errors" toggle in the header is the
entry point and still un-collapses everything on second click.

Visual: foldable bars get a faint horizontal hatch (suggesting
compressed content), an ns-resize cursor, and a hover/dragging
state that intensifies the hatch toward --accent. The grip-lines
icon flanks the line-count to call out the affordance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:27:59 +00:00
cc1e853f89 Merge branch 'paste-buffer-large'
Some checks failed
Publish Docker Image / build-and-push (push) Failing after 4s
Buffers large pastes outside the textarea to prevent the
multi-second UI freeze that happens when megabytes of text get
committed to a <textarea> in one shot. Threshold 256 KB; first 50
lines + a banner render into the textarea, full content uploads
on Save.
2026-05-06 19:18:31 +00:00
0282ab594e fix: buffer large pastes outside the textarea to prevent UI freeze
Pasting (or drag-loading, or file-uploading) several MB of text into
the <textarea> would lock up the browser for seconds while it laid
out millions of characters. The bottleneck is the textarea itself,
not the filter pipeline (filters only run on Save).

Hold above-threshold (256 KB) content in a JS-side `bufferedContent`
variable and render only the first 50 lines + a banner into the
textarea. The Save path uses `bufferedContent` when set, so the
upload sees the full content. The textarea becomes read-only while
the buffer is active; user-typed input invalidates the buffer
automatically (the textarea is the source of truth in normal mode).

A new `loadContent(text)` helper is the single entry point: it
chooses textarea vs. buffer based on length. All call sites
(clipboard read, paste-event for text, file load, text drop) route
through it. Threshold is 256 KB (above which textarea rendering
visibly stutters); preview is 50 lines (enough to recognise the log
without choking the renderer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:18:31 +00:00
8437d62394 Merge branch 'codex-v030-wire'
Some checks failed
Publish Docker Image / build-and-push (push) Failing after 5s
Bumps codex to v0.3.0 and aligns iblogs's save-time PII filter
chain with codex's ProjectZomboidRedactor. Removes the redundant
in-tree IPv4Filter / IPv6Filter chain entries (codex now handles
IPs end-to-end including port suffixes). Adds PZ-specific Steam
ID / player-name / coordinate scrubbing the prior chain never had.
2026-05-06 19:12:50 +00:00
4fced60a83 feat: align save-time redaction with codex v0.3.0 ProjectZomboidRedactor
Bumps the codex constraint from ^0.2.0 to ^0.3.0 to pull the PZ-B42
parser fix and the IP-redaction passes added in the codex v0.3.0
release. Wires codex's ProjectZomboidRedactor into the save-time
Filter chain via a thin ProjectZomboidRedactorFilter wrapper, and
removes the now-redundant IPv4Filter / IPv6Filter chain entries:

- Codex's IPv4 / IPv6 redaction is generic-applicable (not PZ-only)
  and superior to the prior in-tree filters because it consumes the
  port suffix together with the address; previously only the IP was
  scrubbed, leaving e.g. ":27015" visible.
- Codex additionally redacts PZ-specific PII the prior filters never
  touched (Steam IDs, player names, world coordinates).
- The IPv4Filter and IPv6Filter source files are retained on disk
  for easy restore from history if a future paste type proves
  unsuitable for codex-driven IP scrubbing.

UsernameFilter (OS-path username scrubbing — different concern from
PZ player names) and AccessTokenFilter remain untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:12:50 +00:00
33fcd0d81f chore(dev): change local dev port to 4217
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 10:59:43 +00:00
f72e2d0936 chore(docker): install git and ca-certificates
Required so Composer can resolve VCS-based dependencies (e.g. forked codex packages) at build time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 10:59:37 +00:00
3e03f81e74 docs: point README codex link at indifferentketchup/codex
The previous bullet credited aternosorg/codex-minecraft as the
analysis engine; that dependency was dropped in the codex-swap commit.
Updates the link to point at the in-tree indifferentketchup/codex repo
on Gitea while preserving an attribution back to the upstream codex
that iblogs is built on. The footer credit (Aternos / mclogs) remains
the load-bearing attribution per the manual-fork rules.
2026-05-01 22:19:55 +00:00
94326d5a19 refactor: swap Aternos codex deps for IndifferentKetchup\Codex
Drops aternos/codex (and its codex-minecraft / codex-hytale satellites)
plus aternos/sherlock from composer.json. Adds indifferentketchup/codex
pinned to ^0.2.0, sourced via a Composer vcs repository pointing at the
Gitea-hosted ik-codex repo. composer.lock regenerated against the new
dep tree.

Re-points the seven Aternos\Codex\* import sites in src/ to their
IndifferentKetchup\Codex\* equivalents:
  - src/Log.php (5 imports)
  - src/Api/Response/CodexLogResponse.php (1 import)
  - src/Printer/Printer.php (5 imports)
  - src/Detective.php (rewritten — see below)

Stubs three sites that depend on Minecraft-specific code with no
analogue in IndifferentKetchup\Codex:

  - src/Detective.php no longer extends the Aternos Detective with
    Minecraft + Hytale satellites. Instead it extends our codex's
    Detective and registers ProjectZomboidDetective, which itself
    pre-registers all 11 PZ log subclasses.
  - src/Data/Deobfuscator.php is reduced to a no-op shell. mclogs used
    aternos/sherlock to fetch Mojang/Yarn obfuscation maps and
    deobfuscate Vanilla / Fabric stack traces. Project Zomboid uses no
    such mapping scheme; the deobfuscator returns null until iblogs
    supports Minecraft logs again. Class signature preserved so callers
    in src/Log.php and elsewhere don't break.
  - src/Printer/FormatModification.php no longer extends the Minecraft
    section-sign-code translator. It extends IndifferentKetchup\Codex's
    Modification base class with a pass-through modify() implementation.
    The format-* CSS color classes are retained for any future game's
    own format-code scheme to reuse.

Updates composer.json metadata: description rewritten to drop the
"Minecraft" framing, authors entry replaced with indifferentketchup /
samkintop@gmail.com.

Verification:
  composer update --ignore-platform-req=ext-frankenphp \
                  --ignore-platform-req=ext-mongodb
  -> resolves cleanly: 1 install (indifferentketchup/codex 0.2.0),
     4 removals (the aternos/* set).
  php -l on every touched file -> no syntax errors.
  Autoload smoke test -> Detective registers 11 PZ log classes;
     Printer instantiates; FormatModification stub returns input
     unchanged.
2026-05-01 22:19:03 +00:00
6724320c9a chore: replace mclogs/Minecraft branding strings with iblogs equivalents
example.config.json: legal contact, imprint, privacy URLs swapped to
indifferentketchup.com placeholders; abuse address changed to
samkintop@gmail.com (matches the codex package's author entry);
frontend.name placeholder changed from "mclo.gs" to "iblogs.example".

UI templates: tagline replaced with "Built for game-server logs"
(header.php), meta description rewritten to drop Minecraft & Hytale
framing (start.php), api-docs.php's example "Minecraft version" labels
updated to "Engine version" with a 42.16.3 sample value matching the
codex PZ EngineVersionInformation output shape.

CSS comment "Minecraft Format Colors" tagged as legacy mclogs syntax
palette (the underlying color classes survive untouched — they may
still be useful for highlighting log content).

Footer attribution credit linking back to upstream mclogs and Aternos
is intentionally preserved per the manual-fork rules.

Frontend palette (#5cb85c green accent, etc.) and short-domain choice
remain placeholder values; revisit when iblogs branding is finalised.
2026-05-01 22:13:49 +00:00
8f0b067e38 chore: rename mclogs filenames, asset paths, and runtime identifiers
Two file renames (docker/mclogs.ini -> docker/iblogs.ini,
web/public/css/mclogs.css -> web/public/css/iblogs.css) plus the
internal references that pointed at them (Dockerfile COPY directive,
the linux user name in the container).

Also catches the runtime identifier renames: env-var prefix MCLOGS_*
-> IBLOGS_* (compose files), browser cookie name MCLOGS_SETTINGS ->
IBLOGS_SETTINGS (web/public/js/log.js), production image tag
ghcr.io/aternosorg/mclogs:2 -> ghcr.io/indifferentketchup/iblogs:2,
and the README walk-through with the new branding.

example.config.json branding strings (legal contact, mclo.gs frontend
name) and visible UI text (taglines, meta descriptions) are deferred
to a separate branding commit.
2026-05-01 22:11:46 +00:00
4aeebf3732 refactor: rename Aternos\Mclogs to IndifferentKetchup\Iblogs
Bulk substitution across all PHP files in src/, build.php, worker.php,
and web/frontend/. Updates composer.json's package name and PSR-4
autoload root accordingly. Casing matches the existing
IndifferentKetchup\Codex package's namespace convention (capital K).

Strictly a namespace rename. Aternos\Codex\* imports remain in place;
those get re-pointed in a follow-up commit when the codex Composer
dependency itself is swapped. Filename renames (docker/mclogs.ini,
web/public/css/mclogs.css), README walk-through, env-var prefix changes,
and visible-text branding land in subsequent commits.
2026-05-01 22:11:13 +00:00
93 changed files with 717 additions and 690 deletions

View File

@@ -2,8 +2,9 @@ FROM dunglas/frankenphp:1-php8.5
# System Setup
RUN install-php-extensions mongodb zip
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/*
ARG USER=mclogs
ARG USER=iblogs
RUN useradd ${USER} && \
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp
@@ -20,7 +21,7 @@ RUN --mount=type=cache,target=/tmp/cache/composer \
# Application Setup
COPY docker/Caddyfile /etc/frankenphp/Caddyfile
COPY docker/mclogs.ini /usr/local/etc/php/conf.d/mclogs.ini
COPY docker/iblogs.ini /usr/local/etc/php/conf.d/iblogs.ini
COPY . .

View File

@@ -13,7 +13,7 @@
* Syntax highlighting
* Line numbers
* Direct links to specific lines
* Analysis and parsing using [codex](https://github.com/aternosorg/codex-minecraft)
* Analysis and parsing using [indifferentketchup/codex](https://git.indifferentketchup.com/indifferentketchup/ik-codex), forked from [aternos/codex-minecraft](https://github.com/aternosorg/codex-minecraft)
### For developers
* Upload your logs using the API
@@ -22,34 +22,34 @@
* Open source and self-hostable
## Self-hosting
You can self-host mclogs using Docker. A [docker image](https://github.com/aternosorg/mclogs/pkgs/container/mclogs) is available in the GitHub Container Registry: `ghcr.io/aternosorg/mclogs`.
A MongoDB instance is also required to run mclogs.
You can self-host iblogs using Docker. A [docker image](https://github.com/indifferentketchup/iblogs/pkgs/container/iblogs) is available in the GitHub Container Registry: `ghcr.io/indifferentketchup/iblogs`.
A MongoDB instance is also required to run iblogs.
An example docker compose files for self-hosting can be found here: [docker/compose.production.yaml](docker/compose.production.yaml).
### Config
You can configure mclogs by creating a `config.json` file in the root directory, see [example.config.json](example.config.json) or by setting
You can configure iblogs by creating a `config.json` file in the root directory, see [example.config.json](example.config.json) or by setting
environment variables. Environment variables override settings in the config file.
Here is a list of all available config options:
| Variable / JSON Path | Default | Description |
|:--------------------------------------------------------------------|:--------------------|:--------------------------------------------|
| `MCLOGS_STORAGE_TTL` <br> `storage.ttl` | `7776000` (90d) | Time until logs are deleted after last view |
| `MCLOGS_STORAGE_LIMIT_BYTES` <br> `storage.limit.bytes` | `10485760` (10 MiB) | Maximum size of a log in bytes |
| `MCLOGS_STORAGE_LIMIT_LINES` <br> `storage.limit.lines` | `25000` | Maximum number of lines in a log |
| `MCLOGS_MONGODB_URL` <br> `mongodb.url` | `"mongodb://mongo"` | MongoDB connection URL |
| `MCLOGS_MONGODB_DATABASE` <br> `mongodb.database` | `"mclogs"` | Name of the MongoDB database |
| `MCLOGS_ID_LENGTH` <br> `id.length` | `7` | The default length for new IDs |
| `MCLOGS_LEGAL_ABUSE` <br> `legal.abuse` | `null` | Public email address to report abuse |
| `MCLOGS_LEGAL_IMPRINT` <br> `legal.imprint` | `null` | The imprint URL |
| `MCLOGS_LEGAL_PRIVACY` <br> `legal.privacy` | `null` | The privacy policy URL |
| `MCLOGS_FRONTEND_NAME` <br> `frontend.name` | `null` | Instance name (defaults to domain) |
| `MCLOGS_FRONTEND_COLOR_ACCENT` <br> `frontend.color.accent` | `#5cb85c` | The accent/primary color |
| `MCLOGS_FRONTEND_COLOR_BACKGROUND` <br> `frontend.color.background` | `#1a1a1a` | The background color |
| `MCLOGS_FRONTEND_COLOR_TEXT` <br> `frontend.color.text` | `#e8e8e8` | The text color |
| `MCLOGS_FRONTEND_COLOR_ERROR` <br> `frontend.color.error` | `#f62451` | The error color |
| `MCLOGS_WORKER_REQUESTS` <br> `worker.requests` | `500` | Max requests per single worker |
| `IBLOGS_STORAGE_TTL` <br> `storage.ttl` | `7776000` (90d) | Time until logs are deleted after last view |
| `IBLOGS_STORAGE_LIMIT_BYTES` <br> `storage.limit.bytes` | `10485760` (10 MiB) | Maximum size of a log in bytes |
| `IBLOGS_STORAGE_LIMIT_LINES` <br> `storage.limit.lines` | `25000` | Maximum number of lines in a log |
| `IBLOGS_MONGODB_URL` <br> `mongodb.url` | `"mongodb://mongo"` | MongoDB connection URL |
| `IBLOGS_MONGODB_DATABASE` <br> `mongodb.database` | `"iblogs"` | Name of the MongoDB database |
| `IBLOGS_ID_LENGTH` <br> `id.length` | `7` | The default length for new IDs |
| `IBLOGS_LEGAL_ABUSE` <br> `legal.abuse` | `null` | Public email address to report abuse |
| `IBLOGS_LEGAL_IMPRINT` <br> `legal.imprint` | `null` | The imprint URL |
| `IBLOGS_LEGAL_PRIVACY` <br> `legal.privacy` | `null` | The privacy policy URL |
| `IBLOGS_FRONTEND_NAME` <br> `frontend.name` | `null` | Instance name (defaults to domain) |
| `IBLOGS_FRONTEND_COLOR_ACCENT` <br> `frontend.color.accent` | `#5cb85c` | The accent/primary color |
| `IBLOGS_FRONTEND_COLOR_BACKGROUND` <br> `frontend.color.background` | `#1a1a1a` | The background color |
| `IBLOGS_FRONTEND_COLOR_TEXT` <br> `frontend.color.text` | `#e8e8e8` | The text color |
| `IBLOGS_FRONTEND_COLOR_ERROR` <br> `frontend.color.error` | `#f62451` | The error color |
| `IBLOGS_WORKER_REQUESTS` <br> `worker.requests` | `500` | Max requests per single worker |
There are a few more environment variables that can be set to modify the FrankenPHP/Caddy setup directly:
@@ -69,10 +69,10 @@ There are a few more environment variables that can be set to modify the Franken
### Installation
```bash
# clone repository
git clone git@github.com:aternosorg/mclogs.git
git clone git@github.com:indifferentketchup/iblogs.git
# install composer dependencies
cd mclogs
cd iblogs
composer install
# start development environment

View File

@@ -2,4 +2,4 @@
require_once __DIR__ . '/vendor/autoload.php';
\Aternos\Mclogs\Frontend\Assets\AssetLoader::getInstance()->writeCache();
\IndifferentKetchup\Iblogs\Frontend\Assets\AssetLoader::getInstance()->writeCache();

View File

@@ -1,10 +1,16 @@
{
"name": "aternos/mclogs",
"description": "Paste, share and analyse Minecraft logs",
"name": "indifferentketchup/iblogs",
"description": "Paste, share, and analyse game-server logs.",
"authors": [
{
"name": "Matthias Neid",
"email": "matthias@aternos.org"
"name": "indifferentketchup",
"email": "samkintop@gmail.com"
}
],
"repositories": [
{
"type": "vcs",
"url": "https://git.indifferentketchup.com/indifferentketchup/ik-codex"
}
],
"require": {
@@ -15,14 +21,12 @@
"ext-mongodb": "*",
"ext-uri": "*",
"ext-zlib": "*",
"aternos/codex-hytale": "^2.0",
"aternos/codex-minecraft": "^5.0.1",
"aternos/sherlock": "^1.0.2",
"indifferentketchup/codex": "^0.3.0",
"mongodb/mongodb": "2.1.2"
},
"autoload": {
"psr-4": {
"Aternos\\Mclogs\\": "src/"
"IndifferentKetchup\\Iblogs\\": "src/"
}
}
}

184
composer.lock generated
View File

@@ -4,21 +4,15 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a570f76a4698742115ca4d1d4113a836",
"content-hash": "c970170e823f1c31130ee1eec742a090",
"packages": [
{
"name": "aternos/codex",
"version": "v4.1.0",
"name": "indifferentketchup/codex",
"version": "v0.3.0",
"source": {
"type": "git",
"url": "https://github.com/aternosorg/codex.git",
"reference": "2d0b1930464d9c5129e90c5e69314b1da22c7c4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aternosorg/codex/zipball/2d0b1930464d9c5129e90c5e69314b1da22c7c4a",
"reference": "2d0b1930464d9c5129e90c5e69314b1da22c7c4a",
"shasum": ""
"url": "https://git.indifferentketchup.com/indifferentketchup/ik-codex",
"reference": "656142dbf8979da7d5f06908e5dd53afa1b5e56d"
},
"require": {
"php": ">=8.4"
@@ -29,161 +23,31 @@
"type": "library",
"autoload": {
"psr-4": {
"Aternos\\Codex\\": "src/"
"IndifferentKetchup\\Codex\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matthias Neid",
"email": "matthias@aternos.org"
}
],
"description": "PHP library to read, parse, print and analyse log files.",
"support": {
"issues": "https://github.com/aternosorg/codex/issues",
"source": "https://github.com/aternosorg/codex/tree/v4.1.0"
},
"time": "2026-01-21T14:12:19+00:00"
},
{
"name": "aternos/codex-hytale",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/aternosorg/codex-hytale.git",
"reference": "9b48e2d0fa4b82a3f10c8833a766b7e76e233271"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aternosorg/codex-hytale/zipball/9b48e2d0fa4b82a3f10c8833a766b7e76e233271",
"reference": "9b48e2d0fa4b82a3f10c8833a766b7e76e233271",
"shasum": ""
},
"require": {
"aternos/codex": "^v4.1.0",
"ext-json": "*",
"php": ">=8.4.0"
},
"require-dev": {
"phpunit/phpunit": "^12.4"
},
"type": "library",
"autoload": {
"autoload-dev": {
"psr-4": {
"Aternos\\Codex\\Hytale\\": "src/"
"IndifferentKetchup\\Codex\\Test\\Src\\": "test/src/",
"IndifferentKetchup\\Codex\\Test\\Tests\\": "test/tests/"
}
},
"notification-url": "https://packagist.org/downloads/",
"scripts": {
"test": [
"phpunit test/tests"
]
},
"license": [
"MIT"
],
"authors": [
{
"name": "Matthias",
"email": "matthias@aternos.org"
"name": "indifferentketchup",
"email": "samkintop@gmail.com"
}
],
"description": "PHP library to read, parse, print and analyse Hytale log files.",
"support": {
"issues": "https://github.com/aternosorg/codex-hytale/issues",
"source": "https://github.com/aternosorg/codex-hytale/tree/v2.0.0"
},
"time": "2026-01-23T12:25:09+00:00"
},
{
"name": "aternos/codex-minecraft",
"version": "v5.1.0",
"source": {
"type": "git",
"url": "https://github.com/aternosorg/codex-minecraft.git",
"reference": "f921d0277449af04a72c3dbb784e37b5fa4934b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aternosorg/codex-minecraft/zipball/f921d0277449af04a72c3dbb784e37b5fa4934b1",
"reference": "f921d0277449af04a72c3dbb784e37b5fa4934b1",
"shasum": ""
},
"require": {
"aternos/codex": "^v4.1.0",
"ext-json": "*",
"php": ">=8.4.0"
},
"require-dev": {
"phpunit/phpunit": "^12"
},
"type": "library",
"autoload": {
"psr-4": {
"Aternos\\Codex\\Minecraft\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matthias",
"email": "matthias@aternos.org"
}
],
"description": "PHP library to read, parse, print and analyse Minecraft log files.",
"support": {
"issues": "https://github.com/aternosorg/codex-minecraft/issues",
"source": "https://github.com/aternosorg/codex-minecraft/tree/v5.1.0"
},
"time": "2026-03-30T18:21:47+00:00"
},
{
"name": "aternos/sherlock",
"version": "v1.1.3",
"source": {
"type": "git",
"url": "https://github.com/aternosorg/sherlock.git",
"reference": "2bfb6427790b24df860f20905b76f09978a7df3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aternosorg/sherlock/zipball/2bfb6427790b24df860f20905b76f09978a7df3a",
"reference": "2bfb6427790b24df860f20905b76f09978a7df3a",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-simplexml": "*",
"ext-zlib": "*",
"php": ">=8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Aternos\\Sherlock\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Julian Vennen",
"email": "julian@aternos.org"
}
],
"description": "PHP library to apply minecraft mappings to log files",
"support": {
"issues": "https://github.com/aternosorg/sherlock/issues",
"source": "https://github.com/aternosorg/sherlock/tree/v1.1.3"
},
"time": "2026-02-09T10:37:21+00:00"
"description": "Generic PHP log parsing and analysis framework.",
"time": "2026-05-06T19:04:37+00:00"
},
{
"name": "mongodb/mongodb",
@@ -314,16 +178,16 @@
},
{
"name": "symfony/polyfill-php85",
"version": "v1.33.0",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php85.git",
"reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91"
"reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
"reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee",
"reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee",
"shasum": ""
},
"require": {
@@ -370,7 +234,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0"
"source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0"
},
"funding": [
{
@@ -390,7 +254,7 @@
"type": "tidelift"
}
],
"time": "2025-06-23T16:12:55+00:00"
"time": "2026-04-26T13:10:57+00:00"
}
],
"packages-dev": [],

View File

@@ -1,14 +1,14 @@
name: "mclogs"
name: "iblogs"
services:
web:
build:
context: ..
dockerfile: ./Dockerfile
environment:
- MCLOGS_WORKER_REQUESTS=1
- IBLOGS_WORKER_REQUESTS=1
- FRANKENPHP_WORKERS=4
ports:
- "80:80"
- "4217:80"
volumes:
- ../:/app
- ./dev.ini:/usr/local/etc/php/conf.d/dev.ini

View File

@@ -1,6 +1,6 @@
services:
web:
image: ghcr.io/aternosorg/mclogs:2
image: ghcr.io/indifferentketchup/iblogs:2
restart: always
ports:
# Expose HTTP (80) and HTTPS (443)
@@ -9,16 +9,16 @@ services:
- "443:443"
- "443:443/udp"
environment:
# Set this to your domain (e.g., mclogs.example.com) to enable Auto-SSL.
# Set this to your domain (e.g., iblogs.example.com) to enable Auto-SSL.
# If running behind a proxy (Cloudflare/Nginx), set to ":80" to disable Auto-SSL.
SERVER_NAME: :80
MCLOGS_MONGODB_URL: mongodb://mongo:27017
MCLOGS_MONGODB_DATABASE: mclogs
IBLOGS_MONGODB_URL: mongodb://mongo:27017
IBLOGS_MONGODB_DATABASE: iblogs
# Optional MCLOGS configuration
# Optional IBLOGS configuration
# See README.md for full list of available options
# MCLOGS_FRONTEND_NAME: "mclogs"
# IBLOGS_FRONTEND_NAME: "iblogs"
volumes:
# For caddy cache (SSL certificates)

View File

@@ -8,18 +8,18 @@
},
"mongodb": {
"url": "mongodb://127.0.0.1:27017",
"database": "mclogs"
"database": "iblogs"
},
"id": {
"length": 7
},
"legal": {
"abuse": "abuse@aternos.org",
"imprint": "https://aternos.gmbh/imprint/",
"privacy": "https://aternos.gmbh/en/mclogs/privacy"
"abuse": "samkintop@gmail.com",
"imprint": "https://indifferentketchup.com/imprint",
"privacy": "https://indifferentketchup.com/iblogs/privacy"
},
"frontend": {
"name": "mclo.gs",
"name": "iblogs.example",
"assets": {
"integrity": true
},

View File

@@ -1,12 +1,12 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\LogContentParser;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\CodexLogResponse;
use Aternos\Mclogs\Log;
use IndifferentKetchup\Iblogs\Api\LogContentParser;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\CodexLogResponse;
use IndifferentKetchup\Iblogs\Log;
class AnalyseLogAction extends ApiAction
{

View File

@@ -1,10 +1,10 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\ContentParser;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Router\Action;
use IndifferentKetchup\Iblogs\Api\ContentParser;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Router\Action;
abstract class ApiAction extends Action
{

View File

@@ -1,14 +1,14 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\ContentParser;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\MultiResponse;
use Aternos\Mclogs\Id;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Storage\MongoDBClient;
use IndifferentKetchup\Iblogs\Api\ContentParser;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\MultiResponse;
use IndifferentKetchup\Iblogs\Id;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Storage\MongoDBClient;
class BulkDeleteLogsAction extends ApiAction
{

View File

@@ -1,13 +1,13 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\LogContentParser;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\LogResponse;
use Aternos\Mclogs\Data\MetadataEntry;
use Aternos\Mclogs\Log;
use IndifferentKetchup\Iblogs\Api\LogContentParser;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\LogResponse;
use IndifferentKetchup\Iblogs\Data\MetadataEntry;
use IndifferentKetchup\Iblogs\Log;
class CreateLogAction extends ApiAction
{

View File

@@ -1,12 +1,12 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Id;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Id;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Util\URL;
class DeleteLogAction extends ApiAction
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Router\Action;
use IndifferentKetchup\Iblogs\Router\Action;
class EmptyAction extends Action
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
class EndpointNotFoundAction extends ApiAction
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\FiltersResponse;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\FiltersResponse;
class GetFiltersAction extends ApiAction
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\LimitsResponse;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\LimitsResponse;
class GetLimitsAction extends ApiAction
{

View File

@@ -1,13 +1,13 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\LogResponse;
use Aternos\Mclogs\Id;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\LogResponse;
use IndifferentKetchup\Iblogs\Id;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Util\URL;
class LogInfoAction extends ApiAction
{

View File

@@ -1,13 +1,13 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\CodexLogResponse;
use Aternos\Mclogs\Id;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\CodexLogResponse;
use IndifferentKetchup\Iblogs\Id;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Util\URL;
class LogInsightsAction extends ApiAction
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
class RateLimitErrorAction extends ApiAction
{

View File

@@ -1,13 +1,13 @@
<?php
namespace Aternos\Mclogs\Api\Action;
namespace IndifferentKetchup\Iblogs\Api\Action;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\RawLogResponse;
use Aternos\Mclogs\Id;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\RawLogResponse;
use IndifferentKetchup\Iblogs\Id;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Util\URL;
class RawLogAction extends ApiAction
{

View File

@@ -1,11 +1,11 @@
<?php
namespace Aternos\Mclogs\Api;
namespace IndifferentKetchup\Iblogs\Api;
use Aternos\Mclogs\Router\Router;
use Aternos\Mclogs\Frontend;
use Aternos\Mclogs\Id;
use Aternos\Mclogs\Router\Method;
use IndifferentKetchup\Iblogs\Router\Router;
use IndifferentKetchup\Iblogs\Frontend;
use IndifferentKetchup\Iblogs\Id;
use IndifferentKetchup\Iblogs\Router\Method;
class ApiRouter extends Router
{

View File

@@ -1,10 +1,10 @@
<?php
namespace Aternos\Mclogs\Api;
namespace IndifferentKetchup\Iblogs\Api;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
/**
* Utility class for reading log content from the http request

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Api;
namespace IndifferentKetchup\Iblogs\Api;
use Aternos\Mclogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
class LogContentParser extends ContentParser
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Api\Response;
namespace IndifferentKetchup\Iblogs\Api\Response;
class ApiError extends ApiResponse
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Api\Response;
namespace IndifferentKetchup\Iblogs\Api\Response;
class ApiResponse implements \JsonSerializable
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Api\Response;
namespace IndifferentKetchup\Iblogs\Api\Response;
use Aternos\Codex\Log\LogInterface;
use IndifferentKetchup\Codex\Log\LogInterface;
class CodexLogResponse extends ApiResponse
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Api\Response;
namespace IndifferentKetchup\Iblogs\Api\Response;
use Aternos\Mclogs\Filter\Filter;
use IndifferentKetchup\Iblogs\Filter\Filter;
class FiltersResponse extends ApiResponse
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Api\Response;
namespace IndifferentKetchup\Iblogs\Api\Response;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
class LimitsResponse extends ApiResponse
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Api\Response;
namespace IndifferentKetchup\Iblogs\Api\Response;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Util\URL;
class LogResponse extends ApiResponse
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Api\Response;
namespace IndifferentKetchup\Iblogs\Api\Response;
class MultiResponse extends ApiResponse
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Api\Response;
namespace IndifferentKetchup\Iblogs\Api\Response;
use Aternos\Mclogs\Log;
use IndifferentKetchup\Iblogs\Log;
class RawLogResponse extends ApiResponse
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Cache;
namespace IndifferentKetchup\Iblogs\Cache;
use Aternos\Mclogs\Storage\MongoDBClient;
use IndifferentKetchup\Iblogs\Storage\MongoDBClient;
use MongoDB\BSON\UTCDateTime;
class CacheEntry

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Config;
namespace IndifferentKetchup\Iblogs\Config;
use Aternos\Mclogs\Util\Singleton;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Util\Singleton;
use IndifferentKetchup\Iblogs\Util\URL;
class Config
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Config;
namespace IndifferentKetchup\Iblogs\Config;
enum ConfigKey
{
@@ -40,7 +40,7 @@ enum ConfigKey
ConfigKey::STORAGE_LIMIT_LINES => 25000,
ConfigKey::MONGODB_URL => 'mongodb://mongo:27017',
ConfigKey::MONGODB_DATABASE => 'mclogs',
ConfigKey::MONGODB_DATABASE => 'iblogs',
ConfigKey::ID_LENGTH => 7,
@@ -66,7 +66,7 @@ enum ConfigKey
*/
public function getEnvironmentVariable(): string
{
return "MCLOGS_" . $this->name;
return "IBLOGS_" . $this->name;
}
/**

View File

@@ -1,28 +1,20 @@
<?php
namespace Aternos\Mclogs\Data;
namespace IndifferentKetchup\Iblogs\Data;
use Aternos\Codex\Analysis\Information;
use Aternos\Codex\Log\AnalysableLog;
use Aternos\Codex\Log\LogInterface;
use Aternos\Codex\Minecraft\Analysis\Information\Vanilla\VanillaVersionInformation;
use Aternos\Codex\Minecraft\Log\Minecraft\Vanilla\Fabric\FabricLog;
use Aternos\Codex\Minecraft\Log\Minecraft\Vanilla\VanillaClientLog;
use Aternos\Codex\Minecraft\Log\Minecraft\Vanilla\VanillaCrashReportLog;
use Aternos\Codex\Minecraft\Log\Minecraft\Vanilla\VanillaLog;
use Aternos\Codex\Minecraft\Log\Minecraft\Vanilla\VanillaNetworkProtocolErrorReportLog;
use Aternos\Codex\Minecraft\Log\Minecraft\Vanilla\VanillaServerLog;
use Aternos\Mclogs\Cache\CacheEntry;
use Aternos\Sherlock\MapLocator\FabricMavenMapLocator;
use Aternos\Sherlock\MapLocator\LauncherMetaMapLocator;
use Aternos\Sherlock\Maps\GZURLYarnMap;
use Aternos\Sherlock\Maps\ObfuscationMap;
use Aternos\Sherlock\Maps\URLVanillaObfuscationMap;
use Aternos\Sherlock\Maps\VanillaObfuscationMap;
use Aternos\Sherlock\Maps\YarnMap;
use Aternos\Sherlock\ObfuscatedString;
use Exception;
use IndifferentKetchup\Codex\Log\LogInterface;
/**
* Stub for v1 — `mclogs` used `aternos/sherlock` plus the Vanilla / Fabric
* Minecraft codex log subclasses to deobfuscate Mojang and Yarn mappings
* in stack traces. iblogs targets Project Zomboid, which uses no such
* mapping scheme, so the deobfuscator is a no-op until iblogs gains
* Minecraft support.
*
* Restore the original mapping flow (Sherlock map locators + obfuscation
* maps) when re-introducing Minecraft logs. The historical implementation
* lives in mclogs upstream commits prior to the iblogs fork.
*/
class Deobfuscator
{
public function __construct(protected LogInterface $codexLog)
@@ -31,108 +23,6 @@ class Deobfuscator
public function deobfuscate(): ?string
{
if (!$this->codexLog instanceof AnalysableLog) {
return null;
}
if (!$this->codexLog instanceof VanillaLog) {
return null;
}
$analysis = $this->codexLog->analyse();
/**
* @var ?Information $version
*/
$version = $analysis->getFilteredInsights(VanillaVersionInformation::class)[0] ?? null;
if (!$version) {
return null;
}
$version = $version->getValue();
try {
$map = $this->getObfuscationMap($version);
} catch (Exception) {
$map = null;
}
if ($map === null) {
return null;
}
$obfuscatedContent = new ObfuscatedString($this->codexLog->getLogFile()->getContent(), $map);
if ($content = $obfuscatedContent->getMappedContent()) {
return $content;
}
return null;
}
/**
* Get the obfuscation map matching this log
*
* @param $version
* @return ObfuscationMap|null
* @throws Exception
*/
protected function getObfuscationMap($version): ?ObfuscationMap
{
if (in_array(get_class($this->codexLog), [
VanillaServerLog::class,
VanillaClientLog::class,
VanillaCrashReportLog::class,
VanillaNetworkProtocolErrorReportLog::class
])) {
$urlCache = new CacheEntry("sherlock:vanilla:$version:client");
$mapURL = $urlCache->get();
if (!$mapURL) {
$mapURL = new LauncherMetaMapLocator($version, "client")->findMappingURL();
if (!$mapURL) {
return null;
}
$urlCache->set($mapURL, 30 * 24 * 60 * 60);
}
try {
$mapCache = new CacheEntry("sherlock:$mapURL");
if ($mapContent = $mapCache->get()) {
$map = new VanillaObfuscationMap($mapContent);
} else {
$map = new URLVanillaObfuscationMap($mapURL);
$mapCache->set($map->getContent());
}
} catch (Exception) {
}
return $map ?? null;
}
if ($this->codexLog instanceof FabricLog) {
$urlCache = new CacheEntry("sherlock:yarn:$version:server");
$mapURL = $urlCache->get();
if (!$mapURL) {
$mapURL = new FabricMavenMapLocator($version)->findMappingURL();
if (!$mapURL) {
return null;
}
$urlCache->set($mapURL, 24 * 60 * 60);
}
try {
$mapCache = new CacheEntry("sherlock:$mapURL");
if ($mapContent = $mapCache->get()) {
$map = new YarnMap($mapContent);
} else {
$map = new GZURLYarnMap($mapURL);
$mapCache->set($map->getContent());
}
} catch (Exception) {
}
return $map ?? null;
}
return null;
}
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Data;
namespace IndifferentKetchup\Iblogs\Data;
use MongoDB\BSON\Serializable;
use MongoDB\Model\BSONDocument;

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Data;
namespace IndifferentKetchup\Iblogs\Data;
use Random\RandomException;

View File

@@ -1,16 +1,13 @@
<?php
namespace Aternos\Mclogs;
namespace IndifferentKetchup\Iblogs;
use Aternos\Codex\Minecraft\Log\Minecraft\MinecraftLog;
use IndifferentKetchup\Codex\Detective\ProjectZomboid\ProjectZomboidDetective;
class Detective extends \Aternos\Codex\Detective\Detective
class Detective extends \IndifferentKetchup\Codex\Detective\Detective
{
protected string $defaultLogClass = MinecraftLog::class;
public function __construct()
{
$this->addDetective(new \Aternos\Codex\Minecraft\Detective\Detective())
->addDetective(new \Aternos\Codex\Hytale\Detective\Detective());
$this->addDetective(new ProjectZomboidDetective());
}
}
}

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
use Aternos\Mclogs\Filter\Pattern\PatternWithReplacement;
use IndifferentKetchup\Iblogs\Filter\Pattern\PatternWithReplacement;
class AccessTokenFilter extends RegexFilter
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
abstract class Filter implements \JsonSerializable
{
@@ -23,8 +23,7 @@ abstract class Filter implements \JsonSerializable
new TrimFilter(),
new LimitBytesFilter(),
new LimitLinesFilter(),
new IPv4Filter(),
new IPv6Filter(),
new ProjectZomboidRedactorFilter(),
new UsernameFilter(),
new AccessTokenFilter(),
];

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
enum FilterType: string
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
use Aternos\Mclogs\Filter\Pattern\Pattern;
use Aternos\Mclogs\Filter\Pattern\PatternWithReplacement;
use IndifferentKetchup\Iblogs\Filter\Pattern\Pattern;
use IndifferentKetchup\Iblogs\Filter\Pattern\PatternWithReplacement;
class IPv4Filter extends RegexFilter
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
use Aternos\Mclogs\Filter\Pattern\Pattern;
use Aternos\Mclogs\Filter\Pattern\PatternWithReplacement;
use IndifferentKetchup\Iblogs\Filter\Pattern\Pattern;
use IndifferentKetchup\Iblogs\Filter\Pattern\PatternWithReplacement;
class IPv6Filter extends RegexFilter
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
class LimitBytesFilter extends Filter
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
class LimitLinesFilter extends Filter
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Filter\Pattern;
namespace IndifferentKetchup\Iblogs\Filter\Pattern;
/**
* https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Filter\Pattern;
namespace IndifferentKetchup\Iblogs\Filter\Pattern;
class Pattern implements \JsonSerializable
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Filter\Pattern;
namespace IndifferentKetchup\Iblogs\Filter\Pattern;
class PatternWithReplacement extends Pattern
{

View File

@@ -0,0 +1,45 @@
<?php
namespace IndifferentKetchup\Iblogs\Filter;
use IndifferentKetchup\Codex\Util\ProjectZomboid\ProjectZomboidRedactor;
/**
* Save-time wrapper that delegates to codex's ProjectZomboidRedactor.
*
* Codex owns the canonical Project Zomboid PII patterns (Steam IDs, player
* names, world coordinates, plus IPv4 / IPv6 addresses with the v0.3.0
* release). This filter is the single point at which PZ-shaped PII is
* scrubbed on save; it replaces the previous IPv4Filter + IPv6Filter
* stage (whose IP-only matches left port suffixes intact) and adds the
* PZ-specific Steam ID, player-name, and coordinate redaction the generic
* filters never touched.
*
* Codex's IPv4 / IPv6 regexes are generic and apply to non-PZ pastes too;
* the PZ-specific regexes (Steam ID, player name, coords) mostly no-op on
* non-PZ content because they rely on PZ-specific anchors (`76561198`,
* the Steam-ID placeholder, `Combat:` / `Safety:` prefixes, `at` / `[`
* coord wrappers + trailing PvP verbs).
*
* Patterns are encapsulated inside the codex redactor and are not exposed
* to the client-side preview JS (`getData()` returns an empty array).
* Server-side redaction on save is the privacy guarantee; the preview is
* only a UX hint for users about what gets scrubbed.
*/
class ProjectZomboidRedactorFilter extends Filter
{
public function getType(): FilterType
{
return FilterType::REGEX;
}
public function getData(): array
{
return [];
}
public function filter(string $data): string
{
return new ProjectZomboidRedactor()->redact($data);
}
}

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
use Aternos\Mclogs\Filter\Pattern\Pattern;
use Aternos\Mclogs\Filter\Pattern\PatternWithReplacement;
use IndifferentKetchup\Iblogs\Filter\Pattern\Pattern;
use IndifferentKetchup\Iblogs\Filter\Pattern\PatternWithReplacement;
abstract class RegexFilter extends Filter
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
class TrimFilter extends Filter
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Filter;
namespace IndifferentKetchup\Iblogs\Filter;
use Aternos\Mclogs\Filter\Pattern\PatternWithReplacement;
use IndifferentKetchup\Iblogs\Filter\Pattern\PatternWithReplacement;
class UsernameFilter extends RegexFilter
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Frontend\Action;
namespace IndifferentKetchup\Iblogs\Frontend\Action;
use Aternos\Mclogs\Router\Action;
use IndifferentKetchup\Iblogs\Router\Action;
class ApiDocsAction extends Action
{

View File

@@ -1,10 +1,10 @@
<?php
namespace Aternos\Mclogs\Frontend\Action;
namespace IndifferentKetchup\Iblogs\Frontend\Action;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Util\URL;
class CreateLogAction extends \Aternos\Mclogs\Api\Action\CreateLogAction
class CreateLogAction extends \IndifferentKetchup\Iblogs\Api\Action\CreateLogAction
{
protected bool $includeCookie = true;
protected bool $includeToken = false;

View File

@@ -1,12 +1,12 @@
<?php
namespace Aternos\Mclogs\Frontend\Action;
namespace IndifferentKetchup\Iblogs\Frontend\Action;
use Aternos\Mclogs\Frontend\Cookie\TokenCookie;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Frontend\Cookie\TokenCookie;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Util\URL;
class DeleteLogAction extends \Aternos\Mclogs\Api\Action\DeleteLogAction
class DeleteLogAction extends \IndifferentKetchup\Iblogs\Api\Action\DeleteLogAction
{
protected function getAllowedOrigin(): string
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Frontend\Action;
namespace IndifferentKetchup\Iblogs\Frontend\Action;
use Aternos\Mclogs\Router\Action;
use IndifferentKetchup\Iblogs\Router\Action;
class FaviconAction extends Action
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Frontend\Action;
namespace IndifferentKetchup\Iblogs\Frontend\Action;
use Aternos\Mclogs\Router\Action;
use IndifferentKetchup\Iblogs\Router\Action;
class NotFoundAction extends Action
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Frontend\Action;
namespace IndifferentKetchup\Iblogs\Frontend\Action;
use Aternos\Mclogs\Router\Action;
use IndifferentKetchup\Iblogs\Router\Action;
class StartAction extends Action
{

View File

@@ -1,11 +1,11 @@
<?php
namespace Aternos\Mclogs\Frontend\Action;
namespace IndifferentKetchup\Iblogs\Frontend\Action;
use Aternos\Mclogs\Id;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Router\Action;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Id;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Router\Action;
use IndifferentKetchup\Iblogs\Util\URL;
class ViewLogAction extends Action
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Frontend\Assets;
namespace IndifferentKetchup\Iblogs\Frontend\Assets;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
class Asset implements \JsonSerializable
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Frontend\Assets;
namespace IndifferentKetchup\Iblogs\Frontend\Assets;
use Aternos\Mclogs\Util\Singleton;
use IndifferentKetchup\Iblogs\Util\Singleton;
class AssetLoader
{
@@ -90,7 +90,7 @@ class AssetLoader
public function writeCache(): void
{
$assets = [
new Asset(AssetType::CSS, "css/mclogs.css"),
new Asset(AssetType::CSS, "css/iblogs.css"),
new Asset(AssetType::JS, "js/start.js"),
new Asset(AssetType::JS, "js/log.js"),
new Asset(AssetType::CSS, "vendor/fontawesome/css/fontawesome.min.css")

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Frontend\Assets;
namespace IndifferentKetchup\Iblogs\Frontend\Assets;
enum AssetType: string
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Frontend\Cookie;
namespace IndifferentKetchup\Iblogs\Frontend\Cookie;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Util\URL;
abstract class Cookie
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Frontend\Cookie;
namespace IndifferentKetchup\Iblogs\Frontend\Cookie;
class SettingsCookie extends Cookie
{
@@ -9,6 +9,6 @@ class SettingsCookie extends Cookie
*/
protected function getKey(): string
{
return "MCLOGS_SETTINGS";
return "IBLOGS_SETTINGS";
}
}

View File

@@ -1,10 +1,10 @@
<?php
namespace Aternos\Mclogs\Frontend\Cookie;
namespace IndifferentKetchup\Iblogs\Frontend\Cookie;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use Aternos\Mclogs\Log;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Log;
class TokenCookie extends Cookie
{
@@ -23,7 +23,7 @@ class TokenCookie extends Cookie
*/
protected function getKey(): string
{
return "MCLOGS_LOG_TOKEN";
return "IBLOGS_LOG_TOKEN";
}
/**

View File

@@ -1,10 +1,10 @@
<?php
namespace Aternos\Mclogs\Frontend;
namespace IndifferentKetchup\Iblogs\Frontend;
use Aternos\Mclogs\Router\Router;
use Aternos\Mclogs\Id;
use Aternos\Mclogs\Router\Method;
use IndifferentKetchup\Iblogs\Router\Router;
use IndifferentKetchup\Iblogs\Id;
use IndifferentKetchup\Iblogs\Router\Method;
class FrontendRouter extends Router
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Frontend\Settings;
namespace IndifferentKetchup\Iblogs\Frontend\Settings;
enum Setting: string
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs\Frontend\Settings;
namespace IndifferentKetchup\Iblogs\Frontend\Settings;
use Aternos\Mclogs\Frontend\Cookie\SettingsCookie;
use IndifferentKetchup\Iblogs\Frontend\Cookie\SettingsCookie;
class Settings
{

View File

@@ -1,8 +1,8 @@
<?php
namespace Aternos\Mclogs;
namespace IndifferentKetchup\Iblogs;
use Aternos\Mclogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
class Id implements \JsonSerializable
{
@@ -26,7 +26,7 @@ class Id implements \JsonSerializable
*/
protected function generate(): string
{
$config = \Aternos\Mclogs\Config\Config::getInstance();
$config = \IndifferentKetchup\Iblogs\Config\Config::getInstance();
$idLength = $config->get(ConfigKey::ID_LENGTH);
$newId = "";

View File

@@ -1,21 +1,21 @@
<?php
namespace Aternos\Mclogs;
namespace IndifferentKetchup\Iblogs;
use Aternos\Codex\Analysis\Analysis;
use Aternos\Codex\Log\AnalysableLogInterface;
use Aternos\Codex\Log\File\StringLogFile;
use Aternos\Codex\Log\Level;
use Aternos\Codex\Log\LogInterface;
use Aternos\Mclogs\Config\ConfigKey;
use Aternos\Mclogs\Data\Deobfuscator;
use Aternos\Mclogs\Data\MetadataEntry;
use Aternos\Mclogs\Data\Token;
use Aternos\Mclogs\Filter\Filter;
use Aternos\Mclogs\Frontend\Cookie\TokenCookie;
use Aternos\Mclogs\Printer\Printer;
use Aternos\Mclogs\Storage\MongoDBClient;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Codex\Analysis\Analysis;
use IndifferentKetchup\Codex\Log\AnalysableLogInterface;
use IndifferentKetchup\Codex\Log\File\StringLogFile;
use IndifferentKetchup\Codex\Log\Level;
use IndifferentKetchup\Codex\Log\LogInterface;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Data\Deobfuscator;
use IndifferentKetchup\Iblogs\Data\MetadataEntry;
use IndifferentKetchup\Iblogs\Data\Token;
use IndifferentKetchup\Iblogs\Filter\Filter;
use IndifferentKetchup\Iblogs\Frontend\Cookie\TokenCookie;
use IndifferentKetchup\Iblogs\Printer\Printer;
use IndifferentKetchup\Iblogs\Storage\MongoDBClient;
use IndifferentKetchup\Iblogs\Util\URL;
use MongoDB\BSON\UTCDateTime;
use Uri\Rfc3986\Uri;
@@ -371,7 +371,7 @@ class Log
*/
protected function getExpiryTimestamp(): UTCDateTime
{
$ttl = \Aternos\Mclogs\Config\Config::getInstance()->get(ConfigKey::STORAGE_TTL);
$ttl = \IndifferentKetchup\Iblogs\Config\Config::getInstance()->get(ConfigKey::STORAGE_TTL);
$expires = time() + $ttl;
return new UTCDateTime($expires * 1000);
}

View File

@@ -1,20 +1,24 @@
<?php
namespace Aternos\Mclogs\Printer;
namespace IndifferentKetchup\Iblogs\Printer;
use IndifferentKetchup\Codex\Printer\Modification;
/**
* Class FormatModification
* Stub for v1 — `mclogs` extended `Aternos\Codex\Minecraft\Printer\FormatModification`
* to translate Minecraft section-sign format codes into HTML format spans
* (`format-black`, `format-darkblue`, etc., backed by the format-colors block
* in the iblogs CSS). Project Zomboid logs do not carry these codes, so this
* Modification is a pass-through under iblogs.
*
* @package Printer
* The CSS `format-*` color classes are retained so re-introducing a real
* Minecraft FormatModification later (or any other game with its own
* format-code scheme) can reuse the existing styling.
*/
class FormatModification extends \Aternos\Codex\Minecraft\Printer\FormatModification
class FormatModification extends Modification
{
/**
* @param string $format
* @return string
*/
protected function getClasses(string $format): string
public function modify(string $text): string
{
return "format format-" . $format;
return $text;
}
}
}

View File

@@ -1,13 +1,13 @@
<?php
namespace Aternos\Mclogs\Printer;
namespace IndifferentKetchup\Iblogs\Printer;
use Aternos\Codex\Log\Entry;
use Aternos\Codex\Log\EntryInterface;
use Aternos\Codex\Log\Level;
use Aternos\Codex\Log\LineInterface;
use Aternos\Codex\Printer\ModifiableDefaultPrinter;
use Aternos\Mclogs\Id;
use IndifferentKetchup\Codex\Log\Entry;
use IndifferentKetchup\Codex\Log\EntryInterface;
use IndifferentKetchup\Codex\Log\Level;
use IndifferentKetchup\Codex\Log\LineInterface;
use IndifferentKetchup\Codex\Printer\ModifiableDefaultPrinter;
use IndifferentKetchup\Iblogs\Id;
/**
* Class Printer

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Router;
namespace IndifferentKetchup\Iblogs\Router;
abstract class Action
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Router;
namespace IndifferentKetchup\Iblogs\Router;
enum Method: string
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Router;
namespace IndifferentKetchup\Iblogs\Router;
class Route
{

View File

@@ -1,9 +1,9 @@
<?php
namespace Aternos\Mclogs\Router;
namespace IndifferentKetchup\Iblogs\Router;
use Aternos\Mclogs\Util\Singleton;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Util\Singleton;
use IndifferentKetchup\Iblogs\Util\URL;
class Router
{

View File

@@ -1,10 +1,10 @@
<?php
namespace Aternos\Mclogs\Storage;
namespace IndifferentKetchup\Iblogs\Storage;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use Aternos\Mclogs\Util\Singleton;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Util\Singleton;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Client;
use MongoDB\Collection;

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Util;
namespace IndifferentKetchup\Iblogs\Util;
trait Singleton
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Util;
namespace IndifferentKetchup\Iblogs\Util;
class TimeInterval
{

View File

@@ -1,6 +1,6 @@
<?php
namespace Aternos\Mclogs\Util;
namespace IndifferentKetchup\Iblogs\Util;
use Uri\Rfc3986\Uri;

View File

@@ -1,12 +1,12 @@
<?php
use Aternos\Mclogs\Api\Action\BulkDeleteLogsAction;
use Aternos\Mclogs\Api\Response\ApiError;
use Aternos\Mclogs\Api\Response\ApiResponse;
use Aternos\Mclogs\Api\Response\MultiResponse;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Api\Action\BulkDeleteLogsAction;
use IndifferentKetchup\Iblogs\Api\Response\ApiError;
use IndifferentKetchup\Iblogs\Api\Response\ApiResponse;
use IndifferentKetchup\Iblogs\Api\Response\MultiResponse;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Util\URL;
$config = Config::getInstance();
?>
@@ -433,8 +433,8 @@ $config = Config::getInstance();
{
"message": "Label: value",
"counter": 1,
"label": "The label of this information, e.g. Minecraft version",
"value": "The value of this information, e.g. 1.12.2",
"label": "The label of this information, e.g. Engine version",
"value": "The value of this information, e.g. 42.16.3",
"entry": {
"level": 6,
"time": null,
@@ -515,8 +515,8 @@ $config = Config::getInstance();
{
"message": "Label: value",
"counter": 1,
"label": "The label of this information, e.g. Minecraft version",
"value": "The value of this information, e.g. 1.12.2",
"label": "The label of this information, e.g. Engine version",
"value": "The value of this information, e.g. 42.16.3",
"entry": {
"level": 6,
"time": null,
@@ -586,7 +586,7 @@ $config = Config::getInstance();
</div>
<h3>Success <span class="content-type">application/json</span></h3>
<pre class="api-code">
<?=htmlspecialchars(json_encode(\Aternos\Mclogs\Filter\Filter::getAll(), JSON_PRETTY_PRINT)); ?></pre>
<?=htmlspecialchars(json_encode(\IndifferentKetchup\Iblogs\Filter\Filter::getAll(), JSON_PRETTY_PRINT)); ?></pre>
<h3>Filter types</h3>
<table class="api-table">
<tr>

View File

@@ -1,13 +1,13 @@
<?php
use Aternos\Mclogs\Frontend\Assets\AssetLoader;
use Aternos\Mclogs\Frontend\Assets\AssetType;
use Aternos\Mclogs\Log;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use Aternos\Mclogs\Frontend\Settings\Setting;
use Aternos\Mclogs\Frontend\Settings\Settings;
use Aternos\Mclogs\Util\TimeInterval;
use IndifferentKetchup\Iblogs\Frontend\Assets\AssetLoader;
use IndifferentKetchup\Iblogs\Frontend\Assets\AssetType;
use IndifferentKetchup\Iblogs\Log;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Frontend\Settings\Setting;
use IndifferentKetchup\Iblogs\Frontend\Settings\Settings;
use IndifferentKetchup\Iblogs\Util\TimeInterval;
/** @var Log $log */

View File

@@ -1,4 +1,4 @@
<svg width="41" height="42" viewBox="0 0 41 42" fill="<?=htmlspecialchars(\Aternos\Mclogs\Config\Config::getInstance()->get(\Aternos\Mclogs\Config\ConfigKey::FRONTEND_COLOR_ACCENT)); ?>" xmlns="http://www.w3.org/2000/svg">
<svg width="41" height="42" viewBox="0 0 41 42" fill="<?=htmlspecialchars(\IndifferentKetchup\Iblogs\Config\Config::getInstance()->get(\IndifferentKetchup\Iblogs\Config\ConfigKey::FRONTEND_COLOR_ACCENT)); ?>" xmlns="http://www.w3.org/2000/svg">
<rect width="41" height="5" rx="2"/>
<rect y="9.25" width="33" height="5" rx="2"/>
<rect y="18.5" width="19" height="5" rx="2"/>

Before

Width:  |  Height:  |  Size: 470 B

After

Width:  |  Height:  |  Size: 492 B

View File

@@ -1,5 +1,5 @@
<?php
use Aternos\Mclogs\Config\Config;use Aternos\Mclogs\Config\ConfigKey;use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Config\Config;use IndifferentKetchup\Iblogs\Config\ConfigKey;use IndifferentKetchup\Iblogs\Util\URL;
$imprintUrl = Config::getInstance()->get(ConfigKey::LEGAL_IMPRINT);
$privacyUrl = Config::getInstance()->get(ConfigKey::LEGAL_PRIVACY);
@@ -19,10 +19,9 @@ $privacyUrl = Config::getInstance()->get(ConfigKey::LEGAL_PRIVACY);
</nav>
<?php endif; ?>
<nav class="footer-nav">
<a href="https://github.com/aternosorg/mclogs" title="mclo.gs on Github" target="_blank"><i class="fa-brands fa-github"></i>GitHub</a>
<a href="https://modrinth.com/plugin/mclogs" title="Download mclo.gs Mod/Plugin" target="_blank"><i class="fa-solid fa-cube"></i>Mod/Plugin</a>
<a href="<?=htmlspecialchars(URL::getApi()->toString()); ?>" title="mclo.gs API"><i class="fa-solid fa-code"></i>API</a>
<a href="https://github.com/indifferentketchup/iblogs" title="iblogs on Github" target="_blank"><i class="fa-brands fa-github"></i>GitHub</a>
<a href="<?=htmlspecialchars(URL::getApi()->toString()); ?>" title="iblogs API"><i class="fa-solid fa-code"></i>API</a>
</nav>
<span class="footer-text">developed by <a href="https://aternos.org" target="_blank" title="Aternos website">Aternos</a>
<span class="footer-text">based on <a href="https://github.com/aternosorg/mclogs" target="_blank" title="Original mclogs project">mclogs</a> by <a href="https://github.com/aternosorg" target="_blank" title="Aternos on GitHub"><i class="fa-brands fa-github"></i> Aternos</a>
</span>
</footer>

View File

@@ -1,17 +1,17 @@
<?php
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use Aternos\Mclogs\Frontend\Assets\AssetLoader;
use Aternos\Mclogs\Frontend\Assets\AssetType;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Frontend\Assets\AssetLoader;
use IndifferentKetchup\Iblogs\Frontend\Assets\AssetType;
use IndifferentKetchup\Iblogs\Util\URL;
?>
<meta charset="utf-8"/>
<base href="/"/>
<?= AssetLoader::getInstance()->getHTML(AssetType::CSS, "vendor/fontawesome/css/fontawesome.min.css"); ?>
<?= AssetLoader::getInstance()->getHTML(AssetType::CSS, "css/mclogs.css"); ?>
<?= AssetLoader::getInstance()->getHTML(AssetType::CSS, "css/iblogs.css"); ?>
<style>
:root {

View File

@@ -1,5 +1,5 @@
<header>
<a href="<?=htmlspecialchars(\Aternos\Mclogs\Util\URL::getBase()->toString()); ?>" class="logo">
<a href="<?=htmlspecialchars(\IndifferentKetchup\Iblogs\Util\URL::getBase()->toString()); ?>" class="logo">
<svg class="logo-icon" width="41" height="42" viewBox="0 0 41 42" fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect width="41" height="5" rx="2" fill="currentColor"/>
@@ -8,11 +8,11 @@
<rect y="27.75" width="33" height="5" rx="2" fill="currentColor"/>
<rect y="37" width="41" height="5" rx="2" fill="currentColor"/>
</svg>
<span class="logo-text"><?= htmlspecialchars(\Aternos\Mclogs\Config\Config::getInstance()->getName()); ?></span>
<span class="logo-text"><?= htmlspecialchars(\IndifferentKetchup\Iblogs\Config\Config::getInstance()->getName()); ?></span>
</a>
<div class="tagline">
<h1 class="tagline-main"><span class="title-verb">Paste</span> your logs.</h1>
<div class="tagline-sub">Built for Minecraft & Hytale</div>
<div class="tagline-sub">Built for game-server logs</div>
</div>
<script>
const titles = ["Paste", "Share", "Analyse"];

View File

@@ -1,14 +1,14 @@
<?php
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Filter\Filter;
use Aternos\Mclogs\Frontend\Assets\AssetLoader;
use Aternos\Mclogs\Frontend\Assets\AssetType;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Filter\Filter;
use IndifferentKetchup\Iblogs\Frontend\Assets\AssetLoader;
use IndifferentKetchup\Iblogs\Frontend\Assets\AssetType;
?><!DOCTYPE html>
<html lang="en">
<head>
<?php include __DIR__ . '/parts/head.php'; ?>
<title><?= htmlspecialchars(Config::getInstance()->getName()); ?> - Paste, share & analyse your logs</title>
<meta name="description" content="Easily paste your Minecraft & Hytale logs to share and analyse them." />
<meta name="description" content="Easily paste your game-server logs to share and analyse them." />
</head>
<body data-name="<?=htmlspecialchars(Config::getInstance()->getName()); ?>">
<?php include __DIR__ . '/parts/header.php'; ?>

View File

@@ -950,6 +950,24 @@ main {
width: 100%;
}
/*
* Skip layout / paint of off-screen log cells until they scroll into
* view. With logs running up to ~25 000 entries the eager render of
* every cell is what causes the multi-second freeze on initial load.
*
* `.entry` itself is `display: contents` (no box), so the rule has to
* land on the actual grid items it produces. `contain-intrinsic-size:
* auto 1.5em` lets the browser remember measured heights after first
* paint and falls back to ~one line tall as the placeholder for
* never-seen cells. Multi-line stack traces correct on first pass; the
* scroll position adjusts once.
*/
.log-inner .line-number-container,
.log-inner .line-content {
content-visibility: auto;
contain-intrinsic-size: auto 1.5em;
}
.log-inner .entry.entry-error .line-content,
.log-inner .entry.entry-error .line-number-container{
background-color: var(--error-bg);
@@ -1088,6 +1106,82 @@ main {
color: var(--accent);
}
/*
* Foldable variant generated by the smart "errors + 25 context" toggle in
* log.js. The bar is draggable: vertical pointer drag reveals or re-hides
* lines from the top of the hidden range. Visual cues:
* - ns-resize cursor announces the drag direction.
* - Faint horizontal hatch hints at compressed content underneath.
* - Hover and drag states intensify the hatch toward --accent so the
* bar reads as an interactive separator, not decorative chrome.
*/
.collapsed-lines-foldable {
cursor: ns-resize;
user-select: none;
-webkit-user-select: none;
}
.collapsed-lines-foldable .collapsed-lines-count {
background:
repeating-linear-gradient(
to bottom,
transparent 0,
transparent 3px,
color-mix(in srgb, var(--border) 60%, transparent) 3px,
color-mix(in srgb, var(--border) 60%, transparent) 4px
),
var(--surface);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
transition:
background-color 0.12s ease,
color 0.12s ease,
border-color 0.12s ease,
box-shadow 0.12s ease;
}
.collapsed-lines-foldable:hover .collapsed-lines-count,
.collapsed-lines-dragging .collapsed-lines-count {
background:
repeating-linear-gradient(
to bottom,
transparent 0,
transparent 3px,
color-mix(in srgb, var(--accent) 35%, transparent) 3px,
color-mix(in srgb, var(--accent) 35%, transparent) 4px
),
var(--accent-bg);
border-top-color: color-mix(in srgb, var(--accent) 60%, transparent);
border-bottom-color: color-mix(in srgb, var(--accent) 60%, transparent);
color: var(--accent);
}
.collapsed-lines-foldable:hover .collapsed-lines-count i,
.collapsed-lines-dragging .collapsed-lines-count i {
color: var(--accent);
}
.collapsed-lines-dragging {
cursor: grabbing;
}
.collapsed-lines-dragging .collapsed-lines-count {
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--accent) 70%, transparent),
0 6px 16px color-mix(in srgb, var(--accent) 25%, transparent);
}
/*
* The header error-count chip is informational only the smart fold is
* applied automatically on page load and folds can only be expanded
* individually. Strip the .btn cursor / hover affordance so the chip
* doesn't read as interactive.
*/
#error-toggle {
cursor: default;
pointer-events: none;
}
.log-inner .level {
display: block;
white-space: pre-wrap;
@@ -1145,7 +1239,7 @@ main {
color: #A4A4A4;
}
/** Minecraft Format Colors **/
/** Format colors (legacy mclogs syntax-highlighting palette) **/
.format-black {
color: #000;

View File

@@ -47,93 +47,155 @@ function scrollToHeight(top, smoothScrollLimit = 10000) {
window.scrollTo({left: 0, top, behavior});
}
/* error collapse toggle */
const toggleErrorsButton = document.getElementById("error-toggle");
if (toggleErrorsButton) {
toggleErrorsButton.addEventListener("click", toggleErrors);
}
/*
* Smart fold around errors.
*
* Every entry within ±ERROR_WINDOW_SIZE of an error stays visible; runs of
* non-error entries with no nearby errors collapse into a draggable fold
* bar. Vertical drag on the bar progressively reveals or re-hides lines;
* a click without drag reveals the next DEFAULT_CLICK_REVEAL lines.
*
* The fold is applied automatically on page load when the log contains
* errors. Folds can only be expanded individually — there is no
* "unfold everything" path. The error-count chip in the header is purely
* informational; it is not interactive.
*/
const ERROR_WINDOW_SIZE = 25;
const PIXELS_PER_LINE_DRAG = 6;
const DEFAULT_CLICK_REVEAL = 25;
const DRAG_PIXEL_THRESHOLD = 3;
function toggleErrors() {
if (toggleErrorsButton.classList.contains("toggled")) {
toggleErrorsButton.classList.remove("toggled");
uncollapseAllErrors();
} else {
toggleErrorsButton.classList.add("toggled");
collapseAllErrors();
}
}
applySmartFold();
function collapseAllErrors() {
let firstNoErrorLine = false;
let lines = document.querySelectorAll('.log-inner > .entry');
let totalLines = lines.length;
for (const [i, line] of lines.entries()) {
let lineNumber = line.querySelector(".line-number").innerHTML;
if (line.classList.contains("entry-no-error")) {
line.style.display = "none";
function applySmartFold() {
const lines = Array.from(document.querySelectorAll('.log-inner > .entry'));
const total = lines.length;
if (!total) return;
if (firstNoErrorLine === false) {
firstNoErrorLine = lineNumber;
}
if (i + 1 === totalLines && firstNoErrorLine) {
line.insertAdjacentElement("afterend", generateCollapsedLines(firstNoErrorLine, lineNumber));
}
} else {
if (firstNoErrorLine) {
line.insertAdjacentElement("beforebegin", generateCollapsedLines(firstNoErrorLine, lineNumber - 1));
firstNoErrorLine = false;
}
// Pass 1: mark entries within ±ERROR_WINDOW_SIZE of any error. If the
// log has no errors, every entry stays visible (we never produce one
// giant fold spanning the whole log).
const mustShow = new Array(total).fill(false);
let sawError = false;
for (let i = 0; i < total; i++) {
if (lines[i].classList.contains("entry-error")) {
sawError = true;
const lo = Math.max(0, i - ERROR_WINDOW_SIZE);
const hi = Math.min(total - 1, i + ERROR_WINDOW_SIZE);
for (let j = lo; j <= hi; j++) mustShow[j] = true;
}
}
if (!sawError) return;
// Pass 2: hide unmarked entries; emit a fold bar at the START of each run.
let runEntries = [];
const flushRun = () => {
if (!runEntries.length) return;
const bar = createFoldBar(runEntries);
runEntries[0].insertAdjacentElement("beforebegin", bar);
runEntries = [];
};
for (let i = 0; i < total; i++) {
if (!mustShow[i]) {
lines[i].style.display = "none";
runEntries.push(lines[i]);
} else {
flushRun();
}
}
flushRun();
}
function uncollapseAllErrors() {
document.querySelectorAll('.entry-no-error').forEach(line => line.style.removeProperty("display"));
document.querySelectorAll('.collapsed-lines').forEach(collapsed => collapsed.remove());
function createFoldBar(entries) {
const bar = document.createElement("div");
bar.classList.add("collapsed-lines", "collapsed-lines-foldable");
bar.appendChild(document.createElement("div")); // line-number column slot
const count = document.createElement("div");
count.classList.add("collapsed-lines-count");
bar.appendChild(count);
bar._hiddenEntries = entries;
bar._revealedCount = 0;
renderFoldLabel(bar);
attachFoldDragHandler(bar);
return bar;
}
function handleCollapsedClick(e) {
let collapsed = e.currentTarget;
let positionElement = document.getElementById(`L${parseInt(collapsed.dataset.end) + 1}`);
let position;
if (positionElement) {
position = positionElement.getBoundingClientRect().top - window.scrollY;
}
for (let i = parseInt(collapsed.dataset.start); i <= parseInt(collapsed.dataset.end); i++) {
document.getElementById(`L${i}`).parentElement.parentElement.style.removeProperty("display");
}
if (positionElement) {
window.scrollTo({
left: 0,
top: positionElement.getBoundingClientRect().top - position - collapsed.offsetHeight,
behavior: "instant"
});
}
collapsed.remove();
function renderFoldLabel(bar) {
const entries = bar._hiddenEntries;
const total = entries.length;
const revealed = bar._revealedCount;
const remaining = total - revealed;
const count = bar.querySelector(".collapsed-lines-count");
count.replaceChildren();
if (remaining <= 0) return;
const firstHiddenLine = parseInt(entries[revealed].querySelector(".line-number").innerHTML, 10);
const lastHiddenLine = parseInt(entries[total - 1].querySelector(".line-number").innerHTML, 10);
const word = remaining === 1 ? "line" : "lines";
const grip = document.createElement("i");
grip.className = "fa-solid fa-grip-lines";
count.appendChild(grip);
count.append(` ${remaining} ${word} hidden · ${firstHiddenLine}${lastHiddenLine} · drag to reveal `);
count.appendChild(grip.cloneNode());
}
function generateCollapsedLines(start, end) {
let count = end - start + 1;
let string = count === 1 ? "line" : "lines";
function applyFoldReveal(bar, n) {
const entries = bar._hiddenEntries;
n = Math.max(0, Math.min(entries.length, n));
if (n === bar._revealedCount) return;
for (let i = 0; i < entries.length; i++) {
if (i < n) entries[i].style.removeProperty("display");
else entries[i].style.display = "none";
}
bar._revealedCount = n;
renderFoldLabel(bar);
}
let collapsedRow = document.createElement("div");
collapsedRow.classList.add("collapsed-lines");
collapsedRow.dataset.start = start;
collapsedRow.dataset.end = end;
collapsedRow.appendChild(document.createElement("div"));
collapsedRow.addEventListener("click", handleCollapsedClick);
function attachFoldDragHandler(bar) {
let dragging = false;
let dragStartY = 0;
let dragStartReveal = 0;
let didDrag = false;
let collapsedLinesCount = document.createElement("div");
collapsedLinesCount.classList.add("collapsed-lines-count");
let icon = document.createElement("i");
icon.classList.add("fa-solid", "fa-angle-up");
collapsedLinesCount.appendChild(icon);
collapsedLinesCount.append(` ${count} ${string} `);
collapsedLinesCount.append(icon.cloneNode());
collapsedRow.appendChild(collapsedLinesCount);
bar.addEventListener("pointerdown", (e) => {
if (e.button !== 0) return;
dragging = true;
didDrag = false;
dragStartY = e.clientY;
dragStartReveal = bar._revealedCount;
bar.setPointerCapture(e.pointerId);
bar.classList.add("collapsed-lines-dragging");
e.preventDefault();
});
return collapsedRow;
bar.addEventListener("pointermove", (e) => {
if (!dragging) return;
const dy = e.clientY - dragStartY;
if (Math.abs(dy) >= DRAG_PIXEL_THRESHOLD) didDrag = true;
const target = dragStartReveal + Math.round(dy / PIXELS_PER_LINE_DRAG);
applyFoldReveal(bar, target);
});
const finish = (e) => {
if (!dragging) return;
dragging = false;
try { bar.releasePointerCapture(e.pointerId); } catch {}
bar.classList.remove("collapsed-lines-dragging");
if (!didDrag) {
// Plain click: reveal the next chunk of lines.
applyFoldReveal(bar, bar._revealedCount + DEFAULT_CLICK_REVEAL);
}
if (bar._revealedCount >= bar._hiddenEntries.length) {
bar.remove();
}
};
bar.addEventListener("pointerup", finish);
bar.addEventListener("pointercancel", finish);
}
/* convert timestamps */
@@ -203,7 +265,7 @@ function saveSettings() {
for (const checkbox of settingCheckboxes) {
data[checkbox.dataset.key] = checkbox.checked;
}
document.cookie = "MCLOGS_SETTINGS=" + encodeURIComponent(JSON.stringify(data)) + ";path=/;expires=" + new Date(new Date().getTime() + 100 * 365 * 24 * 60 * 60 * 1000).toUTCString();
document.cookie = "IBLOGS_SETTINGS=" + encodeURIComponent(JSON.stringify(data)) + ";path=/;expires=" + new Date(new Date().getTime() + 100 * 365 * 24 * 60 * 60 * 1000).toUTCString();
}
/* copy to clipboard */

View File

@@ -7,8 +7,27 @@ const fileSelectButton = document.getElementById('paste-select-file');
const pasteClipboardButton = document.getElementById('paste-clipboard');
const pasteError = document.getElementById('paste-error');
/*
* Large-paste buffering. <textarea> rendering is the bottleneck once content
* crosses a few hundred KB; the browser locks up while it lays out millions
* of characters. For pastes / file loads above the threshold, we hold the
* full content in `bufferedContent` and only render a small preview into the
* textarea. The save path uses `bufferedContent` when set.
*/
const PASTE_BUFFER_THRESHOLD_BYTES = 256 * 1024; // 256 KB
const PASTE_PREVIEW_LINES = 50;
let bufferedContent = null;
pasteArea.focus();
pasteArea.addEventListener('input', reevaluateContentStatus);
pasteArea.addEventListener('input', () => {
// User-typed input invalidates the buffer (the textarea is now the
// source of truth). The buffer is only set programmatically, so a
// user-driven `input` event always means edit-from-preview.
if (bufferedContent !== null) {
clearBuffer();
}
reevaluateContentStatus();
});
pasteArea.addEventListener('paste', handlePasteEvent);
pasteSaveButtons.forEach(button => button.addEventListener('click', sendLog));
fileSelectButton.addEventListener('click', selectLogFile);
@@ -39,7 +58,7 @@ async function sendLog() {
pasteSaveButtons.forEach(button => button.classList.add("btn-working"));
try {
let log = pasteArea.value;
let log = bufferedContent ?? pasteArea.value;
log = applyFilters(log);
const bodyData = {
@@ -149,13 +168,56 @@ async function pasteFromClipboard() {
showError("Clipboard is empty.");
return;
}
pasteArea.value = content;
reevaluateContentStatus();
loadContent(content);
} catch (err) {
showError("Clipboard is empty or not accessible.");
}
}
/*
* Single entry point for "place this string in the editor."
* Routes large content into the buffer + preview; small content goes into
* the textarea normally so the user can keep editing it.
*/
function loadContent(text) {
if (text.length > PASTE_BUFFER_THRESHOLD_BYTES) {
loadIntoBuffer(text);
} else {
clearBuffer();
pasteArea.value = text;
reevaluateContentStatus();
}
}
function loadIntoBuffer(text) {
bufferedContent = text;
const lines = text.split('\n');
const sizeMb = (text.length / 1024 / 1024).toFixed(2);
if (lines.length <= PASTE_PREVIEW_LINES) {
pasteArea.value = text;
} else {
const preview = lines.slice(0, PASTE_PREVIEW_LINES).join('\n');
const remaining = lines.length - PASTE_PREVIEW_LINES;
pasteArea.value =
`[Large paste buffered: ${lines.length.toLocaleString()} lines, ${sizeMb} MB.\n` +
` Full content uploads on Save. Edit this textarea to clear the buffer.]\n` +
`\n` +
`--- preview: first ${PASTE_PREVIEW_LINES} of ${lines.length.toLocaleString()} lines ---\n` +
preview +
`\n--- ${remaining.toLocaleString()} more lines hidden ---\n`;
}
pasteArea.readOnly = true;
reevaluateContentStatus();
}
function clearBuffer() {
if (bufferedContent === null) {
return;
}
bufferedContent = null;
pasteArea.readOnly = false;
}
function reevaluateContentStatus() {
clearError();
if (pasteArea.value.length > 0) {
@@ -184,6 +246,14 @@ async function handlePasteEvent(e) {
if (e.clipboardData?.files?.length > 0) {
e.preventDefault();
await loadFileContents(e.clipboardData.files[0]);
return;
}
// Intercept large text pastes before the browser commits them to the
// textarea — assigning multi-MB strings to a <textarea> freezes the page.
const text = e.clipboardData?.getData('text');
if (text && text.length > PASTE_BUFFER_THRESHOLD_BYTES) {
e.preventDefault();
loadIntoBuffer(text);
}
}
@@ -220,8 +290,7 @@ async function loadFileContents(file) {
return;
}
pasteArea.value = new TextDecoder().decode(content);
reevaluateContentStatus();
loadContent(new TextDecoder().decode(content));
}
function selectLogFile() {
@@ -355,8 +424,7 @@ async function handleDropEvent(e) {
let files = e.dataTransfer.files;
if (files.length !== 1) {
if (Array.from(e.dataTransfer.types).includes('text/plain')) {
pasteArea.value = e.dataTransfer.getData('text/plain');
reevaluateContentStatus();
loadContent(e.dataTransfer.getData('text/plain'));
}
return;
}

View File

@@ -1,11 +1,11 @@
<?php
use Aternos\Mclogs\Api\ApiRouter;
use Aternos\Mclogs\Config\Config;
use Aternos\Mclogs\Config\ConfigKey;
use Aternos\Mclogs\Frontend\FrontendRouter;
use Aternos\Mclogs\Storage\MongoDBClient;
use Aternos\Mclogs\Util\URL;
use IndifferentKetchup\Iblogs\Api\ApiRouter;
use IndifferentKetchup\Iblogs\Config\Config;
use IndifferentKetchup\Iblogs\Config\ConfigKey;
use IndifferentKetchup\Iblogs\Frontend\FrontendRouter;
use IndifferentKetchup\Iblogs\Storage\MongoDBClient;
use IndifferentKetchup\Iblogs\Util\URL;
require_once __DIR__ . '/vendor/autoload.php';