directus-extension-storage-manager 1.0.0
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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/api.js +1 -0
- package/dist/app.js +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 domdus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Storage Manager
|
|
2
|
+
|
|
3
|
+
Browse, upload, and move files between your Directus storage locations (local disk, S3, Google Cloud, Azure, and more) — without losing the file’s identity in Directus.
|
|
4
|
+
|
|
5
|
+
> **Important:** This extension moves or copies the **actual files** between storage backends and updates where Directus looks for them. Prefer **Copy** when you try a new storage for the first time; use **Move** only when you are sure the new location is correct.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
<img alt="Storage overview with local, S3, and GCS adapters" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_migration.png" width="800" />
|
|
10
|
+
|
|
11
|
+
Directus can use several storage locations at once, but it does not move existing files for you when you change storage. **Storage Manager** fills that gap: see what lives on each adapter, upload new files, find files on disk that are missing from Directus, and migrate selected files or whole folders between storages.
|
|
12
|
+
|
|
13
|
+
Files keep the same ID in Directus. Only their storage location changes. Image thumbnails are moved or copied with them when possible.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
### Overview & adapters
|
|
18
|
+
|
|
19
|
+
- See every configured storage location with file counts and usage
|
|
20
|
+
- Open a location to browse its files (same card/table layouts as the File Library)
|
|
21
|
+
- Drag and drop (or use **Upload**) to add files straight onto that storage
|
|
22
|
+
|
|
23
|
+
### Folders
|
|
24
|
+
|
|
25
|
+
- Browse the same virtual folders as the File Library
|
|
26
|
+
- Migrate everything in a folder (optionally including subfolders), across any storage
|
|
27
|
+
|
|
28
|
+
### Migrate
|
|
29
|
+
|
|
30
|
+
<img alt="Migrate Files drawer — choose target storage and Move or Copy" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_migration_files.png" width="800" />
|
|
31
|
+
|
|
32
|
+
- **Migrate selected** files, **all files on a storage**, or a **whole folder**
|
|
33
|
+
- **Move** — after a successful transfer, remove the file from the old storage
|
|
34
|
+
- **Copy** — leave a copy on the old storage (it will no longer be linked in Directus; you can clean it up or re-import with Detect)
|
|
35
|
+
- Live progress: from → to, current file, amount transferred, and speed
|
|
36
|
+
|
|
37
|
+
<img alt="Migrate progress from GCS to local storage" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_migration_storage_gcs.png" width="800" />
|
|
38
|
+
|
|
39
|
+
### Detect files
|
|
40
|
+
|
|
41
|
+
- Scan a storage for files that exist on disk/bucket but are **not** in Directus yet
|
|
42
|
+
- Import them into the library (creates database entries only — files stay where they are)
|
|
43
|
+
- Automatically skips Directus-generated image thumbnails
|
|
44
|
+
|
|
45
|
+
### Automation
|
|
46
|
+
|
|
47
|
+
- Use the **Storage Manager** Flow operation to copy or move files in automations
|
|
48
|
+
|
|
49
|
+
## Getting started
|
|
50
|
+
|
|
51
|
+
1. Make sure your project has more than one storage location configured (for example `local` and `s3`).
|
|
52
|
+
2. As an admin, open **Settings → Project Settings → Modules** and enable **Storage Manager**.
|
|
53
|
+
3. Open **Storage Manager** from the left bar.
|
|
54
|
+
4. Pick a storage card (or **Folders**) and browse files.
|
|
55
|
+
5. Select files (or migrate all on that storage / folder), choose a **target storage**, then **Move** or **Copy**.
|
|
56
|
+
|
|
57
|
+
Tips:
|
|
58
|
+
|
|
59
|
+
- Test with **Copy** first, then switch to **Move** once you are happy with the result.
|
|
60
|
+
- After a **Copy**, leftovers on the old storage can show up under **Detect files** — that is expected.
|
|
61
|
+
- Cloud storages often cannot report a full disk quota; the UI then shows Directus file totals only.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
Requires **Directus 9.26+ through 12.x**. The extension must run in the Directus API process (not the sandbox).
|
|
66
|
+
|
|
67
|
+
### npm
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install directus-extension-storage-manager
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Place the package in your Directus `extensions` folder (or install into a project that loads extensions from `node_modules`), then restart Directus.
|
|
74
|
+
|
|
75
|
+
### Marketplace
|
|
76
|
+
|
|
77
|
+
Search for **Storage Manager** in **Settings → Marketplace**. This bundle includes an API endpoint, so some environments only allow App extensions from the Marketplace — use the npm/manual install below if install is blocked.
|
|
78
|
+
|
|
79
|
+
### Manual installation
|
|
80
|
+
|
|
81
|
+
1. Install and build:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd directus-extension-storage-manager
|
|
85
|
+
npm install
|
|
86
|
+
npm run build
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
2. Copy the built package into your Directus `extensions` folder (include `package.json` and the `dist` folder).
|
|
90
|
+
|
|
91
|
+
3. Restart Directus.
|
|
92
|
+
|
|
93
|
+
4. In the Data Studio:
|
|
94
|
+
|
|
95
|
+
1. Open **Settings → Project Settings → Modules**
|
|
96
|
+
2. Enable **Storage Manager**
|
|
97
|
+
3. Open **Storage Manager** from the left bar
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
package/dist/api.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Transform as e}from"node:stream";import{createRequire as t}from"node:module";import r from"node:fs";import n from"node:path";import{pathToFileURL as a}from"node:url";import s from"node:fs/promises";import{randomUUID as o}from"node:crypto";let i=null;async function l(e){try{const t=await import(e),r=t?.getStorage||t?.default?.getStorage;if("function"==typeof r)return r}catch{}return null}async function c(e){if(!r.existsSync(e))return null;try{const r=t(n.join(process.cwd(),"package.json"))(e),a=r?.getStorage||r?.default?.getStorage;if("function"==typeof a)return a}catch{}try{return await l(a(e).href)}catch{return null}}async function d(){if(i)return i;const e=["@directus/api/dist/storage/index.js","@directus/api/storage/index.js","directus/dist/storage/index.js"];for(const t of e){const e=await l(t);if(e)return i=e,e}for(const e of function(){const e=process.cwd(),t=Array.from(new Set([e,n.resolve(e,".."),"/directus",n.dirname(process.execPath)].filter(Boolean))),r=["node_modules/@directus/api/dist/storage/index.js","node_modules/@directus/api/storage/index.js","node_modules/directus/dist/storage/index.js","node_modules/directus/storage/index.js"],a=[];for(const e of t)for(const t of r)a.push(n.join(e,t));return a}()){const t=await c(e);if(t)return i=t,t}throw new Error("Could not resolve Directus getStorage(). Ensure this extension runs inside the Directus API process (not sandboxed).")}async function u(){return(await d())()}async function f(e,t){if("function"==typeof e.read)return await e.read(t);if("function"==typeof e.getStream)return e.getStream(t);throw new Error("Storage disk does not support read/getStream")}async function g(e,t,r,n){if("function"!=typeof e.write){if("function"!=typeof e.put)throw new Error("Storage disk does not support write/put");await e.put(t,r,n||void 0)}else await e.write(t,r,n||void 0)}async function m(e,t){if("function"==typeof e.exists)return await e.exists(t);try{return await p(e,t),!0}catch{return!1}}async function p(e,t){if("function"==typeof e.stat){const r=await e.stat(t);return{size:Number(r.size)||0}}if("function"==typeof e.getStat){const r=await e.getStat(t);return{size:Number(r.size)||0}}throw new Error("Storage disk does not support stat/getStat")}async function _(e,t){if("function"!=typeof e.delete)throw new Error("Storage disk does not support delete");await e.delete(t)}const y=/__[a-f0-9]{16,}(?:\.[^.]+)?$/i;async function w(e,t){const r=n.parse(t).name;if("function"!=typeof e.list)await _(e,t);else for await(const t of e.list(r))try{await _(e,t)}catch{}}function h(e){return String(e.title||e.filename_download||e.filename_disk||e.id)}async function b(e,t,r,n){const a=new Array(e.length);let s=0;const o=Array.from({length:Math.min(t,e.length)||1},()=>async function(){for(;s<e.length;){if(n?.())return;const t=s++;a[t]=await r(e[t],t)}}());return await Promise.all(o),a}async function S(e,t,r,a){let s;try{s=await async function(e,t){if("function"!=typeof e.list)return[];const r=n.parse(t).name,a=`${r}__`,s=[];for await(const t of e.list(r)){const e=String(t||"").replace(/^[/\\]+/,""),r=n.basename(e);r.startsWith(a)&&y.test(r)&&s.push(e)}return s}(e,r)}catch(e){return void a?.warn?.(`[storage-manager] Could not list asset transforms for ${r}: ${e instanceof Error?e.message:String(e)}`)}for(const r of s)try{if(await m(t,r))continue;const n=await f(e,r);await g(t,r,n,null)}catch(e){a?.warn?.(`[storage-manager] Failed copying asset transform ${r}: ${e instanceof Error?e.message:String(e)}`)}}async function k(t,r,n,a,s,o){const i={id:t.id,filename_disk:t.filename_disk,from:t.storage,to:r,status:"failed"};if(!t.filename_disk)return{...i,status:"failed",error:"Missing filename_disk"};if(t.storage===r)return{...i,status:"skipped",error:"Already on target storage"};const l=await u(),c=l.location(t.storage),d=l.location(r);if(!await m(c,t.filename_disk))return{...i,status:"failed",error:`Source object missing on ${t.storage}`};if(await m(d,t.filename_disk)){if(await S(c,d,t.filename_disk,s),await a("directus_files").where({id:t.id}).update({storage:r}),"move"===n)try{await w(c,t.filename_disk)}catch(e){s?.warn?.(`[storage-manager] DB updated but failed deleting source ${t.id}: ${e instanceof Error?e.message:String(e)}`)}const e=await p(d,t.filename_disk).catch(()=>({size:Number(t.filesize)||0}));return o&&e.size>0&&o(e.size),{...i,status:"move"===n?"moved":"copied",bytes:e.size}}const _=await f(c,t.filename_disk),y=o?_.pipe((h=o,new e({transform(e,t,r){const n=Buffer.isBuffer(e)?e.length:Buffer.byteLength(e);n>0&&h(n),r(null,e)}}))):_;var h;try{await g(d,t.filename_disk,y,t.type)}catch(e){try{await m(d,t.filename_disk)&&await w(d,t.filename_disk)}catch{}return{...i,status:"failed",error:`Write failed: ${e instanceof Error?e.message:String(e)}`}}if(!await m(d,t.filename_disk))return{...i,status:"failed",error:"Target write did not persist (exists check failed)"};let b=0;try{b=(await p(d,t.filename_disk)).size}catch(e){return{...i,status:"failed",error:`Target stat failed after write: ${e instanceof Error?e.message:String(e)}`}}if(null!=t.filesize&&Number(t.filesize)>0&&b>0){const e=Number(t.filesize);if(Math.abs(e-b)>Math.max(64,.01*e)&&b<.95*e){try{await w(d,t.filename_disk)}catch{}return{...i,status:"failed",error:`Size mismatch after write (expected ~${e}, got ${b})`}}}await S(c,d,t.filename_disk,s);try{await a("directus_files").where({id:t.id}).update({storage:r})}catch(e){try{await w(d,t.filename_disk)}catch{}return{...i,status:"failed",error:`DB update failed: ${e instanceof Error?e.message:String(e)}`}}if("move"===n)try{await w(c,t.filename_disk)}catch(e){return s?.warn?.(`[storage-manager] Moved ${t.id} to ${r} but source delete failed: ${e instanceof Error?e.message:String(e)}`),{...i,status:"moved",bytes:b,error:`Moved, but source cleanup failed: ${e instanceof Error?e.message:String(e)}`}}return{...i,status:"move"===n?"moved":"copied",bytes:b}}async function v(e){const{fileIds:t,targetStorage:r,mode:n,database:a,logger:s,onProgress:o,isCancelled:i}=e,l=Math.min(8,Math.max(1,Number(e.concurrency)||3)),c=Date.now(),d=Array.from(new Set(t.filter(Boolean)));if(0===d.length){const e={mode:n,target_storage:r,total:0,succeeded:0,skipped:0,failed:0,results:[],transferred_bytes:0,total_bytes:0,elapsed_ms:0};return o?.({type:"done",data:e}),e}const u=await a("directus_files").select("id","storage","filename_disk","filename_download","title","type","filesize").whereIn("id",d),f=new Map(u.map(e=>[String(e.id),e])),g=u.reduce((e,t)=>e+(Number(t.filesize)||0),0),m=u.length&&u.every(e=>e.storage===u[0].storage)?u[0].storage:null;o?.({type:"start",mode:n,from:m,to:r,total:d.length,total_bytes:g});let p=0,_=0,y=0,w=0,S=0;const v=new Map;let $=0;const j=await b(d,l,async e=>{if(i?.())return{id:e,filename_disk:"",from:"",to:r,status:"failed",error:"Cancelled"};const t=f.get(e),l=++S;if(!t){w+=1;const t={id:e,filename_disk:"",from:"",to:r,status:"failed",error:"File not found"};return o?.({type:"file_done",index:l,total:d.length,result:t,name:e,succeeded:_,skipped:y,failed:w,transferred_bytes:p,total_bytes:g,elapsed_ms:Date.now()-c}),t}let u;o?.({type:"file_start",index:l,total:d.length,id:t.id,name:h(t),filename_disk:t.filename_disk,from:t.storage,to:r,bytes:Number(t.filesize)||0}),v.set(t.id,0);try{u=await k(t,r,n,a,s,e=>((e,t,r)=>{p+=r;const n=(v.get(t.id)||0)+r;v.set(t.id,n);const a=Date.now();a-$<120||($=a,o?.({type:"file_bytes",index:e,id:t.id,file_transferred:n,file_bytes:Number(t.filesize)||0,transferred_bytes:p,total_bytes:g,elapsed_ms:a-c}))})(l,t,e))}catch(e){u={id:t.id,filename_disk:t.filename_disk,from:t.storage,to:r,status:"failed",error:e instanceof Error?e.message:String(e)}}if("moved"===u.status||"copied"===u.status){_+=1;const e=v.get(t.id)||0,r=Number(u.bytes)||Number(t.filesize)||0;r>e&&(p+=r-e,v.set(t.id,r))}else"skipped"===u.status?y+=1:w+=1;return o?.({type:"file_done",index:l,total:d.length,result:u,name:h(t),succeeded:_,skipped:y,failed:w,transferred_bytes:p,total_bytes:g,elapsed_ms:Date.now()-c}),u},i),A={mode:n,target_storage:r,total:j.filter(Boolean).length,succeeded:_,skipped:y,failed:w,results:j.filter(Boolean),transferred_bytes:p,total_bytes:g,elapsed_ms:Date.now()-c};return s?.info?.(`[storage-manager] ${n} → ${r}: ${_} ok, ${y} skipped, ${w} failed (of ${A.total})`),o?.({type:"done",data:A}),A}const $={local:{id:"local",label:"Local Disk",short:"Local",icon:"hard_drive"},s3:{id:"s3",label:"Amazon S3",short:"S3",icon:"cloud"},gcs:{id:"gcs",label:"Google Cloud Storage",short:"GCS",icon:"cloud_queue"},azure:{id:"azure",label:"Azure Blob",short:"Azure",icon:"cloud_done"},cloudinary:{id:"cloudinary",label:"Cloudinary",short:"Cloudinary",icon:"photo_library"},supabase:{id:"supabase",label:"Supabase",short:"Supabase",icon:"storage"}};function j(e,t){const r=e[t];if(null==r)return null;const n=String(r).trim();return""===n?null:n}function A(e){const t=(r=e.STORAGE_LOCATIONS)?Array.isArray(r)?r.map(e=>String(e).trim()).filter(Boolean):String(r).split(",").map(e=>e.trim()).filter(Boolean):[];var r;return t.length>0?t:["local"]}function N(e,t){return j(e,`STORAGE_${t.toUpperCase()}_DRIVER`)||"local"}function z(e,t){return j(e,`STORAGE_${t.toUpperCase()}_ROOT`)}function x(e,t){return j(e,`STORAGE_${t.toUpperCase()}_BUCKET`)||j(e,`STORAGE_${t.toUpperCase()}_CONTAINER_NAME`)}async function E(e,t,a){const s=N(e,a),{file_count:o,total_bytes:i}=await async function(e,t){const r=await e("directus_files").where({storage:t}).select(e.raw("count(*) as file_count"),e.raw("coalesce(sum(filesize), 0) as total_bytes")).first();return{file_count:Number(r?.file_count||0),total_bytes:Number(r?.total_bytes||0)}}(t,a);let l={disk_total_bytes:null,disk_free_bytes:null,disk_used_bytes:null,disk_used_percent:null,disk_available:!1};return"local"===s&&(l=await async function(e){const t={disk_total_bytes:null,disk_free_bytes:null,disk_used_bytes:null,disk_used_percent:null,disk_available:!1};if(!e)return t;const a=process.env.PWD||process.cwd()||"/directus",s=n.isAbsolute(e)?e:n.join(a,e);try{if(!r.existsSync(s))return t;const e=r.promises.statfs;if("function"==typeof e){const t=await e(s),r=Number(t.bsize||t.frsize||0),n=r*Number(t.blocks||0),a=r*Number(t.bavail??t.bfree??0),o=Math.max(0,n-a);return{disk_total_bytes:n||null,disk_free_bytes:a||null,disk_used_bytes:o||null,disk_used_percent:n>0?o/n*100:null,disk_available:n>0}}}catch{}return t}(z(e,a))),{location:a,driver:s,file_count:o,total_bytes:i,...l}}async function q(e,t,r){const n=await E(e,t,r),a=function(e){const t=String(e||"local").toLowerCase();return $[t]||{id:t,label:t,short:t.slice(0,8).toUpperCase(),icon:"storage"}}(n.driver);return{...n,label:a.label,short:a.short,icon:a.icon,root:z(e,r),bucket:x(e,r)}}const C=/__[a-f0-9]{16,}(?:\.[^.]+)?$/i;function B(e){const t=n.basename(e);return!(t&&!t.startsWith("."))||("directus-health-file"===t||!!function(e){return C.test(n.basename(e))}(e))}const I={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".avif":"image/avif",".svg":"image/svg+xml",".pdf":"application/pdf",".mp4":"video/mp4",".webm":"video/webm",".mov":"video/quicktime",".mp3":"audio/mpeg",".wav":"audio/wav",".json":"application/json",".txt":"text/plain",".csv":"text/csv",".zip":"application/zip"};function M(e){const t=n.extname(e).toLowerCase();return I[t]||null}function W(e){const t=n.parse(n.basename(e)).name;return t.replace(/_/g," ").replace(/\s+/g," ").trim()||t||e}function D(e){const t=n.parse(n.basename(e)).name;return/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(t)?t.toLowerCase():null}async function O(e,t){const r=new Set;if(t){const a=N(t,e),o=z(t,e);if("local"===a&&o){for(const e of await async function(e){const t=n.isAbsolute(e)?e:n.join(process.cwd(),e),r=[];return await async function e(t,a=""){let o;try{o=await s.readdir(t,{withFileTypes:!0})}catch{return}for(const s of o){const o=a?`${a}/${s.name}`:s.name;s.isDirectory()?await e(n.join(t,s.name),o):s.isFile()&&(B(o)||r.push(o))}}(t),r}(o))r.add(e);return Array.from(r)}}const a=(await u()).location(e);try{for await(const e of async function*(e,t=""){if("function"!=typeof e.list)throw new Error("Storage disk does not support list() — cannot detect orphan files on this driver");for await(const r of e.list(t))yield String(r)}(a,"")){const t=String(e||"").replace(/^[/\\]+/,"");t&&!B(t)&&r.add(t)}}catch(t){throw new Error(`Could not list files on storage “${e}”: ${t?.message||t}`)}return Array.from(r)}async function U(e,t,r){const a=await O(t,r),s=await e("directus_files").select("filename_disk").where("storage",t),o=new Set(s.map(e=>String(e.filename_disk||"")).filter(Boolean)),i=(await u()).location(t),l=[];for(const e of a){if(o.has(e))continue;let t=0;try{const r=await p(i,e);t=Number(r.size)||0}catch{t=0}l.push({filename_disk:e,filesize:t,type:M(e),suggested_id:D(e),filename_download:n.basename(e),title:W(e)})}return l.sort((e,t)=>e.filename_disk.localeCompare(t.filename_disk)),{orphans:l,scanned:a.length,known:o.size}}function T(e,t){return!(!(r=e.accountability)||"object"!=typeof r||!0!==r.admin)||(t.status(403).json({errors:[{message:"Admin access required",extensions:{code:"FORBIDDEN"}}]}),!1);var r}async function P(e,t,r){if(!t)return[];if(!r)return[t];const n=await e("directus_folders").select("id","parent"),a=new Map;for(const e of n){const t=null==e.parent?null:String(e.parent),r=String(e.id);a.has(t)||a.set(t,[]),a.get(t).push(r)}const s=[],o=[String(t)];for(;o.length;){const e=o.pop();s.push(e);const t=a.get(e)||[];for(const e of t)o.push(e)}return s}async function R(e,t){if(Array.isArray(t.file_ids)&&t.file_ids.length>0)return t.file_ids.map(String);const r=e("directus_files").select("id");if(t.source_storage&&r.where("storage",String(t.source_storage)),void 0!==t.folder_id)if(null===t.folder_id||""===t.folder_id)r.whereNull("folder");else{const n=await P(e,String(t.folder_id),Boolean(t.recursive));0===n.length?r.where("folder",String(t.folder_id)):r.whereIn("folder",n)}return(await r).map(e=>String(e.id))}const G=[],L=[{name:"storage-manager-endpoint",config:{id:"storage-manager",handler:(e,t)=>{const{database:r,env:a,logger:s}=t;e.get("/storages",async(e,t,n)=>{try{if(!T(e,t))return;const n=A(a),s=await Promise.all(n.map(e=>q(a,r,e)));t.json({data:s})}catch(e){n(e)}}),e.get("/storages/:location",async(e,t,n)=>{try{if(!T(e,t))return;const n=String(e.params.location);if(!A(a).includes(n))return void t.status(404).json({errors:[{message:`Unknown storage location: ${n}`}]});const s=await q(a,r,n);t.json({data:s})}catch(e){n(e)}}),e.get("/storages/:location/orphans",async(e,t,n)=>{try{if(!T(e,t))return;const n=String(e.params.location);if(!A(a).includes(n))return void t.status(404).json({errors:[{message:`Unknown storage location: ${n}`}]});const s=await U(r,n,a);t.json({data:s.orphans,meta:{scanned:s.scanned,known:s.known,orphan_count:s.orphans.length}})}catch(e){n(e)}}),e.post("/storages/:location/import-orphans",async(e,t,i)=>{try{if(!T(e,t))return;const i=String(e.params.location);if(!A(a).includes(i))return void t.status(404).json({errors:[{message:`Unknown storage location: ${i}`}]});const l=e.body||{};let c=Array.isArray(l.filename_disks)?l.filename_disks.map(String):[];if(0===c.length){c=(await U(r,i,a)).orphans.map(e=>e.filename_disk)}if(0===c.length)return void t.json({data:{total:0,imported:0,skipped:0,failed:0,results:[]}});const d=await async function(e,t,r,a){const s=(await u()).location(t),i=[];for(const l of r){const r=String(l||"").trim();if(r&&!B(r))try{const l=await e("directus_files").select("id").where({storage:t,filename_disk:r}).first();if(l){i.push({filename_disk:r,id:String(l.id),status:"skipped",error:"Already in database"});continue}if(!await m(s,r)){i.push({filename_disk:r,status:"failed",error:"File not found on disk"});continue}const c=await p(s,r),d=D(r);let u=d||o();d&&await e("directus_files").select("id").where({id:d}).first()&&(u=o());const f=new Date,g=n.basename(r),_=M(r),y=W(r);await e("directus_files").insert({id:u,storage:t,filename_disk:r,filename_download:g,title:y,type:_,filesize:Number(c.size)||0,folder:a?.folder||null,uploaded_on:f,modified_on:f}),i.push({filename_disk:r,id:u,status:"imported"})}catch(e){i.push({filename_disk:r,status:"failed",error:e?.message||String(e)})}else i.push({filename_disk:r,status:"skipped",error:"Ignored path"})}return i}(r,i,c,{folder:void 0===l.folder?null:l.folder});s.info(`[storage-manager] import-orphans location=${i} total=${d.length} imported=${d.filter(e=>"imported"===e.status).length}`),t.json({data:{total:d.length,imported:d.filter(e=>"imported"===e.status).length,skipped:d.filter(e=>"skipped"===e.status).length,failed:d.filter(e=>"failed"===e.status).length,results:d}})}catch(e){i(e)}}),e.get("/files",async(e,t,n)=>{try{if(!T(e,t))return;const n=e.query.storage?String(e.query.storage):null,a=void 0!==e.query.folder?"null"===e.query.folder||""===e.query.folder?null:String(e.query.folder):void 0,s=e.query.search?String(e.query.search).trim():"",o=function(e){if(!e)return null;try{const t="string"==typeof e?JSON.parse(e):e;if(t&&"object"==typeof t&&!Array.isArray(t))return t}catch{}return null}(e.query.filter),i="true"===String(e.query.recursive||""),l=Math.max(1,parseInt(String(e.query.page||"1"),10)||1),c=Math.min(200,Math.max(1,parseInt(String(e.query.limit||"50"),10)||50)),d=String(e.query.sort||"-uploaded_on"),u=r("directus_files").select("id","title","filename_download","filename_disk","storage","type","filesize","folder","uploaded_on","modified_on");if(n&&u.where("storage",n),void 0!==a)if(null===a)u.whereNull("folder");else if(i){const e=await P(r,a,!0);u.whereIn("folder",e)}else u.where("folder",a);if(s){const e=`%${s.replace(/[%_]/g,"\\$&")}%`;u.andWhere(t=>{t.where("title","like",e).orWhere("filename_download","like",e).orWhere("filename_disk","like",e)})}!function(e,t){if(!t)return;const r=(e,t)=>{if(t._and&&Array.isArray(t._and))for(const n of t._and)n&&"object"==typeof n&&r(e,n);else if(t._or&&Array.isArray(t._or))e.andWhere(e=>{for(const n of t._or)n&&"object"==typeof n&&e.orWhere(e=>r(e,n))});else for(const[r,n]of Object.entries(t)){if(r.startsWith("_"))continue;if(!n||"object"!=typeof n)continue;const t=n;"_eq"in t&&e.andWhere(r,t._eq),"_neq"in t&&e.andWhereNot(r,t._neq),"_null"in t&&e.whereNull(r),"_nnull"in t&&e.whereNotNull(r),"_contains"in t&&e.andWhere(r,"like",`%${String(t._contains)}%`),"_in"in t&&Array.isArray(t._in)&&e.whereIn(r,t._in)}};r(e,t)}(u,o);const f=u.clone().clearSelect().count({count:"*"}).first(),g=u.clone().clearSelect().sum({total_bytes:"filesize"}).first(),m=d.startsWith("-")?d.slice(1):d,p=d.startsWith("-")?"desc":"asc",_=new Set(["uploaded_on","modified_on","filename_download","filesize","title","storage","type"]).has(m)?m:"uploaded_on",y=await u.clone().orderBy(_,p).limit(c).offset((l-1)*c),w=await f,h=await g,b=Number(w?.count||0),S=Number(h?.total_bytes||0);t.json({data:y,meta:{total_count:b,filter_count:b,total_bytes:S,page:l,limit:c}})}catch(e){n(e)}}),e.get("/folders",async(e,t,n)=>{try{if(!T(e,t))return;const n=void 0===e.query.parent?void 0:"null"===e.query.parent||""===e.query.parent?null:String(e.query.parent),a=e.query.storage?String(e.query.storage):null;let s=r("directus_folders").select("id","name","parent").orderBy("name","asc");null===n?s=s.whereNull("parent"):void 0!==n&&(s=s.where("parent",n));const o=await s,i=[];for(const e of o){const t=String(e.id);let n=r("directus_files").where("folder",t);a&&(n=n.andWhere("storage",a));const s=await n.select(r.raw("count(*) as file_count"),r.raw("coalesce(sum(filesize), 0) as total_bytes")).first(),o=await r("directus_folders").where("parent",t).count({count:"*"}).first();i.push({id:t,name:e.name,parent:null==e.parent?null:String(e.parent),file_count:Number(s?.file_count||0),total_bytes:Number(s?.total_bytes||0),child_count:Number(o?.count||0)})}let l=null;if(null==n){let e=r("directus_files").whereNull("folder");a&&(e=e.andWhere("storage",a));const t=await e.select(r.raw("count(*) as file_count"),r.raw("coalesce(sum(filesize), 0) as total_bytes")).first();l={file_count:Number(t?.file_count||0),total_bytes:Number(t?.total_bytes||0)}}t.json({data:i,meta:{root_files:l}})}catch(e){n(e)}}),e.get("/folders/tree",async(e,t,n)=>{try{if(!T(e,t))return;const n=(await r("directus_folders").select("id","name","parent").orderBy("name","asc")).map(e=>({id:String(e.id),name:e.name,parent:null==e.parent?null:String(e.parent)}));t.json({data:n})}catch(e){n(e)}}),e.get("/folders/:id/path",async(e,t,n)=>{try{if(!T(e,t))return;const n=String(e.params.id),a=[];let s=n;for(let e=0;e<50&&s;e++){const e=await r("directus_folders").select("id","name","parent").where({id:s}).first();if(!e)break;a.unshift({id:String(e.id),name:e.name}),s=null==e.parent?null:String(e.parent)}t.json({data:a})}catch(e){n(e)}}),e.post("/migrate",async(e,t,n)=>{try{if(!T(e,t))return;const n=e.body||{},o=String(n.target_storage||"").trim(),i="copy"===n.mode?"copy":"move"===n.mode?"move":null;if(!o||!i)return void t.status(400).json({errors:[{message:'target_storage and mode ("copy"|"move") are required'}]});const l=A(a);if(!l.includes(o))return void t.status(400).json({errors:[{message:`Unknown target storage: ${o}. Available: ${l.join(", ")}`}]});if(N(a,o),n.source_storage&&!l.includes(String(n.source_storage)))return void t.status(400).json({errors:[{message:`Unknown source storage: ${n.source_storage}`}]});if(!(Array.isArray(n.file_ids)&&n.file_ids.length>0||Boolean(n.source_storage)||void 0!==n.folder_id))return void t.status(400).json({errors:[{message:"Provide file_ids, source_storage, and/or folder_id to select files to migrate"}]});const c=await R(r,n);if(0===c.length)return void t.json({data:{mode:i,target_storage:o,total:0,succeeded:0,skipped:0,failed:0,results:[]}});const d=5e3;if(c.length>d)return void t.status(400).json({errors:[{message:`Selection contains ${c.length} files (max ${d} per request). Narrow the selection or run in batches.`}]});s.info(`[storage-manager] Starting ${i} of ${c.length} file(s) → ${o} (by ${e.accountability?.user||"unknown"})`);const u=await v({fileIds:c,targetStorage:o,mode:i,concurrency:n.concurrency,database:r,logger:s});t.json({data:u})}catch(e){n(e)}}),e.post("/migrate/stream",async(e,t,n)=>{try{if(!T(e,t))return;const n=e.body||{},o=String(n.target_storage||"").trim(),i="copy"===n.mode?"copy":"move"===n.mode?"move":null;if(!o||!i)return void t.status(400).json({errors:[{message:'target_storage and mode ("copy"|"move") are required'}]});const l=A(a);if(!l.includes(o))return void t.status(400).json({errors:[{message:`Unknown target storage: ${o}. Available: ${l.join(", ")}`}]});if(N(a,o),n.source_storage&&!l.includes(String(n.source_storage)))return void t.status(400).json({errors:[{message:`Unknown source storage: ${n.source_storage}`}]});if(!(Array.isArray(n.file_ids)&&n.file_ids.length>0||Boolean(n.source_storage)||void 0!==n.folder_id))return void t.status(400).json({errors:[{message:"Provide file_ids, source_storage, and/or folder_id to select files to migrate"}]});const c=await R(r,n),d=5e3;if(c.length>d)return void t.status(400).json({errors:[{message:`Selection contains ${c.length} files (max ${d} per request). Narrow the selection or run in batches.`}]});t.status(200),t.setHeader("Content-Type","text/event-stream; charset=utf-8"),t.setHeader("Cache-Control","no-cache, no-transform"),t.setHeader("Connection","keep-alive"),t.setHeader("X-Accel-Buffering","no"),t.flushHeaders?.();let u=!1;const f=()=>{u=!0};e.on("close",f),t.on("close",f);const g=e=>{if(u)return;t.write(`data: ${JSON.stringify(e)}\n\n`);const r=t.flush;"function"==typeof r&&r.call(t)};s.info(`[storage-manager] Streaming ${i} of ${c.length} file(s) → ${o} (by ${e.accountability?.user||"unknown"})`);try{await v({fileIds:c,targetStorage:o,mode:i,concurrency:n.concurrency??1,database:r,logger:s,isCancelled:()=>u,onProgress:e=>g(e)})}catch(e){g({type:"error",message:e instanceof Error?e.message:String(e)})}u||t.end()}catch(e){if(!t.headersSent)return void n(e);try{t.write(`data: ${JSON.stringify({type:"error",message:e instanceof Error?e.message:String(e)})}\n\n`),t.end()}catch{}}})}}}],F=[{name:"storage-manager-operation",config:{id:"storage-manager-operation",handler:async(e,{env:t,database:r,logger:n})=>{const a="copy"===e.mode?"copy":"move",s=String(e.target_storage||"").trim();if(!s)throw new Error("target_storage is required");const o=A(t);if(!o.includes(s))throw new Error(`Unknown target storage "${s}". Available: ${o.join(", ")}`);let i=function(e){if(!e)return[];if(Array.isArray(e))return e.map(String).filter(Boolean);if("string"==typeof e){const t=e.trim();if(!t)return[];try{const e=JSON.parse(t);if(Array.isArray(e))return e.map(String).filter(Boolean)}catch{return[t]}}return[]}(e.file_ids);if(0===i.length){const t=r("directus_files").select("id");if(e.source_storage){const r=String(e.source_storage).trim();if(!o.includes(r))throw new Error(`Unknown source storage "${r}"`);t.where("storage",r)}if(e.folder_id){const n=await async function(e,t,r){if(!r)return[t];const n=await e("directus_folders").select("id","parent"),a=new Map;for(const e of n){const t=null==e.parent?null:String(e.parent),r=String(e.id);a.has(t)||a.set(t,[]),a.get(t).push(r)}const s=[],o=[String(t)];for(;o.length;){const e=o.pop();s.push(e);for(const t of a.get(e)||[])o.push(t)}return s}(r,String(e.folder_id),!1!==e.recursive);t.whereIn("folder",n)}if(!e.source_storage&&!e.folder_id)throw new Error("Provide file_ids, source_storage, and/or folder_id");i=(await t).map(e=>String(e.id))}const l=null!=e.concurrency?parseInt(String(e.concurrency),10):1;return n.info(`[storage-manager-operation] ${a} ${i.length} file(s) → ${s}`),await v({fileIds:i,targetStorage:s,mode:a,concurrency:l,database:r,logger:n})}}}];export{L as endpoints,G as hooks,F as operations};
|
package/dist/app.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useApi as e,useStores as t,defineModule as a}from"@directus/extensions-sdk";import{ref as n,computed as l,defineComponent as o,resolveComponent as r,openBlock as i,createBlock as s,withCtx as d,createVNode as u,createElementBlock as c,Fragment as f,renderList as v,watch as p,onMounted as m,unref as g,createCommentVNode as b,createElementVNode as h,isRef as y,normalizeClass as w,toDisplayString as k,normalizeStyle as x,createTextVNode as _,onUnmounted as S,inject as $,reactive as M,toRefs as B,resolveDirective as F,withDirectives as C,withKeys as z,withModifiers as I,vShow as V,renderSlot as O,resolveDynamicComponent as N,normalizeProps as T,guardReactiveProps as U}from"vue";import{useRoute as L,useRouter as D,onBeforeRouteLeave as E,onBeforeRouteUpdate as j}from"vue-router";const A=n(!1),q=n(null),P=n(null),R=n(["root"]);function K(){const t=e(),a=l(()=>P.value||null),n=l({get:()=>R.value,set(e){R.value=e}});return null===q.value&&o(),{loading:A,folders:q,nestedFolders:a,fetchFolders:o,openFolders:n};async function o(){if(!A.value){A.value=!0;try{const e=await t.get("/folders",{params:{sort:"name",limit:-1,fields:["id","name","parent"]}}),a=e.data?.data||[];q.value=a,P.value=function(e){const t=new Map;for(const a of e)if(a.parent){const e=t.get(a.parent)||[];e.push(a),t.set(a.parent,e)}const a=e=>{const n=t.get(e.id)||[];return n.length>0?{...e,children:n.map(a)}:{...e}};return e.filter(e=>null===e.parent).map(a)}(a)}catch{q.value=[],P.value=[]}finally{A.value=!1}}}}const W=n([]),J=n(!1),Q=n(null);function H(e){const t={"Content-Type":"application/json",Accept:"text/event-stream"},a=e?.defaults?.headers,n=a?.common?.Authorization||a?.Authorization||a?.common?.authorization||a?.authorization;return"string"==typeof n&&n&&(t.Authorization=n),t}function G(){const t=e();return{storages:W,storagesLoading:J,storagesError:Q,loadStorages:async function(e=!1){if(W.value.length&&!e)return W.value;J.value=!0,Q.value=null;try{const e=await t.get("/storage-manager/storages");return W.value=e.data?.data||[],W.value}catch(e){throw Q.value=e?.response?.data?.errors?.[0]?.message||e?.message||"Failed to load storages",e}finally{J.value=!1}},loadFiles:async function(e){const a=await t.get("/storage-manager/files",{params:{storage:e.storage||void 0,folder:null===e.folder?"null":e.folder||void 0,search:e.search||void 0,filter:e.filter?JSON.stringify(e.filter):void 0,recursive:e.recursive?"true":void 0,page:e.page||1,limit:e.limit||50,sort:e.sort||"-uploaded_on"}});return{data:a.data?.data||[],meta:a.data?.meta}},loadFolders:async function(e={}){const a=await t.get("/storage-manager/folders",{params:{parent:null===e.parent?"null":e.parent||void 0,storage:e.storage||void 0}});return{data:a.data?.data||[],root_files:a.data?.meta?.root_files}},loadFolderPath:async function(e){const a=await t.get(`/storage-manager/folders/${e}/path`);return a.data?.data||[]},migrate:async function(e){const a=await t.post("/storage-manager/migrate",e);return a.data?.data},migrateWithProgress:async function(e,a,n){return async function(e,t){if(!e.ok){let t=`Migration failed (${e.status})`;try{const a=await e.json();t=a?.errors?.[0]?.message||t}catch{}throw new Error(t)}if(!e.body)throw new Error("No response body for progress stream");const a=e.body.getReader(),n=new TextDecoder;let l="",o=null;for(;;){const{done:e,value:r}=await a.read();if(e)break;l+=n.decode(r,{stream:!0});const i=l.split("\n\n");l=i.pop()||"";for(const e of i){const a=e.split("\n");for(const e of a){const a=e.trim();if(!a.startsWith("data:"))continue;const n=a.slice(5).trim();if(!n)continue;let l;try{l=JSON.parse(n)}catch{continue}if(t(l),"done"===l.type&&(o=l.data),"error"===l.type)throw new Error(l.message||"Migration failed")}}}if(!o)throw new Error("Migration stream ended without a result");return o}(await fetch(function(e){const t=String(e?.defaults?.baseURL||"").replace(/\/$/,""),a="/storage-manager/migrate/stream";return t?`${t}${a}`:a}(t),{method:"POST",headers:H(t),body:JSON.stringify(e),credentials:"same-origin",signal:n}),a)}}}var X=o({name:"NavFolderItem",__name:"nav-folder-item",props:{folder:{},currentFolder:{},clickHandler:{type:Function}},setup:e=>(t,a)=>{const n=r("v-icon"),l=r("v-list-item-icon"),o=r("v-text-overflow"),p=r("v-list-item-content"),m=r("v-list-item"),g=r("nav-folder-item",!0),b=r("v-list-group");return e.folder.children&&0!==e.folder.children.length?(i(),s(b,{key:1,clickable:"",scope:"files-navigation",value:e.folder.id,active:e.currentFolder===e.folder.id,"arrow-placement":"after","disable-groupable-parent":"",onClick:a[1]||(a[1]=t=>e.clickHandler({folder:e.folder.id}))},{activator:d(()=>[u(l,null,{default:d(()=>[u(n,{name:"folder",outline:""})]),_:1}),u(p,null,{default:d(()=>[u(o,{text:e.folder.name},null,8,["text"])]),_:1})]),default:d(()=>[(i(!0),c(f,null,v(e.folder.children,t=>(i(),s(g,{key:t.id,folder:t,"current-folder":e.currentFolder,"click-handler":e.clickHandler},null,8,["folder","current-folder","click-handler"]))),128))]),_:1},8,["value","active"])):(i(),s(m,{key:0,clickable:"",active:e.currentFolder===e.folder.id,onClick:a[0]||(a[0]=t=>e.clickHandler({folder:e.folder.id}))},{default:d(()=>[u(l,null,{default:d(()=>[u(n,{name:"folder",outline:""})]),_:1}),u(p,null,{default:d(()=>[u(o,{text:e.folder.name},null,8,["text"])]),_:1})]),_:1},8,["active"]))}});const Y={class:"folders"},Z={class:"storages"};var ee=o({__name:"navigation",setup(e){const t=L(),a=D(),{nestedFolders:n,folders:o,loading:w,openFolders:k}=K(),{storages:x,loadStorages:_}=G(),S=l(()=>{if(!t.path.startsWith("/storage-manager/folders"))return;const e=t.params.folderId;return e?String(e):void 0}),$=l(()=>{if(t.path.startsWith("/storage-manager/storage/"))return String(t.params.location||"")||void 0}),M=l(()=>"/storage-manager/folders"===t.path||"/storage-manager/folders/"===t.path),B=l(()=>"/storage-manager"===t.path||"/storage-manager/"===t.path);function F(e){e.folder?a.push(`/storage-manager/folders/${e.folder}`):a.push("/storage-manager/folders")}function C(){a.push("/storage-manager")}return p([S,w],function(){if(!o.value)return;if(!k?.value)return;const e=[],t=o.value.find(e=>e.id===S.value);t?.parent&&function t(a){if(!o.value)return;e.push(a);const n=o.value.find(e=>e.id===a);n?.parent&&t(n.parent)}(t.parent);const a=[...k.value];for(const t of e)!1===a.includes(t)&&a.push(t);1!==a.length&&JSON.stringify(a)!==JSON.stringify(k.value)&&(k.value=a)},{immediate:!0}),m(()=>{_().catch(()=>{})}),(e,t)=>{const l=r("v-icon"),o=r("v-list-item-icon"),p=r("v-text-overflow"),m=r("v-list-item-content"),_=r("v-list-item"),z=r("v-skeleton-loader"),I=r("v-list-group"),V=r("v-item-group"),O=r("v-divider"),N=r("v-list");return i(),s(N,{nav:""},{default:d(()=>[u(_,{clickable:"",active:B.value,onClick:C},{default:d(()=>[u(o,null,{default:d(()=>[u(l,{name:"dashboard"})]),_:1}),u(m,null,{default:d(()=>[u(p,{text:"Storage Manager"})]),_:1})]),_:1},8,["active"]),!g(w)||g(n)&&0!==g(n).length?b("v-if",!0):(i(),c(f,{key:0},v(4,e=>u(_,{key:e},{default:d(()=>[u(z,{type:"list-item-icon"})]),_:1})),64)),h("div",Y,[u(V,{modelValue:g(k),"onUpdate:modelValue":t[1]||(t[1]=e=>y(k)?k.value=e:null),scope:"files-navigation",multiple:""},{default:d(()=>[u(I,{clickable:"",active:M.value||Boolean(S.value),value:"root",scope:"files-navigation",exact:"","disable-groupable-parent":"","arrow-placement":!!(g(n)&&g(n).length>0)&&"after",onClick:t[0]||(t[0]=e=>F({}))},{activator:d(()=>[u(o,null,{default:d(()=>[u(l,{name:"folder_special",outline:""})]),_:1}),u(m,null,{default:d(()=>[u(p,{text:"Folders"})]),_:1})]),default:d(()=>[(i(!0),c(f,null,v(g(n)||[],e=>(i(),s(X,{key:e.id,folder:e,"current-folder":S.value,"click-handler":F},null,8,["folder","current-folder"]))),128))]),_:1},8,["active","arrow-placement"])]),_:1},8,["modelValue"])]),u(O),t[2]||(t[2]=h("div",{class:"section-label"},"Storage adapters",-1)),h("div",Z,[(i(!0),c(f,null,v(g(x),e=>(i(),s(_,{key:e.location,clickable:"",active:$.value===e.location,onClick:t=>{return n=e.location,void a.push(`/storage-manager/storage/${n}`);var n}},{default:d(()=>[u(o,null,{default:d(()=>[u(l,{name:e.icon},null,8,["name"])]),_:2},1024),u(m,null,{default:d(()=>[u(p,{text:e.location},null,8,["text"])]),_:2},1024)]),_:2},1032,["active","onClick"]))),128))])]),_:1})}}}),te=[],ae=[];function ne(e,t){if(e&&"undefined"!=typeof document){var a,n=!0===t.prepend?"prepend":"append",l=!0===t.singleTag,o="string"==typeof t.container?document.querySelector(t.container):document.getElementsByTagName("head")[0];if(l){var r=te.indexOf(o);-1===r&&(r=te.push(o)-1,ae[r]={}),a=ae[r]&&ae[r][n]?ae[r][n]:ae[r][n]=i()}else a=i();65279===e.charCodeAt(0)&&(e=e.substring(1)),a.styleSheet?a.styleSheet.cssText+=e:a.appendChild(document.createTextNode(e))}function i(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),t.attributes)for(var a=Object.keys(t.attributes),l=0;l<a.length;l++)e.setAttribute(a[l],t.attributes[a[l]]);var r="prepend"===n?"afterbegin":"beforeend";return o.insertAdjacentElement(r,e),e}}ne("\n.v-skeleton-loader[data-v-b6d6d43c] {\n\t--v-skeleton-loader-background-color: var(--theme--background-accent);\n}\n.folders[data-v-b6d6d43c],\n.storages[data-v-b6d6d43c] {\n\twidth: 100%;\n\toverflow-x: hidden;\n}\n.folders[data-v-b6d6d43c] .v-list-item-content,\n.storages[data-v-b6d6d43c] .v-list-item-content {\n\toverflow: hidden;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n}\n.section-label[data-v-b6d6d43c] {\n\tpadding: 12px 12px 4px;\n\tfont-size: 11px;\n\tfont-weight: 700;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tcolor: var(--theme--foreground-subdued);\n}\n",{});var le=(e,t)=>{const a=e.__vccOpts||e;for(const[e,n]of t)a[e]=n;return a},oe=le(ee,[["__scopeId","data-v-b6d6d43c"]]);function re(e){if(null==e||!Number.isFinite(e)||e<0)return"—";if(0===e)return"0 B";const t=["B","KB","MB","GB","TB","PB"],a=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),n=e/Math.pow(1024,a),l=n>=100||0===a?0:n>=10?1:2;return`${n.toFixed(l)} ${t[a]}`}function ie(e){if(null==e||!Number.isFinite(e)||e<0)return"—";const t=8*e/1e6;return t<.1?`${t.toFixed(2)} Mbit/s`:t<10?`${t.toFixed(1)} Mbit/s`:`${Math.round(t)} Mbit/s`}function se(e){if(null==e||!Number.isFinite(e)||e<0)return"—";const t=Math.floor(e/1e3),a=Math.floor(t/3600),n=Math.floor(t%3600/60),l=t%60;return a>0?`${a}h ${n}m ${l}s`:n>0?`${n}m ${l}s`:`${l}s`}const de={class:"usage-top"},ue={class:"label"},ce={class:"value"},fe={key:0,class:"track"},ve={key:1,class:"secondary"};var pe=o({__name:"usage-bar",props:{usage:{},label:{default:"Usage"},compact:{type:Boolean,default:!1},plain:{type:Boolean,default:!1}},setup(e){const t=e,a=l(()=>t.usage.disk_available&&null!=t.usage.disk_used_percent),n=l(()=>{const e=t.usage.disk_used_percent??0;return Math.max(0,Math.min(100,e))}),o=l(()=>{const e=n.value;return e>=90?"danger":e>=75?"warning":"ok"}),r=l(()=>{return a.value?`${e=t.usage.disk_used_percent,null!=e&&Number.isFinite(e)?`${Math.round(e)}%`:"—"} used · ${re(t.usage.disk_used_bytes)} / ${re(t.usage.disk_total_bytes)}`:`${t.usage.file_count.toLocaleString()} files · ${re(t.usage.total_bytes)}`;var e}),s=l(()=>a.value?`${t.usage.file_count.toLocaleString()} files in Directus · ${re(t.usage.total_bytes)} · ${re(t.usage.disk_free_bytes)} free`:"Cloud quota not available — showing Directus-tracked size");return(t,l)=>(i(),c("div",{class:w(["usage-bar",{compact:e.compact,plain:e.plain}])},[h("div",de,[h("span",ue,k(e.label),1),h("span",ce,k(r.value),1)]),a.value?(i(),c("div",fe,[h("div",{class:w(["fill",o.value]),style:x({width:`${n.value}%`})},null,6)])):b("v-if",!0),s.value?(i(),c("div",ve,k(s.value),1)):b("v-if",!0)],2))}});ne("\n.usage-bar[data-v-475a6a41] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tpadding: 12px 14px;\n\tbackground: var(--theme--background-subdued);\n\tborder-radius: var(--theme--border-radius);\n\tborder: var(--theme--border-width) solid var(--theme--border-color-subdued);\n}\n.usage-bar.compact[data-v-475a6a41] {\n\tpadding: 8px 10px;\n}\n.usage-bar.plain[data-v-475a6a41] {\n\tpadding: 0;\n\tbackground: transparent;\n\tborder: none;\n\tborder-radius: 0;\n}\n.usage-top[data-v-475a6a41] {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 12px;\n\talign-items: baseline;\n}\n.label[data-v-475a6a41] {\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n}\n.value[data-v-475a6a41] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground);\n\ttext-align: right;\n}\n.track[data-v-475a6a41] {\n\theight: 8px;\n\tborder-radius: 999px;\n\tbackground: var(--theme--background-normal);\n\toverflow: hidden;\n}\n.fill[data-v-475a6a41] {\n\theight: 100%;\n\tborder-radius: inherit;\n\ttransition: width 200ms ease;\n}\n.fill.ok[data-v-475a6a41] {\n\tbackground: var(--theme--primary);\n}\n.fill.warning[data-v-475a6a41] {\n\tbackground: var(--theme--warning);\n}\n.fill.danger[data-v-475a6a41] {\n\tbackground: var(--theme--danger);\n}\n.secondary[data-v-475a6a41] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n",{});var me=le(pe,[["__scopeId","data-v-475a6a41"]]);const ge={class:"page"},be={key:0,class:"loading"},he={class:"cards"},ye=["onClick"],we={class:"card-head"},ke={class:"card-titles"},xe={class:"meta"},_e={key:0};var Se=o({__name:"overview-view",setup(e){const t=D(),{storages:a,storagesError:l,loadStorages:o}=G(),p=n(!0);return m(async function(){p.value=!0;try{await o(!0)}finally{p.value=!1}}),(e,n)=>{const o=r("v-breadcrumb"),m=r("sidebar-detail"),y=r("v-progress-circular"),w=r("v-notice"),x=r("v-icon"),S=r("private-view");return i(),s(S,{title:"Storage Manager",icon:"swap_horiz"},{headline:d(()=>[u(o,{items:[{name:"Storage Manager",to:"/storage-manager"}]})]),navigation:d(()=>[u(oe)]),sidebar:d(()=>[u(m,{id:"about",icon:"info",title:"About"},{default:d(()=>[...n[0]||(n[0]=[h("p",{class:"sidebar-text"},[_(" Copy or move files between configured storage adapters. Physical objects are streamed via Directus storage drivers; database "),h("code",null,"id"),_(" and "),h("code",null,"filename_disk"),_(" stay the same. ")],-1)])]),_:1})]),default:d(()=>[h("div",ge,[p.value&&!g(a).length?(i(),c("div",be,[u(y,{indeterminate:""})])):g(l)?(i(),s(w,{key:1,type:"danger"},{default:d(()=>[_(k(g(l)),1)]),_:1})):(i(),c(f,{key:2},[n[1]||(n[1]=h("p",{class:"intro"},[_(" Select a storage adapter to browse its files, or use Folders to migrate by virtual folder. Totals below come from "),h("code",null,"directus_files"),_("; local adapters also report filesystem capacity when available. ")],-1)),h("div",he,[(i(!0),c(f,null,v(g(a),e=>(i(),c("button",{key:e.location,type:"button",class:"card",onClick:a=>{return n=e.location,void t.push(`/storage-manager/storage/${n}`);var n}},[h("div",we,[u(x,{name:e.icon,large:""},null,8,["name"]),h("div",ke,[h("strong",null,k(e.location),1),h("span",null,k(e.label),1)])]),u(me,{usage:e},null,8,["usage"]),h("div",xe,[h("span",null,k(e.file_count.toLocaleString())+" files",1),e.root?(i(),c("span",_e,k(e.root),1)):b("v-if",!0)])],8,ye))),128))])],64))])]),_:1})}}});ne("\n.page[data-v-5089e94d] {\n\tpadding: var(--content-padding);\n\tpadding-block-end: var(--content-padding-bottom);\n}\n.loading[data-v-5089e94d] {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 64px;\n}\n.intro[data-v-5089e94d] {\n\tmax-width: 720px;\n\tcolor: var(--theme--foreground);\n\tmargin: 0 0 20px;\n}\n.sidebar-text[data-v-5089e94d] {\n\tmargin: 0;\n\tline-height: 1.45;\n}\n.cards[data-v-5089e94d] {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n\tgap: 16px;\n}\n.card[data-v-5089e94d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\ttext-align: left;\n\tpadding: 16px;\n\tborder-radius: var(--theme--border-radius);\n\tborder: var(--theme--border-width) solid var(--theme--border-color);\n\tbackground: var(--theme--background);\n\tcursor: pointer;\n\tcolor: inherit;\n\tfont: inherit;\n}\n.card[data-v-5089e94d]:hover {\n\tborder-color: var(--theme--primary);\n}\n.card-head[data-v-5089e94d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n.card-titles[data-v-5089e94d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n.card-titles span[data-v-5089e94d] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.meta[data-v-5089e94d] {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n",{});var $e=le(Se,[["__scopeId","data-v-5089e94d"]]);function Me(e){const t=n(0),a=n(0),l=new ResizeObserver(([e])=>{void 0!==e&&(t.value=e.contentRect.width,a.value=e.contentRect.height)});return m(()=>{const t=y(e)?e.value:e;null!=t&&l.observe(t)}),S(()=>{l.disconnect()}),{width:t,height:a}}const Be="wrapper",Fe=["selection","layoutOptions","layoutQuery"];function Ce(e){const{layouts:t}=function(){const e=$("extensions");if(!e)throw new Error("[useExtensions]: The extensions could not be found.");return e}(),a=l(()=>t.value.map(e=>function(e){return o({name:`${e.id}-${Be}`,props:{collection:{type:String,required:!0},selection:{type:Array,default:()=>[]},layoutOptions:{type:Object,default:()=>({})},layoutQuery:{type:Object,default:()=>({})},layoutProps:{type:Object,default:()=>({})},filter:{type:Object,default:null},filterUser:{type:Object,default:null},filterSystem:{type:Object,default:null},search:{type:String,default:null},showSelect:{type:String,default:"multiple"},selectMode:{type:Boolean,default:!1},readonly:{type:Boolean,default:!1},resetPreset:{type:Function,default:null},clearFilters:{type:Function,default:null}},emits:Fe.map(e=>`update:${e}`),setup(t,{emit:a}){const n=M({...e.setup(t,{emit:a}),...B(t),sidebarShadow:e.sidebarShadow??!1});for(const e in n)n[`onUpdate:${e}`]=l=>{var o;o=e,Fe.includes(o)?a(`update:${e}`,l):Object.keys(t).includes(e)||(n[e]=l)};return{state:n}},render:e=>void 0!==e.$slots.default?e.$slots.default({layoutState:e.state}):null})}(e)));return{layoutWrapper:l(()=>{const t=a.value.find(t=>t.name===`${e.value}-${Be}`);return void 0===t?a.value.find(e=>e.name===`tabular-${Be}`):t})}}const ze=["value","placeholder","tabindex","disabled"];var Ie=o({__name:"search-input",props:{modelValue:{},disabled:{type:Boolean},showFilter:{type:Boolean,default:!0},collection:{},filter:{},autofocus:{type:Boolean},placeholder:{}},emits:["update:modelValue","update:filter"],setup(e,{emit:t}){const a=e,o=t,v=n(null),g=n(Boolean(a.autofocus)),y=n(!1),k=n(!1),x=$("main-element",void 0),_=n(),{width:S}=Me(x??n(void 0)),{width:M}=Me(_);p([S,M],()=>{if(!_.value)return;const e=x?.value?.firstElementChild,t=_.value.parentElement;if(!t)return;const a=t.offsetWidth-4;if(!e)return void(_.value.style.maxInlineSize="");const n=t.getBoundingClientRect().right-(e.getBoundingClientRect().left+Number(window.getComputedStyle(e).paddingInlineStart.replace("px","")));_.value.style.maxInlineSize=n>a?`${String(n)}px`:"0px"},{immediate:!0}),m(()=>{g.value&&v.value?.focus()});const B=l(()=>{if(!a.filter)return 0;const e=[];return function t(a){for(const[n,l]of Object.entries(a))"_and"===n||"_or"===n?l?.forEach?.(t):n.startsWith("_")?e.push(n):l&&"object"==typeof l&&t(l)}(a.filter),e.length});function O(e){return!(e.path||e.composedPath?.()||[]).some(e=>e?.classList?.contains("v-menu-content"))}function N(){g.value||v.value?.focus(),g.value=!0}function T(){y.value=!y.value,g.value=!0,y.value||v.value?.focus()}function U(){o("update:modelValue",null),g.value&&v.value?.focus()}function L(){g.value=!1,y.value=!1,v.value?.blur()}function D(e){if(y.value)return;const t=e.currentTarget?.closest(".search-input"),a=e.relatedTarget;a&&t?.contains(a)||L()}function E(){v.value&&o("update:modelValue",v.value.value||null)}return p(y,e=>{k.value=!!e}),(t,a)=>{const n=r("v-icon"),l=r("interface-system-filter"),o=r("v-badge"),p=F("tooltip"),m=F("click-outside");return i(),s(o,{bottom:"",right:"",class:w(["search-badge",{active:g.value,"filter-active":y.value}]),value:B.value,disabled:e.disabled||!B.value||y.value},{default:d(()=>[C((i(),c("div",{class:w(["search-input",{active:g.value,disabled:e.disabled,"filter-active":y.value,"has-content":!!e.modelValue,"filter-border":k.value,"show-filter":e.showFilter}]),role:"search",onClick:N},[u(n,{small:"",name:"search",class:"icon-search",disabled:e.disabled,clickable:!g.value,onClick:a[0]||(a[0]=e=>v.value?.focus())},null,8,["disabled","clickable"]),h("input",{ref_key:"input",ref:v,value:e.modelValue,placeholder:e.placeholder??"Search items…",type:"search",spellcheck:"false",autocapitalize:"off",autocorrect:"off",autocomplete:"off",tabindex:g.value||e.modelValue?void 0:-1,disabled:e.disabled,onInput:E,onPaste:E,onKeydown:z(L,["esc"]),onFocusin:N,onFocusout:D},null,40,ze),a[2]||(a[2]=h("div",{class:"spacer"},null,-1)),e.modelValue?C((i(),s(n,{key:0,small:"",clickable:"",class:"icon-clear",name:"close",disabled:e.disabled,onClick:I(U,["stop"])},null,8,["disabled"])),[[p,"Clear value",void 0,{bottom:!0}]]):b("v-if",!0),e.showFilter?(i(),c(f,{key:1},[C(u(n,{small:"",clickable:"",class:"icon-filter",name:"filter_list",disabled:e.disabled,onClick:T},null,8,["disabled"]),[[p,!e.disabled&&"Filter",void 0,{bottom:!0}]]),C(h("div",{ref_key:"filterElement",ref:_,class:w(["filter",{active:g.value}])},[u(l,{class:"filter-input",inline:"",value:e.filter,"collection-name":e.collection,onInput:a[1]||(a[1]=e=>t.$emit("update:filter",e))},null,8,["value","collection-name"])],2),[[V,y.value]])],64)):b("v-if",!0)],2)),[[m,{handler:L,middleware:O,disabled:!g.value}]])]),_:1},8,["class","value","disabled"])}}});ne("\n/* Mirrors upstream search-input.vue styles (logical props → physical where needed). */\n.search-badge[data-v-576fbb1b] {\n\t--v-badge-background-color: var(--theme--primary);\n\t--v-badge-offset-y: 0.4375rem;\n\t--v-badge-offset-x: 0.4375rem;\n}\n@media (width <= 22.5rem) {\n.search-badge.active[data-v-576fbb1b],\n\t.search-badge.filter-active[data-v-576fbb1b] {\n\t\tposition: absolute;\n\t\tinset-inline: 0;\n\t\tz-index: 1;\n\t\tbackground-color: var(--theme--header--background);\n}\n}\n.search-badge[data-v-576fbb1b] .badge {\n\tpointer-events: none;\n}\n.search-input[data-v-576fbb1b] {\n\t--button-size: 2rem;\n\t--search-input-size: calc(var(--button-size) - var(--theme--border-width) * 2);\n\t--search-input-radius: calc(var(--button-size) / 2);\n\t--icon-size: 1rem;\n\t--icon-search-padding-left: 0.375rem;\n\t--icon-search-padding-right: 0.25rem;\n\t--icon-filter-margin-right: 0.4375rem;\n\n\tposition: relative;\n\tbox-sizing: content-box;\n\tdisplay: flex;\n\talign-items: center;\n\twidth: var(--search-input-size);\n\tmin-height: var(--search-input-size);\n\tmax-width: calc(100% - var(--theme--border-width) * 2);\n\toverflow: hidden;\n\tborder: var(--theme--border-width) solid var(--theme--form--field--input--border-color);\n\tborder-radius: var(--search-input-radius);\n\ttransition:\n\t\twidth var(--slow, 300ms) var(--transition, ease-in-out),\n\t\tborder-bottom-left-radius var(--fast, 125ms) var(--transition, ease-in-out),\n\t\tborder-bottom-right-radius var(--fast, 125ms) var(--transition, ease-in-out);\n}\n.search-input.show-filter[data-v-576fbb1b] {\n\twidth: calc(\n\t\tvar(--icon-size) * 2 + var(--icon-search-padding-left) + var(--icon-search-padding-right) +\n\t\t\tvar(--icon-filter-margin-right)\n\t);\n}\n.search-input input[data-v-576fbb1b] {\n\twidth: 0;\n\theight: 100%;\n\tmargin: 0;\n\tpadding: 0;\n\toverflow: hidden;\n\tcolor: var(--theme--foreground);\n\ttext-overflow: ellipsis;\n\tbackground-color: transparent;\n\tborder: none;\n\tborder-radius: 0;\n\tflex-grow: 1;\n\topacity: 0;\n\toutline: none;\n\tfont: inherit;\n}\n.search-input input[data-v-576fbb1b]::placeholder {\n\tcolor: var(--theme--foreground-subdued);\n}\n.search-input.disabled input[data-v-576fbb1b] {\n\tcolor: var(--theme--foreground-subdued);\n}\n.search-input .spacer[data-v-576fbb1b] {\n\twidth: 0.4375rem;\n}\n.search-input .icon-clear[data-v-576fbb1b] {\n\t--v-icon-color: var(--theme--foreground-subdued);\n\t--v-icon-color-hover: var(--theme--danger);\n\tmin-width: auto;\n\toverflow: hidden;\n}\n.search-input .icon-search[data-v-576fbb1b],\n.search-input .icon-filter[data-v-576fbb1b] {\n\t--v-icon-color-hover: var(--theme--primary);\n}\n.search-input.disabled .icon-search[data-v-576fbb1b],\n.search-input.disabled .icon-filter[data-v-576fbb1b] {\n\t--v-icon-color: var(--theme--foreground-subdued);\n}\n.search-input .icon-search[data-v-576fbb1b] {\n\tmargin-block: 0;\n\tmargin-inline: var(--icon-search-padding-left) var(--icon-search-padding-right);\n}\n.search-input .icon-filter[data-v-576fbb1b] {\n\tmargin-inline-end: var(--icon-filter-margin-right);\n}\n.search-input[data-v-576fbb1b]:focus-within,\n.search-input[data-v-576fbb1b]:not(.disabled):not(.active):hover {\n\tborder-color: var(--theme--form--field--input--border-color-hover);\n}\n.search-input.has-content[data-v-576fbb1b] {\n\twidth: 11.25rem;\n}\n.search-input.has-content .icon-clear[data-v-576fbb1b] {\n\tmargin-inline-end: 0.4375rem;\n}\n.search-input.has-content input[data-v-576fbb1b] {\n\topacity: 1;\n}\n.search-input.has-content.show-filter .icon-clear[data-v-576fbb1b] {\n\tmargin-inline-end: 0;\n}\n.search-input.active[data-v-576fbb1b] {\n\twidth: 100%;\n\tborder-color: var(--theme--form--field--input--border-color-focus);\n}\n@media (width > 22.5rem) {\n.search-input.active[data-v-576fbb1b] {\n\t\twidth: 8.4375rem;\n}\n}\n@media (width >= 48rem) {\n.search-input.active[data-v-576fbb1b] {\n\t\twidth: 11.25rem;\n}\n}\n.search-input.active input[data-v-576fbb1b] {\n\topacity: 1;\n}\n.search-input.filter-active[data-v-576fbb1b] {\n\twidth: 100%;\n}\n.search-input.filter-active .icon-filter[data-v-576fbb1b] {\n\t--v-icon-color: var(--theme--primary);\n}\n@media (width > 22.5rem) {\n.search-input.filter-active[data-v-576fbb1b] {\n\t\twidth: 8.4375rem;\n}\n}\n@media (width >= 48rem) {\n.search-input.filter-active[data-v-576fbb1b] {\n\t\twidth: 11.25rem;\n}\n}\n@media (width >= 54rem) {\n.search-input.filter-active[data-v-576fbb1b] {\n\t\twidth: 16.875rem;\n}\n}\n@media (width >= 70.875rem) {\n.search-input.filter-active[data-v-576fbb1b] {\n\t\twidth: 23.625rem;\n}\n}\n.search-input.filter-border[data-v-576fbb1b] {\n\tpadding-block-end: var(--theme--border-width);\n\tborder-block-end: none;\n\tborder-bottom-right-radius: 0;\n\tborder-bottom-left-radius: 0;\n\ttransition:\n\t\tborder-bottom-left-radius 0s,\n\t\tborder-bottom-right-radius 0s;\n}\n.search-input.filter-border[data-v-576fbb1b]::after {\n\tposition: absolute;\n\tinset-inline: var(--theme--border-width) var(--theme--border-width);\n\tbottom: calc(-1 * var(--theme--border-width));\n\twidth: auto;\n\theight: var(--theme--border-width);\n\tbackground-color: var(--theme--border-color-subdued);\n\tcontent: '';\n\tpointer-events: none;\n}\n\n/* Allow the absolute filter panel to paint below the collapsed pill (upstream uses TransitionExpand). */\n.search-input.filter-active[data-v-576fbb1b],\n.search-input.filter-border[data-v-576fbb1b] {\n\toverflow: visible;\n}\n.filter[data-v-576fbb1b] {\n\tposition: absolute;\n\ttop: 100%;\n\tright: 0;\n\tz-index: 10;\n\twidth: auto;\n\tmin-width: 100%;\n\tpadding: 0;\n\tbackground-color: var(--theme--background-subdued);\n\tborder: var(--theme--border-width) solid var(--theme--form--field--input--border-color);\n\tborder-top-right-radius: 0;\n\tborder-bottom-right-radius: var(--search-input-radius);\n\tborder-bottom-left-radius: var(--search-input-radius);\n}\n.filter.active[data-v-576fbb1b] {\n\tborder-color: var(--theme--form--field--input--border-color-focus);\n}\n.filter .filter-input[data-v-576fbb1b] {\n\tmargin: 0.5625rem 0.4375rem;\n}\n",{});var Ve=le(Ie,[["__scopeId","data-v-576fbb1b"]]),Oe=o({__name:"header-action-button",props:{icon:{},disabled:{type:Boolean},loading:{type:Boolean},secondary:{type:Boolean},outlined:{type:Boolean},to:{},active:{type:Boolean}},emits:["click"],setup:e=>(t,a)=>{const n=r("v-icon"),l=r("v-button");return i(),s(l,{disabled:e.disabled,active:e.active,loading:e.loading,secondary:e.secondary,outlined:e.outlined,to:e.to,icon:"",rounded:"",small:"",onClick:a[0]||(a[0]=e=>t.$emit("click"))},{default:d(()=>[u(n,{name:e.icon,small:""},null,8,["name"])]),_:1},8,["disabled","active","loading","secondary","outlined","to"])}});const Ne={class:"layout-options"},Te={class:"field"};var Ue=o({__name:"layout-sidebar-detail",props:{modelValue:{}},emits:["update:modelValue"],setup(e,{emit:t}){const a=e,n=t,o=l({get:()=>a.modelValue??"cards",set(e){n("update:modelValue",e)}}),c=[{text:"Cards",value:"cards",icon:"grid_view"},{text:"Table",value:"tabular",icon:"table_rows"}];return(e,t)=>{const a=r("v-select"),n=r("sidebar-detail");return i(),s(n,{id:"layout",icon:"layers",title:"Layout Options"},{default:d(()=>[h("div",Ne,[h("div",Te,[t[1]||(t[1]=h("div",{class:"type-label"},"Layout",-1)),u(a,{modelValue:o.value,"onUpdate:modelValue":t[0]||(t[0]=e=>o.value=e),items:c,"item-text":"text","item-value":"value","item-icon":"icon"},null,8,["modelValue"])]),O(e.$slots,"default",{},void 0,!0)])]),_:3})}}});ne("\n.layout-options[data-v-65862970] {\n\t--theme--form--row-gap: 1.125rem;\n\tmargin-bottom: 0.25rem;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--theme--form--row-gap);\n}\n.field[data-v-65862970] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n.type-label[data-v-65862970] {\n\tfont-weight: 600;\n\tfont-size: 13px;\n}\n",{});var Le=le(Ue,[["__scopeId","data-v-65862970"]]);const De={class:"progress-panel"},Ee={class:"route"},je={class:"adapter"},Ae={class:"adapter"},qe={class:"file-line"},Pe={class:"file-count"},Re=["title"],Ke={class:"bar-wrap"},We={class:"bar-track"},Je={class:"bar-meta"},Qe={class:"stats"},He={class:"stat"},Ge={class:"stat-value"},Xe={class:"stat"},Ye={class:"stat-value"},Ze={class:"stat"},et={class:"stat-value"},tt={class:"counts"},at={class:"ok"},nt={class:"skip"},lt={class:"fail"};var ot=o({__name:"migrate-progress",props:{storages:{},mode:{},from:{},to:{},currentIndex:{},totalFiles:{},currentName:{},transferredBytes:{},totalBytes:{},elapsedMs:{},speedBps:{},succeeded:{},skipped:{},failed:{},isDone:{type:Boolean}},setup(e){const t=e;function a(e){if(!e)return{short:"—",icon:"storage"};return t.storages.find(t=>t.location===e)||{short:e,icon:"storage",location:e}}const n=l(()=>a(t.from)),o=l(()=>a(t.to)),s=l(()=>t.from?`${t.from} (${n.value.short})`:"Mixed sources"),f=l(()=>`${t.to} (${o.value.short})`),v=l(()=>n.value.icon),p=l(()=>o.value.icon),m=l(()=>"copy"===t.mode?"Copy":"Move"),b=l(()=>t.totalBytes>0?Math.max(0,Math.min(100,t.transferredBytes/t.totalBytes*100)):t.totalFiles>0?Math.max(0,Math.min(100,t.currentIndex/t.totalFiles*100)):0),y=l(()=>Math.round(b.value)),w=l(()=>t.isDone?t.failed>0?"Completed with errors":"Completed":"Transferring");return(t,a)=>{const n=r("v-icon"),l=r("v-chip");return i(),c("div",De,[h("div",Ee,[h("div",je,[u(n,{name:v.value},null,8,["name"]),h("span",null,k(s.value),1)]),u(n,{class:"arrow",name:"arrow_forward"}),h("div",Ae,[u(n,{name:p.value},null,8,["name"]),h("span",null,k(f.value),1)]),u(l,{"x-small":"",class:"mode-chip"},{default:d(()=>[_(k(m.value),1)]),_:1})]),h("div",qe,[h("span",Pe,"File "+k(e.currentIndex)+" / "+k(e.totalFiles),1),h("span",{class:"file-name",title:e.currentName},k(e.currentName||"Preparing…"),9,Re)]),h("div",Ke,[h("div",We,[h("div",{class:"bar-fill",style:x({width:`${b.value}%`})},null,4)]),h("div",Je,[h("strong",null,k(y.value)+"%",1),h("span",null,k(g(re)(e.transferredBytes))+" / "+k(g(re)(e.totalBytes)),1)])]),h("div",Qe,[h("div",He,[a[0]||(a[0]=h("span",{class:"stat-label"},"Speed",-1)),h("span",Ge,k(g(ie)(e.speedBps)),1)]),h("div",Xe,[a[1]||(a[1]=h("span",{class:"stat-label"},"Elapsed",-1)),h("span",Ye,k(g(se)(e.elapsedMs)),1)]),h("div",Ze,[a[2]||(a[2]=h("span",{class:"stat-label"},"Status",-1)),h("span",et,k(w.value),1)])]),h("div",tt,[h("span",at,k(e.succeeded)+" ok",1),h("span",nt,k(e.skipped)+" skipped",1),h("span",lt,k(e.failed)+" failed",1)])])}}});ne("\n.progress-panel[data-v-f85caf88] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tpadding: 16px;\n\tborder-radius: var(--theme--border-radius);\n\tborder: var(--theme--border-width) solid var(--theme--border-color);\n\tbackground: var(--theme--background-subdued);\n}\n.route[data-v-f85caf88] {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 10px;\n}\n.adapter[data-v-f85caf88] {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-weight: 600;\n}\n.arrow[data-v-f85caf88] {\n\tcolor: var(--theme--foreground-subdued);\n}\n.mode-chip[data-v-f85caf88] {\n\tmargin-left: auto;\n}\n.file-line[data-v-f85caf88] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmin-width: 0;\n}\n.file-count[data-v-f85caf88] {\n\tfont-size: 12px;\n\tfont-weight: 700;\n\tletter-spacing: 0.02em;\n\ttext-transform: uppercase;\n\tcolor: var(--theme--foreground-subdued);\n}\n.file-name[data-v-f85caf88] {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.bar-wrap[data-v-f85caf88] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n.bar-track[data-v-f85caf88] {\n\theight: 10px;\n\tborder-radius: 999px;\n\tbackground: var(--theme--background-normal);\n\toverflow: hidden;\n}\n.bar-fill[data-v-f85caf88] {\n\theight: 100%;\n\tborder-radius: inherit;\n\tbackground: var(--theme--primary);\n\ttransition: width 160ms ease;\n}\n.bar-meta[data-v-f85caf88] {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tfont-size: 13px;\n}\n.stats[data-v-f85caf88] {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(3, minmax(0, 1fr));\n\tgap: 8px;\n}\n.stat[data-v-f85caf88] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tpadding: 8px 10px;\n\tborder-radius: var(--theme--border-radius);\n\tbackground: var(--theme--background);\n}\n.stat-label[data-v-f85caf88] {\n\tfont-size: 11px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.03em;\n\tcolor: var(--theme--foreground-subdued);\n}\n.stat-value[data-v-f85caf88] {\n\tfont-weight: 700;\n\tfont-variant-numeric: tabular-nums;\n}\n.counts[data-v-f85caf88] {\n\tdisplay: flex;\n\tgap: 12px;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.counts .ok[data-v-f85caf88] {\n\tcolor: var(--theme--success);\n}\n.counts .fail[data-v-f85caf88] {\n\tcolor: var(--theme--danger);\n}\n",{});var rt=le(ot,[["__scopeId","data-v-f85caf88"]]);const it={class:"drawer-body"},st={class:"intro"},dt={class:"field"},ut={class:"field"},ct={class:"mode-radios"},ft={key:0,class:"field"},vt={key:2,class:"result"},pt={key:0,class:"failures"};var mt=o({__name:"migrate-drawer",props:{modelValue:{type:Boolean},storages:{},sourceStorage:{},selectionKind:{},fileIds:{},folderId:{},estimatedCount:{},estimatedBytes:{}},emits:["update:modelValue","done"],setup(e,{emit:t}){const a=e,o=t,{migrateWithProgress:m}=G(),g=n(null),y=n("move"),w=n(!0),x=n(!1),S=n(null);let $=null;const B=M({from:null,to:"",currentIndex:0,totalFiles:0,currentName:"",transferredBytes:0,totalBytes:0,elapsedMs:0,speedBps:0,succeeded:0,skipped:0,failed:0});function z(e=""){B.from=a.sourceStorage||null,B.to=e,B.currentIndex=0,B.totalFiles=a.estimatedCount||a.fileIds?.length||0,B.currentName="",B.transferredBytes=0,B.totalBytes=a.estimatedBytes||0,B.elapsedMs=0,B.speedBps=0,B.succeeded=0,B.skipped=0,B.failed=0}p(()=>a.modelValue,e=>{if(e){S.value=null,x.value=!1,$?.abort(),$=null;const e=a.storages.find(e=>e.location!==a.sourceStorage);g.value=e?.location||null,y.value="move",w.value=!0,z(e?.location||"")}});const I=l(()=>a.storages.filter(e=>e.location!==a.sourceStorage).map(e=>({text:`${e.location} (${e.short})`,value:e.location}))),V=l(()=>"move"===y.value?"Move":"Copy"),O=l(()=>Boolean(g.value)&&!x.value),N=l(()=>{const e=S.value?.total??(x.value&&B.totalFiles>0?B.totalFiles:null)??a.estimatedCount??a.fileIds?.length??0,t=S.value?.total_bytes??(x.value&&B.totalBytes>0?B.totalBytes:null)??a.estimatedBytes,n=null!=t?` (${re(t)})`:"",l=Boolean(S.value);return"storage"===a.selectionKind?l?`Migrated ${e.toLocaleString()} files${n} from “${a.sourceStorage}”.`:`Migrate all ${e.toLocaleString()} files${n} on “${a.sourceStorage}”.`:"folder"===a.selectionKind?l?`Migrated ${e.toLocaleString()} files${n} from the selected folder.`:`Migrate ${e.toLocaleString()} files${n} in the selected folder.`:l?`Migrated ${e.toLocaleString()} selected file(s)${n}.`:`Migrate ${e.toLocaleString()} selected file(s)${n}.`}),T=l(()=>(S.value?.results||[]).filter(e=>"failed"===e.status));function U(e){!e&&x.value||o("update:modelValue",e)}function L(){x.value?D():o("update:modelValue",!1)}function D(){$?.abort()}function E(e){return"start"===e.type?(B.from=e.from||a.sourceStorage||null,B.to=e.to,B.totalFiles=e.total,B.totalBytes=e.total_bytes,B.currentIndex=0,B.currentName="",B.transferredBytes=0,B.elapsedMs=0,B.speedBps=0,B.succeeded=0,B.skipped=0,void(B.failed=0)):"file_start"===e.type?(B.currentIndex=e.index,B.totalFiles=e.total,B.currentName=e.name||e.filename_disk,B.from||(B.from=e.from),void(B.to=e.to)):"file_bytes"===e.type?(B.currentIndex=e.index,B.transferredBytes=e.transferred_bytes,B.totalBytes=e.total_bytes||B.totalBytes,B.elapsedMs=e.elapsed_ms,void(B.speedBps=e.elapsed_ms>0?1e3*e.transferred_bytes/e.elapsed_ms:0)):"file_done"===e.type?(B.currentIndex=e.index,B.totalFiles=e.total,B.currentName=e.name||e.result.filename_disk,B.succeeded=e.succeeded,B.skipped=e.skipped,B.failed=e.failed,B.transferredBytes=e.transferred_bytes,B.totalBytes=e.total_bytes||B.totalBytes,B.elapsedMs=e.elapsed_ms,void(B.speedBps=e.elapsed_ms>0?1e3*e.transferred_bytes/e.elapsed_ms:0)):void("done"===e.type&&(B.succeeded=e.data.succeeded,B.skipped=e.data.skipped,B.failed=e.data.failed,B.transferredBytes=e.data.transferred_bytes??B.transferredBytes,B.totalBytes=e.data.total_bytes??B.totalBytes,B.elapsedMs=e.data.elapsed_ms??B.elapsedMs,B.totalFiles=e.data.total,B.currentIndex=e.data.total,B.speedBps=B.elapsedMs>0?1e3*B.transferredBytes/B.elapsedMs:0))}async function j(){if(g.value&&!x.value){x.value=!0,S.value=null,z(g.value),$=new AbortController;try{const e={target_storage:g.value,mode:y.value,concurrency:1};"files"===a.selectionKind?e.file_ids=a.fileIds||[]:"storage"===a.selectionKind?e.source_storage=a.sourceStorage||void 0:"folder"===a.selectionKind&&(e.folder_id=a.folderId??null,e.recursive=w.value,a.sourceStorage&&(e.source_storage=a.sourceStorage));const t=await m(e,E,$.signal);S.value=t,o("done",t)}catch(e){S.value="AbortError"===e?.name?{mode:y.value,target_storage:g.value,total:B.totalFiles,succeeded:B.succeeded,skipped:B.skipped,failed:B.failed+1,results:[{id:"",filename_disk:"",from:B.from||"",to:g.value,status:"failed",error:"Cancelled by user"}],transferred_bytes:B.transferredBytes,total_bytes:B.totalBytes,elapsed_ms:B.elapsedMs}:{mode:y.value,target_storage:g.value,total:B.totalFiles,succeeded:B.succeeded,skipped:B.skipped,failed:Math.max(1,B.failed),results:[{id:"",filename_disk:"",from:B.from||"",to:g.value,status:"failed",error:e?.response?.data?.errors?.[0]?.message||e?.message||"Migration failed"}],transferred_bytes:B.transferredBytes,total_bytes:B.totalBytes,elapsed_ms:B.elapsedMs}}finally{x.value=!1,$=null}}}return(t,a)=>{const n=r("v-icon"),l=r("v-button"),o=r("v-select"),p=r("v-radio"),m=r("v-checkbox"),$=r("v-notice"),M=r("v-drawer"),z=F("tooltip");return i(),s(M,{"model-value":e.modelValue,title:"Migrate Files",icon:"swap_horiz",persistent:"","onUpdate:modelValue":U,onCancel:L},{actions:d(()=>[x.value||S.value?x.value?C((i(),s(l,{key:1,icon:"",rounded:"",secondary:"",onClick:D},{default:d(()=>[u(n,{name:"close"})]),_:1})),[[z,"Cancel",void 0,{bottom:!0}]]):b("v-if",!0):C((i(),s(l,{key:0,disabled:!O.value,icon:"",rounded:"",onClick:j},{default:d(()=>[u(n,{name:"check"})]),_:1},8,["disabled"])),[[z,V.value,void 0,{bottom:!0}]])]),default:d(()=>[h("div",it,[h("p",st,k(N.value),1),x.value||S.value?b("v-if",!0):(i(),c(f,{key:0},[h("div",dt,[a[4]||(a[4]=h("label",null,"Target storage",-1)),u(o,{modelValue:g.value,"onUpdate:modelValue":a[0]||(a[0]=e=>g.value=e),items:I.value,"item-text":"text","item-value":"value",placeholder:"Select target…"},null,8,["modelValue","items"])]),h("div",ut,[a[5]||(a[5]=h("label",null,"Mode",-1)),h("div",ct,[u(p,{modelValue:y.value,"onUpdate:modelValue":a[1]||(a[1]=e=>y.value=e),value:"move",label:"Move — delete source after verify",block:""},null,8,["modelValue"]),u(p,{modelValue:y.value,"onUpdate:modelValue":a[2]||(a[2]=e=>y.value=e),value:"copy",label:"Copy — leave source object as orphan",block:""},null,8,["modelValue"])]),a[6]||(a[6]=h("p",{class:"note"}," Files keep the same identity in Directus; only their storage location changes. Image thumbnails are moved or copied with them when possible. Move removes the originals from the old storage; Copy leaves them behind as unused files. ",-1))]),"folder"===e.selectionKind?(i(),c("div",ft,[u(m,{modelValue:w.value,"onUpdate:modelValue":a[3]||(a[3]=e=>w.value=e),label:"Include files in subfolders"},null,8,["modelValue"])])):b("v-if",!0)],64)),x.value||S.value?(i(),s(rt,{key:1,storages:e.storages,mode:y.value,from:B.from,to:B.to,"current-index":B.currentIndex,"total-files":B.totalFiles,"current-name":B.currentName,"transferred-bytes":B.transferredBytes,"total-bytes":B.totalBytes,"elapsed-ms":B.elapsedMs,"speed-bps":B.speedBps,succeeded:B.succeeded,skipped:B.skipped,failed:B.failed,"is-done":Boolean(S.value)&&!x.value},null,8,["storages","mode","from","to","current-index","total-files","current-name","transferred-bytes","total-bytes","elapsed-ms","speed-bps","succeeded","skipped","failed","is-done"])):b("v-if",!0),S.value?(i(),c("div",vt,[u($,{type:S.value.failed?"danger":"success"},{default:d(()=>[_(k(S.value.succeeded)+" succeeded · "+k(S.value.skipped)+" skipped · "+k(S.value.failed)+" failed (of "+k(S.value.total)+") ",1)]),_:1},8,["type"]),T.value.length?(i(),c("div",pt,[(i(!0),c(f,null,v(T.value.slice(0,20),e=>(i(),c("div",{key:e.id,class:"fail-row"},[h("strong",null,k(e.filename_disk||e.id),1),h("span",null,k(e.error),1)]))),128))])):b("v-if",!0)])):b("v-if",!0)])]),_:1},8,["model-value"])}}});ne("\n.drawer-body[data-v-f890b7b4] {\n\tpadding: 20px 24px 32px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 18px;\n}\n.intro[data-v-f890b7b4] {\n\tmargin: 0;\n\tcolor: var(--theme--foreground);\n}\n.field[data-v-f890b7b4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n.field label[data-v-f890b7b4] {\n\tfont-weight: 600;\n}\n.mode-radios[data-v-f890b7b4] {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 1fr;\n\tgap: 10px;\n}\n.mode-radios[data-v-f890b7b4] .v-radio.block {\n\twidth: 100%;\n\tmargin: 0;\n}\n.note[data-v-f890b7b4] {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.result[data-v-f890b7b4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n}\n.failures[data-v-f890b7b4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tmax-height: 240px;\n\toverflow: auto;\n\tpadding: 8px 0;\n}\n.fail-row[data-v-f890b7b4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tfont-size: 12px;\n\tpadding: 8px 10px;\n\tbackground: var(--theme--background-subdued);\n\tborder-radius: var(--theme--border-radius);\n}\n.fail-row span[data-v-f890b7b4] {\n\tcolor: var(--theme--danger);\n}\n",{});var gt=le(mt,[["__scopeId","data-v-f890b7b4"]]);const bt={class:"body"},ht={class:"intro"},yt={class:"toolbar"},wt={key:0,class:"meta"},kt={key:1,class:"loading"},xt={key:2,class:"error"},_t={key:3,class:"empty"},St={key:4,class:"table-wrap"},$t=["title"],Mt=["title"];var Bt=o({__name:"import-orphans-drawer",props:{modelValue:{type:Boolean},location:{}},emits:["update:modelValue","imported"],setup(t,{emit:a}){const l=t,o=a,m=e(),y=n(!1),x=n(!1),S=n(!1),$=n(!1),M=n(null),B=n([]),z=n(null),I=n(null);function V(){S.value=!1,o("update:modelValue",!1)}function O(){B.value.length&&!x.value&&(S.value=!0)}async function N(){if(l.location){y.value=!0,M.value=null;try{const e=await m.get(`/storage-manager/storages/${encodeURIComponent(l.location)}/orphans`);B.value=e.data?.data||[],z.value=e.data?.meta||null,$.value=!0}catch(e){M.value=e?.response?.data?.errors?.[0]?.message||e?.message||String(e),B.value=[],z.value=null}finally{y.value=!1}}}async function T(){if(B.value.length){x.value=!0,M.value=null;try{const e=await m.post(`/storage-manager/storages/${encodeURIComponent(l.location)}/import-orphans`,{filename_disks:B.value.map(e=>e.filename_disk)}),t=e.data?.data;I.value={imported:Number(t?.imported||0),skipped:Number(t?.skipped||0),failed:Number(t?.failed||0)},S.value=!1,o("imported"),await N()}catch(e){M.value=e?.response?.data?.errors?.[0]?.message||e?.message||String(e),S.value=!1}finally{x.value=!1}}}return p(()=>l.modelValue,e=>{e&&(I.value=null,M.value=null,S.value=!1,N())}),(e,a)=>{const n=r("v-icon"),l=r("v-button"),o=r("v-progress-circular"),p=r("v-drawer"),m=r("v-card-title"),U=r("v-card-text"),L=r("v-card-actions"),D=r("v-card"),E=r("v-dialog"),j=F("tooltip");return i(),c(f,null,[u(p,{"model-value":t.modelValue,title:`Detect files on ${t.location}`,icon:"folder_off","onUpdate:modelValue":a[0]||(a[0]=t=>e.$emit("update:modelValue",t)),onCancel:V},{actions:d(()=>[C((i(),s(l,{icon:"",rounded:"",disabled:!B.value.length||x.value||y.value,loading:x.value,onClick:O},{default:d(()=>[u(n,{name:"download"})]),_:1},8,["disabled","loading"])),[[j,"Import all unknown files",void 0,{bottom:!0}]])]),default:d(()=>[h("div",bt,[h("p",ht,[a[4]||(a[4]=_(" Scans storage ",-1)),h("strong",null,k(t.location),1),a[5]||(a[5]=_(" for objects on disk that are not in ",-1)),a[6]||(a[6]=h("code",null,"directus_files",-1)),a[7]||(a[7]=_(" (by ",-1)),a[8]||(a[8]=h("code",null,"filename_disk",-1)),a[9]||(a[9]=_("). Import keeps the exact ",-1)),a[10]||(a[10]=h("code",null,"filename_disk",-1)),a[11]||(a[11]=_(" and sets ",-1)),a[12]||(a[12]=h("code",null,"title",-1)),a[13]||(a[13]=_(" from the name with ",-1)),a[14]||(a[14]=h("code",null,"_",-1)),a[15]||(a[15]=_(" → spaces. Files are not copied — only DB rows are created. Generated Directus image transforms/thumbnails are excluded automatically (filenames ending in ",-1)),a[16]||(a[16]=h("code",null,"__{hash}",-1)),a[17]||(a[17]=_(" or ",-1)),a[18]||(a[18]=h("code",null,"__{hash}.ext",-1)),a[19]||(a[19]=_(", from ",-1)),a[20]||(a[20]=h("code",null,"AssetsService",-1)),a[21]||(a[21]=_(" / ",-1)),a[22]||(a[22]=h("code",null,"getAssetSuffix",-1)),a[23]||(a[23]=_("). ",-1))]),h("div",yt,[u(l,{secondary:"",loading:y.value,onClick:N},{default:d(()=>[u(n,{name:"radar",left:""}),a[24]||(a[24]=_(" Scan disk ",-1))]),_:1},8,["loading"]),u(l,{disabled:!B.value.length||x.value||y.value,loading:x.value,onClick:O},{default:d(()=>[_(" Import "+k(B.value.length||"")+" new file"+k(1===B.value.length?"":"s"),1)]),_:1},8,["disabled","loading"])]),z.value?(i(),c("p",wt," Scanned "+k(z.value.scanned.toLocaleString())+" disk objects · "+k(z.value.known.toLocaleString())+" already in DB · "+k(z.value.orphan_count.toLocaleString())+" unknown ",1)):b("v-if",!0),y.value?(i(),c("div",kt,[u(o,{indeterminate:""}),a[25]||(a[25]=h("span",null,"Listing storage…",-1))])):M.value?(i(),c("p",xt,k(M.value),1)):$.value&&!B.value.length?(i(),c("p",_t," No unknown files on this storage. Everything on disk is already in the database. ")):B.value.length?(i(),c("div",St,[h("table",null,[a[26]||(a[26]=h("thead",null,[h("tr",null,[h("th",null,"filename_disk"),h("th",null,"Title (will be)"),h("th",null,"Type"),h("th",null,"Size")])],-1)),h("tbody",null,[(i(!0),c(f,null,v(B.value,e=>(i(),c("tr",{key:e.filename_disk},[h("td",{class:"name",title:e.filename_disk},k(e.filename_disk),9,$t),h("td",{class:"title-cell",title:e.title},k(e.title),9,Mt),h("td",null,k(e.type||"—"),1),h("td",null,k(g(re)(e.filesize)),1)]))),128))])])])):b("v-if",!0),I.value?(i(),c("div",{key:5,class:w(["result",I.value.failed?"has-fail":"ok"])}," Imported "+k(I.value.imported)+" · skipped "+k(I.value.skipped)+" · failed "+k(I.value.failed),3)):b("v-if",!0)])]),_:1},8,["model-value","title"]),u(E,{"model-value":S.value,"onUpdate:modelValue":a[2]||(a[2]=e=>S.value=e),onEsc:a[3]||(a[3]=e=>S.value=!1)},{default:d(()=>[u(D,null,{default:d(()=>[u(m,null,{default:d(()=>[...a[27]||(a[27]=[_("Import new files?",-1)])]),_:1}),u(U,null,{default:d(()=>[a[28]||(a[28]=_(" Do you want to import ",-1)),h("strong",null,k(B.value.length.toLocaleString()),1),_(" new file"+k(1===B.value.length?"":"s")+" into storage ",1),h("strong",null,k(t.location),1),a[29]||(a[29]=_("? ",-1)),a[30]||(a[30]=h("br",null,null,-1)),a[31]||(a[31]=h("br",null,null,-1)),a[32]||(a[32]=_(" Each file keeps its exact ",-1)),a[33]||(a[33]=h("code",null,"filename_disk",-1)),a[34]||(a[34]=_(". Titles are generated from the filename with underscores replaced by spaces (extension stripped). ",-1))]),_:1}),u(L,null,{default:d(()=>[u(l,{secondary:"",disabled:x.value,onClick:a[1]||(a[1]=e=>S.value=!1)},{default:d(()=>[...a[35]||(a[35]=[_("Cancel",-1)])]),_:1},8,["disabled"]),u(l,{loading:x.value,onClick:T},{default:d(()=>[_(" Import "+k(B.value.length.toLocaleString()),1)]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1},8,["model-value"])],64)}}});ne("\n.body[data-v-1dff44f5] {\n\tpadding: 0 24px 24px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n}\n.intro[data-v-1dff44f5] {\n\tmargin: 0;\n\tline-height: 1.45;\n\tcolor: var(--theme--foreground);\n\tfont-size: 13px;\n}\n.intro code[data-v-1dff44f5],\n.v-card-text code[data-v-1dff44f5] {\n\tfont-size: 12px;\n}\n.toolbar[data-v-1dff44f5] {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n.meta[data-v-1dff44f5],\n.empty[data-v-1dff44f5] {\n\tmargin: 0;\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.loading[data-v-1dff44f5] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.error[data-v-1dff44f5] {\n\tmargin: 0;\n\tcolor: var(--theme--danger);\n\tfont-size: 13px;\n}\n.table-wrap[data-v-1dff44f5] {\n\toverflow: auto;\n\tmax-height: 50vh;\n\tborder: var(--theme--border-width) solid var(--theme--border-color-subdued);\n\tborder-radius: var(--theme--border-radius);\n}\ntable[data-v-1dff44f5] {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 13px;\n}\nth[data-v-1dff44f5],\ntd[data-v-1dff44f5] {\n\tpadding: 8px 10px;\n\ttext-align: left;\n\tborder-bottom: var(--theme--border-width) solid var(--theme--border-color-subdued);\n}\nth[data-v-1dff44f5] {\n\tfont-weight: 700;\n\tbackground: var(--theme--background-subdued);\n\tposition: sticky;\n\ttop: 0;\n}\n.name[data-v-1dff44f5] {\n\tfont-family: var(--theme--fonts--monospace--font-family, monospace);\n\tmax-width: 280px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.title-cell[data-v-1dff44f5] {\n\tmax-width: 240px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.result[data-v-1dff44f5] {\n\tpadding: 10px 12px;\n\tborder-radius: var(--theme--border-radius);\n\tfont-size: 13px;\n\tfont-weight: 600;\n}\n.result.ok[data-v-1dff44f5] {\n\tbackground: color-mix(in srgb, var(--theme--success) 16%, transparent);\n\tcolor: var(--theme--success);\n}\n.result.has-fail[data-v-1dff44f5] {\n\tbackground: color-mix(in srgb, var(--theme--warning) 16%, transparent);\n\tcolor: var(--theme--warning);\n}\n",{});var Ft=le(Bt,[["__scopeId","data-v-1dff44f5"]]);function Ct(a){const o=e(),{useNotificationsStore:r,useServerStore:i}=t(),s=r(),d=i?.(),u=n(!1),c=n(!1),f=n(0),v=l(()=>f.value>0);let p,b;function h(){u.value=!0,p=s.add({title:"Drop to upload",icon:"cloud_upload",type:"info",persist:!0,closeable:!1})}function y(){u.value=!1,p&&(s.remove(p),p=void 0)}function w(e){if(!e.dataTransfer)return;if(-1===e.dataTransfer.types.indexOf("Files"))return;e.preventDefault(),f.value++;const t=e.target&&""===e.target.getAttribute?.("data-dropzone");1===f.value&&!1===u.value&&!1===t&&h(),t&&(y(),f.value=0)}function k(e){e.dataTransfer&&-1!==e.dataTransfer.types.indexOf("Files")&&e.preventDefault()}function x(e){e.dataTransfer&&-1!==e.dataTransfer.types.indexOf("Files")&&(e.preventDefault(),f.value--,0===f.value&&y(),e.target&&""===e.target.getAttribute?.("data-dropzone")&&(h(),f.value=1))}async function _(e){if(!e.dataTransfer)return;if(-1===e.dataTransfer.types.indexOf("Files"))return;e.preventDefault(),u.value=!1,f.value=0,p&&(s.remove(p),p=void 0);const t=Array.from(e.dataTransfer.files).filter(e=>e.type);t.length&&await M(t)}async function $(e,t){const n=new FormData,l=g(a.preset);l.storage&&n.append("storage",l.storage),l.folder&&n.append("folder",l.folder),n.append("file",e),await o.post("/files",n,{onUploadProgress:e=>{e.total&&t(Math.floor(100*e.loaded/e.total))}})}async function M(e){if(!e.length||c.value)return;c.value=!0;const t=e.map(()=>0),n=Math.max(1,Number(d?.info?.uploads?.maxConcurrency)||2);b=s.add({title:`Uploading 0 of ${e.length}…`,type:"info",persist:!0,closeable:!1,loading:!0});const l=()=>{const a=t.filter(e=>100===e).length,n=t.reduce((e,t)=>e+t,0)/t.length;b&&s.update(b,{title:`Uploading ${a} of ${e.length}…`,loading:!1,progress:n})};let o=0,r=0;try{await Promise.all(Array.from({length:Math.min(n,e.length)},()=>async function(){for(;o<e.length;){const a=o++,n=e[a];try{await $(n,e=>{t[a]=e,l()}),t[a]=100,l()}catch(e){r++,t[a]=100,l();const o=e?.response?.data?.errors?.[0]?.message||e?.message||`Failed to upload ${n.name}`;s.add({title:o,type:"error",icon:"error"})}}}()))}finally{b&&(s.remove(b),b=void 0),c.value=!1,r<e.length&&(s.add({title:r?`Uploaded ${e.length-r} of ${e.length}`:"Upload complete",type:r?"warning":"success",icon:r?"warning":"check"}),await(a.onDone?.()))}}return m(()=>{window.addEventListener("dragenter",w),window.addEventListener("dragover",k),window.addEventListener("dragleave",x),window.addEventListener("drop",_)}),S(()=>{window.removeEventListener("dragenter",w),window.removeEventListener("dragover",k),window.removeEventListener("dragleave",x),window.removeEventListener("drop",_),y(),b&&s.remove(b)}),{dragging:v,showDropEffect:u,uploading:c,uploadFiles:M,onFileInputChange:function(e){const t=e.target,a=Array.from(t.files||[]).filter(e=>e.type);t.value="",a.length&&M(a)}}}const zt="storage-manager-files-preset",It="cards",Vt={icon:"insert_drive_file",title:"{{ title }}",subtitle:"{{ type }} • {{ filesize }}",size:4,imageFit:"crop"},Ot={sort:["-uploaded_on"],page:1,limit:25};function Nt(){const e=function(){try{const e=localStorage.getItem(zt);return e?JSON.parse(e):{}}catch{return{}}}(),t=n(e.layout||It),a=n({...Vt,...e.layoutOptions||{}}),l=n({...Ot,...e.layoutQuery||{}}),o=n(null),r=n(null);return p([t,a,l],()=>{try{localStorage.setItem(zt,JSON.stringify({layout:t.value,layoutOptions:a.value,layoutQuery:l.value}))}catch{}},{deep:!0}),{layout:t,layoutOptions:a,layoutQuery:l,filter:o,search:r,resetPreset:function(){t.value=It,a.value={...Vt},l.value={...Ot},o.value=null,r.value=null}}}function Tt(e,t){return e?t?{_and:[e,t]}:e:t??null}const Ut={class:"sidebar-actions"},Lt={class:"sidebar-text storage-label"};var Dt=o({__name:"collection-browser",props:{mode:{},folder:{},storage:{}},setup(e){const t=e,a=L(),o=D(),{folders:v}=K(),{storages:x,loadStorages:S}=G(),$=n(),M=n([]),{layout:B,layoutOptions:z,layoutQuery:I,filter:V,search:O,resetPreset:A}=Nt(),{layoutWrapper:q}=Ce(B),P=l(()=>"storage"===t.mode&&t.storage?{_and:[{type:{_nnull:!0}},{storage:{_eq:t.storage}}]}:function(e){const t={_and:[{type:{_nnull:!0}}]};return e?t._and.push({folder:{_eq:e}}):t._and.push({folder:{_null:!0}}),t}(t.folder??null)),R=l(()=>{if("storage"===t.mode)return t.storage||"Storage";if(t.folder){const e=v.value?.find(e=>e.id===t.folder);return e?.name||"Folder"}return"Folders"}),W=l(()=>{const e=[{name:"Storage Manager",to:"/storage-manager"}];return"storage"===t.mode?e.push({name:t.storage||"Storage",to:`/storage-manager/storage/${t.storage}`}):(e.push({name:"Folders",to:"/storage-manager/folders"}),t.folder&&e.push({name:R.value,to:`/storage-manager/folders/${t.folder}`})),e}),J=l(()=>"storage"===t.mode&&t.storage&&x.value.find(e=>e.location===t.storage)||null),Q=n(!1),H=n(!1),X=n("files"),Y=l(()=>"files"===X.value?M.value.length:"storage"===X.value?J.value?.file_count??void 0:void 0),Z=l(()=>{if("storage"===X.value)return J.value?.total_bytes});async function ee(){await($.value?.state?.refresh?.()),await S(!0).catch(()=>{})}function te(){V.value=null,O.value=null}function ae(e){X.value=e,Q.value=!0}function ne(){H.value=!0,"storage"===t.mode&&t.storage&&"1"!==String(a.query.detect||"")&&o.replace({path:a.path,query:{...a.query,detect:"1"}})}function le(e){if(H.value=e,!e&&"1"===String(a.query.detect||"")){const e={...a.query};delete e.detect,o.replace({path:a.path,query:e})}}async function re(){M.value=[],await ee()}async function ie(){M.value=[],await ee()}E((e,t)=>{e.path!==t.path&&(M.value=[])}),j((e,t)=>{e.path!==t.path&&(M.value=[])}),p(()=>[t.folder,t.storage,t.mode],()=>{M.value=[]}),p(()=>[t.storage,String(a.query.detect||"")],([e,a])=>{"storage"===t.mode&&e&&"1"===a&&(H.value=!0)},{immediate:!0}),m(()=>{S().catch(()=>{})});const se=l(()=>"storage"===t.mode?{storage:t.storage}:{folder:t.folder??null}),de=n(null),{dragging:ue,showDropEffect:ce,uploading:fe,onFileInputChange:ve}=Ct({preset:se,onDone:ee});function pe(){de.value?.click()}const ge=l(()=>{const e=a.query.file;return"string"==typeof e&&e.length>0?e:null});function be(e){const t={...a.query,file:String(e)},n=new URLSearchParams;for(const[e,a]of Object.entries(t))if(null!=a&&""!==a)if(Array.isArray(a))for(const t of a)n.append(e,String(t));else n.set(e,String(a));const l=n.toString();return l?`${a.path}?${l}`:a.path}function he(e){e||(!function(){if(!("file"in a.query))return;const e={...a.query};delete e.file,o.replace({path:a.path,query:e})}(),ee().catch(()=>{}))}function ye(e){const t=e.primaryKeyField?.field||"id";return{...e,getLinkForItem(e){const a=e?.[t];if(null!=a)return be(a)},onRowClick({item:e,event:a}){const n=e?.[t];if(null==n)return;if(M.value.length>0)return void(M.value.includes(n)?M.value=M.value.filter(e=>e!==n):M.value=M.value.concat(n));const l=be(n);a.ctrlKey||a.metaKey?window.open(o.resolve(l).href,"_blank"):o.push(l)}}}return(t,a)=>{const n=r("v-breadcrumb"),l=r("v-button"),o=r("v-info"),v=r("drawer-item"),p=r("sidebar-detail"),m=r("private-view"),S=F("tooltip");return i(),s(N(g(q)),{ref_key:"layoutRef",ref:$,selection:M.value,"onUpdate:selection":a[11]||(a[11]=e=>M.value=e),"layout-options":g(z),"onUpdate:layoutOptions":a[12]||(a[12]=e=>y(z)?z.value=e:null),"layout-query":g(I),"onUpdate:layoutQuery":a[13]||(a[13]=e=>y(I)?I.value=e:null),filter:g(Tt)(g(V),P.value),"filter-user":g(V),"filter-system":P.value,search:g(O),collection:"directus_files","reset-preset":g(A)},{default:d(({layoutState:t})=>[u(m,{title:R.value,icon:"storage"===e.mode?J.value?.icon||"storage":"folder",class:w({dragging:g(ue)})},{headline:d(()=>[u(n,{items:W.value},null,8,["items"])]),"actions:prepend":d(()=>[(i(),s(N(`layout-actions-${g(B)}`),T(U(t)),null,16))]),actions:d(()=>[h("input",{ref_key:"fileInput",ref:de,class:"upload-input",type:"file",multiple:"",onChange:a[0]||(a[0]=(...e)=>g(ve)&&g(ve)(...e))},null,544),u(Ve,{modelValue:g(O),"onUpdate:modelValue":a[1]||(a[1]=e=>y(O)?O.value=e:null),filter:g(V),"onUpdate:filter":a[2]||(a[2]=e=>y(V)?V.value=e:null),collection:"directus_files"},null,8,["modelValue","filter"]),"storage"===e.mode?C((i(),s(Oe,{key:0,icon:"radar",secondary:"",onClick:ne},null,512)),[[S,`Detect Files on ${e.storage}`,void 0,{bottom:!0}]]):b("v-if",!0),M.value.length?C((i(),s(Oe,{key:1,icon:"drive_file_move",secondary:"",onClick:a[3]||(a[3]=e=>ae("files"))},null,512)),[[S,"Migrate Selected",void 0,{bottom:!0}]]):b("v-if",!0),"storage"===e.mode?C((i(),s(Oe,{key:2,icon:"swap_horiz",onClick:a[4]||(a[4]=e=>ae("storage"))},null,512)),[[S,"Migrate All",void 0,{bottom:!0}]]):C((i(),s(Oe,{key:3,icon:"swap_horiz",onClick:a[5]||(a[5]=e=>ae("folder"))},null,512)),[[S,e.folder?"Migrate This Folder":"Migrate Root Files",void 0,{bottom:!0}]])]),navigation:d(()=>[u(oe)]),sidebar:d(()=>[u(Le,{modelValue:g(B),"onUpdate:modelValue":a[6]||(a[6]=e=>y(B)?B.value=e:null)},{default:d(()=>[(i(),s(N(`layout-options-${g(B)}`),T(U(t)),null,16))]),_:2},1032,["modelValue"]),(i(),s(N(`layout-sidebar-${g(B)}`),T(U(t)),null,16)),u(p,{id:"actions",icon:"swap_horiz",title:"Actions"},{default:d(()=>[h("div",Ut,[u(l,{secondary:"","full-width":"",class:"sidebar-btn",loading:g(fe),onClick:pe},{default:d(()=>[_(k("storage"===e.mode?`Upload to ${e.storage}`:"Upload files"),1)]),_:1},8,["loading"]),"storage"===e.mode?(i(),s(l,{key:0,secondary:"","full-width":"",class:"sidebar-btn",onClick:ne},{default:d(()=>[_(" Detect files on "+k(e.storage),1)]),_:1})):b("v-if",!0),M.value.length?(i(),s(l,{key:1,secondary:"","full-width":"",class:"sidebar-btn",onClick:a[7]||(a[7]=e=>ae("files"))},{default:d(()=>[_(" Migrate selected ("+k(M.value.length)+") ",1)]),_:1})):b("v-if",!0),"storage"===e.mode?(i(),s(l,{key:2,"full-width":"",class:"sidebar-btn sidebar-btn-primary",onClick:a[8]||(a[8]=e=>ae("storage"))},{default:d(()=>[_(" Migrate all on "+k(e.storage),1)]),_:1})):(i(),s(l,{key:3,"full-width":"",class:"sidebar-btn sidebar-btn-primary",onClick:a[9]||(a[9]=e=>ae("folder"))},{default:d(()=>[_(k(e.folder?"Migrate this folder":"Migrate root files"),1)]),_:1}))])]),_:1}),"storage"===e.mode&&J.value?(i(),s(p,{key:0,id:"storage-info",icon:"info",title:"Storage"},{default:d(()=>[h("p",Lt,k(J.value.label),1),u(me,{usage:J.value,compact:"",plain:""},null,8,["usage"])]),_:1})):(i(),s(p,{key:1,id:"folders-info",icon:"info",title:"Folders"},{default:d(()=>[...a[24]||(a[24]=[h("p",{class:"sidebar-text"}," Browse virtual folders like the File Library. Migrate selected files or an entire folder between storage adapters. ",-1)])]),_:1}))]),default:d(()=>[g(ce)?(i(),c(f,{key:0},[a[14]||(a[14]=h("div",{class:"drop-border top"},null,-1)),a[15]||(a[15]=h("div",{class:"drop-border right"},null,-1)),a[16]||(a[16]=h("div",{class:"drop-border bottom"},null,-1)),a[17]||(a[17]=h("div",{class:"drop-border left"},null,-1))],64)):b("v-if",!0),(i(),s(N(`layout-${g(B)}`),T(U(ye(t))),{"no-results":d(()=>[g(V)||g(O)?(i(),s(o,{key:1,title:"No results",icon:"search",center:""},{append:d(()=>[u(l,{onClick:te},{default:d(()=>[...a[20]||(a[20]=[_("Clear filters",-1)])]),_:1})]),default:d(()=>[a[21]||(a[21]=_(" No files match your search or filters. ",-1))]),_:1})):(i(),s(o,{key:0,title:"No files",icon:"folder",center:""},{append:d(()=>[u(l,{onClick:pe},{default:d(()=>[...a[18]||(a[18]=[_("Add file",-1)])]),_:1})]),default:d(()=>[a[19]||(a[19]=_(" Drop files here to upload, or use the + button. ",-1))]),_:1}))]),"no-items":d(()=>[u(o,{title:"No files",icon:"folder",center:""},{append:d(()=>[u(l,{onClick:pe},{default:d(()=>[...a[22]||(a[22]=[_("Add file",-1)])]),_:1})]),default:d(()=>[a[23]||(a[23]=_(" Drop files here to upload, or use the + button. ",-1))]),_:1})]),_:1},16)),ge.value?(i(),s(v,{key:1,collection:"directus_files","primary-key":ge.value,active:!0,"onUpdate:active":he},null,8,["primary-key"])):b("v-if",!0),u(gt,{modelValue:Q.value,"onUpdate:modelValue":a[10]||(a[10]=e=>Q.value=e),storages:g(x),"source-storage":"storage"===e.mode&&e.storage||null,"selection-kind":X.value,"file-ids":M.value,"folder-id":"folders"===e.mode?e.folder??null:null,"estimated-count":Y.value,"estimated-bytes":Z.value,onDone:re},null,8,["modelValue","storages","source-storage","selection-kind","file-ids","folder-id","estimated-count","estimated-bytes"]),"storage"===e.mode&&e.storage?(i(),s(Ft,{key:2,"model-value":H.value,location:e.storage,"onUpdate:modelValue":le,onImported:ie},null,8,["model-value","location"])):b("v-if",!0)]),_:2},1032,["title","icon","class"])]),_:1},40,["selection","layout-options","layout-query","filter","filter-user","filter-system","search","reset-preset"])}}});ne("\n.upload-input[data-v-06c5b7c4] {\n\tdisplay: none;\n}\n.drop-border[data-v-06c5b7c4] {\n\tposition: fixed;\n\tz-index: 500;\n\tbackground-color: var(--theme--primary);\n}\n.drop-border.top[data-v-06c5b7c4],\n.drop-border.bottom[data-v-06c5b7c4] {\n\tinline-size: 100%;\n\tblock-size: 0.25rem;\n}\n.drop-border.left[data-v-06c5b7c4],\n.drop-border.right[data-v-06c5b7c4] {\n\tinline-size: 0.25rem;\n\tblock-size: 100%;\n}\n.drop-border.top[data-v-06c5b7c4] {\n\tinset-block-start: 0;\n\tinset-inline-start: 0;\n}\n.drop-border.right[data-v-06c5b7c4] {\n\tinset-block-start: 0;\n\tinset-inline-end: 0;\n}\n.drop-border.bottom[data-v-06c5b7c4] {\n\tinset-block-end: 0;\n\tinset-inline-start: 0;\n}\n.drop-border.left[data-v-06c5b7c4] {\n\tinset-block-start: 0;\n\tinset-inline-start: 0;\n}\n.dragging[data-v-06c5b7c4] * {\n\tpointer-events: none;\n}\n.dragging[data-v-06c5b7c4] [data-dropzone] {\n\tpointer-events: all;\n}\n.sidebar-actions[data-v-06c5b7c4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n/* Sidebar panel bg often matches --theme--background-normal, so default\n secondary buttons look like plain text. Force a visible chip fill. */\n.sidebar-actions[data-v-06c5b7c4] .sidebar-btn {\n\tdisplay: flex;\n\twidth: 100%;\n}\n.sidebar-actions[data-v-06c5b7c4] .sidebar-btn .button {\n\twidth: 100%;\n\tjustify-content: center;\n\tcolor: var(--theme--foreground);\n\tbackground-color: var(--theme--background-accent);\n\tborder-color: var(--theme--background-accent);\n}\n.sidebar-actions[data-v-06c5b7c4] .sidebar-btn .button:hover:not(:disabled) {\n\tbackground-color: var(--theme--background-normal);\n\tborder-color: var(--theme--background-normal);\n}\n\n/* Theme fill only when enabled — disabled stays gray like secondary */\n.sidebar-actions[data-v-06c5b7c4] .sidebar-btn-primary .button:not(:disabled) {\n\tcolor: var(--foreground-inverted, var(--theme--primary-foreground, #fff));\n\tbackground-color: var(--theme--primary);\n\tborder-color: var(--theme--primary);\n}\n.sidebar-actions[data-v-06c5b7c4] .sidebar-btn-primary .button:hover:not(:disabled) {\n\tbackground-color: var(--theme--primary-accent);\n\tborder-color: var(--theme--primary-accent);\n}\n.sidebar-actions[data-v-06c5b7c4] .sidebar-btn .button:disabled {\n\tcolor: var(--theme--foreground-subdued);\n\tbackground-color: var(--theme--background-accent);\n\tborder-color: var(--theme--background-accent);\n\topacity: 0.65;\n\tcursor: not-allowed;\n}\n.sidebar-text[data-v-06c5b7c4] {\n\tmargin: 0;\n\tline-height: 1.45;\n}\n.storage-label[data-v-06c5b7c4] {\n\tmargin-bottom: 10px;\n\tfont-weight: 600;\n}\n",{});var Et=le(Dt,[["__scopeId","data-v-06c5b7c4"]]),jt=o({__name:"storage-view",setup(e){const t=L(),a=l(()=>String(t.params.location||""));return(e,t)=>(i(),s(Et,{mode:"storage",storage:a.value},null,8,["storage"]))}}),At=o({__name:"folders-view",setup(e){const t=L(),a=l(()=>{const e=t.params.folderId;return e?String(e):null});return(e,t)=>(i(),s(Et,{mode:"folders",folder:a.value||void 0},null,8,["folder"]))}});const qt=[],Pt=[],Rt=[],Kt=[a({id:"storage-manager",name:"Storage Manager",icon:"storage",routes:[{path:"",component:$e},{path:"storage/:location",component:jt},{path:"folders",component:At},{path:"folders/:folderId",component:At}],preRegisterCheck:e=>function(e){if(!e||"object"!=typeof e)return!1;const t=e;if(!0===t.admin_access)return!0;const a=t.role;return!(!a||"object"!=typeof a||!0!==a.admin_access)}(e)})],Wt=[],Jt=[],Qt=[{id:"storage-manager-operation",name:"Storage Manager",icon:"swap_horiz",description:"Copy or move Directus files between storage adapters (same file id)",overview:({mode:e,target_storage:t,source_storage:a,folder_id:n})=>[{label:"Mode",text:String(e||"move")},{label:"Target",text:String(t||"")},{label:"Source",text:String(a||n||"file_ids")}],options:[{field:"mode",name:"Mode",type:"string",meta:{width:"half",interface:"select-dropdown",options:{choices:[{text:"Move (delete source after verify)",value:"move"},{text:"Copy (keep source)",value:"copy"}]},note:"Move only deletes the source object after the target is verified and the DB row is updated."},schema:{default_value:"move"}},{field:"target_storage",name:"Target Storage",type:"string",meta:{width:"half",interface:"input",options:{placeholder:"s3"},note:"Must match a configured STORAGE_LOCATIONS name."},schema:{required:!0}},{field:"file_ids",name:"File IDs",type:"json",meta:{width:"full",interface:"input-code",options:{language:"json",placeholder:'["uuid-1", "uuid-2"]'},note:"Optional JSON array of file UUIDs. Leave empty when using source storage or folder."}},{field:"source_storage",name:"Source Storage",type:"string",meta:{width:"half",interface:"input",options:{placeholder:"local"},note:"Migrate all files currently on this storage location."}},{field:"folder_id",name:"Folder ID",type:"uuid",meta:{width:"half",interface:"system-folder",note:"Migrate files in this folder (optional)."}},{field:"recursive",name:"Include Subfolders",type:"boolean",meta:{width:"half",interface:"boolean",note:"When a folder is selected, also migrate files in nested folders."},schema:{default_value:!0}}]}];export{Pt as displays,qt as interfaces,Rt as layouts,Kt as modules,Qt as operations,Wt as panels,Jt as themes};
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "directus-extension-storage-manager",
|
|
3
|
+
"description": "Browse, upload, detect, and migrate Directus files across storage adapters (local, S3, GCS, Azure, and more)",
|
|
4
|
+
"icon": "storage",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"directus",
|
|
9
|
+
"directus-extension",
|
|
10
|
+
"directus-extension-bundle",
|
|
11
|
+
"storage",
|
|
12
|
+
"storage-manager",
|
|
13
|
+
"migration",
|
|
14
|
+
"files",
|
|
15
|
+
"s3",
|
|
16
|
+
"gcs",
|
|
17
|
+
"azure",
|
|
18
|
+
"move",
|
|
19
|
+
"copy",
|
|
20
|
+
"orphans"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/domdus/directus-extension-storage-manager.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/domdus/directus-extension-storage-manager/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/domdus/directus-extension-storage-manager#readme",
|
|
36
|
+
"directus:extension": {
|
|
37
|
+
"type": "bundle",
|
|
38
|
+
"path": {
|
|
39
|
+
"app": "dist/app.js",
|
|
40
|
+
"api": "dist/api.js"
|
|
41
|
+
},
|
|
42
|
+
"entries": [
|
|
43
|
+
{
|
|
44
|
+
"type": "module",
|
|
45
|
+
"name": "storage-manager-module",
|
|
46
|
+
"source": "src/module/index.ts"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "endpoint",
|
|
50
|
+
"name": "storage-manager-endpoint",
|
|
51
|
+
"source": "src/endpoint/index.ts"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"type": "operation",
|
|
55
|
+
"name": "storage-manager-operation",
|
|
56
|
+
"source": {
|
|
57
|
+
"app": "src/operation/app.ts",
|
|
58
|
+
"api": "src/operation/api.ts"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"host": ">=9.26.0 <13.0.0"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "directus-extension build",
|
|
66
|
+
"dev": "directus-extension build -w --no-minify",
|
|
67
|
+
"link": "directus-extension link",
|
|
68
|
+
"add": "directus-extension add"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@directus/extensions-sdk": "^17.0.0",
|
|
72
|
+
"@types/express": "^4.17.21",
|
|
73
|
+
"@types/node": "^20.0.0",
|
|
74
|
+
"typescript": "^5.0.0",
|
|
75
|
+
"vue": "^3.3.7"
|
|
76
|
+
}
|
|
77
|
+
}
|