diff --git a/src/app/_components/Footer.tsx b/src/app/_components/Footer.tsx
new file mode 100644
index 0000000..90509d3
--- /dev/null
+++ b/src/app/_components/Footer.tsx
@@ -0,0 +1,64 @@
+'use client';
+
+import { api } from '~/trpc/react';
+import { Button } from './ui/button';
+import { ExternalLink, FileText } from 'lucide-react';
+
+interface FooterProps {
+ onOpenReleaseNotes: () => void;
+}
+
+export function Footer({ onOpenReleaseNotes }: FooterProps) {
+ const { data: versionData } = api.version.getCurrentVersion.useQuery();
+
+ return (
+
+
+
+
+ © 2024 PVE Scripts Local
+ {versionData?.success && versionData.version && (
+
+ v{versionData.version}
+
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/_components/GeneralSettingsModal.tsx b/src/app/_components/GeneralSettingsModal.tsx
index bfe710f..f2d105b 100644
--- a/src/app/_components/GeneralSettingsModal.tsx
+++ b/src/app/_components/GeneralSettingsModal.tsx
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Toggle } from './ui/toggle';
+import { ContextualHelpIcon } from './ContextualHelpIcon';
interface GeneralSettingsModalProps {
isOpen: boolean;
@@ -280,7 +281,10 @@ export function GeneralSettingsModal({ isOpen, onClose }: GeneralSettingsModalPr
{/* Header */}
-
Settings
+
+
Settings
+
+
+
+
+ Need help?
+
+
setIsOpen(true)}
+ variant="outline"
+ size="default"
+ className="inline-flex items-center"
+ title="Open Help"
+ >
+
+ Help
+
+
+
+ setIsOpen(false)}
+ initialSection={initialSection}
+ />
+ >
+ );
+}
diff --git a/src/app/_components/HelpModal.tsx b/src/app/_components/HelpModal.tsx
new file mode 100644
index 0000000..3c3fd9f
--- /dev/null
+++ b/src/app/_components/HelpModal.tsx
@@ -0,0 +1,492 @@
+'use client';
+
+import { useState } from 'react';
+import { Button } from './ui/button';
+import { HelpCircle, Server, Settings, RefreshCw, Package, HardDrive, FolderOpen, Search, Download } from 'lucide-react';
+
+interface HelpModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ initialSection?: string;
+}
+
+type HelpSection = 'server-settings' | 'general-settings' | 'sync-button' | 'available-scripts' | 'downloaded-scripts' | 'installed-scripts' | 'update-system';
+
+export function HelpModal({ isOpen, onClose, initialSection = 'server-settings' }: HelpModalProps) {
+ const [activeSection, setActiveSection] = useState(initialSection as HelpSection);
+
+ if (!isOpen) return null;
+
+ const sections = [
+ { id: 'server-settings' as HelpSection, label: 'Server Settings', icon: Server },
+ { id: 'general-settings' as HelpSection, label: 'General Settings', icon: Settings },
+ { id: 'sync-button' as HelpSection, label: 'Sync Button', icon: RefreshCw },
+ { id: 'available-scripts' as HelpSection, label: 'Available Scripts', icon: Package },
+ { id: 'downloaded-scripts' as HelpSection, label: 'Downloaded Scripts', icon: HardDrive },
+ { id: 'installed-scripts' as HelpSection, label: 'Installed Scripts', icon: FolderOpen },
+ { id: 'update-system' as HelpSection, label: 'Update System', icon: Download },
+ ];
+
+ const renderContent = () => {
+ switch (activeSection) {
+ case 'server-settings':
+ return (
+
+
+
Server Settings
+
+ Manage your Proxmox VE servers and configure connection settings.
+
+
+
+
+
+
Adding PVE Servers
+
+ • Server Name: A friendly name to identify your server
+ • IP Address: The IP address or hostname of your PVE server
+ • Username: PVE user account (usually root or a dedicated user)
+ • SSH Port: Default is 22, change if your server uses a different port
+
+
+
+
+
Authentication Types
+
+ • Password: Use username and password authentication
+ • SSH Key: Use SSH key pair for secure authentication
+ • Both: Try SSH key first, fallback to password if needed
+
+
+
+
+
Server Color Coding
+
+ Assign colors to servers for visual distinction throughout the application.
+ This helps identify which server you're working with when managing scripts.
+ This needs to be enabled in the General Settings.
+
+
+
+
+ );
+
+ case 'general-settings':
+ return (
+
+
+
General Settings
+
+ Configure application preferences and behavior.
+
+
+
+
+
+
Save Filters
+
+ When enabled, your script filter preferences (search terms, categories, sorting)
+ will be automatically saved and restored when you return to the application.
+
+
+ • Search queries are preserved
+ • Selected script types are remembered
+ • Sort preferences are maintained
+ • Category selections are saved
+
+
+
+
+
Server Color Coding
+
+ Enable visual color coding for servers throughout the application.
+ This makes it easier to identify which server you're working with.
+
+
+
+
+
GitHub Integration
+
+ Add a GitHub Personal Access Token to increase API rate limits and improve performance.
+
+
+ • Bypasses GitHub's rate limiting for unauthenticated requests
+ • Improves script loading and syncing performance
+ • Token is stored securely and only used for API calls
+
+
+
+
+
Authentication
+
+ Secure your application with username and password authentication.
+
+
+ • Set up username and password for app access
+ • Enable/disable authentication as needed
+ • Credentials are stored securely
+
+
+
+
+ );
+
+ case 'sync-button':
+ return (
+
+
+
Sync Button
+
+ Synchronize script metadata from the ProxmoxVE GitHub repository.
+
+
+
+
+
+
What Does Syncing Do?
+
+ • Updates Script Metadata: Downloads the latest script information (JSON files)
+ • Refreshes Available Scripts: Updates the list of scripts you can download
+ • Updates Categories: Refreshes script categories and organization
+ • Checks for Updates: Identifies which downloaded scripts have newer versions
+
+
+
+
+
Important Notes
+
+ • Metadata Only: Syncing only updates script information, not the actual script files
+ • No Downloads: Script files are downloaded separately when you choose to install them
+ • Last Sync Time: Shows when the last successful sync occurred
+ • Rate Limits: GitHub API limits may apply without a personal access token
+
+
+
+
+
When to Sync
+
+ • When you want to see the latest available scripts
+ • To check for updates to your downloaded scripts
+ • If you notice scripts are missing or outdated
+ • After the ProxmoxVE repository has been updated
+
+
+
+
+ );
+
+ case 'available-scripts':
+ return (
+
+
+
Available Scripts
+
+ Browse and discover scripts from the ProxmoxVE repository.
+
+
+
+
+
+
Browsing Scripts
+
+ • Category Sidebar: Filter scripts by category (Storage, Network, Security, etc.)
+ • Search: Find scripts by name or description
+ • View Modes: Switch between card and list view
+ • Sorting: Sort by name or creation date
+
+
+
+
+
Filtering Options
+
+ • Script Types: Filter by CT (Container) or other script types
+ • Update Status: Show only scripts with available updates
+ • Search Query: Search within script names and descriptions
+ • Categories: Filter by specific script categories
+
+
+
+
+
Script Actions
+
+ • View Details: Click on a script to see full information and documentation
+ • Download: Download script files to your local system
+ • Install: Run scripts directly on your PVE servers
+ • Preview: View script content before downloading
+
+
+
+
+ );
+
+ case 'downloaded-scripts':
+ return (
+
+
+
Downloaded Scripts
+
+ Manage scripts that have been downloaded to your local system.
+
+
+
+
+
+
What Are Downloaded Scripts?
+
+ These are scripts that you've downloaded from the repository and are stored locally on your system.
+
+
+ • Script files are stored in your local scripts directory
+ • You can run these scripts on your PVE servers
+ • Scripts can be updated when newer versions are available
+
+
+
+
+
Update Detection
+
+ The system automatically checks if newer versions of your downloaded scripts are available.
+
+
+ • Scripts with updates available are marked with an update indicator
+ • You can filter to show only scripts with available updates
+ • Update detection happens when you sync with the repository
+
+
+
+
+
Managing Downloaded Scripts
+
+ • Update Scripts: Download the latest version of a script
+ • View Details: See script information and documentation
+ • Install/Run: Execute scripts on your PVE servers
+ • Filter & Search: Use the same filtering options as Available Scripts
+
+
+
+
+ );
+
+ case 'installed-scripts':
+ return (
+
+
+
Installed Scripts
+
+ Track and manage scripts that are installed on your PVE servers.
+
+
+
+
+
+
+
+ Auto-Detection (Primary Feature)
+
+
+ The system can automatically detect LXC containers that have community-script tags on your PVE servers.
+
+
+ • Automatic Discovery: Scans your PVE servers for containers with community-script tags
+ • Container Detection: Identifies LXC containers running Proxmox helper scripts
+ • Server Association: Links detected scripts to the specific PVE server
+ • Bulk Import: Automatically creates records for all detected scripts
+
+
+
How Auto-Detection Works:
+
+ 1. Connects to your configured PVE servers
+ 2. Scans LXC container configurations
+ 3. Looks for containers with community-script tags
+ 4. Creates installed script records automatically
+
+
+
+
+
+
Manual Script Management
+
+ • Add Scripts Manually: Create records for scripts not auto-detected
+ • Edit Script Details: Update script names and container IDs
+ • Delete Scripts: Remove scripts from tracking
+ • Bulk Operations: Clean up old or invalid script records
+
+
+
+
+
Script Tracking Features
+
+ • Installation Status: Track success, failure, or in-progress installations
+ • Server Association: Know which server each script is installed on
+ • Container ID: Link scripts to specific LXC containers
+ • Execution Logs: View output and logs from script installations
+ • Filtering: Filter by server, status, or search terms
+
+
+
+
+
Managing Installed Scripts
+
+ • View All Scripts: See all tracked scripts across all servers
+ • Filter by Server: Show scripts for a specific PVE server
+ • Filter by Status: Show successful, failed, or in-progress installations
+ • Sort Options: Sort by name, container ID, server, status, or date
+ • Update Scripts: Re-run or update existing script installations
+
+
+
+
+ );
+
+ case 'update-system':
+ return (
+
+
+
Update System
+
+ Keep your PVE Scripts Management application up to date with the latest features and improvements.
+
+
+
+
+
+
What Does Updating Do?
+
+ • Downloads Latest Version: Fetches the newest release from the GitHub repository
+ • Updates Application Files: Replaces current files with the latest version
+ • Installs Dependencies: Updates Node.js packages and dependencies
+ • Rebuilds Application: Compiles the application with latest changes
+ • Restarts Server: Automatically restarts the application server
+
+
+
+
+
How to Update
+
+
+
Automatic Update (Recommended)
+
+ • Click the "Update Now" button when an update is available
+ • The system will handle everything automatically
+ • You'll see a progress overlay with update logs
+ • The page will reload automatically when complete
+
+
+
+
+
Manual Update (Advanced)
+
If automatic update fails, you can update manually:
+
+
# Navigate to the application directory
+
cd $PVESCRIPTLOCAL_DIR
+
# Pull latest changes
+
git pull
+
# Install dependencies
+
npm install
+
# Build the application
+
npm run build
+
# Start the application
+
npm start
+
+
+
+
+
+
+
Update Process
+
+ 1. Check for Updates: System automatically checks GitHub for new releases
+ 2. Download Update: Downloads the latest release files
+ 3. Backup Current Version: Creates backup of current installation
+ 4. Install New Version: Replaces files and updates dependencies
+ 5. Build Application: Compiles the updated code
+ 6. Restart Server: Stops old server and starts new version
+ 7. Reload Page: Automatically refreshes the browser
+
+
+
+
+
Release Notes
+
+ Click the external link icon next to the update button to view detailed release notes on GitHub.
+
+
+ • See what's new in each version
+ • Read about bug fixes and improvements
+ • Check for any breaking changes
+ • View installation requirements
+
+
+
+
+
Important Notes
+
+ • Backup: Your data and settings are preserved during updates
+ • Downtime: Brief downtime occurs during the update process
+ • Compatibility: Updates maintain backward compatibility with your data
+ • Rollback: If issues occur, you can manually revert to previous version
+
+
+
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+ Help & Documentation
+
+
+
+
+
+
+
+
+
+ {/* Sidebar Navigation */}
+
+
+ {sections.map((section) => {
+ const Icon = section.icon;
+ return (
+ setActiveSection(section.id)}
+ variant={activeSection === section.id ? "default" : "ghost"}
+ size="sm"
+ className="w-full justify-start gap-2 text-left"
+ >
+
+ {section.label}
+
+ );
+ })}
+
+
+
+ {/* Content Area */}
+
+
+ {renderContent()}
+
+
+
+
+
+ );
+}
diff --git a/src/app/_components/ReleaseNotesModal.tsx b/src/app/_components/ReleaseNotesModal.tsx
new file mode 100644
index 0000000..d46ce3b
--- /dev/null
+++ b/src/app/_components/ReleaseNotesModal.tsx
@@ -0,0 +1,202 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { api } from '~/trpc/react';
+import { Button } from './ui/button';
+import { Badge } from './ui/badge';
+import { X, ExternalLink, Calendar, Tag, Loader2 } from 'lucide-react';
+
+interface ReleaseNotesModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ highlightVersion?: string;
+}
+
+interface Release {
+ tagName: string;
+ name: string;
+ publishedAt: string;
+ htmlUrl: string;
+ body: string;
+}
+
+// Helper functions for localStorage
+const getLastSeenVersion = (): string | null => {
+ if (typeof window === 'undefined') return null;
+ return localStorage.getItem('LAST_SEEN_RELEASE_VERSION');
+};
+
+const markVersionAsSeen = (version: string): void => {
+ if (typeof window === 'undefined') return;
+ localStorage.setItem('LAST_SEEN_RELEASE_VERSION', version);
+};
+
+export function ReleaseNotesModal({ isOpen, onClose, highlightVersion }: ReleaseNotesModalProps) {
+ const [currentVersion, setCurrentVersion] = useState(null);
+ const { data: releasesData, isLoading, error } = api.version.getAllReleases.useQuery(undefined, {
+ enabled: isOpen
+ });
+ const { data: versionData } = api.version.getCurrentVersion.useQuery(undefined, {
+ enabled: isOpen
+ });
+
+ // Get current version when modal opens
+ useEffect(() => {
+ if (isOpen && versionData?.success && versionData.version) {
+ setCurrentVersion(versionData.version);
+ }
+ }, [isOpen, versionData]);
+
+ // Mark version as seen when modal closes
+ const handleClose = () => {
+ if (currentVersion) {
+ markVersionAsSeen(currentVersion);
+ }
+ onClose();
+ };
+
+ if (!isOpen) return null;
+
+ const releases: Release[] = releasesData?.success ? releasesData.releases ?? [] : [];
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
Release Notes
+
+
+
+
+
+
+ {/* Content */}
+
+ {isLoading ? (
+
+
+
+ Loading release notes...
+
+
+ ) : error || !releasesData?.success ? (
+
+
+
Failed to load release notes
+
+ {releasesData?.error ?? 'Please try again later'}
+
+
+
+ ) : releases.length === 0 ? (
+
+ ) : (
+
+ {releases.map((release, index) => {
+ const isHighlighted = highlightVersion && release.tagName.replace('v', '') === highlightVersion;
+ const isLatest = index === 0;
+
+ return (
+
+ {/* Release Header */}
+
+
+
+
+ {release.name || release.tagName}
+
+ {isLatest && (
+
+ Latest
+
+ )}
+ {isHighlighted && (
+
+ New
+
+ )}
+
+
+
+
+ {release.tagName}
+
+
+
+
+ {new Date(release.publishedAt).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ })}
+
+
+
+
+
+
+
+
+
+
+
+ {/* Release Body */}
+ {release.body && (
+
+ )}
+
+ );
+ })}
+
+ )}
+
+
+ {/* Footer */}
+
+
+ {currentVersion && (
+ Current version: v{currentVersion}
+ )}
+
+
+ Close
+
+
+
+
+ );
+}
+
+// Export helper functions for use in other components
+export { getLastSeenVersion, markVersionAsSeen };
diff --git a/src/app/_components/ResyncButton.tsx b/src/app/_components/ResyncButton.tsx
index 88c0fbb..8129be1 100644
--- a/src/app/_components/ResyncButton.tsx
+++ b/src/app/_components/ResyncButton.tsx
@@ -3,6 +3,7 @@
import { useState } from 'react';
import { api } from '~/trpc/react';
import { Button } from './ui/button';
+import { ContextualHelpIcon } from './ContextualHelpIcon';
export function ResyncButton() {
const [isResyncing, setIsResyncing] = useState(false);
@@ -44,27 +45,30 @@ export function ResyncButton() {
Sync scripts with ProxmoxVE repo
-
- {isResyncing ? (
- <>
-
- Syncing...
- >
- ) : (
- <>
-
-
-
- Sync Json Files
- >
- )}
-
+
+
+ {isResyncing ? (
+ <>
+
+ Syncing...
+ >
+ ) : (
+ <>
+
+
+
+ Sync Json Files
+ >
+ )}
+
+
+
{lastSync && (
diff --git a/src/app/_components/SettingsModal.tsx b/src/app/_components/SettingsModal.tsx
index 4afd746..5457e9f 100644
--- a/src/app/_components/SettingsModal.tsx
+++ b/src/app/_components/SettingsModal.tsx
@@ -5,6 +5,7 @@ import type { Server, CreateServerData } from '../../types/server';
import { ServerForm } from './ServerForm';
import { ServerList } from './ServerList';
import { Button } from './ui/button';
+import { ContextualHelpIcon } from './ContextualHelpIcon';
interface SettingsModalProps {
isOpen: boolean;
@@ -106,7 +107,10 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
{/* Header */}
-
Settings
+
+
Settings
+
+
void;
+}
+
// Loading overlay component with log streaming
function LoadingOverlay({
isNetworkError = false,
@@ -72,7 +77,7 @@ function LoadingOverlay({
);
}
-export function VersionDisplay() {
+export function VersionDisplay({ onOpenReleaseNotes }: VersionDisplayProps = {}) {
const { data: versionStatus, isLoading, error } = api.version.getVersionStatus.useQuery();
const [isUpdating, setIsUpdating] = useState(false);
const [updateResult, setUpdateResult] = useState<{ success: boolean; message: string } | null>(null);
@@ -230,31 +235,16 @@ export function VersionDisplay() {
{isUpdating && }
-
+
v{currentVersion}
{updateAvailable && releaseInfo && (
-
-
- Update Available
-
-
-
-
How to update:
-
Click the button to update, when installed via the helper script
-
or update manually:
-
cd $PVESCRIPTLOCAL_DIR
-
git pull
-
npm install
-
npm run build
-
npm start
-
-
-
-
-
+
+
+
+
@@ -93,6 +124,7 @@ export default function Home() {
+
@@ -100,54 +132,63 @@ export default function Home() {
- setActiveTab('scripts')}
- className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
- activeTab === 'scripts'
- ? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
- : 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
- }`}>
-
- Available Scripts
- Available
-
- {scriptCounts.available}
-
-
- setActiveTab('downloaded')}
- className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
- activeTab === 'downloaded'
- ? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
- : 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
- }`}>
-
- Downloaded Scripts
- Downloaded
-
- {scriptCounts.downloaded}
-
-
- setActiveTab('installed')}
- className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
- activeTab === 'installed'
- ? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
- : 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
- }`}>
-
- Installed Scripts
- Installed
-
- {scriptCounts.installed}
-
-
+
+
setActiveTab('scripts')}
+ className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
+ activeTab === 'scripts'
+ ? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
+ : 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
+ }`}>
+
+ Available Scripts
+ Available
+
+ {scriptCounts.available}
+
+
+
+
+
+ setActiveTab('downloaded')}
+ className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
+ activeTab === 'downloaded'
+ ? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
+ : 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
+ }`}>
+
+ Downloaded Scripts
+ Downloaded
+
+ {scriptCounts.downloaded}
+
+
+
+
+
+ setActiveTab('installed')}
+ className={`px-3 py-2 text-sm flex items-center justify-center sm:justify-start gap-2 w-full sm:w-auto ${
+ activeTab === 'installed'
+ ? 'bg-accent text-accent-foreground rounded-t-md rounded-b-none'
+ : 'hover:bg-accent hover:text-accent-foreground hover:rounded-t-md hover:rounded-b-none'
+ }`}>
+
+ Installed Scripts
+ Installed
+
+ {scriptCounts.installed}
+
+
+
+
@@ -179,6 +220,16 @@ export default function Home() {
)}
+
+ {/* Footer */}
+
+
+ {/* Release Notes Modal */}
+
);
}
diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts
index 3455d60..de60b05 100644
--- a/src/server/api/routers/version.ts
+++ b/src/server/api/routers/version.ts
@@ -11,6 +11,7 @@ interface GitHubRelease {
name: string;
published_at: string;
html_url: string;
+ body: string;
}
// Helper function to fetch from GitHub API with optional authentication
@@ -127,6 +128,43 @@ export const versionRouter = createTRPCRouter({
}
}),
+ // Get all releases for release notes
+ getAllReleases: publicProcedure
+ .query(async () => {
+ try {
+ const response = await fetchGitHubAPI('https://api.github.com/repos/community-scripts/ProxmoxVE-Local/releases');
+
+ if (!response.ok) {
+ throw new Error(`GitHub API error: ${response.status}`);
+ }
+
+ const releases: GitHubRelease[] = await response.json();
+
+ // Sort by published date (newest first)
+ const sortedReleases = releases
+ .filter(release => !release.tag_name.includes('beta') && !release.tag_name.includes('alpha'))
+ .sort((a, b) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime());
+
+ return {
+ success: true,
+ releases: sortedReleases.map(release => ({
+ tagName: release.tag_name,
+ name: release.name,
+ publishedAt: release.published_at,
+ htmlUrl: release.html_url,
+ body: release.body
+ }))
+ };
+ } catch (error) {
+ console.error('Error fetching all releases:', error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : 'Failed to fetch releases',
+ releases: []
+ };
+ }
+ }),
+
// Get update logs from the log file
getUpdateLogs: publicProcedure
.query(async () => {