dev #3

Merged
codey merged 3 commits from dev into master 2025-12-24 07:50:59 -05:00
8 changed files with 52 additions and 35 deletions
Showing only changes of commit bb71f662ed - Show all commits

View File

@@ -29,10 +29,19 @@
} }
.mobile-menu-dropdown.open { .mobile-menu-dropdown.open {
max-height: 500px; max-height: none;
overflow: visible;
padding-bottom: 0.75rem;
opacity: 1; opacity: 1;
} }
.mobile-menu-dropdown a {
font-size: 0.95rem;
line-height: 1.25rem;
padding: 0.6rem 0.75rem;
border-radius: 12px;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.mobile-menu-dropdown { .mobile-menu-dropdown {
display: none; display: none;

View File

@@ -17,9 +17,9 @@ interface RequestJob {
tracks: number; tracks: number;
quality: string; quality: string;
status: string; status: string;
progress: number; progress: number | string | null;
type?: string; type?: string;
tarball_path?: string; tarball?: string;
created_at?: string; created_at?: string;
updated_at?: string; updated_at?: string;
[key: string]: unknown; [key: string]: unknown;
@@ -40,9 +40,13 @@ export default function RequestManagement() {
const pollingDetailRef = useRef<ReturnType<typeof setInterval> | null>(null); const pollingDetailRef = useRef<ReturnType<typeof setInterval> | null>(null);
const resolveTarballPath = (job: RequestJob) => job.tarball;
const tarballUrl = (absPath: string | undefined, quality: string) => { const tarballUrl = (absPath: string | undefined, quality: string) => {
if (!absPath) return null; if (!absPath) return null;
const filename = absPath.split("/").pop(); // get "SOMETHING.tar.gz" const filename = absPath.split("/").pop(); // get "SOMETHING.tar.gz"
// If the backend already stores a fully qualified URL, return as-is
if (/^https?:\/\//i.test(absPath)) return absPath;
return `${TAR_BASE_URL}/${quality}/${filename}`; return `${TAR_BASE_URL}/${quality}/${filename}`;
}; };
@@ -172,24 +176,21 @@ export default function RequestManagement() {
const formatProgress = (p: unknown) => { const formatProgress = (p: unknown) => {
if (p === null || p === undefined || p === "") return "—"; if (p === null || p === undefined || p === "") return "—";
const num = Number(p); const pct = computePct(p);
if (Number.isNaN(num)) return "—";
const pct = num > 1 ? Math.round(num) : num;
return `${pct}%`; return `${pct}%`;
}; };
const computePct = (p: unknown) => { const computePct = (p: unknown) => {
if (p === null || p === undefined || p === "") return 0; if (p === null || p === undefined || p === "") return 0;
const num = Number(p); const num = Number(p);
if (Number.isNaN(num)) return 0; if (!Number.isFinite(num)) return 0;
return Math.min(100, Math.max(0, num > 1 ? Math.round(num) : Math.round(num * 100))); const normalized = num > 1 ? num : num * 100;
return Math.min(100, Math.max(0, Math.round(normalized)));
}; };
const progressBarTemplate = (rowData: RequestJob) => { const progressBarTemplate = (rowData: RequestJob) => {
const p = rowData.progress; const p = rowData.progress;
if (p === null || p === undefined || p === 0) return "—"; if (p === null || p === undefined || p === "") return "—";
const num = Number(p);
if (Number.isNaN(num)) return "—";
const pct = computePct(p); const pct = computePct(p);
const getProgressColor = () => { const getProgressColor = () => {
@@ -341,7 +342,7 @@ export default function RequestManagement() {
</span> </span>
} }
body={(row: RequestJob) => { body={(row: RequestJob) => {
const url = tarballUrl(row.tarball_path, row.quality || "FLAC"); const url = tarballUrl(resolveTarballPath(row as RequestJob), row.quality || "FLAC");
if (!url) return "—"; if (!url) return "—";
const encodedURL = encodeURI(url); const encodedURL = encodeURI(url);
@@ -409,18 +410,25 @@ export default function RequestManagement() {
<strong>Progress:</strong> <strong>Progress:</strong>
<div className="rm-progress-container mt-2"> <div className="rm-progress-container mt-2">
<div className="rm-progress-track rm-progress-track-lg"> <div className="rm-progress-track rm-progress-track-lg">
{(() => {
const pctDialog = computePct(selectedRequest.progress);
const status = selectedRequest.status;
const fillColor = status === "Failed" ? "bg-red-500" : status === "Finished" ? "bg-green-500" : "bg-blue-500";
return (
<div <div
className={`rm-progress-fill ${selectedRequest.status === "Failed" ? "bg-red-500" : selectedRequest.status === "Finished" ? "bg-green-500" : "bg-blue-500"}`} className={`rm-progress-fill ${fillColor}`}
style={{ style={{
['--rm-progress' as string]: (computePct(selectedRequest.progress) / 100).toString(), ['--rm-progress' as string]: (pctDialog / 100).toString(),
borderTopRightRadius: computePct(selectedRequest.progress) >= 100 ? '999px' : 0, borderTopRightRadius: pctDialog >= 100 ? '999px' : 0,
borderBottomRightRadius: computePct(selectedRequest.progress) >= 100 ? '999px' : 0 borderBottomRightRadius: pctDialog >= 100 ? '999px' : 0
}} }}
data-pct={computePct(selectedRequest.progress)} data-pct={pctDialog}
aria-valuenow={Math.min(100, Math.max(0, Number(selectedRequest.progress) > 1 ? Math.round(selectedRequest.progress) : selectedRequest.progress * 100))} aria-valuenow={pctDialog}
aria-valuemin={0} aria-valuemin={0}
aria-valuemax={100} aria-valuemax={100}
/> />
);
})()}
</div> </div>
<span className="rm-progress-text">{formatProgress(selectedRequest.progress)}</span> <span className="rm-progress-text">{formatProgress(selectedRequest.progress)}</span>
</div> </div>
@@ -437,17 +445,17 @@ export default function RequestManagement() {
{/* --- Tarball Card --- */} {/* --- Tarball Card --- */}
{ {
selectedRequest.tarball_path && ( selectedRequest.tarball && (
<div className="p-3 bg-gray-100 dark:bg-neutral-800 rounded-md"> <div className="p-3 bg-gray-100 dark:bg-neutral-800 rounded-md">
<p> <p>
<strong>Tarball:</strong>{" "} <strong>Tarball:</strong>{" "}
<a <a
href={encodeURI(tarballUrl(selectedRequest.tarball_path, selectedRequest.quality) || "")} href={encodeURI(tarballUrl(resolveTarballPath(selectedRequest), selectedRequest.quality) || "")}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-blue-500 hover:underline" className="text-blue-500 hover:underline"
> >
{tarballUrl(selectedRequest.tarball_path, selectedRequest.quality)?.split("/").pop()} {tarballUrl(resolveTarballPath(selectedRequest), selectedRequest.quality)?.split("/").pop()}
</a> </a>
</p> </p>
</div> </div>

View File

@@ -27,9 +27,9 @@ interface ChannelRow {
export async function GET({ request }: APIContext): Promise<Response> { export async function GET({ request }: APIContext): Promise<Response> {
// Rate limit check // Rate limit check
const rateCheck = checkRateLimit(request, { const rateCheck = checkRateLimit(request, {
limit: 20, limit: 60,
windowMs: 1000, windowMs: 1000,
burstLimit: 100, burstLimit: 240,
burstWindowMs: 10_000, burstWindowMs: 10_000,
}); });

View File

@@ -143,9 +143,9 @@ async function getChannelVisibleMembers(channelId: string, guildId: string): Pro
export async function GET({ request }) { export async function GET({ request }) {
// Rate limit check // Rate limit check
const rateCheck = checkRateLimit(request, { const rateCheck = checkRateLimit(request, {
limit: 20, limit: 80,
windowMs: 1000, windowMs: 1000,
burstLimit: 100, burstLimit: 320,
burstWindowMs: 10_000, burstWindowMs: 10_000,
}); });

View File

@@ -99,9 +99,9 @@ function getSafeImageUrl(originalUrl: string | null, baseUrl: string): string |
export async function GET({ request }: APIContext) { export async function GET({ request }: APIContext) {
// Rate limit check // Rate limit check
const rateCheck = checkRateLimit(request, { const rateCheck = checkRateLimit(request, {
limit: 30, limit: 100,
windowMs: 1000, windowMs: 1000,
burstLimit: 150, burstLimit: 400,
burstWindowMs: 10_000, burstWindowMs: 10_000,
}); });

View File

@@ -7,7 +7,7 @@ import Root from "@/components/AppLayout.jsx";
const user = Astro.locals.user as any; const user = Astro.locals.user as any;
--- ---
<Base> <Base title="Lighting">
<section class="page-section"> <section class="page-section">
<Root child="Lighting" user?={user} client:only="react" /> <Root child="Lighting" user?={user} client:only="react" />
</section> </section>

View File

@@ -9,7 +9,7 @@ const accessDenied = Astro.locals.accessDenied || false;
const requiredRoles = Astro.locals.requiredRoles || []; const requiredRoles = Astro.locals.requiredRoles || [];
--- ---
<Base> <Base title="Login">
<section class="page-section"> <section class="page-section">
<Root child="LoginPage" loggedIn={isLoggedIn} accessDenied={accessDenied} requiredRoles={requiredRoles} client:only="react" /> <Root child="LoginPage" loggedIn={isLoggedIn} accessDenied={accessDenied} requiredRoles={requiredRoles} client:only="react" />
</section> </section>

View File

@@ -4,7 +4,7 @@ import Root from "../components/AppLayout.jsx";
import "@styles/MemeGrid.css"; import "@styles/MemeGrid.css";
--- ---
<Base hideFooter> <Base hideFooter title="Memes">
<section class="page-section"> <section class="page-section">
<Root child="Memes" client:only="react" /> <Root child="Memes" client:only="react" />
</section> </section>