- Front controller and UI in [public/index.php](/Users/tyemeclifford/Documents/GH/CloudflareR2-Manager/public/index.php) and [views/dashboard.php](/Users/tyemeclifford/Documents/GH/CloudflareR2-Manager/views/dashboard.php)

R2 S3 Signature V4 client in 
[src/R2/R2Client.php](/Users/tyemeclifford/Documents/GH/CloudflareR2-Manager/src/R2/R2Client.php)
SQLite mirror, usage ledger, settings, local tags, permission labels, 
and signed URL history under 
[src/Repository](/Users/tyemeclifford/Documents/GH/CloudflareR2-Manager/src/Repository)
Simple password auth and CSRF protection
Uploads, folders, sync, copy/move/delete, downloads, presigned URLs, 
CORS JSON management, and usage/billing-style summaries
Setup docs in 
[README.md](/Users/tyemeclifford/Documents/GH/CloudflareR2-Manager/README.md) 
and config template in 
[.env.example](/Users/tyemeclifford/Documents/GH/CloudflareR2-Manager/.env.example)
Important R2 caveat: Cloudflare’s current R2 S3 compatibility docs say 
ACLs and object-tagging APIs are not implemented, so the app stores tags 
and permission labels locally in SQLite while keeping file bytes on R2. 
I used Cloudflare’s official docs for S3 compatibility, presigned URLs, 
and CORS.
This commit is contained in:
Ty Clifford
2026-06-20 13:57:21 -04:00
parent 1c7f697913
commit 3518555d1e
29 changed files with 3598 additions and 1 deletions
+75
View File
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
use R2Manager\Repository\ObjectRepository;
use R2Manager\Repository\SettingsRepository;
use R2Manager\Repository\UsageRepository;
use R2Manager\Support\Config;
use R2Manager\Support\Database;
use R2Manager\Support\View;
$_SERVER['HTTPS'] = 'off';
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SESSION = ['authenticated' => true];
require dirname(__DIR__) . '/src/bootstrap.php';
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__DIR__) . '/src'));
foreach ($iterator as $file) {
if ($file instanceof SplFileInfo && $file->isFile() && $file->getExtension() === 'php' && $file->getFilename() !== 'bootstrap.php') {
require_once $file->getPathname();
}
}
$config = new Config(dirname(__DIR__));
$database = new Database($config);
new ObjectRepository($database);
new UsageRepository($database);
new SettingsRepository($database);
ob_start();
View::render('login', [
'appName' => 'R2 Manager',
'csrf' => 'token',
'flash' => [],
]);
View::render('dashboard', [
'appName' => 'R2 Manager',
'bucket' => 'example-bucket',
'configured' => false,
'missingSettings' => ['R2_ACCOUNT_ID'],
'prefix' => '',
'breadcrumbs' => [['label' => 'Bucket root', 'prefix' => '']],
'search' => '',
'tagFilter' => '',
'tagNames' => [],
'folders' => [],
'objects' => [],
'stats' => ['objects' => 0, 'folders' => 0, 'bytes' => 0, 'latest_sync' => null],
'eventSummary' => [],
'billingSummary' => [
'storage_bytes' => 0,
'class_a_requests' => 0,
'class_b_requests' => 0,
'local_requests' => 0,
'uploaded_bytes' => 0,
'downloaded_bytes' => 0,
],
'dailySummary' => [],
'recentShares' => [],
'shareCount' => 0,
'selected' => null,
'selectedPublicUrl' => null,
'corsJson' => '[]',
'csrf' => 'token',
'flash' => [],
]);
$html = ob_get_clean();
if (!is_string($html) || !str_contains($html, 'Storage manager') || !str_contains($html, 'Objects')) {
throw new RuntimeException('Expected smoke-render content was not produced.');
}
echo "Smoke OK\n";