made the mods vulnerabilities
This commit is contained in:
parent
a162f66b30
commit
deb62f9730
13 changed files with 628 additions and 109 deletions
|
@ -16,6 +16,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
|
const [vulnerabilities, setVulnerabilities] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -25,6 +26,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => {
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setTitle(data.title);
|
setTitle(data.title);
|
||||||
setContent(data.content);
|
setContent(data.content);
|
||||||
|
setVulnerabilities(data.vulnerabilities || "");
|
||||||
})
|
})
|
||||||
.catch((error) => console.error("Failed to load page", error));
|
.catch((error) => console.error("Failed to load page", error));
|
||||||
}
|
}
|
||||||
|
@ -35,7 +37,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => {
|
||||||
const response = await fetch(`/api/mdx/pages/${slug}`, {
|
const response = await fetch(`/api/mdx/pages/${slug}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ title, content }),
|
body: JSON.stringify({ title, content, vulnerabilities }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
@ -44,9 +46,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => {
|
||||||
router.push(`/admin/multi-logs/`);
|
router.push(`/admin/multi-logs/`);
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
// TEMPERARORY ERROR
|
toast({ description: "Page Updated" });
|
||||||
router.push(`/admin/multi-logs/`);
|
|
||||||
toast({ description: "Updated but cant return data" });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ const EditPage = ({ params }: { params: { slug: string } }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="edit-page" className="wrapper container">
|
<section id="edit-page" className="wrapper container gap-4">
|
||||||
<h1 className="text-3xl font-bold py-6">Edit Page: {slug}</h1>
|
<h1 className="text-3xl font-bold py-6">Edit Page: {slug}</h1>
|
||||||
<Input
|
<Input
|
||||||
value={title}
|
value={title}
|
||||||
|
@ -69,6 +69,12 @@ const EditPage = ({ params }: { params: { slug: string } }) => {
|
||||||
onChange={handleEditorChange}
|
onChange={handleEditorChange}
|
||||||
height={560}
|
height={560}
|
||||||
/>
|
/>
|
||||||
|
<h1 className="text-3xl font-bold py-6">Vulnerabilities</h1>
|
||||||
|
<MarkdownEditor
|
||||||
|
value={vulnerabilities}
|
||||||
|
onChange={(value) => setVulnerabilities(value || "")}
|
||||||
|
height={200}
|
||||||
|
/>
|
||||||
<Button onClick={savePage} disabled={loading} className="mt-4">
|
<Button onClick={savePage} disabled={loading} className="mt-4">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
|
276
app/(auth)/admin/vulnerabilities/mods/page.tsx
Normal file
276
app/(auth)/admin/vulnerabilities/mods/page.tsx
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
"use client";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm, SubmitHandler, useFieldArray } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import {
|
||||||
|
ModsVulnerability,
|
||||||
|
vulnerabilitiesSchema,
|
||||||
|
} from "@/lib/validations/validation";
|
||||||
|
|
||||||
|
interface VulnerabilityEntry {
|
||||||
|
_id: string;
|
||||||
|
title: string;
|
||||||
|
category: string;
|
||||||
|
bullets: { point: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type VulnerabilitiesForm = z.infer<typeof vulnerabilitiesSchema>;
|
||||||
|
|
||||||
|
const AdminLogPage = () => {
|
||||||
|
const [logs, setLogs] = useState<VulnerabilityEntry[]>([]);
|
||||||
|
const [categories, setCategories] = useState<
|
||||||
|
{ title: string; _id: string }[]
|
||||||
|
>([]);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<string>("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<ModsVulnerability>({
|
||||||
|
resolver: zodResolver(vulnerabilitiesSchema),
|
||||||
|
defaultValues: {
|
||||||
|
title: "",
|
||||||
|
category: "",
|
||||||
|
bullets: [{ point: "" }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
control: form.control,
|
||||||
|
name: "bullets",
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchLogs = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/vulnerabilities", { method: "GET" });
|
||||||
|
if (response.ok) {
|
||||||
|
const data: VulnerabilityEntry[] = await response.json();
|
||||||
|
setLogs(data);
|
||||||
|
} else {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setError(error.message || "Failed to fetch logs");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/mdx/pages", { method: "GET" });
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setCategories(data);
|
||||||
|
} else {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setError(error.message || "Failed to fetch categories");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<ModsVulnerability> = async (data) => {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await fetch("/api/vulnerability/mods", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
...data,
|
||||||
|
category: selectedCategory,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
form.reset();
|
||||||
|
fetchLogs();
|
||||||
|
setLoading(false);
|
||||||
|
toast({ description: "Logs successfully added" });
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
toast({ description: "Upload Failed", variant: "destructive" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteLog = async (id: string) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/delete/vulnerability/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
fetchLogs();
|
||||||
|
} else {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setError(error.message || "Failed to delete log");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLogs();
|
||||||
|
fetchCategories();
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchLogs();
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="logs-page" className="wrapper container">
|
||||||
|
<h1 className="text-3xl font-bold py-6">Server Vulnerabilities Form</h1>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Title</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="category"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Category</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={selectedCategory}
|
||||||
|
onValueChange={(value) => setSelectedCategory(field.value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={"Category"} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<SelectItem key={cat._id} value={cat._id}>
|
||||||
|
{cat.title}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<FormField
|
||||||
|
key={field.id}
|
||||||
|
control={form.control}
|
||||||
|
name={`bullets.${index}.point`}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Bullet Point {index + 1}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="mt-2"
|
||||||
|
variant={"secondary"}
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="mb-4"
|
||||||
|
size={"icon"}
|
||||||
|
variant={"outline"}
|
||||||
|
onClick={() => append({ point: "" })}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full text-lg rounded-full"
|
||||||
|
disabled={loading}
|
||||||
|
size={"lg"}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
{/* Section to list and delete logs */}
|
||||||
|
<section id="logs-list" className="py-16 md:py-24">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold">
|
||||||
|
Existing Vulnerabilities
|
||||||
|
</h2>
|
||||||
|
{error && <p className="text-red-500">{error}</p>}
|
||||||
|
<Table className="w-full mt-4 border-muted">
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="border-b px-4 py-2">Title</TableHead>
|
||||||
|
<TableHead className="border-b px-4 py-2">Category</TableHead>
|
||||||
|
<TableHead className="border-b px-4 py-2">Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{logs
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.map((log) => (
|
||||||
|
<TableRow key={log._id}>
|
||||||
|
<TableCell className="border-b px-4 py-2">
|
||||||
|
{log.title}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="border-b px-4 py-2">
|
||||||
|
{log.category}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="border-b px-4 py-2">
|
||||||
|
<Button
|
||||||
|
variant={"destructive"}
|
||||||
|
onClick={() => deleteLog(log._id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminLogPage;
|
|
@ -22,30 +22,34 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { logsSchema } from "@/lib/validations/validation";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { vulnerabilitiesSchema } from "@/lib/validations/validation";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
interface LogEntry {
|
interface VulnerabiltyEntry {
|
||||||
_id: string;
|
_id: string;
|
||||||
version: string;
|
version: string;
|
||||||
date: string;
|
|
||||||
bullets: { point: string }[];
|
bullets: { point: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogsFormValues = z.infer<typeof logsSchema>;
|
type VulnerabiltiesForm = z.infer<typeof vulnerabilitiesSchema>;
|
||||||
|
|
||||||
const AdminLogPage = () => {
|
const AdminLogPage = () => {
|
||||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
const [logs, setLogs] = useState<VulnerabiltyEntry[]>([]);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const form = useForm<LogsFormValues>({
|
const form = useForm<VulnerabiltiesForm>({
|
||||||
resolver: zodResolver(logsSchema),
|
resolver: zodResolver(vulnerabilitiesSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
version: "",
|
version: "",
|
||||||
date: "",
|
|
||||||
bullets: [{ point: "" }],
|
bullets: [{ point: "" }],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -59,7 +63,7 @@ const AdminLogPage = () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/vulnerabilities", { method: "GET" });
|
const response = await fetch("/api/vulnerabilities", { method: "GET" });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data: LogEntry[] = await response.json();
|
const data: VulnerabiltyEntry[] = await response.json();
|
||||||
setLogs(data);
|
setLogs(data);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
@ -69,7 +73,7 @@ const AdminLogPage = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<LogsFormValues> = async (data) => {
|
const onSubmit: SubmitHandler<VulnerabiltiesForm> = async (data) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch("/api/uploadvulnerabilities", {
|
const response = await fetch("/api/uploadvulnerabilities", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -90,7 +94,7 @@ const AdminLogPage = () => {
|
||||||
|
|
||||||
const deleteLog = async (id: string) => {
|
const deleteLog = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/delete/logs/${id}`, {
|
const response = await fetch(`/api/delete/vulnerability/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|
|
@ -74,7 +74,7 @@ const Page = ({ params }: { params: { slug: string } }) => {
|
||||||
<>
|
<>
|
||||||
<section className="wrapper container py-24 md:py-28 gap-2 flex flex-col">
|
<section className="wrapper container py-24 md:py-28 gap-2 flex flex-col">
|
||||||
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
|
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
|
||||||
{page.title}
|
{page.title} Change Log
|
||||||
</h1>
|
</h1>
|
||||||
<ReactMarkdown className="prose max-w-full md:prose-lg dark:prose-invert">
|
<ReactMarkdown className="prose max-w-full md:prose-lg dark:prose-invert">
|
||||||
{page.content}
|
{page.content}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { vulnerabilities } from "@/constants/guidelines";
|
import { VULNERABILITY } from "@/constants/guidelines";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
|
@ -11,14 +11,22 @@ interface Bullet {
|
||||||
|
|
||||||
interface Vulnerabilities {
|
interface Vulnerabilities {
|
||||||
_id: string;
|
_id: string;
|
||||||
date: string;
|
|
||||||
version: string;
|
version: string;
|
||||||
bullets?: Bullet[]; // Make bullets optional
|
bullets?: Bullet[]; // Make bullets optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ModsVulnerability {
|
||||||
|
_id: string;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
content: string;
|
||||||
|
vulnerabilities: string;
|
||||||
|
}
|
||||||
|
|
||||||
const Vulnerabilities = () => {
|
const Vulnerabilities = () => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [downloads, setDownloads] = useState<Vulnerabilities[]>([]);
|
const [downloads, setDownloads] = useState<Vulnerabilities[]>([]);
|
||||||
|
const [mods, setMods] = useState<ModsVulnerability[]>([]);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
@ -29,7 +37,7 @@ const Vulnerabilities = () => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data: Vulnerabilities[] = await response.json();
|
const data: Vulnerabilities[] = await response.json();
|
||||||
setDownloads(data);
|
setDownloads(data);
|
||||||
return (document.title = "Vulnerabilities | SVRJS");
|
document.title = "Vulnerabilities | SVRJS";
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
@ -40,18 +48,43 @@ const Vulnerabilities = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchMods = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/mdx/pages`, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const data: ModsVulnerability[] = await response.json();
|
||||||
|
// Filter out entries where vulnerabilities is undefined or an empty string
|
||||||
|
const filteredMods = data.filter(
|
||||||
|
(mod) => mod.vulnerabilities && mod.vulnerabilities.trim() !== ""
|
||||||
|
);
|
||||||
|
setMods(filteredMods);
|
||||||
|
document.title = "Vulnerabilities | SVRJS";
|
||||||
|
} else {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setError(error.message || "Failed to fetch vulnerabilities");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
|
fetchMods();
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
|
fetchMods();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
const reversedDownloads = [...downloads].reverse();
|
|
||||||
|
|
||||||
// initially loading = true
|
const reversedDownloads = [...downloads].reverse();
|
||||||
|
const reversedMods = [...mods].reverse();
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<section className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
|
<section className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
|
||||||
|
@ -98,9 +131,27 @@ const Vulnerabilities = () => {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="prose max-w-full md:prose-lg dark:prose-invert">
|
<div className="prose max-w-full md:prose-lg dark:prose-invert">
|
||||||
<ReactMarkdown>{vulnerabilities}</ReactMarkdown>
|
<ReactMarkdown>{VULNERABILITY}</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Section with MODS content */}
|
||||||
|
{reversedMods.map((mod) => (
|
||||||
|
<div
|
||||||
|
key={mod._id}
|
||||||
|
className="flex-start flex-col prose dark:prose-invert mb-4 gap-4"
|
||||||
|
>
|
||||||
|
<h2 className="text-3xl md:text-5xl py-1 md:py-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400 -mb-1">
|
||||||
|
{mod.title}
|
||||||
|
</h2>
|
||||||
|
{mod.vulnerabilities && (
|
||||||
|
<div className="prose max-w-full md:prose-lg dark:prose-invert">
|
||||||
|
<ReactMarkdown>{mod.vulnerabilities}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
30
app/api/delete/vulnerability/[id]/route.ts
Normal file
30
app/api/delete/vulnerability/[id]/route.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// app/api/delete/[id]/route.ts
|
||||||
|
import clientPromise from "@/lib/db";
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function DELETE(
|
||||||
|
request: Request,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
const { id } = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = await clientPromise;
|
||||||
|
const db = client.db("downloadsDatabase");
|
||||||
|
const collection = db.collection("vulnerabilities");
|
||||||
|
|
||||||
|
const result = await collection.deleteOne({ _id: new ObjectId(id) });
|
||||||
|
|
||||||
|
if (result.deletedCount === 1) {
|
||||||
|
return NextResponse.json({ message: "Log deleted successfully" });
|
||||||
|
} else {
|
||||||
|
return NextResponse.json({ message: "Log not found" }, { status: 404 });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Failed to delete log", error: error },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
32
app/api/delete/vulnerability/mods/route.ts
Normal file
32
app/api/delete/vulnerability/mods/route.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import clientPromise from "@/lib/db";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
if (req.method === "POST") {
|
||||||
|
try {
|
||||||
|
const client = await clientPromise;
|
||||||
|
const db = client.db("modsVuln");
|
||||||
|
const { title, category, bullets } = req.body;
|
||||||
|
|
||||||
|
if (!title || !category || !bullets || !Array.isArray(bullets)) {
|
||||||
|
return res.status(400).json({ error: "Invalid data" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.collection("vulnerabilities").insertOne({
|
||||||
|
title,
|
||||||
|
category,
|
||||||
|
bullets,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Vulnerability added successfully" });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error || "Failed to add vulnerability" });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.setHeader("Allow", ["POST"]);
|
||||||
|
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,57 +34,35 @@ export const PUT = async (
|
||||||
return NextResponse.json({ message: "Slug is required" }, { status: 400 });
|
return NextResponse.json({ message: "Slug is required" }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, content } = await req.json();
|
const { title, content, vulnerabilities } = await req.json();
|
||||||
|
|
||||||
if (typeof title !== "string" || typeof content !== "string") {
|
if (
|
||||||
|
typeof title !== "string" ||
|
||||||
|
typeof content !== "string" ||
|
||||||
|
typeof vulnerabilities !== "string"
|
||||||
|
) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ message: "Invalid title or content" },
|
{ message: "Invalid title, content, or vulnerabilities" },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// it works here ig
|
const result = await db.collection("pages").findOneAndUpdate(
|
||||||
const result = await db
|
{ slug },
|
||||||
.collection("pages")
|
{ $set: { title, content, vulnerabilities } },
|
||||||
.findOneAndUpdate(
|
{ returnDocument: "after" } // Updated option
|
||||||
{ slug },
|
);
|
||||||
{ $set: { title, content } },
|
|
||||||
{ returnDocument: "after" }
|
|
||||||
);
|
|
||||||
|
|
||||||
// i hate my life fr fr
|
if (result?.value) {
|
||||||
console.log("Update Result:", result);
|
const serializedResult = {
|
||||||
// result returns like
|
...result.value,
|
||||||
|
_id: result.value._id.toString(), // Convert ObjectId to string
|
||||||
// Update Result: {
|
};
|
||||||
// _id: new ObjectId('66a2946b2b91eef505eef943'),
|
return NextResponse.json(serializedResult, { status: 200 });
|
||||||
// title: 'TEST PAGE',
|
} else {
|
||||||
// slug: 'test-page',
|
return NextResponse.json({ message: "Page not found" }, { status: 404 });
|
||||||
// content: 'asd]---\n' +
|
}
|
||||||
// '---\n' +
|
|
||||||
// '\n' +
|
|
||||||
// 'this is basic heading ?\n' +
|
|
||||||
// '\n' +
|
|
||||||
// '**HELLO**\n' +
|
|
||||||
// '\n' +
|
|
||||||
// 'erw\n' +
|
|
||||||
// '\n' +
|
|
||||||
// 'trying another time for test'
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ERRROR : TypeError: Cannot read properties of undefined (reading '_id')
|
|
||||||
// aposdjaoi sdio JUST WORK NIAWWWWWWWWW
|
|
||||||
|
|
||||||
// if (result && result.value) {
|
|
||||||
const serializedResult = {
|
|
||||||
...result?.value,
|
|
||||||
_id: result?.value._id.toString(), // Convert ObjectId to string
|
|
||||||
};
|
|
||||||
return NextResponse.json(result?.value.content, { status: 200 });
|
|
||||||
// } else {
|
|
||||||
// return NextResponse.json({ message: "Page not found" }, { status: 404 });
|
|
||||||
// }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating page:", error);
|
console.error("Error updating page:", error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
160
components/ui/select.tsx
Normal file
160
components/ui/select.tsx
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||||
|
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group
|
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value
|
||||||
|
|
||||||
|
const SelectTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
))
|
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const SelectScrollUpButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUp className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
))
|
||||||
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||||
|
|
||||||
|
const SelectScrollDownButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
))
|
||||||
|
SelectScrollDownButton.displayName =
|
||||||
|
SelectPrimitive.ScrollDownButton.displayName
|
||||||
|
|
||||||
|
const SelectContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
|
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" &&
|
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
))
|
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SelectLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const SelectItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
))
|
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const SelectSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectGroup,
|
||||||
|
SelectValue,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectLabel,
|
||||||
|
SelectItem,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
}
|
|
@ -357,7 +357,7 @@ By contributing to our project, you agree that your contributions will be licens
|
||||||
Thank you for considering contributing to our project!
|
Thank you for considering contributing to our project!
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const vulnerabilities = `
|
export const VULNERABILITY = `
|
||||||
|
|
||||||
### Fixed in SVR.JS 3.15.0 and in SVR.JS 3.14.16 LTS
|
### Fixed in SVR.JS 3.15.0 and in SVR.JS 3.14.16 LTS
|
||||||
- An attacker could send a HTTP forward proxy request with malformed URL not using CONNECT method to possibly crash the server.
|
- An attacker could send a HTTP forward proxy request with malformed URL not using CONNECT method to possibly crash the server.
|
||||||
|
@ -419,46 +419,6 @@ export const vulnerabilities = `
|
||||||
### Fixed in SVR.JS 2.1.1
|
### Fixed in SVR.JS 2.1.1
|
||||||
- An attacker could use directory listing to access secret files (through path traversal).
|
- An attacker could use directory listing to access secret files (through path traversal).
|
||||||
|
|
||||||
## easy-waf integration
|
|
||||||
|
|
||||||
### Fixed in easy-waf integration 1.2.4
|
|
||||||
- An attacker could send a very small chunk of the POST request body (which will not trigger WAF) before the payload to bypass the WAF.
|
|
||||||
|
|
||||||
### Fixed in easy-waf integration 1.2.1
|
|
||||||
- An attacker could access the resource on the server with poorly written SVR.JS mod or server-side JavaScript (that normally would invoke 500 Internal Server Error) to crash the server.
|
|
||||||
|
|
||||||
## RedBrick
|
|
||||||
|
|
||||||
### Fixed in RedBrick 2.5.4
|
|
||||||
- An attacker could add HTTP authentication header to the HTTP request when not required to enable web application functionality normally disabled on unauthenticated requests. View the security advisory
|
|
||||||
|
|
||||||
### Fixed in RedBrick 2.3.3
|
|
||||||
- An attacker could use “CGI-BIN” instead of “cgi-bin” to leak source code, while SVR.JS with RedBrick is running on Windows. View the security advisory
|
|
||||||
- An attacker could leak RedBrick interpreter settings, while SVR.JS with RedBrick is running on Windows. View the security advisory
|
|
||||||
|
|
||||||
## reverse-proxy-mod
|
|
||||||
|
|
||||||
### Fixed in reverse-proxy-mod 1.1.2
|
|
||||||
- An attacker could hack the upstream server, replace the web server or application with one that sends an invalid HTTP response code, and make a request to the hacked server through the reverse proxy to crash the reverse proxy server.
|
|
||||||
|
|
||||||
### Fixed in reverse-proxy-mod 1.0.4
|
|
||||||
- An attacker could leak reverse proxy configuration file. View the security advisory
|
|
||||||
|
|
||||||
## OrangeCircle
|
|
||||||
|
|
||||||
### Fixed in OrangeCircle 1.1.2
|
|
||||||
- An attacker could add HTTP authentication header to the HTTP request when not required to enable web application functionality normally disabled on unauthenticated requests. View the security advisory
|
|
||||||
|
|
||||||
### Fixed in OrangeCircle 1.0.2
|
|
||||||
- An attacker could leak OrangeCircle configuration, while SVR.JS with YellowSquare is running on Windows. View the security advisory
|
|
||||||
|
|
||||||
## YellowSquare
|
|
||||||
|
|
||||||
### Fixed in YellowSquare 1.1.2
|
|
||||||
- An attacker could add HTTP authentication header to the HTTP request when not required to enable web application functionality normally disabled on unauthenticated requests. View the security advisory
|
|
||||||
|
|
||||||
### Fixed in YellowSquare 1.0.1
|
|
||||||
- An attacker could use “JSGI-BIN” instead of “jsgi-bin” to leak source code, while SVR.JS with YellowSquare is running on Windows. View the security advisory
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CHANGE_LOGS = `
|
export const CHANGE_LOGS = `
|
||||||
|
|
|
@ -24,7 +24,28 @@ export const logsSchema = z.object({
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const vulnerabilitiesSchema = z.object({
|
||||||
|
version: z.string(),
|
||||||
|
bullets: z.array(
|
||||||
|
z.object({
|
||||||
|
point: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ModsVulnerabilities = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
category: z.string(),
|
||||||
|
bullets: z.array(
|
||||||
|
z.object({
|
||||||
|
point: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
export type LogsFormValues = z.infer<typeof logsSchema>;
|
export type LogsFormValues = z.infer<typeof logsSchema>;
|
||||||
|
export type VulnerabiltiesForm = z.infer<typeof vulnerabilitiesSchema>;
|
||||||
|
export type ModsVulnerability = z.infer<typeof ModsVulnerabilities>;
|
||||||
|
|
||||||
// Contact Page
|
// Contact Page
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
"@hookform/resolvers": "^3.6.0",
|
"@hookform/resolvers": "^3.6.0",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-navigation-menu": "^1.1.4",
|
"@radix-ui/react-navigation-menu": "^1.1.4",
|
||||||
|
"@radix-ui/react-select": "^2.1.1",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-toast": "^1.2.1",
|
"@radix-ui/react-toast": "^1.2.1",
|
||||||
|
|
Loading…
Reference in a new issue