forked from administration/panel
feat: quarantine button
parent
d053e8ff5c
commit
a03c539890
|
@ -36,9 +36,11 @@ export default async function Server({ params }: { params: { id: string } }) {
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Link href={`/panel/inspect/user/${owner!._id}`}>
|
{
|
||||||
|
owner && <Link href={`/panel/inspect/user/${owner!._id}`}>
|
||||||
<UserCard user={owner!} subtitle="Server Owner" />
|
<UserCard user={owner!} subtitle="Server Owner" />
|
||||||
</Link>
|
</Link>
|
||||||
|
}
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<RecentMessages query={{ channel: { $in: server.channels } }} users />
|
<RecentMessages query={{ channel: { $in: server.channels } }} users />
|
||||||
|
|
|
@ -14,19 +14,21 @@ import {
|
||||||
import { Check, ChevronsUpDown } from "lucide-react";
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { addServerMember, updateServerDiscoverability, updateServerFlags, updateServerOwner } from "@/lib/actions";
|
import { addServerMember, quarantineServer, updateServerDiscoverability, updateServerFlags, updateServerOwner } from "@/lib/actions";
|
||||||
import { useToast } from "../../ui/use-toast";
|
import { useToast } from "../../ui/use-toast";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { DropdownMenu, DropdownMenuContent } from "@/components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent } from "@/components/ui/dropdown-menu";
|
||||||
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
|
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import UserSelector from "@/components/ui/user-selector";
|
import UserSelector from "@/components/ui/user-selector";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { SEVRER_REMOVAL_MESSAGE } from "@/lib/constants";
|
||||||
|
|
||||||
export function ServerActions({ server }: { server: Server }) {
|
export function ServerActions({ server }: { server: Server }) {
|
||||||
const [selectBadges, setSelectBadges] = useState(false);
|
const [selectBadges, setSelectBadges] = useState(false);
|
||||||
const [serverDraft, setDraft] = useState(server);
|
const [serverDraft, setDraft] = useState(server);
|
||||||
|
const [quarantineMessage, setQuarantineMessage] = useState(SEVRER_REMOVAL_MESSAGE(server));
|
||||||
const [newOwner, setNewOwner] = useState<User | null>(null);
|
const [newOwner, setNewOwner] = useState<User | null>(null);
|
||||||
const [newMember, setNewMember] = useState<User | null>(null);
|
const [newMember, setNewMember] = useState<User | null>(null);
|
||||||
const [newMemberEvent, setNewMemberEvent] = useState(true);
|
const [newMemberEvent, setNewMemberEvent] = useState(true);
|
||||||
|
@ -144,9 +146,64 @@ export function ServerActions({ server }: { server: Server }) {
|
||||||
Invites
|
Invites
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Button className="flex-1" variant="destructive">
|
<AlertDialog>
|
||||||
Quarantine
|
<AlertDialogTrigger asChild>
|
||||||
</Button>
|
<Button className="flex-1" variant="destructive">
|
||||||
|
Quarantine
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Quarantine server
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="flex flex-col gap-1">
|
||||||
|
<span>This will remove all members from this server and revoke all invites.</span>
|
||||||
|
<span className="text-red-700">This action is irreversible!</span>
|
||||||
|
<br />
|
||||||
|
<Textarea
|
||||||
|
placeholder="Removal message"
|
||||||
|
value={quarantineMessage}
|
||||||
|
onChange={(e) => setQuarantineMessage(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
className="bg-red-800 hover:bg-red-700"
|
||||||
|
disabled={quarantineMessage == SEVRER_REMOVAL_MESSAGE(server) || !quarantineMessage}
|
||||||
|
onClick={async () => {
|
||||||
|
if (serverDraft.flags) {
|
||||||
|
// Intentionally not clearing the quarantine message draft
|
||||||
|
toast({
|
||||||
|
title: "Refusing to quarantine",
|
||||||
|
description: "This server is marked as verified or official",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await quarantineServer(server._id, quarantineMessage);
|
||||||
|
toast({
|
||||||
|
title: "Quarantined server",
|
||||||
|
});
|
||||||
|
setQuarantineMessage(SEVRER_REMOVAL_MESSAGE(server));
|
||||||
|
} catch(e) {
|
||||||
|
toast({
|
||||||
|
title: "Failed to quarantine",
|
||||||
|
description: String(e),
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Quarantine
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
|
@ -37,7 +37,7 @@ type Permission =
|
||||||
| `servers${
|
| `servers${
|
||||||
| ""
|
| ""
|
||||||
| `/fetch${"" | "/by-id"}`
|
| `/fetch${"" | "/by-id"}`
|
||||||
| `/update${"" | "/flags" | "/discoverability" | "/owner" | "/add-member"}`}`
|
| `/update${"" | "/flags" | "/discoverability" | "/owner" | "/add-member" | "/quarantine"}`}`
|
||||||
| `users${
|
| `users${
|
||||||
| ""
|
| ""
|
||||||
| `/fetch${
|
| `/fetch${
|
||||||
|
@ -148,6 +148,8 @@ const PermissionSets = {
|
||||||
"channels/fetch/invites",
|
"channels/fetch/invites",
|
||||||
"channels/create/dm",
|
"channels/create/dm",
|
||||||
|
|
||||||
|
"servers/update/quarantine",
|
||||||
|
|
||||||
"reports/fetch/related/by-user",
|
"reports/fetch/related/by-user",
|
||||||
"reports/fetch/related/by-content",
|
"reports/fetch/related/by-content",
|
||||||
"reports/fetch/related/against-user",
|
"reports/fetch/related/against-user",
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
Bot,
|
Bot,
|
||||||
Channel,
|
Channel,
|
||||||
File,
|
File,
|
||||||
|
Invite,
|
||||||
Member,
|
Member,
|
||||||
Message,
|
Message,
|
||||||
Report,
|
Report,
|
||||||
|
@ -622,7 +623,13 @@ export async function addServerMember(serverId: string, userId: string, withEven
|
||||||
.find({ server: serverId })
|
.find({ server: serverId })
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
|
const member = await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Member>("server_members")
|
||||||
|
.findOne({ _id: { server: serverId, user: userId } });
|
||||||
|
|
||||||
if (!server) throw new Error("server doesn't exist");
|
if (!server) throw new Error("server doesn't exist");
|
||||||
|
if (member) throw new Error("already a member");
|
||||||
|
|
||||||
await mongo()
|
await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
@ -648,6 +655,74 @@ export async function addServerMember(serverId: string, userId: string, withEven
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function quarantineServer(serverId: string, message: string) {
|
||||||
|
await checkPermission("servers/update/quarantine", { serverId, message });
|
||||||
|
|
||||||
|
const server = await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Server>("servers")
|
||||||
|
.findOne({ _id: serverId });
|
||||||
|
|
||||||
|
const members = await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Member>("server_members")
|
||||||
|
.find({ "_id.server": serverId })
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const invites = await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Invite>("channel_invites")
|
||||||
|
.find({ type: "Server", server: serverId })
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
if (!server) throw new Error("server doesn't exist");
|
||||||
|
|
||||||
|
const backup = {
|
||||||
|
_event: "quarantine",
|
||||||
|
server,
|
||||||
|
members,
|
||||||
|
invites,
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
`./exports/${new Date().toISOString()} - ${serverId}.json`,
|
||||||
|
JSON.stringify(backup),
|
||||||
|
);
|
||||||
|
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Server>("servers")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: serverId },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
owner: "0".repeat(26),
|
||||||
|
analytics: false,
|
||||||
|
discoverable: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Member>("server_members")
|
||||||
|
.deleteMany({ "_id.server": serverId });
|
||||||
|
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Invite>("channel_invites")
|
||||||
|
.deleteMany({ type: "Server", server: serverId });
|
||||||
|
|
||||||
|
await publishMessage(serverId, {
|
||||||
|
type: "ServerDelete",
|
||||||
|
id: serverId,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const member of members) {
|
||||||
|
await sendAlert(member._id.user, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteInvite(invite: string) {
|
export async function deleteInvite(invite: string) {
|
||||||
await checkPermission("channels/update/invites", invite);
|
await checkPermission("channels/update/invites", invite);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Server } from "revolt-api";
|
||||||
|
|
||||||
export const PLATFORM_MOD_ID =
|
export const PLATFORM_MOD_ID =
|
||||||
process.env.PLATFORM_MOD_ID || "01FC17E1WTM2BGE4F3ARN3FDAF";
|
process.env.PLATFORM_MOD_ID || "01FC17E1WTM2BGE4F3ARN3FDAF";
|
||||||
export const AUTUMN_URL =
|
export const AUTUMN_URL =
|
||||||
|
@ -14,3 +16,5 @@ export const RESTRICT_ACCESS_LIST = [
|
||||||
"01EXAHMSGNDCAZTJXDJZ0BK8N3", //- wait what
|
"01EXAHMSGNDCAZTJXDJZ0BK8N3", //- wait what
|
||||||
"01FEEFJCKY5C4DMMJYZ20ACWWC", //- rexo
|
"01FEEFJCKY5C4DMMJYZ20ACWWC", //- rexo
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const SEVRER_REMOVAL_MESSAGE = (server: Server) => `A server you were in ("${server.name}") has been removed from Revolt for the following reason:\n- CHANGEME`;
|
||||||
|
|
Loading…
Reference in New Issue