--- import AdminLayout from "@/layouts/AdminLayout.astro"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { HardDrive, Cloud, FolderSync, CheckCircle, XCircle, Info, Save } from "lucide-react"; const user = Astro.locals.user; // Only admins can access if (!user?.isAdmin) { return Astro.redirect('/'); } // Current storage config from env // NOTE: only `local` and `s3` storage backends are supported. The // rclone-as-adapter option was removed in favour of S3-compatible // object storage and a separate rclone backup utility // (see docs/administration/storage-adapters.md). const currentConfig = { type: process.env.STORAGE_TYPE || 'local', basePath: process.env.STORAGE_PATH || './data/storage', bucket: process.env.STORAGE_BUCKET || '', region: process.env.STORAGE_REGION || '', endpoint: process.env.STORAGE_ENDPOINT || '', }; const storageTypes = [ { value: 'local', label: 'Local Filesystem', icon: HardDrive, description: 'Store files on the local server disk', fields: ['basePath'] }, { value: 's3', label: 'S3 / MinIO / R2 / Garage / SeaweedFS / Ceph RGW', icon: Cloud, description: 'Any S3-compatible object store (AWS S3, MinIO, Cloudflare R2, Garage, SeaweedFS, Ceph RGW, Wasabi, Backblaze B2)', fields: ['bucket', 'region', 'endpoint', 'accessKeyId', 'secretAccessKey'] }, ]; ---

Storage Settings

Configure where repository data and artifacts are stored

← Back to Dashboard
Connection Test
Storage Backend Choose where to store repository data.
{storageTypes.map((type) => { const Icon = type.icon; return ( ); })}
/* Fetch initial config on load */ async function loadConfig() { try { const res = await fetch('/api/admin/config/storage'); if (res.ok) { const config = await res.json(); // Set type const radio = document.querySelector(`input[name="storageType"][value="${config.type}"]`); if (radio) { radio.checked = true; radio.dispatchEvent(new Event('change')); } // Fill fields document.querySelector('[name="basePath"]').value = config.basePath || ''; document.querySelector('[name="bucket"]').value = config.bucket || ''; document.querySelector('[name="region"]').value = config.region || ''; document.querySelector('[name="endpoint"]').value = config.endpoint || ''; document.querySelector('[name="rcloneRemote"]').value = config.rcloneRemote || ''; // Secrets are usually hidden, maybe leave empty or placeholder } } catch (e) { console.error("Failed to load config", e); } } // Call on load loadConfig(); // Toggle config panels document.querySelectorAll('input[name="storageType"]').forEach(radio => { radio.addEventListener('change', (e) => { const value = e.target.value; document.querySelectorAll('[id^="config-"]').forEach(el => el.classList.add('hidden')); document.getElementById(`config-${value}`)?.classList.remove('hidden'); }); }); // Test connection document.getElementById('test-connection')?.addEventListener('click', async () => { const statusText = document.getElementById('status-text'); statusText.textContent = 'Testing...'; statusText.className = 'text-sm text-muted-foreground'; const form = document.getElementById('storage-form'); const formData = new FormData(form); const config = Object.fromEntries(formData.entries()); // Config mapping const type = formData.get('storageType'); const cleanConfig = { ...config, type }; try { const response = await fetch('/api/admin/storage/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cleanConfig) }); const data = await response.json(); if (data.success) { statusText.textContent = '✓ Connection successful'; statusText.className = 'text-sm text-green-600'; } else { statusText.textContent = `✗ ${data.error || 'Connection failed'}`; statusText.className = 'text-sm text-red-600'; } } catch (e) { statusText.textContent = '✗ Failed to test connection'; statusText.className = 'text-sm text-red-600'; } }); // Save Configuration document.getElementById('storage-form')?.addEventListener('submit', async (e) => { e.preventDefault(); if (!confirm("Changing storage settings may make existing files inaccessible until migrated. Continue?")) return; const btn = document.getElementById('save-btn'); const originalText = btn.textContent; btn.textContent = 'Saving...'; btn.disabled = true; const formData = new FormData(e.target); const config = Object.fromEntries(formData.entries()); // Fix structure const type = formData.get('storageType'); const finalConfig = { type, basePath: formData.get('basePath'), // Shared or specific? // Specifics bucket: formData.get('bucket'), region: formData.get('region'), endpoint: formData.get('endpoint'), accessKeyId: formData.get('accessKeyId'), secretAccessKey: formData.get('secretAccessKey'), rcloneRemote: formData.get('rcloneRemote'), }; try { const res = await fetch('/api/admin/config/storage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(finalConfig) }); if (res.ok) { alert("Storage configuration saved successfully."); window.location.reload(); } else { const txt = await res.text(); alert("Failed to save: " + txt); } } catch (err) { console.error(err); alert("Error saving configuration"); } finally { btn.textContent = originalText; btn.disabled = false; } }); // Copy env (Keeping logic but Element might be removed, safe check) document.getElementById('copy-env')?.addEventListener('click', () => { const envText = document.getElementById('env-output')?.textContent || ''; navigator.clipboard.writeText(envText.trim()); const btn = document.getElementById('copy-env'); if (btn) { const original = btn.textContent; btn.textContent = 'Copied!'; setTimeout(() => btn.textContent = original, 2000); } });