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