Enhance mobile menu styles and update progress handling in RequestManagement component
This commit is contained in:
@@ -29,10 +29,19 @@
|
||||
}
|
||||
|
||||
.mobile-menu-dropdown.open {
|
||||
max-height: 500px;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
padding-bottom: 0.75rem;
|
||||
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) {
|
||||
.mobile-menu-dropdown {
|
||||
display: none;
|
||||
|
||||
@@ -17,9 +17,9 @@ interface RequestJob {
|
||||
tracks: number;
|
||||
quality: string;
|
||||
status: string;
|
||||
progress: number;
|
||||
progress: number | string | null;
|
||||
type?: string;
|
||||
tarball_path?: string;
|
||||
tarball?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
[key: string]: unknown;
|
||||
@@ -40,9 +40,13 @@ export default function RequestManagement() {
|
||||
const pollingDetailRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
|
||||
const resolveTarballPath = (job: RequestJob) => job.tarball;
|
||||
|
||||
const tarballUrl = (absPath: string | undefined, quality: string) => {
|
||||
if (!absPath) return null;
|
||||
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}`;
|
||||
};
|
||||
|
||||
@@ -172,24 +176,21 @@ export default function RequestManagement() {
|
||||
|
||||
const formatProgress = (p: unknown) => {
|
||||
if (p === null || p === undefined || p === "") return "—";
|
||||
const num = Number(p);
|
||||
if (Number.isNaN(num)) return "—";
|
||||
const pct = num > 1 ? Math.round(num) : num;
|
||||
const pct = computePct(p);
|
||||
return `${pct}%`;
|
||||
};
|
||||
|
||||
const computePct = (p: unknown) => {
|
||||
if (p === null || p === undefined || p === "") return 0;
|
||||
const num = Number(p);
|
||||
if (Number.isNaN(num)) return 0;
|
||||
return Math.min(100, Math.max(0, num > 1 ? Math.round(num) : Math.round(num * 100)));
|
||||
if (!Number.isFinite(num)) return 0;
|
||||
const normalized = num > 1 ? num : num * 100;
|
||||
return Math.min(100, Math.max(0, Math.round(normalized)));
|
||||
};
|
||||
|
||||
const progressBarTemplate = (rowData: RequestJob) => {
|
||||
const p = rowData.progress;
|
||||
if (p === null || p === undefined || p === 0) return "—";
|
||||
const num = Number(p);
|
||||
if (Number.isNaN(num)) return "—";
|
||||
if (p === null || p === undefined || p === "") return "—";
|
||||
const pct = computePct(p);
|
||||
|
||||
const getProgressColor = () => {
|
||||
@@ -341,7 +342,7 @@ export default function RequestManagement() {
|
||||
</span>
|
||||
}
|
||||
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 "—";
|
||||
const encodedURL = encodeURI(url);
|
||||
|
||||
@@ -409,18 +410,25 @@ export default function RequestManagement() {
|
||||
<strong>Progress:</strong>
|
||||
<div className="rm-progress-container mt-2">
|
||||
<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
|
||||
className={`rm-progress-fill ${selectedRequest.status === "Failed" ? "bg-red-500" : selectedRequest.status === "Finished" ? "bg-green-500" : "bg-blue-500"}`}
|
||||
style={{
|
||||
['--rm-progress' as string]: (computePct(selectedRequest.progress) / 100).toString(),
|
||||
borderTopRightRadius: computePct(selectedRequest.progress) >= 100 ? '999px' : 0,
|
||||
borderBottomRightRadius: computePct(selectedRequest.progress) >= 100 ? '999px' : 0
|
||||
}}
|
||||
data-pct={computePct(selectedRequest.progress)}
|
||||
aria-valuenow={Math.min(100, Math.max(0, Number(selectedRequest.progress) > 1 ? Math.round(selectedRequest.progress) : selectedRequest.progress * 100))}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
className={`rm-progress-fill ${fillColor}`}
|
||||
style={{
|
||||
['--rm-progress' as string]: (pctDialog / 100).toString(),
|
||||
borderTopRightRadius: pctDialog >= 100 ? '999px' : 0,
|
||||
borderBottomRightRadius: pctDialog >= 100 ? '999px' : 0
|
||||
}}
|
||||
data-pct={pctDialog}
|
||||
aria-valuenow={pctDialog}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<span className="rm-progress-text">{formatProgress(selectedRequest.progress)}</span>
|
||||
</div>
|
||||
@@ -437,17 +445,17 @@ export default function RequestManagement() {
|
||||
|
||||
{/* --- Tarball Card --- */}
|
||||
{
|
||||
selectedRequest.tarball_path && (
|
||||
selectedRequest.tarball && (
|
||||
<div className="p-3 bg-gray-100 dark:bg-neutral-800 rounded-md">
|
||||
<p>
|
||||
<strong>Tarball:</strong>{" "}
|
||||
<a
|
||||
href={encodeURI(tarballUrl(selectedRequest.tarball_path, selectedRequest.quality) || "")}
|
||||
href={encodeURI(tarballUrl(resolveTarballPath(selectedRequest), selectedRequest.quality) || "")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
{tarballUrl(selectedRequest.tarball_path, selectedRequest.quality)?.split("/").pop()}
|
||||
{tarballUrl(resolveTarballPath(selectedRequest), selectedRequest.quality)?.split("/").pop()}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -27,9 +27,9 @@ interface ChannelRow {
|
||||
export async function GET({ request }: APIContext): Promise<Response> {
|
||||
// Rate limit check
|
||||
const rateCheck = checkRateLimit(request, {
|
||||
limit: 20,
|
||||
limit: 60,
|
||||
windowMs: 1000,
|
||||
burstLimit: 100,
|
||||
burstLimit: 240,
|
||||
burstWindowMs: 10_000,
|
||||
});
|
||||
|
||||
|
||||
@@ -143,9 +143,9 @@ async function getChannelVisibleMembers(channelId: string, guildId: string): Pro
|
||||
export async function GET({ request }) {
|
||||
// Rate limit check
|
||||
const rateCheck = checkRateLimit(request, {
|
||||
limit: 20,
|
||||
limit: 80,
|
||||
windowMs: 1000,
|
||||
burstLimit: 100,
|
||||
burstLimit: 320,
|
||||
burstWindowMs: 10_000,
|
||||
});
|
||||
|
||||
|
||||
@@ -99,9 +99,9 @@ function getSafeImageUrl(originalUrl: string | null, baseUrl: string): string |
|
||||
export async function GET({ request }: APIContext) {
|
||||
// Rate limit check
|
||||
const rateCheck = checkRateLimit(request, {
|
||||
limit: 30,
|
||||
limit: 100,
|
||||
windowMs: 1000,
|
||||
burstLimit: 150,
|
||||
burstLimit: 400,
|
||||
burstWindowMs: 10_000,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import Root from "@/components/AppLayout.jsx";
|
||||
const user = Astro.locals.user as any;
|
||||
---
|
||||
|
||||
<Base>
|
||||
<Base title="Lighting">
|
||||
<section class="page-section">
|
||||
<Root child="Lighting" user?={user} client:only="react" />
|
||||
</section>
|
||||
|
||||
@@ -9,7 +9,7 @@ const accessDenied = Astro.locals.accessDenied || false;
|
||||
const requiredRoles = Astro.locals.requiredRoles || [];
|
||||
|
||||
---
|
||||
<Base>
|
||||
<Base title="Login">
|
||||
<section class="page-section">
|
||||
<Root child="LoginPage" loggedIn={isLoggedIn} accessDenied={accessDenied} requiredRoles={requiredRoles} client:only="react" />
|
||||
</section>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Root from "../components/AppLayout.jsx";
|
||||
import "@styles/MemeGrid.css";
|
||||
---
|
||||
|
||||
<Base hideFooter>
|
||||
<Base hideFooter title="Memes">
|
||||
<section class="page-section">
|
||||
<Root child="Memes" client:only="react" />
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user