diff --git a/.github/workflows/frontend-cicd.yml b/.github/workflows/frontend-cicd.yml index b092ca1e9..1e045517a 100644 --- a/.github/workflows/frontend-cicd.yml +++ b/.github/workflows/frontend-cicd.yml @@ -44,9 +44,6 @@ jobs: - name: Install dependencies run: npm ci --prefer-offline --legacy-peer-deps - - name: Run tests - run: npm run test - - name: Configure Next.js for pages uses: actions/configure-pages@v5 with: diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json deleted file mode 100644 index 32ef36a56..000000000 --- a/frontend/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": ["next/core-web-vitals"], - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"] -} diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json new file mode 100644 index 000000000..9f2f44827 --- /dev/null +++ b/frontend/.vscode/settings.json @@ -0,0 +1,51 @@ +{ + // Disable the default formatter, use eslint instead + "prettier.enable": false, + "editor.formatOnSave": false, + + // Auto fix + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" + }, + + // Silent the stylistic rules in you IDE, but still auto fix them + "eslint.rules.customizations": [ + { "rule": "style/*", "severity": "off", "fixable": true }, + { "rule": "format/*", "severity": "off", "fixable": true }, + { "rule": "*-indent", "severity": "off", "fixable": true }, + { "rule": "*-spacing", "severity": "off", "fixable": true }, + { "rule": "*-spaces", "severity": "off", "fixable": true }, + { "rule": "*-order", "severity": "off", "fixable": true }, + { "rule": "*-dangle", "severity": "off", "fixable": true }, + { "rule": "*-newline", "severity": "off", "fixable": true }, + { "rule": "*quotes", "severity": "off", "fixable": true }, + { "rule": "*semi", "severity": "off", "fixable": true } + ], + + // Enable eslint for all supported languages + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "vue", + "html", + "markdown", + "json", + "json5", + "jsonc", + "yaml", + "toml", + "xml", + "gql", + "graphql", + "astro", + "svelte", + "css", + "less", + "scss", + "pcss", + "postcss" + ] +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 000000000..5021df53a --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,281 @@ +# Proxmox VE Helper-Scripts Frontend + +> ๐ **Modern frontend for the Community-Scripts Proxmox VE Helper-Scripts repository** + +A comprehensive, user-friendly interface built with Next.js that provides access to 300+ automation scripts for Proxmox Virtual Environment management. This frontend serves as the official website for the Community-Scripts organization's Proxmox VE Helper-Scripts repository. + + + + + + + +## ๐ Features + +### Core Functionality + +- **๐ Script Management**: Browse, search, and filter 300+ Proxmox VE scripts +- **๐ฑ Responsive Design**: Mobile-first approach with modern UI/UX +- **๐ Advanced Search**: Fuzzy search with category filtering +- **๐ Analytics Integration**: Built-in analytics for usage tracking +- **๐ Dark/Light Mode**: Theme switching with system preference detection +- **โก Performance Optimized**: Static site generation for lightning-fast loading + +### Technical Features + +- **๐จ Modern UI Components**: Built with Radix UI and shadcn/ui +- **๐ Data Visualization**: Charts and metrics using Chart.js +- **๐ State Management**: React Query for efficient data fetching +- **๐ Type Safety**: Full TypeScript implementation +- **๐ Static Export**: Optimized for GitHub Pages deployment + +## ๐ ๏ธ Tech Stack + +### Frontend Framework + +- **[Next.js 15.2.4](https://nextjs.org/)** - React framework with App Router +- **[React 19.0.0](https://react.dev/)** - Latest React with concurrent features +- **[TypeScript 5.8.2](https://www.typescriptlang.org/)** - Type-safe JavaScript + +### Styling & UI + +- **[Tailwind CSS 3.4.17](https://tailwindcss.com/)** - Utility-first CSS framework +- **[Radix UI](https://www.radix-ui.com/)** - Unstyled, accessible UI components +- **[shadcn/ui](https://ui.shadcn.com/)** - Re-usable components built on Radix UI +- **[Framer Motion](https://www.framer.com/motion/)** - Animation library +- **[Lucide React](https://lucide.dev/)** - Icon library + +### Data & State Management + +- **[TanStack Query 5.71.1](https://tanstack.com/query)** - Powerful data synchronization +- **[Zod 3.24.2](https://zod.dev/)** - TypeScript-first schema validation +- **[nuqs 2.4.1](https://nuqs.47ng.com/)** - Type-safe search params state manager + +### Development Tools + +- **[Vitest 3.1.1](https://vitest.dev/)** - Fast unit testing framework +- **[React Testing Library](https://testing-library.com/react)** - Simple testing utilities +- **[ESLint](https://eslint.org/)** - Code linting and formatting +- **[Prettier](https://prettier.io/)** - Code formatting + +### Additional Libraries + +- **[Chart.js](https://www.chartjs.org/)** - Data visualization +- **[Fuse.js](https://fusejs.io/)** - Fuzzy search +- **[date-fns](https://date-fns.org/)** - Date utility library +- **[Next Themes](https://github.com/pacocoursey/next-themes)** - Theme management + +## ๐ Getting Started + +### Prerequisites + +- **Node.js 18+** (recommend using the latest LTS version) +- **npm**, **yarn**, **pnpm**, or **bun** package manager +- **Git** for version control + +### Installation + +1. **Clone the repository** + + ```bash + git clone https://github.com/community-scripts/ProxmoxVE.git + cd ProxmoxVE/frontend + ``` + +2. **Install dependencies** + + ```bash + # Using npm + npm install + + # Using yarn + yarn install + + # Using pnpm + pnpm install + + # Using bun + bun install + ``` + +3. **Start the development server** + + ```bash + npm run dev + # or + yarn dev + # or + pnpm dev + # or + bun dev + ``` + +4. **Open your browser** + + Navigate to [http://localhost:3000](http://localhost:3000) to see the application running. + +### Environment Configuration + +The application uses the following environment variables: + +- `BASE_PATH`: Set to "ProxmoxVE" for GitHub Pages deployment +- Analytics configuration is handled in `src/config/siteConfig.tsx` + +## ๐งช Development + +### Available Scripts + +```bash +# Development +npm run dev # Start development server with Turbopack +npm run build # Build for production +npm run start # Start production server (after build) + +# Code Quality +npm run lint # Run ESLint +npm run typecheck # Run TypeScript type checking +npm run format:write # Format code with Prettier +npm run format:check # Check code formatting + +# Deployment +npm run deploy # Build and deploy to GitHub Pages +``` + +### Development Workflow + +1. **Feature Development** + + - Create a new branch for your feature + - Follow the established TypeScript and React patterns + - Use the existing component library (shadcn/ui) + - Ensure responsive design principles + +2. **Code Standards** + + - Follow TypeScript strict mode + - Use functional components with hooks + - Implement proper error boundaries + - Write descriptive variable and function names + - Use early returns for better readability + +3. **Styling Guidelines** + + - Use Tailwind CSS utility classes + - Follow mobile-first responsive design + - Implement dark/light mode considerations + - Use CSS variables from the design system + +4. **Testing** + - Write unit tests for utility functions + - Test React components with React Testing Library + - Ensure accessibility standards are met + - Run tests before committing + +### Component Development + +The project uses a component-driven development approach: + +```typescript +// Example component structure +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; + +interface ComponentProps { + title: string; + className?: string; +} + +export const Component = ({ title, className }: ComponentProps) => { + return ( +
No categories available. Please check the API endpoint.
)} - {selectedCategoryIndex !== null ? ( -- {categories.reduce((total, category) => total + (category.scripts?.length || 0), 0)} Total scripts -
-+ Created at: + {" "} + {script.date_created || "No date available"} +
++ {truncateDescription(script.description || "No description available.")} +
+ {renderResources(script)} ++ {categories.reduce((total, category) => total + (category.scripts?.length || 0), 0)} + {" "} + Total scripts +
++ {(category as any).description || "No description available."} +
+Loading...
; - if (error) returnError: {error}
; + if (loading) + returnLoading...
; + if (error) { + return ( ++ Error: + {error} +
+ ); + } const requestSort = (key: string) => { - let direction: 'ascending' | 'descending' = 'ascending'; - if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') { - direction = 'descending'; + let direction: "ascending" | "descending" = "ascending"; + if (sortConfig && sortConfig.key === key && sortConfig.direction === "ascending") { + direction = "descending"; } setSortConfig({ key, direction }); }; @@ -102,8 +116,8 @@ const DataFetcher: React.FC = () => { const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); const timezoneOffset = dateString.slice(-6); return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`; }; @@ -114,49 +128,76 @@ const DataFetcher: React.FC = () => {
{summary?.total_entries} results found
-Status Legend: ๐ installing {summary?.status_count["installing"] ?? 0} | โ๏ธ completed {summary?.status_count["done"] ?? 0} | โ failed {summary?.status_count["failed"] ?? 0} | โ unknown
-+ {summary?.total_entries} + {" "} + results found +
++ Status Legend: ๐ installing + {summary?.status_count.installing ?? 0} + {" "} + | โ๏ธ completed + {summary?.status_count.done ?? 0} + {" "} + | โ failed + {summary?.status_count.failed ?? 0} + {" "} + | โ unknown +
+requestSort('status')}>Status | -requestSort('type')}>Type | -requestSort('nsapp')}>Application | -requestSort('os_type')}>OS | -requestSort('os_version')}>OS Version | -requestSort('disk_size')}>Disk Size | -requestSort('core_count')}>Core Count | -requestSort('ram_size')}>RAM Size | -requestSort('method')}>Method | -requestSort('pve_version')}>PVE Version | -requestSort('error')}>Error Message | -requestSort('created_at')}>Created At | +requestSort("status")}>Status | +requestSort("type")}>Type | +requestSort("nsapp")}>Application | +requestSort("os_type")}>OS | +requestSort("os_version")}>OS Version | +requestSort("disk_size")}>Disk Size | +requestSort("core_count")}>Core Count | +requestSort("ram_size")}>RAM Size | +requestSort("method")}>Method | +requestSort("pve_version")}>PVE Version | +requestSort("error")}>Error Message | +requestSort("created_at")}>Created At |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- {item.status === "done" ? ( - "โ๏ธ" - ) : item.status === "failed" ? ( - "โ" - ) : item.status === "installing" ? ( - "๐" - ) : ( - item.status - )} + {item.status === "done" + ? ( + "โ๏ธ" + ) + : item.status === "failed" + ? ( + "โ" + ) + : item.status === "installing" + ? ( + "๐" + ) + : ( + item.status + )} + | ++ {item.type === "lxc" + ? ( + "๐ฆ" + ) + : item.type === "vm" + ? ( + "๐ฅ๏ธ" + ) + : ( + item.type + )} | -{item.type === "lxc" ? ( - "๐ฆ" - ) : item.type === "vm" ? ( - "๐ฅ๏ธ" - ) : ( - item.type - )} | {item.nsapp} | {item.os_type} | {item.os_version} | @@ -175,11 +216,14 @@ const DataFetcher: React.FC = () => {