From 0406049c892fb7ce5835ad067ce833f8c2587131 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:27:09 +0200 Subject: [PATCH] website --- .../json-editor/_components/InstallMethod.tsx | 60 ++-- .../src/app/json-editor/_components/Note.tsx | 2 +- .../src/app/json-editor/_schemas/schemas.ts | 4 +- frontend/src/app/json-editor/page.tsx | 157 +++------- frontend/src/app/page.tsx | 199 ++++++------ .../scripts/_components/ResourceDisplay.tsx | 42 +++ .../scripts/_components/ScriptInfoBlocks.tsx | 57 +--- .../app/scripts/_components/ScriptItem.tsx | 286 +++++++++--------- .../_components/ScriptItems/Alerts.tsx | 2 +- .../_components/ScriptItems/Buttons.tsx | 141 +++++---- .../ScriptItems/DefaultPassword.tsx | 13 +- .../ScriptItems/DefaultSettings.tsx | 42 +-- .../_components/ScriptItems/Description.tsx | 12 +- .../ScriptItems/InstallCommand.tsx | 136 ++++----- .../_components/ScriptItems/InterFaces.tsx | 45 +-- .../app/scripts/_components/VersionBadge.tsx | 13 + frontend/src/app/scripts/page.tsx | 2 +- frontend/src/lib/utils/resource-utils.ts | 7 + 18 files changed, 567 insertions(+), 653 deletions(-) create mode 100644 frontend/src/app/scripts/_components/ResourceDisplay.tsx create mode 100644 frontend/src/app/scripts/_components/VersionBadge.tsx create mode 100644 frontend/src/lib/utils/resource-utils.ts diff --git a/frontend/src/app/json-editor/_components/InstallMethod.tsx b/frontend/src/app/json-editor/_components/InstallMethod.tsx index 8a597c5..3f3a7f4 100644 --- a/frontend/src/app/json-editor/_components/InstallMethod.tsx +++ b/frontend/src/app/json-editor/_components/InstallMethod.tsx @@ -1,12 +1,6 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { OperatingSystems } from "@/config/siteConfig"; import { PlusCircle, Trash2 } from "lucide-react"; import { memo, useCallback, useRef } from "react"; @@ -20,21 +14,29 @@ type InstallMethodProps = { setZodErrors: (zodErrors: z.ZodError | null) => void; }; -function InstallMethod({ - script, - setScript, - setIsValid, - setZodErrors, -}: InstallMethodProps) { +function InstallMethod({ script, setScript, setIsValid, setZodErrors }: InstallMethodProps) { const cpuRefs = useRef<(HTMLInputElement | null)[]>([]); const ramRefs = useRef<(HTMLInputElement | null)[]>([]); const hddRefs = useRef<(HTMLInputElement | null)[]>([]); const addInstallMethod = useCallback(() => { setScript((prev) => { + const { type, slug } = prev; + const newMethodType = "default"; + + let scriptPath = ""; + + if (type === "pve") { + scriptPath = `tools/pve/${slug}.sh`; + } else if (type === "addon") { + scriptPath = `tools/addon/${slug}.sh`; + } else { + scriptPath = `${type}/${slug}.sh`; + } + const method = InstallMethodSchema.parse({ - type: "default", - script: `${prev.type}/${prev.slug}.sh`, + type: newMethodType, + script: scriptPath, resources: { cpu: null, ram: null, @@ -43,6 +45,7 @@ function InstallMethod({ version: null, }, }); + return { ...prev, install_methods: [...prev.install_methods, method], @@ -63,9 +66,7 @@ function InstallMethod({ if (key === "type") { updatedMethod.script = - value === "alpine" - ? `${prev.type}/alpine-${prev.slug}.sh` - : `${prev.type}/${prev.slug}.sh`; + value === "alpine" ? `${prev.type}/alpine-${prev.slug}.sh` : `${prev.type}/${prev.slug}.sh`; // Set OS to Alpine and reset version if type is alpine if (value === "alpine") { @@ -112,10 +113,7 @@ function InstallMethod({

Install Methods

{script.install_methods.map((method, index) => (
- updateInstallMethod(index, "type", value)}> @@ -205,9 +203,7 @@ function InstallMethod({ - {OperatingSystems.find( - (os) => os.name === method.resources.os, - )?.versions.map((version) => ( + {OperatingSystems.find((os) => os.name === method.resources.os)?.versions.map((version) => ( {version.name} @@ -215,22 +211,12 @@ function InstallMethod({
- ))} - diff --git a/frontend/src/app/json-editor/_components/Note.tsx b/frontend/src/app/json-editor/_components/Note.tsx index cc4c8ed..5827285 100644 --- a/frontend/src/app/json-editor/_components/Note.tsx +++ b/frontend/src/app/json-editor/_components/Note.tsx @@ -147,4 +147,4 @@ const NoteItem = memo( NoteItem.displayName = 'NoteItem'; -export default memo(Note); +export default memo(Note); \ No newline at end of file diff --git a/frontend/src/app/json-editor/_schemas/schemas.ts b/frontend/src/app/json-editor/_schemas/schemas.ts index ad9d16b..9ffe8f3 100644 --- a/frontend/src/app/json-editor/_schemas/schemas.ts +++ b/frontend/src/app/json-editor/_schemas/schemas.ts @@ -24,8 +24,8 @@ export const ScriptSchema = z.object({ slug: z.string().min(1, "Slug is required"), categories: z.array(z.number()), date_created: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").min(1, "Date is required"), - type: z.enum(["vm", "ct", "misc", "turnkey"], { - errorMap: () => ({ message: "Type must be either 'vm', 'ct', 'misc' or 'turnkey'" }) + type: z.enum(["vm", "ct", "pve", "addon", "turnkey"], { + errorMap: () => ({ message: "Type must be either 'vm', 'ct', 'pve', 'addon' or 'turnkey'" }) }), updateable: z.boolean(), privileged: z.boolean(), diff --git a/frontend/src/app/json-editor/page.tsx b/frontend/src/app/json-editor/page.tsx index 4e16155..1789b0a 100644 --- a/frontend/src/app/json-editor/page.tsx +++ b/frontend/src/app/json-editor/page.tsx @@ -5,18 +5,8 @@ import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { fetchCategories } from "@/lib/data"; @@ -66,29 +56,37 @@ export default function JSONGenerator() { .catch((error) => console.error("Error fetching categories:", error)); }, []); - const updateScript = useCallback( - (key: keyof Script, value: Script[keyof Script]) => { - setScript((prev) => { - const updated = { ...prev, [key]: value }; + const updateScript = useCallback((key: keyof Script, value: Script[keyof Script]) => { + setScript((prev) => { + const updated = { ...prev, [key]: value }; - if (key === "type" || key === "slug") { - updated.install_methods = updated.install_methods.map((method) => ({ + if (updated.slug && updated.type) { + updated.install_methods = updated.install_methods.map((method) => { + let scriptPath = ""; + + if (updated.type === "pve") { + scriptPath = `tools/pve/${updated.slug}.sh`; + } else if (updated.type === "addon") { + scriptPath = `tools/addon/${updated.slug}.sh`; + } else if (method.type === "alpine") { + scriptPath = `${updated.type}/alpine-${updated.slug}.sh`; + } else { + scriptPath = `${updated.type}/${updated.slug}.sh`; + } + + return { ...method, - script: - method.type === "alpine" - ? `${updated.type}/alpine-${updated.slug}.sh` - : `${updated.type}/${updated.slug}.sh`, - })); - } + script: scriptPath, + }; + }); + } - const result = ScriptSchema.safeParse(updated); - setIsValid(result.success); - setZodErrors(result.success ? null : result.error); - return updated; - }); - }, - [], - ); + const result = ScriptSchema.safeParse(updated); + setIsValid(result.success); + setZodErrors(result.success ? null : result.error); + return updated; + }); + }, []); const handleCopy = useCallback(() => { navigator.clipboard.writeText(JSON.stringify(script, null, 2)); @@ -101,13 +99,13 @@ export default function JSONGenerator() { const jsonString = JSON.stringify(script, null, 2); const blob = new Blob([jsonString], { type: "application/json" }); const url = URL.createObjectURL(blob); - + const a = document.createElement("a"); a.href = url; a.download = `${script.slug || "script"}.json`; document.body.appendChild(a); a.click(); - + URL.revokeObjectURL(url); document.body.removeChild(a); }, [script]); @@ -120,16 +118,13 @@ export default function JSONGenerator() { ); const formattedDate = useMemo( - () => - script.date_created ? format(script.date_created, "PPP") : undefined, + () => (script.date_created ? format(script.date_created, "PPP") : undefined), [script.date_created], ); const validationAlert = useMemo( () => ( - + {isValid ? "Valid JSON" : "Invalid JSON"} {isValid @@ -160,21 +155,13 @@ export default function JSONGenerator() { - updateScript("name", e.target.value)} - /> + updateScript("name", e.target.value)} />
- updateScript("slug", e.target.value)} - /> + updateScript("slug", e.target.value)} />
@@ -197,11 +184,7 @@ export default function JSONGenerator() { onChange={(e) => updateScript("description", e.target.value)} />
- +
@@ -209,10 +192,7 @@ export default function JSONGenerator() {
- updateScript("type", value)}> LXC Container Virtual Machine - Miscellaneous + PVE-Tool + Add-On
- - updateScript("updateable", checked) - } - /> + updateScript("updateable", checked)} />
- - updateScript("privileged", checked) - } - /> + updateScript("privileged", checked)} />
@@ -269,12 +237,7 @@ export default function JSONGenerator() { placeholder="Interface Port" type="number" value={script.interface_port || ""} - onChange={(e) => - updateScript( - "interface_port", - e.target.value ? Number(e.target.value) : null, - ) - } + onChange={(e) => updateScript("interface_port", e.target.value ? Number(e.target.value) : null)} />
- updateScript("documentation", e.target.value || null) - } + onChange={(e) => updateScript("documentation", e.target.value || null)} />
- +

Default Credentials

- +
{validationAlert}
- -
- +
             {JSON.stringify(script, null, 2)}
           
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 247b649..d06b557 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,4 +1,5 @@ "use client"; +import FAQ from "@/components/FAQ"; import AnimatedGradientText from "@/components/ui/animated-gradient-text"; import { Button } from "@/components/ui/button"; import { CardFooter } from "@/components/ui/card"; @@ -34,99 +35,109 @@ export default function Page() { }, [theme]); return ( -
- -
-
- - -
- -
- ❤️ - - Develop Instance - - -
- - - - Thank You! - - A big thank you to tteck and the many contributors who have - made this project possible. Your hard work is truly - appreciated by the entire Proxmox community! - - - - - - - -
+ <> +
+ +
+
+ + +
+ +
+ ❤️ + + Scripts by tteck + + +
+ + + + Thank You! + + A big thank you to tteck and the many contributors who have made this project possible. Your hard + work is truly appreciated by the entire Proxmox community! + + + + + + + +
-
-

- Beta Scripts -

-
-

- On this Website you can find a collection of scripts that are under development and only for testing. - We do not provide any support for these scripts when run in production, but you can help us by testing them and providing feedback. -

-
-
-
- - - -
-
-
-
- ); +
+

+ Make managing your Homelab a breeze +

+
+

+ We are a community-driven initiative that simplifies the setup of Proxmox Virtual Environment (VE). +

+

+ With 300+ scripts to help you manage your Proxmox VE environment. Whether you're a seasoned + user or a newcomer, we've got you covered. +

+
+
+
+ + + +
+
+ + {/* FAQ Section */} +
+
+
+

Frequently Asked Questions

+

+ Find answers to common questions about our Proxmox VE scripts +

+
+ +
+
+
+
+ + ); } diff --git a/frontend/src/app/scripts/_components/ResourceDisplay.tsx b/frontend/src/app/scripts/_components/ResourceDisplay.tsx new file mode 100644 index 0000000..7c9ed63 --- /dev/null +++ b/frontend/src/app/scripts/_components/ResourceDisplay.tsx @@ -0,0 +1,42 @@ +import { CPUIcon, HDDIcon, RAMIcon } from "@/components/icons/resource-icons"; +import { getDisplayValueFromRAM } from "@/lib/utils/resource-utils"; + +interface ResourceDisplayProps { + title: string; + cpu: number | null; + ram: number | null; + hdd: number | null; +} + +interface IconTextProps { + icon: React.ReactNode; + label: string; +} + +function IconText({ icon, label }: IconTextProps) { + return ( + + {icon} + {label} + + ); +} + +export function ResourceDisplay({ title, cpu, ram, hdd }: ResourceDisplayProps) { + const hasCPU = typeof cpu === "number" && cpu > 0; + const hasRAM = typeof ram === "number" && ram > 0; + const hasHDD = typeof hdd === "number" && hdd > 0; + + if (!hasCPU && !hasRAM && !hasHDD) return null; + + return ( +
+ {title} +
+ {hasCPU && } label={`${cpu} vCPU`} />} + {hasRAM && } label={getDisplayValueFromRAM(ram!)} />} + {hasHDD && } label={`${hdd} GB`} />} +
+
+ ); +} diff --git a/frontend/src/app/scripts/_components/ScriptInfoBlocks.tsx b/frontend/src/app/scripts/_components/ScriptInfoBlocks.tsx index 3713a16..7ee300a 100644 --- a/frontend/src/app/scripts/_components/ScriptInfoBlocks.tsx +++ b/frontend/src/app/scripts/_components/ScriptInfoBlocks.tsx @@ -1,12 +1,5 @@ import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { basePath, mostPopularScripts } from "@/config/siteConfig"; import { extractDate } from "@/lib/time"; import { Category, Script } from "@/lib/types"; @@ -23,7 +16,8 @@ export const getDisplayValueFromType = (type: string) => { return "LXC"; case "vm": return "VM"; - case "misc": + case "pve": + case "addon": return ""; default: return ""; @@ -35,7 +29,7 @@ export function LatestScripts({ items }: { items: Category[] }) { const latestScripts = useMemo(() => { if (!items) return []; - + const scripts = items.flatMap((category) => category.scripts || []); // Filter out duplicates by slug @@ -47,8 +41,7 @@ export function LatestScripts({ items }: { items: Category[] }) { }); return Array.from(uniqueScriptsMap.values()).sort( - (a, b) => - new Date(b.date_created).getTime() - new Date(a.date_created).getTime(), + (a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime(), ); }, [items]); @@ -59,7 +52,7 @@ export function LatestScripts({ items }: { items: Category[] }) { const goToPreviousPage = () => { setPage((prevPage) => prevPage - 1); }; - + const startIndex = (page - 1) * ITEMS_PER_PAGE; const endIndex = page * ITEMS_PER_PAGE; @@ -74,18 +67,12 @@ export function LatestScripts({ items }: { items: Category[] }) {

Newest Scripts

{page > 1 && ( -
+
Previous
)} {endIndex < latestScripts.length && ( -
+
{page === 1 ? "More.." : "Next"}
)} @@ -94,10 +81,7 @@ export function LatestScripts({ items }: { items: Category[] }) { )}
{latestScripts.slice(startIndex, endIndex).map((script) => ( - +
@@ -107,10 +91,7 @@ export function LatestScripts({ items }: { items: Category[] }) { height={64} width={64} alt="" - onError={(e) => - ((e.currentTarget as HTMLImageElement).src = - `/${basePath}/logo.png`) - } + onError={(e) => ((e.currentTarget as HTMLImageElement).src = `/${basePath}/logo.png`)} className="h-11 w-11 object-contain" />
@@ -126,9 +107,7 @@ export function LatestScripts({ items }: { items: Category[] }) {
- - {script.description} - + {script.description} - {linksVisible && } - -
-
-
- -
-
- - -
-
-
-

- How to {item.type == "misc" ? "use" : "install"} -

- -
- - -
-
- -
-
-
+ return ( +
+
+
+ ((e.currentTarget as HTMLImageElement).src = `/${basePath}/logo.png`)} + height={400} + alt={item.name} + unoptimized + />
- ); +
+
+
+
+

+ {item.name} + + + {getDisplayValueFromType(item.type)} + +

+
+ Added {extractDate(item.date_created)} + + + {os} {version} + +
+
+ {/* */} +
+
+ {defaultInstallMethod?.resources && ( + + )} + {item.install_methods.find((method) => method.type === "alpine")?.resources && ( + method.type === "alpine")!.resources!} + /> + )} +
+
+
+
+
+ +
+ +
+
+
+ ); } -export default ScriptItem; +function VersionInfo({ item }: { item: Script }) { + const { data: versions = [], isLoading } = useVersions(); + + if (isLoading || versions.length === 0) { + return

Loading versions...

; + } + + const matchedVersion = versions.find((v: AppVersion) => { + const cleanName = v.name.replace(/[^a-z0-9]/gi, "").toLowerCase(); + return cleanName === cleanSlug(item.slug) || cleanName.includes(cleanSlug(item.slug)); + }); + + if (!matchedVersion) return null; + + return {matchedVersion.version}; +} + +export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) { + const closeScript = () => { + window.history.pushState({}, document.title, window.location.pathname); + setSelectedScript(null); + }; + + return ( +
+
+
+

Selected Script

+ +
+ +
+
+ }> + + + + + + +
+
+

+ How to {item.type === "pve" ? "use" : item.type === "addon" ? "apply" : "install"} +

+ +
+ +
+ +
+
+ + +
+
+
+
+ ); +} diff --git a/frontend/src/app/scripts/_components/ScriptItems/Alerts.tsx b/frontend/src/app/scripts/_components/ScriptItems/Alerts.tsx index b915be3..c4c2902 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/Alerts.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/Alerts.tsx @@ -14,7 +14,7 @@ export default function Alerts({ item }: { item: Script }) { <> {item?.notes?.length > 0 && item.notes.map((note: NoteProps, index: number) => ( -
+

{ - const baseUrl = `https://raw.githubusercontent.com/community-scripts/${basePath}/main`; - return `${baseUrl}/install/${slug}-install.sh`; + const baseUrl = `https://raw.githubusercontent.com/community-scripts/${basePath}/main`; + return `${baseUrl}/install/${slug}-install.sh`; }; const generateSourceUrl = (slug: string, type: string) => { - const baseUrl = `https://raw.githubusercontent.com/community-scripts/${basePath}/main`; - return type === "vm" ? `${baseUrl}/vm/${slug}.sh` : `${baseUrl}/misc/${slug}.sh`; - return `${baseUrl}/misc/${slug}.sh`; + const baseUrl = `https://raw.githubusercontent.com/community-scripts/${basePath}/main`; + + switch (type) { + case "vm": + return `${baseUrl}/vm/${slug}.sh`; + case "pve": + return `${baseUrl}/tools/pve/${slug}.sh`; + case "addon": + return `${baseUrl}/tools/addon/${slug}.sh`; + default: + return `${baseUrl}/ct/${slug}.sh`; // fallback for "ct" + } }; const generateUpdateUrl = (slug: string) => { - const baseUrl = `https://raw.githubusercontent.com/community-scripts/${basePath}/main`; - return `${baseUrl}/ct/${slug}.sh`; + const baseUrl = `https://raw.githubusercontent.com/community-scripts/${basePath}/main`; + return `${baseUrl}/ct/${slug}.sh`; }; -interface ButtonLinkProps { - href: string; - icon: React.ReactNode; - text: string; +interface LinkItem { + href: string; + icon: React.ReactNode; + text: string; } -const ButtonLink = ({ href, icon, text }: ButtonLinkProps) => ( - -); - export default function Buttons({ item }: { item: Script }) { - const isCtOrDefault = ["ct"].includes(item.type); - const installSourceUrl = isCtOrDefault ? generateInstallSourceUrl(item.slug) : null; - const updateSourceUrl = isCtOrDefault ? generateUpdateUrl(item.slug) : null; - const sourceUrl = !isCtOrDefault ? generateSourceUrl(item.slug, item.type) : null; + const isCtOrDefault = ["ct"].includes(item.type); + const installSourceUrl = isCtOrDefault ? generateInstallSourceUrl(item.slug) : null; + const updateSourceUrl = isCtOrDefault ? generateUpdateUrl(item.slug) : null; + const sourceUrl = !isCtOrDefault ? generateSourceUrl(item.slug, item.type) : null; - const buttons = [ - item.website && { - href: item.website, - icon: , - text: "Website", - }, - item.documentation && { - href: item.documentation, - icon: , - text: "Documentation", - }, - installSourceUrl && { - href: installSourceUrl, - icon: , - text: "Install-Source", - }, - updateSourceUrl && { - href: updateSourceUrl, - icon: , - text: "Update-Source", - }, - sourceUrl && { - href: sourceUrl, - icon: , - text: "Source Code", - }, - ].filter(Boolean) as ButtonLinkProps[]; + const links = [ + item.website && { + href: item.website, + icon: , + text: "Website", + }, + item.documentation && { + href: item.documentation, + icon: , + text: "Documentation", + }, + installSourceUrl && { + href: installSourceUrl, + icon: , + text: "Install Source", + }, + updateSourceUrl && { + href: updateSourceUrl, + icon: , + text: "Update Source", + }, + sourceUrl && { + href: sourceUrl, + icon: , + text: "Source Code", + }, + ].filter(Boolean) as LinkItem[]; - return ( + if (links.length === 0) return null; -

- {buttons.map((props, index) => ( - - ))} -
- ); + return ( + + + + + + {links.map((link, index) => ( + + + {link.icon} + {link.text} + + + ))} + + + ); } diff --git a/frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx b/frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx index 15a9623..5034bc9 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx @@ -14,24 +14,19 @@ export default function DefaultPassword({ item }: { item: Script }) { }; return ( -
-
+
+

Default Login Credentials

- You can use the following credentials to login to the {item.name}{" "} - {item.type}. + You can use the following credentials to login to the {item.name} {item.type}.

{["username", "password"].map((type) => (
{type.charAt(0).toUpperCase() + type.slice(1)}:{" "} -
diff --git a/frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx b/frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx index bfa6175..33ae0b8 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx @@ -1,51 +1,29 @@ import { Script } from "@/lib/types"; export default function DefaultSettings({ item }: { item: Script }) { - const getDisplayValueFromRAM = (ram: number) => - ram >= 1024 ? `${Math.floor(ram / 1024)}GB` : `${ram}MB`; + const getDisplayValueFromRAM = (ram: number) => (ram >= 1024 ? `${Math.floor(ram / 1024)}GB` : `${ram}MB`); - const ResourceDisplay = ({ - settings, - title, - }: { - settings: (typeof item.install_methods)[0]; - title: string; - }) => { + const ResourceDisplay = ({ settings, title }: { settings: (typeof item.install_methods)[0]; title: string }) => { const { cpu, ram, hdd } = settings.resources; return (

{title}

CPU: {cpu}vCPU

-

- RAM: {getDisplayValueFromRAM(ram ?? 0)} -

+

RAM: {getDisplayValueFromRAM(ram ?? 0)}

HDD: {hdd}GB

); }; - const defaultSettings = item.install_methods.find( - (method) => method.type === "default", - ); - const defaultAlpineSettings = item.install_methods.find( - (method) => method.type === "alpine", - ); + const defaultSettings = item.install_methods.find((method) => method.type === "default"); + const defaultAlpineSettings = item.install_methods.find((method) => method.type === "alpine"); - const hasDefaultSettings = - defaultSettings?.resources && - Object.values(defaultSettings.resources).some(Boolean); + const hasDefaultSettings = defaultSettings?.resources && Object.values(defaultSettings.resources).some(Boolean); return ( - <> - {hasDefaultSettings && ( - - )} - {defaultAlpineSettings && ( - - )} - +
+ {hasDefaultSettings && } + {defaultAlpineSettings && } +
); } diff --git a/frontend/src/app/scripts/_components/ScriptItems/Description.tsx b/frontend/src/app/scripts/_components/ScriptItems/Description.tsx index d87277d..6a09f0a 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/Description.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/Description.tsx @@ -1,21 +1,11 @@ import TextCopyBlock from "@/components/TextCopyBlock"; import { Script } from "@/lib/types"; -import { AlertColors } from "@/config/siteConfig"; -import { AlertCircle, NotepadText } from "lucide-react"; -import { cn } from "@/lib/utils"; export default function Description({ item }: { item: Script }) { return (

Description

-

- - Only use for testing, not in production! -

-

+

{TextCopyBlock(item.description)}

diff --git a/frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx b/frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx index 48b5a2a..ff95097 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx @@ -5,85 +5,73 @@ import { Script } from "@/lib/types"; import { getDisplayValueFromType } from "../ScriptInfoBlocks"; const getInstallCommand = (scriptPath = "", isAlpine = false) => { - const url = `https://github.com/community-scripts/${basePath}/raw/main/${scriptPath}`; - return isAlpine - ? `bash -c "$(curl -fsSL ${url})"` - : `bash -c "$(curl -fsSL ${url})"`; + const url = `https://raw.githubusercontent.com/community-scripts/${basePath}/main/${scriptPath}`; + return isAlpine ? `bash -c "$(curl -fsSL ${url})"` : `bash -c "$(curl -fsSL ${url})"`; }; - export default function InstallCommand({ item }: { item: Script }) { - const alpineScript = item.install_methods.find( - (method) => method.type === "alpine", - ); + const alpineScript = item.install_methods.find((method) => method.type === "alpine"); - const defaultScript = item.install_methods.find( - (method) => method.type === "default", - ); + const defaultScript = item.install_methods.find((method) => method.type === "default"); - const renderInstructions = (isAlpine = false) => ( + const renderInstructions = (isAlpine = false) => ( + <> +

+ {isAlpine ? ( + <> + As an alternative option, you can use Alpine Linux and the {item.name} package to create a {item.name}{" "} + {getDisplayValueFromType(item.type)} container with faster creation time and minimal system resource usage. + You are also obliged to adhere to updates provided by the package maintainer. + + ) : item.type === "pve" ? ( + <> + To use the {item.name} script, run the command below **only** in the Proxmox VE Shell. This script is + intended for managing or enhancing the host system directly. + + ) : item.type === "addon" ? ( + <> + This script enhances an existing setup. You can use it inside a running LXC container or directly on the + Proxmox VE host to extend functionality with {item.name}. + + ) : ( + <> + To create a new Proxmox VE {item.name} {getDisplayValueFromType(item.type)}, run the command below in the + Proxmox VE Shell. + + )} +

+ {isAlpine && ( +

+ To create a new Proxmox VE Alpine-{item.name} {getDisplayValueFromType(item.type)}, run the command below in + the Proxmox VE Shell. +

+ )} + + ); + + return ( +
+ {alpineScript ? ( + + + Default + Alpine Linux + + + {renderInstructions()} + {getInstallCommand(defaultScript?.script)} + + + {renderInstructions(true)} + {getInstallCommand(alpineScript.script, true)} + + + ) : defaultScript?.script ? ( <> -

- {isAlpine ? ( - <> - As an alternative option, you can use Alpine Linux and the{" "} - {item.name} package to create a {item.name}{" "} - {getDisplayValueFromType(item.type)} container with faster creation - time and minimal system resource usage. You are also obliged to - adhere to updates provided by the package maintainer. - - ) : item.type == "misc" ? ( - <> - To use the {item.name} script, run the command below in the shell. - - ) : ( - <> - {" "} - To create a new Proxmox VE {item.name}{" "} - {getDisplayValueFromType(item.type)}, run the command below in the - Proxmox VE Shell. - - )} -

- {isAlpine && ( -

- To create a new Proxmox VE Alpine-{item.name}{" "} - {getDisplayValueFromType(item.type)}, run the command below in the - Proxmox VE Shell -

- )} + {renderInstructions()} + {getInstallCommand(defaultScript.script)} - ); - - return ( -
- {alpineScript ? ( - - - Default - Alpine Linux - - - {renderInstructions()} - - {getInstallCommand(defaultScript?.script)} - - - - {renderInstructions(true)} - - {getInstallCommand(alpineScript.script, true)} - - - - ) : defaultScript?.script ? ( - <> - {renderInstructions()} - - {getInstallCommand(defaultScript.script)} - - - ) : null} -
- ); + ) : null} +
+ ); } diff --git a/frontend/src/app/scripts/_components/ScriptItems/InterFaces.tsx b/frontend/src/app/scripts/_components/ScriptItems/InterFaces.tsx index ea4e6c0..d7ea148 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/InterFaces.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/InterFaces.tsx @@ -4,39 +4,18 @@ import { Script } from "@/lib/types"; import { cn } from "@/lib/utils"; import { ClipboardIcon } from "lucide-react"; -const CopyButton = ({ - label, - value, -}: { - label: string; - value: string | number; -}) => ( - - {value} - handleCopy(label, String(value))} - className="size-4 cursor-pointer" - /> - -); - export default function InterFaces({ item }: { item: Script }) { - return ( -
- {item.interface_port !== null ? ( -
-

- {"Default Interface:"} -

{" "} - - -
- ) : null} + return ( +
+ {item.interface_port !== null ? ( +
+

Default Interface:

+ + {item.interface_port} + handleCopy("default interface", String(item.interface_port))} className="size-4 cursor-pointer" /> +
- ); + ) : null} +
+ ); } diff --git a/frontend/src/app/scripts/_components/VersionBadge.tsx b/frontend/src/app/scripts/_components/VersionBadge.tsx new file mode 100644 index 0000000..712893d --- /dev/null +++ b/frontend/src/app/scripts/_components/VersionBadge.tsx @@ -0,0 +1,13 @@ +import { AppVersion } from "@/lib/types"; + +interface VersionBadgeProps { + version: AppVersion; +} + +export function VersionBadge({ version }: VersionBadgeProps) { + return ( +
+ {version.version} +
+ ); +} diff --git a/frontend/src/app/scripts/page.tsx b/frontend/src/app/scripts/page.tsx index a02f80b..590b673 100644 --- a/frontend/src/app/scripts/page.tsx +++ b/frontend/src/app/scripts/page.tsx @@ -2,7 +2,7 @@ export const dynamic = "force-static"; -import ScriptItem from "@/app/scripts/_components/ScriptItem"; +import { ScriptItem } from "@/app/scripts/_components/ScriptItem"; import { fetchCategories } from "@/lib/data"; import { Category, Script } from "@/lib/types"; import { Loader2 } from "lucide-react"; diff --git a/frontend/src/lib/utils/resource-utils.ts b/frontend/src/lib/utils/resource-utils.ts new file mode 100644 index 0000000..25e0966 --- /dev/null +++ b/frontend/src/lib/utils/resource-utils.ts @@ -0,0 +1,7 @@ +export function getDisplayValueFromRAM(ram: number): string { + return ram >= 1024 ? `${Math.floor(ram / 1024)}GB` : `${ram}MB`; +} + +export function cleanSlug(slug: string): string { + return slug.replace(/[^a-z0-9]/gi, "").toLowerCase(); +}