통합 파일 탐색

IT 엔지니어개발자

시작하기

기본 개념

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

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

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

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

탐색 흐름

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

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

① 장비 선택

② 폴더 탐색

③ 파일 확인

④ 필요한 파일 선택

⑤ 전송 대상 선택

⑥ 파일 전송

⑦ 결과 확인

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

업무 변화

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

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

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

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

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

IT 엔지니어

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

장비 연결

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

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

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

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

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

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

접근 범위

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

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

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

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

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

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

파일 탐색

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

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

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

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

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

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

파일 전송

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

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

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

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

text
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[].typefile 또는 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가 아니라 sourceItemisDir: 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"])

폴더 단위로 보낼 때는 sourcePathssendAllFolder: 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를 함께 넘겨 서버의 항목별 조회를 건너뛸 수 있습니다.

"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파일별 실패 원인