mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-12-16 17:16:25 +00:00
Update AppImage
This commit is contained in:
@@ -41,6 +41,16 @@ function getWebSocketUrl(): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getApiUrl(): string {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return "http://localhost:8008"
|
||||||
|
}
|
||||||
|
|
||||||
|
const { protocol, hostname } = window.location
|
||||||
|
const apiProtocol = protocol === "https:" ? "https:" : "http:"
|
||||||
|
return `${apiProtocol}//${hostname}:${API_PORT}`
|
||||||
|
}
|
||||||
|
|
||||||
const proxmoxCommands = [
|
const proxmoxCommands = [
|
||||||
{ cmd: "pvesh get /nodes", desc: "List all Proxmox nodes" },
|
{ cmd: "pvesh get /nodes", desc: "List all Proxmox nodes" },
|
||||||
{ cmd: "pvesh get /nodes/{node}/qemu", desc: "List VMs on a node" },
|
{ cmd: "pvesh get /nodes/{node}/qemu", desc: "List VMs on a node" },
|
||||||
@@ -156,35 +166,35 @@ export const TerminalPanel: React.FC<TerminalPanelProps> = ({ websocketUrl, onCl
|
|||||||
try {
|
try {
|
||||||
setIsSearching(true)
|
setIsSearching(true)
|
||||||
|
|
||||||
const formattedQuery = query.trim().replace(/\s+/g, "+")
|
// Usar el proxy local de Flask
|
||||||
const url = `https://cht.sh/${formattedQuery}?QT`
|
const apiUrl = getApiUrl()
|
||||||
|
const response = await fetch(`${apiUrl}/api/terminal/search-command?q=${encodeURIComponent(query)}`, {
|
||||||
console.log("[v0] Fetching from cheat.sh:", url)
|
method: "GET",
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
signal: AbortSignal.timeout(10000),
|
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "curl/7.68.0",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.log("[v0] API response not OK:", response.status)
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
throw new Error(`API request failed: ${response.status}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = await response.text()
|
const data = await response.json()
|
||||||
console.log("[v0] Received response, length:", text.length)
|
|
||||||
|
|
||||||
if (!text || text.includes("Unknown topic") || text.includes("nothing found")) {
|
if (!data.success || !data.content) {
|
||||||
throw new Error("No results found")
|
throw new Error("No content received")
|
||||||
}
|
}
|
||||||
|
|
||||||
const blocks = text.split(/\n\s*\n/).filter((block) => block.trim())
|
console.log("[v0] Cheat.sh response received from proxy")
|
||||||
|
|
||||||
|
// Parsear la respuesta de cheat.sh
|
||||||
|
const text = data.content
|
||||||
|
const blocks = text.split(/\n\s*\n/).filter((block: string) => block.trim())
|
||||||
const examples: string[] = []
|
const examples: string[] = []
|
||||||
|
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
const lines = block.split("\n").filter((line) => {
|
const lines = block.split("\n").filter((line: string) => {
|
||||||
const trimmed = line.trim()
|
const trimmed = line.trim()
|
||||||
return trimmed && !trimmed.startsWith("http") && !trimmed.includes("cheat.sh") && !trimmed.includes("[")
|
return trimmed && !trimmed.startsWith("http") && !trimmed.includes("cheat.sh") && !trimmed.includes("[")
|
||||||
})
|
})
|
||||||
@@ -212,7 +222,8 @@ export const TerminalPanel: React.FC<TerminalPanelProps> = ({ websocketUrl, onCl
|
|||||||
throw new Error("No valid examples found")
|
throw new Error("No valid examples found")
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("[v0] Error fetching from cheat.sh, showing offline results:", error)
|
console.log("[v0] Error fetching from cheat.sh proxy:", error)
|
||||||
|
// Mostrar resultados offline si falla
|
||||||
const filtered = proxmoxCommands.filter(
|
const filtered = proxmoxCommands.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.cmd.toLowerCase().includes(query.toLowerCase()) ||
|
item.cmd.toLowerCase().includes(query.toLowerCase()) ||
|
||||||
@@ -220,6 +231,7 @@ export const TerminalPanel: React.FC<TerminalPanelProps> = ({ websocketUrl, onCl
|
|||||||
)
|
)
|
||||||
setFilteredCommands(filtered)
|
setFilteredCommands(filtered)
|
||||||
setSearchResults([])
|
setSearchResults([])
|
||||||
|
setUseOnline(false)
|
||||||
} finally {
|
} finally {
|
||||||
setIsSearching(false)
|
setIsSearching(false)
|
||||||
}
|
}
|
||||||
@@ -227,13 +239,12 @@ export const TerminalPanel: React.FC<TerminalPanelProps> = ({ websocketUrl, onCl
|
|||||||
|
|
||||||
const debounce = setTimeout(() => {
|
const debounce = setTimeout(() => {
|
||||||
if (searchQuery && searchQuery.length >= 2) {
|
if (searchQuery && searchQuery.length >= 2) {
|
||||||
// Only search if query is at least 2 characters
|
|
||||||
searchCheatSh(searchQuery)
|
searchCheatSh(searchQuery)
|
||||||
} else {
|
} else {
|
||||||
setSearchResults([])
|
setSearchResults([])
|
||||||
setFilteredCommands(proxmoxCommands)
|
setFilteredCommands(proxmoxCommands)
|
||||||
}
|
}
|
||||||
}, 800) // Increased debounce to 800ms to avoid premature requests
|
}, 800)
|
||||||
|
|
||||||
return () => clearTimeout(debounce)
|
return () => clearTimeout(debounce)
|
||||||
}, [searchQuery])
|
}, [searchQuery])
|
||||||
@@ -434,8 +445,10 @@ export const TerminalPanel: React.FC<TerminalPanelProps> = ({ websocketUrl, onCl
|
|||||||
const sendToActiveTerminal = (command: string) => {
|
const sendToActiveTerminal = (command: string) => {
|
||||||
const activeTerminal = terminals.find((t) => t.id === activeTerminalId)
|
const activeTerminal = terminals.find((t) => t.id === activeTerminalId)
|
||||||
if (activeTerminal?.ws && activeTerminal.ws.readyState === WebSocket.OPEN) {
|
if (activeTerminal?.ws && activeTerminal.ws.readyState === WebSocket.OPEN) {
|
||||||
activeTerminal.ws.send(command + "\n")
|
// Solo enviamos el comando sin el \n para que no se ejecute automáticamente
|
||||||
setSearchModalOpen(false) // Close the search modal after sending a command
|
// El usuario puede editar el comando y presionar Enter cuando esté listo
|
||||||
|
activeTerminal.ws.send(command)
|
||||||
|
setSearchModalOpen(false) // Cerrar el modal después de escribir el comando
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ ProxMenux Terminal WebSocket Routes
|
|||||||
Provides a WebSocket endpoint for interactive terminal sessions
|
Provides a WebSocket endpoint for interactive terminal sessions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import Blueprint
|
from flask import Blueprint, jsonify, request
|
||||||
from flask_sock import Sock
|
from flask_sock import Sock
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
@@ -15,6 +15,7 @@ import fcntl
|
|||||||
import termios
|
import termios
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
terminal_bp = Blueprint('terminal', __name__)
|
terminal_bp = Blueprint('terminal', __name__)
|
||||||
sock = Sock()
|
sock = Sock()
|
||||||
@@ -27,6 +28,45 @@ def terminal_health():
|
|||||||
"""Health check for terminal service"""
|
"""Health check for terminal service"""
|
||||||
return {'success': True, 'active_sessions': len(active_sessions)}
|
return {'success': True, 'active_sessions': len(active_sessions)}
|
||||||
|
|
||||||
|
@terminal_bp.route('/api/terminal/search-command', methods=['GET'])
|
||||||
|
def search_command():
|
||||||
|
"""Proxy endpoint for cheat.sh API to avoid CORS issues"""
|
||||||
|
query = request.args.get('q', '')
|
||||||
|
|
||||||
|
if not query or len(query) < 2:
|
||||||
|
return jsonify({'error': 'Query too short'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Hacer petición a cheat.sh desde el servidor
|
||||||
|
url = f'https://cht.sh/{query.replace(" ", "+")}?QT'
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'curl/7.68.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'content': response.text
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': f'API returned status {response.status_code}'
|
||||||
|
}), response.status_code
|
||||||
|
|
||||||
|
except requests.Timeout:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Request timeout'
|
||||||
|
}), 504
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
def set_winsize(fd, rows, cols):
|
def set_winsize(fd, rows, cols):
|
||||||
"""Set terminal window size"""
|
"""Set terminal window size"""
|
||||||
try:
|
try:
|
||||||
@@ -68,7 +108,7 @@ def terminal_websocket(ws):
|
|||||||
stdout=slave_fd,
|
stdout=slave_fd,
|
||||||
stderr=slave_fd,
|
stderr=slave_fd,
|
||||||
preexec_fn=os.setsid,
|
preexec_fn=os.setsid,
|
||||||
cwd='/root',
|
cwd='/',
|
||||||
env=dict(os.environ, TERM='xterm-256color', PS1='\\u@\\h:\\w\\$ ')
|
env=dict(os.environ, TERM='xterm-256color', PS1='\\u@\\h:\\w\\$ ')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user