Moderators block a file network-wide by its sha256, from a product's files or the console's new Blocks tab, which first shows the products using it. Blocking removes those products (their creators are told and may appeal), puts a `block` change in the changes feed of every catalog hosting it, and refuses uploads with it; moderators see who tried. Unblocking lets it in again; the products it removed stay removed until restored. Co-Authored-By: Claude Opus 5.5 <[email protected]>
89 lines
3.6 KiB
TypeScript
89 lines
3.6 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import { notFound, redirect } from "next/navigation";
|
|
import { PageHeader } from "@/components/status";
|
|
import { DateTime } from "@/components/time";
|
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
import { api, unwrap } from "@/lib/api";
|
|
import { ConsoleNav, requireConsole } from "../console-nav";
|
|
|
|
export const metadata: Metadata = { title: "Blocks" };
|
|
|
|
export default async function BlocksPage({ searchParams }: { searchParams: Promise<Record<string, string | string[]>> }) {
|
|
const { console: c } = await requireConsole("/console/blocks");
|
|
if (!c.sections.includes("blocks")) notFound();
|
|
const params = await searchParams;
|
|
// Looking a file up goes to its page.
|
|
if (typeof params.sha256 === "string") {
|
|
const sha256 = params.sha256.trim().toLowerCase();
|
|
if (/^[0-9a-f]{64}$/.test(sha256)) redirect(`/console/blocks/${sha256}`);
|
|
}
|
|
const before = typeof params.before === "string" && !Number.isNaN(Date.parse(params.before)) ? params.before : undefined;
|
|
const data = unwrap(await api.blocks.get({ query: before ? { before } : {} }));
|
|
|
|
return (
|
|
<>
|
|
<PageHeader title="Blocks">Blocked files: uploads with them are refused, and catalogs delete them.</PageHeader>
|
|
<ConsoleNav />
|
|
<form action="/console/blocks" className="mb-6 flex flex-wrap gap-2">
|
|
<Input
|
|
name="sha256"
|
|
required
|
|
pattern="\s*[0-9a-fA-F]{64}\s*"
|
|
defaultValue={typeof params.sha256 === "string" ? params.sha256 : ""}
|
|
placeholder="A file's sha256"
|
|
aria-label="sha256"
|
|
className="max-w-xl font-mono"
|
|
/>
|
|
<Button type="submit">Look up</Button>
|
|
</form>
|
|
<Card>
|
|
<CardContent>
|
|
{data.blocks.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground">
|
|
Nothing blocked. Block a file from a product's files, or look it up above.
|
|
</p>
|
|
) : (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>File</TableHead>
|
|
<TableHead>Reason</TableHead>
|
|
<TableHead>By</TableHead>
|
|
<TableHead>When</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data.blocks.map((b) => (
|
|
<TableRow key={b.sha256}>
|
|
<TableCell className="font-mono text-xs">
|
|
<Link href={`/console/blocks/${b.sha256}`} className="underline-offset-4 hover:underline">
|
|
{b.sha256.slice(0, 16)}…
|
|
</Link>
|
|
</TableCell>
|
|
<TableCell className="whitespace-normal">{b.reason}</TableCell>
|
|
<TableCell className="text-muted-foreground">{b.by ?? ""}</TableCell>
|
|
<TableCell className="text-muted-foreground">
|
|
<DateTime iso={b.at} />
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
{data.before && (
|
|
<div className="mt-4">
|
|
<Link href={`/console/blocks?before=${encodeURIComponent(data.before)}`} className={buttonVariants({ variant: "outline" })}>
|
|
Older
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|