Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ npm i -g yarn
- [Billing](/billing) - Allows users to purchase servers via Stripe - **Proof of Concept - Do absolutely NOT use in production!**
- [Generic OIDC Providers](/generic-oidc-providers) - Create generic OIDC providers for authentication
- [Legal Pages](/legal-pages) - Adds legal pages (Imprint, Privacy Policy, ToS) to the panel
- [McLogCleaner](/mclogcleaner) - Delete old logs with ease
- [MCLogs Uploader](/mclogs-uploader) - Upload console logs to mclo.gs
- [Minecraft Modrinth](/minecraft-modrinth) - Download Minecraft mods & plugins from Modrinth
- [PasteFox Share](/pastefox-share) - Share console logs via pastefox.com
Expand Down
15 changes: 15 additions & 0 deletions mclogcleaner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## McLogCleaner
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## McLogCleaner
# McLogCleaner (by JuggleGaming)


McLogCleaner automatically deletes all `.log.gz` files from the server’s `logs` folder.

> **Note:** `latest.log` will always remain intact and is never deleted.

### Usage
To use this plugin, add `mclogcleaner` as a feature to the egg you want to run it with.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Usage
To use this plugin, add `mclogcleaner` as a feature to the egg you want to run it with.
## Setup
To use this plugin, add `mclogcleaner` as a _feature_ to the egg you want to run it with.


### Log Deletion Options
When you click **Delete logs**, a dropdown menu appears where you can choose the **minimum age (in days)** of log files to delete:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Log Deletion Options
When you click **Delete logs**, a dropdown menu appears where you can choose the **minimum age (in days)** of log files to delete:
## Log Deletion Options
When you click **Delete logs**, a dropdown menu appears where you can choose the **minimum age (in days)** of log files to delete:

- Logs older than 7 days
- Logs older than 30 days
- All logs (regardless of age)
- A custom age in days
5 changes: 5 additions & 0 deletions mclogcleaner/config/logcleaner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
// Config values for LogCleaner
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove empty config.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

];
15 changes: 15 additions & 0 deletions mclogcleaner/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "mclogcleaner",
"name": "McLogCleaner",
"author": "JuggleGaming",
"version": "1.1.1",
"description": "Clean your Minecraft-logs with ease",
"category": "plugin",
"url": "https://github.com/pelican-dev/plugins/tree/main/mclogcleaner",
"update_url": null,
"namespace": "JuggleGaming\\McLogCleaner",
"class": "McLogCleanerPlugin",
"panels": null,
"panel_version": null,
"composer_packages": null
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
28 changes: 28 additions & 0 deletions mclogcleaner/src/Enums/EggFeature.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace JuggleGaming\McLogCleaner\Enums;

use App\Models\Server;

enum EggFeature: string
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this enum exist?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first i wanted to add multiple games to this plugin but decided, just to do it for minecraft an then forgot about making it much easier.

{
case Check = 'mclogcleaner';

public static function fromServer(Server $server): ?self
{
$server->loadMissing('egg');

$features = $server->egg->features ?? [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential null dereference when $server->egg is null.

If a server has no associated egg, $server->egg returns null and $server->egg->features will throw a "property access on null" error in PHP 8+. The ?? operator does not prevent this — it only handles the case where the entire expression evaluates to null without error. Use the nullsafe operator instead.

🛡️ Proposed fix
-        $features = $server->egg->features ?? [];
+        $features = $server->egg?->features ?? [];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$server->loadMissing('egg');
$features = $server->egg->features ?? [];
$server->loadMissing('egg');
$features = $server->egg?->features ?? [];
🤖 Prompt for AI Agents
In `@mclogcleaner/src/Enums/EggFeature.php` around lines 13 - 15, The current
access of $server->egg->features can throw a null dereference if $server->egg is
null; update the access to use PHP's nullsafe operator so the expression becomes
$server->egg?->features ?? [] (keep the existing $server->loadMissing('egg')
call and assign to $features) to safely fallback to an empty array when no egg
is present; locate the code around the $server->loadMissing('egg') call and
replace the direct property access to egg->features with the nullsafe form.


if (in_array(self::Check->value, $features, true)) {
return self::Check;
}

return null;
}

public static function serverSupportsLogCleaner(Server $server): bool
{
return self::fromServer($server) !== null;
}
}
147 changes: 147 additions & 0 deletions mclogcleaner/src/Filament/Components/Actions/McLogCleanAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php
Comment thread
coderabbitai[bot] marked this conversation as resolved.

namespace JuggleGaming\McLogCleaner\Filament\Components\Actions;

use App\Models\Server;
use Carbon\Carbon;
use Exception;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Support\Enums\Size;
use Illuminate\Support\Facades\Http;
use JuggleGaming\McLogCleaner\Enums\EggFeature;

class McLogCleanAction extends Action
{
public static function getDefaultName(): ?string
{
return 'clean_logs';
}

protected function setUp(): void
{
parent::setUp();
$this->hidden(function () {
/** @var Server|null $server */
$server = Filament::getTenant();

return !EggFeature::serverSupportsLogCleaner($server);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

$this->label('Delete logs');
$this->icon('tabler-trash');
$this->color('danger');
$this->size(Size::ExtraLarge);
$this->requiresConfirmation()
->modalHeading('Delete logs')
->modalDescription('Choose which logs should be deleted.')
->modalSubmitActionLabel('Delete logs')
->form([
Select::make('mode')
->label('Delete logs')
->options([
7 => 'Older than 7 days',
30 => 'Older than 30 days',
-1 => 'Delete all logs',
'custom' => 'Custom (days)',
])
->default(7)
->required()
->reactive(),
TextInput::make('custom_days')
->label('Delete logs older than (days)')
->numeric()
->minValue(1)
->maxValue(365)
->placeholder('e.g. 14')
->required(fn ($get) => $get('mode') === 'custom')
->visible(fn ($get) => $get('mode') === 'custom'),
]);

$this->action(function (array $data) {
/** @var Server|null $server */
$server = Filament::getTenant();
$mode = $data['mode'];
if ($mode !== 'custom') {
$mode = (int) $mode;
}
if ($mode === 'custom') {
$days = max(1, (int) $data['custom_days']);
} elseif ($mode === -1) {
$days = 0;
} else {
$days = $mode;
}
try {
$files = Http::daemon($server->node)
->get("/api/servers/{$server->uuid}/files/list-directory", [
'directory' => 'logs',
])
->throw()
->json();
if (!is_array($files)) {
throw new Exception('Invalid log directory response.');
}
$threshold = now()->subDays($days)->startOfDay();
$logsToDelete = collect($files)
->filter(fn ($file) => str_ends_with($file['name'], '.log.gz'))
->filter(function ($file) use ($days, $threshold) {
if ($days === 0) {
return true;
}
$logDate = $this->extractLogDate($file['name']);
if (!$logDate) {
return false;
}

return $logDate->lessThan($threshold);
})
->pluck('name')
->map(fn ($name) => 'logs/' . $name)
->values()
->all();
if (empty($logsToDelete)) {
Notification::make()
->title('McLogCleaner')
->body('No logs matching your selection were found.')
->success()
->send();

return;
}
Http::daemon($server->node)
->post("/api/servers/{$server->uuid}/files/delete", [
'root' => '/',
'files' => $logsToDelete,
])
->throw();
Notification::make()
->title('Logfolder cleaned')
->body(count($logsToDelete) . ' files were deleted.')
->success()
->send();
} catch (\Throwable $e) {
report($e);
Notification::make()
->title('Cleanup failed.')
->body('An error occurred while deleting log files. Please try again later.')
->danger()
->send();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
}

private function extractLogDate(string $filename): ?Carbon
{
if (preg_match('/(\d{4}-\d{2}-\d{2})/', $filename, $matches)) {
$date = Carbon::createFromFormat('Y-m-d', $matches[1]);

return $date ? $date->startOfDay() : null;
}

return null;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
24 changes: 24 additions & 0 deletions mclogcleaner/src/McLogCleanerPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace JuggleGaming\McLogCleaner;

use Filament\Contracts\Plugin;
use Filament\Panel;

class McLogCleanerPlugin implements Plugin
{
public function getId(): string
{
return 'mclogcleaner';
}

public function register(Panel $panel): void
{
//
}

public function boot(Panel $panel): void
{
//
}
}
21 changes: 21 additions & 0 deletions mclogcleaner/src/Providers/McLogCleanerPluginProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace JuggleGaming\McLogCleaner\Providers;

use App\Enums\HeaderActionPosition;
use App\Filament\Server\Pages\Console;
use Illuminate\Support\ServiceProvider;
use JuggleGaming\McLogCleaner\Filament\Components\Actions\McLogCleanAction;

class McLogCleanerPluginProvider extends ServiceProvider
{
public function register(): void
{
Console::registerCustomHeaderActions(HeaderActionPosition::Before, McLogCleanAction::make());
}

public function boot(): void
{
//
}
}