feat: add interactive terminal support for containers via WebSocket
All checks were successful
Build and Push Docker Images / docker (push) Successful in 30s

- Implemented WebSocket endpoint for executing interactive shells in Docker containers.
- Added ticket-based authentication for terminal access to enhance security.
- Updated frontend to support terminal interactions, including lazy loading of terminal components.
- Enhanced backend to manage terminal session tickets and settings for enabling/disabling terminal access.
- Updated Nginx configuration to support WebSocket connections for terminal sessions.
- Added necessary dependencies for terminal functionality in the frontend.
- Improved user interface to include terminal access controls and feedback on terminal status.
This commit is contained in:
jeanotx32
2026-08-01 01:49:26 -04:00
parent f37b639226
commit e277e99155
15 changed files with 732 additions and 24 deletions

View File

@@ -33,15 +33,20 @@ export default function Modal({
children,
bodyClassName = 'overflow-y-auto p-4',
panelClassName = '',
// Le terminal a besoin de Tab (complétion) et d'Échap (vim) : il désactive
// ces deux raccourcis et se ferme via la croix.
trapTab = true,
closeOnEscape = true,
}) {
const panelRef = useRef(null)
const onCloseRef = useRef(onClose)
const titleId = useId()
const panelRef = useRef(null)
const titleId = useId()
// `onClose` est souvent une lambda recréée à chaque rendu du parent : on la
// lit via une ref pour que l'effet ne se rejoue pas (sinon le focus sauterait
// au premier champ à chaque rafraîchissement automatique).
useEffect(() => { onCloseRef.current = onClose })
// `onClose` est souvent une lambda recréée à chaque rendu du parent : le
// gestionnaire de touches lit ces valeurs via une ref pour que l'effet ne se
// rejoue pas (sinon le focus sauterait au premier champ à chaque
// rafraîchissement automatique).
const handlersRef = useRef({ onClose, trapTab, closeOnEscape })
useEffect(() => { handlersRef.current = { onClose, trapTab, closeOnEscape } })
useEffect(() => {
const previouslyFocused = document.activeElement
@@ -50,12 +55,12 @@ export default function Modal({
;(firstFocusable ?? panel)?.focus({ preventScroll: true })
const onKeyDown = (e) => {
if (e.key === 'Escape') {
if (e.key === 'Escape' && handlersRef.current.closeOnEscape) {
e.stopPropagation()
onCloseRef.current?.()
handlersRef.current.onClose?.()
return
}
if (e.key !== 'Tab' || !panel) return
if (e.key !== 'Tab' || !handlersRef.current.trapTab || !panel) return
const items = [...panel.querySelectorAll(FOCUSABLE)].filter(el => el.offsetParent !== null)
if (items.length === 0) return