Skip to content

Commit ceef5c7

Browse files
feat: Add UI Access button and rearrange the Action Buttons in a Dropdown. (#146)
* 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 * 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 * 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 * 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 * 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
1 parent 58e1fb3 commit ceef5c7

File tree

11 files changed

+1608
-139
lines changed

11 files changed

+1608
-139
lines changed

package-lock.json

Lines changed: 679 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"typecheck": "tsc --noEmit"
2323
},
2424
"dependencies": {
25+
"@radix-ui/react-dropdown-menu": "^2.1.16",
2526
"@radix-ui/react-slot": "^1.2.3",
2627
"@t3-oss/env-nextjs": "^0.13.8",
2728
"@tanstack/react-query": "^5.90.3",

server.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,55 @@ class ScriptExecutionHandler {
131131
return null;
132132
}
133133

134+
/**
135+
* Parse Web UI URL from terminal output
136+
* @param {string} output - Terminal output to parse
137+
* @returns {{ip: string, port: number}|null} - Object with ip and port if found, null otherwise
138+
*/
139+
parseWebUIUrl(output) {
140+
// First, strip ANSI color codes to make pattern matching more reliable
141+
const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
142+
143+
// Look for URL patterns with any valid IP address (private or public)
144+
const patterns = [
145+
// HTTP/HTTPS URLs with IP and port
146+
/https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)/gi,
147+
// URLs without explicit port (assume default ports)
148+
/https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\/|$|\s)/gi,
149+
// URLs with trailing slash and port
150+
/https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)\//gi,
151+
// URLs with just IP and port (no protocol)
152+
/(?:^|\s)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)(?:\s|$)/gi,
153+
// URLs with just IP (no protocol, no port)
154+
/(?:^|\s)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\s|$)/gi,
155+
];
156+
157+
// Try patterns on both original and cleaned output
158+
const outputsToTry = [output, cleanOutput];
159+
160+
for (const testOutput of outputsToTry) {
161+
for (const pattern of patterns) {
162+
const matches = [...testOutput.matchAll(pattern)];
163+
for (const match of matches) {
164+
if (match[1]) {
165+
const ip = match[1];
166+
const port = match[2] || (match[0].startsWith('https') ? '443' : '80');
167+
168+
// Validate IP address format
169+
if (ip.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
170+
return {
171+
ip: ip,
172+
port: parseInt(port, 10)
173+
};
174+
}
175+
}
176+
}
177+
}
178+
}
179+
180+
return null;
181+
}
182+
134183
/**
135184
* Create installation record
136185
* @param {string} scriptName - Name of the script
@@ -364,6 +413,18 @@ class ScriptExecutionHandler {
364413
this.updateInstallationRecord(installationId, { container_id: containerId });
365414
}
366415

416+
// Parse for Web UI URL
417+
const webUIUrl = this.parseWebUIUrl(output);
418+
if (webUIUrl && installationId) {
419+
const { ip, port } = webUIUrl;
420+
if (ip && port) {
421+
this.updateInstallationRecord(installationId, {
422+
web_ui_ip: ip,
423+
web_ui_port: port
424+
});
425+
}
426+
}
427+
367428
this.sendMessage(ws, {
368429
type: 'output',
369430
data: output,
@@ -447,6 +508,18 @@ class ScriptExecutionHandler {
447508
this.updateInstallationRecord(installationId, { container_id: containerId });
448509
}
449510

511+
// Parse for Web UI URL
512+
const webUIUrl = this.parseWebUIUrl(data);
513+
if (webUIUrl && installationId) {
514+
const { ip, port } = webUIUrl;
515+
if (ip && port) {
516+
this.updateInstallationRecord(installationId, {
517+
web_ui_ip: ip,
518+
web_ui_port: port
519+
});
520+
}
521+
}
522+
450523
// Handle data output
451524
this.sendMessage(ws, {
452525
type: 'output',

src/app/_components/ContextualHelpIcon.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import { useState } from 'react';
44
import { HelpModal } from './HelpModal';
5-
import { Button } from './ui/button';
65
import { HelpCircle } from 'lucide-react';
76

87
interface ContextualHelpIconProps {
@@ -26,15 +25,13 @@ export function ContextualHelpIcon({
2625

2726
return (
2827
<>
29-
<Button
28+
<div
3029
onClick={() => setIsOpen(true)}
31-
variant="ghost"
32-
size="icon"
33-
className={`${sizeClasses} text-muted-foreground hover:text-foreground hover:bg-muted ${className}`}
30+
className={`${sizeClasses} text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer inline-flex items-center justify-center rounded-md transition-colors ${className}`}
3431
title={tooltip}
3532
>
3633
<HelpCircle className="w-4 h-4" />
37-
</Button>
34+
</div>
3835

3936
<HelpModal
4037
isOpen={isOpen}

src/app/_components/HelpModal.tsx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings'
319319
<li><strong>Installation Status:</strong> Track success, failure, or in-progress installations</li>
320320
<li><strong>Server Association:</strong> Know which server each script is installed on</li>
321321
<li><strong>Container ID:</strong> Link scripts to specific LXC containers</li>
322+
<li><strong>Web UI Access:</strong> Track and access Web UI IP addresses and ports</li>
322323
<li><strong>Execution Logs:</strong> View output and logs from script installations</li>
323324
<li><strong>Filtering:</strong> Filter by server, status, or search terms</li>
324325
</ul>
@@ -335,8 +336,47 @@ export function HelpModal({ isOpen, onClose, initialSection = 'server-settings'
335336
</ul>
336337
</div>
337338

339+
<div className="p-4 border border-border rounded-lg bg-blue-900/20 border-blue-700/50">
340+
<h4 className="font-medium text-foreground mb-2">Web UI Access </h4>
341+
<p className="text-sm text-muted-foreground mb-3">
342+
Automatically detect and access Web UI interfaces for your installed scripts.
343+
</p>
344+
<ul className="text-sm text-muted-foreground space-y-2">
345+
<li><strong>Auto-Detection:</strong> Automatically detects Web UI URLs from script installation output</li>
346+
<li><strong>IP & Port Tracking:</strong> Stores and displays Web UI IP addresses and ports</li>
347+
<li><strong>One-Click Access:</strong> Click IP:port to open Web UI in new tab</li>
348+
<li><strong>Manual Detection:</strong> Re-detect IP using <code>hostname -I</code> inside container</li>
349+
<li><strong>Port Detection:</strong> Uses script metadata to get correct port (e.g., actualbudget:5006)</li>
350+
<li><strong>Editable Fields:</strong> Manually edit IP and port values as needed</li>
351+
</ul>
352+
<div className="mt-3 p-3 bg-blue-900/30 rounded-lg border border-blue-700/30">
353+
<p className="text-sm font-medium text-blue-300">💡 How it works:</p>
354+
<ul className="text-sm text-muted-foreground mt-1 space-y-1">
355+
<li>• Scripts automatically detect URLs like <code>http://10.10.10.1:3000</code> during installation</li>
356+
<li>• Re-detect button runs <code>hostname -I</code> inside the container via SSH</li>
357+
<li>• Port defaults to 80, but uses script metadata when available</li>
358+
<li>• Web UI buttons are disabled when container is stopped</li>
359+
</ul>
360+
</div>
361+
</div>
362+
363+
<div className="p-4 border border-border rounded-lg bg-accent/50 dark:bg-accent/20">
364+
<h4 className="font-medium text-foreground mb-2">Actions Dropdown </h4>
365+
<p className="text-sm text-muted-foreground mb-3">
366+
Clean interface with all actions organized in a dropdown menu.
367+
</p>
368+
<ul className="text-sm text-muted-foreground space-y-2">
369+
<li><strong>Edit Button:</strong> Always visible for quick script editing</li>
370+
<li><strong>Actions Dropdown:</strong> Contains Update, Shell, Open UI, Start/Stop, Destroy, Delete</li>
371+
<li><strong>Smart Visibility:</strong> Dropdown only appears when actions are available</li>
372+
<li><strong>Color Coding:</strong> Start (green), Stop (red), Update (cyan), Shell (gray), Open UI (blue)</li>
373+
<li><strong>Auto-Close:</strong> Dropdown closes after clicking any action</li>
374+
<li><strong>Disabled States:</strong> Actions are disabled when container is stopped</li>
375+
</ul>
376+
</div>
377+
338378
<div className="p-4 border border-border rounded-lg bg-accent/50 dark:bg-accent/20">
339-
<h4 className="font-medium text-foreground mb-2">Container Control (NEW)</h4>
379+
<h4 className="font-medium text-foreground mb-2">Container Control</h4>
340380
<p className="text-sm text-muted-foreground mb-3">
341381
Directly control LXC containers from the installed scripts page via SSH.
342382
</p>

0 commit comments

Comments
 (0)