Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kustomize/overlays/ephemeral/configmap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ENVIRONMENT=ephemeral
LOG_LEVEL=DEBUG
API_BASE_URL=https://dev.api.cks.fullstack.pw/api/v1
API_BASE_URL=https://pr-6-cks-backend.ephemeral.fullstack.pw/api/v1
547 changes: 8 additions & 539 deletions src/components/Terminal.js

Large diffs are not rendered by default.

164 changes: 86 additions & 78 deletions src/components/TerminalContainer.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,84 @@
// frontend/components/TerminalContainer.js
import React from 'react';
import Terminal from './Terminal';
import { Button, LoadingState, ErrorState, StatusIndicator } from './common';
import { Button, LoadingState, ErrorState } from './common';
import { useTerminal } from '../hooks/useTerminal';

/**
* Container component for terminals that manages tabs using the unified useTerminal hook
*/
const formatTabLabel = (id) => {
const [prefix, num] = id.split('-');
return `${prefix === 'cp' ? 'CP' : 'WK'} ${num}`;
};

const TerminalContainer = ({ sessionId }) => {
const {
terminals,
activeTerminal,
activeTarget,
activeTabId,
sessionStatus,
createTerminal,
switchTerminal,
handleConnectionChange,
addTerminal,
closeTerminal,
switchTarget,
switchTab,
isSessionReady
} = useTerminal(sessionId);

const activeTabs = terminals[activeTarget];

return (
<div className="h-full flex flex-col">
{/* Terminal tabs */}
<div className="bg-gray-800 px-4 py-2 text-white flex overflow-x-auto">
<Button
variant={activeTerminal === 'control-plane' ? 'primary' : 'secondary'}
onClick={() => switchTerminal('control-plane')}
variant={activeTarget === 'control-plane' ? 'primary' : 'secondary'}
onClick={() => switchTarget('control-plane')}
disabled={!isSessionReady}
className={`mr-2 flex items-center ${!isSessionReady ? 'opacity-50' : ''}`}
>
Control Plane
{terminals['control-plane'].status === 'connected' && (
<StatusIndicator status="connected" size="sm" className="ml-2" />
)}
</Button>

<Button
variant={activeTerminal === 'worker-node' ? 'primary' : 'secondary'}
onClick={() => switchTerminal('worker-node')}
variant={activeTarget === 'worker-node' ? 'primary' : 'secondary'}
onClick={() => switchTarget('worker-node')}
disabled={!isSessionReady}
className={`flex items-center ${!isSessionReady ? 'opacity-50' : ''}`}
>
Worker Node
{terminals['worker-node'].status === 'connected' && (
<StatusIndicator status="connected" size="sm" className="ml-2" />
)}
</Button>
</div>

{/* Terminal content area */}
{isSessionReady && activeTabs.length > 0 && (
<div className="bg-gray-700 px-2 py-1 flex items-center overflow-x-auto gap-1">
{activeTabs.map(tab => (
<button
key={tab.id}
onClick={() => switchTab(tab.id)}
className={`flex items-center px-3 py-1 text-xs rounded transition-colors ${
tab.id === activeTabId
? 'bg-gray-600 text-white'
: 'text-gray-300 hover:text-white hover:bg-gray-600'
}`}
>
<span>{formatTabLabel(tab.id)}</span>
{activeTabs.length > 1 && (
<span
onClick={(e) => { e.stopPropagation(); closeTerminal(tab.id); }}
className="ml-2 hover:text-red-400 cursor-pointer"
>
x
</span>
)}
</button>
))}
<button
onClick={addTerminal}
className="px-2 py-1 text-xs text-gray-300 hover:text-white hover:bg-gray-600 rounded transition-colors"
>
+
</button>
</div>
)}

<div className="flex-1 overflow-hidden relative">
{/* Session not ready state */}
{!isSessionReady && (
<div className="flex flex-col justify-center items-center h-full bg-gray-800 text-white p-4">
{sessionStatus.isLoading ? (
Expand All @@ -69,73 +98,52 @@ const TerminalContainer = ({ sessionId }) => {
</div>
)}

{/* Terminal content */}
{isSessionReady && (
<>
{/* Control plane terminal */}
<div className={`absolute inset-0 transition-opacity duration-300 ${activeTerminal === 'control-plane' ? 'opacity-100 z-10' : 'opacity-0 z-0'
}`}>
{terminals['control-plane'].id ? (
<Terminal
terminalId={terminals['control-plane'].id}
onConnectionChange={(connected) => handleConnectionChange('control-plane', connected)}
/>
) : (
<div className="flex flex-col justify-center items-center h-full bg-gray-800 text-white p-4">
{terminals['control-plane'].isLoading ? (
<LoadingState message="Creating terminal session..." size="md" />
) : terminals['control-plane'].error ? (
<ErrorState
message="Failed to create terminal"
details={terminals['control-plane'].error}
onRetry={() => createTerminal('control-plane')}
/>
{['control-plane', 'worker-node'].map(target =>
terminals[target].map(tab => (
<div
key={tab.id}
className={`absolute inset-0 transition-opacity duration-200 ${
tab.id === activeTabId
? 'opacity-100 z-10'
: 'opacity-0 z-0 pointer-events-none'
}`}
>
{tab.url ? (
<Terminal terminalUrl={tab.url} />
) : (
<Button
variant="primary"
onClick={() => createTerminal('control-plane')}
>
Connect to Control Plane
</Button>
<div className="flex flex-col justify-center items-center h-full bg-gray-800 text-white p-4">
{tab.isLoading ? (
<LoadingState message="Creating terminal session..." size="md" />
) : tab.error ? (
<ErrorState
message="Failed to create terminal"
details={tab.error}
onRetry={() => createTerminal(target)}
/>
) : null}
</div>
)}
</div>
)}
</div>
))
)}

{/* Worker node terminal */}
<div className={`absolute inset-0 transition-opacity duration-300 ${activeTerminal === 'worker-node' ? 'opacity-100 z-10' : 'opacity-0 z-0'
}`}>
{terminals['worker-node'].id ? (
<Terminal
terminalId={terminals['worker-node'].id}
onConnectionChange={(connected) => handleConnectionChange('worker-node', connected)}
/>
) : (
<div className="flex flex-col justify-center items-center h-full bg-gray-800 text-white p-4">
{terminals['worker-node'].isLoading ? (
<LoadingState message="Creating terminal session..." size="md" />
) : terminals['worker-node'].error ? (
<ErrorState
message="Failed to create terminal"
details={terminals['worker-node'].error}
onRetry={() => createTerminal('worker-node')}
/>
) : (
<Button
variant="primary"
onClick={() => createTerminal('worker-node')}
>
Connect to Worker Node
</Button>
)}
</div>
)}
</div>
{activeTabs.length === 0 && (
<div className="flex flex-col justify-center items-center h-full bg-gray-800 text-white p-4">
<Button
variant="primary"
onClick={() => createTerminal(activeTarget)}
>
Connect to {activeTarget === 'control-plane' ? 'Control Plane' : 'Worker Node'}
</Button>
</div>
)}
</>
)}
</div>
</div>
);
};

export default React.memo(TerminalContainer);
export default React.memo(TerminalContainer);
Loading