diff --git a/gns3server/api/routes/compute/projects.py b/gns3server/api/routes/compute/projects.py index 25de86740..711e78416 100644 --- a/gns3server/api/routes/compute/projects.py +++ b/gns3server/api/routes/compute/projects.py @@ -192,14 +192,14 @@ async def get_compute_project_files(project: Project = Depends(dep_project)) -> return await project.list_files() -@router.get("/projects/{project_id}/nodes/{node_type}/{node_id}/files", response_model=List[schemas.ProjectFile]) +@router.get("/projects/{project_id}/nodes/{node_type}/{node_id}/files", response_model=List[schemas.NodeFile]) async def get_compute_node_files( node_type: str, node_id: str, project: Project = Depends(dep_project) -) -> List[schemas.ProjectFile]: +) -> List[schemas.NodeFile]: """ - Return files belonging to a specific node. + Return files belonging to a specific node with detailed metadata. """ node_path = f"project-files/{node_type}/{node_id}" diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py index 68b009275..0ee624ce4 100644 --- a/gns3server/api/routes/controller/nodes.py +++ b/gns3server/api/routes/controller/nodes.py @@ -522,10 +522,10 @@ async def delete_disk_image( await node.delete(f"/disk_image/{disk_name}") -@router.get("/{node_id}/files", response_model=List[schemas.ProjectFile], dependencies=[Depends(has_privilege("Node.Audit"))]) -async def list_node_files(node: Node = Depends(dep_node)) -> List[schemas.ProjectFile]: +@router.get("/{node_id}/files", response_model=List[schemas.NodeFile], dependencies=[Depends(has_privilege("Node.Audit"))]) +async def list_node_files(node: Node = Depends(dep_node)) -> List[schemas.NodeFile]: """ - List files in a node directory. + List files in a node directory with detailed metadata. Required privilege: Node.Audit """ diff --git a/gns3server/compute/project.py b/gns3server/compute/project.py index ca189a4a2..1539a7cd0 100644 --- a/gns3server/compute/project.py +++ b/gns3server/compute/project.py @@ -18,6 +18,7 @@ import os import shutil import asyncio import hashlib +import datetime from uuid import UUID, uuid4 from fastapi import HTTPException @@ -420,10 +421,10 @@ class Project: async def list_node_files(self, node_path: str): """ - List files in a specific node directory. + List files in a specific node directory with detailed metadata. :param node_path: Relative path to node directory (e.g., "project-files/qemu/node-id") - :returns: Array of files in the node directory + :returns: Array of files in the node directory with metadata """ node_full_path = os.path.normpath(os.path.join(self.path, node_path)) @@ -442,14 +443,37 @@ class Project: for filename in os.listdir(node_full_path): file_path = os.path.join(node_full_path, filename) if os.path.isfile(file_path) and not filename.endswith(".ghost"): - file_info = {"path": filename} try: - file_info["md5sum"] = await wait_run_in_executor( - self._hash_file, file_path + # Get file stat information + stat_info = await wait_run_in_executor(os.stat, file_path) + + # Get file extension + _, extension = os.path.splitext(filename) + extension = extension.lstrip('.') + + # Format timestamps as ISO 8601 + created_at = await wait_run_in_executor( + lambda: datetime.datetime.fromtimestamp(stat_info.st_ctime).isoformat() ) - except OSError: + modified_at = await wait_run_in_executor( + lambda: datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat() + ) + + # Get MD5 checksum + md5sum = await wait_run_in_executor(self._hash_file, file_path) + + file_info = { + "path": filename, + "md5sum": md5sum, + "size": stat_info.st_size, + "created_at": created_at, + "modified_at": modified_at, + "extension": extension + } + files.append(file_info) + except OSError as e: + log.warning(f"Error getting metadata for file '{filename}': {e}") continue - files.append(file_info) except OSError as e: log.error(f"Error listing node files: {e}") diff --git a/gns3server/schemas/controller/projects.py b/gns3server/schemas/controller/projects.py index 711eabb6b..bbb6164db 100644 --- a/gns3server/schemas/controller/projects.py +++ b/gns3server/schemas/controller/projects.py @@ -105,6 +105,19 @@ class ProjectFile(BaseModel): md5sum: str = Field(..., description="File checksum") +class NodeFile(BaseModel): + """ + Detailed file information for node files. + """ + + path: str = Field(..., description="File name") + md5sum: str = Field(..., description="File checksum") + size: int = Field(..., description="File size in bytes") + created_at: str = Field(..., description="File creation time (ISO 8601)") + modified_at: str = Field(..., description="File modification time (ISO 8601)") + extension: str = Field(..., description="File extension") + + class ProjectCompression(str, Enum): """ Supported project compression.