From 93fd495f65dbde4ea7e8c2933ecb64d13d9e460e Mon Sep 17 00:00:00 2001 From: Bram Suurd <78373894+BramSuurdje@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:47:04 +0100 Subject: [PATCH] Switch from Pocketbase data retrieval to JSON (#100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new animation for switching themes. * Remove unused metadata files from testing * increase duration on theme switch * Reduce animation duration for view transition effect to improve responsiveness * Fetch categories and scripts from external sources, updating `GET` endpoint to aggregate data. Adjust type definitions for Script and Category * Refactor all components to use data from new API * Refactor `InterFaces` component to use updated `Script` type and streamline interface/port handling for better clarity * Refactor `CommandMenu` component to utilize updated `Category` and `Script` types, simplifying the sorting logic and enhancing clarity * Fix animation duration in `globals.css` to ensure proper view transition functionality across the application * Remove unnecessary console log for file name in `fetchAllMetaDataFiles` to clean up code * Refactor category fetching in `ScriptContent` and `CommandMenu` to utilize centralized `fetchCategories` for improved maintainability * Use `formattedBadge` in `ScriptAccordion` and `CommandMenu` for consistent badge rendering across script types * Refactor source URL generation in `Buttons` component to enhance clarity and streamline the installation script logic * Check default settings availability in `DefaultSettings` component and handle undefined values more gracefully in rendering * Fix install command generation to handle optional script parameter and update copy button logic for improved functionality * Add most popular scripts feature and update script rendering logic in `ScriptInfoBlocks` component * Enhance `ScriptItem` component to display correct type naming alongside script name for better clarity in the UI * Add conversion utility to display RAM in GB for better readability in `DefaultSettings` component * Refactor Next.js config to use dynamic basePath and update sitemap URLs for improved adaptability and host configuration * Refactor site configuration to utilize centralized settings for analytics and base path; replace PocketBase imports with new data module * Refactor sitemap generation to use centralized basePath from config, enhancing adaptability for URL management * Refactor to replace PocketBase with a new data module across components * Refactor layout to use centralized analytics configuration * Update deployment workflow to include JSON files for GitHub Pages publishing * Remove caching step from GitHub Pages deploy workflow to avoid caching * Remove basePath from Next.js config to simplify configuration and avoid potential issues with path resolution * Add category sorting and fetching logic in data.ts * Add analytics configuration and basePath to siteConfig * Remove obsolete environment files for analytics and PocketBase * Update sitemap to use a fixed domain for the generated sitemap instead of deriving from headers * Refactor layout to utilize basePath for metadata base URL and image links for better configurability * use cleaner `basePath` variable around codebase for easier management * Update frontend/src/app/api/categories/route.ts Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/app/api/categories/route.ts Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/app/api/categories/route.ts Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/components/CommandMenu.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/components/ui/theme-toggle.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/components/CommandMenu.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/app/api/categories/route.ts Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/app/api/categories/route.ts Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update src/lib/data.ts with necessary changes. Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update src/app/api/categories/route.ts with necessary modifications * Update frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update src/components/CommandMenu.tsx with necessary improvements * Add renamed themetoggle * Update frontend/src/app/scripts/_components/ScriptInfoBlocks.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx with new settings configuration * Update src/app/scripts/_components/ScriptInfoBlocks.tsx with enhancements and fixes * Update src/app/scripts/_components/ScriptItems/InstallCommand.tsx * Update src/app/scripts/_components/ScriptItem.tsx * Update src/app/scripts/_components/ScriptAccordion.tsx with necessary adjustments and improvements * Update Interfaces to use strict check * updated interfaces to use normal string label instead of jsx * Update configuration to use environment variable for BASE_PATH and reflect changes in siteConfig * force static base path * Update CommandMenu.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update DefaultSettings.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Update DefaultSettings.tsx Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> * Ensure fetchScripts returns a typed Script array by specifying return type in map function * Remove commented-out import for unused Category type in CommandMenu component * Fix fetch URLs by removing unnecessary slashes and ensure proper return type in fetchScripts map function * Refactor MostViewedScripts to ensure proper type annotations and improve array concatenation method for better readability * Update BASE_PATH handling in next.config and fix fetch URLs to ensure correct path structure in API routes --------- Co-authored-by: Håvard Gjøby Thom <34199185+havardthom@users.noreply.github.com> --- .github/workflows/deploy-pages.yml | 9 +- frontend/.env.local | 3 - frontend/example.env | 4 - frontend/next.config.mjs | 4 +- frontend/package-lock.json | 11 ++- frontend/package.json | 3 +- frontend/public/metadata/docker.json | 23 ----- .../public/metadata/nginxproxymanager.json | 20 ---- frontend/src/app/api/categories/route.ts | 42 +++++++-- frontend/src/app/layout.tsx | 16 +++- frontend/src/app/manifest.ts | 7 +- frontend/src/app/page.tsx | 3 +- frontend/src/app/robots.ts | 3 +- .../scripts/_components/ScriptAccordion.tsx | 77 +++++++--------- .../scripts/_components/ScriptInfoBlocks.tsx | 90 ++++++++---------- .../app/scripts/_components/ScriptItem.tsx | 11 ++- .../_components/ScriptItems/Alerts.tsx | 6 +- .../_components/ScriptItems/Buttons.tsx | 51 +++-------- .../ScriptItems/DefaultPassword.tsx | 12 +-- .../ScriptItems/DefaultSettings.tsx | 36 ++++++-- .../ScriptItems/InstallCommand.tsx | 50 +++++----- .../_components/ScriptItems/InterFaces.tsx | 17 ++-- .../_components/ScriptItems/Tooltips.tsx | 4 +- .../src/app/scripts/_components/Sidebar.tsx | 2 +- frontend/src/app/scripts/page.tsx | 37 ++------ frontend/src/app/sitemap.ts | 13 ++- frontend/src/components/CommandMenu.tsx | 91 +++++++++---------- frontend/src/components/Footer.tsx | 3 +- frontend/src/components/Navbar.tsx | 29 +----- frontend/src/components/ui/codeblock.tsx | 3 +- .../components/ui/star-on-github-button.tsx | 5 +- frontend/src/components/ui/theme-toggle.tsx | 42 +++++++++ frontend/src/config/siteConfig.tsx | 19 +++- frontend/src/lib/data.ts | 23 +++++ frontend/src/lib/pocketbase.ts | 10 -- frontend/src/lib/types.ts | 87 ++++++++---------- frontend/src/styles/globals.css | 61 +++++++++++++ 37 files changed, 474 insertions(+), 453 deletions(-) delete mode 100644 frontend/.env.local delete mode 100644 frontend/example.env delete mode 100644 frontend/public/metadata/docker.json delete mode 100644 frontend/public/metadata/nginxproxymanager.json create mode 100644 frontend/src/components/ui/theme-toggle.tsx create mode 100644 frontend/src/lib/data.ts delete mode 100644 frontend/src/lib/pocketbase.ts diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index ca23f3268..435c5c801 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -9,6 +9,7 @@ on: branches: ["main"] paths: - frontend/** + - json/** workflow_dispatch: @@ -57,14 +58,6 @@ jobs: uses: actions/configure-pages@v5 with: static_site_generator: next - - name: Restore cache - uses: actions/cache@v4 - with: - path: | - frontend/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('frontend/**/package-lock.json', 'frontend/**/yarn.lock') }}-${{ hashFiles('frontend/**.[jt]s', 'frontend/**.[jt]sx') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('frontend/**/package-lock.json', 'frontend/**/yarn.lock') }}- - name: Install dependencies run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} --legacy-peer-deps - name: Build with Next.js diff --git a/frontend/.env.local b/frontend/.env.local deleted file mode 100644 index 478cdb202..000000000 --- a/frontend/.env.local +++ /dev/null @@ -1,3 +0,0 @@ -NEXT_PUBLIC_ANALYTICS_TOKEN="b60d3032-1a11-4244-a100-81d26c5c49a7" -NEXT_PUBLIC_ANALYTICS_URL="analytics.proxmoxve-scripts.com" -NEXT_PUBLIC_POCKETBASE_URL="https://pocketbase.proxmoxve-scripts.com" diff --git a/frontend/example.env b/frontend/example.env deleted file mode 100644 index fcdd4567f..000000000 --- a/frontend/example.env +++ /dev/null @@ -1,4 +0,0 @@ -NEXT_PUBLIC_POCKETBASE_URL=https://pocketbase.proxmoxve-scripts.com -NEXT_PUBLIC_ANALYTICS_URL=https://analytics.proxmoxve-scripts.com -NEXT_PUBLIC_ANALYTICS_TOKEN=b60d130323-1a11-4244-a1010-81d263c5c49a7 -NODE_ENV=production diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index 528a6a3e3..a2529f34c 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -15,11 +15,11 @@ const nextConfig = { }, env: { - NEXT_PUBLIC_BUILD_TIME: `${Date.now()}`, + BASE_PATH: "ProxmoxVE", }, output: "export", - basePath: "/ProxmoxVE", + basePath: `/${process.env.BASE_PATH}`, }; export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d0ac45ee3..c2e0b68f0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -21,7 +22,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "framer-motion": "^11.11.10", + "framer-motion": "^11.11.11", "fuse.js": "^7.0.0", "lucide-react": "^0.453.0", "mini-svg-data-uri": "^1.4.4", @@ -1335,6 +1336,14 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.1.tgz", + "integrity": "sha512-QvYompk0X+8Yjlo/Fv4McrzxohDdM5GgLHyQcPpcsPvlOSXCGFjdbuyGL5dzRbg0GpknAjQJJZzdiRK7iWVuFQ==", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.x" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index be0c307b4..6bd130d2b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -31,7 +32,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "framer-motion": "^11.11.10", + "framer-motion": "^11.11.11", "fuse.js": "^7.0.0", "lucide-react": "^0.453.0", "mini-svg-data-uri": "^1.4.4", diff --git a/frontend/public/metadata/docker.json b/frontend/public/metadata/docker.json deleted file mode 100644 index 55e76004d..000000000 --- a/frontend/public/metadata/docker.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "slug": "docker", - "logo": "https://raw.githubusercontent.com/loganmarchione/homelab-svg-assets/main/assets/docker.svg", - "description": "Docker is an open-source project for automating the deployment of applications as portable, self-sufficient containers.", - "date_created": "2024-05-02", - "website": "https://www.docker.com/", - "documentation": "", - "default_credentials": { - "username": "", - "password": "" - }, - "alerts": [ - { - "alert": "If the LXC is created Privileged, the script will automatically set up USB passthrough." - }, - { - "alert": "Run Compose V2 by replacing the hyphen (-) with a space, using `docker compose`, instead of `docker-compose`." - }, - { - "alert": "Options to Install Portainer and/or Docker Compose V2" - } - ] -} diff --git a/frontend/public/metadata/nginxproxymanager.json b/frontend/public/metadata/nginxproxymanager.json deleted file mode 100644 index 6424080f4..000000000 --- a/frontend/public/metadata/nginxproxymanager.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "slug": "nginxproxymanager", - "logo": "https://raw.githubusercontent.com/loganmarchione/homelab-svg-assets/main/assets/nginxproxymanager.svg", - "description": "Nginx Proxy Manager is a tool that provides a web-based interface to manage Nginx reverse proxies. It enables users to easily and securely expose their services to the internet by providing features such as HTTPS encryption, domain mapping, and access control. It eliminates the need for manual configuration of Nginx reverse proxies, making it easy for users to quickly and securely expose their services to the public.", - "date_created": "2024-05-02", - "website": "https://nginxproxymanager.com/", - "documentation": "", - "default_credentials": { - "username": "admin", - "password": "admin" - }, - "alerts": [ - { - "alert": "Since there are hundreds of Certbot instances, it's necessary to install the specific Certbot of your preference." - }, - { - "alert": "This is another example of an alert." - } - ] -} diff --git a/frontend/src/app/api/categories/route.ts b/frontend/src/app/api/categories/route.ts index 6b6368fbf..52470a129 100644 --- a/frontend/src/app/api/categories/route.ts +++ b/frontend/src/app/api/categories/route.ts @@ -1,23 +1,45 @@ -import { pb } from "@/lib/pocketbase"; -import { Category } from "@/lib/types"; +import { basePath } from "@/config/siteConfig"; +import { Category, Script } from "@/lib/types"; import { NextResponse } from "next/server"; export const dynamic = "force-static"; +const fetchCategories = async (): Promise => { + const response = await fetch( + `https://raw.githubusercontent.com/community-scripts/${basePath}/refs/heads/main/json/metadata.json`, + ); + const data = await response.json(); + return data.categories; +}; + +const fetchScripts = async (): Promise => { + const response = await fetch( + `https://api.github.com/repos/community-scripts/${basePath}/contents/json`, + ); + const files: { download_url: string }[] = await response.json(); + const scripts = await Promise.all( + files.map(async (file) : Promise - +
diff --git a/frontend/src/app/manifest.ts b/frontend/src/app/manifest.ts index 0e4cb5dd1..1cfc38f5f 100644 --- a/frontend/src/app/manifest.ts +++ b/frontend/src/app/manifest.ts @@ -1,3 +1,4 @@ +import { basePath } from "@/config/siteConfig"; import type { MetadataRoute } from "next"; export const generateStaticParams = () => { @@ -9,13 +10,13 @@ export default function manifest(): MetadataRoute.Manifest { name: "Proxmox VE Helper-Scripts", short_name: "Proxmox VE Helper-Scripts", description: - "A Re-designed Front-end for the Proxmox VE Helper-Scripts Repository. Featuring over 150+ scripts to help you manage your Proxmox VE environment.", + "A Re-designed Front-end for the Proxmox VE Helper-Scripts Repository. Featuring over 200+ scripts to help you manage your Proxmox VE environment.", theme_color: "#030712", background_color: "#030712", display: "standalone", orientation: "portrait", - scope: "/Proxmox/", - start_url: "/Proxmox/", + scope: `${basePath}`, + start_url: `${basePath}`, icons: [ { src: "logo.png", diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index ea486cb02..67577c6c7 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -11,6 +11,7 @@ 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 { basePath } from "@/config/siteConfig"; function CustomArrowRightIcon() { return ; @@ -80,7 +81,7 @@ export default function Page() {
)}
- {latestScripts.slice(startIndex, endIndex).map((item) => ( + {latestScripts.slice(startIndex, endIndex).map((script) => (

- {item.title} {item.item_type} + {script.name} {getDisplayValueFromType(script.type)}

- {extractDate(item.created)} + {extractDate(script.date_created)}

- {item.description} + {script.description} @@ -106,7 +121,7 @@ export function LatestScripts({ items }: { items: Category[] }) { View Script @@ -121,29 +136,12 @@ export function LatestScripts({ items }: { items: Category[] }) { } export function MostViewedScripts({ items }: { items: Category[] }) { - const [page, setPage] = useState(1); - - const mostViewedScripts = useMemo(() => { - if (!items) return []; - const scripts = items.flatMap((category) => category.expand.items || []); - const mostViewedScripts = scripts - .filter((script) => script.isMostViewed) - .map((script) => ({ - ...script, - })); - return mostViewedScripts; - }, [items]); - - const goToNextPage = () => { - setPage((prevPage) => prevPage + 1); - }; - - const goToPreviousPage = () => { - setPage((prevPage) => prevPage - 1); - }; - - const startIndex = (page - 1) * ITEMS_PER_PAGE; - const endIndex = page * ITEMS_PER_PAGE; + const mostViewedScripts = items.reduce((acc: Script[], category) => { + const foundScripts = category.scripts.filter((script) => + mostPopularScripts.includes(script.name), + ); + return acc.concat(foundScripts); + }, []); return (
@@ -153,9 +151,9 @@ export function MostViewedScripts({ items }: { items: Category[] }) { )}
- {mostViewedScripts.slice(startIndex, endIndex).map((item) => ( + {mostViewedScripts.map((script) => ( @@ -163,7 +161,7 @@ export function MostViewedScripts({ items }: { items: Category[] }) {

- {item.title} {item.item_type} + {script.name} {getDisplayValueFromType(script.type)}

- {extractDate(item.created)} + {extractDate(script.date_created)}

- {item.description} + {script.description} @@ -191,7 +189,7 @@ export function MostViewedScripts({ items }: { items: Category[] }) { @@ -202,18 +200,6 @@ export function MostViewedScripts({ items }: { items: Category[] }) { ))}
-
- {page > 1 && ( - - )} - {endIndex < mostViewedScripts.length && ( - - )} -
); } diff --git a/frontend/src/app/scripts/_components/ScriptItem.tsx b/frontend/src/app/scripts/_components/ScriptItem.tsx index d1e273e78..840246d9c 100644 --- a/frontend/src/app/scripts/_components/ScriptItem.tsx +++ b/frontend/src/app/scripts/_components/ScriptItem.tsx @@ -5,6 +5,7 @@ import { Script } from "@/lib/types"; import { X } from "lucide-react"; import Image from "next/image"; +import { getDisplayValueFromType } from "./ScriptInfoBlocks"; import Alerts from "./ScriptItems/Alerts"; import Buttons from "./ScriptItems/Buttons"; import DefaultPassword from "./ScriptItems/DefaultPassword"; @@ -39,21 +40,21 @@ function ScriptItem({
((e.currentTarget as HTMLImageElement).src = "/logo.png") } height={400} - alt={item.title} + alt={item.name} unoptimized />
-

{item.title}

+

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

- Date added: {extractDate(item.created)} + Date added: {extractDate(item.date_created)}

@@ -76,7 +77,7 @@ function ScriptItem({

- How to {item.item_type ? "install" : "use"} + How to {item.type ? "install" : "use"}

diff --git a/frontend/src/app/scripts/_components/ScriptItems/Alerts.tsx b/frontend/src/app/scripts/_components/ScriptItems/Alerts.tsx index db54f16d2..e4c56e160 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/Alerts.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/Alerts.tsx @@ -5,12 +5,12 @@ import { Info } from "lucide-react"; export default function Alerts({ item }: { item: Script }) { return ( <> - {item.expand?.alerts?.length > 0 && - item.expand.alerts.map((alert: any, index: number) => ( + {item?.notes?.length > 0 && + item.notes.map((note: any, index: number) => (

- {TextCopyBlock(alert.content)} + {TextCopyBlock(note.text)}

))} diff --git a/frontend/src/app/scripts/_components/ScriptItems/Buttons.tsx b/frontend/src/app/scripts/_components/ScriptItems/Buttons.tsx index 89bc2c0bd..00a667827 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/Buttons.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/Buttons.tsx @@ -1,33 +1,18 @@ import { Button } from "@/components/ui/button"; +import { basePath } from "@/config/siteConfig"; import { Script } from "@/lib/types"; -import { BookOpenText, Code, ExternalLink, Globe } from "lucide-react"; +import { BookOpenText, Code, Globe } from "lucide-react"; import Link from "next/link"; -import { useMemo } from "react"; + +const generateSourceUrl = (slug: string, type: string) => { + if (type === "ct") { + return `https://raw.githubusercontent.com/community-scripts/${basePath}/main/install/${slug}-install.sh`; + } else { + return `https://raw.githubusercontent.com/community-scripts/${basePath}/main/${type}/${slug}.sh`; + } +}; export default function Buttons({ item }: { item: Script }) { - const pattern = useMemo( - () => - /(https:\/\/github\.com\/community-scripts\/ProxmoxVE\/raw\/main\/(ct|misc|vm)\/([^\/]+)\.sh)/, - [], - ); - - const transformUrlToInstallScript = (url: string): string => { - if (url.includes("/pve/")) { - return url; - } else if (url.includes("/ct/")) { - return url.replace("/ct/", "/install/").replace(/\.sh$/, "-install.sh"); - } - return url; - }; - - const sourceUrl = useMemo(() => { - if (item.installCommand) { - const match = item.installCommand.match(pattern); - return match ? transformUrlToInstallScript(match[0]) : null; - } - return null; - }, [item.installCommand, pattern]); - return (
{item.website && ( @@ -49,26 +34,16 @@ export default function Buttons({ item }: { item: Script }) { )} - {item.post_install && ( + { - )} - {item.installCommand && sourceUrl && ( - - )} + }
); } diff --git a/frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx b/frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx index 93c561564..55193d8f9 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/DefaultPassword.tsx @@ -4,7 +4,7 @@ import handleCopy from "@/components/handleCopy"; import { Script } from "@/lib/types"; export default function DefaultPassword({ item }: { item: Script }) { - const hasDefaultLogin = item?.expand?.default_login !== undefined; + const hasDefaultLogin = item.default_credentials.username && item.default_credentials.password; return (
@@ -17,7 +17,7 @@ export default function DefaultPassword({ item }: { item: Script }) {

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

Username:{" "} @@ -25,10 +25,10 @@ export default function DefaultPassword({ item }: { item: Script }) { variant={"secondary"} size={"null"} onClick={() => - handleCopy("username", item.expand.default_login.username) + handleCopy("username", item.default_credentials.username ?? "") } > - {item.expand.default_login.username} + {item.default_credentials.username}
@@ -37,10 +37,10 @@ export default function DefaultPassword({ item }: { item: Script }) { variant={"secondary"} size={"null"} onClick={() => - handleCopy("password", item.expand.default_login.password) + handleCopy("password", item.default_credentials.password ?? "") } > - {item.expand.default_login.password} + {item.default_credentials.password}
diff --git a/frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx b/frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx index 05f3ae627..f1b2d8bbd 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/DefaultSettings.tsx @@ -1,35 +1,53 @@ import { Script } from "@/lib/types"; export default function DefaultSettings({ item }: { item: Script }) { - const hasAlpineScript = item?.expand?.alpine_script !== undefined; + const defaultSettings = item.install_methods.find( + (method) => method.type === "default", + ); + + const defaultSettingsAvailable = + defaultSettings?.resources.cpu || + defaultSettings?.resources.ram || + defaultSettings?.resources.hdd; + + const defaultAlpineSettings = item.install_methods.find( + (method) => method.type === "alpine", + ); + + const getDisplayValueFromRAM = (ram: number) => { + if (ram >= 1024) { + return (ram / 1024).toFixed(0) + "GB"; + } + return ram + "MB"; + }; return ( <> - {item.default_cpu && ( + {defaultSettingsAvailable && (

Default settings

- CPU: {item.default_cpu} + CPU: {defaultSettings?.resources.cpu}vCPU

- RAM: {item.default_ram} + RAM: {getDisplayValueFromRAM(defaultSettings?.resources.ram ?? 0)}

- HDD: {item.default_hdd} + HDD: {defaultSettings?.resources.hdd}GB

)} - {hasAlpineScript && ( + {defaultAlpineSettings && (

Default Alpine settings

- CPU: {item.expand.alpine_script.default_cpu} + CPU: {defaultAlpineSettings?.resources.cpu}vCPU

- RAM: {item.expand.alpine_script.default_ram} + RAM: {getDisplayValueFromRAM(defaultAlpineSettings?.resources.ram ?? 0)}

- HDD: {item.expand.alpine_script.default_hdd} + HDD: {defaultAlpineSettings?.resources.hdd}GB

)} diff --git a/frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx b/frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx index 31e1e302c..795569b16 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/InstallCommand.tsx @@ -1,33 +1,43 @@ import CodeCopyButton from "@/components/ui/code-copy-button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { basePath } from "@/config/siteConfig"; import { Script } from "@/lib/types"; +const getInstallCommand = (scriptPath?: string) => { + return `bash -c "$(wget -qLO - https://github.com/community-scripts/${basePath}/raw/main/${scriptPath})"`; +} + export default function InstallCommand({ item }: { item: Script }) { - const { title, item_type, installCommand, expand } = item; - const hasAlpineScript = expand?.alpine_script !== undefined; + const alpineScript = item.install_methods.find( + (method) => method.type === "alpine", + ); + + const defaultScript = item.install_methods.find( + (method) => method.type === "default" + ); const renderInstructions = (isAlpine = false) => ( <>

{isAlpine ? ( <> - As an alternative option, you can use Alpine Linux and the {title}{" "} - package to create a {title} {item_type} container with faster + As an alternative option, you can use Alpine Linux and the {item.name}{" "} + package to create a {item.name} {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 ? ( + ) : item.type ? ( <> - To create a new Proxmox VE {title} {item_type}, run the command + To create a new Proxmox VE {item.name} {item.type}, run the command below in the Proxmox VE Shell. ) : ( - <>To use the {title} script, run the command below in the shell. + <>To use the {item.name} script, run the command below in the shell. )}

{isAlpine && (

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

)} @@ -36,7 +46,7 @@ export default function InstallCommand({ item }: { item: Script }) { return (
- {hasAlpineScript ? ( + {alpineScript ? ( Default @@ -44,25 +54,23 @@ export default function InstallCommand({ item }: { item: Script }) { {renderInstructions()} - {installCommand} + {getInstallCommand(defaultScript?.script)} - {expand.alpine_script && ( - <> - {renderInstructions(true)} - - {expand.alpine_script.installCommand} - - - )} + {renderInstructions(true)} + + {getInstallCommand(alpineScript.script)} + - ) : ( + ) : defaultScript?.script ? ( <> {renderInstructions()} - {installCommand && {installCommand}} + + {getInstallCommand(defaultScript.script)} + - )} + ) : null}
); } diff --git a/frontend/src/app/scripts/_components/ScriptItems/InterFaces.tsx b/frontend/src/app/scripts/_components/ScriptItems/InterFaces.tsx index e5bab0e84..2d2ae1f11 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/InterFaces.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/InterFaces.tsx @@ -2,11 +2,7 @@ import { Button, buttonVariants } from "@/components/ui/button"; import handleCopy from "@/components/handleCopy"; import { cn } from "@/lib/utils"; import { ClipboardIcon } from "lucide-react"; - -interface Item { - interface?: string; - port?: number; -} +import { Script } from "@/lib/types"; const CopyButton = ({ label, @@ -24,19 +20,18 @@ const CopyButton = ({ ); -export default function InterFaces({ item }: { item: Item }) { - const { interface: iface, port } = item; +export default function InterFaces({item} : {item : Script}) { return (
- {iface || (port && port !== 0) ? ( + {item.interface_port !== null ? (

- {iface ? "Interface:" : "Default Port:"} + {"Default Interface:"}

{" "}
) : null} diff --git a/frontend/src/app/scripts/_components/ScriptItems/Tooltips.tsx b/frontend/src/app/scripts/_components/ScriptItems/Tooltips.tsx index f0af794ba..7e70b68e9 100644 --- a/frontend/src/app/scripts/_components/ScriptItems/Tooltips.tsx +++ b/frontend/src/app/scripts/_components/ScriptItems/Tooltips.tsx @@ -37,11 +37,11 @@ export default function Tooltips({ item }: { item: Script }) { content="This script will be run in a privileged LXC" /> )} - {item.isUpdateable && ( + {item.updateable && ( )}
diff --git a/frontend/src/app/scripts/_components/Sidebar.tsx b/frontend/src/app/scripts/_components/Sidebar.tsx index a993e118b..f1618e170 100644 --- a/frontend/src/app/scripts/_components/Sidebar.tsx +++ b/frontend/src/app/scripts/_components/Sidebar.tsx @@ -18,7 +18,7 @@ const Sidebar = ({

Categories

{items.reduce( - (acc, category) => acc + category.expand.items.length, + (acc, category) => acc + category.scripts.length, 0, )}{" "} Total scripts diff --git a/frontend/src/app/scripts/page.tsx b/frontend/src/app/scripts/page.tsx index 2ccbc7fc4..2e82a3147 100644 --- a/frontend/src/app/scripts/page.tsx +++ b/frontend/src/app/scripts/page.tsx @@ -12,6 +12,7 @@ import { LatestScripts, MostViewedScripts, } from "./_components/ScriptInfoBlocks"; +import { fetchCategories } from "@/lib/data"; function ScriptContent() { const [selectedScript, setSelectedScript] = useQueryState("id"); @@ -21,41 +22,19 @@ function ScriptContent() { useEffect(() => { if (selectedScript && links.length > 0) { const script = links - .map((category) => category.expand.items) + .map((category) => category.scripts) .flat() - .find((script) => script.title === selectedScript); + .find((script) => script.name === selectedScript); setItem(script); } }, [selectedScript, links]); - const sortCategories = (categories: Category[]): Category[] => { - return categories.sort((a: Category, b: Category) => { - if ( - a.catagoryName === "Proxmox VE Tools" && - b.catagoryName !== "Proxmox VE Tools" - ) { - return -1; - } else if ( - a.catagoryName !== "Proxmox VE Tools" && - b.catagoryName === "Proxmox VE Tools" - ) { - return 1; - } else { - return a.catagoryName.localeCompare(b.catagoryName); - } - }); - }; - useEffect(() => { - fetch( - `api/categories?_=${process.env.NEXT_PUBLIC_BUILD_TIME || Date.now()}`, - ) - .then((response) => response.json()) - .then((categories) => { - const sortedCategories = sortCategories(categories); - setLinks(sortedCategories); - }) - .catch((error) => console.error(error)); + fetchCategories() + .then((categories) => { + setLinks(categories); + }) + .catch((error) => console.error(error)); }, []); return ( diff --git a/frontend/src/app/sitemap.ts b/frontend/src/app/sitemap.ts index b24847279..9377f25d8 100644 --- a/frontend/src/app/sitemap.ts +++ b/frontend/src/app/sitemap.ts @@ -1,20 +1,19 @@ +import { basePath } from "@/config/siteConfig"; import type { MetadataRoute } from "next"; export const dynamic = "force-static"; -export default function sitemap(): MetadataRoute.Sitemap { +export default async function sitemap(): Promise { + let domain = "community-scripts.github.io"; + let protocol = "https"; return [ { - url: "https://community-scripts.github.io/Proxmox/", + url: `${protocol}://${domain}/${basePath}`, lastModified: new Date(), - changeFrequency: "yearly", - priority: 0.8, }, { - url: "https://community-scripts.github.io/Proxmox/scripts", + url: `${protocol}://${domain}/${basePath}/scripts`, lastModified: new Date(), - changeFrequency: "monthly", - priority: 1, }, ]; } diff --git a/frontend/src/components/CommandMenu.tsx b/frontend/src/components/CommandMenu.tsx index e5157e036..cc6d690f2 100644 --- a/frontend/src/components/CommandMenu.tsx +++ b/frontend/src/components/CommandMenu.tsx @@ -6,30 +6,28 @@ import { CommandItem, CommandList, } from "@/components/ui/command"; +import { fetchCategories } from "@/lib/data"; import { Category } from "@/lib/types"; import { cn } from "@/lib/utils"; import Image from "next/image"; import { useRouter } from "next/navigation"; -import React, { useEffect } from "react"; +import React from "react"; +import { Badge } from "./ui/badge"; import { Button } from "./ui/button"; import { DialogTitle } from "./ui/dialog"; -const sortCategories = (categories: Category[]): Category[] => { - return categories.sort((a: Category, b: Category) => { - if ( - a.catagoryName === "Proxmox VE Tools" && - b.catagoryName !== "Proxmox VE Tools" - ) { - return -1; - } else if ( - a.catagoryName !== "Proxmox VE Tools" && - b.catagoryName === "Proxmox VE Tools" - ) { - return 1; - } else { - return a.catagoryName.localeCompare(b.catagoryName); - } - }); +export const formattedBadge = (type: string) => { + switch (type) { + case "vm": + return VM; + case "ct": + return ( + LXC + ); + case "misc": + return MISC; + } + return null; }; export default function CommandMenu() { @@ -50,21 +48,17 @@ export default function CommandMenu() { return () => document.removeEventListener("keydown", down); }, []); - const fetchCategories = async () => { + const fetchSortedCategories = () => { setIsLoading(true); - fetch( - `api/categories?_=${process.env.NEXT_PUBLIC_BUILD_TIME || Date.now()}`, - ) - .then((response) => response.json()) - .then((categories) => { - const sortedCategories = sortCategories(categories); - setLinks(sortedCategories); - setIsLoading(false); - }) - .catch((error) => { - setIsLoading(false); - console.error(error); - }); + fetchCategories() + .then((categories) => { + setLinks(categories); + setIsLoading(false); + }) + .catch((error) => { + setIsLoading(false); + console.error(error); + }); }; return ( @@ -75,8 +69,8 @@ export default function CommandMenu() { "relative h-9 w-full justify-start rounded-[0.5rem] bg-muted/50 text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-64", )} onClick={() => { - fetchCategories(); - setOpen(true) + fetchSortedCategories(); + setOpen(true); }} > Search scripts... @@ -85,41 +79,40 @@ export default function CommandMenu() { - Search scripts - + Search scripts + - {isLoading ? "Loading..." : "No scripts found."} + + {isLoading ? "Loading..." : "No scripts found."} + {links.map((category) => ( - {category.expand.items.map((script) => ( + {category.scripts.map((script) => ( { setOpen(false); - router.push(`/scripts?id=${script.title}`); + router.push(`/scripts?id=${script.name}`); }} >

setOpen(false)}> ((e.currentTarget as HTMLImageElement).src = "/logo.png") } width={16} + height={16} alt="" className="h-5 w-5" /> - {script.title} - - {script.item_type} - + {script.name} + {formattedBadge(script.type)}
))} diff --git a/frontend/src/components/Footer.tsx b/frontend/src/components/Footer.tsx index d76a44a22..83aa05cec 100644 --- a/frontend/src/components/Footer.tsx +++ b/frontend/src/components/Footer.tsx @@ -1,3 +1,4 @@ +import { basePath } from "@/config/siteConfig"; import Link from "next/link"; export default function Footer() { @@ -7,7 +8,7 @@ export default function Footer() {
Website build by the community. The source code is avaliable on{" "} { const handleScroll = () => { @@ -56,7 +53,6 @@ function Navbar() { /> Proxmox VE Helper-Scripts - {/* */}
@@ -81,28 +77,7 @@ function Navbar() { ))} - - - - - - - Theme Toggle - - - +
diff --git a/frontend/src/components/ui/codeblock.tsx b/frontend/src/components/ui/codeblock.tsx index f4251e536..16cc1bcc2 100644 --- a/frontend/src/components/ui/codeblock.tsx +++ b/frontend/src/components/ui/codeblock.tsx @@ -8,6 +8,7 @@ import * as React from "react"; import { toast } from "sonner"; import { Button } from "./button"; import { Separator } from "./separator"; +import { basePath } from "@/config/siteConfig"; 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", @@ -67,7 +68,7 @@ const handleCopy = (type: string, value: string) => {
+ + + Theme Toggle + + + + ); +} diff --git a/frontend/src/config/siteConfig.tsx b/frontend/src/config/siteConfig.tsx index cd87e0e4f..d32952c9a 100644 --- a/frontend/src/config/siteConfig.tsx +++ b/frontend/src/config/siteConfig.tsx @@ -1,23 +1,36 @@ import { MessagesSquare, Scroll } from "lucide-react"; import { FaGithub } from "react-icons/fa"; +export const basePath = process.env.BASE_PATH; + export const navbarLinks = [ { - href: "https://github.com/community-scripts/ProxmoxVE", + href: `https://github.com/community-scripts/${basePath}`, event: "Github", icon: , text: "Github", }, { - href: "https://github.com/community-scripts/ProxmoxVE/blob/main/CHANGELOG.md", + href: `https://github.com/community-scripts/${basePath}/blob/main/CHANGELOG.md`, event: "Change Log", icon: , text: "Change Log", }, { - href: "https://github.com/community-scripts/ProxmoxVE/discussions", + href: `https://github.com/community-scripts/${basePath}/discussions`, event: "Discussions", icon: , text: "Discussions", }, ]; + +export const mostPopularScripts = [ + "Proxmox VE Post Install", + "Docker", + "Home Assistant OS", +]; + +export const analytics = { + url: "analytics.proxmoxve-scripts.com", + token: "b60d3032-1a11-4244-a100-81d26c5c49a7", +}; diff --git a/frontend/src/lib/data.ts b/frontend/src/lib/data.ts new file mode 100644 index 000000000..8c665e1ab --- /dev/null +++ b/frontend/src/lib/data.ts @@ -0,0 +1,23 @@ +import { Category } from "./types"; + +const sortCategories = (categories: Category[]) => { + 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 => { + const response = await fetch("api/categories"); + const categories = await response.json(); + return sortCategories(categories); +}; diff --git a/frontend/src/lib/pocketbase.ts b/frontend/src/lib/pocketbase.ts deleted file mode 100644 index 86dcbace3..000000000 --- a/frontend/src/lib/pocketbase.ts +++ /dev/null @@ -1,10 +0,0 @@ -import PocketBase from "pocketbase"; - -export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL); -export const pbBackup = new PocketBase( - process.env.NEXT_PUBLIC_POCKETBASE_URL_BACKUP, -); - -export const getImageURL = (recordId: string, fileName: string) => { - return `${process.env.NEXT_PUBLIC_POCKETBASE_URL}/${recordId}/${fileName}`; -}; diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index b65d5a0a2..3ce26cd11 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -1,55 +1,44 @@ -// these are all the interfaces that are used in the site. these all come from the pocketbase database - -export interface Script { - title: string; - description: string; - documentation: string; - website: string; - logo: string; - created: string; - updated: string; - id: string; - item_type: string; - interface: string; - installCommand: string; - port: number; - post_install: string; - default_cpu: string; - default_hdd: string; - default_ram: string; - isUpdateable: boolean; - isMostViewed: boolean; +export type Script = { + name: string; + slug: string; + categories: number[]; + date_created: string; + type: "vm" | "ct" | "misc"; + updateable: boolean; privileged: boolean; - alpineScript: alpine_script; - expand: { - alpine_script: alpine_script; - alerts: alerts[]; - default_login: default_login; + interface_port: number | null; + documentation: string | null; + website: string | null; + logo: string | null; + description: string; + install_methods: { + type: "default" | "alpine"; + script: string; + resources: { + cpu: number | null; + ram: number | null; + hdd: number | null; + os: string | null; + version: number | null; + }; + }[]; + default_credentials: { + username: string | null; + password: string | null; }; + notes: [{ + text: string; + type: string; + }] } -export interface Category { - catagoryName: string; - categoryId: string; - id: string; - created: string; - expand: { - items: Script[]; - }; +export type Category = { + name: string; + id: number; + sort_order: number; + scripts: Script[]; } -interface alpine_script { - installCommand: string; - default_cpu: string; - default_hdd: string; - default_ram: string; -} - -interface alerts { - content: string; -} - -interface default_login { - username: string; - password: string; -} +export type ScriptList = { + categories: Category[]; +} \ No newline at end of file diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index e9820c8a3..d0ca4158f 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -30,6 +30,29 @@ --chart-3: 197 37% 24%; --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; + --expo-out: linear( + 0 0%, + 0.1684 2.66%, + 0.3165 5.49%, + 0.446 8.52%, + 0.5581 11.78%, + 0.6535 15.29%, + 0.7341 19.11%, + 0.8011 23.3%, + 0.8557 27.93%, + 0.8962 32.68%, + 0.9283 38.01%, + 0.9529 44.08%, + 0.9711 51.14%, + 0.9833 59.06%, + 0.9915 68.74%, + 1 100% + ); + } + + ::selection { + background-color: hsl(var(--accent)); + color: hsl(var(--foreground)); } .dark { @@ -58,8 +81,46 @@ --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; } + + ::view-transition-group(root) { + animation-duration: 0.7bun s; + animation-timing-function: var(--expo-out); + } + + ::view-transition-new(root) { + animation-name: reveal-light; + } + + ::view-transition-old(root), + .dark::view-transition-old(root) { + animation: none; + z-index: -1; + } + .dark::view-transition-new(root) { + animation-name: reveal-dark; + } + + @keyframes reveal-dark { + from { + clip-path: polygon(50% -71%, -50% 71%, -50% 71%, 50% -71%); + } + to { + clip-path: polygon(50% -71%, -50% 71%, 50% 171%, 171% 50%); + } + } + + @keyframes reveal-light { + from { + clip-path: polygon(171% 50%, 50% 171%, 50% 171%, 171% 50%); + } + to { + clip-path: polygon(171% 50%, 50% 171%, -50% 71%, 50% -71%); + } + } } + + @layer base { * { @apply border-border;