여러 장비의 파일을 한곳에서 찾고 직접 전송하기

INNORIX에서 통합 파일 탐색기을 구성하고 연결된 시스템 간 파일 작업을 일관되게 운영하는 방법을 알아보세요.

IT 엔지니어개발자

시작하기#

기본 개념#

여러 장비의 파일을 한곳에서 확인하기

통합 파일 탐색은 여러 장비를 연결하고 각 장비의 파일과 폴더를 하나의 탐색 화면에서 확인할 수 있도록 구성하는 기능입니다.

사용자는 연결된 장비를 선택하고 폴더를 탐색해 필요한 파일을 확인할 수 있으며, 여러 장비의 파일을 하나의 업무 환경에서 이어서 활용할 수 있습니다.

예를 들어 업무 PC의 문서, 서버의 데이터, 스토리지의 결과 파일을 하나의 탐색 환경에서 확인하고 필요한 파일을 선택할 수 있습니다.

탐색 흐름#

장비 선택부터 파일 확인과 전송까지 이어가기

통합 파일 탐색은 연결된 장비에서 필요한 파일을 찾고, 선택한 파일을 다음 업무 환경으로 전송하는 흐름으로 구성됩니다.

  1. 장비 선택
  2. 폴더 탐색
  3. 파일 확인
  4. 필요한 파일 선택
  5. 전송 대상 선택
  6. 파일 전송
  7. 결과 확인

이 흐름을 통해 파일을 찾는 과정부터 필요한 업무 위치로 전송하고 결과를 확인하는 과정까지 이어갈 수 있습니다.

업무 변화#

파일 탐색과 전송 과정을 하나의 업무 흐름으로 연결하기

여러 장비에서 파일을 사용하는 업무에서는 필요한 파일이 있는 위치를 확인하고, 해당 장비에서 파일을 찾은 뒤 다음 업무 환경으로 준비하는 과정이 이어집니다.

통합 파일 탐색을 활용하면 연결된 장비에서 필요한 파일을 찾고, 선택한 파일을 지정된 장비나 작업 공간으로 바로 전송할 수 있습니다.

구분 장비별 파일 관리 통합 파일 탐색
업무 시작 필요한 파일이 있는 장비 확인 연결된 장비 목록에서 바로 선택
파일 탐색 장비별 환경에서 파일과 폴더 확인 하나의 화면에서 장비와 파일 탐색
파일 준비 파일을 확인한 후 다음 업무 위치로 준비 선택한 파일을 대상 장비로 바로 전송
업무 연결 파일 준비 후 다음 업무 진행 전송 완료 후 다음 업무에 바로 활용

이렇게 파일 탐색과 전송을 하나의 흐름으로 구성하면 여러 업무 환경에 있는 파일을 필요한 위치에서 바로 활용할 수 있습니다.

IT 엔지니어#

여러 장비의 파일 탐색 환경을 구성하고 관리하기

장비 연결#

탐색할 장비를 연결하고 필요한 환경까지 확장하기

통합 파일 탐색을 구성하려면 먼저 파일을 확인할 PC, 서버, 스토리지 등 업무 환경의 장비를 연결합니다.

각 장비의 연결 정보를 설정하면 통합 탐색 화면에서 해당 시스템의 파일과 폴더를 확인할 수 있습니다.

업무 환경이 확장되면 새로운 서버나 스토리지를 같은 방식으로 추가할 수 있습니다. 추가한 장비도 연결 정보를 설정한 후 기존 탐색 환경에 포함할 수 있습니다.

장비 유형 활용 파일
업무 PC 개인 및 팀 업무 파일
Windows 서버 업무 문서와 운영 파일
Linux 서버 데이터와 처리 파일
스토리지 공유 파일과 결과 파일

장비를 추가하면 기존 파일 탐색 환경을 유지하면서 업무에 필요한 시스템까지 탐색 범위를 확장할 수 있습니다.

접근 범위#

사용자와 장비별로 확인할 파일과 폴더 범위 설정하기

장비를 연결한 후에는 사용자와 업무 역할에 따라 확인할 수 있는 장비와 파일 경로를 설정합니다.

사용자 또는 사용자 그룹별로 탐색할 장비를 지정하고, 각 장비에서 사용할 폴더 범위를 구성할 수 있습니다.

예를 들어 운영팀은 운영 서버의 지정된 폴더를 확인하고, 데이터팀은 분석 서버와 데이터 스토리지의 업무 경로를 확인하도록 구성할 수 있습니다.

사용자 그룹 탐색 장비 파일 범위
운영팀 운영 서버 운영 파일 경로
데이터팀 분석 서버 데이터 폴더
업무팀 공유 스토리지 업무 자료 폴더

이렇게 사용자별 탐색 범위를 구성하면 각 업무에서 필요한 장비와 파일을 중심으로 통합 파일 탐색 환경을 운영할 수 있습니다.

파일 탐색#

연결된 여러 장비에서 필요한 파일 찾기

장비와 접근 범위가 구성되면 사용자는 통합 탐색 화면에서 필요한 장비를 선택하고 폴더와 파일을 확인할 수 있습니다.

장비 목록에서 시스템을 선택하면 해당 장비의 폴더 구조와 파일 목록을 확인할 수 있으며, 파일 이름과 경로를 기준으로 필요한 파일을 찾을 수 있습니다.

파일 탐색 화면에서는 다음과 같은 정보를 함께 확인할 수 있습니다.

확인 항목 내용
Device 파일이 있는 장비
Path 현재 파일 경로
File Name 파일 이름
Size 파일 크기
Modified 마지막 변경 시간

필요한 파일을 찾으면 해당 항목을 선택해 바로 다음 전송 작업으로 이어갈 수 있습니다.

파일 전송#

선택한 파일을 필요한 장비와 작업 공간으로 전송하기

탐색 화면에서 필요한 파일을 선택한 후 전송할 장비와 대상 경로를 지정합니다.

선택한 파일은 현재 위치에서 지정된 대상 장비나 작업 공간으로 전송되며, 전송된 파일은 다음 업무에서 확인하고 활용할 수 있습니다.

전송 흐름은 다음과 같이 구성됩니다.

Device A
파일 탐색
파일 선택
대상 선택
Device B
작업 공간

하나의 탐색 화면에서 파일을 찾고 대상 위치까지 지정하면 파일 탐색과 전송을 하나의 업무 흐름으로 연결할 수 있습니다.

결과 확인#

전송 상태와 파일 처리 결과 확인하기

파일 전송이 실행되면 Runs에서 작업의 진행 상태와 처리 결과를 확인할 수 있습니다.

각 작업에서는 전송한 파일의 Source와 Target, 파일 수와 전송량, 진행률, 실행 시간, 현재 상태 등을 확인할 수 있습니다.

확인 항목 확인 내용
Source 파일을 선택한 장비와 경로
Target 파일을 전송할 장비와 작업 공간
Files 처리된 파일 수
Size 전체 전송량
Progress 전송 진행률
Status 현재 작업 상태
Time 실행 시간과 완료 시간

운영자는 실행 결과를 통해 장비 간 파일 전송 흐름과 처리 상태를 확인하고, 각 업무 환경에 파일이 반영된 결과를 관리할 수 있습니다.

개발자#

원격 장비의 파일 목록과 검색 결과를 API로 받아 장비 간 전송 실행하기

연동 준비#

공통 호출 코드와 상태 값 준비하기

import os
import requests

BASE_URL = os.getenv("INNORIX_BASE_URL", "https://app.innorix.com").rstrip("/")
TOKEN = os.environ["INNORIX_ACCESS_TOKEN"]
WORKSPACE_ID = os.getenv("INNORIX_WORKSPACE_ID")   # optional; falls back to the current workspace

STATUS_COMPLETE = 2
TERMINAL = {2, 4, 5, 9, 99}          # complete / error / cancelled / partial / failed
NOT_SUCCEEDED = {4, 5, 9, 99}


def api(method, path, body=None, params=None):
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {TOKEN}",
    }

    if WORKSPACE_ID:
        headers["x-workspace-id"] = WORKSPACE_ID

    response = requests.request(
        method, BASE_URL + path,
        headers=headers, json=body, params=params, timeout=30,
    )

    payload = response.json() if response.content else {}

    if not response.ok:
        raise RuntimeError(payload.get("message") or f"HTTP {response.status_code}")

    return payload.get("data")


def is_terminal(detail):
    return detail.get("isTerminal", detail.get("status") in TERMINAL)

전송 상태는 아래 값으로 판단합니다. 종료 상태는 다섯 개이고 성공에 해당하는 값은 완료(2)입니다.

상태 값 의미 종료
2 완료 예
4 오류 예
5 취소 예
9 부분 완료 예
99 실패 예
1, 6, 12, 13 시작, 전송 중, 동기화 중, 수신 중 아니오

폴더 조회#

장비별 파일 목록을 받아 화면에 표시하기

장비 식별자와 경로를 지정해 해당 폴더의 파일과 하위 폴더를 조회합니다. --device에 넣는 값은 경로 파라미터로 들어가므로 장비 ID여야 합니다.

result = api("GET", f"/api/devices/{device_id}/files", params={
    "path": "/data/reports",
    "page": 1,
    "size": 50,
    "sort": "name:asc",
    "type": "all",
})

for item in result["items"]:
    kind = "DIR " if item["isDir"] else "FILE"
    print(kind, item["name"], item["size"], item.get("modifiedAt"))
응답 항목 내용
items 파일과 폴더 목록
total, lastPage 전체 개수와 마지막 페이지
truncated 항목 수가 상한을 넘어 일부만 반환됐는지 여부
isDir 폴더 여부

장비 ID는 목록 조회로 확인합니다.

result = api("GET", "/api/devices", params={"page": 1, "size": 200})

for device in result["devices"]:
    print(device["deviceId"], device["name"], device.get("os"),
          device.get("ipAddress"))

장비 목록은 data.devices 배열로 반환됩니다.

하위 폴더까지 재귀 탐색해 파일 찾기

폴더 조회는 해당 폴더만 보여줍니다. 하위 폴더까지 찾으려면 검색을 시작하고 결과를 커서로 이어 받습니다.

RESTART_CODES = {"INVALID_CURSOR", "CURSOR_OUT_OF_SEQUENCE", "SEARCH_EXPIRED"}


def start_search(device_id, path, page_size=500):
    return api("POST", f"/api/devices/{device_id}/files/search",
               {"path": path, "pageSize": page_size})


def iter_search(device_id, path, max_pages=200):
    page = start_search(device_id, path)
    search_id = page.get("searchId")

    for _ in range(max_pages):
        for item in page.get("items") or []:
            yield search_id, item

        if not page.get("hasMore"):
            return

        try:
            page = api("GET", f"/api/devices/{device_id}/files/search",
                       params={"cursor": page["nextCursor"]})
        except RuntimeError:
            # restart the scan when the cursor expires or falls out of sequence
            page = start_search(device_id, path)
            search_id = page.get("searchId")

검색 요청은 기준 경로와 페이지 크기만 받습니다. 이름과 확장자 조건은 받은 결과에서 판단하므로, 기준 경로를 좁게 잡을수록 검색 범위가 줄어듭니다.

응답 항목 내용
searchId 검색 중단 요청에 넘기는 검색 식별자
items[].type file 또는 directory
hasMore, nextCursor 다음 페이지 존재 여부와 조회에 사용할 커서

검색 중단#

진행 중인 검색 멈추기

검색은 장비가 디스크를 실제로 탐색하는 작업입니다. 사용자가 화면을 벗어나면 중단해 장비 부하가 쌓이지 않게 합니다.

def cancel_search(device_id, search_id):
    api("POST", f"/api/devices/{device_id}/files/search/cancel",
        {"uuid": search_id})       # pass the searchId returned when the search started


search_id = None

try:
    for search_id, item in iter_search(device_id, "/data"):
        if item["type"] == "file" and item["name"].endswith(".csv"):
            print(item["path"], item["size"])
finally:
    if search_id:
        cancel_search(device_id, search_id)

새 검색을 시작할 때와 화면을 닫을 때 모두 같은 정리 코드를 실행합니다.

파일 전송#

선택한 파일을 다른 장비로 보내기

즉시 전송은 장비를 이름, IP, 식별자 중 어느 것으로도 지정할 수 있고, 경로는 평문 문자열로 넘깁니다. 탐색 API가 장비 ID만 받는 것과 다릅니다.

파일 목록을 보낼 때는 sourcePaths가 아니라 sourceItem에 isDir: false로 명시합니다. sourcePaths는 모든 경로를 폴더로 취급하므로, 파일을 넣으면 서버가 각 파일을 폴더로 스캔하려 해 느려지거나 시간이 초과됩니다.

def send_files(source, target, target_path, paths, action="numbering"):
    transfer = api("POST", "/api/transfers/manual", {
        "sourceDevice": source,
        "targetDevice": target,
        "targetPath": target_path,
        "sourceItem": [{"path": p, "isDir": False} for p in paths],
        "sendAllFolder": False,
        "transferOptions": {"target-action": action},
    })

    return transfer["monitorId"]


monitor_id = send_files("device-a", "device-b", "/data/collected",
                        ["/data/reports/2026-08.csv"])

폴더 단위로 보낼 때는 sourcePaths와 sendAllFolder: True를 씁니다.

api("POST", "/api/transfers/manual", {
    "sourceDevice": "device-a",
    "targetDevice": "device-b",
    "targetPath": "/data/collected",
    "sourcePaths": ["/data/reports"],
    "sendAllFolder": True,
    "transferOptions": {"target-action": "numbering"},
})

파일 크기를 이미 알고 있다면 fileSize를 함께 넘겨 서버의 항목별 조회를 건너뛸 수 있습니다.

json
"sourceItem": [
  { "path": "/data/a.csv", "isDir": false, "fileSize": 1200 }
]

결과 확인#

전송 상태와 파일별 처리 결과 확인하기

import time


def wait(monitor_id, timeout=1800, interval=3):
    deadline = time.time() + timeout

    while time.time() < deadline:
        detail = api("GET", f"/api/transfers/{monitor_id}")

        if is_terminal(detail):
            return detail

        time.sleep(interval)

    raise TimeoutError(monitor_id)


detail = wait(monitor_id)
succeeded = detail["status"] == STATUS_COMPLETE

전체 실패와 일부 실패는 화면에서 다르게 보여야 합니다. 파일 조회로 파일별 상태를 확인하고, 실패한 파일만 재전송합니다.

def failed_files(monitor_id):
    result = api("GET", f"/api/transfers/{monitor_id}/files", params={
        "state": "any", "size": 500,
    }) or {}

    return [r for r in (result.get("children") or [])
            if r.get("status") in NOT_SUCCEEDED]


def retry_failed(monitor_id):
    rows = failed_files(monitor_id)

    if not rows:
        return 0

    api("POST", f"/api/transfers/{monitor_id}/retry", {
        "filesRetry": [
            {"filePath": r["sourceFilePath"], "isDir": bool(r.get("isFolder"))}
            for r in rows
        ]
    })

    return len(rows)

재전송은 전송이 종료된 뒤에만 호출할 수 있습니다. 진행 중이면 거부되므로 상태를 먼저 확인합니다.

확인 항목 확인 내용
status 전송 상태 값
statusLabel 화면에 표시할 상태 문자열
percent 진행률
fileCount, totalSize 처리 파일 수와 전체 용량
children[].errorCode 파일별 실패 원인