mirror of
https://github.com/community-scripts/ProxmoxVE
synced 2025-01-26 18:46:18 +00:00
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:
parent
97008d0273
commit
93fd495f65
9
.github/workflows/deploy-pages.yml
vendored
9
.github/workflows/deploy-pages.yml
vendored
@ -9,6 +9,7 @@ on:
|
|||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
paths:
|
paths:
|
||||||
- frontend/**
|
- frontend/**
|
||||||
|
- json/**
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@ -57,14 +58,6 @@ jobs:
|
|||||||
uses: actions/configure-pages@v5
|
uses: actions/configure-pages@v5
|
||||||
with:
|
with:
|
||||||
static_site_generator: next
|
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
|
- name: Install dependencies
|
||||||
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} --legacy-peer-deps
|
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} --legacy-peer-deps
|
||||||
- name: Build with Next.js
|
- name: Build with Next.js
|
||||||
|
@ -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"
|
|
@ -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
|
|
@ -15,11 +15,11 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
env: {
|
env: {
|
||||||
NEXT_PUBLIC_BUILD_TIME: `${Date.now()}`,
|
BASE_PATH: "ProxmoxVE",
|
||||||
},
|
},
|
||||||
|
|
||||||
output: "export",
|
output: "export",
|
||||||
basePath: "/ProxmoxVE",
|
basePath: `/${process.env.BASE_PATH}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@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-navigation-menu": "^1.1.4",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
@ -21,7 +22,7 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"framer-motion": "^11.11.10",
|
"framer-motion": "^11.11.11",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"lucide-react": "^0.453.0",
|
"lucide-react": "^0.453.0",
|
||||||
"mini-svg-data-uri": "^1.4.4",
|
"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": {
|
"node_modules/@radix-ui/react-id": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@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-navigation-menu": "^1.1.4",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
@ -31,7 +32,7 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"framer-motion": "^11.11.10",
|
"framer-motion": "^11.11.11",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"lucide-react": "^0.453.0",
|
"lucide-react": "^0.453.0",
|
||||||
"mini-svg-data-uri": "^1.4.4",
|
"mini-svg-data-uri": "^1.4.4",
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -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."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,23 +1,45 @@
|
|||||||
import { pb } from "@/lib/pocketbase";
|
import { basePath } from "@/config/siteConfig";
|
||||||
import { Category } from "@/lib/types";
|
import { Category, Script } from "@/lib/types";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
|
|
||||||
|
const fetchCategories = async (): Promise<Category[]> => {
|
||||||
|
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<Script[]> => {
|
||||||
|
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<Script> => {
|
||||||
|
const response = await fetch(file.download_url);
|
||||||
|
const script = await response.json();
|
||||||
|
return script;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return scripts;
|
||||||
|
};
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const response = await pb.collection("categories").getFullList<Category>({
|
const categories = await fetchCategories();
|
||||||
expand: "items.alerts,items.alpine_script,items.default_login",
|
const scripts = await fetchScripts();
|
||||||
sort: "order",
|
for (const category of categories) {
|
||||||
});
|
category.scripts = scripts.filter((script) => script.categories.includes(category.id));
|
||||||
|
}
|
||||||
return NextResponse.json(response);
|
return NextResponse.json(categories);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching categories:", error);
|
console.error(error as Error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Failed to fetch categories" },
|
{ error: "Failed to fetch categories" },
|
||||||
{ status: 500 },
|
{ status: 500 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import "@/styles/globals.css";
|
|||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||||
|
import { analytics, basePath } from "@/config/siteConfig";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ export const metadata = {
|
|||||||
address: false,
|
address: false,
|
||||||
telephone: false,
|
telephone: false,
|
||||||
},
|
},
|
||||||
metadataBase: new URL("https://community-scripts.github.io/Proxmox/"),
|
metadataBase: new URL(`https://community-scripts.github.io/${basePath}/`),
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Proxmox VE Helper-Scripts",
|
title: "Proxmox VE Helper-Scripts",
|
||||||
description:
|
description:
|
||||||
@ -42,7 +43,7 @@ export const metadata = {
|
|||||||
url: "/defaultimg.png",
|
url: "/defaultimg.png",
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: "https://community-scripts.github.io/Proxmox/defaultimg.png",
|
url: `https://community-scripts.github.io/${basePath}/defaultimg.png`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
locale: "en_US",
|
locale: "en_US",
|
||||||
@ -60,15 +61,20 @@ export default function RootLayout({
|
|||||||
<head>
|
<head>
|
||||||
<script
|
<script
|
||||||
defer
|
defer
|
||||||
src={`https://${process.env.NEXT_PUBLIC_ANALYTICS_URL}/script.js`}
|
src={`https://${analytics.url}/script.js`}
|
||||||
data-website-id={process.env.NEXT_PUBLIC_ANALYTICS_TOKEN}
|
data-website-id={analytics.token}
|
||||||
></script>
|
></script>
|
||||||
<link rel="manifest" href="manifest.webmanifest" />
|
<link rel="manifest" href="manifest.webmanifest" />
|
||||||
<link rel="preconnect" href={process.env.NEXT_PUBLIC_POCKETBASE_URL} />
|
<link rel="preconnect" href={process.env.NEXT_PUBLIC_POCKETBASE_URL} />
|
||||||
<link rel="preconnect" href="https://api.github.com" />
|
<link rel="preconnect" href="https://api.github.com" />
|
||||||
</head>
|
</head>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="dark"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
<div className="flex w-full flex-col justify-center">
|
<div className="flex w-full flex-col justify-center">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div className="flex min-h-screen flex-col justify-center">
|
<div className="flex min-h-screen flex-col justify-center">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
export const generateStaticParams = () => {
|
export const generateStaticParams = () => {
|
||||||
@ -9,13 +10,13 @@ export default function manifest(): MetadataRoute.Manifest {
|
|||||||
name: "Proxmox VE Helper-Scripts",
|
name: "Proxmox VE Helper-Scripts",
|
||||||
short_name: "Proxmox VE Helper-Scripts",
|
short_name: "Proxmox VE Helper-Scripts",
|
||||||
description:
|
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",
|
theme_color: "#030712",
|
||||||
background_color: "#030712",
|
background_color: "#030712",
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
orientation: "portrait",
|
orientation: "portrait",
|
||||||
scope: "/Proxmox/",
|
scope: `${basePath}`,
|
||||||
start_url: "/Proxmox/",
|
start_url: `${basePath}`,
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
src: "logo.png",
|
src: "logo.png",
|
||||||
|
@ -11,6 +11,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
import { CardFooter } from "@/components/ui/card";
|
import { CardFooter } from "@/components/ui/card";
|
||||||
import { FaGithub } from "react-icons/fa";
|
import { FaGithub } from "react-icons/fa";
|
||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
|
|
||||||
function CustomArrowRightIcon() {
|
function CustomArrowRightIcon() {
|
||||||
return <ArrowRightIcon className="h-4 w-4" width={1} />;
|
return <ArrowRightIcon className="h-4 w-4" width={1} />;
|
||||||
@ -80,7 +81,7 @@ export default function Page() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button className="w-full" asChild>
|
<Button className="w-full" asChild>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/community-scripts/ProxmoxVE"
|
href={`https://github.com/community-scripts/${basePath}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center justify-center"
|
className="flex items-center justify-center"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
@ -8,6 +9,6 @@ export default function robots(): MetadataRoute.Robots {
|
|||||||
userAgent: "*",
|
userAgent: "*",
|
||||||
allow: "/",
|
allow: "/",
|
||||||
},
|
},
|
||||||
sitemap: "https://community-scripts.github.io/Proxmox/sitemap.xml",
|
sitemap: `https://community-scripts.github.io/${basePath}/sitemap.xml`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import Image from "next/image";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Badge } from "../../../components/ui/badge";
|
import { Badge } from "../../../components/ui/badge";
|
||||||
|
import { formattedBadge } from "@/components/CommandMenu";
|
||||||
|
|
||||||
export default function ScriptAccordion({
|
export default function ScriptAccordion({
|
||||||
items,
|
items,
|
||||||
@ -42,10 +43,10 @@ export default function ScriptAccordion({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedScript) {
|
if (selectedScript) {
|
||||||
const category = items.find((category) =>
|
const category = items.find((category) =>
|
||||||
category.expand.items.some((script) => script.title === selectedScript),
|
category.scripts.some((script) => script.name === selectedScript),
|
||||||
);
|
);
|
||||||
if (category) {
|
if (category) {
|
||||||
setExpandedItem(category.catagoryName);
|
setExpandedItem(category.name);
|
||||||
handleSelected(selectedScript);
|
handleSelected(selectedScript);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,82 +61,68 @@ export default function ScriptAccordion({
|
|||||||
{items.map((category) => (
|
{items.map((category) => (
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
key={category.id + ":category"}
|
key={category.id + ":category"}
|
||||||
value={category.catagoryName}
|
value={category.name}
|
||||||
className={cn("sm:text-md flex flex-col border-none", {
|
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
|
<AccordionTrigger
|
||||||
className={cn(
|
className={cn(
|
||||||
"duration-250 rounded-lg transition ease-in-out hover:-translate-y-1 hover:scale-105 hover:bg-accent",
|
"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">
|
<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">
|
<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>
|
</span>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent
|
<AccordionContent
|
||||||
data-state={
|
data-state={
|
||||||
expandedItem === category.catagoryName ? "open" : "closed"
|
expandedItem === category.name ? "open" : "closed"
|
||||||
}
|
}
|
||||||
className="pt-0"
|
className="pt-0"
|
||||||
>
|
>
|
||||||
{category.expand.items
|
{category.scripts
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => a.title.localeCompare(b.title))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
.map((script, index) => (
|
.map((script, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: "/scripts",
|
pathname: "/scripts",
|
||||||
query: { id: script.title },
|
query: { id: script.name},
|
||||||
}}
|
}}
|
||||||
prefetch={false}
|
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 ${
|
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"
|
? "rounded-lg bg-accent font-semibold dark:bg-accent/30 dark:text-white"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleSelected(script.title)}
|
onClick={() => handleSelected(script.name)}
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
linkRefs.current[script.title] = el;
|
linkRefs.current[script.name] = el;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<div className="flex items-center">
|
||||||
src={script.logo}
|
<Image
|
||||||
height={16}
|
src={script.logo || "/logo.png"}
|
||||||
width={16}
|
height={16}
|
||||||
unoptimized
|
width={16}
|
||||||
onError={(e) =>
|
unoptimized
|
||||||
((e.currentTarget as HTMLImageElement).src =
|
onError={(e) =>
|
||||||
"/logo.png")
|
((e.currentTarget as HTMLImageElement).src =
|
||||||
}
|
"/logo.png")
|
||||||
alt={script.title}
|
}
|
||||||
className="mr-1 w-4 h-4 rounded-full"
|
alt={script.name}
|
||||||
/>
|
className="mr-1 w-4 h-4 rounded-full"
|
||||||
<span className="flex items-center gap-2">
|
/>
|
||||||
{script.title}
|
<span className="flex items-center gap-2">
|
||||||
{script.isMostViewed && (
|
{script.name}
|
||||||
<Star className="h-3 w-3 text-yellow-500"></Star>
|
</span>
|
||||||
)}
|
</div>
|
||||||
</span>
|
{formattedBadge(script.type)}
|
||||||
<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>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -7,8 +7,9 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
import { mostPopularScripts } from "@/config/siteConfig";
|
||||||
import { extractDate } from "@/lib/time";
|
import { extractDate } from "@/lib/time";
|
||||||
import { Category } from "@/lib/types";
|
import { Category, Script } from "@/lib/types";
|
||||||
import { CalendarPlus } from "lucide-react";
|
import { CalendarPlus } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@ -16,14 +17,28 @@ import { useMemo, useState } from "react";
|
|||||||
|
|
||||||
const ITEMS_PER_PAGE = 3;
|
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[] }) {
|
export function LatestScripts({ items }: { items: Category[] }) {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
const latestScripts = useMemo(() => {
|
const latestScripts = useMemo(() => {
|
||||||
if (!items) return [];
|
if (!items) return [];
|
||||||
const scripts = items.flatMap((category) => category.expand.items || []);
|
const scripts = items.flatMap((category) => category.scripts || []);
|
||||||
return scripts.sort(
|
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]);
|
}, [items]);
|
||||||
|
|
||||||
@ -68,16 +83,16 @@ export function LatestScripts({ items }: { items: Category[] }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="min-w flex w-full flex-row flex-wrap gap-4">
|
<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
|
<Card
|
||||||
key={item.id}
|
key={script.name}
|
||||||
className="min-w-[250px] flex-1 flex-grow bg-accent/30"
|
className="min-w-[250px] flex-1 flex-grow bg-accent/30"
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-3">
|
<CardTitle className="flex items-center gap-3">
|
||||||
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-accent p-1">
|
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-accent p-1">
|
||||||
<Image
|
<Image
|
||||||
src={item.logo}
|
src={script.logo || "/logo.png"}
|
||||||
unoptimized
|
unoptimized
|
||||||
height={64}
|
height={64}
|
||||||
width={64}
|
width={64}
|
||||||
@ -87,18 +102,18 @@ export function LatestScripts({ items }: { items: Category[] }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-lg line-clamp-1">
|
<p className="text-lg line-clamp-1">
|
||||||
{item.title} {item.item_type}
|
{script.name} {getDisplayValueFromType(script.type)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground flex items-center gap-1">
|
<p className="text-sm text-muted-foreground flex items-center gap-1">
|
||||||
<CalendarPlus className="h-4 w-4" />
|
<CalendarPlus className="h-4 w-4" />
|
||||||
{extractDate(item.created)}
|
{extractDate(script.date_created)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<CardDescription className="line-clamp-3 text-card-foreground">
|
<CardDescription className="line-clamp-3 text-card-foreground">
|
||||||
{item.description}
|
{script.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="">
|
<CardFooter className="">
|
||||||
@ -106,7 +121,7 @@ export function LatestScripts({ items }: { items: Category[] }) {
|
|||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: "/scripts",
|
pathname: "/scripts",
|
||||||
query: { id: item.title },
|
query: { id: script.name },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
View Script
|
View Script
|
||||||
@ -121,29 +136,12 @@ export function LatestScripts({ items }: { items: Category[] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MostViewedScripts({ items }: { items: Category[] }) {
|
export function MostViewedScripts({ items }: { items: Category[] }) {
|
||||||
const [page, setPage] = useState(1);
|
const mostViewedScripts = items.reduce((acc: Script[], category) => {
|
||||||
|
const foundScripts = category.scripts.filter((script) =>
|
||||||
const mostViewedScripts = useMemo(() => {
|
mostPopularScripts.includes(script.name),
|
||||||
if (!items) return [];
|
);
|
||||||
const scripts = items.flatMap((category) => category.expand.items || []);
|
return acc.concat(foundScripts);
|
||||||
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;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<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">
|
<div className="min-w flex w-full flex-row flex-wrap gap-4">
|
||||||
{mostViewedScripts.slice(startIndex, endIndex).map((item) => (
|
{mostViewedScripts.map((script) => (
|
||||||
<Card
|
<Card
|
||||||
key={item.id}
|
key={script.name}
|
||||||
className="min-w-[250px] flex-1 flex-grow bg-accent/30"
|
className="min-w-[250px] flex-1 flex-grow bg-accent/30"
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<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">
|
<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
|
<Image
|
||||||
unoptimized
|
unoptimized
|
||||||
src={item.logo}
|
src={script.logo || "/logo.png"}
|
||||||
height={64}
|
height={64}
|
||||||
width={64}
|
width={64}
|
||||||
alt=""
|
alt=""
|
||||||
@ -172,18 +170,18 @@ export function MostViewedScripts({ items }: { items: Category[] }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="line-clamp-1 text-lg">
|
<p className="line-clamp-1 text-lg">
|
||||||
{item.title} {item.item_type}
|
{script.name} {getDisplayValueFromType(script.type)}
|
||||||
</p>
|
</p>
|
||||||
<p className="flex items-center gap-1 text-sm text-muted-foreground">
|
<p className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||||
<CalendarPlus className="h-4 w-4" />
|
<CalendarPlus className="h-4 w-4" />
|
||||||
{extractDate(item.created)}
|
{extractDate(script.date_created)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<CardDescription className="line-clamp-3 text-card-foreground break-words">
|
<CardDescription className="line-clamp-3 text-card-foreground break-words">
|
||||||
{item.description}
|
{script.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="">
|
<CardFooter className="">
|
||||||
@ -191,7 +189,7 @@ export function MostViewedScripts({ items }: { items: Category[] }) {
|
|||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: "/scripts",
|
pathname: "/scripts",
|
||||||
query: { id: item.title },
|
query: { id: script.name },
|
||||||
}}
|
}}
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
>
|
>
|
||||||
@ -202,18 +200,6 @@ export function MostViewedScripts({ items }: { items: Category[] }) {
|
|||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { Script } from "@/lib/types";
|
|||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
|
import { getDisplayValueFromType } from "./ScriptInfoBlocks";
|
||||||
import Alerts from "./ScriptItems/Alerts";
|
import Alerts from "./ScriptItems/Alerts";
|
||||||
import Buttons from "./ScriptItems/Buttons";
|
import Buttons from "./ScriptItems/Buttons";
|
||||||
import DefaultPassword from "./ScriptItems/DefaultPassword";
|
import DefaultPassword from "./ScriptItems/DefaultPassword";
|
||||||
@ -39,21 +40,21 @@ function ScriptItem({
|
|||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Image
|
<Image
|
||||||
className="h-32 w-32 rounded-lg bg-accent/60 object-contain p-3 shadow-md"
|
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}
|
width={400}
|
||||||
onError={(e) =>
|
onError={(e) =>
|
||||||
((e.currentTarget as HTMLImageElement).src = "/logo.png")
|
((e.currentTarget as HTMLImageElement).src = "/logo.png")
|
||||||
}
|
}
|
||||||
height={400}
|
height={400}
|
||||||
alt={item.title}
|
alt={item.name}
|
||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
<div className="ml-4 flex flex-col justify-between">
|
<div className="ml-4 flex flex-col justify-between">
|
||||||
<div className="flex h-full w-full flex-col justify-between">
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-lg font-semibold">{item.title}</h1>
|
<h1 className="text-lg font-semibold">{item.name} {getDisplayValueFromType(item.type)}</h1>
|
||||||
<p className="w-full text-sm text-muted-foreground">
|
<p className="w-full text-sm text-muted-foreground">
|
||||||
Date added: {extractDate(item.created)}
|
Date added: {extractDate(item.date_created)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-5">
|
<div className="flex gap-5">
|
||||||
@ -76,7 +77,7 @@ function ScriptItem({
|
|||||||
<div className="mt-4 rounded-lg border bg-accent/50">
|
<div className="mt-4 rounded-lg border bg-accent/50">
|
||||||
<div className="flex gap-3 px-4 py-2">
|
<div className="flex gap-3 px-4 py-2">
|
||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
How to {item.item_type ? "install" : "use"}
|
How to {item.type ? "install" : "use"}
|
||||||
</h2>
|
</h2>
|
||||||
<Tooltips item={item} />
|
<Tooltips item={item} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,12 +5,12 @@ import { Info } from "lucide-react";
|
|||||||
export default function Alerts({ item }: { item: Script }) {
|
export default function Alerts({ item }: { item: Script }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{item.expand?.alerts?.length > 0 &&
|
{item?.notes?.length > 0 &&
|
||||||
item.expand.alerts.map((alert: any, index: number) => (
|
item.notes.map((note: any, index: number) => (
|
||||||
<div key={index} className="mt-4 flex flex-col gap-2">
|
<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">
|
<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" />
|
<Info className="h-4 min-h-4 w-4 min-w-4" />
|
||||||
<span>{TextCopyBlock(alert.content)}</span>
|
<span>{TextCopyBlock(note.text)}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -1,33 +1,18 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import { Script } from "@/lib/types";
|
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 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 }) {
|
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 (
|
return (
|
||||||
<div className="flex flex-wrap justify-end gap-2">
|
<div className="flex flex-wrap justify-end gap-2">
|
||||||
{item.website && (
|
{item.website && (
|
||||||
@ -49,26 +34,16 @@ export default function Buttons({ item }: { item: Script }) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{item.post_install && (
|
{
|
||||||
<Button variant="secondary" asChild>
|
<Button variant="secondary" asChild>
|
||||||
<Link target="_blank" href={item.post_install}>
|
<Link target="_blank" href={generateSourceUrl(item.slug, item.type)}>
|
||||||
<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)}>
|
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Code className="h-4 w-4" />
|
<Code className="h-4 w-4" />
|
||||||
Source Code
|
Source Code
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import handleCopy from "@/components/handleCopy";
|
|||||||
import { Script } from "@/lib/types";
|
import { Script } from "@/lib/types";
|
||||||
|
|
||||||
export default function DefaultPassword({ item }: { item: Script }) {
|
export default function DefaultPassword({ item }: { item: Script }) {
|
||||||
const hasDefaultLogin = item?.expand?.default_login !== undefined;
|
const hasDefaultLogin = item.default_credentials.username && item.default_credentials.password;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -17,7 +17,7 @@ export default function DefaultPassword({ item }: { item: Script }) {
|
|||||||
<div className="flex flex-col gap-2 p-4">
|
<div className="flex flex-col gap-2 p-4">
|
||||||
<p className="mb-2 text-sm">
|
<p className="mb-2 text-sm">
|
||||||
You can use the following credentials to login to the {""}
|
You can use the following credentials to login to the {""}
|
||||||
{item.title} {item.item_type}.
|
{item.name} {item.type}.
|
||||||
</p>
|
</p>
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
Username:{" "}
|
Username:{" "}
|
||||||
@ -25,10 +25,10 @@ export default function DefaultPassword({ item }: { item: Script }) {
|
|||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
size={"null"}
|
size={"null"}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleCopy("username", item.expand.default_login.username)
|
handleCopy("username", item.default_credentials.username ?? "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{item.expand.default_login.username}
|
{item.default_credentials.username}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
@ -37,10 +37,10 @@ export default function DefaultPassword({ item }: { item: Script }) {
|
|||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
size={"null"}
|
size={"null"}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleCopy("password", item.expand.default_login.password)
|
handleCopy("password", item.default_credentials.password ?? "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{item.expand.default_login.password}
|
{item.default_credentials.password}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,35 +1,53 @@
|
|||||||
import { Script } from "@/lib/types";
|
import { Script } from "@/lib/types";
|
||||||
|
|
||||||
export default function DefaultSettings({ item }: { item: Script }) {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{item.default_cpu && (
|
{defaultSettingsAvailable && (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-md font-semibold">Default settings</h2>
|
<h2 className="text-md font-semibold">Default settings</h2>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
CPU: {item.default_cpu}
|
CPU: {defaultSettings?.resources.cpu}vCPU
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
RAM: {item.default_ram}
|
RAM: {getDisplayValueFromRAM(defaultSettings?.resources.ram ?? 0)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
HDD: {item.default_hdd}
|
HDD: {defaultSettings?.resources.hdd}GB
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasAlpineScript && (
|
{defaultAlpineSettings && (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-md font-semibold">Default Alpine settings</h2>
|
<h2 className="text-md font-semibold">Default Alpine settings</h2>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
CPU: {item.expand.alpine_script.default_cpu}
|
CPU: {defaultAlpineSettings?.resources.cpu}vCPU
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
RAM: {item.expand.alpine_script.default_ram}
|
RAM: {getDisplayValueFromRAM(defaultAlpineSettings?.resources.ram ?? 0)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
HDD: {item.expand.alpine_script.default_hdd}
|
HDD: {defaultAlpineSettings?.resources.hdd}GB
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -1,33 +1,43 @@
|
|||||||
import CodeCopyButton from "@/components/ui/code-copy-button";
|
import CodeCopyButton from "@/components/ui/code-copy-button";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import { Script } from "@/lib/types";
|
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 }) {
|
export default function InstallCommand({ item }: { item: Script }) {
|
||||||
const { title, item_type, installCommand, expand } = item;
|
const alpineScript = item.install_methods.find(
|
||||||
const hasAlpineScript = expand?.alpine_script !== undefined;
|
(method) => method.type === "alpine",
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultScript = item.install_methods.find(
|
||||||
|
(method) => method.type === "default"
|
||||||
|
);
|
||||||
|
|
||||||
const renderInstructions = (isAlpine = false) => (
|
const renderInstructions = (isAlpine = false) => (
|
||||||
<>
|
<>
|
||||||
<p className="text-sm mt-2">
|
<p className="text-sm mt-2">
|
||||||
{isAlpine ? (
|
{isAlpine ? (
|
||||||
<>
|
<>
|
||||||
As an alternative option, you can use Alpine Linux and the {title}{" "}
|
As an alternative option, you can use Alpine Linux and the {item.name}{" "}
|
||||||
package to create a {title} {item_type} container with faster
|
package to create a {item.name} {item.type} container with faster
|
||||||
creation time and minimal system resource usage. You are also
|
creation time and minimal system resource usage. You are also
|
||||||
obliged to adhere to updates provided by the package maintainer.
|
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.
|
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>
|
</p>
|
||||||
{isAlpine && (
|
{isAlpine && (
|
||||||
<p className="mt-2 text-sm">
|
<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
|
below in the Proxmox VE Shell
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@ -36,7 +46,7 @@ export default function InstallCommand({ item }: { item: Script }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
{hasAlpineScript ? (
|
{alpineScript ? (
|
||||||
<Tabs defaultValue="default" className="mt-2 w-full max-w-4xl">
|
<Tabs defaultValue="default" className="mt-2 w-full max-w-4xl">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="default">Default</TabsTrigger>
|
<TabsTrigger value="default">Default</TabsTrigger>
|
||||||
@ -44,25 +54,23 @@ export default function InstallCommand({ item }: { item: Script }) {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="default">
|
<TabsContent value="default">
|
||||||
{renderInstructions()}
|
{renderInstructions()}
|
||||||
<CodeCopyButton>{installCommand}</CodeCopyButton>
|
<CodeCopyButton>{getInstallCommand(defaultScript?.script)}</CodeCopyButton>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="alpine">
|
<TabsContent value="alpine">
|
||||||
{expand.alpine_script && (
|
{renderInstructions(true)}
|
||||||
<>
|
<CodeCopyButton>
|
||||||
{renderInstructions(true)}
|
{getInstallCommand(alpineScript.script)}
|
||||||
<CodeCopyButton>
|
</CodeCopyButton>
|
||||||
{expand.alpine_script.installCommand}
|
|
||||||
</CodeCopyButton>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
) : (
|
) : defaultScript?.script ? (
|
||||||
<>
|
<>
|
||||||
{renderInstructions()}
|
{renderInstructions()}
|
||||||
{installCommand && <CodeCopyButton>{installCommand}</CodeCopyButton>}
|
<CodeCopyButton>
|
||||||
|
{getInstallCommand(defaultScript.script)}
|
||||||
|
</CodeCopyButton>
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,7 @@ import { Button, buttonVariants } from "@/components/ui/button";
|
|||||||
import handleCopy from "@/components/handleCopy";
|
import handleCopy from "@/components/handleCopy";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ClipboardIcon } from "lucide-react";
|
import { ClipboardIcon } from "lucide-react";
|
||||||
|
import { Script } from "@/lib/types";
|
||||||
interface Item {
|
|
||||||
interface?: string;
|
|
||||||
port?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CopyButton = ({
|
const CopyButton = ({
|
||||||
label,
|
label,
|
||||||
@ -24,19 +20,18 @@ const CopyButton = ({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function InterFaces({ item }: { item: Item }) {
|
export default function InterFaces({item} : {item : Script}) {
|
||||||
const { interface: iface, port } = item;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{iface || (port && port !== 0) ? (
|
{item.interface_port !== null ? (
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end">
|
||||||
<h2 className="mr-2 text-end text-lg font-semibold">
|
<h2 className="mr-2 text-end text-lg font-semibold">
|
||||||
{iface ? "Interface:" : "Default Port:"}
|
{"Default Interface:"}
|
||||||
</h2>{" "}
|
</h2>{" "}
|
||||||
<CopyButton
|
<CopyButton
|
||||||
label={iface ? "interface" : "port"}
|
label="default interface"
|
||||||
value={iface || port!}
|
value={item.interface_port}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -37,11 +37,11 @@ export default function Tooltips({ item }: { item: Script }) {
|
|||||||
content="This script will be run in a privileged LXC"
|
content="This script will be run in a privileged LXC"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.isUpdateable && (
|
{item.updateable && (
|
||||||
<TooltipBadge
|
<TooltipBadge
|
||||||
variant="success"
|
variant="success"
|
||||||
label="Updateable"
|
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>
|
</div>
|
||||||
|
@ -18,7 +18,7 @@ const Sidebar = ({
|
|||||||
<h1 className="text-xl font-bold">Categories</h1>
|
<h1 className="text-xl font-bold">Categories</h1>
|
||||||
<p className="text-xs italic text-muted-foreground">
|
<p className="text-xs italic text-muted-foreground">
|
||||||
{items.reduce(
|
{items.reduce(
|
||||||
(acc, category) => acc + category.expand.items.length,
|
(acc, category) => acc + category.scripts.length,
|
||||||
0,
|
0,
|
||||||
)}{" "}
|
)}{" "}
|
||||||
Total scripts
|
Total scripts
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
LatestScripts,
|
LatestScripts,
|
||||||
MostViewedScripts,
|
MostViewedScripts,
|
||||||
} from "./_components/ScriptInfoBlocks";
|
} from "./_components/ScriptInfoBlocks";
|
||||||
|
import { fetchCategories } from "@/lib/data";
|
||||||
|
|
||||||
function ScriptContent() {
|
function ScriptContent() {
|
||||||
const [selectedScript, setSelectedScript] = useQueryState("id");
|
const [selectedScript, setSelectedScript] = useQueryState("id");
|
||||||
@ -21,41 +22,19 @@ function ScriptContent() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedScript && links.length > 0) {
|
if (selectedScript && links.length > 0) {
|
||||||
const script = links
|
const script = links
|
||||||
.map((category) => category.expand.items)
|
.map((category) => category.scripts)
|
||||||
.flat()
|
.flat()
|
||||||
.find((script) => script.title === selectedScript);
|
.find((script) => script.name === selectedScript);
|
||||||
setItem(script);
|
setItem(script);
|
||||||
}
|
}
|
||||||
}, [selectedScript, links]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
fetch(
|
fetchCategories()
|
||||||
`api/categories?_=${process.env.NEXT_PUBLIC_BUILD_TIME || Date.now()}`,
|
.then((categories) => {
|
||||||
)
|
setLinks(categories);
|
||||||
.then((response) => response.json())
|
})
|
||||||
.then((categories) => {
|
.catch((error) => console.error(error));
|
||||||
const sortedCategories = sortCategories(categories);
|
|
||||||
setLinks(sortedCategories);
|
|
||||||
})
|
|
||||||
.catch((error) => console.error(error));
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
|
|
||||||
export default function sitemap(): MetadataRoute.Sitemap {
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||||
|
let domain = "community-scripts.github.io";
|
||||||
|
let protocol = "https";
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
url: "https://community-scripts.github.io/Proxmox/",
|
url: `${protocol}://${domain}/${basePath}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: "yearly",
|
|
||||||
priority: 0.8,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "https://community-scripts.github.io/Proxmox/scripts",
|
url: `${protocol}://${domain}/${basePath}/scripts`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: "monthly",
|
|
||||||
priority: 1,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -6,30 +6,28 @@ import {
|
|||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "@/components/ui/command";
|
} from "@/components/ui/command";
|
||||||
|
import { fetchCategories } from "@/lib/data";
|
||||||
import { Category } from "@/lib/types";
|
import { Category } from "@/lib/types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
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 { Button } from "./ui/button";
|
||||||
import { DialogTitle } from "./ui/dialog";
|
import { DialogTitle } from "./ui/dialog";
|
||||||
|
|
||||||
const sortCategories = (categories: Category[]): Category[] => {
|
export const formattedBadge = (type: string) => {
|
||||||
return categories.sort((a: Category, b: Category) => {
|
switch (type) {
|
||||||
if (
|
case "vm":
|
||||||
a.catagoryName === "Proxmox VE Tools" &&
|
return <Badge className="text-blue-500/75 border-blue-500/75">VM</Badge>;
|
||||||
b.catagoryName !== "Proxmox VE Tools"
|
case "ct":
|
||||||
) {
|
return (
|
||||||
return -1;
|
<Badge className="text-yellow-500/75 border-yellow-500/75">LXC</Badge>
|
||||||
} else if (
|
);
|
||||||
a.catagoryName !== "Proxmox VE Tools" &&
|
case "misc":
|
||||||
b.catagoryName === "Proxmox VE Tools"
|
return <Badge className="text-red-500/75 border-red-500/75">MISC</Badge>;
|
||||||
) {
|
}
|
||||||
return 1;
|
return null;
|
||||||
} else {
|
|
||||||
return a.catagoryName.localeCompare(b.catagoryName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CommandMenu() {
|
export default function CommandMenu() {
|
||||||
@ -50,21 +48,17 @@ export default function CommandMenu() {
|
|||||||
return () => document.removeEventListener("keydown", down);
|
return () => document.removeEventListener("keydown", down);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchCategories = async () => {
|
const fetchSortedCategories = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetch(
|
fetchCategories()
|
||||||
`api/categories?_=${process.env.NEXT_PUBLIC_BUILD_TIME || Date.now()}`,
|
.then((categories) => {
|
||||||
)
|
setLinks(categories);
|
||||||
.then((response) => response.json())
|
setIsLoading(false);
|
||||||
.then((categories) => {
|
})
|
||||||
const sortedCategories = sortCategories(categories);
|
.catch((error) => {
|
||||||
setLinks(sortedCategories);
|
setIsLoading(false);
|
||||||
setIsLoading(false);
|
console.error(error);
|
||||||
})
|
});
|
||||||
.catch((error) => {
|
|
||||||
setIsLoading(false);
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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",
|
"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={() => {
|
onClick={() => {
|
||||||
fetchCategories();
|
fetchSortedCategories();
|
||||||
setOpen(true)
|
setOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="inline-flex">Search scripts...</span>
|
<span className="inline-flex">Search scripts...</span>
|
||||||
@ -85,41 +79,40 @@ export default function CommandMenu() {
|
|||||||
</kbd>
|
</kbd>
|
||||||
</Button>
|
</Button>
|
||||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTitle className="sr-only">Search scripts</DialogTitle>
|
<DialogTitle className="sr-only">Search scripts</DialogTitle>
|
||||||
<CommandInput placeholder="search for a script..." />
|
<CommandInput placeholder="Search for a script..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>{isLoading ? "Loading..." : "No scripts found."}</CommandEmpty>
|
<CommandEmpty>
|
||||||
|
{isLoading ? "Loading..." : "No scripts found."}
|
||||||
|
</CommandEmpty>
|
||||||
{links.map((category) => (
|
{links.map((category) => (
|
||||||
<CommandGroup
|
<CommandGroup
|
||||||
key={"category:" + category.catagoryName}
|
key={`category:${category.name}`}
|
||||||
heading={category.catagoryName}
|
heading={category.name}
|
||||||
>
|
>
|
||||||
{category.expand.items.map((script) => (
|
{category.scripts.map((script) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={"script:" + script.id}
|
key={`script:${script.name}`}
|
||||||
value={script.title}
|
value={script.name}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
router.push(`/scripts?id=${script.title}`);
|
router.push(`/scripts?id=${script.name}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex gap-2" onClick={() => setOpen(false)}>
|
<div className="flex gap-2" onClick={() => setOpen(false)}>
|
||||||
<Image
|
<Image
|
||||||
src={script.logo}
|
src={script.logo || "/logo.png"}
|
||||||
unoptimized
|
|
||||||
height={16}
|
|
||||||
onError={(e) =>
|
onError={(e) =>
|
||||||
((e.currentTarget as HTMLImageElement).src =
|
((e.currentTarget as HTMLImageElement).src =
|
||||||
"/logo.png")
|
"/logo.png")
|
||||||
}
|
}
|
||||||
width={16}
|
width={16}
|
||||||
|
height={16}
|
||||||
alt=""
|
alt=""
|
||||||
className="h-5 w-5"
|
className="h-5 w-5"
|
||||||
/>
|
/>
|
||||||
<span>{script.title}</span>
|
<span>{script.name}</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span>{formattedBadge(script.type)}</span>
|
||||||
{script.item_type}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
@ -7,7 +8,7 @@ export default function Footer() {
|
|||||||
<div className="mx-6 w-full max-w-7xl text-sm text-muted-foreground">
|
<div className="mx-6 w-full max-w-7xl text-sm text-muted-foreground">
|
||||||
Website build by the community. The source code is avaliable on{" "}
|
Website build by the community. The source code is avaliable on{" "}
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/community-scripts/Proxmox"
|
href={`https://github.com/community-scripts/${basePath}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="font-semibold underline-offset-2 duration-300 hover:underline"
|
className="font-semibold underline-offset-2 duration-300 hover:underline"
|
||||||
|
@ -6,11 +6,9 @@ import { useEffect, useState } from "react";
|
|||||||
|
|
||||||
import { navbarLinks } from "@/config/siteConfig";
|
import { navbarLinks } from "@/config/siteConfig";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { MoonIcon, SunIcon } from "lucide-react";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import CommandMenu from "./CommandMenu";
|
import CommandMenu from "./CommandMenu";
|
||||||
import StarOnGithubButton from "./ui/star-on-github-button";
|
import StarOnGithubButton from "./ui/star-on-github-button";
|
||||||
|
import { ThemeToggle } from "./ui/theme-toggle";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@ -22,7 +20,6 @@ export const dynamic = "force-dynamic";
|
|||||||
|
|
||||||
function Navbar() {
|
function Navbar() {
|
||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
const { theme, setTheme } = useTheme();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
@ -56,7 +53,6 @@ function Navbar() {
|
|||||||
/>
|
/>
|
||||||
<span className="hidden lg:block">Proxmox VE Helper-Scripts</span>
|
<span className="hidden lg:block">Proxmox VE Helper-Scripts</span>
|
||||||
</Link>
|
</Link>
|
||||||
{/* <MobileNav /> */}
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<CommandMenu />
|
<CommandMenu />
|
||||||
<StarOnGithubButton />
|
<StarOnGithubButton />
|
||||||
@ -81,28 +77,7 @@ function Navbar() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
))}
|
))}
|
||||||
<TooltipProvider>
|
<ThemeToggle />
|
||||||
<Tooltip delayDuration={100}>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
type="button"
|
|
||||||
size="icon"
|
|
||||||
className={cn("px-2")}
|
|
||||||
aria-label="Toggle theme"
|
|
||||||
onClick={() =>
|
|
||||||
setTheme(theme === "dark" ? "light" : "dark")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SunIcon className="size-[1.2rem] text-neutral-800 dark:hidden dark:text-neutral-200" />
|
|
||||||
<MoonIcon className="hidden size-[1.2rem] text-neutral-800 dark:block dark:text-neutral-200" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom" className="text-xs">
|
|
||||||
Theme Toggle
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,6 +8,7 @@ import * as React from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { Separator } from "./separator";
|
import { Separator } from "./separator";
|
||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
@ -67,7 +68,7 @@ const handleCopy = (type: string, value: string) => {
|
|||||||
<div>
|
<div>
|
||||||
<Button className="text-white">
|
<Button className="text-white">
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/community-scripts/ProxmoxVE"
|
href={`https://github.com/community-scripts/${basePath}`}
|
||||||
data-umami-event="Star on Github"
|
data-umami-event="Star on Github"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { FaGithub, FaStar } from "react-icons/fa";
|
import { FaGithub, FaStar } from "react-icons/fa";
|
||||||
import NumberTicker from "./number-ticker";
|
import NumberTicker from "./number-ticker";
|
||||||
import { buttonVariants } from "./button";
|
import { buttonVariants } from "./button";
|
||||||
|
import { basePath } from "@/config/siteConfig";
|
||||||
|
|
||||||
export default function StarOnGithubButton() {
|
export default function StarOnGithubButton() {
|
||||||
const [stars, setStars] = useState(0);
|
const [stars, setStars] = useState(0);
|
||||||
@ -11,7 +12,7 @@ export default function StarOnGithubButton() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchStars = async () => {
|
const fetchStars = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("https://api.github.com/repos/community-scripts/ProxmoxVE", {
|
const res = await fetch(`https://api.github.com/repos/community-scripts/${basePath}`, {
|
||||||
next: { revalidate: 60 * 60 * 24 },
|
next: { revalidate: 60 * 60 * 24 },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ export default function StarOnGithubButton() {
|
|||||||
"group relative justify-center gap-2 rounded-md transition-all duration-300 ease-out hover:ring-2 hover:ring-primary hover:ring-offset-2",
|
"group relative justify-center gap-2 rounded-md transition-all duration-300 ease-out hover:ring-2 hover:ring-primary hover:ring-offset-2",
|
||||||
)}
|
)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://github.com/community-scripts/ProxmoxVE"
|
href={`https://github.com/community-scripts/${basePath}`}
|
||||||
>
|
>
|
||||||
<span className="absolute right-0 -mt-12 h-32 translate-x-12 rotate-12 bg-white opacity-10 transition-all duration-1000 ease-out group-hover:-translate-x-40" />
|
<span className="absolute right-0 -mt-12 h-32 translate-x-12 rotate-12 bg-white opacity-10 transition-all duration-1000 ease-out group-hover:-translate-x-40" />
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
42
frontend/src/components/ui/theme-toggle.tsx
Normal file
42
frontend/src/components/ui/theme-toggle.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
||||||
|
import { Button } from "./button";
|
||||||
|
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
|
export function ThemeToggle() {
|
||||||
|
const { setTheme, theme: currentTheme } = useTheme();
|
||||||
|
|
||||||
|
const handleChangeTheme = (theme: "light" | "dark") => {
|
||||||
|
if (theme === currentTheme) return;
|
||||||
|
|
||||||
|
if (!document.startViewTransition) return setTheme(theme);
|
||||||
|
document.startViewTransition(() => setTheme(theme));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip delayDuration={100}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
type="button"
|
||||||
|
size="icon"
|
||||||
|
className="px-2"
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
onClick={() =>
|
||||||
|
handleChangeTheme(currentTheme === "dark" ? "light" : "dark")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SunIcon className="size-[1.2rem] text-neutral-800 dark:hidden dark:text-neutral-200" />
|
||||||
|
<MoonIcon className="hidden size-[1.2rem] text-neutral-800 dark:block dark:text-neutral-200" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom" className="text-xs">
|
||||||
|
Theme Toggle
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
@ -1,23 +1,36 @@
|
|||||||
import { MessagesSquare, Scroll } from "lucide-react";
|
import { MessagesSquare, Scroll } from "lucide-react";
|
||||||
import { FaGithub } from "react-icons/fa";
|
import { FaGithub } from "react-icons/fa";
|
||||||
|
|
||||||
|
export const basePath = process.env.BASE_PATH;
|
||||||
|
|
||||||
export const navbarLinks = [
|
export const navbarLinks = [
|
||||||
{
|
{
|
||||||
href: "https://github.com/community-scripts/ProxmoxVE",
|
href: `https://github.com/community-scripts/${basePath}`,
|
||||||
event: "Github",
|
event: "Github",
|
||||||
icon: <FaGithub className="h-4 w-4" />,
|
icon: <FaGithub className="h-4 w-4" />,
|
||||||
text: "Github",
|
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",
|
event: "Change Log",
|
||||||
icon: <Scroll className="h-4 w-4" />,
|
icon: <Scroll className="h-4 w-4" />,
|
||||||
text: "Change Log",
|
text: "Change Log",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "https://github.com/community-scripts/ProxmoxVE/discussions",
|
href: `https://github.com/community-scripts/${basePath}/discussions`,
|
||||||
event: "Discussions",
|
event: "Discussions",
|
||||||
icon: <MessagesSquare className="h-4 w-4" />,
|
icon: <MessagesSquare className="h-4 w-4" />,
|
||||||
text: "Discussions",
|
text: "Discussions",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
23
frontend/src/lib/data.ts
Normal file
23
frontend/src/lib/data.ts
Normal file
@ -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<Category[]> => {
|
||||||
|
const response = await fetch("api/categories");
|
||||||
|
const categories = await response.json();
|
||||||
|
return sortCategories(categories);
|
||||||
|
};
|
@ -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}`;
|
|
||||||
};
|
|
@ -1,55 +1,44 @@
|
|||||||
// these are all the interfaces that are used in the site. these all come from the pocketbase database
|
export type Script = {
|
||||||
|
name: string;
|
||||||
export interface Script {
|
slug: string;
|
||||||
title: string;
|
categories: number[];
|
||||||
description: string;
|
date_created: string;
|
||||||
documentation: string;
|
type: "vm" | "ct" | "misc";
|
||||||
website: string;
|
updateable: boolean;
|
||||||
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;
|
|
||||||
privileged: boolean;
|
privileged: boolean;
|
||||||
alpineScript: alpine_script;
|
interface_port: number | null;
|
||||||
expand: {
|
documentation: string | null;
|
||||||
alpine_script: alpine_script;
|
website: string | null;
|
||||||
alerts: alerts[];
|
logo: string | null;
|
||||||
default_login: default_login;
|
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 {
|
export type Category = {
|
||||||
catagoryName: string;
|
name: string;
|
||||||
categoryId: string;
|
id: number;
|
||||||
id: string;
|
sort_order: number;
|
||||||
created: string;
|
scripts: Script[];
|
||||||
expand: {
|
|
||||||
items: Script[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface alpine_script {
|
export type ScriptList = {
|
||||||
installCommand: string;
|
categories: Category[];
|
||||||
default_cpu: string;
|
|
||||||
default_hdd: string;
|
|
||||||
default_ram: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface alerts {
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface default_login {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
}
|
}
|
@ -30,6 +30,29 @@
|
|||||||
--chart-3: 197 37% 24%;
|
--chart-3: 197 37% 24%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 43 74% 66%;
|
||||||
--chart-5: 27 87% 67%;
|
--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 {
|
.dark {
|
||||||
@ -58,8 +81,46 @@
|
|||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--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 {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
|
Loading…
Reference in New Issue
Block a user