| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- import { useEffect, useRef, useState } from "react";
- import type { FC } from "react";
- import styles from "./Terminal.module.css";
- interface LogEntry {
- timestamp: string;
- level: string;
- name: string;
- message: string;
- }
- interface TerminalProps {
- onClose: () => void;
- }
- export const Terminal: FC<TerminalProps> = ({ onClose }) => {
- const [logs, setLogs] = useState<LogEntry[]>([]);
- const [isConnected, setIsConnected] = useState(false);
- const wsRef = useRef<WebSocket | null>(null);
- const logsEndRef = useRef<HTMLDivElement>(null);
- const [autoScroll, setAutoScroll] = useState(true);
- useEffect(() => {
- // 连接WebSocket
- const ws = new WebSocket("ws://43.106.118.91:8000/api/logs/watch");
- wsRef.current = ws;
- ws.onopen = () => {
- setIsConnected(true);
- console.log("Terminal WebSocket connected");
- };
- ws.onmessage = (event) => {
- try {
- const logEntry: LogEntry = JSON.parse(event.data);
- setLogs((prev) => [...prev, logEntry]);
- } catch (error) {
- console.error("Failed to parse log entry:", error);
- }
- };
- ws.onerror = (error) => {
- console.error("Terminal WebSocket error:", error);
- setIsConnected(false);
- };
- ws.onclose = () => {
- setIsConnected(false);
- console.log("Terminal WebSocket disconnected");
- };
- return () => {
- ws.close();
- };
- }, []);
- useEffect(() => {
- if (autoScroll && logsEndRef.current) {
- logsEndRef.current.scrollIntoView({ behavior: "smooth" });
- }
- }, [logs, autoScroll]);
- const handleClear = () => {
- setLogs([]);
- };
- const getLevelColor = (level: string) => {
- switch (level) {
- case "ERROR":
- return styles.error;
- case "WARNING":
- return styles.warning;
- case "INFO":
- return styles.info;
- case "DEBUG":
- return styles.debug;
- default:
- return "";
- }
- };
- return (
- <div className={styles.terminal}>
- <div className={styles.header}>
- <div className={styles.title}>
- <span className={styles.icon}>▶</span>
- <span>控制台输出</span>
- <span className={`${styles.status} ${isConnected ? styles.connected : styles.disconnected}`}>
- {isConnected ? "●" : "○"}
- </span>
- </div>
- <div className={styles.actions}>
- <button
- className={styles.button}
- onClick={handleClear}
- title="清空日志"
- >
- 清空
- </button>
- <button
- className={`${styles.button} ${autoScroll ? styles.active : ""}`}
- onClick={() => setAutoScroll(!autoScroll)}
- title="自动滚动"
- >
- {autoScroll ? "🔒" : "🔓"}
- </button>
- <button
- className={styles.closeButton}
- onClick={onClose}
- title="关闭"
- >
- ×
- </button>
- </div>
- </div>
- <div className={styles.content}>
- {logs.length === 0 ? (
- <div className={styles.empty}>等待日志输出...</div>
- ) : (
- logs.map((log, index) => (
- <div
- key={index}
- className={`${styles.logEntry} ${getLevelColor(log.level)}`}
- >
- <span className={styles.timestamp}>
- {new Date(log.timestamp).toLocaleTimeString()}
- </span>
- <span className={styles.level}>[{log.level}]</span>
- <span className={styles.name}>{log.name}:</span>
- <span className={styles.message}>{log.message}</span>
- </div>
- ))
- )}
- <div ref={logsEndRef} />
- </div>
- </div>
- );
- };
|