mirror of
https://github.com/johndoe6345789/docker-swarm-termina.git
synced 2026-04-24 13:45:01 +00:00
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:
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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('');
|
||||
|
||||
35
frontend/lib/interfaces/container.ts
Normal file
35
frontend/lib/interfaces/container.ts
Normal 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;
|
||||
}
|
||||
7
frontend/lib/interfaces/dashboard.ts
Normal file
7
frontend/lib/interfaces/dashboard.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface DashboardHeaderProps {
|
||||
containerCount: number;
|
||||
isMobile: boolean;
|
||||
isRefreshing: boolean;
|
||||
onRefresh: () => void;
|
||||
onLogout: () => void;
|
||||
}
|
||||
61
frontend/lib/interfaces/terminal.ts
Normal file
61
frontend/lib/interfaces/terminal.ts
Normal 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;
|
||||
}
|
||||
41
frontend/lib/utils/terminal.tsx
Normal file
41
frontend/lib/utils/terminal.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user