directus-extension-storage-manager 1.0.0 → 1.1.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/dist/api.js +1 -1
- package/dist/app.js +1 -1
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -1 +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};
|
|
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 s}from"node:url";import a from"node:fs/promises";import{randomUUID as i}from"node:crypto";let o=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),s=r?.getStorage||r?.default?.getStorage;if("function"==typeof s)return s}catch{}try{return await l(s(e).href)}catch{return null}}async function d(){if(o)return o;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 o=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"],s=[];for(const e of t)for(const t of r)s.push(n.join(e,t));return s}()){const t=await c(e);if(t)return o=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 h(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 w(e){return String(e.title||e.filename_download||e.filename_disk||e.id)}async function b(e,t,r,n){const s=new Array(e.length);let a=0;const i=Array.from({length:Math.min(t,e.length)||1},()=>async function(){for(;a<e.length;){if(n?.())return;const t=a++;s[t]=await r(e[t],t)}}());return await Promise.all(i),s}async function S(e,t,r,s){let a;try{a=await async function(e,t){if("function"!=typeof e.list)return[];const r=n.parse(t).name,s=`${r}__`,a=[];for await(const t of e.list(r)){const e=String(t||"").replace(/^[/\\]+/,""),r=n.basename(e);r.startsWith(s)&&y.test(r)&&a.push(e)}return a}(e,r)}catch(e){return void s?.warn?.(`[storage-manager] Could not list asset transforms for ${r}: ${e instanceof Error?e.message:String(e)}`)}for(const r of a)try{if(await m(t,r))continue;const n=await f(e,r);await g(t,r,n,null)}catch(e){s?.warn?.(`[storage-manager] Failed copying asset transform ${r}: ${e instanceof Error?e.message:String(e)}`)}}async function k(t,r,n,s,a,i){const o={id:t.id,filename_disk:t.filename_disk,from:t.storage,to:r,status:"failed"};if(!t.filename_disk)return{...o,status:"failed",error:"Missing filename_disk"};if(t.storage===r)return{...o,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{...o,status:"failed",error:`Source object missing on ${t.storage}`};if(await m(d,t.filename_disk)){if(await S(c,d,t.filename_disk,a),await s("directus_files").where({id:t.id}).update({storage:r}),"move"===n)try{await h(c,t.filename_disk)}catch(e){a?.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 i&&e.size>0&&i(e.size),{...o,status:"move"===n?"moved":"copied",bytes:e.size}}const _=await f(c,t.filename_disk),y=i?_.pipe((w=i,new e({transform(e,t,r){const n=Buffer.isBuffer(e)?e.length:Buffer.byteLength(e);n>0&&w(n),r(null,e)}}))):_;var w;try{await g(d,t.filename_disk,y,t.type)}catch(e){try{await m(d,t.filename_disk)&&await h(d,t.filename_disk)}catch{}return{...o,status:"failed",error:`Write failed: ${e instanceof Error?e.message:String(e)}`}}if(!await m(d,t.filename_disk))return{...o,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{...o,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 h(d,t.filename_disk)}catch{}return{...o,status:"failed",error:`Size mismatch after write (expected ~${e}, got ${b})`}}}await S(c,d,t.filename_disk,a);try{await s("directus_files").where({id:t.id}).update({storage:r})}catch(e){try{await h(d,t.filename_disk)}catch{}return{...o,status:"failed",error:`DB update failed: ${e instanceof Error?e.message:String(e)}`}}if("move"===n)try{await h(c,t.filename_disk)}catch(e){return a?.warn?.(`[storage-manager] Moved ${t.id} to ${r} but source delete failed: ${e instanceof Error?e.message:String(e)}`),{...o,status:"moved",bytes:b,error:`Moved, but source cleanup failed: ${e instanceof Error?e.message:String(e)}`}}return{...o,status:"move"===n?"moved":"copied",bytes:b}}async function v(e){const{fileIds:t,targetStorage:r,mode:n,database:s,logger:a,onProgress:i,isCancelled:o}=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 i?.({type:"done",data:e}),e}const u=await s("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;i?.({type:"start",mode:n,from:m,to:r,total:d.length,total_bytes:g});let p=0,_=0,y=0,h=0,S=0;const v=new Map;let $=0;const j=await b(d,l,async e=>{if(o?.())return{id:e,filename_disk:"",from:"",to:r,status:"failed",error:"Cancelled"};const t=f.get(e),l=++S;if(!t){h+=1;const t={id:e,filename_disk:"",from:"",to:r,status:"failed",error:"File not found"};return i?.({type:"file_done",index:l,total:d.length,result:t,name:e,succeeded:_,skipped:y,failed:h,transferred_bytes:p,total_bytes:g,elapsed_ms:Date.now()-c}),t}let u;i?.({type:"file_start",index:l,total:d.length,id:t.id,name:w(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,s,a,e=>((e,t,r)=>{p+=r;const n=(v.get(t.id)||0)+r;v.set(t.id,n);const s=Date.now();s-$<120||($=s,i?.({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:s-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:h+=1;return i?.({type:"file_done",index:l,total:d.length,result:u,name:w(t),succeeded:_,skipped:y,failed:h,transferred_bytes:p,total_bytes:g,elapsed_ms:Date.now()-c}),u},o),A={mode:n,target_storage:r,total:j.filter(Boolean).length,succeeded:_,skipped:y,failed:h,results:j.filter(Boolean),transferred_bytes:p,total_bytes:g,elapsed_ms:Date.now()-c};return a?.info?.(`[storage-manager] ${n} → ${r}: ${_} ok, ${y} skipped, ${h} failed (of ${A.total})`),i?.({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,s){const a=N(e,s),{file_count:i,total_bytes:o}=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,s);let l={disk_total_bytes:null,disk_free_bytes:null,disk_used_bytes:null,disk_used_percent:null,disk_available:!1};return"local"===a&&(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 s=process.env.PWD||process.cwd()||"/directus",a=n.isAbsolute(e)?e:n.join(s,e);try{if(!r.existsSync(a))return t;const e=r.promises.statfs;if("function"==typeof e){const t=await e(a),r=Number(t.bsize||t.frsize||0),n=r*Number(t.blocks||0),s=r*Number(t.bavail??t.bfree??0),i=Math.max(0,n-s);return{disk_total_bytes:n||null,disk_free_bytes:s||null,disk_used_bytes:i||null,disk_used_percent:n>0?i/n*100:null,disk_available:n>0}}}catch{}return t}(z(e,s))),{location:s,driver:a,file_count:i,total_bytes:o,...l}}async function q(e,t,r){const n=await E(e,t,r),s=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:s.label,short:s.short,icon:s.icon,root:z(e,r),bucket:x(e,r)}}const C=/__[a-f0-9]{16,}(?:\.[^.]+)?$/i;function B(e){return C.test(n.basename(e))}function I(e){const t=n.basename(e);return!(t&&!t.startsWith("."))||("directus-health-file"===t||!!B(e))}const D={".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 D[t]||null}function W(e){const t=n.parse(n.basename(e)).name;return t.replace(/_/g," ").replace(/\s+/g," ").trim()||t||e}function O(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 U(e,t){const r=new Set;if(t){const s=N(t,e),i=z(t,e);if("local"===s&&i){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,s=""){let i;try{i=await a.readdir(t,{withFileTypes:!0})}catch{return}for(const a of i){const i=s?`${s}/${a.name}`:a.name;a.isDirectory()?await e(n.join(t,a.name),i):a.isFile()&&(I(i)||r.push(i))}}(t),r}(i))r.add(e);return Array.from(r)}}const s=(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)}(s,"")){const t=String(e||"").replace(/^[/\\]+/,"");t&&!I(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 T(e,t,r){const s=await U(t,r),a=await e("directus_files").select("filename_disk").where("storage",t),i=new Set(a.map(e=>String(e.filename_disk||"")).filter(Boolean)),o=(await u()).location(t),l=[];for(const e of s){if(i.has(e))continue;let t=0;try{const r=await p(o,e);t=Number(r.size)||0}catch{t=0}l.push({filename_disk:e,filesize:t,type:M(e),suggested_id:O(e),filename_download:n.basename(e),title:W(e)})}return l.sort((e,t)=>e.filename_disk.localeCompare(t.filename_disk)),{orphans:l,scanned:s.length,known:i.size}}function P(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 R(e,t,r){if(!t)return[];if(!r)return[t];const n=await e("directus_folders").select("id","parent"),s=new Map;for(const e of n){const t=null==e.parent?null:String(e.parent),r=String(e.id);s.has(t)||s.set(t,[]),s.get(t).push(r)}const a=[],i=[String(t)];for(;i.length;){const e=i.pop();a.push(e);const t=s.get(e)||[];for(const e of t)i.push(e)}return a}async function G(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 R(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 L=[],F=[{name:"storage-manager-endpoint",config:{id:"storage-manager",handler:(e,t)=>{const{database:r,env:s,logger:a}=t;e.get("/storages",async(e,t,n)=>{try{if(!P(e,t))return;const n=A(s),a=await Promise.all(n.map(e=>q(s,r,e)));t.json({data:a})}catch(e){n(e)}}),e.get("/storages/:location",async(e,t,n)=>{try{if(!P(e,t))return;const n=String(e.params.location);if(!A(s).includes(n))return void t.status(404).json({errors:[{message:`Unknown storage location: ${n}`}]});const a=await q(s,r,n);t.json({data:a})}catch(e){n(e)}}),e.get("/storages/:location/orphans",async(e,t,n)=>{try{if(!P(e,t))return;const n=String(e.params.location);if(!A(s).includes(n))return void t.status(404).json({errors:[{message:`Unknown storage location: ${n}`}]});const a=await T(r,n,s);t.json({data:a.orphans,meta:{scanned:a.scanned,known:a.known,orphan_count:a.orphans.length}})}catch(e){n(e)}}),e.post("/storages/:location/import-orphans",async(e,t,o)=>{try{if(!P(e,t))return;const o=String(e.params.location);if(!A(s).includes(o))return void t.status(404).json({errors:[{message:`Unknown storage location: ${o}`}]});const l=e.body||{};let c=Array.isArray(l.filename_disks)?l.filename_disks.map(String):[];if(0===c.length){c=(await T(r,o,s)).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,s){const a=(await u()).location(t),o=[];for(const l of r){const r=String(l||"").trim();if(r&&!I(r))try{const l=await e("directus_files").select("id").where({storage:t,filename_disk:r}).first();if(l){o.push({filename_disk:r,id:String(l.id),status:"skipped",error:"Already in database"});continue}if(!await m(a,r)){o.push({filename_disk:r,status:"failed",error:"File not found on disk"});continue}const c=await p(a,r),d=O(r);let u=d||i();d&&await e("directus_files").select("id").where({id:d}).first()&&(u=i());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:s?.folder||null,uploaded_on:f,modified_on:f}),o.push({filename_disk:r,id:u,status:"imported"})}catch(e){o.push({filename_disk:r,status:"failed",error:e?.message||String(e)})}else o.push({filename_disk:r,status:"skipped",error:"Ignored path"})}return o}(r,o,c,{folder:void 0===l.folder?null:l.folder});a.info(`[storage-manager] import-orphans location=${o} 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){o(e)}}),e.post("/storages/:location/delete-orphans",async(e,t,n)=>{try{if(!P(e,t))return;const n=String(e.params.location);if(!A(s).includes(n))return void t.status(404).json({errors:[{message:`Unknown storage location: ${n}`}]});const i=e.body||{},o=Array.isArray(i.filename_disks)?i.filename_disks.map(String).filter(Boolean):[];if(0===o.length)return void t.status(400).json({errors:[{message:"Provide filename_disks — at least one orphan to delete"}]});const l=await async function(e,t,r){const n=(await u()).location(t),s=[];for(const a of r){const r=String(a||"").trim();if(r&&!I(r))try{if(await e("directus_files").select("id").where({storage:t,filename_disk:r}).first()){s.push({filename_disk:r,status:"skipped",error:"Registered in Directus — use File Library to delete"});continue}if(!await m(n,r)){s.push({filename_disk:r,status:"skipped",error:"Not found on disk"});continue}await _(n,r),s.push({filename_disk:r,status:"deleted"})}catch(e){s.push({filename_disk:r,status:"failed",error:e?.message||String(e)})}else s.push({filename_disk:r,status:"skipped",error:r&&B(r)?"Generated thumbnail":"Ignored path"})}return s}(r,n,o);a.info(`[storage-manager] delete-orphans location=${n} total=${l.length} deleted=${l.filter(e=>"deleted"===e.status).length}`),t.json({data:{total:l.length,deleted:l.filter(e=>"deleted"===e.status).length,skipped:l.filter(e=>"skipped"===e.status).length,failed:l.filter(e=>"failed"===e.status).length,results:l}})}catch(e){n(e)}}),e.get("/files",async(e,t,n)=>{try{if(!P(e,t))return;const n=e.query.storage?String(e.query.storage):null,s=void 0!==e.query.folder?"null"===e.query.folder||""===e.query.folder?null:String(e.query.folder):void 0,a=e.query.search?String(e.query.search).trim():"",i=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),o="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!==s)if(null===s)u.whereNull("folder");else if(o){const e=await R(r,s,!0);u.whereIn("folder",e)}else u.where("folder",s);if(a){const e=`%${a.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,i);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),h=await f,w=await g,b=Number(h?.count||0),S=Number(w?.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(!P(e,t))return;const n=void 0===e.query.parent?void 0:"null"===e.query.parent||""===e.query.parent?null:String(e.query.parent),s=e.query.storage?String(e.query.storage):null;let a=r("directus_folders").select("id","name","parent").orderBy("name","asc");null===n?a=a.whereNull("parent"):void 0!==n&&(a=a.where("parent",n));const i=await a,o=[];for(const e of i){const t=String(e.id);let n=r("directus_files").where("folder",t);s&&(n=n.andWhere("storage",s));const a=await n.select(r.raw("count(*) as file_count"),r.raw("coalesce(sum(filesize), 0) as total_bytes")).first(),i=await r("directus_folders").where("parent",t).count({count:"*"}).first();o.push({id:t,name:e.name,parent:null==e.parent?null:String(e.parent),file_count:Number(a?.file_count||0),total_bytes:Number(a?.total_bytes||0),child_count:Number(i?.count||0)})}let l=null;if(null==n){let e=r("directus_files").whereNull("folder");s&&(e=e.andWhere("storage",s));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:o,meta:{root_files:l}})}catch(e){n(e)}}),e.get("/folders/tree",async(e,t,n)=>{try{if(!P(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(!P(e,t))return;const n=String(e.params.id),s=[];let a=n;for(let e=0;e<50&&a;e++){const e=await r("directus_folders").select("id","name","parent").where({id:a}).first();if(!e)break;s.unshift({id:String(e.id),name:e.name}),a=null==e.parent?null:String(e.parent)}t.json({data:s})}catch(e){n(e)}}),e.post("/migrate",async(e,t,n)=>{try{if(!P(e,t))return;const n=e.body||{},i=String(n.target_storage||"").trim(),o="copy"===n.mode?"copy":"move"===n.mode?"move":null;if(!i||!o)return void t.status(400).json({errors:[{message:'target_storage and mode ("copy"|"move") are required'}]});const l=A(s);if(!l.includes(i))return void t.status(400).json({errors:[{message:`Unknown target storage: ${i}. Available: ${l.join(", ")}`}]});if(N(s,i),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 G(r,n);if(0===c.length)return void t.json({data:{mode:o,target_storage:i,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.`}]});a.info(`[storage-manager] Starting ${o} of ${c.length} file(s) → ${i} (by ${e.accountability?.user||"unknown"})`);const u=await v({fileIds:c,targetStorage:i,mode:o,concurrency:n.concurrency,database:r,logger:a});t.json({data:u})}catch(e){n(e)}}),e.post("/migrate/stream",async(e,t,n)=>{try{if(!P(e,t))return;const n=e.body||{},i=String(n.target_storage||"").trim(),o="copy"===n.mode?"copy":"move"===n.mode?"move":null;if(!i||!o)return void t.status(400).json({errors:[{message:'target_storage and mode ("copy"|"move") are required'}]});const l=A(s);if(!l.includes(i))return void t.status(400).json({errors:[{message:`Unknown target storage: ${i}. Available: ${l.join(", ")}`}]});if(N(s,i),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 G(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)};a.info(`[storage-manager] Streaming ${o} of ${c.length} file(s) → ${i} (by ${e.accountability?.user||"unknown"})`);try{await v({fileIds:c,targetStorage:i,mode:o,concurrency:n.concurrency??1,database:r,logger:a,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{}}})}}}],H=[{name:"storage-manager-operation",config:{id:"storage-manager-operation",handler:async(e,{env:t,database:r,logger:n})=>{const s="copy"===e.mode?"copy":"move",a=String(e.target_storage||"").trim();if(!a)throw new Error("target_storage is required");const i=A(t);if(!i.includes(a))throw new Error(`Unknown target storage "${a}". Available: ${i.join(", ")}`);let o=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===o.length){const t=r("directus_files").select("id");if(e.source_storage){const r=String(e.source_storage).trim();if(!i.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"),s=new Map;for(const e of n){const t=null==e.parent?null:String(e.parent),r=String(e.id);s.has(t)||s.set(t,[]),s.get(t).push(r)}const a=[],i=[String(t)];for(;i.length;){const e=i.pop();a.push(e);for(const t of s.get(e)||[])i.push(t)}return a}(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");o=(await t).map(e=>String(e.id))}const l=null!=e.concurrency?parseInt(String(e.concurrency),10):1;return n.info(`[storage-manager-operation] ${s} ${o.length} file(s) → ${a}`),await v({fileIds:o,targetStorage:a,mode:s,concurrency:l,database:r,logger:n})}}}];export{F as endpoints,L as hooks,H as operations};
|
package/dist/app.js
CHANGED
|
@@ -1 +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};
|
|
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 d,withCtx as s,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 k,toDisplayString as w,normalizeStyle as x,createTextVNode as _,onUnmounted as S,inject as $,reactive as M,toRefs as B,resolveDirective as C,withDirectives as F,withKeys as V,withModifiers as z,vShow as I,renderSlot as N,resolveDynamicComponent as O,normalizeProps as U,guardReactiveProps as D}from"vue";import{useRoute as L,useRouter as T,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 G(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 H(){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:G(t),body:JSON.stringify(e),credentials:"same-origin",signal:n}),a)}}}var Y=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(),d(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:s(()=>[u(l,null,{default:s(()=>[u(n,{name:"folder",outline:""})]),_:1}),u(p,null,{default:s(()=>[u(o,{text:e.folder.name},null,8,["text"])]),_:1})]),default:s(()=>[(i(!0),c(f,null,v(e.folder.children,t=>(i(),d(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(),d(m,{key:0,clickable:"",active:e.currentFolder===e.folder.id,onClick:a[0]||(a[0]=t=>e.clickHandler({folder:e.folder.id}))},{default:s(()=>[u(l,null,{default:s(()=>[u(n,{name:"folder",outline:""})]),_:1}),u(p,null,{default:s(()=>[u(o,{text:e.folder.name},null,8,["text"])]),_:1})]),_:1},8,["active"]))}});const X={class:"folders"},Z={class:"storages"};var ee=o({__name:"navigation",setup(e){const t=L(),a=T(),{nestedFolders:n,folders:o,loading:k,openFolders:w}=K(),{storages:x,loadStorages:_}=H(),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 C(e){e.folder?a.push(`/storage-manager/folders/${e.folder}`):a.push("/storage-manager/folders")}function F(){a.push("/storage-manager")}return p([S,k],function(){if(!o.value)return;if(!w?.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=[...w.value];for(const t of e)!1===a.includes(t)&&a.push(t);1!==a.length&&JSON.stringify(a)!==JSON.stringify(w.value)&&(w.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"),V=r("v-skeleton-loader"),z=r("v-list-group"),I=r("v-item-group"),N=r("v-divider"),O=r("v-list");return i(),d(O,{nav:""},{default:s(()=>[u(_,{clickable:"",active:B.value,onClick:F},{default:s(()=>[u(o,null,{default:s(()=>[u(l,{name:"dashboard"})]),_:1}),u(m,null,{default:s(()=>[u(p,{text:"Storage Manager"})]),_:1})]),_:1},8,["active"]),!g(k)||g(n)&&0!==g(n).length?b("v-if",!0):(i(),c(f,{key:0},v(4,e=>u(_,{key:e},{default:s(()=>[u(V,{type:"list-item-icon"})]),_:1})),64)),h("div",X,[u(I,{modelValue:g(w),"onUpdate:modelValue":t[1]||(t[1]=e=>y(w)?w.value=e:null),scope:"files-navigation",multiple:""},{default:s(()=>[u(z,{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=>C({}))},{activator:s(()=>[u(o,null,{default:s(()=>[u(l,{name:"folder_special",outline:""})]),_:1}),u(m,null,{default:s(()=>[u(p,{text:"Folders"})]),_:1})]),default:s(()=>[(i(!0),c(f,null,v(g(n)||[],e=>(i(),d(Y,{key:e.id,folder:e,"current-folder":S.value,"click-handler":C},null,8,["folder","current-folder"]))),128))]),_:1},8,["active","arrow-placement"])]),_:1},8,["modelValue"])]),u(N),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(),d(_,{key:e.location,clickable:"",active:$.value===e.location,onClick:t=>{return n=e.location,void a.push(`/storage-manager/storage/${n}`);var n}},{default:s(()=>[u(o,null,{default:s(()=>[u(l,{name:e.icon},null,8,["name"])]),_:2},1024),u(m,null,{default:s(()=>[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 de(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 se={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}),d=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:k(["usage-bar",{compact:e.compact,plain:e.plain}])},[h("div",se,[h("span",ue,w(e.label),1),h("span",ce,w(r.value),1)]),a.value?(i(),c("div",fe,[h("div",{class:k(["fill",o.value]),style:x({width:`${n.value}%`})},null,6)])):b("v-if",!0),d.value?(i(),c("div",ve,w(d.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"],ke={class:"card-head"},we={class:"card-titles"},xe={class:"meta"},_e={key:0};var Se=o({__name:"overview-view",setup(e){const t=T(),{storages:a,storagesError:l,loadStorages:o}=H(),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"),k=r("v-notice"),x=r("v-icon"),S=r("private-view");return i(),d(S,{title:"Storage Manager",icon:"swap_horiz"},{headline:s(()=>[u(o,{items:[{name:"Storage Manager",to:"/storage-manager"}]})]),navigation:s(()=>[u(oe)]),sidebar:s(()=>[u(m,{id:"about",icon:"info",title:"About"},{default:s(()=>[...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:s(()=>[h("div",ge,[p.value&&!g(a).length?(i(),c("div",be,[u(y,{indeterminate:""})])):g(l)?(i(),d(k,{key:1,type:"danger"},{default:s(()=>[_(w(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",ke,[u(x,{name:e.icon,large:""},null,8,["name"]),h("div",we,[h("strong",null,w(e.location),1),h("span",null,w(e.label),1)])]),u(me,{usage:e},null,8,["usage"]),h("div",xe,[h("span",null,w(e.file_count.toLocaleString())+" files",1),e.root?(i(),c("span",_e,w(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",Ce=["selection","layoutOptions","layoutQuery"];function Fe(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:Ce.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,Ce.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 Ve=["value","placeholder","tabindex","disabled"];var ze=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),w=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 N(e){return!(e.path||e.composedPath?.()||[]).some(e=>e?.classList?.contains("v-menu-content"))}function O(){g.value||v.value?.focus(),g.value=!0}function U(){y.value=!y.value,g.value=!0,y.value||v.value?.focus()}function D(){o("update:modelValue",null),g.value&&v.value?.focus()}function L(){g.value=!1,y.value=!1,v.value?.blur()}function T(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=>{w.value=!!e}),(t,a)=>{const n=r("v-icon"),l=r("interface-system-filter"),o=r("v-badge"),p=C("tooltip"),m=C("click-outside");return i(),d(o,{bottom:"",right:"",class:k(["search-badge",{active:g.value,"filter-active":y.value}]),value:B.value,disabled:e.disabled||!B.value||y.value},{default:s(()=>[F((i(),c("div",{class:k(["search-input",{active:g.value,disabled:e.disabled,"filter-active":y.value,"has-content":!!e.modelValue,"filter-border":w.value,"show-filter":e.showFilter}]),role:"search",onClick:O},[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:V(L,["esc"]),onFocusin:O,onFocusout:T},null,40,Ve),a[2]||(a[2]=h("div",{class:"spacer"},null,-1)),e.modelValue?F((i(),d(n,{key:0,small:"",clickable:"",class:"icon-clear",name:"close",disabled:e.disabled,onClick:z(D,["stop"])},null,8,["disabled"])),[[p,"Clear value",void 0,{bottom:!0}]]):b("v-if",!0),e.showFilter?(i(),c(f,{key:1},[F(u(n,{small:"",clickable:"",class:"icon-filter",name:"filter_list",disabled:e.disabled,onClick:U},null,8,["disabled"]),[[p,!e.disabled&&"Filter",void 0,{bottom:!0}]]),F(h("div",{ref_key:"filterElement",ref:_,class:k(["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),[[I,y.value]])],64)):b("v-if",!0)],2)),[[m,{handler:L,middleware:N,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 Ie=le(ze,[["__scopeId","data-v-576fbb1b"]]),Ne=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(),d(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:s(()=>[u(n,{name:e.icon,small:""},null,8,["name"])]),_:1},8,["disabled","active","loading","secondary","outlined","to"])}});const Oe={class:"layout-options"},Ue={class:"field"};var De=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(),d(n,{id:"layout",icon:"layers",title:"Layout Options"},{default:s(()=>[h("div",Oe,[h("div",Ue,[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"])]),N(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(De,[["__scopeId","data-v-65862970"]]);const Te={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"},Ge={class:"stat"},He={class:"stat-value"},Ye={class:"stat"},Xe={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)),d=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)),k=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",Te,[h("div",Ee,[h("div",je,[u(n,{name:v.value},null,8,["name"]),h("span",null,w(d.value),1)]),u(n,{class:"arrow",name:"arrow_forward"}),h("div",Ae,[u(n,{name:p.value},null,8,["name"]),h("span",null,w(f.value),1)]),u(l,{"x-small":"",class:"mode-chip"},{default:s(()=>[_(w(m.value),1)]),_:1})]),h("div",qe,[h("span",Pe,"File "+w(e.currentIndex)+" / "+w(e.totalFiles),1),h("span",{class:"file-name",title:e.currentName},w(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,w(y.value)+"%",1),h("span",null,w(g(re)(e.transferredBytes))+" / "+w(g(re)(e.totalBytes)),1)])]),h("div",Qe,[h("div",Ge,[a[0]||(a[0]=h("span",{class:"stat-label"},"Speed",-1)),h("span",He,w(g(ie)(e.speedBps)),1)]),h("div",Ye,[a[1]||(a[1]=h("span",{class:"stat-label"},"Elapsed",-1)),h("span",Xe,w(g(de)(e.elapsedMs)),1)]),h("div",Ze,[a[2]||(a[2]=h("span",{class:"stat-label"},"Status",-1)),h("span",et,w(k.value),1)])]),h("div",tt,[h("span",at,w(e.succeeded)+" ok",1),h("span",nt,w(e.skipped)+" skipped",1),h("span",lt,w(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"},dt={class:"intro"},st={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}=H(),g=n(null),y=n("move"),k=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 V(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",k.value=!0,V(e?.location||"")}});const z=l(()=>a.storages.filter(e=>e.location!==a.sourceStorage).map(e=>({text:`${e.location} (${e.short})`,value:e.location}))),I=l(()=>"move"===y.value?"Move":"Copy"),N=l(()=>Boolean(g.value)&&!x.value),O=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}.`}),U=l(()=>(S.value?.results||[]).filter(e=>"failed"===e.status));function D(e){!e&&x.value||o("update:modelValue",e)}function L(){x.value?T():o("update:modelValue",!1)}function T(){$?.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,V(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=k.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"),V=C("tooltip");return i(),d(M,{"model-value":e.modelValue,title:"Migrate Files",icon:"swap_horiz",persistent:"","onUpdate:modelValue":D,onCancel:L},{actions:s(()=>[x.value||S.value?x.value?F((i(),d(l,{key:1,icon:"",rounded:"",secondary:"",onClick:T},{default:s(()=>[u(n,{name:"close"})]),_:1})),[[V,"Cancel",void 0,{bottom:!0}]]):b("v-if",!0):F((i(),d(l,{key:0,disabled:!N.value,icon:"",rounded:"",onClick:j},{default:s(()=>[u(n,{name:"check"})]),_:1},8,["disabled"])),[[V,I.value,void 0,{bottom:!0}]])]),default:s(()=>[h("div",it,[h("p",dt,w(O.value),1),x.value||S.value?b("v-if",!0):(i(),c(f,{key:0},[h("div",st,[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:z.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:k.value,"onUpdate:modelValue":a[3]||(a[3]=e=>k.value=e),label:"Include files in subfolders"},null,8,["modelValue"])])):b("v-if",!0)],64)),x.value||S.value?(i(),d(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:s(()=>[_(w(S.value.succeeded)+" succeeded · "+w(S.value.skipped)+" skipped · "+w(S.value.failed)+" failed (of "+w(S.value.total)+") ",1)]),_:1},8,["type"]),U.value.length?(i(),c("div",pt,[(i(!0),c(f,null,v(U.value.slice(0,20),e=>(i(),c("div",{key:e.id,class:"fail-row"},[h("strong",null,w(e.filename_disk||e.id),1),h("span",null,w(e.error),1)]))),128))])):b("v-if",!0)])):b("v-if",!0)])]),_:1},8,["model-value"])}}});ne("\n.drawer-body[data-v-fb3af6d9] {\n\tpadding: 20px 24px 32px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 18px;\n}\n.intro[data-v-fb3af6d9] {\n\tmargin: 0;\n\tcolor: var(--theme--foreground);\n}\n.field[data-v-fb3af6d9] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n.field label[data-v-fb3af6d9] {\n\tfont-weight: 600;\n}\n.mode-radios[data-v-fb3af6d9] {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 1fr;\n\tgap: 10px;\n}\n.mode-radios[data-v-fb3af6d9] .v-radio.block {\n\twidth: 100%;\n\tmargin: 0;\n}\n.note[data-v-fb3af6d9] {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.result[data-v-fb3af6d9] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n}\n.failures[data-v-fb3af6d9] {\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-fb3af6d9] {\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-fb3af6d9] {\n\tcolor: var(--theme--danger);\n}\n",{});var gt=le(mt,[["__scopeId","data-v-fb3af6d9"]]);const bt={class:"body"},ht={class:"intro"},yt={class:"toolbar"},kt={key:0,class:"meta"},wt={key:1,class:"loading"},xt={key:2,class:"error"},_t={key:3,class:"empty"},St={key:4,class:"table-wrap"},$t={class:"check-col"},Mt={class:"check-col"},Bt=["title"],Ct=["title"];var Ft=o({__name:"import-orphans-drawer",props:{modelValue:{type:Boolean},location:{}},emits:["update:modelValue","imported"],setup(t,{emit:a}){const o=t,m=a,y=e(),x=n(!1),S=n(!1),$=n(!1),M=n(!1),B=n(!1),V=n(!1),z=n(null),I=n([]),N=n([]),O=n(null),U=n(null),D=n(null),L=l(()=>I.value.length>0&&N.value.length===I.value.length);function T(){M.value=!1,B.value=!1,m("update:modelValue",!1)}function E(e){N.value=e?I.value.map(e=>e.filename_disk):[]}function j(){!I.value.length||S.value||$.value||(M.value=!0)}function A(){!N.value.length||S.value||$.value||(B.value=!0)}async function q(){if(o.location){x.value=!0,z.value=null;try{const e=await y.get(`/storage-manager/storages/${encodeURIComponent(o.location)}/orphans`);I.value=e.data?.data||[],O.value=e.data?.meta||null,V.value=!0,N.value=[]}catch(e){z.value=e?.response?.data?.errors?.[0]?.message||e?.message||String(e),I.value=[],O.value=null}finally{x.value=!1}}}async function P(){if(I.value.length){S.value=!0,z.value=null;try{const e=await y.post(`/storage-manager/storages/${encodeURIComponent(o.location)}/import-orphans`,{filename_disks:I.value.map(e=>e.filename_disk)}),t=e.data?.data;U.value={imported:Number(t?.imported||0),skipped:Number(t?.skipped||0),failed:Number(t?.failed||0)},M.value=!1,m("imported"),await q()}catch(e){z.value=e?.response?.data?.errors?.[0]?.message||e?.message||String(e),M.value=!1}finally{S.value=!1}}}async function R(){if(!N.value.length)return;$.value=!0,z.value=null;const e=[...N.value];try{const t=await y.post(`/storage-manager/storages/${encodeURIComponent(o.location)}/delete-orphans`,{filename_disks:e}),a=t.data?.data;D.value={deleted:Number(a?.deleted||0),skipped:Number(a?.skipped||0),failed:Number(a?.failed||0)},U.value=null,B.value=!1,m("imported"),await q()}catch(e){z.value=e?.response?.data?.errors?.[0]?.message||e?.message||String(e),B.value=!1}finally{$.value=!1}}return p(()=>o.modelValue,e=>{e&&(U.value=null,D.value=null,z.value=null,M.value=!1,B.value=!1,q())}),(e,a)=>{const n=r("v-icon"),l=r("v-button"),o=r("v-progress-circular"),p=r("v-checkbox"),m=r("v-drawer"),y=r("v-card-title"),K=r("v-card-text"),W=r("v-card-actions"),J=r("v-card"),Q=r("v-dialog"),G=C("tooltip");return i(),c(f,null,[u(m,{"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:T},{actions:s(()=>[N.value.length?F((i(),d(l,{key:0,icon:"",rounded:"",secondary:"",disabled:$.value||x.value,loading:$.value,onClick:A},{default:s(()=>[u(n,{name:"delete"})]),_:1},8,["disabled","loading"])),[[G,"Delete selected from disk",void 0,{bottom:!0}]]):b("v-if",!0),F((i(),d(l,{icon:"",rounded:"",disabled:!I.value.length||S.value||x.value||$.value,loading:S.value,onClick:j},{default:s(()=>[u(n,{name:"download"})]),_:1},8,["disabled","loading"])),[[G,"Import all unknown files",void 0,{bottom:!0}]])]),default:s(()=>[h("div",bt,[h("p",ht,[a[7]||(a[7]=_(" Finds files on ",-1)),h("strong",null,w(t.location),1),a[8]||(a[8]=_(" that aren’t registered in Directus yet. Import only creates database rows — nothing is copied. Titles come from the filename (underscores become spaces). You can delete selected unknown files from disk. Generated thumbnails are excluded. ",-1))]),h("div",yt,[u(l,{secondary:"",loading:x.value,disabled:$.value,onClick:q},{default:s(()=>[u(n,{name:"radar",left:""}),a[9]||(a[9]=_(" Scan disk ",-1))]),_:1},8,["loading","disabled"]),u(l,{disabled:!I.value.length||S.value||x.value||$.value,loading:S.value,onClick:j},{default:s(()=>[_(" Import "+w(I.value.length||"")+" new file"+w(1===I.value.length?"":"s"),1)]),_:1},8,["disabled","loading"]),u(l,{secondary:"",disabled:!N.value.length||S.value||x.value||$.value,loading:$.value,onClick:A},{default:s(()=>[u(n,{name:"delete",left:""}),_(" Delete selected ("+w(N.value.length)+") ",1)]),_:1},8,["disabled","loading"])]),O.value?(i(),c("p",kt," Scanned "+w(O.value.scanned.toLocaleString())+" disk objects · "+w(O.value.known.toLocaleString())+" already in DB · "+w(O.value.orphan_count.toLocaleString())+" unknown ",1)):b("v-if",!0),x.value?(i(),c("div",wt,[u(o,{indeterminate:""}),a[10]||(a[10]=h("span",null,"Listing storage…",-1))])):z.value?(i(),c("p",xt,w(z.value),1)):V.value&&!I.value.length?(i(),c("p",_t," No unknown files on this storage. Everything on disk is already in the database. ")):I.value.length?(i(),c("div",St,[h("table",null,[h("thead",null,[h("tr",null,[h("th",$t,[u(p,{"model-value":L.value,"onUpdate:modelValue":E},null,8,["model-value"])]),a[11]||(a[11]=h("th",null,"filename_disk",-1)),a[12]||(a[12]=h("th",null,"Title (will be)",-1)),a[13]||(a[13]=h("th",null,"Type",-1)),a[14]||(a[14]=h("th",null,"Size",-1))])]),h("tbody",null,[(i(!0),c(f,null,v(I.value,e=>(i(),c("tr",{key:e.filename_disk},[h("td",Mt,[u(p,{"model-value":N.value.includes(e.filename_disk),"onUpdate:modelValue":t=>function(e,t){t?N.value.includes(e)||(N.value=[...N.value,e]):N.value=N.value.filter(t=>t!==e)}(e.filename_disk,t)},null,8,["model-value","onUpdate:modelValue"])]),h("td",{class:"name",title:e.filename_disk},w(e.filename_disk),9,Bt),h("td",{class:"title-cell",title:e.title},w(e.title),9,Ct),h("td",null,w(e.type||"—"),1),h("td",null,w(g(re)(e.filesize)),1)]))),128))])])])):b("v-if",!0),D.value?(i(),c("div",{key:5,class:k(["result",D.value.failed?"has-fail":"ok"])}," Deleted "+w(D.value.deleted)+" · skipped "+w(D.value.skipped)+" · failed "+w(D.value.failed),3)):b("v-if",!0),U.value?(i(),c("div",{key:6,class:k(["result",U.value.failed?"has-fail":"ok"])}," Imported "+w(U.value.imported)+" · skipped "+w(U.value.skipped)+" · failed "+w(U.value.failed),3)):b("v-if",!0)])]),_:1},8,["model-value","title"]),u(Q,{"model-value":M.value,"onUpdate:modelValue":a[2]||(a[2]=e=>M.value=e),onEsc:a[3]||(a[3]=e=>M.value=!1)},{default:s(()=>[u(J,null,{default:s(()=>[u(y,null,{default:s(()=>[...a[15]||(a[15]=[_("Import new files?",-1)])]),_:1}),u(K,null,{default:s(()=>[a[16]||(a[16]=_(" Do you want to import ",-1)),h("strong",null,w(I.value.length.toLocaleString()),1),_(" new file"+w(1===I.value.length?"":"s")+" into storage ",1),h("strong",null,w(t.location),1),a[17]||(a[17]=_("? ",-1)),a[18]||(a[18]=h("br",null,null,-1)),a[19]||(a[19]=h("br",null,null,-1)),a[20]||(a[20]=_(" Each file keeps its exact ",-1)),a[21]||(a[21]=h("code",null,"filename_disk",-1)),a[22]||(a[22]=_(". Titles are generated from the filename with underscores replaced by spaces (extension stripped). ",-1))]),_:1}),u(W,null,{default:s(()=>[u(l,{secondary:"",disabled:S.value,onClick:a[1]||(a[1]=e=>M.value=!1)},{default:s(()=>[...a[23]||(a[23]=[_("Cancel",-1)])]),_:1},8,["disabled"]),u(l,{loading:S.value,onClick:P},{default:s(()=>[_(" Import "+w(I.value.length.toLocaleString()),1)]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1},8,["model-value"]),u(Q,{"model-value":B.value,"onUpdate:modelValue":a[5]||(a[5]=e=>B.value=e),onEsc:a[6]||(a[6]=e=>B.value=!1)},{default:s(()=>[u(J,null,{default:s(()=>[u(y,null,{default:s(()=>[...a[24]||(a[24]=[_("Delete from disk?",-1)])]),_:1}),u(K,null,{default:s(()=>[a[25]||(a[25]=_(" Permanently delete ",-1)),h("strong",null,w(N.value.length.toLocaleString()),1),_(" file"+w(1===N.value.length?"":"s")+" from storage ",1),h("strong",null,w(t.location),1),a[26]||(a[26]=_("? ",-1)),a[27]||(a[27]=h("br",null,null,-1)),a[28]||(a[28]=h("br",null,null,-1)),a[29]||(a[29]=_(" Only unregistered files are removed. Generated thumbnails are not deleted. ",-1))]),_:1}),u(W,null,{default:s(()=>[u(l,{secondary:"",disabled:$.value,onClick:a[4]||(a[4]=e=>B.value=!1)},{default:s(()=>[...a[30]||(a[30]=[_("Cancel",-1)])]),_:1},8,["disabled"]),u(l,{kind:"danger",loading:$.value,onClick:R},{default:s(()=>[_(" Delete "+w(N.value.length.toLocaleString()),1)]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1},8,["model-value"])],64)}}});ne("\n.body[data-v-b1ddf9b6] {\n\tpadding: 0 24px 24px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n}\n.intro[data-v-b1ddf9b6] {\n\tmargin: 0;\n\tline-height: 1.45;\n\tcolor: var(--theme--foreground);\n\tfont-size: 13px;\n}\n.intro code[data-v-b1ddf9b6],\n.v-card-text code[data-v-b1ddf9b6] {\n\tfont-size: 12px;\n}\n.toolbar[data-v-b1ddf9b6] {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n.meta[data-v-b1ddf9b6],\n.empty[data-v-b1ddf9b6] {\n\tmargin: 0;\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.loading[data-v-b1ddf9b6] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.error[data-v-b1ddf9b6] {\n\tmargin: 0;\n\tcolor: var(--theme--danger);\n\tfont-size: 13px;\n}\n.table-wrap[data-v-b1ddf9b6] {\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-b1ddf9b6] {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 13px;\n}\nth[data-v-b1ddf9b6],\ntd[data-v-b1ddf9b6] {\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-b1ddf9b6] {\n\tfont-weight: 700;\n\tbackground: var(--theme--background-subdued);\n\tposition: sticky;\n\ttop: 0;\n}\n.name[data-v-b1ddf9b6] {\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.check-col[data-v-b1ddf9b6] {\n\twidth: 36px;\n\tpadding-left: 8px;\n\tpadding-right: 4px;\n\tvertical-align: middle;\n}\n.check-col[data-v-b1ddf9b6] .v-checkbox {\n\tmargin: 0;\n}\n.title-cell[data-v-b1ddf9b6] {\n\tmax-width: 240px;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n.result[data-v-b1ddf9b6] {\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-b1ddf9b6] {\n\tbackground: color-mix(in srgb, var(--theme--success) 16%, transparent);\n\tcolor: var(--theme--success);\n}\n.result.has-fail[data-v-b1ddf9b6] {\n\tbackground: color-mix(in srgb, var(--theme--warning) 16%, transparent);\n\tcolor: var(--theme--warning);\n}\n",{});var Vt=le(Ft,[["__scopeId","data-v-b1ddf9b6"]]);function zt(a){const o=e(),{useNotificationsStore:r,useServerStore:i}=t(),d=r(),s=i?.(),u=n(!1),c=n(!1),f=n(0),v=l(()=>f.value>0);let p,b;function h(){u.value=!0,p=d.add({title:"Drop to upload",icon:"cloud_upload",type:"info",persist:!0,closeable:!1})}function y(){u.value=!1,p&&(d.remove(p),p=void 0)}function k(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 w(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&&(d.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(s?.info?.uploads?.maxConcurrency)||2);b=d.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&&d.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}`;d.add({title:o,type:"error",icon:"error"})}}}()))}finally{b&&(d.remove(b),b=void 0),c.value=!1,r<e.length&&(d.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",k),window.addEventListener("dragover",w),window.addEventListener("dragleave",x),window.addEventListener("drop",_)}),S(()=>{window.removeEventListener("dragenter",k),window.removeEventListener("dragover",w),window.removeEventListener("dragleave",x),window.removeEventListener("drop",_),y(),b&&d.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 It="storage-manager-files-preset",Nt="cards",Ot={icon:"insert_drive_file",title:"{{ title }}",subtitle:"{{ type }} • {{ filesize }}",size:4,imageFit:"crop"},Ut={sort:["-uploaded_on"],page:1,limit:25};function Dt(){const e=function(){try{const e=localStorage.getItem(It);return e?JSON.parse(e):{}}catch{return{}}}(),t=n(e.layout||Nt),a=n({...Ot,...e.layoutOptions||{}}),l=n({...Ut,...e.layoutQuery||{}}),o=n(null),r=n(null);return p([t,a,l],()=>{try{localStorage.setItem(It,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=Nt,a.value={...Ot},l.value={...Ut},o.value=null,r.value=null}}}function Lt(e,t){return e?t?{_and:[e,t]}:e:t??null}const Tt={class:"sidebar-actions"},Et={class:"sidebar-text storage-label"};var jt=o({__name:"collection-browser",props:{mode:{},folder:{},storage:{}},setup(e){const t=e,a=L(),o=T(),{folders:v}=K(),{storages:x,loadStorages:S}=H(),$=n(),M=n([]),{layout:B,layoutOptions:V,layoutQuery:z,filter:I,search:N,resetPreset:A}=Dt(),{layoutWrapper:q}=Fe(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),G=n(!1),Y=n("files"),X=l(()=>"files"===Y.value?M.value.length:"storage"===Y.value?J.value?.file_count??void 0:void 0),Z=l(()=>{if("storage"===Y.value)return J.value?.total_bytes});async function ee(){await($.value?.state?.refresh?.()),await S(!0).catch(()=>{})}function te(){I.value=null,N.value=null}function ae(e){Y.value=e,Q.value=!0}function ne(){G.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(G.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&&(G.value=!0)},{immediate:!0}),m(()=>{S().catch(()=>{})});const de=l(()=>"storage"===t.mode?{storage:t.storage}:{folder:t.folder??null}),se=n(null),{dragging:ue,showDropEffect:ce,uploading:fe,onFileInputChange:ve}=zt({preset:de,onDone:ee});function pe(){se.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=C("tooltip");return i(),d(O(g(q)),{ref_key:"layoutRef",ref:$,selection:M.value,"onUpdate:selection":a[11]||(a[11]=e=>M.value=e),"layout-options":g(V),"onUpdate:layoutOptions":a[12]||(a[12]=e=>y(V)?V.value=e:null),"layout-query":g(z),"onUpdate:layoutQuery":a[13]||(a[13]=e=>y(z)?z.value=e:null),filter:g(Lt)(g(I),P.value),"filter-user":g(I),"filter-system":P.value,search:g(N),collection:"directus_files","reset-preset":g(A)},{default:s(({layoutState:t})=>[u(m,{title:R.value,icon:"storage"===e.mode?J.value?.icon||"storage":"folder",class:k({dragging:g(ue)})},{headline:s(()=>[u(n,{items:W.value},null,8,["items"])]),"actions:prepend":s(()=>[(i(),d(O(`layout-actions-${g(B)}`),U(D(t)),null,16))]),actions:s(()=>[h("input",{ref_key:"fileInput",ref:se,class:"upload-input",type:"file",multiple:"",onChange:a[0]||(a[0]=(...e)=>g(ve)&&g(ve)(...e))},null,544),u(Ie,{modelValue:g(N),"onUpdate:modelValue":a[1]||(a[1]=e=>y(N)?N.value=e:null),filter:g(I),"onUpdate:filter":a[2]||(a[2]=e=>y(I)?I.value=e:null),collection:"directus_files"},null,8,["modelValue","filter"]),"storage"===e.mode?F((i(),d(Ne,{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?F((i(),d(Ne,{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?F((i(),d(Ne,{key:2,icon:"swap_horiz",onClick:a[4]||(a[4]=e=>ae("storage"))},null,512)),[[S,"Migrate All",void 0,{bottom:!0}]]):F((i(),d(Ne,{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:s(()=>[u(oe)]),sidebar:s(()=>[u(Le,{modelValue:g(B),"onUpdate:modelValue":a[6]||(a[6]=e=>y(B)?B.value=e:null)},{default:s(()=>[(i(),d(O(`layout-options-${g(B)}`),U(D(t)),null,16))]),_:2},1032,["modelValue"]),(i(),d(O(`layout-sidebar-${g(B)}`),U(D(t)),null,16)),u(p,{id:"actions",icon:"swap_horiz",title:"Actions"},{default:s(()=>[h("div",Tt,[u(l,{secondary:"","full-width":"",class:"sidebar-btn",loading:g(fe),onClick:pe},{default:s(()=>[_(w("storage"===e.mode?`Upload to ${e.storage}`:"Upload files"),1)]),_:1},8,["loading"]),"storage"===e.mode?(i(),d(l,{key:0,secondary:"","full-width":"",class:"sidebar-btn",onClick:ne},{default:s(()=>[_(" Detect files on "+w(e.storage),1)]),_:1})):b("v-if",!0),M.value.length?(i(),d(l,{key:1,secondary:"","full-width":"",class:"sidebar-btn",onClick:a[7]||(a[7]=e=>ae("files"))},{default:s(()=>[_(" Migrate selected ("+w(M.value.length)+") ",1)]),_:1})):b("v-if",!0),"storage"===e.mode?(i(),d(l,{key:2,"full-width":"",class:"sidebar-btn sidebar-btn-primary",onClick:a[8]||(a[8]=e=>ae("storage"))},{default:s(()=>[_(" Migrate all on "+w(e.storage),1)]),_:1})):(i(),d(l,{key:3,"full-width":"",class:"sidebar-btn sidebar-btn-primary",onClick:a[9]||(a[9]=e=>ae("folder"))},{default:s(()=>[_(w(e.folder?"Migrate this folder":"Migrate root files"),1)]),_:1}))])]),_:1}),"storage"===e.mode&&J.value?(i(),d(p,{key:0,id:"storage-info",icon:"info",title:"Storage"},{default:s(()=>[h("p",Et,w(J.value.label),1),u(me,{usage:J.value,compact:"",plain:""},null,8,["usage"])]),_:1})):(i(),d(p,{key:1,id:"folders-info",icon:"info",title:"Folders"},{default:s(()=>[...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:s(()=>[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(),d(O(`layout-${g(B)}`),U(D(ye(t))),{"no-results":s(()=>[g(I)||g(N)?(i(),d(o,{key:1,title:"No results",icon:"search",center:""},{append:s(()=>[u(l,{onClick:te},{default:s(()=>[...a[20]||(a[20]=[_("Clear filters",-1)])]),_:1})]),default:s(()=>[a[21]||(a[21]=_(" No files match your search or filters. ",-1))]),_:1})):(i(),d(o,{key:0,title:"No files",icon:"folder",center:""},{append:s(()=>[u(l,{onClick:pe},{default:s(()=>[...a[18]||(a[18]=[_("Add file",-1)])]),_:1})]),default:s(()=>[a[19]||(a[19]=_(" Drop files here to upload, or use the + button. ",-1))]),_:1}))]),"no-items":s(()=>[u(o,{title:"No files",icon:"folder",center:""},{append:s(()=>[u(l,{onClick:pe},{default:s(()=>[...a[22]||(a[22]=[_("Add file",-1)])]),_:1})]),default:s(()=>[a[23]||(a[23]=_(" Drop files here to upload, or use the + button. ",-1))]),_:1})]),_:1},16)),ge.value?(i(),d(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":Y.value,"file-ids":M.value,"folder-id":"folders"===e.mode?e.folder??null:null,"estimated-count":X.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(),d(Vt,{key:2,"model-value":G.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 At=le(jt,[["__scopeId","data-v-06c5b7c4"]]),qt=o({__name:"storage-view",setup(e){const t=L(),a=l(()=>String(t.params.location||""));return(e,t)=>(i(),d(At,{mode:"storage",storage:a.value},null,8,["storage"]))}}),Pt=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(),d(At,{mode:"folders",folder:a.value||void 0},null,8,["folder"]))}});const Rt=[],Kt=[],Wt=[],Jt=[a({id:"storage-manager",name:"Storage Manager",icon:"storage",routes:[{path:"",component:$e},{path:"storage/:location",component:qt},{path:"folders",component:Pt},{path:"folders/:folderId",component:Pt}],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)})],Qt=[],Gt=[],Ht=[{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{Kt as displays,Rt as interfaces,Wt as layouts,Jt as modules,Ht as operations,Qt as panels,Gt as themes};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "directus-extension-storage-manager",
|
|
3
3
|
"description": "Browse, upload, detect, and migrate Directus files across storage adapters (local, S3, GCS, Azure, and more)",
|
|
4
4
|
"icon": "storage",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.1.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"directus",
|