forked from administration/panel
feat: flesh out inspector
parent
2b296330c9
commit
b7a3a61717
|
@ -0,0 +1,26 @@
|
|||
import { UserCard } from "@/components/cards/UserCard";
|
||||
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
|
||||
import { fetchUserById } from "@/lib/db";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function User({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string; type: string };
|
||||
}) {
|
||||
const user = await fetchUserById(params.id);
|
||||
// if (!user) return notFound();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<NavigationToolbar>Inspecting Account</NavigationToolbar>
|
||||
{user && <UserCard user={user} subtitle="Associated User" />}
|
||||
update password, reset 2FA, disable / undisable (disabled if pending
|
||||
delete), delete / cancel delete
|
||||
<br />
|
||||
list active 2FA methods without keys
|
||||
<br />
|
||||
email account
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { ChannelCard } from "@/components/cards/ChannelCard";
|
||||
import { JsonCard } from "@/components/cards/JsonCard";
|
||||
import { ServerCard } from "@/components/cards/ServerCard";
|
||||
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
|
||||
import { fetchChannelById, fetchServerById } from "@/lib/db";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function Message({ params }: { params: { id: string } }) {
|
||||
const channel = await fetchChannelById(params.id);
|
||||
if (!channel) return notFound();
|
||||
|
||||
const server =
|
||||
channel.channel_type === "TextChannel"
|
||||
? await fetchServerById(channel.server)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<NavigationToolbar>Inspecting Channel</NavigationToolbar>
|
||||
<ChannelCard channel={channel!} subtitle="Channel" />
|
||||
{server && <ServerCard server={server} subtitle="Server" />}
|
||||
<JsonCard obj={channel} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import { ChannelCard } from "@/components/cards/ChannelCard";
|
||||
import { JsonCard } from "@/components/cards/JsonCard";
|
||||
import { UserCard } from "@/components/cards/UserCard";
|
||||
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
|
||||
import { Card, CardHeader } from "@/components/ui/card";
|
||||
import { fetchChannelById, fetchMessageById, fetchUserById } from "@/lib/db";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function Message({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string; type: string };
|
||||
}) {
|
||||
const message = await fetchMessageById(params.id);
|
||||
if (!message) return notFound();
|
||||
|
||||
const author = await fetchUserById(message.author);
|
||||
const channel = await fetchChannelById(message.channel);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<NavigationToolbar>Inspecting Message</NavigationToolbar>
|
||||
|
||||
<Card className="shadow-none">
|
||||
<CardHeader>
|
||||
<p>{message.content}</p>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<Link href={`/panel/inspect/user/${author!._id}`}>
|
||||
<UserCard user={author!} subtitle="Message Author" />
|
||||
</Link>
|
||||
<Link href={`/panel/inspect/channel/${channel!._id}`}>
|
||||
<ChannelCard channel={channel!} subtitle="Channel" />
|
||||
</Link>
|
||||
<JsonCard obj={message} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Inspect() {
|
||||
const [id, setId] = useState("");
|
||||
const router = useRouter();
|
||||
|
||||
const createHandler = (type: string) => () =>
|
||||
router.push(`/panel/inspect/${type}/${id}`);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Input
|
||||
placeholder="Enter an ID..."
|
||||
value={id}
|
||||
onChange={(e) => setId(e.currentTarget.value)}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
onClick={createHandler("user")}
|
||||
>
|
||||
User
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
onClick={createHandler("server")}
|
||||
>
|
||||
Server
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
onClick={createHandler("channel")}
|
||||
>
|
||||
Channel
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
fetch("https://api.revolt.chat/invites/" + id)
|
||||
.then((res) => res.json())
|
||||
.then((invite) =>
|
||||
router.push(`/panel/inspect/server/${invite.server_id}`)
|
||||
)
|
||||
}
|
||||
>
|
||||
Invite
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
onClick={createHandler("message")}
|
||||
>
|
||||
Message
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { JsonCard } from "@/components/cards/JsonCard";
|
||||
import { ServerCard } from "@/components/cards/ServerCard";
|
||||
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
|
||||
import { fetchServerById } from "@/lib/db";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function Server({ params }: { params: { id: string } }) {
|
||||
const server = await fetchServerById(params.id);
|
||||
if (!server) return notFound();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<NavigationToolbar>Inspecting Server</NavigationToolbar>
|
||||
<ServerCard server={server} subtitle="Server" />
|
||||
<JsonCard obj={server} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
import { JsonCard } from "@/components/cards/JsonCard";
|
||||
import { UserCard } from "@/components/cards/UserCard";
|
||||
import { NavigationToolbar } from "@/components/common/NavigationToolbar";
|
||||
import { RelevantModerationNotices } from "@/components/inspector/RelevantModerationNotices";
|
||||
import { RelevantObjects } from "@/components/inspector/RelevantObjects";
|
||||
import { RelevantReports } from "@/components/inspector/RelevantReports";
|
||||
import { UserActions } from "@/components/inspector/UserActions";
|
||||
import { Card, CardHeader } from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { PLATFORM_MOD_ID } from "@/lib/constants";
|
||||
import {
|
||||
fetchBotsByUser,
|
||||
fetchMembershipsByUser,
|
||||
fetchMessages,
|
||||
fetchReports,
|
||||
fetchServers,
|
||||
fetchSnapshots,
|
||||
fetchStrikesByUser,
|
||||
fetchUserById,
|
||||
fetchUsersById,
|
||||
findDM,
|
||||
} from "@/lib/db";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function User({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string; type: string };
|
||||
}) {
|
||||
const user = await fetchUserById(params.id);
|
||||
if (!user) return notFound();
|
||||
|
||||
const botOwner = user.bot ? await fetchUserById(user.bot.owner) : undefined;
|
||||
|
||||
// Fetch strikes
|
||||
const strikes = await fetchStrikesByUser(user._id);
|
||||
|
||||
// Fetch moderation alerts
|
||||
const dm = await findDM(PLATFORM_MOD_ID, user._id);
|
||||
const notices = dm ? await fetchMessages({ channel: dm._id }) : [];
|
||||
|
||||
// Fetch friends and bots
|
||||
const botIds = await fetchBotsByUser(user._id).then((bots) =>
|
||||
bots.map((bot) => bot._id)
|
||||
);
|
||||
|
||||
const relevantUsers = await fetchUsersById([
|
||||
...botIds,
|
||||
...(
|
||||
user.relations?.filter((relation) => relation.status === "Friend") ?? []
|
||||
).map((relation) => relation._id),
|
||||
]);
|
||||
|
||||
relevantUsers.sort((a) => (a.bot ? -1 : 0));
|
||||
|
||||
// Fetch server memberships
|
||||
const serverMemberships = await fetchMembershipsByUser(user._id);
|
||||
const servers = await fetchServers({
|
||||
_id: {
|
||||
$in: serverMemberships.map((member) => member._id.server),
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch reports
|
||||
const reportsByUser = await fetchReports({
|
||||
author_id: user._id,
|
||||
});
|
||||
|
||||
const reportIdsInvolvingUser = await fetchSnapshots({
|
||||
// TODO: slow query
|
||||
$or: [{ "content._id": user._id }, { "content.author": user._id }],
|
||||
}).then((snapshots) => [
|
||||
...new Set(snapshots.map((snapshot) => snapshot.report_id)),
|
||||
]);
|
||||
|
||||
const reportsAgainstUser = await fetchReports({
|
||||
_id: {
|
||||
$in: reportIdsInvolvingUser,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<NavigationToolbar>Inspecting User</NavigationToolbar>
|
||||
|
||||
<UserCard user={user} subtitle={user.status?.text ?? "No status set"} />
|
||||
<UserActions id={user._id} />
|
||||
|
||||
{user.profile?.content && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<p>{user.profile?.content}</p>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{botOwner && (
|
||||
<Link href={`/panel/inspect/user/${botOwner!._id}`}>
|
||||
<UserCard user={botOwner} subtitle="Bot Owner" />
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
<RelevantModerationNotices strikes={strikes} notices={notices} />
|
||||
|
||||
<Separator />
|
||||
<RelevantObjects
|
||||
users={relevantUsers}
|
||||
servers={servers}
|
||||
userId={user._id}
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
<RelevantReports byUser={reportsByUser} forUser={reportsAgainstUser} />
|
||||
|
||||
<Separator />
|
||||
<JsonCard obj={user} />
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue