Switch from Pocketbase data retrieval to JSON (#100)

* 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>
This commit is contained in:
Bram Suurd
2024-11-06 23:47:04 +01:00
committed by GitHub
parent 97008d0273
commit 93fd495f65
37 changed files with 474 additions and 453 deletions

View File

@@ -13,6 +13,7 @@ import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { Badge } from "../../../components/ui/badge";
import { formattedBadge } from "@/components/CommandMenu";
export default function ScriptAccordion({
items,
@@ -42,10 +43,10 @@ export default function ScriptAccordion({
useEffect(() => {
if (selectedScript) {
const category = items.find((category) =>
category.expand.items.some((script) => script.title === selectedScript),
category.scripts.some((script) => script.name === selectedScript),
);
if (category) {
setExpandedItem(category.catagoryName);
setExpandedItem(category.name);
handleSelected(selectedScript);
}
}
@@ -60,82 +61,68 @@ export default function ScriptAccordion({
{items.map((category) => (
<AccordionItem
key={category.id + ":category"}
value={category.catagoryName}
value={category.name}
className={cn("sm:text-md flex flex-col border-none", {
"rounded-lg bg-accent/30": expandedItem === category.catagoryName,
"rounded-lg bg-accent/30": expandedItem === category.name,
})}
>
<AccordionTrigger
className={cn(
"duration-250 rounded-lg transition ease-in-out hover:-translate-y-1 hover:scale-105 hover:bg-accent",
{ "": expandedItem === category.catagoryName },
)}
>
<div className="mr-2 flex w-full items-center justify-between">
<span className="pl-2">{category.catagoryName} </span>
<span className="pl-2">{category.name} </span>
<span className="rounded-full bg-gray-200 px-2 py-1 text-xs text-muted-foreground hover:no-underline dark:bg-blue-800/20">
{category.expand.items.length}
{category.scripts.length}
</span>
</div>{" "}
</AccordionTrigger>
<AccordionContent
data-state={
expandedItem === category.catagoryName ? "open" : "closed"
expandedItem === category.name ? "open" : "closed"
}
className="pt-0"
>
{category.expand.items
{category.scripts
.slice()
.sort((a, b) => a.title.localeCompare(b.title))
.sort((a, b) => a.name.localeCompare(b.name))
.map((script, index) => (
<div key={index}>
<Link
href={{
pathname: "/scripts",
query: { id: script.title },
query: { id: script.name},
}}
prefetch={false}
className={`flex cursor-pointer items-center justify-between gap-1 px-1 py-1 text-muted-foreground hover:rounded-lg hover:bg-accent/60 hover:dark:bg-accent/20 ${
selectedScript === script.title
selectedScript === script.name
? "rounded-lg bg-accent font-semibold dark:bg-accent/30 dark:text-white"
: ""
}`}
onClick={() => handleSelected(script.title)}
onClick={() => handleSelected(script.name)}
ref={(el) => {
linkRefs.current[script.title] = el;
linkRefs.current[script.name] = el;
}}
>
<Image
src={script.logo}
height={16}
width={16}
unoptimized
onError={(e) =>
((e.currentTarget as HTMLImageElement).src =
"/logo.png")
}
alt={script.title}
className="mr-1 w-4 h-4 rounded-full"
/>
<span className="flex items-center gap-2">
{script.title}
{script.isMostViewed && (
<Star className="h-3 w-3 text-yellow-500"></Star>
)}
</span>
<Badge
className={cn(
"ml-auto w-[37.69px] justify-center text-center",
{
"text-primary/75": script.item_type === "VM",
"text-yellow-500/75": script.item_type === "LXC",
"border-none": script.item_type === "",
hidden: !["VM", "LXC", ""].includes(script.item_type),
},
)}
>
{script.item_type}
</Badge>
<div className="flex items-center">
<Image
src={script.logo || "/logo.png"}
height={16}
width={16}
unoptimized
onError={(e) =>
((e.currentTarget as HTMLImageElement).src =
"/logo.png")
}
alt={script.name}
className="mr-1 w-4 h-4 rounded-full"
/>
<span className="flex items-center gap-2">
{script.name}
</span>
</div>
{formattedBadge(script.type)}
</Link>
</div>
))}

View File

@@ -7,8 +7,9 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { mostPopularScripts } from "@/config/siteConfig";
import { extractDate } from "@/lib/time";
import { Category } from "@/lib/types";
import { Category, Script } from "@/lib/types";
import { CalendarPlus } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
@@ -16,14 +17,28 @@ import { useMemo, useState } from "react";
const ITEMS_PER_PAGE = 3;
export const getDisplayValueFromType = (type: string) => {
switch (type) {
case "ct":
return "LXC";
case "vm":
return "VM";
case "misc":
return "";
default:
return "";
}
};
export function LatestScripts({ items }: { items: Category[] }) {
const [page, setPage] = useState(1);
const latestScripts = useMemo(() => {
if (!items) return [];
const scripts = items.flatMap((category) => category.expand.items || []);
const scripts = items.flatMap((category) => category.scripts || []);
return scripts.sort(
(a, b) => new Date(b.created).getTime() - new Date(a.created).getTime(),
(a, b) =>
new Date(b.date_created).getTime() - new Date(a.date_created).getTime(),
);
}, [items]);
@@ -68,16 +83,16 @@ export function LatestScripts({ items }: { items: Category[] }) {
</div>
)}
<div className="min-w flex w-full flex-row flex-wrap gap-4">
{latestScripts.slice(startIndex, endIndex).map((item) => (
{latestScripts.slice(startIndex, endIndex).map((script) => (
<Card
key={item.id}
key={script.name}
className="min-w-[250px] flex-1 flex-grow bg-accent/30"
>
<CardHeader>
<CardTitle className="flex items-center gap-3">
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-accent p-1">
<Image
src={item.logo}
src={script.logo || "/logo.png"}
unoptimized
height={64}
width={64}
@@ -87,18 +102,18 @@ export function LatestScripts({ items }: { items: Category[] }) {
</div>
<div className="flex flex-col">
<p className="text-lg line-clamp-1">
{item.title} {item.item_type}
{script.name} {getDisplayValueFromType(script.type)}
</p>
<p className="text-sm text-muted-foreground flex items-center gap-1">
<CalendarPlus className="h-4 w-4" />
{extractDate(item.created)}
{extractDate(script.date_created)}
</p>
</div>
</CardTitle>
</CardHeader>
<CardContent>
<CardDescription className="line-clamp-3 text-card-foreground">
{item.description}
{script.description}
</CardDescription>
</CardContent>
<CardFooter className="">
@@ -106,7 +121,7 @@ export function LatestScripts({ items }: { items: Category[] }) {
<Link
href={{
pathname: "/scripts",
query: { id: item.title },
query: { id: script.name },
}}
>
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 (
<div className="">
@@ -153,9 +151,9 @@ export function MostViewedScripts({ items }: { items: Category[] }) {
</>
)}
<div className="min-w flex w-full flex-row flex-wrap gap-4">
{mostViewedScripts.slice(startIndex, endIndex).map((item) => (
{mostViewedScripts.map((script) => (
<Card
key={item.id}
key={script.name}
className="min-w-[250px] flex-1 flex-grow bg-accent/30"
>
<CardHeader>
@@ -163,7 +161,7 @@ export function MostViewedScripts({ items }: { items: Category[] }) {
<div className="flex max-h-16 min-h-16 min-w-16 max-w-16 items-center justify-center rounded-lg bg-accent p-1">
<Image
unoptimized
src={item.logo}
src={script.logo || "/logo.png"}
height={64}
width={64}
alt=""
@@ -172,18 +170,18 @@ export function MostViewedScripts({ items }: { items: Category[] }) {
</div>
<div className="flex flex-col">
<p className="line-clamp-1 text-lg">
{item.title} {item.item_type}
{script.name} {getDisplayValueFromType(script.type)}
</p>
<p className="flex items-center gap-1 text-sm text-muted-foreground">
<CalendarPlus className="h-4 w-4" />
{extractDate(item.created)}
{extractDate(script.date_created)}
</p>
</div>
</CardTitle>
</CardHeader>
<CardContent>
<CardDescription className="line-clamp-3 text-card-foreground break-words">
{item.description}
{script.description}
</CardDescription>
</CardContent>
<CardFooter className="">
@@ -191,7 +189,7 @@ export function MostViewedScripts({ items }: { items: Category[] }) {
<Link
href={{
pathname: "/scripts",
query: { id: item.title },
query: { id: script.name },
}}
prefetch={false}
>
@@ -202,18 +200,6 @@ export function MostViewedScripts({ items }: { items: Category[] }) {
</Card>
))}
</div>
<div className="flex justify-end gap-1 p-2">
{page > 1 && (
<Button onClick={goToPreviousPage} variant="outline">
Previous
</Button>
)}
{endIndex < mostViewedScripts.length && (
<Button onClick={goToNextPage} variant="outline">
{page === 1 ? "More.." : "Next"}
</Button>
)}
</div>
</div>
);
}

View File

@@ -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({
<div className="flex">
<Image
className="h-32 w-32 rounded-lg bg-accent/60 object-contain p-3 shadow-md"
src={item.logo}
src={item.logo || "/logo.png"}
width={400}
onError={(e) =>
((e.currentTarget as HTMLImageElement).src = "/logo.png")
}
height={400}
alt={item.title}
alt={item.name}
unoptimized
/>
<div className="ml-4 flex flex-col justify-between">
<div className="flex h-full w-full flex-col justify-between">
<div>
<h1 className="text-lg font-semibold">{item.title}</h1>
<h1 className="text-lg font-semibold">{item.name} {getDisplayValueFromType(item.type)}</h1>
<p className="w-full text-sm text-muted-foreground">
Date added: {extractDate(item.created)}
Date added: {extractDate(item.date_created)}
</p>
</div>
<div className="flex gap-5">
@@ -76,7 +77,7 @@ function ScriptItem({
<div className="mt-4 rounded-lg border bg-accent/50">
<div className="flex gap-3 px-4 py-2">
<h2 className="text-lg font-semibold">
How to {item.item_type ? "install" : "use"}
How to {item.type ? "install" : "use"}
</h2>
<Tooltips item={item} />
</div>

View File

@@ -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) => (
<div key={index} className="mt-4 flex flex-col gap-2">
<p className="inline-flex items-center gap-2 rounded-lg border border-red-500/25 bg-destructive/25 p-2 pl-4 text-sm">
<Info className="h-4 min-h-4 w-4 min-w-4" />
<span>{TextCopyBlock(alert.content)}</span>
<span>{TextCopyBlock(note.text)}</span>
</p>
</div>
))}

View File

@@ -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 (
<div className="flex flex-wrap justify-end gap-2">
{item.website && (
@@ -49,26 +34,16 @@ export default function Buttons({ item }: { item: Script }) {
</Link>
</Button>
)}
{item.post_install && (
{
<Button variant="secondary" asChild>
<Link target="_blank" href={item.post_install}>
<span className="flex items-center gap-2">
<ExternalLink className="h-4 w-4" />
Post Install
</span>
</Link>
</Button>
)}
{item.installCommand && sourceUrl && (
<Button variant="secondary" asChild>
<Link target="_blank" href={transformUrlToInstallScript(sourceUrl)}>
<Link target="_blank" href={generateSourceUrl(item.slug, item.type)}>
<span className="flex items-center gap-2">
<Code className="h-4 w-4" />
Source Code
</span>
</Link>
</Button>
)}
}
</div>
);
}

View File

@@ -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 (
<div>
@@ -17,7 +17,7 @@ export default function DefaultPassword({ item }: { item: Script }) {
<div className="flex flex-col gap-2 p-4">
<p className="mb-2 text-sm">
You can use the following credentials to login to the {""}
{item.title} {item.item_type}.
{item.name} {item.type}.
</p>
<div className="text-sm">
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}
</Button>
</div>
<div className="text-sm">
@@ -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}
</Button>
</div>
</div>

View File

@@ -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 && (
<div>
<h2 className="text-md font-semibold">Default settings</h2>
<p className="text-sm text-muted-foreground">
CPU: {item.default_cpu}
CPU: {defaultSettings?.resources.cpu}vCPU
</p>
<p className="text-sm text-muted-foreground">
RAM: {item.default_ram}
RAM: {getDisplayValueFromRAM(defaultSettings?.resources.ram ?? 0)}
</p>
<p className="text-sm text-muted-foreground">
HDD: {item.default_hdd}
HDD: {defaultSettings?.resources.hdd}GB
</p>
</div>
)}
{hasAlpineScript && (
{defaultAlpineSettings && (
<div>
<h2 className="text-md font-semibold">Default Alpine settings</h2>
<p className="text-sm text-muted-foreground">
CPU: {item.expand.alpine_script.default_cpu}
CPU: {defaultAlpineSettings?.resources.cpu}vCPU
</p>
<p className="text-sm text-muted-foreground">
RAM: {item.expand.alpine_script.default_ram}
RAM: {getDisplayValueFromRAM(defaultAlpineSettings?.resources.ram ?? 0)}
</p>
<p className="text-sm text-muted-foreground">
HDD: {item.expand.alpine_script.default_hdd}
HDD: {defaultAlpineSettings?.resources.hdd}GB
</p>
</div>
)}

View File

@@ -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) => (
<>
<p className="text-sm mt-2">
{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.</>
)}
</p>
{isAlpine && (
<p className="mt-2 text-sm">
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
</p>
)}
@@ -36,7 +46,7 @@ export default function InstallCommand({ item }: { item: Script }) {
return (
<div className="p-4">
{hasAlpineScript ? (
{alpineScript ? (
<Tabs defaultValue="default" className="mt-2 w-full max-w-4xl">
<TabsList>
<TabsTrigger value="default">Default</TabsTrigger>
@@ -44,25 +54,23 @@ export default function InstallCommand({ item }: { item: Script }) {
</TabsList>
<TabsContent value="default">
{renderInstructions()}
<CodeCopyButton>{installCommand}</CodeCopyButton>
<CodeCopyButton>{getInstallCommand(defaultScript?.script)}</CodeCopyButton>
</TabsContent>
<TabsContent value="alpine">
{expand.alpine_script && (
<>
{renderInstructions(true)}
<CodeCopyButton>
{expand.alpine_script.installCommand}
</CodeCopyButton>
</>
)}
{renderInstructions(true)}
<CodeCopyButton>
{getInstallCommand(alpineScript.script)}
</CodeCopyButton>
</TabsContent>
</Tabs>
) : (
) : defaultScript?.script ? (
<>
{renderInstructions()}
{installCommand && <CodeCopyButton>{installCommand}</CodeCopyButton>}
<CodeCopyButton>
{getInstallCommand(defaultScript.script)}
</CodeCopyButton>
</>
)}
) : null}
</div>
);
}

View File

@@ -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 = ({
</span>
);
export default function InterFaces({ item }: { item: Item }) {
const { interface: iface, port } = item;
export default function InterFaces({item} : {item : Script}) {
return (
<div className="flex flex-col gap-2">
{iface || (port && port !== 0) ? (
{item.interface_port !== null ? (
<div className="flex items-center justify-end">
<h2 className="mr-2 text-end text-lg font-semibold">
{iface ? "Interface:" : "Default Port:"}
{"Default Interface:"}
</h2>{" "}
<CopyButton
label={iface ? "interface" : "port"}
value={iface || port!}
label="default interface"
value={item.interface_port}
/>
</div>
) : null}

View File

@@ -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 && (
<TooltipBadge
variant="success"
label="Updateable"
content={`To Update ${item.title}, run the command below (or type update) in the LXC Console.`}
content={`To Update ${item.name}, run the command below (or type update) in the LXC Console.`}
/>
)}
</div>

View File

@@ -18,7 +18,7 @@ const Sidebar = ({
<h1 className="text-xl font-bold">Categories</h1>
<p className="text-xs italic text-muted-foreground">
{items.reduce(
(acc, category) => acc + category.expand.items.length,
(acc, category) => acc + category.scripts.length,
0,
)}{" "}
Total scripts