mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-02-19 00:46:31 +00:00
Update terminal modal
This commit is contained in:
@@ -12,7 +12,16 @@ import {
|
|||||||
ArrowRight,
|
ArrowRight,
|
||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
GripHorizontal,
|
GripHorizontal,
|
||||||
|
ChevronDown,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
import "xterm/css/xterm.css"
|
import "xterm/css/xterm.css"
|
||||||
import { API_PORT } from "@/lib/api-config"
|
import { API_PORT } from "@/lib/api-config"
|
||||||
|
|
||||||
@@ -62,12 +71,7 @@ export function LxcTerminalModal({
|
|||||||
const resizeBarRef = useRef<HTMLDivElement>(null)
|
const resizeBarRef = useRef<HTMLDivElement>(null)
|
||||||
const modalHeightRef = useRef(500)
|
const modalHeightRef = useRef(500)
|
||||||
|
|
||||||
const [showLoginModal, setShowLoginModal] = useState(false)
|
|
||||||
const [loginUsername, setLoginUsername] = useState("")
|
|
||||||
const [loginPassword, setLoginPassword] = useState("")
|
|
||||||
const [loginError, setLoginError] = useState("")
|
|
||||||
const [isLoggingIn, setIsLoggingIn] = useState(false)
|
|
||||||
const waitingForPasswordRef = useRef(false)
|
|
||||||
|
|
||||||
// Detect mobile/tablet
|
// Detect mobile/tablet
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -171,7 +175,7 @@ export function LxcTerminalModal({
|
|||||||
const ws = new WebSocket(wsUrl)
|
const ws = new WebSocket(wsUrl)
|
||||||
wsRef.current = ws
|
wsRef.current = ws
|
||||||
|
|
||||||
// Reset state for new connection
|
// Reset state for new connection
|
||||||
isInsideLxcRef.current = false
|
isInsideLxcRef.current = false
|
||||||
outputBufferRef.current = ""
|
outputBufferRef.current = ""
|
||||||
|
|
||||||
@@ -354,33 +358,6 @@ export function LxcTerminalModal({
|
|||||||
const sendEnter = useCallback(() => sendKey("\r"), [sendKey])
|
const sendEnter = useCallback(() => sendKey("\r"), [sendKey])
|
||||||
const sendCtrlC = useCallback(() => sendKey("\x03"), [sendKey]) // Ctrl+C
|
const sendCtrlC = useCallback(() => sendKey("\x03"), [sendKey]) // Ctrl+C
|
||||||
|
|
||||||
// Ctrl key state - user presses Ctrl button, then types a letter
|
|
||||||
const [ctrlPressed, setCtrlPressed] = useState(false)
|
|
||||||
|
|
||||||
const handleCtrlPress = useCallback(() => {
|
|
||||||
setCtrlPressed(true)
|
|
||||||
setTimeout(() => setCtrlPressed(false), 3000)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Handle keyboard input when Ctrl is pressed
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ctrlPressed || !isOpen) return
|
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
|
||||||
if (e.key.length === 1) {
|
|
||||||
e.preventDefault()
|
|
||||||
const code = e.key.toLowerCase().charCodeAt(0) - 96
|
|
||||||
if (code >= 1 && code <= 26) {
|
|
||||||
sendKey(String.fromCharCode(code))
|
|
||||||
}
|
|
||||||
setCtrlPressed(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("keydown", handleKeyDown)
|
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown)
|
|
||||||
}, [ctrlPressed, isOpen, sendKey])
|
|
||||||
|
|
||||||
const showMobileControls = isMobile || isTablet
|
const showMobileControls = isMobile || isTablet
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -477,16 +454,34 @@ export function LxcTerminalModal({
|
|||||||
<CornerDownLeft className="h-4 w-4 mr-1" />
|
<CornerDownLeft className="h-4 w-4 mr-1" />
|
||||||
Enter
|
Enter
|
||||||
</Button>
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleCtrlPress}
|
className="h-8 px-2 text-xs bg-zinc-800 border-zinc-700 text-zinc-300 gap-1"
|
||||||
className={`h-8 px-2 text-xs ${ctrlPressed
|
|
||||||
? "bg-yellow-600/30 border-yellow-600/50 text-yellow-400"
|
|
||||||
: "bg-zinc-800 border-zinc-700 text-zinc-300"}`}
|
|
||||||
>
|
>
|
||||||
{ctrlPressed ? "Ctrl+?" : "Ctrl"}
|
Ctrl
|
||||||
|
<ChevronDown className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-48">
|
||||||
|
<DropdownMenuLabel className="text-xs text-muted-foreground">Control Sequences</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onSelect={() => sendKey("\x03")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+C</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Cancel/Interrupt</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onSelect={() => sendKey("\x18")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+X</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Exit (nano)</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onSelect={() => sendKey("\x12")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+R</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Search history</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,7 +15,16 @@ import {
|
|||||||
ArrowRight,
|
ArrowRight,
|
||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
GripHorizontal,
|
GripHorizontal,
|
||||||
|
ChevronDown,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
import "xterm/css/xterm.css"
|
import "xterm/css/xterm.css"
|
||||||
import { API_PORT } from "@/lib/api-config"
|
import { API_PORT } from "@/lib/api-config"
|
||||||
|
|
||||||
@@ -741,18 +750,34 @@ export function ScriptTerminalModal({
|
|||||||
<CornerDownLeft className="h-4 w-4 mr-1" />
|
<CornerDownLeft className="h-4 w-4 mr-1" />
|
||||||
Enter
|
Enter
|
||||||
</Button>
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
onPointerDown={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
sendCommand("\x03")
|
|
||||||
}}
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 px-2 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white min-w-[65px]"
|
className="h-8 px-2 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white min-w-[65px] gap-1"
|
||||||
>
|
>
|
||||||
Ctrl
|
Ctrl
|
||||||
|
<ChevronDown className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-48">
|
||||||
|
<DropdownMenuLabel className="text-xs text-muted-foreground">Control Sequences</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onSelect={() => sendCommand("\x03")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+C</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Cancel/Interrupt</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onSelect={() => sendCommand("\x18")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+X</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Exit (nano)</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onSelect={() => sendCommand("\x12")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+R</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Search history</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,16 @@ import {
|
|||||||
AlignJustify,
|
AlignJustify,
|
||||||
Grid2X2,
|
Grid2X2,
|
||||||
GripHorizontal,
|
GripHorizontal,
|
||||||
|
ChevronDown,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
@@ -967,18 +976,34 @@ const handleClose = () => {
|
|||||||
>
|
>
|
||||||
↵ Enter
|
↵ Enter
|
||||||
</Button>
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
onPointerDown={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
sendSequence("\x03", e)
|
|
||||||
}}
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 px-2 text-xs"
|
className="h-8 px-2 text-xs gap-1 bg-transparent"
|
||||||
>
|
>
|
||||||
Ctrl
|
Ctrl
|
||||||
|
<ChevronDown className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-48">
|
||||||
|
<DropdownMenuLabel className="text-xs text-muted-foreground">Control Sequences</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onSelect={() => sendSequence("\x03")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+C</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Cancel/Interrupt</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onSelect={() => sendSequence("\x18")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+X</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Exit (nano)</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onSelect={() => sendSequence("\x12")}>
|
||||||
|
<span className="font-mono text-xs mr-2">Ctrl+R</span>
|
||||||
|
<span className="text-muted-foreground text-xs">Search history</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user