diff --git a/prisma/migrations/20251017092130_init/migration.sql b/prisma/migrations/20251017092130_init/migration.sql new file mode 100644 index 0000000..2a9f5fb --- /dev/null +++ b/prisma/migrations/20251017092130_init/migration.sql @@ -0,0 +1,74 @@ +-- CreateTable +CREATE TABLE "installed_scripts" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "script_name" TEXT NOT NULL, + "script_path" TEXT NOT NULL, + "container_id" TEXT, + "server_id" INTEGER, + "execution_mode" TEXT NOT NULL, + "installation_date" DATETIME DEFAULT CURRENT_TIMESTAMP, + "status" TEXT NOT NULL, + "output_log" TEXT, + "web_ui_ip" TEXT, + "web_ui_port" INTEGER, + CONSTRAINT "installed_scripts_server_id_fkey" FOREIGN KEY ("server_id") REFERENCES "servers" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "servers" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "ip" TEXT NOT NULL, + "user" TEXT NOT NULL, + "password" TEXT, + "auth_type" TEXT DEFAULT 'password', + "ssh_key" TEXT, + "ssh_key_passphrase" TEXT, + "ssh_port" INTEGER DEFAULT 22, + "color" TEXT, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME, + "ssh_key_path" TEXT, + "key_generated" BOOLEAN DEFAULT false +); + +-- CreateTable +CREATE TABLE "lxc_configs" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "installed_script_id" INTEGER NOT NULL, + "arch" TEXT, + "cores" INTEGER, + "memory" INTEGER, + "hostname" TEXT, + "swap" INTEGER, + "onboot" INTEGER, + "ostype" TEXT, + "unprivileged" INTEGER, + "net_name" TEXT, + "net_bridge" TEXT, + "net_hwaddr" TEXT, + "net_ip_type" TEXT, + "net_ip" TEXT, + "net_gateway" TEXT, + "net_type" TEXT, + "net_vlan" INTEGER, + "rootfs_storage" TEXT, + "rootfs_size" TEXT, + "feature_keyctl" INTEGER, + "feature_nesting" INTEGER, + "feature_fuse" INTEGER, + "feature_mount" TEXT, + "tags" TEXT, + "advanced_config" TEXT, + "synced_at" DATETIME, + "config_hash" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "lxc_configs_installed_script_id_fkey" FOREIGN KEY ("installed_script_id") REFERENCES "installed_scripts" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "servers_name_key" ON "servers"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "lxc_configs_installed_script_id_key" ON "lxc_configs"("installed_script_id"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..2a5a444 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "sqlite" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 341d63a..065ec72 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,6 +20,7 @@ model InstalledScript { web_ui_ip String? web_ui_port Int? server Server? @relation(fields: [server_id], references: [id], onDelete: SetNull) + lxc_config LXCConfig? @@map("installed_scripts") } @@ -43,3 +44,54 @@ model Server { @@map("servers") } + +model LXCConfig { + id Int @id @default(autoincrement()) + installed_script_id Int @unique + installed_script InstalledScript @relation(fields: [installed_script_id], references: [id], onDelete: Cascade) + + // Basic settings + arch String? + cores Int? + memory Int? + hostname String? + swap Int? + onboot Int? // 0 or 1 + ostype String? + unprivileged Int? // 0 or 1 + + // Network settings (net0) + net_name String? + net_bridge String? + net_hwaddr String? + net_ip_type String? // 'dhcp' or 'static' + net_ip String? // IP with CIDR for static + net_gateway String? + net_type String? // usually 'veth' + net_vlan Int? + + // Storage + rootfs_storage String? + rootfs_size String? + + // Features + feature_keyctl Int? // 0 or 1 + feature_nesting Int? // 0 or 1 + feature_fuse Int? // 0 or 1 + feature_mount String? // other mount features + + // Tags + tags String? + + // Advanced/raw settings (lxc.* entries and other uncommon settings) + advanced_config String? // Text blob for advanced settings + + // Metadata + synced_at DateTime? + config_hash String? // Hash of server config for diff detection + + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@map("lxc_configs") +} diff --git a/src/app/_components/HelpModal.tsx b/src/app/_components/HelpModal.tsx index 2a90531..6c096e9 100644 --- a/src/app/_components/HelpModal.tsx +++ b/src/app/_components/HelpModal.tsx @@ -10,7 +10,7 @@ interface HelpModalProps { initialSection?: string; } -type HelpSection = 'server-settings' | 'general-settings' | 'sync-button' | 'available-scripts' | 'downloaded-scripts' | 'installed-scripts' | 'update-system'; +type HelpSection = 'server-settings' | 'general-settings' | 'sync-button' | 'available-scripts' | 'downloaded-scripts' | 'installed-scripts' | 'lxc-settings' | 'update-system'; export function HelpModal({ isOpen, onClose, initialSection = 'server-settings' }: HelpModalProps) { const [activeSection, setActiveSection] = useState(initialSection as HelpSection); @@ -24,6 +24,7 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings' { 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: 'lxc-settings' as HelpSection, label: 'LXC Settings', icon: Settings }, { id: 'update-system' as HelpSection, label: 'Update System', icon: Download }, ]; @@ -501,6 +502,131 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings' ); + case 'lxc-settings': + return ( +
+
+

LXC Settings

+

+ Edit LXC container configuration files directly from the installed scripts interface. This feature allows you to modify container settings without manually accessing the Proxmox VE server. +

+
+ +
+
+

Overview

+

+ The LXC Settings modal provides a user-friendly interface to edit container configuration files. It parses common settings into editable fields while preserving advanced configurations. +

+
    +
  • Common Settings: Edit basic container parameters like cores, memory, network, and storage
  • +
  • Advanced Settings: Raw text editing for lxc.* entries and other advanced configurations
  • +
  • Database Caching: Configurations are cached locally for faster access
  • +
  • Change Detection: Warns when cached config differs from server version
  • +
+
+ +
+

Common Settings Tab

+
+
+
Basic Configuration
+
    +
  • Architecture: Container architecture (usually amd64)
  • +
  • Cores: Number of CPU cores allocated to the container
  • +
  • Memory: RAM allocation in megabytes
  • +
  • Swap: Swap space allocation in megabytes
  • +
  • Hostname: Container hostname
  • +
  • OS Type: Operating system type (e.g., debian, ubuntu)
  • +
  • Start on Boot: Whether to start container automatically on host boot
  • +
  • Unprivileged: Whether the container runs in unprivileged mode
  • +
+
+ +
+
Network Configuration
+
    +
  • IP Configuration: Choose between DHCP or static IP assignment
  • +
  • IP Address: Static IP with CIDR notation (e.g., 10.10.10.164/24)
  • +
  • Gateway: Network gateway for static IP configuration
  • +
  • Bridge: Network bridge interface (usually vmbr0)
  • +
  • MAC Address: Hardware address for the network interface
  • +
  • VLAN Tag: Optional VLAN tag for network segmentation
  • +
+
+ +
+
Storage & Features
+
    +
  • Root Filesystem: Storage location and disk identifier
  • +
  • Size: Disk size allocation (e.g., 4G, 8G)
  • +
  • Features: Container capabilities (keyctl, nesting, fuse)
  • +
  • Tags: Comma-separated tags for organization
  • +
+
+
+
+ +
+

Advanced Settings Tab

+

+ The Advanced Settings tab provides raw text editing for configurations not covered in the Common Settings tab. +

+
    +
  • lxc.* entries: Low-level LXC configuration options
  • +
  • Comments: Configuration file comments and documentation
  • +
  • Custom settings: Any other configuration parameters
  • +
  • Preservation: All content is preserved when switching between tabs
  • +
+
+ +
+

Saving Changes

+
+

+ To save configuration changes, you must type the container ID exactly as shown to confirm your changes. +

+
+
⚠️ Important Warnings
+
    +
  • • Modifying LXC configuration can break your container
  • +
  • • Some changes may require container restart to take effect
  • +
  • • Always backup your configuration before making changes
  • +
  • • Test changes in a non-production environment first
  • +
+
+
+
+ +
+

Sync from Server

+

+ The "Sync from Server" button allows you to refresh the configuration from the actual server file, useful when: +

+
    +
  • • Configuration was modified outside of this interface
  • +
  • • You want to discard local changes and get the latest server version
  • +
  • • The warning banner indicates the cached config differs from server
  • +
  • • You want to ensure you're working with the most current configuration
  • +
+
+ +
+

Database Caching

+

+ LXC configurations are cached in the database for improved performance and offline access. +

+
    +
  • Automatic caching: Configs are cached during auto-detection and after saves
  • +
  • Cache expiration: Cached configs expire after 5 minutes for freshness
  • +
  • Change detection: Hash comparison detects external modifications
  • +
  • Manual sync: Always available via the "Sync from Server" button
  • +
+
+
+
+ ); + default: return null; } diff --git a/src/app/_components/InstalledScriptsTab.tsx b/src/app/_components/InstalledScriptsTab.tsx index 280044e..05a40bf 100644 --- a/src/app/_components/InstalledScriptsTab.tsx +++ b/src/app/_components/InstalledScriptsTab.tsx @@ -9,6 +9,7 @@ import { ScriptInstallationCard } from './ScriptInstallationCard'; import { ConfirmationModal } from './ConfirmationModal'; import { ErrorModal } from './ErrorModal'; import { LoadingModal } from './LoadingModal'; +import { LXCSettingsModal } from './LXCSettingsModal'; import { getContrastColor } from '../../lib/colorUtils'; import { DropdownMenu, @@ -17,6 +18,7 @@ import { DropdownMenuTrigger, DropdownMenuSeparator, } from './ui/dropdown-menu'; +import { Settings } from 'lucide-react'; interface InstalledScript { id: number; @@ -91,6 +93,12 @@ export function InstalledScriptsTab() { action: string; } | null>(null); + // LXC Settings modal state + const [lxcSettingsModal, setLxcSettingsModal] = useState<{ + isOpen: boolean; + script: InstalledScript | null; + }>({ isOpen: false, script: null }); + // Fetch installed scripts const { data: scriptsData, refetch: refetchScripts, isLoading } = api.installedScripts.getAllInstalledScripts.useQuery(); const { data: statsData } = api.installedScripts.getInstallationStats.useQuery(); @@ -388,7 +396,7 @@ export function InstalledScriptsTab() { containerStatusMutation.mutate({ serverIds }); } }, 500); - }, []); + }, []); // Run cleanup when component mounts and scripts are loaded (only once) useEffect(() => { @@ -404,7 +412,7 @@ export function InstalledScriptsTab() { console.log('Status check triggered - scripts length:', scripts.length); fetchContainerStatuses(); } - }, [scripts.length, fetchContainerStatuses]); + }, [scripts.length]); // Cleanup timeout on unmount useEffect(() => { @@ -704,6 +712,10 @@ export function InstalledScriptsTab() { setEditFormData({ script_name: '', container_id: '', web_ui_ip: '', web_ui_port: '' }); }; + const handleLXCSettings = (script: InstalledScript) => { + setLxcSettingsModal({ isOpen: true, script }); + }; + const handleSaveEdit = () => { if (!editFormData.script_name.trim()) { setErrorModal({ @@ -922,7 +934,7 @@ export function InstalledScriptsTab() { + + + + + {/* Warning Banner */} + {configData?.has_changes && ( +
+
+ +
+

+ Configuration Mismatch Detected +

+

+ The cached configuration differs from the server. Click "Sync from Server" to get the latest version. +

+
+
+
+ )} + + {/* Success Message */} + {successMessage && ( +
+
+ +
+

{successMessage}

+
+ +
+
+ )} + + {/* Error Message */} + {error && ( +
+
+ +
+

Error

+

{error}

+
+ +
+
+ )} + + {/* Content */} +
+ {/* Tab Navigation */} +
+ +
+ + {/* Common Settings Tab */} + {activeTab === 'common' && ( +
+ {/* Basic Configuration */} +
+

Basic Configuration

+
+
+ + handleInputChange('arch', e.target.value)} + placeholder="amd64" + /> +
+
+ + handleInputChange('cores', parseInt(e.target.value) || 0)} + min="1" + /> +
+
+ + handleInputChange('memory', parseInt(e.target.value) || 0)} + min="128" + /> +
+
+ + handleInputChange('swap', parseInt(e.target.value) || 0)} + min="0" + /> +
+
+ + handleInputChange('hostname', e.target.value)} + placeholder="container-hostname" + /> +
+
+ + handleInputChange('ostype', e.target.value)} + placeholder="debian" + /> +
+
+ +
+
+ handleInputChange('onboot', e.target.checked)} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + +
+
+ handleInputChange('unprivileged', e.target.checked)} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + +
+
+
+ + {/* Network Configuration */} +
+

Network Configuration

+
+
+ + handleInputChange('net_name', e.target.value)} + placeholder="eth0" + /> +
+
+ + handleInputChange('net_bridge', e.target.value)} + placeholder="vmbr0" + /> +
+
+ + handleInputChange('net_hwaddr', e.target.value)} + placeholder="BC:24:11:2D:2D:AB" + /> +
+
+ + handleInputChange('net_type', e.target.value)} + placeholder="veth" + /> +
+
+ + +
+ {formData.net_ip_type === 'static' && ( + <> +
+ + handleInputChange('net_ip', e.target.value)} + placeholder="10.10.10.164/24" + /> +
+
+ + handleInputChange('net_gateway', e.target.value)} + placeholder="10.10.10.254" + /> +
+ + )} +
+ + handleInputChange('net_vlan', parseInt(e.target.value) || 0)} + placeholder="Optional" + /> +
+
+
+ + {/* Storage */} +
+

Storage

+
+
+ + handleInputChange('rootfs_storage', e.target.value)} + placeholder="PROX2-STORAGE2:vm-109-disk-0" + /> +
+
+ + handleInputChange('rootfs_size', e.target.value)} + placeholder="4G" + /> +
+
+
+ + {/* Features */} +
+

Features

+
+
+ handleInputChange('feature_keyctl', e.target.checked)} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + +
+
+ handleInputChange('feature_nesting', e.target.checked)} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + +
+
+ handleInputChange('feature_fuse', e.target.checked)} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + +
+
+
+ + handleInputChange('feature_mount', e.target.value)} + placeholder="Additional features (comma-separated)" + /> +
+
+ + {/* Tags */} +
+

Tags

+
+ + handleInputChange('tags', e.target.value)} + placeholder="community-script;pve-scripts-local" + /> +
+
+
+ )} + + {/* Advanced Settings Tab */} + {activeTab === 'advanced' && ( +
+
+ +