DM Log

[RDP 모니터링 프로젝트 #6] 운영 서버 - React 영상 재생 UI 구축 본문

PJT/영상 파일 재생 PJT

[RDP 모니터링 프로젝트 #6] 운영 서버 - React 영상 재생 UI 구축

Dev. Dong 2025. 12. 6. 22:06

개요

운영 서버에서 제공하는 flask API를 활용하여 React 환경에서 영상 조회 UI를 구축

  1. 서버 목록 조회
  2. 날짜 목록 조회
  3. 영상 파일 목록 조회
  4. 영상 재생 기능

초기 환경 구성 (CRA)

  • React TypeScript 프로젝트 생성
npx create-react-app frontend --template typescript
cd frontend
npm install axios react-router-dom
  • 폴더 구조
src/
 ├── api/
 │     └── rdp.ts
 ├── components/
 │     ├── ServerSelect.tsx
 │     ├── DateSelect.tsx
 │     ├── FileList.tsx
 │     └── VideoPlayer.tsx
 ├── pages/
 │     └── RdpPage.tsx
 ├── App.tsx
 └── index.tsx

Conponent 및 Utill 환경 구성

  • Page 생성 - src/pages/RdpPage.tsx
import React, { useEffect, useState } from "react";
import { fetchServers, fetchDates, fetchFileList } from "../api/rdp";

import ServerSelect from "../components/ServerSelect";
import DateSelect from "../components/DateSelect";
import FileList from "../components/FileList";
import VideoPlayer from "../components/VideoPlayer";

export default function RdpPage() {
  const [servers, setServers] = useState<any[]>([]);
  const [selectedServer, setSelectedServer] = useState("");
  const [dates, setDates] = useState<string[]>([]);
  const [files, setFiles] = useState<any[]>([]);
  const [selectedFile, setSelectedFile] = useState<any>(null);

  useEffect(() => {
    fetchServers().then(setServers);
  }, []);

  const handleServer = (serverName: string) => {
    setSelectedServer(serverName);
    setSelectedFile(null);
    fetchDates(serverName).then(setDates);
  };

  const handleDate = (date: string) => {
    fetchFileList(selectedServer, date).then(setFiles);
  };

  return (
    <div style={{ padding: 20 }}>
      <h2>RDP 영상 재생</h2>

      <ServerSelect servers={servers} onSelect={handleServer} />
      <DateSelect dates={dates} onSelect={handleDate} />
      <FileList files={files} onSelect={setSelectedFile} />

      <VideoPlayer filepath={selectedFile?.filepath} />
    </div>
  );
}
  • 라우팅 기반 App.tsx 생성 - src/App.tsx
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import RdpPage from "./pages/RdpPage";

export default function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<div>메인 페이지</div>} />
        <Route path="/rdp" element={<RdpPage />} />
      </Routes>
    </Router>
  );
}
  • API통신 모듈 - src/api/rdp.ts
import axios from "axios";

export const API_BASE = "http://운영서버IP:5001/rdp";

export const fetchServers = async () => {
  const res = await axios.get(`${API_BASE}/servers`);
  return res.data;
};

export const fetchDates = async (server: string) => {
  const res = await axios.get(`${API_BASE}/dates`, {
    params: { server },
  });
  return res.data;
};

export const fetchFileList = async (server: string, date: string) => {
  const res = await axios.get(`${API_BASE}/list`, {
    params: { server, date },
  });
  return res.data;
};
  • 서버 선택 UI - src/components/ServerSelect.tsx
interface Props {
  servers: any[];
  onSelect: (server: string) => void;
}

export default function ServerSelect({ servers, onSelect }: Props) {
  return (
    <div>
      <h3>서버 선택</h3>
      <select onChange={(e) => onSelect(e.target.value)}>
        <option value="">선택하세요</option>
        {servers.map((s) => (
          <option key={s.name} value={s.name}>
            {s.name} ({s.ip})
          </option>
        ))}
      </select>
    </div>
  );
}
  • 날짜 선택 UI - src/components/DateSelect.tsx
interface Props {
  dates: string[];
  onSelect: (date: string) => void;
}

export default function DateSelect({ dates, onSelect }: Props) {
  return (
    <div>
      <h3>날짜 선택</h3>
      <select onChange={(e) => onSelect(e.target.value)}>
        <option value="">선택하세요</option>
        {dates.map((d) => (
          <option key={d}>{d}</option>
        ))}
      </select>
    </div>
  );
}
  • 영상 파일 목록 UI - src/components/FileList.tsx
interface Props {
  files: any[];
  onSelect: (file: any) => void;
}

export default function FileList({ files, onSelect }: Props) {
  return (
    <div>
      <h3>영상 파일 목록</h3>
      <ul>
        {files.map((f) => (
          <li
            key={f.id}
            onClick={() => onSelect(f)}
            style={{ cursor: "pointer" }}
          >
            {f.filename}
            {f.uploaded === 0 && (
              <span style={{ color: "red" }}> (전송 전)</span>
            )}
          </li>
        ))}
      </ul>
    </div>
  );
}
  • 영상 재생 UI - src/components/VideoPlayer.tsx
interface Props {
  filepath: string;
}

export default function VideoPlayer({ filepath }: Props) {
  if (!filepath) return null;

  return (
    <div>
      <h3>영상 재생</h3>
      <video
        src={`http://운영서버IP:5001/rdp/video/${filepath}`}
        controls
        style={{ width: "100%", maxWidth: "800px" }}
      />
    </div>
  );
}