Initial commit
This commit is contained in:
commit
78f8d225ee
21173 changed files with 2907774 additions and 0 deletions
BIN
app/files/__pycache__/main.cpython-313.pyc
Normal file
BIN
app/files/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
17
app/files/layout.tsx
Normal file
17
app/files/layout.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { Suspense } from "react";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<div className="p-4">
|
||||
<a
|
||||
href="https://makearmy.io"
|
||||
className="inline-block mb-4 px-4 py-2 bg-accent text-background rounded-md text-sm"
|
||||
>
|
||||
← Back to Main Menu
|
||||
</a>
|
||||
</div>
|
||||
{children}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
44
app/files/main.py
Normal file
44
app/files/main.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
from fastapi import FastAPI, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
import os
|
||||
|
||||
app = FastAPI()
|
||||
FILES_ROOT = Path("/var/www/lasereverything.net.db/files").resolve()
|
||||
|
||||
@app.get("/list-files")
|
||||
def list_files(
|
||||
path: str = "",
|
||||
offset: int = 0,
|
||||
limit: int = 50
|
||||
):
|
||||
base_path = (FILES_ROOT / path).resolve()
|
||||
|
||||
if not base_path.is_dir() or not str(base_path).startswith(str(FILES_ROOT)):
|
||||
raise HTTPException(status_code=400, detail="Invalid path")
|
||||
|
||||
entries = []
|
||||
for item in base_path.iterdir():
|
||||
if item.is_dir():
|
||||
entries.append(f"{item.name}/")
|
||||
else:
|
||||
entries.append(item.name)
|
||||
|
||||
entries.sort()
|
||||
paginated = entries[offset:offset + limit]
|
||||
|
||||
return {
|
||||
"total": len(entries),
|
||||
"items": paginated
|
||||
}
|
||||
|
||||
@app.get("/download/{file_path:path}")
|
||||
def download_file(file_path: str):
|
||||
full_path = (FILES_ROOT / file_path).resolve()
|
||||
|
||||
if not full_path.is_file() or not str(full_path).startswith(str(FILES_ROOT)):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
return FileResponse(full_path, filename=full_path.name)
|
||||
|
||||
135
app/files/page.tsx
Normal file
135
app/files/page.tsx
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// /var/www/makearmy.io/app/app/files/page.tsx
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
|
||||
type FileItem = {
|
||||
name: string;
|
||||
isDir: boolean;
|
||||
size: number;
|
||||
mtime: number;
|
||||
};
|
||||
|
||||
export default function FilesPage() {
|
||||
const search = useSearchParams();
|
||||
const router = useRouter();
|
||||
const path = useMemo(() => search.get("path") || "/", [search]);
|
||||
|
||||
const [items, setItems] = useState<FileItem[] | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/files/list?path=${encodeURIComponent(path)}`
|
||||
);
|
||||
if (!res.ok) {
|
||||
if (!cancelled) setError(`HTTP ${res.status}`);
|
||||
return;
|
||||
}
|
||||
const json = await res.json();
|
||||
if (!cancelled) setItems(json.items || []);
|
||||
} catch (e: any) {
|
||||
if (!cancelled) setError(e?.message || String(e));
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
}
|
||||
load();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [path]);
|
||||
|
||||
const upPath = useMemo(() => {
|
||||
if (path === "/") return null;
|
||||
const parts = path.replace(/\/+$/, "").split("/").filter(Boolean);
|
||||
parts.pop();
|
||||
return "/" + parts.join("/");
|
||||
}, [path]);
|
||||
|
||||
return (
|
||||
<div className="p-6 text-sm">
|
||||
|
||||
<div className="mb-3">
|
||||
<span className="opacity-70 mr-1">Path:</span>
|
||||
<code>{path}</code>
|
||||
{upPath && (
|
||||
<>
|
||||
<span className="mx-2 opacity-50">•</span>
|
||||
<Link href={`/files?path=${encodeURIComponent(upPath)}`}>
|
||||
Up one level
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{loading && <div>Loading…</div>}
|
||||
{error && (
|
||||
<div className="bg-red-900/60 text-red-200 p-3 rounded border border-red-800">
|
||||
Error loading files: {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && items && (
|
||||
<table className="w-full text-left mt-3 border-collapse">
|
||||
<thead className="opacity-70">
|
||||
<tr>
|
||||
<th className="py-2 pr-4">Name</th>
|
||||
<th className="py-2 pr-4">Type</th>
|
||||
<th className="py-2 pr-4">Size</th>
|
||||
<th className="py-2 pr-4">Modified</th>
|
||||
<th className="py-2 pr-4"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((it) => {
|
||||
const href = it.isDir
|
||||
? `/files?path=${encodeURIComponent(
|
||||
(path.endsWith("/") ? path : path + "/") + it.name
|
||||
)}`
|
||||
: `/api/files/raw?path=${encodeURIComponent(
|
||||
(path.endsWith("/") ? path : path + "/") + it.name
|
||||
)}`;
|
||||
const dl = it.isDir
|
||||
? null
|
||||
: `/api/files/download?path=${encodeURIComponent(
|
||||
(path.endsWith("/") ? path : path + "/") + it.name
|
||||
)}`;
|
||||
|
||||
return (
|
||||
<tr key={it.name} className="border-t border-white/10">
|
||||
<td className="py-2 pr-4">
|
||||
<Link href={href}>{it.name}</Link>
|
||||
</td>
|
||||
<td className="py-2 pr-4">{it.isDir ? "Dir" : "File"}</td>
|
||||
<td className="py-2 pr-4">
|
||||
{it.isDir ? "-" : `${it.size.toLocaleString()} B`}
|
||||
</td>
|
||||
<td className="py-2 pr-4">
|
||||
{new Date(it.mtime).toLocaleString()}
|
||||
</td>
|
||||
<td className="py-2 pr-4">
|
||||
{!it.isDir && dl && (
|
||||
<a href={dl} className="underline">
|
||||
Download
|
||||
</a>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue