시작하기
기본 개념
여러 장비의 파일을 한곳에서 확인하기
통합 파일 탐색은 여러 장비를 연결하고 각 장비의 파일과 폴더를 하나의 탐색 화면에서 확인할 수 있도록 구성하는 기능입니다.
사용자는 연결된 장비를 선택하고 폴더를 탐색해 필요한 파일을 확인할 수 있으며, 여러 장비의 파일을 하나의 업무 환경에서 이어서 활용할 수 있습니다.

예를 들어 업무 PC의 문서, 서버의 데이터, 스토리지의 결과 파일을 하나의 탐색 환경에서 확인하고 필요한 파일을 선택할 수 있습니다.
탐색 흐름
장비 선택부터 파일 확인과 전송까지 이어가기
통합 파일 탐색은 연결된 장비에서 필요한 파일을 찾고, 선택한 파일을 다음 업무 환경으로 전송하는 흐름으로 구성됩니다.
① 장비 선택
↓
② 폴더 탐색
↓
③ 파일 확인
↓
④ 필요한 파일 선택
↓
⑤ 전송 대상 선택
↓
⑥ 파일 전송
↓
⑦ 결과 확인

이 흐름을 통해 파일을 찾는 과정부터 필요한 업무 위치로 전송하고 결과를 확인하는 과정까지 이어갈 수 있습니다.
업무 변화
파일 탐색과 전송 과정을 하나의 업무 흐름으로 연결하기
여러 장비에서 파일을 사용하는 업무에서는 필요한 파일이 있는 위치를 확인하고, 해당 장비에서 파일을 찾은 뒤 다음 업무 환경으로 준비하는 과정이 이어집니다.
통합 파일 탐색을 활용하면 연결된 장비에서 필요한 파일을 찾고, 선택한 파일을 지정된 장비나 작업 공간으로 바로 전송할 수 있습니다.
| 구분 | 장비별 파일 관리 | 통합 파일 탐색 |
|---|---|---|
| 업무 시작 | 필요한 파일이 있는 장비 확인 | 연결된 장비 목록에서 바로 선택 |
| 파일 탐색 | 장비별 환경에서 파일과 폴더 확인 | 하나의 화면에서 장비와 파일 탐색 |
| 파일 준비 | 파일을 확인한 후 다음 업무 위치로 준비 | 선택한 파일을 대상 장비로 바로 전송 |
| 업무 연결 | 파일 준비 후 다음 업무 진행 | 전송 완료 후 다음 업무에 바로 활용 |
이렇게 파일 탐색과 전송을 하나의 흐름으로 구성하면 여러 업무 환경에 있는 파일을 필요한 위치에서 바로 활용할 수 있습니다.
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를 함께 넘겨 서버의 항목별 조회를 건너뛸 수 있습니다.
"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 | 파일별 실패 원인 |