Organize interfaces and utilities into centralized folders

Move all TypeScript interfaces from component files to /lib/interfaces folder
Move terminal utility functions to /lib/utils folder
Update all component imports to use centralized interfaces and utilities
Fix JSX.Element type to React.ReactElement in terminal utils

This improves code organization and reduces duplication across components

https://claude.ai/code/session_G4kZm
This commit is contained in:
Claude
2026-01-30 23:16:45 +00:00
parent 748bf87699
commit e97b50a916
18 changed files with 160 additions and 154 deletions

View File

@@ -3,18 +3,13 @@
import React, { useState } from 'react';
import { Card, CardContent, Divider, Snackbar, Alert } from '@mui/material';
import { Container } from '@/lib/api';
import { ContainerCardProps } from '@/lib/interfaces/container';
import { useContainerActions } from '@/lib/hooks/useContainerActions';
import ContainerHeader from './ContainerCard/ContainerHeader';
import ContainerInfo from './ContainerCard/ContainerInfo';
import ContainerActions from './ContainerCard/ContainerActions';
import DeleteConfirmDialog from './ContainerCard/DeleteConfirmDialog';
interface ContainerCardProps {
container: Container;
onOpenShell: () => void;
onContainerUpdate?: () => void;
}
const borderColors = {
running: '#38b2ac',
stopped: '#718096',

View File

@@ -1,16 +1,7 @@
import React from 'react';
import { Box, Button, CircularProgress } from '@mui/material';
import { PlayArrow, Stop, Refresh, Delete, Terminal } from '@mui/icons-material';
interface ContainerActionsProps {
status: string;
isLoading: boolean;
onStart: () => void;
onStop: () => void;
onRestart: () => void;
onRemove: () => void;
onOpenShell: () => void;
}
import { ContainerActionsProps } from '@/lib/interfaces/container';
export default function ContainerActions({
status,

View File

@@ -1,12 +1,7 @@
import React from 'react';
import { Box, Typography, Chip } from '@mui/material';
import { PlayArrow, Inventory2 } from '@mui/icons-material';
interface ContainerHeaderProps {
name: string;
image: string;
status: string;
}
import { ContainerHeaderProps } from '@/lib/interfaces/container';
const statusColors = {
running: 'success',

View File

@@ -1,10 +1,6 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
interface ContainerInfoProps {
id: string;
uptime: string;
}
import { ContainerInfoProps } from '@/lib/interfaces/container';
export default function ContainerInfo({ id, uptime }: ContainerInfoProps) {
return (

View File

@@ -7,13 +7,7 @@ import {
DialogActions,
Button,
} from '@mui/material';
interface DeleteConfirmDialogProps {
open: boolean;
containerName: string;
onClose: () => void;
onConfirm: () => void;
}
import { DeleteConfirmDialogProps } from '@/lib/interfaces/container';
export default function DeleteConfirmDialog({
open,

View File

@@ -9,14 +9,7 @@ import {
CircularProgress,
} from '@mui/material';
import { Logout, Refresh, Inventory2 } from '@mui/icons-material';
interface DashboardHeaderProps {
containerCount: number;
isMobile: boolean;
isRefreshing: boolean;
onRefresh: () => void;
onLogout: () => void;
}
import { DashboardHeaderProps } from '@/lib/interfaces/dashboard';
export default function DashboardHeader({
containerCount,

View File

@@ -4,18 +4,12 @@ import React, { useState } from 'react';
import { Dialog, DialogContent, DialogActions, Button, useMediaQuery, useTheme } from '@mui/material';
import { useSimpleTerminal } from '@/lib/hooks/useSimpleTerminal';
import { useInteractiveTerminal } from '@/lib/hooks/useInteractiveTerminal';
import { TerminalModalProps } from '@/lib/interfaces/terminal';
import TerminalHeader from './TerminalModal/TerminalHeader';
import SimpleTerminal from './TerminalModal/SimpleTerminal';
import InteractiveTerminal from './TerminalModal/InteractiveTerminal';
import FallbackNotification from './TerminalModal/FallbackNotification';
interface TerminalModalProps {
open: boolean;
onClose: () => void;
containerName: string;
containerId: string;
}
export default function TerminalModal({
open,
onClose,

View File

@@ -1,26 +1,8 @@
import React from 'react';
import { Box, Typography, TextField, Button, IconButton } from '@mui/material';
import { Send } from '@mui/icons-material';
interface CommandInputProps {
command: string;
workdir: string;
isExecuting: boolean;
isMobile: boolean;
containerName: string;
onCommandChange: (value: string) => void;
onExecute: () => void;
onKeyPress: (e: React.KeyboardEvent) => void;
}
const formatPrompt = (containerName: string, workdir: string) => {
let displayDir = workdir;
if (workdir.length > 30) {
const parts = workdir.split('/');
displayDir = '.../' + parts[parts.length - 1];
}
return `root@${containerName}:${displayDir}#`;
};
import { CommandInputProps } from '@/lib/interfaces/terminal';
import { formatPrompt } from '@/lib/utils/terminal';
export default function CommandInput({
command,

View File

@@ -1,13 +1,7 @@
import React from 'react';
import { Snackbar, Alert, Typography, Button } from '@mui/material';
import { Warning } from '@mui/icons-material';
interface FallbackNotificationProps {
show: boolean;
reason: string;
onClose: () => void;
onRetry: () => void;
}
import { FallbackNotificationProps } from '@/lib/interfaces/terminal';
export default function FallbackNotification({
show,

View File

@@ -1,10 +1,7 @@
import React from 'react';
import { Box } from '@mui/material';
import '@xterm/xterm/css/xterm.css';
interface InteractiveTerminalProps {
terminalRef: React.RefObject<HTMLDivElement | null>;
}
import { InteractiveTerminalProps } from '@/lib/interfaces/terminal';
export default function InteractiveTerminal({ terminalRef }: InteractiveTerminalProps) {
return (

View File

@@ -1,21 +1,8 @@
import React from 'react';
import { OutputLine } from '@/lib/hooks/useSimpleTerminal';
import { SimpleTerminalProps } from '@/lib/interfaces/terminal';
import TerminalOutput from './TerminalOutput';
import CommandInput from './CommandInput';
interface SimpleTerminalProps {
output: OutputLine[];
command: string;
workdir: string;
isExecuting: boolean;
isMobile: boolean;
containerName: string;
outputRef: React.RefObject<HTMLDivElement | null>;
onCommandChange: (value: string) => void;
onExecute: () => void;
onKeyPress: (e: React.KeyboardEvent) => void;
}
export default function SimpleTerminal({
output,
command,

View File

@@ -9,14 +9,7 @@ import {
Tooltip,
} from '@mui/material';
import { Close, Terminal as TerminalIcon, Code, Warning } from '@mui/icons-material';
interface TerminalHeaderProps {
containerName: string;
mode: 'simple' | 'interactive';
interactiveFailed: boolean;
onModeChange: (event: React.MouseEvent<HTMLElement>, newMode: 'simple' | 'interactive' | null) => void;
onClose: () => void;
}
import { TerminalHeaderProps } from '@/lib/interfaces/terminal';
export default function TerminalHeader({
containerName,

View File

@@ -1,51 +1,7 @@
import React from 'react';
import { Box, Paper, Typography } from '@mui/material';
import { OutputLine } from '@/lib/hooks/useSimpleTerminal';
interface TerminalOutputProps {
output: OutputLine[];
containerName: string;
outputRef: React.RefObject<HTMLDivElement | null>;
}
const formatPrompt = (containerName: string, workdir: string) => {
let displayDir = workdir;
if (workdir.length > 30) {
const parts = workdir.split('/');
displayDir = '.../' + parts[parts.length - 1];
}
return `root@${containerName}:${displayDir}#`;
};
const highlightCommand = (line: OutputLine, containerName: string) => {
if (line.type === 'command') {
const prompt = formatPrompt(containerName, line.workdir || '/');
const parts = line.content.split(' ');
const cmd = parts[0];
const args = parts.slice(1).join(' ');
return (
<div style={{ marginBottom: '4px' }}>
<span style={{ color: '#8BE9FD', fontWeight: 'bold' }}>{prompt}</span>
{' '}
<span style={{ color: '#50FA7B', fontWeight: 'bold' }}>{cmd}</span>
{args && <span style={{ color: '#F8F8F2' }}> {args}</span>}
</div>
);
} else if (line.type === 'error') {
return (
<div style={{ color: '#FF5555', marginBottom: '2px' }}>
{line.content}
</div>
);
} else {
return (
<div style={{ color: '#F8F8F2', marginBottom: '2px', whiteSpace: 'pre-wrap' }}>
{line.content}
</div>
);
}
};
import { TerminalOutputProps } from '@/lib/interfaces/terminal';
import { highlightCommand } from '@/lib/utils/terminal';
export default function TerminalOutput({ output, containerName, outputRef }: TerminalOutputProps) {
return (

View File

@@ -1,11 +1,6 @@
import { useState, useRef, useEffect } from 'react';
import { apiClient } from '@/lib/api';
export interface OutputLine {
type: 'command' | 'output' | 'error';
content: string;
workdir?: string;
}
import { OutputLine } from '@/lib/interfaces/terminal';
export function useSimpleTerminal(containerId: string) {
const [command, setCommand] = useState('');

View File

@@ -0,0 +1,35 @@
import { Container } from '@/lib/api';
export interface ContainerCardProps {
container: Container;
onOpenShell: () => void;
onContainerUpdate?: () => void;
}
export interface ContainerHeaderProps {
name: string;
image: string;
status: string;
}
export interface ContainerInfoProps {
id: string;
uptime: string;
}
export interface ContainerActionsProps {
status: string;
isLoading: boolean;
onStart: () => void;
onStop: () => void;
onRestart: () => void;
onRemove: () => void;
onOpenShell: () => void;
}
export interface DeleteConfirmDialogProps {
open: boolean;
containerName: string;
onClose: () => void;
onConfirm: () => void;
}

View File

@@ -0,0 +1,7 @@
export interface DashboardHeaderProps {
containerCount: number;
isMobile: boolean;
isRefreshing: boolean;
onRefresh: () => void;
onLogout: () => void;
}

View File

@@ -0,0 +1,61 @@
export interface OutputLine {
type: 'command' | 'output' | 'error';
content: string;
workdir?: string;
}
export interface TerminalModalProps {
open: boolean;
onClose: () => void;
containerName: string;
containerId: string;
}
export interface TerminalHeaderProps {
containerName: string;
mode: 'simple' | 'interactive';
interactiveFailed: boolean;
onModeChange: (event: React.MouseEvent<HTMLElement>, newMode: 'simple' | 'interactive' | null) => void;
onClose: () => void;
}
export interface InteractiveTerminalProps {
terminalRef: React.RefObject<HTMLDivElement | null>;
}
export interface SimpleTerminalProps {
output: OutputLine[];
command: string;
workdir: string;
isExecuting: boolean;
isMobile: boolean;
containerName: string;
outputRef: React.RefObject<HTMLDivElement | null>;
onCommandChange: (value: string) => void;
onExecute: () => void;
onKeyPress: (e: React.KeyboardEvent) => void;
}
export interface TerminalOutputProps {
output: OutputLine[];
containerName: string;
outputRef: React.RefObject<HTMLDivElement | null>;
}
export interface CommandInputProps {
command: string;
workdir: string;
isExecuting: boolean;
isMobile: boolean;
containerName: string;
onCommandChange: (value: string) => void;
onExecute: () => void;
onKeyPress: (e: React.KeyboardEvent) => void;
}
export interface FallbackNotificationProps {
show: boolean;
reason: string;
onClose: () => void;
onRetry: () => void;
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { OutputLine } from '@/lib/interfaces/terminal';
export const formatPrompt = (containerName: string, workdir: string): string => {
let displayDir = workdir;
if (workdir.length > 30) {
const parts = workdir.split('/');
displayDir = '.../' + parts[parts.length - 1];
}
return `root@${containerName}:${displayDir}#`;
};
export const highlightCommand = (line: OutputLine, containerName: string): React.ReactElement => {
if (line.type === 'command') {
const prompt = formatPrompt(containerName, line.workdir || '/');
const parts = line.content.split(' ');
const cmd = parts[0];
const args = parts.slice(1).join(' ');
return (
<div style={{ marginBottom: '4px' }}>
<span style={{ color: '#8BE9FD', fontWeight: 'bold' }}>{prompt}</span>
{' '}
<span style={{ color: '#50FA7B', fontWeight: 'bold' }}>{cmd}</span>
{args && <span style={{ color: '#F8F8F2' }}> {args}</span>}
</div>
);
} else if (line.type === 'error') {
return (
<div style={{ color: '#FF5555', marginBottom: '2px' }}>
{line.content}
</div>
);
} else {
return (
<div style={{ color: '#F8F8F2', marginBottom: '2px', whiteSpace: 'pre-wrap' }}>
{line.content}
</div>
);
}
};