@nicemeta/file-manager 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +45 -0
- package/dist/cli.cjs +353 -0
- package/dist/lib-static/file-manager/css/file-manager.css +1 -0
- package/dist/lib-static/file-manager/fonts/symbols.woff2 +0 -0
- package/dist/lib-static/file-manager/index.html +44 -0
- package/dist/openapi.json +1 -0
- package/dist/openapi.yaml +219 -0
- package/dist/static/swagger-ui.html +15 -0
- package/package.json +37 -0
- package/src/app.js +84 -0
- package/src/openapi.yaml +219 -0
- package/static/swagger-ui.html +15 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:'Material Symbols';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/symbols.woff2') format('woff2')}:root{--bg:#0d1117;--bg-card:#151c2c;--bg-card-hover:#1c2438;--bg-surface:#151c2c;--bg-hover:#1c2438;--bg-active:#243044;--text:#e0e6ed;--text-muted:#546178;--accent:#6c7ee1;--accent-hover:#8b9bf0;--icon-color:#6c7ee1;--delete-color:#df4d4d;--drop-ok:#4caf50;--drop-reject:#f44336;--radius:10px;--radius-sm:6px;--transition:0.18s ease;--zoom:1}*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html,body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:14px;line-height:1.5;height:100%;overflow:hidden}body{display:flex;flex-direction:column;padding:16px 24px;gap:8px}a{color:var(--accent);text-decoration:none;transition:color var(--transition)}a:hover{color:var(--accent-hover)}.breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:2px;padding:6px 8px;font-size:13px;min-height:32px;flex-shrink:0}.breadcrumb a{padding:2px 6px;border-radius:var(--radius-sm);transition:background var(--transition),color var(--transition);color:var(--text)}.breadcrumb a:hover{background:var(--bg-hover);color:var(--accent-hover)}#content-pane{flex:1;overflow:hidden;border-radius:var(--radius);border:2px solid transparent;transition:border-color 0.25s ease,box-shadow 0.25s ease;position:relative;display:flex;flex-direction:column}#content-pane.drop-zone-active{border-color:var(--drop-ok);box-shadow:inset 0 0 40px rgba(76,175,80,0.10)}#content-pane.drop-zone-reject{border-color:var(--drop-reject);box-shadow:inset 0 0 40px rgba(244,67,54,0.10)}.file-list{list-style:none;flex:1;overflow-y:auto;overflow-x:hidden;padding:4px 0;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));grid-auto-rows:min-content;gap:12px;align-content:start;transform:scale(var(--zoom));transform-origin:top left;width:calc(100% / var(--zoom));height:calc(100% / var(--zoom))}.file-list::-webkit-scrollbar{width:6px}.file-list::-webkit-scrollbar-track{background:transparent}.file-list::-webkit-scrollbar-thumb{background:#2a3548;border-radius:3px}.file-list::-webkit-scrollbar-thumb:hover{background:#3a4a60}.file-list .section-header{grid-column:1 / -1;font-size:13px;font-weight:600;color:var(--text);padding:8px 4px 0;border:none;background:none;min-height:auto;cursor:default}.file-list .section-header:hover{background:none}.file-list li{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:var(--radius);background:var(--bg-card);cursor:default;transition:background var(--transition);min-height:72px;overflow:hidden;position:relative}.file-list li:hover{background:var(--bg-card-hover)}.file-list li.folder-drop-hover{background:rgba(76,175,80,0.12);outline:2px solid var(--drop-ok);outline-offset:-2px}.mi{font-family:'Material Symbols';font-style:normal;font-weight:normal;text-rendering:optimizeLegibility;font-feature-settings:'liga';font-variation-settings:'FILL' 0,'GRAD' 0,'opsz' 48,'wght' 400;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.mi-filled{font-variation-settings:'FILL' 1,'GRAD' 0,'opsz' 48,'wght' 400}.card-icon{flex-shrink:0;width:44px;height:44px;font-size:44px;line-height:1;color:var(--icon-color)}.card-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}.card-info .name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text);font-size:13px;font-weight:500}.card-info .meta{font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.directory .card-link{display:flex;align-items:center;gap:14px;color:var(--text);flex:1;min-width:0}.directory .card-link:hover{color:var(--text)}.directory .card-link:hover .name{color:var(--accent-hover)}.file .card-content{display:flex;align-items:center;gap:14px;flex:1;min-width:0}.item-tool{display:none;align-items:center;gap:4px;flex-shrink:0;position:absolute;top:6px;right:6px}li:hover .item-tool{display:inline-flex}.item-tool a{font-size:18px;opacity:0.5;transition:opacity var(--transition),color var(--transition);padding:3px;line-height:1;border-radius:4px;color:var(--text-muted)}.item-tool a:hover{opacity:1;background:var(--bg-active)}.item-tool a.delete{color:var(--delete-color);opacity:0.6}.item-tool a.delete:hover{color:#ff6b6b;opacity:1}.item-tool a.download:hover{color:var(--accent-hover)}.item-tool a.view:hover{color:var(--text)}#zoom-control{display:flex;align-items:center;gap:8px;padding:6px 12px;flex-shrink:0;font-size:11px;color:var(--text-muted);user-select:none}#zoom-control label{white-space:nowrap}#zoom-control input[type="range"]{-webkit-appearance:none;appearance:none;width:100px;height:3px;background:#1c2438;border-radius:2px;outline:none;cursor:pointer}#zoom-control input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:12px;height:12px;background:var(--accent);border-radius:50%;cursor:pointer;transition:background var(--transition)}#zoom-control input[type="range"]::-webkit-slider-thumb:hover{background:var(--accent-hover)}#zoom-control input[type="range"]::-moz-range-thumb{width:12px;height:12px;background:var(--accent);border-radius:50%;border:none;cursor:pointer}#zoom-control span{min-width:32px;text-align:right}#mobile-upload{display:none}@media (pointer:coarse){#mobile-upload{display:inline-flex;align-items:center;justify-content:center;position:fixed;bottom:60px;right:20px;width:48px;height:48px;border-radius:50%;background:var(--accent);color:#fff;font-size:24px;border:none;cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,0.5);z-index:100}#mobile-upload .mi{font-size:24px}#mobile-upload:hover{background:var(--accent-hover)}}#drop-message{display:none;position:absolute;inset:0;background:rgba(13,17,23,0.88);color:var(--text);font-size:18px;align-items:center;justify-content:center;border-radius:var(--radius);pointer-events:none;z-index:10}#content-pane.drop-zone-active #drop-message,#content-pane.drop-zone-reject #drop-message{display:flex}#confirm-overlay{display:none;position:fixed;inset:0;z-index:200;background:rgba(0,0,0,0.6);align-items:center;justify-content:center}#confirm-overlay.visible{display:flex}#confirm-panel{background:var(--bg-card);border:1px solid #1c2438;border-radius:var(--radius);padding:24px 28px;min-width:320px;max-width:440px;box-shadow:0 12px 40px rgba(0,0,0,0.6);display:flex;flex-direction:column;gap:16px}#confirm-panel .confirm-title{font-size:15px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:8px}#confirm-panel .confirm-title .mi{color:var(--delete-color);font-size:22px}#confirm-panel .confirm-message{font-size:13px;color:var(--text-muted);word-break:break-all}#confirm-panel .confirm-buttons{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}#confirm-panel button{padding:6px 20px;border-radius:var(--radius-sm);border:1px solid #1c2438;font-size:13px;cursor:pointer;transition:background var(--transition),border-color var(--transition)}#confirm-panel .btn-cancel{background:var(--bg-hover);color:var(--text)}#confirm-panel .btn-cancel:hover{background:var(--bg-active)}#confirm-panel .btn-ok{background:var(--delete-color);color:#fff;border-color:var(--delete-color)}#confirm-panel .btn-ok:hover{background:#e85858;border-color:#e85858}
|
|
Binary file
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>File Manager</title>
|
|
8
|
+
<link rel="stylesheet" href="css/file-manager.css">
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
|
|
13
|
+
<p id="breadCrumb" class="breadcrumb"></p>
|
|
14
|
+
|
|
15
|
+
<div id="content-pane">
|
|
16
|
+
<div id="drop-message">Drop files to upload</div>
|
|
17
|
+
<ul id="file-list" class="file-list"></ul>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Mobile-only upload button (touch devices without drag-and-drop) -->
|
|
21
|
+
<button id="mobile-upload" title="Upload files"><i class="mi">upload</i></button>
|
|
22
|
+
<input type="file" id="mobile-file-input" multiple style="display:none">
|
|
23
|
+
|
|
24
|
+
<div id="zoom-control">
|
|
25
|
+
<label for="zoom-slider">Zoom</label>
|
|
26
|
+
<input type="range" id="zoom-slider" min="0.7" max="2" step="0.05" value="1">
|
|
27
|
+
<span id="zoom-value">100%</span>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Delete confirmation overlay -->
|
|
31
|
+
<div id="confirm-overlay">
|
|
32
|
+
<div id="confirm-panel">
|
|
33
|
+
<div class="confirm-title"><i class="mi">delete</i> Confirm Delete</div>
|
|
34
|
+
<div class="confirm-message" id="confirm-message"></div>
|
|
35
|
+
<div class="confirm-buttons">
|
|
36
|
+
<button class="btn-cancel" id="confirm-cancel">Cancel</button>
|
|
37
|
+
<button class="btn-ok" id="confirm-ok">Delete</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<script>"use strict";const API={list:"api/list?path=",download:"api/download?path=",zip:"api/zip?path=",view:"api/view?path=",delete:"api/delete?path=",upload:"api/upload?path="},fmUri="?path=",ZOOM_BASE=1.05,contentPane=document.getElementById("content-pane"),fileList=document.getElementById("file-list"),breadCrumb=document.getElementById("breadCrumb"),dropMessage=document.getElementById("drop-message"),zoomSlider=document.getElementById("zoom-slider"),zoomValue=document.getElementById("zoom-value"),mobileUpload=document.getElementById("mobile-upload"),mobileFileInput=document.getElementById("mobile-file-input"),confirmOverlay=document.getElementById("confirm-overlay"),confirmMessage=document.getElementById("confirm-message"),confirmOk=document.getElementById("confirm-ok"),confirmCancel=document.getElementById("confirm-cancel");let currentPath="",currentWritable=!1,dragCounter=0,scrollDebounce=null,pendingDeleteUrl=null;function encodePath(e){return encodeURIComponent(e).replace(/%2F/g,"/")}function formatSize(e){if(e==null)return"\u2014";if(e===0)return"0 B";const n=["B","KB","MB","GB","TB"],o=Math.floor(Math.log(e)/Math.log(1024)),r=e/Math.pow(1024,o);return(o===0?r:r.toFixed(2))+" "+n[o]}function timeAgo(e){if(!e)return"";const n=new Date(e);if(isNaN(n.getTime()))return"";const o=Math.floor((Date.now()-n.getTime())/1e3);if(o<60)return"just now";const r=[[31536e3,"year"],[2592e3,"month"],[86400,"day"],[3600,"hour"],[60,"minute"]];for(const[a,s]of r){const i=Math.floor(o/a);if(i>=1)return i===1?"a "+s+" ago":i+" "+s+"s ago"}return"just now"}function applyZoom(e){const n=e*ZOOM_BASE;document.documentElement.style.setProperty("--zoom",n),zoomSlider.value=e,zoomValue.textContent=Math.round(e*100)+"%",localStorage.setItem("fm-zoom",e)}(function(){const n=localStorage.getItem("fm-zoom"),o=parseFloat(zoomSlider.min),r=parseFloat(zoomSlider.max),a=n?Math.min(r,Math.max(o,parseFloat(n))):1;applyZoom(a)})(),zoomSlider.addEventListener("input",()=>applyZoom(parseFloat(zoomSlider.value)));function saveScroll(){currentPath!==void 0&&localStorage.setItem("fm-scroll-"+currentPath,fileList.scrollTop)}function restoreScroll(){const e=localStorage.getItem("fm-scroll-"+currentPath);e&&(fileList.scrollTop=parseInt(e,10))}fileList.addEventListener("scroll",()=>{clearTimeout(scrollDebounce),scrollDebounce=setTimeout(saveScroll,200)});function uploadFiles(e,n){if(!e||e.length===0)return;const o=new FormData;for(const a of e)o.append("file",a);const r=API.upload+encodePath(n);fetch(r,{method:"POST",body:o}).then(()=>loadDirectory(currentPath)).catch(a=>console.error("Upload failed:",a))}document.addEventListener("dragover",e=>e.preventDefault()),document.addEventListener("drop",e=>e.preventDefault()),contentPane.addEventListener("dragenter",e=>{e.preventDefault(),dragCounter++,dragCounter===1&&(contentPane.classList.add(currentWritable?"drop-zone-active":"drop-zone-reject"),dropMessage.textContent=currentWritable?"Drop files to upload":"This folder is read-only")}),contentPane.addEventListener("dragover",e=>{e.preventDefault(),e.dataTransfer.dropEffect=currentWritable?"copy":"none"}),contentPane.addEventListener("dragleave",e=>{e.preventDefault(),dragCounter--,dragCounter<=0&&(dragCounter=0,contentPane.classList.remove("drop-zone-active","drop-zone-reject"))}),contentPane.addEventListener("drop",e=>{e.preventDefault(),dragCounter=0,contentPane.classList.remove("drop-zone-active","drop-zone-reject"),currentWritable&&e.dataTransfer.files.length>0&&uploadFiles(e.dataTransfer.files,currentPath)}),mobileUpload.addEventListener("click",()=>mobileFileInput.click()),mobileFileInput.addEventListener("change",()=>{mobileFileInput.files.length>0&¤tWritable&&(uploadFiles(mobileFileInput.files,currentPath),mobileFileInput.value="")});function requestDelete(e,n){pendingDeleteUrl=e,confirmMessage.textContent="Are you sure you want to delete \u201C"+n+"\u201D?",confirmOverlay.classList.add("visible")}confirmOk.addEventListener("click",()=>{confirmOverlay.classList.remove("visible"),pendingDeleteUrl&&fetch(pendingDeleteUrl,{method:"DELETE"}).then(()=>{pendingDeleteUrl=null,loadDirectory(currentPath)}).catch(e=>{pendingDeleteUrl=null,console.error("Delete failed:",e)})}),confirmCancel.addEventListener("click",()=>{confirmOverlay.classList.remove("visible"),pendingDeleteUrl=null}),confirmOverlay.addEventListener("click",e=>{e.target===confirmOverlay&&(confirmOverlay.classList.remove("visible"),pendingDeleteUrl=null)}),document.addEventListener("keydown",e=>{confirmOverlay.classList.contains("visible")&&(e.key==="Escape"?confirmCancel.click():e.key==="Enter"&&confirmOk.click())});function createItemTools(e,n,o,r,a){const s=document.createElement("span");s.classList.add("item-tool");const i=document.createElement("a");if(i.classList.add("download","mi"),i.href=o?API.zip+e:API.download+e,i.title="Download",i.textContent="download",s.appendChild(i),!o){const t=document.createElement("a");t.classList.add("view","mi"),t.href=API.view+e,t.target="_blank",t.title="View",t.textContent="open_in_new",s.appendChild(t)}if(!a&&r){const t=document.createElement("a");t.classList.add("delete","mi"),t.href="javascript:void(0)",t.title="Delete",t.textContent="delete",t.addEventListener("click",d=>{d.preventDefault(),requestDelete(API.delete+e,n)}),s.appendChild(t)}return s}function createItem(e){const n=e.parent?encodePath(e.parent+(e.parent.endsWith("/")?"":"/")+e.name):e.name,o=document.createElement("li");o.classList.add(e.isDirectory?"directory":"file");const r=document.createElement("i");r.classList.add("mi","card-icon"),e.isDirectory?(r.classList.add("mi-filled"),r.textContent="folder"):r.textContent="description";const a=document.createElement("div");a.classList.add("card-info");const s=document.createElement("span");s.classList.add("name"),s.textContent=e.isDirectory?e.name.replace(/:[/]$/,""):e.name,a.appendChild(s);const i=document.createElement("span");if(i.classList.add("meta"),i.textContent=e.isDirectory?"\u2014":formatSize(e.length),a.appendChild(i),e.modified){const t=document.createElement("span");t.classList.add("meta"),t.textContent=timeAgo(e.modified),a.appendChild(t)}if(e.isDirectory){const t=document.createElement("a");t.classList.add("card-link"),t.href="javascript:void(0)",t.addEventListener("click",()=>navigateTo(n)),t.appendChild(r),t.appendChild(a),o.appendChild(t)}else{const t=document.createElement("span");t.classList.add("card-content"),t.appendChild(r),t.appendChild(a),o.appendChild(t)}if(o.appendChild(createItemTools(n,e.name,e.isDirectory,e.isWritable,e.hasChildren)),e.isDirectory){let t=0;o.addEventListener("dragenter",d=>{d.preventDefault(),d.stopPropagation(),t++,t===1&&e.isWritable&&o.classList.add("folder-drop-hover")}),o.addEventListener("dragover",d=>{d.preventDefault(),d.stopPropagation(),d.dataTransfer.dropEffect=e.isWritable?"copy":"none"}),o.addEventListener("dragleave",d=>{d.preventDefault(),d.stopPropagation(),t--,t<=0&&(t=0,o.classList.remove("folder-drop-hover"))}),o.addEventListener("drop",d=>{if(d.preventDefault(),d.stopPropagation(),t=0,o.classList.remove("folder-drop-hover"),dragCounter=0,contentPane.classList.remove("drop-zone-active","drop-zone-reject"),e.isWritable&&d.dataTransfer.files.length>0){const c=e.parent?e.parent+(e.parent.endsWith("/")?"":"/")+e.name:e.name;uploadFiles(d.dataTransfer.files,c)}})}return o}function createSectionHeader(e){const n=document.createElement("li");return n.classList.add("section-header"),n.textContent=e,n}function createBreadCrumb(e,n){const o=document.createElement("span");return e&&(e.slice().reverse().forEach(r=>{const a=document.createElement("a");a.href="javascript:void(0)",a.addEventListener("click",()=>navigateTo(r.path)),a.textContent=r.name,o.appendChild(a),o.appendChild(document.createTextNode(" / "))}),o.appendChild(document.createTextNode(n))),o}function loadDirectory(e){fetch(API.list+encodePath(e)).then(n=>n.json()).then(n=>{currentWritable=n.isWritable,contentPane.dataset.writable=n.isWritable,breadCrumb.innerHTML="",breadCrumb.style.display=n.path==="/"?"none":"",breadCrumb.appendChild(createBreadCrumb(n.parentInfos,n.name)),fileList.innerHTML="";const o=!1;if(n.files&&n.files.length>0){const r=[],a=[];n.files.forEach(s=>{(!s.name.startsWith(".")||o)&&(s.isDirectory?r:a).push(s)}),r.length>0&&(fileList.appendChild(createSectionHeader("Folders")),r.forEach(s=>fileList.appendChild(createItem(s)))),a.length>0&&(fileList.appendChild(createSectionHeader("Files")),a.forEach(s=>fileList.appendChild(createItem(s))))}requestAnimationFrame(restoreScroll)})}function navigateTo(e){saveScroll(),currentPath=e||"",window.history.pushState(currentPath,"",fmUri+encodePath(currentPath)),loadDirectory(currentPath)}window.addEventListener("popstate",e=>{saveScroll(),currentPath=e.state!=null?e.state:"",loadDirectory(currentPath)});const params=new URLSearchParams(window.location.search),initPath=params.has("path")?params.get("path"):"";currentPath=initPath,window.history.replaceState(initPath,"",fmUri+encodePath(initPath)),loadDirectory(initPath);</script>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"openapi":"3.0.3","info":{"title":"File Manager API","description":"File system browsing, upload, download, and management","version":"0.1.0"},"servers":[{"url":"/"}],"tags":[{"name":"File Manager","description":"File system browsing, upload, download, and management"}],"paths":{"/file-manager/api/list":{"get":{"tags":["File Manager"],"summary":"List directory contents","description":"Returns the contents of the specified directory including file metadata","parameters":[{"name":"path","in":"query","description":"Absolute path of the directory to list","schema":{"type":"string","default":"/"}}],"responses":{"200":{"description":"Directory listing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryInfo"}}}}}}},"/file-manager/api/download":{"get":{"tags":["File Manager"],"summary":"Download a file","description":"Downloads the specified file as an attachment","parameters":[{"name":"path","in":"query","required":true,"description":"Absolute path of the file to download","schema":{"type":"string"}}],"responses":{"200":{"description":"File content","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Path is not a file"}}}},"/file-manager/api/zip":{"get":{"tags":["File Manager"],"summary":"Download as ZIP","description":"Downloads the specified file or directory as a ZIP archive","parameters":[{"name":"path","in":"query","required":true,"description":"Absolute path of the file or directory to zip","schema":{"type":"string"}}],"responses":{"200":{"description":"ZIP archive","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}}}}},"/file-manager/api/view":{"get":{"tags":["File Manager"],"summary":"View a file inline","description":"Returns the file content for inline viewing in the browser","parameters":[{"name":"path","in":"query","required":true,"description":"Absolute path of the file to view","schema":{"type":"string"}}],"responses":{"200":{"description":"File content (inline)","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Path is not a file"}}}},"/file-manager/api/delete":{"delete":{"tags":["File Manager"],"summary":"Delete a file or directory","description":"Recursively deletes the specified file or directory","parameters":[{"name":"path","in":"query","required":true,"description":"Absolute path of the file or directory to delete","schema":{"type":"string"}}],"responses":{"204":{"description":"Successfully deleted"}}}},"/file-manager/api/upload":{"post":{"tags":["File Manager"],"summary":"Upload files to a directory","description":"Uploads one or more files to the specified directory","parameters":[{"name":"path","in":"query","required":true,"description":"Absolute path of the target directory","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"Files uploaded successfully"}}}},"/file-manager/api/mode":{"put":{"tags":["File Manager"],"summary":"Change file permissions","description":"Changes read/write/execute permissions. Mode format: +rwx or -rwx","parameters":[{"name":"path","in":"query","required":true,"description":"Absolute path of the file","schema":{"type":"string"}},{"name":"mode","in":"query","required":true,"description":"Permission mode (e.g. +rw, -x)","schema":{"type":"string"}}],"responses":{"200":{"description":"Permissions changed"}}}}},"components":{"schemas":{"DirectoryInfo":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string"},"isWritable":{"type":"boolean"},"parentInfos":{"type":"array","items":{"$ref":"#/components/schemas/ParentInfo"}},"files":{"type":"array","items":{"$ref":"#/components/schemas/FileInfo"}}}},"ParentInfo":{"type":"object","properties":{"path":{"type":"string"},"name":{"type":"string"}}},"FileInfo":{"type":"object","properties":{"parent":{"type":"string"},"name":{"type":"string"},"length":{"type":"integer","format":"int64"},"isDirectory":{"type":"boolean"},"isReadable":{"type":"boolean"},"isWritable":{"type":"boolean"},"isExecutable":{"type":"boolean"},"hasChildren":{"type":"boolean"},"modified":{"type":"string","description":"ISO local date-time (no timezone)"},"created":{"type":"string","description":"ISO local date-time (no timezone)"}}}}}}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
title: File Manager API
|
|
4
|
+
description: File system browsing, upload, download, and management
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
servers:
|
|
7
|
+
- url: /
|
|
8
|
+
tags:
|
|
9
|
+
- name: File Manager
|
|
10
|
+
description: File system browsing, upload, download, and management
|
|
11
|
+
paths:
|
|
12
|
+
/file-manager/api/list:
|
|
13
|
+
get:
|
|
14
|
+
tags: [File Manager]
|
|
15
|
+
summary: List directory contents
|
|
16
|
+
description: Returns the contents of the specified directory including file metadata
|
|
17
|
+
parameters:
|
|
18
|
+
- name: path
|
|
19
|
+
in: query
|
|
20
|
+
description: Absolute path of the directory to list
|
|
21
|
+
schema:
|
|
22
|
+
type: string
|
|
23
|
+
default: /
|
|
24
|
+
responses:
|
|
25
|
+
'200':
|
|
26
|
+
description: Directory listing
|
|
27
|
+
content:
|
|
28
|
+
application/json:
|
|
29
|
+
schema:
|
|
30
|
+
$ref: '#/components/schemas/DirectoryInfo'
|
|
31
|
+
|
|
32
|
+
/file-manager/api/download:
|
|
33
|
+
get:
|
|
34
|
+
tags: [File Manager]
|
|
35
|
+
summary: Download a file
|
|
36
|
+
description: Downloads the specified file as an attachment
|
|
37
|
+
parameters:
|
|
38
|
+
- name: path
|
|
39
|
+
in: query
|
|
40
|
+
required: true
|
|
41
|
+
description: Absolute path of the file to download
|
|
42
|
+
schema:
|
|
43
|
+
type: string
|
|
44
|
+
responses:
|
|
45
|
+
'200':
|
|
46
|
+
description: File content
|
|
47
|
+
content:
|
|
48
|
+
application/octet-stream:
|
|
49
|
+
schema:
|
|
50
|
+
type: string
|
|
51
|
+
format: binary
|
|
52
|
+
'400':
|
|
53
|
+
description: Path is not a file
|
|
54
|
+
|
|
55
|
+
/file-manager/api/zip:
|
|
56
|
+
get:
|
|
57
|
+
tags: [File Manager]
|
|
58
|
+
summary: Download as ZIP
|
|
59
|
+
description: Downloads the specified file or directory as a ZIP archive
|
|
60
|
+
parameters:
|
|
61
|
+
- name: path
|
|
62
|
+
in: query
|
|
63
|
+
required: true
|
|
64
|
+
description: Absolute path of the file or directory to zip
|
|
65
|
+
schema:
|
|
66
|
+
type: string
|
|
67
|
+
responses:
|
|
68
|
+
'200':
|
|
69
|
+
description: ZIP archive
|
|
70
|
+
content:
|
|
71
|
+
application/zip:
|
|
72
|
+
schema:
|
|
73
|
+
type: string
|
|
74
|
+
format: binary
|
|
75
|
+
|
|
76
|
+
/file-manager/api/view:
|
|
77
|
+
get:
|
|
78
|
+
tags: [File Manager]
|
|
79
|
+
summary: View a file inline
|
|
80
|
+
description: Returns the file content for inline viewing in the browser
|
|
81
|
+
parameters:
|
|
82
|
+
- name: path
|
|
83
|
+
in: query
|
|
84
|
+
required: true
|
|
85
|
+
description: Absolute path of the file to view
|
|
86
|
+
schema:
|
|
87
|
+
type: string
|
|
88
|
+
responses:
|
|
89
|
+
'200':
|
|
90
|
+
description: File content (inline)
|
|
91
|
+
content:
|
|
92
|
+
application/octet-stream:
|
|
93
|
+
schema:
|
|
94
|
+
type: string
|
|
95
|
+
format: binary
|
|
96
|
+
'400':
|
|
97
|
+
description: Path is not a file
|
|
98
|
+
|
|
99
|
+
/file-manager/api/delete:
|
|
100
|
+
delete:
|
|
101
|
+
tags: [File Manager]
|
|
102
|
+
summary: Delete a file or directory
|
|
103
|
+
description: Recursively deletes the specified file or directory
|
|
104
|
+
parameters:
|
|
105
|
+
- name: path
|
|
106
|
+
in: query
|
|
107
|
+
required: true
|
|
108
|
+
description: Absolute path of the file or directory to delete
|
|
109
|
+
schema:
|
|
110
|
+
type: string
|
|
111
|
+
responses:
|
|
112
|
+
'204':
|
|
113
|
+
description: Successfully deleted
|
|
114
|
+
|
|
115
|
+
/file-manager/api/upload:
|
|
116
|
+
post:
|
|
117
|
+
tags: [File Manager]
|
|
118
|
+
summary: Upload files to a directory
|
|
119
|
+
description: Uploads one or more files to the specified directory
|
|
120
|
+
parameters:
|
|
121
|
+
- name: path
|
|
122
|
+
in: query
|
|
123
|
+
required: true
|
|
124
|
+
description: Absolute path of the target directory
|
|
125
|
+
schema:
|
|
126
|
+
type: string
|
|
127
|
+
requestBody:
|
|
128
|
+
required: true
|
|
129
|
+
content:
|
|
130
|
+
multipart/form-data:
|
|
131
|
+
schema:
|
|
132
|
+
type: object
|
|
133
|
+
properties:
|
|
134
|
+
file:
|
|
135
|
+
type: array
|
|
136
|
+
items:
|
|
137
|
+
type: string
|
|
138
|
+
format: binary
|
|
139
|
+
responses:
|
|
140
|
+
'200':
|
|
141
|
+
description: Files uploaded successfully
|
|
142
|
+
|
|
143
|
+
/file-manager/api/mode:
|
|
144
|
+
put:
|
|
145
|
+
tags: [File Manager]
|
|
146
|
+
summary: Change file permissions
|
|
147
|
+
description: "Changes read/write/execute permissions. Mode format: +rwx or -rwx"
|
|
148
|
+
parameters:
|
|
149
|
+
- name: path
|
|
150
|
+
in: query
|
|
151
|
+
required: true
|
|
152
|
+
description: Absolute path of the file
|
|
153
|
+
schema:
|
|
154
|
+
type: string
|
|
155
|
+
- name: mode
|
|
156
|
+
in: query
|
|
157
|
+
required: true
|
|
158
|
+
description: "Permission mode (e.g. +rw, -x)"
|
|
159
|
+
schema:
|
|
160
|
+
type: string
|
|
161
|
+
responses:
|
|
162
|
+
'200':
|
|
163
|
+
description: Permissions changed
|
|
164
|
+
|
|
165
|
+
components:
|
|
166
|
+
schemas:
|
|
167
|
+
DirectoryInfo:
|
|
168
|
+
type: object
|
|
169
|
+
properties:
|
|
170
|
+
name:
|
|
171
|
+
type: string
|
|
172
|
+
path:
|
|
173
|
+
type: string
|
|
174
|
+
isWritable:
|
|
175
|
+
type: boolean
|
|
176
|
+
parentInfos:
|
|
177
|
+
type: array
|
|
178
|
+
items:
|
|
179
|
+
$ref: '#/components/schemas/ParentInfo'
|
|
180
|
+
files:
|
|
181
|
+
type: array
|
|
182
|
+
items:
|
|
183
|
+
$ref: '#/components/schemas/FileInfo'
|
|
184
|
+
|
|
185
|
+
ParentInfo:
|
|
186
|
+
type: object
|
|
187
|
+
properties:
|
|
188
|
+
path:
|
|
189
|
+
type: string
|
|
190
|
+
name:
|
|
191
|
+
type: string
|
|
192
|
+
|
|
193
|
+
FileInfo:
|
|
194
|
+
type: object
|
|
195
|
+
properties:
|
|
196
|
+
parent:
|
|
197
|
+
type: string
|
|
198
|
+
name:
|
|
199
|
+
type: string
|
|
200
|
+
length:
|
|
201
|
+
type: integer
|
|
202
|
+
format: int64
|
|
203
|
+
isDirectory:
|
|
204
|
+
type: boolean
|
|
205
|
+
isReadable:
|
|
206
|
+
type: boolean
|
|
207
|
+
isWritable:
|
|
208
|
+
type: boolean
|
|
209
|
+
isExecutable:
|
|
210
|
+
type: boolean
|
|
211
|
+
hasChildren:
|
|
212
|
+
type: boolean
|
|
213
|
+
modified:
|
|
214
|
+
type: string
|
|
215
|
+
description: ISO local date-time (no timezone)
|
|
216
|
+
created:
|
|
217
|
+
type: string
|
|
218
|
+
description: ISO local date-time (no timezone)
|
|
219
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>File Manager API</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<script
|
|
10
|
+
id="api-reference"
|
|
11
|
+
data-url="/file-manager/api-docs/openapi.json"
|
|
12
|
+
data-configuration='{"darkMode":true,"hideDownloadButton":true,"hiddenClients":["ruby","php","python","csharp","clojure","c","objc","ocaml","r","swift","kotlin","powershell"]}'></script>
|
|
13
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nicemeta/file-manager",
|
|
3
|
+
"version": "0.6.4",
|
|
4
|
+
"description": "File manager web application",
|
|
5
|
+
"main": "src/app.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"file-manager": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"static/",
|
|
13
|
+
"dist/"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"file-manager",
|
|
17
|
+
"file-browser",
|
|
18
|
+
"upload",
|
|
19
|
+
"download",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "node build.js",
|
|
25
|
+
"clean": "rm -rf dist",
|
|
26
|
+
"start": "node bin/cli.js",
|
|
27
|
+
"start:prod": "node dist/cli.cjs"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@nicemeta/file-manager-lib": "^0.6.4",
|
|
31
|
+
"express": "^5.1.0",
|
|
32
|
+
"js-yaml": "^4.1.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/app.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const { createRouter } = require('@nicemeta/file-manager-lib');
|
|
7
|
+
|
|
8
|
+
// ── OpenAPI spec ──
|
|
9
|
+
// In dev: loaded from openapi.yaml via js-yaml (optional dev dependency)
|
|
10
|
+
// In prod bundle: loaded from openapi.json (pre-converted by build.js)
|
|
11
|
+
function loadSpec() {
|
|
12
|
+
const jsonPath = path.join(__dirname, 'openapi.json');
|
|
13
|
+
if (fs.existsSync(jsonPath)) {
|
|
14
|
+
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
// Dev fallback: parse YAML
|
|
17
|
+
const yamlPath = path.join(__dirname, 'openapi.yaml');
|
|
18
|
+
const yaml = require('js-yaml');
|
|
19
|
+
return yaml.load(fs.readFileSync(yamlPath, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create the full Express application.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} [options]
|
|
26
|
+
* @param {string} [options.root='/'] — root directory for file operations
|
|
27
|
+
* @returns {import('express').Application}
|
|
28
|
+
*/
|
|
29
|
+
function createApp(options = {}) {
|
|
30
|
+
const app = express();
|
|
31
|
+
|
|
32
|
+
// Determine where static UI assets are:
|
|
33
|
+
// In dev: lib's own static/file-manager/ (resolved by the lib via __dirname)
|
|
34
|
+
// In prod bundle: dist/lib-static/file-manager/ (next to the bundle)
|
|
35
|
+
const libStaticDir = path.join(__dirname, 'lib-static', 'file-manager');
|
|
36
|
+
const routerOptions = { ...options };
|
|
37
|
+
if (fs.existsSync(libStaticDir)) {
|
|
38
|
+
routerOptions.staticDir = libStaticDir;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Mount file-manager library router (API + static UI)
|
|
42
|
+
app.use(createRouter(routerOptions));
|
|
43
|
+
|
|
44
|
+
// Root redirect
|
|
45
|
+
app.get('/', (_req, res) => {
|
|
46
|
+
res.redirect('/file-manager/index.html');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ── OpenAPI spec (JSON) ──
|
|
50
|
+
const specDoc = loadSpec();
|
|
51
|
+
|
|
52
|
+
app.get('/file-manager/api-docs/openapi.json', (_req, res) => {
|
|
53
|
+
res.json(specDoc);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ── Swagger UI (CDN-loaded, dark theme inlined) ──
|
|
57
|
+
// Try multiple locations: static/ next to __dirname, or ../static/ for dev
|
|
58
|
+
const candidates = [
|
|
59
|
+
path.join(__dirname, 'static', 'swagger-ui.html'),
|
|
60
|
+
path.join(__dirname, '..', 'static', 'swagger-ui.html'),
|
|
61
|
+
];
|
|
62
|
+
const swaggerHtmlPath = candidates.find(p => fs.existsSync(p));
|
|
63
|
+
const swaggerHtml = swaggerHtmlPath
|
|
64
|
+
? fs.readFileSync(swaggerHtmlPath, 'utf8')
|
|
65
|
+
: null;
|
|
66
|
+
|
|
67
|
+
app.get('/file-manager/swagger-ui/index.html', (_req, res) => {
|
|
68
|
+
if (swaggerHtml) {
|
|
69
|
+
res.type('html').send(swaggerHtml);
|
|
70
|
+
} else {
|
|
71
|
+
res.status(404).send('swagger-ui.html not found');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Redirect bare /file-manager/swagger-ui to /file-manager/swagger-ui/index.html
|
|
76
|
+
app.get('/file-manager/swagger-ui', (_req, res) => {
|
|
77
|
+
res.redirect('/file-manager/swagger-ui/index.html');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return app;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { createApp };
|
|
84
|
+
|
package/src/openapi.yaml
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
title: File Manager API
|
|
4
|
+
description: File system browsing, upload, download, and management
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
servers:
|
|
7
|
+
- url: /
|
|
8
|
+
tags:
|
|
9
|
+
- name: File Manager
|
|
10
|
+
description: File system browsing, upload, download, and management
|
|
11
|
+
paths:
|
|
12
|
+
/file-manager/api/list:
|
|
13
|
+
get:
|
|
14
|
+
tags: [File Manager]
|
|
15
|
+
summary: List directory contents
|
|
16
|
+
description: Returns the contents of the specified directory including file metadata
|
|
17
|
+
parameters:
|
|
18
|
+
- name: path
|
|
19
|
+
in: query
|
|
20
|
+
description: Absolute path of the directory to list
|
|
21
|
+
schema:
|
|
22
|
+
type: string
|
|
23
|
+
default: /
|
|
24
|
+
responses:
|
|
25
|
+
'200':
|
|
26
|
+
description: Directory listing
|
|
27
|
+
content:
|
|
28
|
+
application/json:
|
|
29
|
+
schema:
|
|
30
|
+
$ref: '#/components/schemas/DirectoryInfo'
|
|
31
|
+
|
|
32
|
+
/file-manager/api/download:
|
|
33
|
+
get:
|
|
34
|
+
tags: [File Manager]
|
|
35
|
+
summary: Download a file
|
|
36
|
+
description: Downloads the specified file as an attachment
|
|
37
|
+
parameters:
|
|
38
|
+
- name: path
|
|
39
|
+
in: query
|
|
40
|
+
required: true
|
|
41
|
+
description: Absolute path of the file to download
|
|
42
|
+
schema:
|
|
43
|
+
type: string
|
|
44
|
+
responses:
|
|
45
|
+
'200':
|
|
46
|
+
description: File content
|
|
47
|
+
content:
|
|
48
|
+
application/octet-stream:
|
|
49
|
+
schema:
|
|
50
|
+
type: string
|
|
51
|
+
format: binary
|
|
52
|
+
'400':
|
|
53
|
+
description: Path is not a file
|
|
54
|
+
|
|
55
|
+
/file-manager/api/zip:
|
|
56
|
+
get:
|
|
57
|
+
tags: [File Manager]
|
|
58
|
+
summary: Download as ZIP
|
|
59
|
+
description: Downloads the specified file or directory as a ZIP archive
|
|
60
|
+
parameters:
|
|
61
|
+
- name: path
|
|
62
|
+
in: query
|
|
63
|
+
required: true
|
|
64
|
+
description: Absolute path of the file or directory to zip
|
|
65
|
+
schema:
|
|
66
|
+
type: string
|
|
67
|
+
responses:
|
|
68
|
+
'200':
|
|
69
|
+
description: ZIP archive
|
|
70
|
+
content:
|
|
71
|
+
application/zip:
|
|
72
|
+
schema:
|
|
73
|
+
type: string
|
|
74
|
+
format: binary
|
|
75
|
+
|
|
76
|
+
/file-manager/api/view:
|
|
77
|
+
get:
|
|
78
|
+
tags: [File Manager]
|
|
79
|
+
summary: View a file inline
|
|
80
|
+
description: Returns the file content for inline viewing in the browser
|
|
81
|
+
parameters:
|
|
82
|
+
- name: path
|
|
83
|
+
in: query
|
|
84
|
+
required: true
|
|
85
|
+
description: Absolute path of the file to view
|
|
86
|
+
schema:
|
|
87
|
+
type: string
|
|
88
|
+
responses:
|
|
89
|
+
'200':
|
|
90
|
+
description: File content (inline)
|
|
91
|
+
content:
|
|
92
|
+
application/octet-stream:
|
|
93
|
+
schema:
|
|
94
|
+
type: string
|
|
95
|
+
format: binary
|
|
96
|
+
'400':
|
|
97
|
+
description: Path is not a file
|
|
98
|
+
|
|
99
|
+
/file-manager/api/delete:
|
|
100
|
+
delete:
|
|
101
|
+
tags: [File Manager]
|
|
102
|
+
summary: Delete a file or directory
|
|
103
|
+
description: Recursively deletes the specified file or directory
|
|
104
|
+
parameters:
|
|
105
|
+
- name: path
|
|
106
|
+
in: query
|
|
107
|
+
required: true
|
|
108
|
+
description: Absolute path of the file or directory to delete
|
|
109
|
+
schema:
|
|
110
|
+
type: string
|
|
111
|
+
responses:
|
|
112
|
+
'204':
|
|
113
|
+
description: Successfully deleted
|
|
114
|
+
|
|
115
|
+
/file-manager/api/upload:
|
|
116
|
+
post:
|
|
117
|
+
tags: [File Manager]
|
|
118
|
+
summary: Upload files to a directory
|
|
119
|
+
description: Uploads one or more files to the specified directory
|
|
120
|
+
parameters:
|
|
121
|
+
- name: path
|
|
122
|
+
in: query
|
|
123
|
+
required: true
|
|
124
|
+
description: Absolute path of the target directory
|
|
125
|
+
schema:
|
|
126
|
+
type: string
|
|
127
|
+
requestBody:
|
|
128
|
+
required: true
|
|
129
|
+
content:
|
|
130
|
+
multipart/form-data:
|
|
131
|
+
schema:
|
|
132
|
+
type: object
|
|
133
|
+
properties:
|
|
134
|
+
file:
|
|
135
|
+
type: array
|
|
136
|
+
items:
|
|
137
|
+
type: string
|
|
138
|
+
format: binary
|
|
139
|
+
responses:
|
|
140
|
+
'200':
|
|
141
|
+
description: Files uploaded successfully
|
|
142
|
+
|
|
143
|
+
/file-manager/api/mode:
|
|
144
|
+
put:
|
|
145
|
+
tags: [File Manager]
|
|
146
|
+
summary: Change file permissions
|
|
147
|
+
description: "Changes read/write/execute permissions. Mode format: +rwx or -rwx"
|
|
148
|
+
parameters:
|
|
149
|
+
- name: path
|
|
150
|
+
in: query
|
|
151
|
+
required: true
|
|
152
|
+
description: Absolute path of the file
|
|
153
|
+
schema:
|
|
154
|
+
type: string
|
|
155
|
+
- name: mode
|
|
156
|
+
in: query
|
|
157
|
+
required: true
|
|
158
|
+
description: "Permission mode (e.g. +rw, -x)"
|
|
159
|
+
schema:
|
|
160
|
+
type: string
|
|
161
|
+
responses:
|
|
162
|
+
'200':
|
|
163
|
+
description: Permissions changed
|
|
164
|
+
|
|
165
|
+
components:
|
|
166
|
+
schemas:
|
|
167
|
+
DirectoryInfo:
|
|
168
|
+
type: object
|
|
169
|
+
properties:
|
|
170
|
+
name:
|
|
171
|
+
type: string
|
|
172
|
+
path:
|
|
173
|
+
type: string
|
|
174
|
+
isWritable:
|
|
175
|
+
type: boolean
|
|
176
|
+
parentInfos:
|
|
177
|
+
type: array
|
|
178
|
+
items:
|
|
179
|
+
$ref: '#/components/schemas/ParentInfo'
|
|
180
|
+
files:
|
|
181
|
+
type: array
|
|
182
|
+
items:
|
|
183
|
+
$ref: '#/components/schemas/FileInfo'
|
|
184
|
+
|
|
185
|
+
ParentInfo:
|
|
186
|
+
type: object
|
|
187
|
+
properties:
|
|
188
|
+
path:
|
|
189
|
+
type: string
|
|
190
|
+
name:
|
|
191
|
+
type: string
|
|
192
|
+
|
|
193
|
+
FileInfo:
|
|
194
|
+
type: object
|
|
195
|
+
properties:
|
|
196
|
+
parent:
|
|
197
|
+
type: string
|
|
198
|
+
name:
|
|
199
|
+
type: string
|
|
200
|
+
length:
|
|
201
|
+
type: integer
|
|
202
|
+
format: int64
|
|
203
|
+
isDirectory:
|
|
204
|
+
type: boolean
|
|
205
|
+
isReadable:
|
|
206
|
+
type: boolean
|
|
207
|
+
isWritable:
|
|
208
|
+
type: boolean
|
|
209
|
+
isExecutable:
|
|
210
|
+
type: boolean
|
|
211
|
+
hasChildren:
|
|
212
|
+
type: boolean
|
|
213
|
+
modified:
|
|
214
|
+
type: string
|
|
215
|
+
description: ISO local date-time (no timezone)
|
|
216
|
+
created:
|
|
217
|
+
type: string
|
|
218
|
+
description: ISO local date-time (no timezone)
|
|
219
|
+
|