mirror of
https://github.com/community-scripts/ProxmoxVE
synced 2025-01-10 19:05:09 +00:00
Use static assets instead of fetching from github (#156)
This commit is contained in:
parent
2af11d145f
commit
d199762427
1
frontend/public/json
Symbolic link
1
frontend/public/json
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../json
|
@ -1,26 +1,30 @@
|
|||||||
import { basePath } from "@/config/siteConfig";
|
import { Metadata, Script } from "@/lib/types";
|
||||||
import { Category, Script } from "@/lib/types";
|
import { promises as fs } from "fs";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
|
|
||||||
const fetchCategories = async (): Promise<Category[]> => {
|
const jsonDir = "public/json";
|
||||||
const response = await fetch(
|
const metadataFileName = "metadata.json";
|
||||||
`https://raw.githubusercontent.com/community-scripts/${basePath}/refs/heads/main/json/metadata.json`,
|
const encoding = "utf-8";
|
||||||
);
|
|
||||||
const data = await response.json();
|
const getMetadata = async () => {
|
||||||
return data.categories;
|
const filePath = path.resolve(jsonDir, metadataFileName);
|
||||||
|
const fileContent = await fs.readFile(filePath, encoding);
|
||||||
|
const metadata: Metadata = JSON.parse(fileContent);
|
||||||
|
return metadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchScripts = async (): Promise<Script[]> => {
|
const getScripts = async () => {
|
||||||
const response = await fetch(
|
const filePaths = (await fs.readdir(jsonDir))
|
||||||
`https://api.github.com/repos/community-scripts/${basePath}/contents/json`,
|
.filter((fileName) => fileName !== metadataFileName)
|
||||||
);
|
.map((fileName) => path.resolve(jsonDir, fileName));
|
||||||
const files: { download_url: string }[] = await response.json();
|
|
||||||
const scripts = await Promise.all(
|
const scripts = await Promise.all(
|
||||||
files.map(async (file) : Promise<Script> => {
|
filePaths.map(async (filePath) => {
|
||||||
const response = await fetch(file.download_url);
|
const fileContent = await fs.readFile(filePath, encoding);
|
||||||
const script = await response.json();
|
const script: Script = JSON.parse(fileContent);
|
||||||
return script;
|
return script;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -29,11 +33,18 @@ const fetchScripts = async (): Promise<Script[]> => {
|
|||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const categories = await fetchCategories();
|
const metadata = await getMetadata();
|
||||||
const scripts = await fetchScripts();
|
const scripts = await getScripts();
|
||||||
for (const category of categories) {
|
|
||||||
category.scripts = scripts.filter((script) => script.categories.includes(category.id));
|
const categories = metadata.categories
|
||||||
}
|
.map((category) => {
|
||||||
|
category.scripts = scripts.filter((script) =>
|
||||||
|
script.categories.includes(category.id),
|
||||||
|
);
|
||||||
|
return category;
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.sort_order - b.sort_order);
|
||||||
|
|
||||||
return NextResponse.json(categories);
|
return NextResponse.json(categories);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error as Error);
|
console.error(error as Error);
|
||||||
|
@ -2,11 +2,11 @@ import Footer from "@/components/Footer";
|
|||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
|
import { analytics, basePath } from "@/config/siteConfig";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import React from "react";
|
|
||||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||||
import { analytics, basePath } from "@/config/siteConfig";
|
import React from "react";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
@ -65,7 +65,6 @@ export default function RootLayout({
|
|||||||
data-website-id={analytics.token}
|
data-website-id={analytics.token}
|
||||||
></script>
|
></script>
|
||||||
<link rel="manifest" href="manifest.webmanifest" />
|
<link rel="manifest" href="manifest.webmanifest" />
|
||||||
<link rel="preconnect" href={process.env.NEXT_PUBLIC_POCKETBASE_URL} />
|
|
||||||
<link rel="preconnect" href="https://api.github.com" />
|
<link rel="preconnect" href="https://api.github.com" />
|
||||||
</head>
|
</head>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import AnimatedGradientText from "@/components/ui/animated-gradient-text";
|
import AnimatedGradientText from "@/components/ui/animated-gradient-text";
|
||||||
import Particles from "@/components/ui/particles";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { CardFooter } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import Particles from "@/components/ui/particles";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ArrowRightIcon, ExternalLink } from "lucide-react";
|
import { ArrowRightIcon, ExternalLink } from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
|
||||||
import { CardFooter } from "@/components/ui/card";
|
|
||||||
import { FaGithub } from "react-icons/fa";
|
import { FaGithub } from "react-icons/fa";
|
||||||
import { basePath } from "@/config/siteConfig";
|
|
||||||
|
|
||||||
function CustomArrowRightIcon() {
|
function CustomArrowRightIcon() {
|
||||||
return <ArrowRightIcon className="h-4 w-4" width={1} />;
|
return <ArrowRightIcon className="h-4 w-4" width={1} />;
|
||||||
|
@ -52,7 +52,9 @@ function ScriptItem({
|
|||||||
<div className="ml-4 flex flex-col justify-between">
|
<div className="ml-4 flex flex-col justify-between">
|
||||||
<div className="flex h-full w-full flex-col justify-between">
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-lg font-semibold">{item.name} {getDisplayValueFromType(item.type)}</h1>
|
<h1 className="text-lg font-semibold">
|
||||||
|
{item.name} {getDisplayValueFromType(item.type)}
|
||||||
|
</h1>
|
||||||
<p className="w-full text-sm text-muted-foreground">
|
<p className="w-full text-sm text-muted-foreground">
|
||||||
Date added: {extractDate(item.date_created)}
|
Date added: {extractDate(item.date_created)}
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
import handleCopy from "@/components/handleCopy";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import handleCopy from "@/components/handleCopy";
|
|
||||||
import { Script } from "@/lib/types";
|
import { Script } from "@/lib/types";
|
||||||
|
|
||||||
export default function DefaultPassword({ item }: { item: Script }) {
|
export default function DefaultPassword({ item }: { item: Script }) {
|
||||||
const hasDefaultLogin = item.default_credentials.username && item.default_credentials.password;
|
const hasDefaultLogin =
|
||||||
|
item.default_credentials.username && item.default_credentials.password;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -25,7 +26,10 @@ export default function DefaultPassword({ item }: { item: Script }) {
|
|||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
size={"null"}
|
size={"null"}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleCopy("username", item.default_credentials.username ?? "")
|
handleCopy(
|
||||||
|
"username",
|
||||||
|
item.default_credentials.username ?? "",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{item.default_credentials.username}
|
{item.default_credentials.username}
|
||||||
@ -37,7 +41,10 @@ export default function DefaultPassword({ item }: { item: Script }) {
|
|||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
size={"null"}
|
size={"null"}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleCopy("password", item.default_credentials.password ?? "")
|
handleCopy(
|
||||||
|
"password",
|
||||||
|
item.default_credentials.password ?? "",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{item.default_credentials.password}
|
{item.default_credentials.password}
|
||||||
|
@ -44,7 +44,8 @@ export default function DefaultSettings({ item }: { item: Script }) {
|
|||||||
CPU: {defaultAlpineSettings?.resources.cpu}vCPU
|
CPU: {defaultAlpineSettings?.resources.cpu}vCPU
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
RAM: {getDisplayValueFromRAM(defaultAlpineSettings?.resources.ram ?? 0)}
|
RAM:{" "}
|
||||||
|
{getDisplayValueFromRAM(defaultAlpineSettings?.resources.ram ?? 0)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
HDD: {defaultAlpineSettings?.resources.hdd}GB
|
HDD: {defaultAlpineSettings?.resources.hdd}GB
|
||||||
|
@ -6,7 +6,7 @@ import { getDisplayValueFromType } from "../ScriptInfoBlocks";
|
|||||||
|
|
||||||
const getInstallCommand = (scriptPath?: string) => {
|
const getInstallCommand = (scriptPath?: string) => {
|
||||||
return `bash -c "$(wget -qLO - https://github.com/community-scripts/${basePath}/raw/main/${scriptPath})"`;
|
return `bash -c "$(wget -qLO - https://github.com/community-scripts/${basePath}/raw/main/${scriptPath})"`;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function InstallCommand({ item }: { item: Script }) {
|
export default function InstallCommand({ item }: { item: Script }) {
|
||||||
const alpineScript = item.install_methods.find(
|
const alpineScript = item.install_methods.find(
|
||||||
@ -14,7 +14,7 @@ export default function InstallCommand({ item }: { item: Script }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const defaultScript = item.install_methods.find(
|
const defaultScript = item.install_methods.find(
|
||||||
(method) => method.type === "default"
|
(method) => method.type === "default",
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderInstructions = (isAlpine = false) => (
|
const renderInstructions = (isAlpine = false) => (
|
||||||
@ -60,7 +60,9 @@ export default function InstallCommand({ item }: { item: Script }) {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="default">
|
<TabsContent value="default">
|
||||||
{renderInstructions()}
|
{renderInstructions()}
|
||||||
<CodeCopyButton>{getInstallCommand(defaultScript?.script)}</CodeCopyButton>
|
<CodeCopyButton>
|
||||||
|
{getInstallCommand(defaultScript?.script)}
|
||||||
|
</CodeCopyButton>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="alpine">
|
<TabsContent value="alpine">
|
||||||
{renderInstructions(true)}
|
{renderInstructions(true)}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Button, buttonVariants } from "@/components/ui/button";
|
|
||||||
import handleCopy from "@/components/handleCopy";
|
import handleCopy from "@/components/handleCopy";
|
||||||
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
|
import { Script } from "@/lib/types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ClipboardIcon } from "lucide-react";
|
import { ClipboardIcon } from "lucide-react";
|
||||||
import { Script } from "@/lib/types";
|
|
||||||
|
|
||||||
const CopyButton = ({
|
const CopyButton = ({
|
||||||
label,
|
label,
|
||||||
@ -11,7 +11,12 @@ const CopyButton = ({
|
|||||||
label: string;
|
label: string;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
}) => (
|
}) => (
|
||||||
<span className={cn(buttonVariants({size: "sm", variant: "secondary"}), "flex items-center gap-2")}>
|
<span
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ size: "sm", variant: "secondary" }),
|
||||||
|
"flex items-center gap-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{value}
|
{value}
|
||||||
<ClipboardIcon
|
<ClipboardIcon
|
||||||
onClick={() => handleCopy(label, String(value))}
|
onClick={() => handleCopy(label, String(value))}
|
||||||
@ -20,8 +25,7 @@ const CopyButton = ({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function InterFaces({item} : {item : Script}) {
|
export default function InterFaces({ item }: { item: Script }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{item.interface_port !== null ? (
|
{item.interface_port !== null ? (
|
||||||
@ -29,10 +33,7 @@ export default function InterFaces({item} : {item : Script}) {
|
|||||||
<h2 className="mr-2 text-end text-lg font-semibold">
|
<h2 className="mr-2 text-end text-lg font-semibold">
|
||||||
{"Default Interface:"}
|
{"Default Interface:"}
|
||||||
</h2>{" "}
|
</h2>{" "}
|
||||||
<CopyButton
|
<CopyButton label="default interface" value={item.interface_port} />
|
||||||
label="default interface"
|
|
||||||
value={item.interface_port}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,15 +17,16 @@ const Sidebar = ({
|
|||||||
<div className="flex items-end justify-between pb-4">
|
<div className="flex items-end justify-between pb-4">
|
||||||
<h1 className="text-xl font-bold">Categories</h1>
|
<h1 className="text-xl font-bold">Categories</h1>
|
||||||
<p className="text-xs italic text-muted-foreground">
|
<p className="text-xs italic text-muted-foreground">
|
||||||
{items.reduce(
|
{items.reduce((acc, category) => acc + category.scripts.length, 0)}{" "}
|
||||||
(acc, category) => acc + category.scripts.length,
|
|
||||||
0,
|
|
||||||
)}{" "}
|
|
||||||
Total scripts
|
Total scripts
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg">
|
<div className="rounded-lg">
|
||||||
<ScriptAccordion items={items} selectedScript={selectedScript} setSelectedScript={setSelectedScript} />
|
<ScriptAccordion
|
||||||
|
items={items}
|
||||||
|
selectedScript={selectedScript}
|
||||||
|
setSelectedScript={setSelectedScript}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { Clipboard, Copy } from "lucide-react";
|
import { Clipboard, Copy } from "lucide-react";
|
||||||
@ -8,7 +9,6 @@ import * as React from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { Separator } from "./separator";
|
import { Separator } from "./separator";
|
||||||
import { basePath } from "@/config/siteConfig";
|
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
@ -135,4 +135,4 @@ const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(
|
|||||||
);
|
);
|
||||||
CodeBlock.displayName = "CodeBlock";
|
CodeBlock.displayName = "CodeBlock";
|
||||||
|
|
||||||
export { CodeBlock, buttonVariants };
|
export { buttonVariants, CodeBlock };
|
||||||
|
@ -123,6 +123,6 @@ export {
|
|||||||
NavigationMenuLink,
|
NavigationMenuLink,
|
||||||
NavigationMenuList,
|
NavigationMenuList,
|
||||||
NavigationMenuTrigger,
|
NavigationMenuTrigger,
|
||||||
NavigationMenuViewport,
|
|
||||||
navigationMenuTriggerStyle,
|
navigationMenuTriggerStyle,
|
||||||
|
NavigationMenuViewport,
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FaGithub, FaStar } from "react-icons/fa";
|
import { FaGithub, FaStar } from "react-icons/fa";
|
||||||
import NumberTicker from "./number-ticker";
|
|
||||||
import { buttonVariants } from "./button";
|
import { buttonVariants } from "./button";
|
||||||
import { basePath } from "@/config/siteConfig";
|
import NumberTicker from "./number-ticker";
|
||||||
|
|
||||||
export default function StarOnGithubButton() {
|
export default function StarOnGithubButton() {
|
||||||
const [stars, setStars] = useState(0);
|
const [stars, setStars] = useState(0);
|
||||||
@ -12,9 +12,12 @@ export default function StarOnGithubButton() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchStars = async () => {
|
const fetchStars = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`https://api.github.com/repos/community-scripts/${basePath}`, {
|
const res = await fetch(
|
||||||
next: { revalidate: 60 * 60 * 24 },
|
`https://api.github.com/repos/community-scripts/${basePath}`,
|
||||||
});
|
{
|
||||||
|
next: { revalidate: 60 * 60 * 24 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
||||||
import { Button } from "./button";
|
|
||||||
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
|
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { Button } from "./button";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "./tooltip";
|
||||||
|
|
||||||
export function ThemeToggle() {
|
export function ThemeToggle() {
|
||||||
const { setTheme, theme: currentTheme } = useTheme();
|
const { setTheme, theme: currentTheme } = useTheme();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { MessagesSquare, Scroll } from "lucide-react";
|
import { MessagesSquare, Scroll } from "lucide-react";
|
||||||
import { FaGithub } from "react-icons/fa";
|
import { FaGithub } from "react-icons/fa";
|
||||||
|
|
||||||
export const basePath = process.env.BASE_PATH;
|
export const basePath = process.env.BASE_PATH;
|
||||||
|
|
||||||
export const navbarLinks = [
|
export const navbarLinks = [
|
||||||
{
|
{
|
||||||
@ -17,7 +17,7 @@ export const navbarLinks = [
|
|||||||
text: "Change Log",
|
text: "Change Log",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: `https://github.com/community-scripts/${basePath}/discussions`,
|
href: `https://github.com/community-scripts/${basePath}/discussions`,
|
||||||
event: "Discussions",
|
event: "Discussions",
|
||||||
icon: <MessagesSquare className="h-4 w-4" />,
|
icon: <MessagesSquare className="h-4 w-4" />,
|
||||||
text: "Discussions",
|
text: "Discussions",
|
||||||
|
@ -1,23 +1,10 @@
|
|||||||
import { Category } from "./types";
|
import { Category } from "./types";
|
||||||
|
|
||||||
const sortCategories = (categories: Category[]) => {
|
export const fetchCategories = async () => {
|
||||||
return categories.sort((a, b) => {
|
|
||||||
if (a.name === "Proxmox VE Tools") {
|
|
||||||
return -1;
|
|
||||||
} else if (b.name === "Proxmox VE Tools") {
|
|
||||||
return 1;
|
|
||||||
} else if (a.name === "Miscellaneous") {
|
|
||||||
return 1;
|
|
||||||
} else if (b.name === "Miscellaneous") {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchCategories = async (): Promise<Category[]> => {
|
|
||||||
const response = await fetch("api/categories");
|
const response = await fetch("api/categories");
|
||||||
const categories = await response.json();
|
if (!response.ok) {
|
||||||
return sortCategories(categories);
|
throw new Error(`Failed to fetch categories: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const categories: Category[] = await response.json();
|
||||||
|
return categories;
|
||||||
};
|
};
|
||||||
|
@ -26,19 +26,21 @@ export type Script = {
|
|||||||
username: string | null;
|
username: string | null;
|
||||||
password: string | null;
|
password: string | null;
|
||||||
};
|
};
|
||||||
notes: [{
|
notes: [
|
||||||
text: string;
|
{
|
||||||
type: string;
|
text: string;
|
||||||
}]
|
type: string;
|
||||||
}
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
export type Category = {
|
export type Category = {
|
||||||
name: string;
|
name: string;
|
||||||
id: number;
|
id: number;
|
||||||
sort_order: number;
|
sort_order: number;
|
||||||
scripts: Script[];
|
scripts: Script[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ScriptList = {
|
export type Metadata = {
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
}
|
};
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"categories":
|
"categories":
|
||||||
[
|
[
|
||||||
{"name": "Miscellaneous", "id": 0, "sort_order": 99.0},
|
|
||||||
{"name": "Proxmox VE Tools", "id": 1, "sort_order": 1.0},
|
{"name": "Proxmox VE Tools", "id": 1, "sort_order": 1.0},
|
||||||
{"name": "Home Assistant", "id": 2, "sort_order": 2.0},
|
{"name": "AdBlocker - DNS", "id": 13, "sort_order": 2.0},
|
||||||
{"name": "Automation", "id": 3, "sort_order": 3.0},
|
{"name": "Automation", "id": 3, "sort_order": 3.0},
|
||||||
{"name": "MQTT", "id": 4, "sort_order": 4.0},
|
{"name": "Dashboards", "id": 15, "sort_order": 4.0},
|
||||||
{"name": "Database", "id": 5, "sort_order": 5.0},
|
{"name": "Database", "id": 5, "sort_order": 5.0},
|
||||||
{"name": "Zigbee - Zwave", "id": 6, "sort_order": 6.0},
|
{"name": "Docker - Kubernetes", "id": 8, "sort_order": 6.0},
|
||||||
{"name": "Monitoring - Analytics", "id": 7, "sort_order": 7.0},
|
{"name": "Document - Notes", "id": 14, "sort_order": 7.0},
|
||||||
{"name": "Docker - Kubernetes", "id": 8, "sort_order": 8.0},
|
{"name": "File - Code", "id": 16, "sort_order": 8.0},
|
||||||
{"name": "Operating System", "id": 9, "sort_order": 9.0},
|
{"name": "Home Assistant", "id": 2, "sort_order": 9.0},
|
||||||
{"name": "TurnKey", "id": 10, "sort_order": 10.0},
|
{"name": "Media - Photo", "id": 12, "sort_order": 10.0},
|
||||||
{"name": "Server - Networking", "id": 11, "sort_order": 11.0},
|
{"name": "Monitoring - Analytics", "id": 7, "sort_order": 11.0},
|
||||||
{"name": "Media - Photo", "id": 12, "sort_order": 12.0},
|
{"name": "MQTT", "id": 4, "sort_order": 12.0},
|
||||||
{"name": "AdBlocker - DNS", "id": 13, "sort_order": 13.0},
|
{"name": "NVR - DVR", "id": 17, "sort_order": 13.0},
|
||||||
{"name": "Document - Notes", "id": 14, "sort_order": 14.0},
|
{"name": "Operating System", "id": 9, "sort_order": 14.0},
|
||||||
{"name": "Dashboards", "id": 15, "sort_order": 15.0},
|
{"name": "Server - Networking", "id": 11, "sort_order": 15.0},
|
||||||
{"name": "File - Code", "id": 16, "sort_order": 16.0},
|
{"name": "TurnKey", "id": 10, "sort_order": 16.0},
|
||||||
{"name": "NVR - DVR", "id": 17, "sort_order": 17.0}
|
{"name": "Zigbee - Zwave", "id": 6, "sort_order": 17.0},
|
||||||
|
{"name": "Miscellaneous", "id": 0, "sort_order": 99.0}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user