Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
680 changes: 679 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-slot": "^1.2.3",
"@t3-oss/env-nextjs": "^0.13.8",
"@tanstack/react-query": "^5.90.3",
Expand Down
73 changes: 73 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,55 @@ class ScriptExecutionHandler {
return null;
}

/**
* Parse Web UI URL from terminal output
* @param {string} output - Terminal output to parse
* @returns {{ip: string, port: number}|null} - Object with ip and port if found, null otherwise
*/
parseWebUIUrl(output) {
// First, strip ANSI color codes to make pattern matching more reliable
const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');

// Look for URL patterns with any valid IP address (private or public)
const patterns = [
// HTTP/HTTPS URLs with IP and port
/https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)/gi,
// URLs without explicit port (assume default ports)
/https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\/|$|\s)/gi,
// URLs with trailing slash and port
/https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)\//gi,
// URLs with just IP and port (no protocol)
/(?:^|\s)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)(?:\s|$)/gi,
// URLs with just IP (no protocol, no port)
/(?:^|\s)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\s|$)/gi,
];

// Try patterns on both original and cleaned output
const outputsToTry = [output, cleanOutput];

for (const testOutput of outputsToTry) {
for (const pattern of patterns) {
const matches = [...testOutput.matchAll(pattern)];
for (const match of matches) {
if (match[1]) {
const ip = match[1];
const port = match[2] || (match[0].startsWith('https') ? '443' : '80');

// Validate IP address format
if (ip.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
return {
ip: ip,
port: parseInt(port, 10)
};
}
}
}
}
}

return null;
}

/**
* Create installation record
* @param {string} scriptName - Name of the script
Expand Down Expand Up @@ -364,6 +413,18 @@ class ScriptExecutionHandler {
this.updateInstallationRecord(installationId, { container_id: containerId });
}

// Parse for Web UI URL
const webUIUrl = this.parseWebUIUrl(output);
if (webUIUrl && installationId) {
const { ip, port } = webUIUrl;
if (ip && port) {
this.updateInstallationRecord(installationId, {
web_ui_ip: ip,
web_ui_port: port
});
}
}

this.sendMessage(ws, {
type: 'output',
data: output,
Expand Down Expand Up @@ -447,6 +508,18 @@ class ScriptExecutionHandler {
this.updateInstallationRecord(installationId, { container_id: containerId });
}

// Parse for Web UI URL
const webUIUrl = this.parseWebUIUrl(data);
if (webUIUrl && installationId) {
const { ip, port } = webUIUrl;
if (ip && port) {
this.updateInstallationRecord(installationId, {
web_ui_ip: ip,
web_ui_port: port
});
}
}

// Handle data output
this.sendMessage(ws, {
type: 'output',
Expand Down
9 changes: 3 additions & 6 deletions src/app/_components/ContextualHelpIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { useState } from 'react';
import { HelpModal } from './HelpModal';
import { Button } from './ui/button';
import { HelpCircle } from 'lucide-react';

interface ContextualHelpIconProps {
Expand All @@ -26,15 +25,13 @@ export function ContextualHelpIcon({

return (
<>
<Button
<div
onClick={() => setIsOpen(true)}
variant="ghost"
size="icon"
className={`${sizeClasses} text-muted-foreground hover:text-foreground hover:bg-muted ${className}`}
className={`${sizeClasses} text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer inline-flex items-center justify-center rounded-md transition-colors ${className}`}
title={tooltip}
>
<HelpCircle className="w-4 h-4" />
</Button>
</div>

<HelpModal
isOpen={isOpen}
Expand Down
42 changes: 41 additions & 1 deletion src/app/_components/HelpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings'
<li>β€’ <strong>Installation Status:</strong> Track success, failure, or in-progress installations</li>
<li>β€’ <strong>Server Association:</strong> Know which server each script is installed on</li>
<li>β€’ <strong>Container ID:</strong> Link scripts to specific LXC containers</li>
<li>β€’ <strong>Web UI Access:</strong> Track and access Web UI IP addresses and ports</li>
<li>β€’ <strong>Execution Logs:</strong> View output and logs from script installations</li>
<li>β€’ <strong>Filtering:</strong> Filter by server, status, or search terms</li>
</ul>
Expand All @@ -335,8 +336,47 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings'
</ul>
</div>

<div className="p-4 border border-border rounded-lg bg-blue-900/20 border-blue-700/50">
<h4 className="font-medium text-foreground mb-2">Web UI Access </h4>
<p className="text-sm text-muted-foreground mb-3">
Automatically detect and access Web UI interfaces for your installed scripts.
</p>
<ul className="text-sm text-muted-foreground space-y-2">
<li>β€’ <strong>Auto-Detection:</strong> Automatically detects Web UI URLs from script installation output</li>
<li>β€’ <strong>IP & Port Tracking:</strong> Stores and displays Web UI IP addresses and ports</li>
<li>β€’ <strong>One-Click Access:</strong> Click IP:port to open Web UI in new tab</li>
<li>β€’ <strong>Manual Detection:</strong> Re-detect IP using <code>hostname -I</code> inside container</li>
<li>β€’ <strong>Port Detection:</strong> Uses script metadata to get correct port (e.g., actualbudget:5006)</li>
<li>β€’ <strong>Editable Fields:</strong> Manually edit IP and port values as needed</li>
</ul>
<div className="mt-3 p-3 bg-blue-900/30 rounded-lg border border-blue-700/30">
<p className="text-sm font-medium text-blue-300">πŸ’‘ How it works:</p>
<ul className="text-sm text-muted-foreground mt-1 space-y-1">
<li>β€’ Scripts automatically detect URLs like <code>http://10.10.10.1:3000</code> during installation</li>
<li>β€’ Re-detect button runs <code>hostname -I</code> inside the container via SSH</li>
<li>β€’ Port defaults to 80, but uses script metadata when available</li>
<li>β€’ Web UI buttons are disabled when container is stopped</li>
</ul>
</div>
</div>

<div className="p-4 border border-border rounded-lg bg-accent/50 dark:bg-accent/20">
<h4 className="font-medium text-foreground mb-2">Actions Dropdown </h4>
<p className="text-sm text-muted-foreground mb-3">
Clean interface with all actions organized in a dropdown menu.
</p>
<ul className="text-sm text-muted-foreground space-y-2">
<li>β€’ <strong>Edit Button:</strong> Always visible for quick script editing</li>
<li>β€’ <strong>Actions Dropdown:</strong> Contains Update, Shell, Open UI, Start/Stop, Destroy, Delete</li>
<li>β€’ <strong>Smart Visibility:</strong> Dropdown only appears when actions are available</li>
<li>β€’ <strong>Color Coding:</strong> Start (green), Stop (red), Update (cyan), Shell (gray), Open UI (blue)</li>
<li>β€’ <strong>Auto-Close:</strong> Dropdown closes after clicking any action</li>
<li>β€’ <strong>Disabled States:</strong> Actions are disabled when container is stopped</li>
</ul>
</div>

<div className="p-4 border border-border rounded-lg bg-accent/50 dark:bg-accent/20">
<h4 className="font-medium text-foreground mb-2">Container Control (NEW)</h4>
<h4 className="font-medium text-foreground mb-2">Container Control</h4>
<p className="text-sm text-muted-foreground mb-3">
Directly control LXC containers from the installed scripts page via SSH.
</p>
Expand Down
Loading