Compare commits

..

6 Commits

Author SHA1 Message Date
Michel Roegl-Brunner
0ab76b883a Merge branch 'main' into feat/open_script_ui 2025-10-14 15:33:35 +02:00
Michel Roegl-Brunner
3e8ba6a9bf Fix TypeScript build error in server.js
- Updated parseWebUIUrl JSDoc return type from Object|null to {ip: string, port: number}|null
- This fixes the TypeScript error where 'ip' property was not recognized on type 'Object'
- Build now completes successfully without errors
2025-10-14 15:30:31 +02:00
Michel Roegl-Brunner
64a3142dc8 feat: Add actions dropdown menu with conditional Start/Stop colors and update help
- Create dropdown-menu.tsx component using Radix UI primitives
- Move all action buttons except Edit into dropdown menu
- Keep Edit and Save/Cancel buttons always visible
- Add conditional styling: Start (green), Stop (red)
- Apply changes to both desktop table and mobile card views
- Add smart visibility - dropdown only shows when actions available
- Auto-close dropdown after clicking any action
- Style dropdown to match existing button theme
- Fix syntax error in dropdown-menu.tsx component
- Update help section with Web UI Access and Actions Dropdown documentation
- Add detailed explanations of auto-detection, IP/port tracking, and color coding
2025-10-14 15:22:23 +02:00
Michel Roegl-Brunner
cfc664b9a6 feat: Align Re-detect buttons consistently in Web UI column
- Change flex layout from space-x-2 to justify-between for consistent button alignment
- Add flex-shrink-0 to prevent IP:port text and buttons from shrinking
- Add ml-2 margin to Re-detect button for proper spacing
- Apply changes to both desktop table and mobile card views
- Buttons now align vertically regardless of IP:port text length
2025-10-14 15:09:00 +02:00
Michel Roegl-Brunner
77f3bcb5d2 feat: Disable Open UI button when container is stopped
- Add disabled state to Open UI button in desktop table when container is stopped
- Update mobile card Open UI button to be disabled when container is stopped
- Apply consistent styling with Shell and Update buttons
- Prevent users from accessing Web UI when container is not running
- Add cursor-not-allowed styling for disabled clickable IP links
2025-10-14 15:07:58 +02:00
Michel Roegl-Brunner
e3b9cee5bc feat: Add Web UI IP:Port tracking and access functionality
- Add web_ui_ip and web_ui_port columns to installed_scripts table with migration
- Update database CRUD methods to handle new Web UI fields
- Add terminal output parsing to auto-detect Web UI URLs during installation
- Create autoDetectWebUI mutation that runs hostname -I in containers via SSH
- Add Web UI column to desktop table with editable IP and port fields
- Add Open UI button that opens http://ip:port in new tab
- Add Re-detect button for manual IP detection using script metadata
- Update mobile card view with Web UI fields and buttons
- Fix nested button hydration error in ContextualHelpIcon
- Prioritize script metadata interface_port over existing database values
- Use pct exec instead of pct enter for container command execution
- Add comprehensive error handling and user feedback
- Style auto-detect button with muted colors and Re-detect text

Features:
- Automatic Web UI detection during script installation
- Manual IP detection with port lookup from script metadata
- Editable IP and port fields in both desktop and mobile views
- Clickable Web UI links that open in new tabs
- Support for both local and SSH script executions
- Proper port detection from script JSON metadata (e.g., actualbudget:5006)
- Clean UI with subtle button styling and clear text labels
2025-10-14 15:01:32 +02:00
11 changed files with 153 additions and 1746 deletions

243
README.md
View File

@@ -210,249 +210,6 @@ The application uses SQLite for storing server configurations:
- **Backup**: Copy `data/settings.db` to backup your server configurations
- **Reset**: Delete `data/settings.db` to reset all server configurations
## 📖 Feature Guide
This section provides detailed information about the application's key features and how to use them effectively.
### 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.
### 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
### 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
### 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
### 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
### 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
- **Web UI Access**: Track and access Web UI IP addresses and ports
- **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
**Web UI Access:**
Automatically detect and access Web UI interfaces for your installed scripts:
- **Auto-Detection**: Automatically detects Web UI URLs from script installation output
- **IP & Port Tracking**: Stores and displays Web UI IP addresses and ports
- **One-Click Access**: Click IP:port to open Web UI in new tab
- **Manual Detection**: Re-detect IP using `hostname -I` inside container
- **Port Detection**: Uses script metadata to get correct port (e.g., actualbudget:5006)
- **Editable Fields**: Manually edit IP and port values as needed
**Actions Dropdown:**
Clean interface with all actions organized in a dropdown menu:
- **Edit Button**: Always visible for quick script editing
- **Actions Dropdown**: Contains Update, Shell, Open UI, Start/Stop, Destroy, Delete
- **Smart Visibility**: Dropdown only appears when actions are available
- **Auto-Close**: Dropdown closes after clicking any action
- **Disabled States**: Actions are disabled when container is stopped
**Container Control:**
Directly control LXC containers from the installed scripts page via SSH:
- **Start/Stop Button**: Control container state with `pct start/stop <ID>`
- **Container Status**: Real-time status indicator (running/stopped/unknown)
- **Destroy Button**: Permanently remove LXC container with `pct destroy <ID>`
- **Confirmation Modals**: Simple OK/Cancel for start/stop, type container ID to confirm destroy
- **SSH Execution**: All commands executed remotely via configured SSH connections
**Safety Features:**
- Start/Stop actions require simple confirmation
- Destroy action requires typing the container ID to confirm
- All actions show loading states and error handling
- Only works with SSH scripts that have valid container IDs
### 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:
```bash
# 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
## 📁 Project Structure
```

View File

@@ -1 +1 @@
0.4.1
0.4.0

1374
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,6 @@
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-slot": "^1.2.3",
"@t3-oss/env-nextjs": "^0.13.8",
"@tailwindcss/typography": "^0.5.19",
"@tanstack/react-query": "^5.90.3",
"@trpc/client": "^11.6.0",
"@trpc/react-query": "^11.6.0",
@@ -45,10 +44,8 @@
"node-pty": "^1.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.6",
"refractor": "^5.0.0",
"remark-gfm": "^4.0.1",
"server-only": "^0.0.1",
"strip-ansi": "^7.1.2",
"superjson": "^2.2.1",
@@ -72,11 +69,11 @@
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
"eslint": "^9.23.0",
"eslint-config-next": "^15.5.5",
"eslint-config-next": "^15.5.4",
"jsdom": "^27.0.0",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.7.0",
"prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^4.1.14",
"typescript": "^5.8.2",
"typescript-eslint": "^8.46.1",

View File

@@ -93,6 +93,17 @@ export function FilterBar({
</div>
)}
{/* Filter Persistence Status */}
{!isLoadingFilters && saveFiltersEnabled && (
<div className="mb-4 flex items-center justify-center py-1">
<div className="flex items-center space-x-2 text-xs text-green-600">
<svg className="h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span>Filters are being saved automatically</span>
</div>
</div>
)}
{/* Filter Header */}
{!isLoadingFilters && (
@@ -380,30 +391,18 @@ export function FilterBar({
{/* Filter Summary and Clear All */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
<div className="flex items-center gap-4">
<div className="text-sm text-muted-foreground">
{filteredCount === totalScripts ? (
<span>Showing all {totalScripts} scripts</span>
) : (
<span>
{filteredCount} of {totalScripts} scripts{" "}
{hasActiveFilters && (
<span className="font-medium text-blue-600">
(filtered)
</span>
)}
</span>
)}
</div>
{/* Filter Persistence Status */}
{!isLoadingFilters && saveFiltersEnabled && (
<div className="flex items-center space-x-1 text-xs text-green-600">
<svg className="h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span>Filters are being saved automatically</span>
</div>
<div className="text-sm text-muted-foreground">
{filteredCount === totalScripts ? (
<span>Showing all {totalScripts} scripts</span>
) : (
<span>
{filteredCount} of {totalScripts} scripts{" "}
{hasActiveFilters && (
<span className="font-medium text-blue-600">
(filtered)
</span>
)}
</span>
)}
</div>

View File

@@ -377,7 +377,7 @@ export function InstalledScriptsTab() {
containerStatusMutation.mutate({ serverIds });
}
}, 500);
}, []);
}, [containerStatusMutation]);
// Run cleanup when component mounts and scripts are loaded (only once)
useEffect(() => {
@@ -1365,17 +1365,21 @@ export function InstalledScriptsTab() {
</div>
) : (
script.web_ui_ip ? (
<div className="flex items-center space-x-3">
<span className="text-sm text-foreground">
<div className="flex items-center justify-between w-full">
<button
onClick={() => handleOpenWebUI(script)}
className="text-sm font-mono text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 hover:underline flex-shrink-0"
>
{script.web_ui_ip}:{script.web_ui_port ?? 80}
</span>
{containerStatuses.get(script.id) === 'running' && (
</button>
{script.container_id && script.execution_mode === 'ssh' && (
<button
onClick={() => handleOpenWebUI(script)}
className="text-xs px-2 py-1 bg-blue-900/20 hover:bg-blue-900/30 border border-blue-700/50 text-blue-300 hover:text-blue-200 hover:border-blue-600/60 transition-all duration-200 hover:scale-105 hover:shadow-md rounded disabled:opacity-50 flex-shrink-0"
title="Open Web UI"
onClick={() => handleAutoDetectWebUI(script)}
disabled={autoDetectWebUIMutation.isPending}
className="text-xs px-2 py-1 bg-blue-900 hover:bg-blue-800 text-blue-300 border border-blue-700 rounded disabled:opacity-50 transition-colors flex-shrink-0 ml-2"
title="Re-detect IP and port"
>
Open UI
{autoDetectWebUIMutation.isPending ? '...' : 'Re-detect'}
</button>
)}
</div>
@@ -1483,15 +1487,6 @@ export function InstalledScriptsTab() {
Open UI
</DropdownMenuItem>
)}
{script.container_id && script.execution_mode === 'ssh' && script.web_ui_ip && (
<DropdownMenuItem
onClick={() => handleAutoDetectWebUI(script)}
disabled={autoDetectWebUIMutation.isPending || containerStatuses.get(script.id) === 'stopped'}
className="text-blue-300 hover:text-blue-200 hover:bg-blue-900/20 focus:bg-blue-900/20"
>
{autoDetectWebUIMutation.isPending ? 'Re-detect...' : 'Re-detect IP/Port'}
</DropdownMenuItem>
)}
{script.container_id && script.execution_mode === 'ssh' && (
<>
<DropdownMenuSeparator className="bg-gray-700" />

View File

@@ -5,8 +5,6 @@ import { api } from '~/trpc/react';
import { Button } from './ui/button';
import { Badge } from './ui/badge';
import { X, ExternalLink, Calendar, Tag, Loader2 } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
interface ReleaseNotesModalProps {
isOpen: boolean;
@@ -172,23 +170,9 @@ export function ReleaseNotesModal({ isOpen, onClose, highlightVersion }: Release
{/* Release Body */}
{release.body && (
<div className="prose prose-sm max-w-none dark:prose-invert">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({children}) => <h1 className="text-2xl font-bold text-card-foreground mb-4 mt-6">{children}</h1>,
h2: ({children}) => <h2 className="text-xl font-semibold text-card-foreground mb-3 mt-5">{children}</h2>,
h3: ({children}) => <h3 className="text-lg font-medium text-card-foreground mb-2 mt-4">{children}</h3>,
p: ({children}) => <p className="text-card-foreground mb-3 leading-relaxed">{children}</p>,
ul: ({children}) => <ul className="list-disc list-inside text-card-foreground mb-3 space-y-1">{children}</ul>,
ol: ({children}) => <ol className="list-decimal list-inside text-card-foreground mb-3 space-y-1">{children}</ol>,
li: ({children}) => <li className="text-card-foreground">{children}</li>,
a: ({href, children}) => <a href={href} className="text-blue-500 hover:text-blue-400 underline" target="_blank" rel="noopener noreferrer">{children}</a>,
strong: ({children}) => <strong className="font-semibold text-card-foreground">{children}</strong>,
em: ({children}) => <em className="italic text-card-foreground">{children}</em>,
}}
>
<div className="whitespace-pre-wrap text-sm text-card-foreground leading-relaxed">
{release.body}
</ReactMarkdown>
</div>
</div>
)}
</div>

View File

@@ -359,91 +359,91 @@ export function ScriptDetailModal({
})()}
</div>
{/* Load Message */}
{loadMessage && (
<div className="mx-4 sm:mx-6 mb-4 rounded-lg bg-primary/10 p-3 text-sm text-primary">
{loadMessage}
</div>
)}
{/* Script Files Status */}
{(scriptFilesLoading || comparisonLoading) && (
<div className="mx-4 sm:mx-6 mb-4 rounded-lg bg-primary/10 p-3 text-sm text-primary">
<div className="flex items-center space-x-2">
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-blue-600"></div>
<span>Loading script status...</span>
</div>
</div>
)}
{scriptFilesData?.success &&
!scriptFilesLoading &&
(() => {
// Determine script type from the first install method
const firstScript = script?.install_methods?.[0]?.script;
let scriptType = "Script";
if (firstScript?.startsWith("ct/")) {
scriptType = "CT Script";
} else if (firstScript?.startsWith("tools/")) {
scriptType = "Tools Script";
} else if (firstScript?.startsWith("vm/")) {
scriptType = "VM Script";
} else if (firstScript?.startsWith("vw/")) {
scriptType = "VW Script";
}
return (
<div className="mx-4 sm:mx-6 mb-4 rounded-lg bg-muted p-3 text-sm text-muted-foreground">
<div className="flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0 sm:space-x-4">
<div className="flex items-center space-x-2">
<div
className={`h-2 w-2 rounded-full ${scriptFilesData.ctExists ? "bg-green-500" : "bg-muted"}`}
></div>
<span>
{scriptType}:{" "}
{scriptFilesData.ctExists ? "Available" : "Not loaded"}
</span>
</div>
<div className="flex items-center space-x-2">
<div
className={`h-2 w-2 rounded-full ${scriptFilesData.installExists ? "bg-green-500" : "bg-muted"}`}
></div>
<span>
Install Script:{" "}
{scriptFilesData.installExists
? "Available"
: "Not loaded"}
</span>
</div>
{scriptFilesData?.success &&
(scriptFilesData.ctExists ||
scriptFilesData.installExists) &&
comparisonData?.success &&
!comparisonLoading && (
<div className="flex items-center space-x-2">
<div
className={`h-2 w-2 rounded-full ${comparisonData.hasDifferences ? "bg-orange-500" : "bg-green-500"}`}
></div>
<span>
Status:{" "}
{comparisonData.hasDifferences
? "Update available"
: "Up to date"}
</span>
</div>
)}
</div>
{scriptFilesData.files.length > 0 && (
<div className="mt-2 text-xs text-muted-foreground break-words">
Files: {scriptFilesData.files.join(", ")}
</div>
)}
</div>
);
})()}
{/* Content */}
<div className="space-y-4 sm:space-y-6 p-4 sm:p-6">
{/* Script Files Status */}
{(scriptFilesLoading || comparisonLoading) && (
<div className="mb-4 rounded-lg bg-primary/10 p-3 text-sm text-primary">
<div className="flex items-center space-x-2">
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-blue-600"></div>
<span>Loading script status...</span>
</div>
</div>
)}
{scriptFilesData?.success &&
!scriptFilesLoading &&
(() => {
// Determine script type from the first install method
const firstScript = script?.install_methods?.[0]?.script;
let scriptType = "Script";
if (firstScript?.startsWith("ct/")) {
scriptType = "CT Script";
} else if (firstScript?.startsWith("tools/")) {
scriptType = "Tools Script";
} else if (firstScript?.startsWith("vm/")) {
scriptType = "VM Script";
} else if (firstScript?.startsWith("vw/")) {
scriptType = "VW Script";
}
return (
<div className="mb-4 rounded-lg bg-muted p-3 text-sm text-muted-foreground">
<div className="flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0 sm:space-x-4">
<div className="flex items-center space-x-2">
<div
className={`h-2 w-2 rounded-full ${scriptFilesData.ctExists ? "bg-green-500" : "bg-muted"}`}
></div>
<span>
{scriptType}:{" "}
{scriptFilesData.ctExists ? "Available" : "Not loaded"}
</span>
</div>
<div className="flex items-center space-x-2">
<div
className={`h-2 w-2 rounded-full ${scriptFilesData.installExists ? "bg-green-500" : "bg-muted"}`}
></div>
<span>
Install Script:{" "}
{scriptFilesData.installExists
? "Available"
: "Not loaded"}
</span>
</div>
{scriptFilesData?.success &&
(scriptFilesData.ctExists ||
scriptFilesData.installExists) &&
comparisonData?.success &&
!comparisonLoading && (
<div className="flex items-center space-x-2">
<div
className={`h-2 w-2 rounded-full ${comparisonData.hasDifferences ? "bg-orange-500" : "bg-green-500"}`}
></div>
<span>
Status:{" "}
{comparisonData.hasDifferences
? "Update available"
: "Up to date"}
</span>
</div>
)}
</div>
{scriptFilesData.files.length > 0 && (
<div className="mt-2 text-xs text-muted-foreground break-words">
Files: {scriptFilesData.files.join(", ")}
</div>
)}
</div>
);
})()}
{/* Load Message */}
{loadMessage && (
<div className="mb-4 rounded-lg bg-primary/10 p-3 text-sm text-primary">
{loadMessage}
</div>
)}
{/* Description */}
<div>
<h3 className="mb-2 text-base sm:text-lg font-semibold text-foreground">

View File

@@ -147,7 +147,7 @@ export default function Home() {
{/* Header */}
<div className="text-center mb-6 sm:mb-8">
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold text-foreground mb-2 flex items-center justify-center gap-2 sm:gap-3">
<Rocket className="h-6 w-6 sm:h-8 w-8 lg:h-9 lg:w-9" />
<span className="break-words">PVE Scripts Management</span>
</h1>
<p className="text-sm sm:text-base text-muted-foreground mb-4 px-2">

View File

@@ -1137,11 +1137,9 @@ export const installedScriptsRouter = createTRPCRouter({
console.log('✅ Successfully updated database');
return {
success: true,
message: `Successfully detected IP: ${detectedIp}:${detectedPort} for LXC ${scriptData.container_id} on ${(server as any).name}`,
message: `Successfully detected IP: ${detectedIp}:${detectedPort}`,
detectedIp,
detectedPort: detectedPort,
containerId: scriptData.container_id,
serverName: (server as any).name
detectedPort: detectedPort
};
} catch (error) {
console.error('Error in autoDetectWebUI:', error);

View File

@@ -1,5 +1,4 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@layer base {
:root {