Fix #362: auto-detect race, VM shell path, UI hints
- Defer resolve in autoDetectLXCContainers (pct/qm list) so stdout is complete - Pass containerType when opening shell; use qm terminal for VMs, pct enter for LXC - Add UI hint for VM shell (serial console, Ctrl+O, serial port requirement) - Rename auto-detect to Containers & VMs and update help text Fixes #362
This commit is contained in:
29
server.js
29
server.js
@@ -312,7 +312,7 @@ class ScriptExecutionHandler {
|
|||||||
} else if (isUpdate && containerId) {
|
} else if (isUpdate && containerId) {
|
||||||
await this.startUpdateExecution(ws, containerId, executionId, mode, server, backupStorage);
|
await this.startUpdateExecution(ws, containerId, executionId, mode, server, backupStorage);
|
||||||
} else if (isShell && containerId) {
|
} else if (isShell && containerId) {
|
||||||
await this.startShellExecution(ws, containerId, executionId, mode, server);
|
await this.startShellExecution(ws, containerId, executionId, mode, server, containerType);
|
||||||
} else {
|
} else {
|
||||||
await this.startScriptExecution(ws, scriptPath, executionId, mode, server, envVars);
|
await this.startScriptExecution(ws, scriptPath, executionId, mode, server, envVars);
|
||||||
}
|
}
|
||||||
@@ -1474,21 +1474,21 @@ class ScriptExecutionHandler {
|
|||||||
* @param {string} executionId
|
* @param {string} executionId
|
||||||
* @param {string} mode
|
* @param {string} mode
|
||||||
* @param {ServerInfo|null} server
|
* @param {ServerInfo|null} server
|
||||||
|
* @param {'lxc'|'vm'} [containerType='lxc']
|
||||||
*/
|
*/
|
||||||
async startShellExecution(ws, containerId, executionId, mode = 'local', server = null) {
|
async startShellExecution(ws, containerId, executionId, mode = 'local', server = null, containerType = 'lxc') {
|
||||||
try {
|
try {
|
||||||
|
const typeLabel = containerType === 'vm' ? 'VM' : 'container';
|
||||||
// Send start message
|
|
||||||
this.sendMessage(ws, {
|
this.sendMessage(ws, {
|
||||||
type: 'start',
|
type: 'start',
|
||||||
data: `Starting shell session for container ${containerId}...`,
|
data: `Starting shell session for ${typeLabel} ${containerId}...`,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mode === 'ssh' && server) {
|
if (mode === 'ssh' && server) {
|
||||||
await this.startSSHShellExecution(ws, containerId, executionId, server);
|
await this.startSSHShellExecution(ws, containerId, executionId, server, containerType);
|
||||||
} else {
|
} else {
|
||||||
await this.startLocalShellExecution(ws, containerId, executionId);
|
await this.startLocalShellExecution(ws, containerId, executionId, containerType);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1505,12 +1505,12 @@ class ScriptExecutionHandler {
|
|||||||
* @param {ExtendedWebSocket} ws
|
* @param {ExtendedWebSocket} ws
|
||||||
* @param {string} containerId
|
* @param {string} containerId
|
||||||
* @param {string} executionId
|
* @param {string} executionId
|
||||||
|
* @param {'lxc'|'vm'} [containerType='lxc']
|
||||||
*/
|
*/
|
||||||
async startLocalShellExecution(ws, containerId, executionId) {
|
async startLocalShellExecution(ws, containerId, executionId, containerType = 'lxc') {
|
||||||
const { spawn } = await import('node-pty');
|
const { spawn } = await import('node-pty');
|
||||||
|
const shellCommand = containerType === 'vm' ? `qm terminal ${containerId}` : `pct enter ${containerId}`;
|
||||||
// Create a shell process that will run pct enter
|
const childProcess = spawn('bash', ['-c', shellCommand], {
|
||||||
const childProcess = spawn('bash', ['-c', `pct enter ${containerId}`], {
|
|
||||||
name: 'xterm-color',
|
name: 'xterm-color',
|
||||||
cols: 80,
|
cols: 80,
|
||||||
rows: 24,
|
rows: 24,
|
||||||
@@ -1553,14 +1553,15 @@ class ScriptExecutionHandler {
|
|||||||
* @param {string} containerId
|
* @param {string} containerId
|
||||||
* @param {string} executionId
|
* @param {string} executionId
|
||||||
* @param {ServerInfo} server
|
* @param {ServerInfo} server
|
||||||
|
* @param {'lxc'|'vm'} [containerType='lxc']
|
||||||
*/
|
*/
|
||||||
async startSSHShellExecution(ws, containerId, executionId, server) {
|
async startSSHShellExecution(ws, containerId, executionId, server, containerType = 'lxc') {
|
||||||
const sshService = getSSHExecutionService();
|
const sshService = getSSHExecutionService();
|
||||||
|
const shellCommand = containerType === 'vm' ? `qm terminal ${containerId}` : `pct enter ${containerId}`;
|
||||||
try {
|
try {
|
||||||
const execution = await sshService.executeCommand(
|
const execution = await sshService.executeCommand(
|
||||||
server,
|
server,
|
||||||
`pct enter ${containerId}`,
|
shellCommand,
|
||||||
/** @param {string} data */
|
/** @param {string} data */
|
||||||
(data) => {
|
(data) => {
|
||||||
this.sendMessage(ws, {
|
this.sendMessage(ws, {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export function InstalledScriptsTab() {
|
|||||||
id: number;
|
id: number;
|
||||||
containerId: string;
|
containerId: string;
|
||||||
server?: any;
|
server?: any;
|
||||||
|
containerType?: 'lxc' | 'vm';
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [showBackupPrompt, setShowBackupPrompt] = useState(false);
|
const [showBackupPrompt, setShowBackupPrompt] = useState(false);
|
||||||
const [showStorageSelection, setShowStorageSelection] = useState(false);
|
const [showStorageSelection, setShowStorageSelection] = useState(false);
|
||||||
@@ -1167,6 +1168,7 @@ export function InstalledScriptsTab() {
|
|||||||
id: script.id,
|
id: script.id,
|
||||||
containerId: script.container_id,
|
containerId: script.container_id,
|
||||||
server: server,
|
server: server,
|
||||||
|
containerType: script.is_vm ? 'vm' : 'lxc',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1452,6 +1454,13 @@ export function InstalledScriptsTab() {
|
|||||||
{/* Shell Terminal */}
|
{/* Shell Terminal */}
|
||||||
{openingShell && (
|
{openingShell && (
|
||||||
<div className="mb-8" data-terminal="shell">
|
<div className="mb-8" data-terminal="shell">
|
||||||
|
{openingShell.containerType === 'vm' && (
|
||||||
|
<p className="text-muted-foreground mb-2 text-sm">
|
||||||
|
VM shell uses the Proxmox serial console. The VM must have a
|
||||||
|
serial port configured (e.g. <code className="bg-muted rounded px-1">qm set {openingShell.containerId} -serial0 socket</code>).
|
||||||
|
Detach with <kbd className="bg-muted rounded px-1">Ctrl+O</kbd>.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<Terminal
|
<Terminal
|
||||||
scriptPath={`shell-${openingShell.containerId}`}
|
scriptPath={`shell-${openingShell.containerId}`}
|
||||||
onClose={handleCloseShellTerminal}
|
onClose={handleCloseShellTerminal}
|
||||||
@@ -1459,6 +1468,7 @@ export function InstalledScriptsTab() {
|
|||||||
server={openingShell.server}
|
server={openingShell.server}
|
||||||
isShell={true}
|
isShell={true}
|
||||||
containerId={openingShell.containerId}
|
containerId={openingShell.containerId}
|
||||||
|
containerType={openingShell.containerType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1538,7 +1548,7 @@ export function InstalledScriptsTab() {
|
|||||||
>
|
>
|
||||||
{showAutoDetectForm
|
{showAutoDetectForm
|
||||||
? "Cancel Auto-Detect"
|
? "Cancel Auto-Detect"
|
||||||
: '🔍 Auto-Detect LXC Containers (Must contain a tag with "community-script")'}
|
: '🔍 Auto-Detect Containers & VMs (tag: community-script)'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1764,12 +1774,11 @@ export function InstalledScriptsTab() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Auto-Detect LXC Containers Form */}
|
{/* Auto-Detect Containers & VMs Form */}
|
||||||
{showAutoDetectForm && (
|
{showAutoDetectForm && (
|
||||||
<div className="bg-card border-border mb-6 rounded-lg border p-4 shadow-sm sm:p-6">
|
<div className="bg-card border-border mb-6 rounded-lg border p-4 shadow-sm sm:p-6">
|
||||||
<h3 className="text-foreground mb-4 text-lg font-semibold sm:mb-6">
|
<h3 className="text-foreground mb-4 text-lg font-semibold sm:mb-6">
|
||||||
Auto-Detect LXC Containers (Must contain a tag with
|
Auto-Detect Containers & VMs (tag: community-script)
|
||||||
"community-script")
|
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-4 sm:space-y-6">
|
<div className="space-y-4 sm:space-y-6">
|
||||||
<div className="bg-muted/30 border-muted rounded-lg border p-4">
|
<div className="bg-muted/30 border-muted rounded-lg border p-4">
|
||||||
@@ -1795,12 +1804,12 @@ export function InstalledScriptsTab() {
|
|||||||
<p>This feature will:</p>
|
<p>This feature will:</p>
|
||||||
<ul className="mt-1 list-inside list-disc space-y-1">
|
<ul className="mt-1 list-inside list-disc space-y-1">
|
||||||
<li>Connect to the selected server via SSH</li>
|
<li>Connect to the selected server via SSH</li>
|
||||||
<li>Scan all LXC config files in /etc/pve/lxc/</li>
|
<li>Scan LXC configs in /etc/pve/lxc/ and VM configs in /etc/pve/qemu-server/</li>
|
||||||
<li>
|
<li>
|
||||||
Find containers with "community-script" in
|
Find containers and VMs with "community-script" in
|
||||||
their tags
|
their tags
|
||||||
</li>
|
</li>
|
||||||
<li>Extract the container ID and hostname</li>
|
<li>Extract the container/VM ID and hostname or name</li>
|
||||||
<li>Add them as installed script entries</li>
|
<li>Add them as installed script entries</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -2302,6 +2311,11 @@ export function InstalledScriptsTab() {
|
|||||||
"stopped"
|
"stopped"
|
||||||
}
|
}
|
||||||
className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20"
|
className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20"
|
||||||
|
title={
|
||||||
|
script.is_vm
|
||||||
|
? "VM serial console (requires serial port; detach with Ctrl+O)"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Shell
|
Shell
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -1060,7 +1060,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
reject(new Error(`pct list failed: ${error}`));
|
reject(new Error(`pct list failed: ${error}`));
|
||||||
},
|
},
|
||||||
(_exitCode: number) => {
|
(_exitCode: number) => {
|
||||||
resolve();
|
setImmediate(() => resolve());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1079,7 +1079,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
|||||||
reject(new Error(`qm list failed: ${error}`));
|
reject(new Error(`qm list failed: ${error}`));
|
||||||
},
|
},
|
||||||
(_exitCode: number) => {
|
(_exitCode: number) => {
|
||||||
resolve();
|
setImmediate(() => resolve());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user