@acuity/directus-extension-acuity-backup 2.0.0 → 2.0.1

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 CHANGED
@@ -46,4 +46,4 @@ import{readFileSync as t,existsSync as e,writeFileSync as r,mkdirSync as n,creat
46
46
  * @ignore
47
47
  * @license [MIT]{@link https://github.com/archiverjs/node-archiver/blob/master/LICENSE}
48
48
  * @copyright (c) 2012-2014 Chris Talkington, contributors.
49
- */()),Fl=r}(),Zl=P(Hl);const Vl=new Map,Ql=new Map;function Yl(t,e){const r={sessionId:e,backupId:t,status:"pending",phase:"Initializing backup restore",progressPercentage:0,progressCurrent:0,progressTotal:0,startTime:(new Date).toISOString(),elapsedSeconds:0,errors:[]};Vl.set(e,r),function(t){const e=setInterval(()=>{const r=Vl.get(t);if(!r)return void clearInterval(e);const n=new Date(r.startTime).getTime(),i=Math.floor((Date.now()-n)/1e3);r.elapsedSeconds=i,"running"!==r.status&&clearInterval(e)},1e3)}(e)}function Kl(t,e){const r=Vl.get(t);if(!r)return;const n="completed"===r.status||"failed"===r.status;Object.assign(r,e),n||(r.status="running")}function Jl(t,e){const r=Vl.get(t);r&&r.errors.push({...e,timestamp:(new Date).toISOString()})}function Xl(t){return Vl.get(t)??null}function th(t,e){const r=Vl.get(t);if(!r)return;r.status=e,r.phase="completed"===e?"Restore completed":"Restore failed",r.progressPercentage=100;const n=setTimeout(()=>{Vl.delete(t),Ql.delete(t)},6e4);Ql.set(t,n)}function eh(){return l(process.env.BACKUP_PATH??"/directus/backups","restore-logs")}function rh(){const t=eh();e(t)||n(t,{recursive:!0})}const nh=new Set(["directus_access","directus_activity","directus_collections","directus_comments","directus_dashboards","directus_extensions","directus_fields","directus_files","directus_flows","directus_folders","directus_migrations","directus_notifications","directus_operations","directus_panels","directus_permissions","directus_policies","directus_presets","directus_relations","directus_revisions","directus_roles","directus_sessions","directus_settings","directus_shares","directus_translations","directus_users","directus_versions","directus_webhooks"]);function ih(){return process.env.BACKUP_PATH??"/directus/backups"}function sh(){const t=ih();e(t)||n(t,{recursive:!0})}let oh=null;function ah(){return l(ih(),"schedule.json")}function uh(){const r=ah();if(!e(r))return{enabled:!1,cronExpression:"0 2 * * *"};try{const e=t(r,"utf-8");return JSON.parse(e)}catch{return{enabled:!1,cronExpression:"0 2 * * *"}}}function ch(t){sh(),r(ah(),JSON.stringify(t,null,2),"utf-8")}function lh(t){null!==oh&&clearInterval(oh),oh=setInterval(async()=>{try{const e=uh();if(!e.enabled)return;const r=new Date,n=new Date(r.getFullYear(),r.getMonth(),r.getDate(),r.getHours(),r.getMinutes());if(!function(t,e){const r=t.trim().split(/\s+/);if(5!==r.length)return!1;function n(t,e){if("*"===t)return!0;if(t.includes("/")){const[,r]=t.split("/"),n=parseInt(r??"1",10);return e%n===0}if(t.includes(","))return t.split(",").some(t=>parseInt(t,10)===e);if(t.includes("-")){const[r,n]=t.split("-"),i=parseInt(r??"0",10),s=parseInt(n??"0",10);return e>=i&&e<=s}return parseInt(t,10)===e}const[i,s,o,a,u]=r;return n(i,e.getMinutes())&&n(s,e.getHours())&&n(o,e.getDate())&&n(a,e.getMonth()+1)&&n(u,e.getDay())}(e.cronExpression,n))return;if(e.lastRun){const t=new Date(e.lastRun);if(new Date(t.getFullYear(),t.getMonth(),t.getDate(),t.getHours(),t.getMinutes()).getTime()===n.getTime())return}t.logger.info("[acuity-backup] Scheduled backup triggered"),await dh(t,{type:"full",includeMedia:!0}),ch({...e,lastRun:r.toISOString()})}catch(e){t.logger.error("[acuity-backup] Scheduled backup failed:",e)}},6e4)}async function hh(t){const{CollectionsService:e}=t.services,r=new e({schema:await t.getSchema(),accountability:null});return(await r.readByQuery()).filter(t=>!nh.has(t.collection)&&!t.collection.startsWith("directus_")).map(t=>t.collection)}async function fh(t,e){const{ItemsService:r}=t.services,n=new r(e,{schema:await t.getSchema(),accountability:null});return await n.readByQuery({limit:-1,fields:["*"]})}async function dh(n,o){const a=await async function(t,e){sh();const n=(new Date).toISOString().replace(/[:.]/g,"-"),i=E(),o=`backup-${n}-${i}.zip`,a=l(ih(),o),c=await t.getSchema(),{RelationsService:h}=t.services,f="full"===e.type?await hh(t):e.collections??[],d={};let p=0;for(const e of f)try{const r=await fh(t,e);d[e]=r,p+=r.length}catch(r){t.logger.warn(`[acuity-backup] Failed to export collection "${e}":`,r),d[e]=[]}const g=new h({schema:c,accountability:null}),y=await g.readAll(),m=await async function(t,e){const r=await t.getSchema(),{ItemsService:n}=t.services;let i=[];try{const t=new n("directus_fields",{schema:r,accountability:null});i=(await t.readByQuery({limit:-1,filter:{collection:{_in:e}},fields:["collection","field","group"]})).filter(t=>null!==t.group&&void 0!==t.group).map(t=>({collection:t.collection,field:t.field,group:t.group}))}catch(e){t.logger.warn("[acuity-backup] Failed to export field groups:",e)}let s=[];try{const t=new n("directus_collections",{schema:r,accountability:null});s=(await t.readByQuery({limit:-1,filter:{collection:{_in:e}},fields:["*"]})).map(t=>{const{collection:e,...r}=t;return{collection:t.collection,display:null,display_template:t.display_template??null,sort_field:t.sort_field??null,hidden:t.hidden??!1,sort:t.sort??null,icon:t.icon??null,note:t.note??null,meta:r}})}catch(e){t.logger.warn("[acuity-backup] Failed to export collection views:",e)}return{schemaVersion:"1.0",fieldGroups:i,collectionViews:s}}(t,f);let b=[];try{const{ItemsService:e}=t.services,r=new e("directus_fields",{schema:c,accountability:null});b=await r.readByQuery({limit:-1,filter:{collection:{_in:f}},fields:["*"]})}catch(e){t.logger.warn("[acuity-backup] Failed to export field metadata:",e)}let _=[];try{const{ItemsService:e}=t.services,r=new e("directus_presets",{schema:c,accountability:null});_=await r.readByQuery({limit:-1,filter:{collection:{_in:f}},fields:["*"]})}catch(e){t.logger.warn("[acuity-backup] Failed to export collection presets:",e)}let v=[];if(e.includeMedia)try{const{ItemsService:e}=t.services,r=new e("directus_files",{schema:c,accountability:null});v=await r.readByQuery({limit:-1,fields:["*"]})}catch(e){t.logger.warn("[acuity-backup] Failed to list media files:",e)}const w={timestamp:(new Date).toISOString(),version:"1.2.0",type:e.type,collections:f,includeMedia:e.includeMedia,totalItems:p,fileSize:0,includesViewsAndGroups:!0};await new Promise((t,r)=>{const n=s(a),i=Zl("zip",{zlib:{level:6}});n.on("close",t),i.on("error",r),i.pipe(n),i.append(JSON.stringify(c,null,2),{name:"schema.json"}),i.append(JSON.stringify(y,null,2),{name:"relations.json"}),i.append(JSON.stringify(m,null,2),{name:"views.json"}),i.append(JSON.stringify(b,null,2),{name:"fields-meta.json"}),i.append(JSON.stringify(_,null,2),{name:"presets.json"}),i.append(JSON.stringify(w,null,2),{name:"manifest.json"});for(const[t,e]of Object.entries(d))i.append(JSON.stringify(e,null,2),{name:`collections/${t}.json`});if(e.includeMedia&&v.length>0)for(const t of v)i.append(JSON.stringify(t,null,2),{name:`files/${t.id}.meta.json`});i.finalize()});try{const t=await u(a);w.fileSize=t.size;const e=a.replace(/\.zip$/,".meta.json"),n={id:i,filename:o,timestamp:w.timestamp,type:w.type,collections:w.collections,includeMedia:w.includeMedia,fileSize:w.fileSize};r(e,JSON.stringify(n,null,2),"utf-8")}catch{}return a}(n,o),c=h(a),f=a.replace(/\.zip$/,".meta.json");let d;d=e(f)?JSON.parse(t(f,"utf-8")):{id:E(),filename:c,timestamp:(new Date).toISOString(),type:o.type,collections:o.collections??[],includeMedia:o.includeMedia,fileSize:0};try{const{FilesService:t}=n.services,e=new t({schema:await n.getSchema(),accountability:null}),s=i(a),o=await e.uploadOne(s,{storage:process.env.STORAGE_DEFAULT??process.env.STORAGE_LOCATIONS?.split(",")[0]?.trim()??"local",filename_download:c,type:"application/zip",title:`Backup ${d.timestamp}`});d.directusFileId=String(o),r(f,JSON.stringify(d,null,2),"utf-8")}catch(t){n.logger.warn("[acuity-backup] Failed to upload backup to Directus Files:",t)}return d}async function ph(){sh();const e=ih();let r;try{r=await o(e)}catch{return[]}const n=r.filter(t=>t.endsWith(".meta.json")),i=[];for(const r of n)try{const n=t(l(e,r),"utf-8");i.push(JSON.parse(n))}catch{}return i.sort((t,e)=>new Date(e.timestamp).getTime()-new Date(t.timestamp).getTime())}async function gh(t,n,i){const s=ih(),{backupId:a,truncateCollections:u=!1,collections:c}=n;i&&(Yl(a,i),Kl(i,{phase:"Finding backup archive",progressPercentage:5}));let h,f=null;try{h=await o(s)}catch{throw new Error("Backup directory is not accessible")}for(const t of h)if(t.endsWith(".zip")&&t.includes(a)){f=l(s,t);break}if(!f||!e(f))throw new Error(`Backup with id "${a}" not found`);i&&Kl(i,{phase:"Extracting ZIP archive",progressPercentage:10});let d;try{d=await new Function("m","return import(m)")("unzipper")}catch{throw new Error('The "unzipper" package is required for restore operations. Install it: npm install unzipper')}t.logger.info("[acuity-backup] Unzipper loaded, opening ZIP: "+f);const{ItemsService:p}=t.services,g={},y={},m=await d.Open.file(f);for(const t of m.files){const e=t.path;if(e.startsWith("collections/")||"manifest.json"===e||"schema.json"===e||"relations.json"===e||"views.json"===e||"fields-meta.json"===e||"presets.json"===e||e.startsWith("files/")&&e.endsWith(".meta.json")){const r=await t.buffer();e.startsWith("collections/")?g[e]=r:y[e]=r}}t.logger.info(`[acuity-backup] ZIP extracted: ${Object.keys(g).length} collection files, ${Object.keys(y).length} meta files`),i&&Kl(i,{phase:"Extraction complete",progressPercentage:20});const b=y["manifest.json"];if(!b)throw new Error("Invalid backup archive: manifest.json not found");const _=JSON.parse(b.toString("utf-8"));let v={},w=[],S=null;const E=y["schema.json"];if(E)try{v=JSON.parse(E.toString("utf-8"))}catch(e){t.logger.warn("[acuity-backup] Failed to parse schema.json:",e)}const k=y["relations.json"];if(k)try{w=JSON.parse(k.toString("utf-8"))}catch(e){t.logger.warn("[acuity-backup] Failed to parse relations.json:",e)}const x=y["views.json"];if(x)try{S=JSON.parse(x.toString("utf-8"))}catch{}let O=[];const T=y["fields-meta.json"];if(T)try{O=JSON.parse(T.toString("utf-8"))}catch(e){t.logger.warn("[acuity-backup] Failed to parse fields-meta.json:",e)}let R=[];const I=y["presets.json"];if(I)try{R=JSON.parse(I.toString("utf-8"))}catch(e){t.logger.warn("[acuity-backup] Failed to parse presets.json:",e)}const j=new Map;for(const t of w){const e=t;"directus_files"===e.related_collection&&e.collection&&e.field&&(j.has(e.collection)||j.set(e.collection,new Set),j.get(e.collection).add(e.field))}for(const t of O){const e=t.special;if(e&&Array.isArray(e)&&e.includes("file")){const e=t.collection,r=t.field;j.has(e)||j.set(e,new Set),j.get(e).add(r)}}const L=new Map;for(const t of w){const e=t;e.collection&&e.field&&(L.has(e.collection)||L.set(e.collection,new Set),L.get(e.collection).add(e.field))}for(const t of O){const e=t.special;if(e&&Array.isArray(e)&&(e.includes("m2o")||e.includes("file")||e.includes("user-created")||e.includes("user-updated"))){const e=t.collection,r=t.field;L.has(e)||L.set(e,new Set),L.get(e).add(r)}}const A=c??_.collections,M=[];let D=0,P=0,N=0;await async function(t,e,r,n,i,s){const o=await t.getSchema(),{CollectionsService:a,RelationsService:u}=t.services,c=[];let l=0;const h=e?.collections??{},f=new Map;for(const t of s){const e=`${t.collection}.${t.field}`;f.set(e,t)}const d=new a({schema:o,accountability:null});if(i)for(const e of i.collectionViews){const r=e.collection;if(h[r])continue;if(!n.includes(r))continue;const i=e.meta??{},{id:s,collection:o,...a}=i;a.icon=e.icon??a.icon??null,a.note=e.note??a.note??null,a.sort=e.sort??a.sort??null;try{await d.readOne(r);try{await d.updateOne(r,{meta:a}),t.logger.info(`[acuity-backup] Updated virtual folder "${r}" meta`)}catch{}continue}catch{}try{await d.createOne({collection:r,schema:null,meta:a}),c.push(r),t.logger.info(`[acuity-backup] Created virtual folder collection "${r}"`)}catch(e){t.logger.warn(`[acuity-backup] Failed to create virtual folder "${r}":`,e)}}const p=n.filter(t=>!o.collections[t]&&!c.includes(t));if(0===p.length&&0===c.length&&0===r.length)return{createdCollections:c,createdRelations:l};p.length>0&&t.logger.info(`[acuity-backup] Need to create ${p.length} missing collection(s): ${p.join(", ")}`);for(const e of p){const r=h[e];if(!r){t.logger.warn(`[acuity-backup] Collection "${e}" not found in backup schema — cannot recreate`);continue}const n=[],s=r.fields??{};for(const[t,i]of Object.entries(s)){const s=t===r.primary,o=`${e}.${t}`,a=f.get(o);if(i.alias){const e={field:i.field??t,type:"alias",schema:null,meta:a?{interface:a.interface,special:a.special??i.special??["alias","no-data"],options:a.options??null,display:a.display??null,display_options:a.display_options??null,width:a.width??"full",sort:a.sort??null,note:a.note??null,group:a.group??null,hidden:a.hidden??!1,required:a.required??!1}:{special:i.special??["alias","no-data"]}};n.push(e);continue}const u={special:i.special??[],hidden:!!s||(a?.hidden??!1),readonly:!!s||(a?.readonly??!1)};a&&(u.interface=a.interface,u.display=a.display??null,u.display_options=a.display_options??null,u.options=a.options??null,u.width=a.width??"full",u.sort=a.sort??null,u.note=a.note??null,u.required=a.required??!1,u.group=a.group??null,a.special&&(u.special=a.special));const c={field:i.field??t,type:i.type,schema:{is_nullable:!s&&!1!==i.nullable,default_value:i.defaultValue??null,is_primary_key:s,has_auto_increment:s&&("integer"===i.type||!0===i.generated)},meta:u};i.maxLength&&(c.schema.max_length=i.maxLength),n.push(c)}let o={};if(i){const t=i.collectionViews.find(t=>t.collection===e);if(t?.meta){const{collection:e,id:r,...n}=t.meta;o=n}}try{await d.createOne({collection:e,schema:{name:e},meta:{...o,singleton:r.singleton??!1,sort_field:r.sortField??null},fields:n}),c.push(e),t.logger.info(`[acuity-backup] Created collection "${e}" with ${n.length} fields`)}catch(r){t.logger.error(`[acuity-backup] Failed to create collection "${e}":`,r)}}if(r.length>0&&c.length>0){const e=await t.getSchema(),n=new u({schema:e,accountability:null});for(const i of r){const r=i;if((!r.collection?.startsWith("directus_")||!r.related_collection?.startsWith("directus_"))&&(c.includes(r.collection)||null!=r.related_collection&&c.includes(r.related_collection)))if(!e.collections[r.collection]||r.related_collection&&!e.collections[r.related_collection])t.logger.warn(`[acuity-backup] Skipping relation ${r.collection}.${r.field}: one or both collections missing`);else try{const e={...r.meta??{}};delete e.id,await n.createOne({collection:r.collection,field:r.field,related_collection:r.related_collection,meta:e,schema:r.schema??null}),l++,t.logger.info(`[acuity-backup] Created relation: ${r.collection}.${r.field} → ${r.related_collection}`)}catch(e){t.logger.warn(`[acuity-backup] Failed to create relation ${r.collection}.${r.field}:`,e)}}}return{createdCollections:c,createdRelations:l}}(t,v,w,A,S,O);let C=await t.getSchema(),B=0;if(O.length>0){i&&Kl(i,{phase:"Restoring field metadata",progressPercentage:22});const{FieldsService:e}=t.services,r=new e({schema:C,accountability:null}),n=new Map;for(const t of O){const e=`${t.collection}.${t.field}`;n.set(e,t)}for(const e of O){const n=e.collection,i=e.field;if(!A.includes(n))continue;const{id:s,collection:o,field:a,...u}=e;try{await r.updateField(n,i,{meta:u}),B++}catch{const s=e.special;if(s&&Array.isArray(s)&&s.includes("alias"))try{await r.createField(n,{field:i,type:"alias",schema:null,meta:u}),B++}catch(e){t.logger.warn(`[acuity-backup] Failed to create alias field "${n}.${i}":`,e)}}}t.logger.info(`[acuity-backup] Restored metadata for ${B} fields`),C=await t.getSchema()}i&&Kl(i,{phase:"Restoring media files",progressPercentage:25});let F=0;const z={};if(_.includeMedia){const e=new p("directus_files",{schema:C,accountability:null});for(const[r,n]of Object.entries(y))if(r.startsWith("files/")&&r.endsWith(".meta.json"))try{const r=JSON.parse(n.toString("utf-8")),s=r.id;try{let t=null;try{t=await e.readOne(s)}catch{}if(t){const{id:t,...n}=r;await e.updateOne(s,n),z[s]=s}else{const t=await e.createOne(r);z[s]=String(t)}F++}catch(e){t.logger.warn(`[acuity-backup] Failed to restore file record "${s}":`,e),i&&Jl(i,{message:`Failed to restore file: ${s}`})}}catch{}}t.logger.info(`[acuity-backup] Restored ${F} media file records`),i&&Kl(i,{phase:"Media files restored",progressPercentage:35});for(let e=0;e<A.length;e++){const r=A[e],n=`collections/${r}.json`,s=g[n];if(!s){t.logger.warn(`[acuity-backup] Collection file not found in archive: ${n}`),P++,i&&Jl(i,{collection:r,message:"Collection file not found in archive"});continue}let o;try{o=JSON.parse(s.toString("utf-8"))}catch{t.logger.warn(`[acuity-backup] Failed to parse collection JSON: ${n}`),P++,i&&Jl(i,{collection:r,message:"Failed to parse collection JSON"});continue}if(!Array.isArray(o)||0===o.length){if(M.push(r),D++,i){const t=35+30*(e+1)/A.length;Kl(i,{phase:`Restoring collections (${e+1}/${A.length})`,progressPercentage:Math.round(t),progressCurrent:e+1,progressTotal:A.length})}continue}const a=L.get(r);if(a)for(const t of o)for(const e of a){const r=t[e];if(null!==r&&"object"==typeof r&&!Array.isArray(r)){const n=r;void 0!==n.id&&(t[e]=n.id)}}const c=j.get(r);if(c&&Object.keys(z).length>0)for(const t of o)for(const e of c){const r=t[e];"string"==typeof r&&z[r]&&z[r]!==r&&(t[e]=z[r])}const l=new p(r,{schema:C,accountability:null});try{if(u){const t=await l.readByQuery({limit:-1,fields:["id"]});if(t.length>0){const e=t.map(t=>t.id);await l.deleteMany(e)}}if(await l.upsertMany(o),M.push(r),D++,N+=o.length,i){const t=35+30*(e+1)/A.length;Kl(i,{phase:`Restoring collections (${e+1}/${A.length})`,progressPercentage:Math.round(t),progressCurrent:e+1,progressTotal:A.length})}}catch(e){t.logger.error(`[acuity-backup] Failed to restore collection "${r}":`,e),P++,i&&Jl(i,{collection:r,message:String(e)})}}let U=0,W=0;if(i&&Kl(i,{phase:"Restoring field groups and view settings",progressPercentage:75}),S&&!1!==_.includesViewsAndGroups){const{FieldsService:e,CollectionsService:r}=t.services,n=await t.getSchema();if(S.fieldGroups.length>0){const r=new e({schema:n,accountability:null});for(const e of S.fieldGroups)if(A.includes(e.collection))try{await r.updateField(e.collection,e.field,{group:e.group}),U++}catch(r){t.logger.warn(`[acuity-backup] Failed to restore group for field "${e.collection}.${e.field}":`,r),i&&Jl(i,{message:`Failed to restore field group for ${e.collection}.${e.field}`})}}if(S.collectionViews.length>0){const e=new r({schema:n,accountability:null});for(const r of S.collectionViews){if(!A.includes(r.collection))continue;const n=r.meta??{},{id:s,collection:o,...a}=n,u={...a,display:r.display,display_template:r.display_template,sort_field:r.sort_field,hidden:r.hidden,sort:r.sort,icon:r.icon,note:r.note};try{await e.updateOne(r.collection,{meta:u}),W++}catch(e){t.logger.warn(`[acuity-backup] Failed to restore view settings for collection "${r.collection}":`,e),i&&Jl(i,{collection:r.collection,message:"Failed to restore view settings"})}}}}let G=0;if(i&&Kl(i,{phase:"Restoring collection layout presets",progressPercentage:85}),R.length>0){const{ItemsService:e}=t.services,r=new e("directus_presets",{schema:await t.getSchema(),accountability:null}),n=R.filter(t=>t.collection&&A.includes(t.collection));for(const e of n)try{const{id:t,user:n,...i}=e,s=await r.readByQuery({limit:1,filter:{collection:{_eq:i.collection},user:{_null:!0},...i.bookmark?{bookmark:{_eq:i.bookmark}}:{bookmark:{_null:!0}},...i.role?{role:{_eq:i.role}}:{role:{_null:!0}}},fields:["id"]});s.length>0?await r.updateOne(s[0].id,i):await r.createOne(i),G++}catch(r){t.logger.warn(`[acuity-backup] Failed to restore preset for collection "${e.collection}":`,r),i&&Jl(i,{collection:e.collection,message:"Failed to restore layout preset"})}}const q={restoredCollections:M,restoredFiles:F,restoredFieldGroups:U,restoredCollectionViews:W,restoredPresets:G,errors:[]};if(i){const t=Xl(i);if(t){const e=function(t,e){const r=new Date(t.startTime).getTime(),n=Date.now()-r;return{id:t.sessionId,sessionId:t.sessionId,backupId:t.backupId,timestamp:t.startTime,duration:n,status:"completed"===t.status?0===t.errors.length?"success":"partial":"failed",summary:e,errors:t.errors}}(t,{collectionsAttempted:A.length,collectionsSuccess:D,collectionsFailed:P,itemsRestored:N,mediaFilesRestored:F,fieldGroupsRestored:U,collectionViewsRestored:W,presetsRestored:G});!function(t){rh();const e=l(eh(),`${t.sessionId}.json`);r(e,JSON.stringify(t,null,2),"utf-8")}(e),th(i,(t.errors.length,"completed"))}Kl(i,{phase:"Restore completed",progressPercentage:100})}return q}function yh(t,e,r){const n=t.accountability;n?.admin?r():e.status(403).json({errors:[{message:"Forbidden: admin access required",extensions:{code:"FORBIDDEN"}}]})}const mh=[],bh=[{name:"acuity-backup",config:(n,h)=>{lh(h),n.get("/collections",yh,async(t,e)=>{try{const t=await hh(h),{CollectionsService:r}=h.services,n=new r({schema:await h.getSchema(),accountability:null}),i=(await n.readByQuery()).filter(e=>t.includes(e.collection)).map(t=>({collection:t.collection,icon:t.meta?.icon??null,note:t.meta?.note??null,singleton:t.meta?.singleton??!1}));e.json({data:i})}catch(t){h.logger.error("[acuity-backup] GET /collections error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.get("/list",yh,async(t,e)=>{try{const t=await ph();e.json({data:t})}catch(t){h.logger.error("[acuity-backup] GET /list error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.post("/run",yh,async(t,e)=>{try{const r=t.body,n="selective"===r.type?"selective":"full",i=!1!==r.includeMedia,s="selective"===n?r.collections??[]:void 0;if("selective"===n&&(!s||0===s.length))return void e.status(400).json({errors:[{message:'Selective backup requires at least one collection in the "collections" array.'}]});const o=await dh(h,{type:n,collections:s,includeMedia:i});e.status(201).json({data:o})}catch(t){h.logger.error("[acuity-backup] POST /run error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.get("/schedule",yh,(t,e)=>{try{const t=uh();e.json({data:t})}catch(t){h.logger.error("[acuity-backup] GET /schedule error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.post("/schedule",yh,(t,e)=>{try{const r=t.body;if("boolean"!=typeof r.enabled)return void e.status(400).json({errors:[{message:'"enabled" must be a boolean'}]});if("string"!=typeof r.cronExpression||!r.cronExpression.trim())return void e.status(400).json({errors:[{message:'"cronExpression" must be a non-empty string'}]});if(5!==r.cronExpression.trim().split(/\s+/).length)return void e.status(400).json({errors:[{message:'"cronExpression" must be a valid 5-field cron expression (minute hour dom month dow)'}]});const n={...uh(),enabled:r.enabled,cronExpression:r.cronExpression.trim()};ch(n),e.json({data:n})}catch(t){h.logger.error("[acuity-backup] POST /schedule error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.get("/restore/:id",yh,async(t,e)=>{try{const{id:r}=t.params,n=(await ph()).find(t=>t.id===r);if(!n)return void e.status(404).json({errors:[{message:`Backup "${r}" not found`}]});e.json({data:n})}catch(t){h.logger.error("[acuity-backup] GET /restore/:id error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.post("/restore",yh,async(t,e)=>{try{const r=t.body;if(!r.backupId||"string"!=typeof r.backupId)return void e.status(400).json({errors:[{message:'"backupId" is required'}]});const n=E(),i={backupId:r.backupId,truncateCollections:r.truncateCollections??!1,collections:r.collections,sessionId:n};e.json({data:{sessionId:n,message:"Restore started. Poll /acuity-backup/status/:sessionId for progress."}}),(async()=>{try{await gh(h,i,n)}catch(t){h.logger.error("[acuity-backup] Background restore error:",t);Xl(n)&&(Jl(n,{message:String(t)}),th(n,"failed"))}})()}catch(t){h.logger.error("[acuity-backup] POST /restore error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.delete("/:id",yh,async(t,r)=>{try{const{id:n}=t.params,i=(await ph()).find(t=>t.id===n);if(!i)return void r.status(404).json({errors:[{message:`Backup "${n}" not found`}]});const s=l(ih(),i.filename),o=s.replace(/\.zip$/,".meta.json"),u=[];e(s)&&u.push(a(s)),e(o)&&u.push(a(o)),await Promise.all(u),r.json({data:{success:!0,id:n}})}catch(t){h.logger.error("[acuity-backup] DELETE /:id error:",t),r.status(500).json({errors:[{message:String(t)}]})}}),n.get("/status/:sessionId",yh,(t,e)=>{try{const{sessionId:r}=t.params,n=Xl(r);if(!n)return void e.status(404).json({errors:[{message:`Session "${r}" not found`}]});e.json({data:n})}catch(t){h.logger.error("[acuity-backup] GET /status/:sessionId error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.get("/restore-logs",yh,async(e,r)=>{try{const e=await async function(){rh();const e=eh();try{const r=await o(e),n=[];for(const i of r){if(!i.endsWith(".json"))continue;const r=l(e,i);try{const e=t(r,"utf-8"),i=JSON.parse(e);n.push(i)}catch{}}return n.sort((t,e)=>new Date(e.timestamp).getTime()-new Date(t.timestamp).getTime()),n}catch{return[]}}();r.json({data:e})}catch(t){h.logger.error("[acuity-backup] GET /restore-logs error:",t),r.status(500).json({errors:[{message:String(t)}]})}}),n.get("/restore-logs/:logId",yh,(r,n)=>{try{const{logId:i}=r.params,s=function(r){rh();const n=l(eh(),`${r}.json`);if(!e(n))return null;try{const e=t(n,"utf-8");return JSON.parse(e)}catch{return null}}(i);if(!s)return void n.status(404).json({errors:[{message:`Restore log "${i}" not found`}]});n.json({data:s})}catch(t){h.logger.error("[acuity-backup] GET /restore-logs/:logId error:",t),n.status(500).json({errors:[{message:String(t)}]})}}),n.delete("/restore-logs/:logId",yh,async(t,r)=>{try{const{logId:n}=t.params;await async function(t){const r=l(eh(),`${t}.json`);e(r)&&await a(r)}(n),r.json({data:{success:!0,logId:n}})}catch(t){h.logger.error("[acuity-backup] DELETE /restore-logs/:logId error:",t),r.status(500).json({errors:[{message:String(t)}]})}}),n.get("/download/:id",yh,async(t,r)=>{try{const{id:n}=t.params,s=(await ph()).find(t=>t.id===n);if(!s)return void r.status(404).json({errors:[{message:`Backup "${n}" not found`}]});const o=l(ih(),s.filename);if(!e(o))return void r.status(404).json({errors:[{message:`Backup file "${s.filename}" not found on disk`}]});r.setHeader("Content-Type","application/zip"),r.setHeader("Content-Disposition",`attachment; filename="${s.filename}"`);i(o).pipe(r)}catch(t){h.logger.error("[acuity-backup] GET /download/:id error:",t),r.status(500).json({errors:[{message:String(t)}]})}}),n.post("/upload",yh,async(t,e)=>{const n=524288e3;try{const i=parseInt(t.headers["content-length"]??"0",10);if(i>n)return void e.status(413).json({errors:[{message:`Upload too large (${Math.round(i/1024/1024)} MB). Maximum is 500 MB.`}]});sh();const o=ih(),d=E(),p=l(o,`upload-temp-${d}.zip`);await f(t,s(p));const g=await u(p);if(0===g.size)return await a(p),void e.status(400).json({errors:[{message:"Uploaded file is empty."}]});if(g.size>n)return await a(p),void e.status(413).json({errors:[{message:`Upload too large (${Math.round(g.size/1024/1024)} MB). Maximum is 500 MB.`}]});const y="unzipper";let m,b;try{m=await new Function("m","return import(m)")(y)}catch{return await a(p),void e.status(500).json({errors:[{message:'The "unzipper" package is required. Install it: npm install unzipper'}]})}try{const t=(await m.Open.file(p)).files.find(t=>"manifest.json"===t.path);if(!t)return await a(p),void e.status(400).json({errors:[{message:"Invalid backup: ZIP does not contain a manifest.json file."}]});const r=await t.buffer();b=JSON.parse(r.toString("utf-8"))}catch(t){return await a(p),void e.status(400).json({errors:[{message:"Invalid or corrupted ZIP file: "+String(t)}]})}const _=E(),v=b.timestamp||(new Date).toISOString(),w=`backup-${v.replace(/[:.]/g,"-").replace("T","_").slice(0,19)}-${_}.zip`,S=l(o,w);await c(p,S);const k={id:_,filename:w,timestamp:v,type:b.type??"full",collections:b.collections??[],includeMedia:b.includeMedia??!1,fileSize:g.size},x=S.replace(/\.zip$/,".meta.json");r(x,JSON.stringify(k,null,2),"utf-8"),h.logger.info(`[acuity-backup] Uploaded backup: ${w} (${Math.round(g.size/1024)} KB)`),e.json({data:k})}catch(t){h.logger.error("[acuity-backup] POST /upload error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),async function(){rh();const t=eh(),e=Date.now()-7776e6;let r=0;try{const n=await o(t);for(const i of n){if(!i.endsWith(".json"))continue;const n=l(t,i);try{(await u(n)).mtime.getTime()<e&&(await a(n),r++)}catch{}}}catch{}return r}().then(t=>{t>0&&h.logger.info(`[acuity-backup] Cleaned up ${t} restore logs older than 90 days`)})}}],_h=[];export{bh as endpoints,mh as hooks,_h as operations};
49
+ */()),Fl=r}(),Zl=P(Hl);const Vl=new Map,Ql=new Map;function Yl(t,e){const r={sessionId:e,backupId:t,status:"pending",phase:"Initializing backup restore",progressPercentage:0,progressCurrent:0,progressTotal:0,startTime:(new Date).toISOString(),elapsedSeconds:0,errors:[]};Vl.set(e,r),function(t){const e=setInterval(()=>{const r=Vl.get(t);if(!r)return void clearInterval(e);const n=new Date(r.startTime).getTime(),i=Math.floor((Date.now()-n)/1e3);r.elapsedSeconds=i,"running"!==r.status&&clearInterval(e)},1e3)}(e)}function Kl(t,e){const r=Vl.get(t);if(!r)return;const n="completed"===r.status||"failed"===r.status;Object.assign(r,e),n||(r.status="running")}function Jl(t,e){const r=Vl.get(t);r&&r.errors.push({...e,timestamp:(new Date).toISOString()})}function Xl(t){return Vl.get(t)??null}function th(t,e){const r=Vl.get(t);if(!r)return;r.status=e,r.phase="completed"===e?"Restore completed":"Restore failed",r.progressPercentage=100;const n=setTimeout(()=>{Vl.delete(t),Ql.delete(t)},6e4);Ql.set(t,n)}function eh(){return l(process.env.BACKUP_PATH??"/directus/backups","restore-logs")}function rh(){const t=eh();e(t)||n(t,{recursive:!0})}const nh=new Set(["directus_access","directus_activity","directus_collections","directus_comments","directus_dashboards","directus_extensions","directus_fields","directus_files","directus_flows","directus_folders","directus_migrations","directus_notifications","directus_operations","directus_panels","directus_permissions","directus_policies","directus_presets","directus_relations","directus_revisions","directus_roles","directus_sessions","directus_settings","directus_shares","directus_translations","directus_users","directus_versions","directus_webhooks"]);function ih(){return process.env.BACKUP_PATH??"/directus/backups"}function sh(){const t=ih();e(t)||n(t,{recursive:!0})}let oh=null;function ah(){return l(ih(),"schedule.json")}function uh(){const r=ah();if(!e(r))return{enabled:!1,cronExpression:"0 2 * * *"};try{const e=t(r,"utf-8");return JSON.parse(e)}catch{return{enabled:!1,cronExpression:"0 2 * * *"}}}function ch(t){sh(),r(ah(),JSON.stringify(t,null,2),"utf-8")}function lh(t){null!==oh&&clearInterval(oh),oh=setInterval(async()=>{try{const e=uh();if(!e.enabled)return;const r=new Date,n=new Date(r.getFullYear(),r.getMonth(),r.getDate(),r.getHours(),r.getMinutes());if(!function(t,e){const r=t.trim().split(/\s+/);if(5!==r.length)return!1;function n(t,e){if("*"===t)return!0;if(t.includes("/")){const[,r]=t.split("/"),n=parseInt(r??"1",10);return e%n===0}if(t.includes(","))return t.split(",").some(t=>parseInt(t,10)===e);if(t.includes("-")){const[r,n]=t.split("-"),i=parseInt(r??"0",10),s=parseInt(n??"0",10);return e>=i&&e<=s}return parseInt(t,10)===e}const[i,s,o,a,u]=r;return n(i,e.getMinutes())&&n(s,e.getHours())&&n(o,e.getDate())&&n(a,e.getMonth()+1)&&n(u,e.getDay())}(e.cronExpression,n))return;if(e.lastRun){const t=new Date(e.lastRun);if(new Date(t.getFullYear(),t.getMonth(),t.getDate(),t.getHours(),t.getMinutes()).getTime()===n.getTime())return}t.logger.info("[acuity-backup] Scheduled backup triggered"),await dh(t,{type:"full",includeMedia:!0}),ch({...e,lastRun:r.toISOString()})}catch(e){t.logger.error("[acuity-backup] Scheduled backup failed:",e)}},6e4)}async function hh(t){const{CollectionsService:e}=t.services,r=new e({schema:await t.getSchema(),accountability:null});return(await r.readByQuery()).filter(t=>!nh.has(t.collection)&&!t.collection.startsWith("directus_")).map(t=>t.collection)}async function fh(t,e){const{ItemsService:r}=t.services,n=new r(e,{schema:await t.getSchema(),accountability:null});return await n.readByQuery({limit:-1,fields:["*"]})}async function dh(n,o){const a=await async function(t,e){sh();const n=(new Date).toISOString().replace(/[:.]/g,"-"),i=E(),o=`backup-${n}-${i}.zip`,a=l(ih(),o),c=await t.getSchema(),{RelationsService:h}=t.services,f="full"===e.type?await hh(t):e.collections??[],d={};let p=0;for(const e of f)try{const r=await fh(t,e);d[e]=r,p+=r.length}catch(r){t.logger.warn(`[acuity-backup] Failed to export collection "${e}":`,r),d[e]=[]}const g=new h({schema:c,accountability:null}),y=await g.readAll(),m=await async function(t,e){const r=await t.getSchema(),{ItemsService:n}=t.services;let i=[];try{const t=new n("directus_fields",{schema:r,accountability:null});i=(await t.readByQuery({limit:-1,filter:{collection:{_in:e}},fields:["collection","field","group"]})).filter(t=>null!==t.group&&void 0!==t.group).map(t=>({collection:t.collection,field:t.field,group:t.group}))}catch(e){t.logger.warn("[acuity-backup] Failed to export field groups:",e)}let s=[];try{const t=new n("directus_collections",{schema:r,accountability:null});s=(await t.readByQuery({limit:-1,filter:{collection:{_in:e}},fields:["*"]})).map(t=>{const{collection:e,...r}=t;return{collection:t.collection,display:null,display_template:t.display_template??null,sort_field:t.sort_field??null,hidden:t.hidden??!1,sort:t.sort??null,icon:t.icon??null,note:t.note??null,meta:r}})}catch(e){t.logger.warn("[acuity-backup] Failed to export collection views:",e)}return{schemaVersion:"1.0",fieldGroups:i,collectionViews:s}}(t,f);let b=[];try{const{ItemsService:e}=t.services,r=new e("directus_fields",{schema:c,accountability:null});b=await r.readByQuery({limit:-1,filter:{collection:{_in:f}},fields:["*"]})}catch(e){t.logger.warn("[acuity-backup] Failed to export field metadata:",e)}let _=[];try{const{ItemsService:e}=t.services,r=new e("directus_presets",{schema:c,accountability:null});_=await r.readByQuery({limit:-1,filter:{collection:{_in:f}},fields:["*"]})}catch(e){t.logger.warn("[acuity-backup] Failed to export collection presets:",e)}let v=[];if(e.includeMedia)try{const{ItemsService:e}=t.services,r=new e("directus_files",{schema:c,accountability:null});v=await r.readByQuery({limit:-1,fields:["*"]})}catch(e){t.logger.warn("[acuity-backup] Failed to list media files:",e)}const w={timestamp:(new Date).toISOString(),version:"1.2.0",type:e.type,collections:f,includeMedia:e.includeMedia,totalItems:p,fileSize:0,includesViewsAndGroups:!0};await new Promise((t,r)=>{const n=s(a),i=Zl("zip",{zlib:{level:6}});n.on("close",t),i.on("error",r),i.pipe(n),i.append(JSON.stringify(c,null,2),{name:"schema.json"}),i.append(JSON.stringify(y,null,2),{name:"relations.json"}),i.append(JSON.stringify(m,null,2),{name:"views.json"}),i.append(JSON.stringify(b,null,2),{name:"fields-meta.json"}),i.append(JSON.stringify(_,null,2),{name:"presets.json"}),i.append(JSON.stringify(w,null,2),{name:"manifest.json"});for(const[t,e]of Object.entries(d))i.append(JSON.stringify(e,null,2),{name:`collections/${t}.json`});if(e.includeMedia&&v.length>0)for(const t of v)i.append(JSON.stringify(t,null,2),{name:`files/${t.id}.meta.json`});i.finalize()});try{const t=await u(a);w.fileSize=t.size;const e=a.replace(/\.zip$/,".meta.json"),n={id:i,filename:o,timestamp:w.timestamp,type:w.type,collections:w.collections,includeMedia:w.includeMedia,fileSize:w.fileSize};r(e,JSON.stringify(n,null,2),"utf-8")}catch{}return a}(n,o),c=h(a),f=a.replace(/\.zip$/,".meta.json");let d;d=e(f)?JSON.parse(t(f,"utf-8")):{id:E(),filename:c,timestamp:(new Date).toISOString(),type:o.type,collections:o.collections??[],includeMedia:o.includeMedia,fileSize:0};try{const{FilesService:t}=n.services,e=new t({schema:await n.getSchema(),accountability:null}),s=i(a),o=await e.uploadOne(s,{storage:process.env.STORAGE_DEFAULT??process.env.STORAGE_LOCATIONS?.split(",")[0]?.trim()??"local",filename_download:c,type:"application/zip",title:`Backup ${d.timestamp}`});d.directusFileId=String(o),r(f,JSON.stringify(d,null,2),"utf-8")}catch(t){n.logger.warn("[acuity-backup] Failed to upload backup to Directus Files:",t)}return d}async function ph(){sh();const e=ih();let r;try{r=await o(e)}catch{return[]}const n=r.filter(t=>t.endsWith(".meta.json")),i=[];for(const r of n)try{const n=t(l(e,r),"utf-8");i.push(JSON.parse(n))}catch{}return i.sort((t,e)=>new Date(e.timestamp).getTime()-new Date(t.timestamp).getTime())}async function gh(t,n,i){const s=ih(),{backupId:a,truncateCollections:u=!1,collections:c}=n;i&&(Yl(a,i),Kl(i,{phase:"Finding backup archive",progressPercentage:5}));let h,f=null;try{h=await o(s)}catch{throw new Error("Backup directory is not accessible")}for(const t of h)if(t.endsWith(".zip")&&t.includes(a)){f=l(s,t);break}if(!f||!e(f))throw new Error(`Backup with id "${a}" not found`);i&&Kl(i,{phase:"Extracting ZIP archive",progressPercentage:10});let d;try{d=await new Function("m","return import(m)")("unzipper")}catch{throw new Error('The "unzipper" package is required for restore operations. Install it: npm install unzipper')}t.logger.info("[acuity-backup] Unzipper loaded, opening ZIP: "+f);const{ItemsService:p}=t.services,g={},y={},m=await d.Open.file(f);for(const t of m.files){const e=t.path;if(e.startsWith("collections/")||"manifest.json"===e||"schema.json"===e||"relations.json"===e||"views.json"===e||"fields-meta.json"===e||"presets.json"===e||e.startsWith("files/")&&e.endsWith(".meta.json")){const r=await t.buffer();e.startsWith("collections/")?g[e]=r:y[e]=r}}t.logger.info(`[acuity-backup] ZIP extracted: ${Object.keys(g).length} collection files, ${Object.keys(y).length} meta files`),i&&Kl(i,{phase:"Extraction complete",progressPercentage:20});const b=y["manifest.json"];if(!b)throw new Error("Invalid backup archive: manifest.json not found");const _=JSON.parse(b.toString("utf-8"));let v={},w=[],S=null;const E=y["schema.json"];if(E)try{v=JSON.parse(E.toString("utf-8"))}catch(e){t.logger.warn("[acuity-backup] Failed to parse schema.json:",e)}const k=y["relations.json"];if(k)try{w=JSON.parse(k.toString("utf-8"))}catch(e){t.logger.warn("[acuity-backup] Failed to parse relations.json:",e)}const x=y["views.json"];if(x)try{S=JSON.parse(x.toString("utf-8"))}catch{}let O=[];const T=y["fields-meta.json"];if(T)try{O=JSON.parse(T.toString("utf-8"))}catch(e){t.logger.warn("[acuity-backup] Failed to parse fields-meta.json:",e)}let R=[];const I=y["presets.json"];if(I)try{R=JSON.parse(I.toString("utf-8"))}catch(e){t.logger.warn("[acuity-backup] Failed to parse presets.json:",e)}const j=new Map;for(const t of w){const e=t;"directus_files"===e.related_collection&&e.collection&&e.field&&(j.has(e.collection)||j.set(e.collection,new Set),j.get(e.collection).add(e.field))}for(const t of O){const e=t.special;if(e&&Array.isArray(e)&&e.includes("file")){const e=t.collection,r=t.field;j.has(e)||j.set(e,new Set),j.get(e).add(r)}}const L=new Map;for(const t of w){const e=t;e.collection&&e.field&&(L.has(e.collection)||L.set(e.collection,new Set),L.get(e.collection).add(e.field))}for(const t of O){const e=t.special;if(e&&Array.isArray(e)&&(e.includes("m2o")||e.includes("file")||e.includes("user-created")||e.includes("user-updated"))){const e=t.collection,r=t.field;L.has(e)||L.set(e,new Set),L.get(e).add(r)}}const A=c??_.collections,M=[];let D=0,P=0,N=0;await async function(t,e,r,n,i,s){const o=await t.getSchema(),{CollectionsService:a,RelationsService:u}=t.services,c=[];let l=0;const h=e?.collections??{},f=new Map;for(const t of s){const e=`${t.collection}.${t.field}`;f.set(e,t)}const d=new a({schema:o,accountability:null});if(i)for(const e of i.collectionViews){const r=e.collection;if(h[r])continue;if(!n.includes(r))continue;const i=e.meta??{},{id:s,collection:o,...a}=i;a.icon=e.icon??a.icon??null,a.note=e.note??a.note??null,a.sort=e.sort??a.sort??null;try{await d.readOne(r);try{await d.updateOne(r,{meta:a}),t.logger.info(`[acuity-backup] Updated virtual folder "${r}" meta`)}catch{}continue}catch{}try{await d.createOne({collection:r,schema:null,meta:a}),c.push(r),t.logger.info(`[acuity-backup] Created virtual folder collection "${r}"`)}catch(e){t.logger.warn(`[acuity-backup] Failed to create virtual folder "${r}":`,e)}}const p=n.filter(t=>!o.collections[t]&&!c.includes(t));if(0===p.length&&0===c.length&&0===r.length)return{createdCollections:c,createdRelations:l};p.length>0&&t.logger.info(`[acuity-backup] Need to create ${p.length} missing collection(s): ${p.join(", ")}`);for(const e of p){const r=h[e];if(!r){t.logger.warn(`[acuity-backup] Collection "${e}" not found in backup schema — cannot recreate`);continue}const n=[],s=r.fields??{};for(const[t,i]of Object.entries(s)){const s=t===r.primary,o=`${e}.${t}`,a=f.get(o);if(i.alias){const e={field:i.field??t,type:"alias",schema:null,meta:a?{interface:a.interface,special:a.special??i.special??["alias","no-data"],options:a.options??null,display:a.display??null,display_options:a.display_options??null,width:a.width??"full",sort:a.sort??null,note:a.note??null,group:a.group??null,hidden:a.hidden??!1,required:a.required??!1}:{special:i.special??["alias","no-data"]}};n.push(e);continue}const u={special:i.special??[],hidden:!!s||(a?.hidden??!1),readonly:!!s||(a?.readonly??!1)};a&&(u.interface=a.interface,u.display=a.display??null,u.display_options=a.display_options??null,u.options=a.options??null,u.width=a.width??"full",u.sort=a.sort??null,u.note=a.note??null,u.required=a.required??!1,u.group=a.group??null,a.special&&(u.special=a.special));const c={field:i.field??t,type:i.type,schema:{is_nullable:!s&&!1!==i.nullable,default_value:i.defaultValue??null,is_primary_key:s,has_auto_increment:s&&("integer"===i.type||!0===i.generated)},meta:u};i.maxLength&&(c.schema.max_length=i.maxLength),n.push(c)}let o={};if(i){const t=i.collectionViews.find(t=>t.collection===e);if(t?.meta){const{collection:e,id:r,...n}=t.meta;o=n}}try{await d.createOne({collection:e,schema:{name:e},meta:{...o,singleton:r.singleton??!1,sort_field:r.sortField??null},fields:n}),c.push(e),t.logger.info(`[acuity-backup] Created collection "${e}" with ${n.length} fields`)}catch(r){t.logger.error(`[acuity-backup] Failed to create collection "${e}":`,r)}}if(r.length>0&&c.length>0){const e=await t.getSchema(),n=new u({schema:e,accountability:null});for(const i of r){const r=i;if((!r.collection?.startsWith("directus_")||!r.related_collection?.startsWith("directus_"))&&(c.includes(r.collection)||null!=r.related_collection&&c.includes(r.related_collection)))if(!e.collections[r.collection]||r.related_collection&&!e.collections[r.related_collection])t.logger.warn(`[acuity-backup] Skipping relation ${r.collection}.${r.field}: one or both collections missing`);else try{const e={...r.meta??{}};delete e.id,await n.createOne({collection:r.collection,field:r.field,related_collection:r.related_collection,meta:e,schema:r.schema??null}),l++,t.logger.info(`[acuity-backup] Created relation: ${r.collection}.${r.field} → ${r.related_collection}`)}catch(e){t.logger.warn(`[acuity-backup] Failed to create relation ${r.collection}.${r.field}:`,e)}}}return{createdCollections:c,createdRelations:l}}(t,v,w,A,S,O);let C=await t.getSchema(),B=0;if(O.length>0){i&&Kl(i,{phase:"Restoring field metadata",progressPercentage:22});const{FieldsService:e}=t.services,r=new e({schema:C,accountability:null}),n=new Map;for(const t of O){const e=`${t.collection}.${t.field}`;n.set(e,t)}for(const e of O){const n=e.collection,i=e.field;if(!A.includes(n))continue;const{id:s,collection:o,field:a,...u}=e;try{await r.updateField(n,i,{meta:u}),B++}catch{const s=e.special;if(s&&Array.isArray(s)&&s.includes("alias"))try{await r.createField(n,{field:i,type:"alias",schema:null,meta:u}),B++}catch(e){t.logger.warn(`[acuity-backup] Failed to create alias field "${n}.${i}":`,e)}}}t.logger.info(`[acuity-backup] Restored metadata for ${B} fields`),C=await t.getSchema()}i&&Kl(i,{phase:"Restoring media files",progressPercentage:25});let F=0;const z={};if(_.includeMedia){const e=new p("directus_files",{schema:C,accountability:null});for(const[r,n]of Object.entries(y))if(r.startsWith("files/")&&r.endsWith(".meta.json"))try{const r=JSON.parse(n.toString("utf-8")),s=r.id;try{let t=null;try{t=await e.readOne(s)}catch{}if(t){const{id:t,...n}=r;await e.updateOne(s,n),z[s]=s}else{const t=await e.createOne(r);z[s]=String(t)}F++}catch(e){t.logger.warn(`[acuity-backup] Failed to restore file record "${s}":`,e),i&&Jl(i,{message:`Failed to restore file: ${s}`})}}catch{}}t.logger.info(`[acuity-backup] Restored ${F} media file records`),i&&Kl(i,{phase:"Media files restored",progressPercentage:35});for(let e=0;e<A.length;e++){const r=A[e],n=`collections/${r}.json`,s=g[n];if(!s){t.logger.warn(`[acuity-backup] Collection file not found in archive: ${n}`),P++,i&&Jl(i,{collection:r,message:"Collection file not found in archive"});continue}let o;try{o=JSON.parse(s.toString("utf-8"))}catch{t.logger.warn(`[acuity-backup] Failed to parse collection JSON: ${n}`),P++,i&&Jl(i,{collection:r,message:"Failed to parse collection JSON"});continue}if(!Array.isArray(o)||0===o.length){if(M.push(r),D++,i){const t=35+30*(e+1)/A.length;Kl(i,{phase:`Restoring collections (${e+1}/${A.length})`,progressPercentage:Math.round(t),progressCurrent:e+1,progressTotal:A.length})}continue}const a=L.get(r);if(a)for(const t of o)for(const e of a){const r=t[e];if(null!==r&&"object"==typeof r&&!Array.isArray(r)){const n=r;void 0!==n.id&&(t[e]=n.id)}}const c=j.get(r);if(c&&Object.keys(z).length>0)for(const t of o)for(const e of c){const r=t[e];"string"==typeof r&&z[r]&&z[r]!==r&&(t[e]=z[r])}const l=new p(r,{schema:C,accountability:null});try{if(u){const t=await l.readByQuery({limit:-1,fields:["id"]});if(t.length>0){const e=t.map(t=>t.id);await l.deleteMany(e)}}if(await l.upsertMany(o),M.push(r),D++,N+=o.length,i){const t=35+30*(e+1)/A.length;Kl(i,{phase:`Restoring collections (${e+1}/${A.length})`,progressPercentage:Math.round(t),progressCurrent:e+1,progressTotal:A.length})}}catch(e){t.logger.error(`[acuity-backup] Failed to restore collection "${r}":`,e),P++,i&&Jl(i,{collection:r,message:String(e)})}}let U=0,W=0;if(i&&Kl(i,{phase:"Restoring field groups and view settings",progressPercentage:75}),S&&!1!==_.includesViewsAndGroups){const{FieldsService:e,CollectionsService:r}=t.services,n=await t.getSchema();if(S.fieldGroups.length>0){const r=new e({schema:n,accountability:null});for(const e of S.fieldGroups)if(A.includes(e.collection))try{await r.updateField(e.collection,e.field,{group:e.group}),U++}catch(r){t.logger.warn(`[acuity-backup] Failed to restore group for field "${e.collection}.${e.field}":`,r),i&&Jl(i,{message:`Failed to restore field group for ${e.collection}.${e.field}`})}}if(S.collectionViews.length>0){const e=new r({schema:n,accountability:null});for(const r of S.collectionViews){if(!A.includes(r.collection))continue;const n=r.meta??{},{id:s,collection:o,...a}=n,u={...a,display:r.display,display_template:r.display_template,sort_field:r.sort_field,hidden:r.hidden,sort:r.sort,icon:r.icon,note:r.note};try{await e.updateOne(r.collection,{meta:u}),W++}catch(e){t.logger.warn(`[acuity-backup] Failed to restore view settings for collection "${r.collection}":`,e),i&&Jl(i,{collection:r.collection,message:"Failed to restore view settings"})}}}}let G=0;if(i&&Kl(i,{phase:"Restoring collection layout presets",progressPercentage:85}),R.length>0){const{ItemsService:e}=t.services,r=new e("directus_presets",{schema:await t.getSchema(),accountability:null}),n=R.filter(t=>t.collection&&A.includes(t.collection));for(const e of n)try{const{id:t,user:n,...i}=e,s=await r.readByQuery({limit:1,filter:{collection:{_eq:i.collection},user:{_null:!0},...i.bookmark?{bookmark:{_eq:i.bookmark}}:{bookmark:{_null:!0}},...i.role?{role:{_eq:i.role}}:{role:{_null:!0}}},fields:["id"]});s.length>0?await r.updateOne(s[0].id,i):await r.createOne(i),G++}catch(r){t.logger.warn(`[acuity-backup] Failed to restore preset for collection "${e.collection}":`,r),i&&Jl(i,{collection:e.collection,message:"Failed to restore layout preset"})}}const q={restoredCollections:M,restoredFiles:F,restoredFieldGroups:U,restoredCollectionViews:W,restoredPresets:G,errors:[]};if(i){const t=Xl(i);if(t){const e=function(t,e){const r=new Date(t.startTime).getTime(),n=Date.now()-r;return{id:t.sessionId,sessionId:t.sessionId,backupId:t.backupId,timestamp:t.startTime,duration:n,status:"completed"===t.status?0===t.errors.length?"success":"partial":"failed",summary:e,errors:t.errors}}(t,{collectionsAttempted:A.length,collectionsSuccess:D,collectionsFailed:P,itemsRestored:N,mediaFilesRestored:F,fieldGroupsRestored:U,collectionViewsRestored:W,presetsRestored:G});!function(t){rh();const e=l(eh(),`${t.sessionId}.json`);r(e,JSON.stringify(t,null,2),"utf-8")}(e),th(i,(t.errors.length,"completed"))}Kl(i,{phase:"Restore completed",progressPercentage:100})}return q}function yh(t,e,r){const n=t.accountability;n?.admin?r():e.status(403).json({errors:[{message:"Forbidden: admin access required",extensions:{code:"FORBIDDEN"}}]})}const mh=[],bh=[{name:"acuity-backup",config:(n,h)=>{lh(h),n.get("/collections",yh,async(t,e)=>{try{const t=await hh(h),{CollectionsService:r}=h.services,n=new r({schema:await h.getSchema(),accountability:null}),i=(await n.readByQuery()).filter(e=>t.includes(e.collection)).map(t=>({collection:t.collection,icon:t.meta?.icon??null,note:t.meta?.note??null,singleton:t.meta?.singleton??!1}));e.json({data:i})}catch(t){h.logger.error("[acuity-backup] GET /collections error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.get("/list",yh,async(t,e)=>{try{const t=await ph();e.json({data:t})}catch(t){h.logger.error("[acuity-backup] GET /list error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.post("/run",yh,async(t,e)=>{try{const r=t.body,n="selective"===r.type?"selective":"full",i=!1!==r.includeMedia,s="selective"===n?r.collections??[]:void 0;if("selective"===n&&(!s||0===s.length))return void e.status(400).json({errors:[{message:'Selective backup requires at least one collection in the "collections" array.'}]});const o=await dh(h,{type:n,collections:s,includeMedia:i});e.status(201).json({data:o})}catch(t){h.logger.error("[acuity-backup] POST /run error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.get("/schedule",yh,(t,e)=>{try{const t=uh();e.json({data:t})}catch(t){h.logger.error("[acuity-backup] GET /schedule error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.post("/schedule",yh,(t,e)=>{try{const r=t.body;if("boolean"!=typeof r.enabled)return void e.status(400).json({errors:[{message:'"enabled" must be a boolean'}]});if("string"!=typeof r.cronExpression||!r.cronExpression.trim())return void e.status(400).json({errors:[{message:'"cronExpression" must be a non-empty string'}]});if(5!==r.cronExpression.trim().split(/\s+/).length)return void e.status(400).json({errors:[{message:'"cronExpression" must be a valid 5-field cron expression (minute hour dom month dow)'}]});const n={...uh(),enabled:r.enabled,cronExpression:r.cronExpression.trim()};ch(n),e.json({data:n})}catch(t){h.logger.error("[acuity-backup] POST /schedule error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.get("/restore/:id",yh,async(t,e)=>{try{const{id:r}=t.params,n=(await ph()).find(t=>t.id===r);if(!n)return void e.status(404).json({errors:[{message:`Backup "${r}" not found`}]});e.json({data:n})}catch(t){h.logger.error("[acuity-backup] GET /restore/:id error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.post("/restore",yh,async(t,e)=>{try{const r=t.body;if(!r.backupId||"string"!=typeof r.backupId)return void e.status(400).json({errors:[{message:'"backupId" is required'}]});const n=E(),i={backupId:r.backupId,truncateCollections:r.truncateCollections??!1,collections:r.collections,sessionId:n};e.json({data:{sessionId:n,message:"Restore started. Poll /acuity-backup/status/:sessionId for progress."}}),(async()=>{try{await gh(h,i,n)}catch(t){h.logger.error("[acuity-backup] Background restore error:",t);Xl(n)&&(Jl(n,{message:String(t)}),th(n,"failed"))}})()}catch(t){h.logger.error("[acuity-backup] POST /restore error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.delete("/:id",yh,async(t,r)=>{try{const{id:n}=t.params,i=(await ph()).find(t=>t.id===n);if(!i)return void r.status(404).json({errors:[{message:`Backup "${n}" not found`}]});const s=l(ih(),i.filename),o=s.replace(/\.zip$/,".meta.json"),u=[];e(s)&&u.push(a(s)),e(o)&&u.push(a(o)),await Promise.all(u),r.json({data:{success:!0,id:n}})}catch(t){h.logger.error("[acuity-backup] DELETE /:id error:",t),r.status(500).json({errors:[{message:String(t)}]})}}),n.get("/status/:sessionId",yh,(t,e)=>{try{const{sessionId:r}=t.params,n=Xl(r);if(!n)return void e.status(404).json({errors:[{message:`Session "${r}" not found`}]});e.json({data:n})}catch(t){h.logger.error("[acuity-backup] GET /status/:sessionId error:",t),e.status(500).json({errors:[{message:String(t)}]})}}),n.get("/restore-logs",yh,async(e,r)=>{try{const e=await async function(){rh();const e=eh();try{const r=await o(e),n=[];for(const i of r){if(!i.endsWith(".json"))continue;const r=l(e,i);try{const e=t(r,"utf-8"),i=JSON.parse(e);n.push(i)}catch{}}return n.sort((t,e)=>new Date(e.timestamp).getTime()-new Date(t.timestamp).getTime()),n}catch{return[]}}();r.json({data:e})}catch(t){h.logger.error("[acuity-backup] GET /restore-logs error:",t),r.status(500).json({errors:[{message:String(t)}]})}}),n.get("/restore-logs/:logId",yh,(r,n)=>{try{const{logId:i}=r.params,s=function(r){rh();const n=l(eh(),`${r}.json`);if(!e(n))return null;try{const e=t(n,"utf-8");return JSON.parse(e)}catch{return null}}(i);if(!s)return void n.status(404).json({errors:[{message:`Restore log "${i}" not found`}]});n.json({data:s})}catch(t){h.logger.error("[acuity-backup] GET /restore-logs/:logId error:",t),n.status(500).json({errors:[{message:String(t)}]})}}),n.delete("/restore-logs/:logId",yh,async(t,r)=>{try{const{logId:n}=t.params;await async function(t){const r=l(eh(),`${t}.json`);e(r)&&await a(r)}(n),r.json({data:{success:!0,logId:n}})}catch(t){h.logger.error("[acuity-backup] DELETE /restore-logs/:logId error:",t),r.status(500).json({errors:[{message:String(t)}]})}}),n.get("/download/:id",yh,async(t,r)=>{try{const{id:n}=t.params,s=(await ph()).find(t=>t.id===n);if(!s)return void r.status(404).json({errors:[{message:`Backup "${n}" not found`}]});const o=l(ih(),s.filename);if(!e(o))return void r.status(404).json({errors:[{message:`Backup file "${s.filename}" not found on disk`}]});r.setHeader("Content-Type","application/zip"),r.setHeader("Content-Disposition",`attachment; filename="${s.filename}"`);i(o).pipe(r)}catch(t){h.logger.error("[acuity-backup] GET /download/:id error:",t),r.status(500).json({errors:[{message:String(t)}]})}}),n.post("/upload",yh,async(t,n)=>{try{const{fileId:o}=t.body;if(!o||"string"!=typeof o)return void n.status(400).json({errors:[{message:'"fileId" is required. Upload the ZIP via POST /files first.'}]});const{ItemsService:d}=h.services,p=new d("directus_files",{schema:await h.getSchema(),accountability:null});let g;try{g=await p.readOne(o)}catch{return void n.status(404).json({errors:[{message:`File "${o}" not found in Directus.`}]})}const y=process.env.STORAGE_LOCAL_ROOT||"./uploads",m=g.filename_disk,b=l(y,m);if(!e(b))return void n.status(400).json({errors:[{message:"Uploaded file not found on disk. Only local storage is currently supported for backup uploads."}]});const _=await u(b);if(_.size>524288e3)return void n.status(413).json({errors:[{message:`Upload too large (${Math.round(_.size/1024/1024)} MB). Maximum is 500 MB.`}]});sh();const v=ih(),w=E(),S=l(v,`upload-temp-${w}.zip`);await f(i(b),s(S));const k="unzipper";let x,O;try{x=await new Function("m","return import(m)")(k)}catch{return await a(S),void n.status(500).json({errors:[{message:'The "unzipper" package is required. Install it: npm install unzipper'}]})}try{const t=(await x.Open.file(S)).files.find(t=>"manifest.json"===t.path);if(!t)return await a(S),void n.status(400).json({errors:[{message:"Invalid backup: ZIP does not contain a manifest.json file."}]});const e=await t.buffer();O=JSON.parse(e.toString("utf-8"))}catch(t){return await a(S),void n.status(400).json({errors:[{message:"Invalid or corrupted ZIP file: "+String(t)}]})}const T=E(),R=O.timestamp||(new Date).toISOString(),I=`backup-${R.replace(/[:.]/g,"-").replace("T","_").slice(0,19)}-${T}.zip`,j=l(v,I);await c(S,j);const L={id:T,filename:I,timestamp:R,type:O.type??"full",collections:O.collections??[],includeMedia:O.includeMedia??!1,fileSize:_.size},A=j.replace(/\.zip$/,".meta.json");r(A,JSON.stringify(L,null,2),"utf-8");try{await p.deleteOne(o)}catch{}h.logger.info(`[acuity-backup] Uploaded backup: ${I} (${Math.round(_.size/1024)} KB)`),n.json({data:L})}catch(t){h.logger.error("[acuity-backup] POST /upload error:",t),n.status(500).json({errors:[{message:String(t)}]})}}),async function(){rh();const t=eh(),e=Date.now()-7776e6;let r=0;try{const n=await o(t);for(const i of n){if(!i.endsWith(".json"))continue;const n=l(t,i);try{(await u(n)).mtime.getTime()<e&&(await a(n),r++)}catch{}}}catch{}return r}().then(t=>{t>0&&h.logger.info(`[acuity-backup] Cleaned up ${t} restore logs older than 90 days`)})}}],_h=[];export{bh as endpoints,mh as hooks,_h as operations};
package/dist/app.js CHANGED
@@ -1 +1 @@
1
- import{defineModule as e,useApi as a,useStores as t}from"@directus/extensions-sdk";import{defineComponent as n,resolveComponent as l,createBlock as o,openBlock as r,withCtx as s,createElementBlock as d,Fragment as i,renderList as c,createVNode as u,ref as p,onMounted as v,resolveDirective as m,createElementVNode as g,createCommentVNode as f,withDirectives as b,toDisplayString as h,normalizeClass as y,createTextVNode as x,computed as k,watch as w,onBeforeUnmount as _,onUnmounted as z,withModifiers as C,vModelRadio as S,Transition as R,vModelCheckbox as D}from"vue";import{useRouter as B}from"vue-router";const V=[],T=[],E=[],F=[e({id:"acuity-backup",name:"Backup",icon:"cloud_download",routes:[{path:"",props:!0,component:()=>Promise.resolve().then(function(){return Rt})},{path:":page",props:!0,component:()=>Promise.resolve().then(function(){return Rt})}]})],j=[],I=[],U=[];var L=n({__name:"navigation",props:{current:{},pages:{}},setup:e=>(a,t)=>{const n=l("v-icon"),p=l("v-list-item-icon"),v=l("v-text-overflow"),m=l("v-list-item-content"),g=l("v-list-item"),f=l("v-list");return r(),o(f,{nav:""},{default:s(()=>[(r(!0),d(i,null,c(e.pages,a=>(r(),o(g,{key:a.route,clickable:"",active:e.current===a.route,to:"/acuity-backup"+(a.route?"/"+a.route:"")},{default:s(()=>[u(p,null,{default:s(()=>[u(n,{name:a.icon},null,8,["name"])]),_:2},1024),u(m,null,{default:s(()=>[u(v,{text:a.label},null,8,["text"])]),_:2},1024)]),_:2},1032,["active","to"]))),128))]),_:1})}});const A={class:"restore-logs-page"},P={class:"section"},$={class:"section-header"},M={key:0,class:"empty-state"},N={class:"table-wrapper"},O={class:"logs-table"},G={class:"col-date"},H={class:"date-primary"},W={class:"date-relative"},q={class:"col-backup"},K={class:"backup-id"},Y={class:"col-status"},J={class:"col-duration"},Q={class:"col-items"},X={class:"col-errors"},Z={key:0,class:"no-errors"},ee={key:1,class:"error-count"},ae={class:"col-actions"},te={class:"detail-header"},ne={class:"detail-section"},le={class:"detail-row"},oe={class:"detail-value mono"},re={class:"detail-row"},se={class:"detail-value mono"},de={class:"detail-row"},ie={class:"detail-value"},ce={class:"detail-row"},ue={class:"detail-row"},pe={class:"detail-value"},ve={class:"detail-section"},me={class:"summary-grid"},ge={class:"summary-item"},fe={class:"summary-value"},be={class:"summary-item"},he={class:"summary-value success"},ye={class:"summary-item"},xe={class:"summary-item"},ke={class:"summary-value"},we={class:"summary-item"},_e={class:"summary-value"},ze={class:"summary-item"},Ce={class:"summary-value"},Se={class:"summary-item"},Re={class:"summary-value"},De={key:0,class:"detail-section"},Be={class:"section-subtitle"},Ve={class:"errors-container"},Te={key:0,class:"error-collection"},Ee={class:"error-message"},Fe={class:"error-timestamp"},je={class:"dialog-title-row"},Ie={class:"dialog-title-icon-wrap danger-icon-wrap"};var Ue=n({__name:"restore-logs",setup(e){const t=a(),n=p([]),k=p(!1),w=p(!1),_=p(null),z=p(!1),C=p(!1),S=p(null);async function R(){k.value=!0;try{const e=await t.get("/acuity-backup/restore-logs");n.value=e.data.data}catch(e){console.error("Failed to load restore logs:",e)}finally{k.value=!1}}async function D(){if(S.value){C.value=!0;try{await t.delete(`/acuity-backup/restore-logs/${S.value.id}`),n.value=n.value.filter(e=>e.id!==S.value.id),z.value=!1,w.value=!1}catch(e){console.error("Failed to delete restore log:",e)}finally{C.value=!1}}}function B(e){return new Date(e).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit"})}function V(e){const a=new Date(e),t=new Date,n=Math.floor((t.getTime()-a.getTime())/1e3);if(n<60)return"just now";const l=Math.floor(n/60);if(l<60)return`${l}m ago`;const o=Math.floor(l/60);if(o<24)return`${o}h ago`;return`${Math.floor(o/24)}d ago`}function T(e){const a=Math.floor(e/1e3);if(a<60)return`${a}s`;return`${Math.floor(a/60)}m ${a%60}s`}function E(e){switch(e){case"success":return"Success";case"partial":return"Partial";case"failed":return"Failed";default:return e}}return v(()=>{R()}),(e,a)=>{const t=l("v-icon"),p=l("v-button"),v=l("v-card-title"),F=l("v-card-text"),j=l("v-card-actions"),I=l("v-card"),U=l("v-dialog"),L=l("v-notice"),Ue=m("tooltip");return r(),d(i,null,[g("div",A,[g("div",P,[g("div",$,[a[7]||(a[7]=g("h2",{class:"section-title"},"Restore History",-1)),b((r(),o(p,{small:"",secondary:"",icon:"",loading:k.value,onClick:R},{default:s(()=>[u(t,{name:"refresh"})]),_:1},8,["loading"])),[[Ue,"Refresh list"]])]),f(" Empty state "),k.value||0!==n.value.length?(r(),d(i,{key:1},[f(" Table "),g("div",N,[g("table",O,[a[10]||(a[10]=g("thead",null,[g("tr",null,[g("th",{class:"col-date"},"Date"),g("th",{class:"col-backup"},"Backup ID"),g("th",{class:"col-status"},"Status"),g("th",{class:"col-duration"},"Duration"),g("th",{class:"col-items"},"Items"),g("th",{class:"col-errors"},"Errors"),g("th",{class:"col-actions"},"Actions")])],-1)),g("tbody",null,[(r(!0),d(i,null,c(n.value,e=>{return r(),d("tr",{key:e.id,class:"log-row"},[g("td",G,[g("span",H,h((a=e.timestamp,new Date(a).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}))),1),g("span",W,h(V(e.timestamp)),1)]),g("td",q,[g("code",K,h(e.backupId.substring(0,8)),1)]),g("td",Y,[g("span",{class:y(["status-badge",`status-${e.status}`])},h(E(e.status)),3)]),g("td",J,h(T(e.duration)),1),g("td",Q,h(e.summary.itemsRestored),1),g("td",X,[0===e.errors.length?(r(),d("span",Z,"—")):(r(),d("span",ee,h(e.errors.length),1))]),g("td",ae,[b((r(),o(p,{small:"",icon:"",secondary:"",onClick:a=>function(e){_.value=e,w.value=!0}(e)},{default:s(()=>[u(t,{name:"info"})]),_:1},8,["onClick"])),[[Ue,"View details"]]),b((r(),o(p,{small:"",icon:"",secondary:"",danger:"",onClick:a=>function(e){S.value=e,z.value=!0}(e)},{default:s(()=>[u(t,{name:"delete"})]),_:1},8,["onClick"])),[[Ue,"Delete log"]])])]);var a}),128))])])])],2112)):(r(),d("div",M,[u(t,{name:"history",class:"empty-icon"}),a[8]||(a[8]=g("p",{class:"empty-title"},"No restore logs",-1)),a[9]||(a[9]=g("p",{class:"empty-subtitle"},"Restore operations will appear here.",-1))]))])]),f(" Detail Modal "),u(U,{modelValue:w.value,"onUpdate:modelValue":a[2]||(a[2]=e=>w.value=e),onEsc:a[3]||(a[3]=e=>w.value=!1)},{default:s(()=>[_.value?(r(),o(I,{key:0,class:"detail-modal"},{default:s(()=>[u(v,null,{default:s(()=>[g("div",te,[a[11]||(a[11]=g("span",null,"Restore Log",-1)),u(p,{icon:"",secondary:"",small:"",onClick:a[0]||(a[0]=e=>w.value=!1)},{default:s(()=>[u(t,{name:"close"})]),_:1})])]),_:1}),u(F,null,{default:s(()=>{return[g("div",ne,[g("div",le,[a[12]||(a[12]=g("span",{class:"detail-label"},"Session ID",-1)),g("code",oe,h(_.value.sessionId),1)]),g("div",re,[a[13]||(a[13]=g("span",{class:"detail-label"},"Backup ID",-1)),g("code",se,h(_.value.backupId),1)]),g("div",de,[a[14]||(a[14]=g("span",{class:"detail-label"},"Date",-1)),g("span",ie,h((e=_.value.timestamp,new Date(e).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}))),1)]),g("div",ce,[a[15]||(a[15]=g("span",{class:"detail-label"},"Status",-1)),g("span",{class:y(["status-badge",`status-${_.value.status}`])},h(E(_.value.status)),3)]),g("div",ue,[a[16]||(a[16]=g("span",{class:"detail-label"},"Duration",-1)),g("span",pe,h(T(_.value.duration)),1)])]),g("div",ve,[a[24]||(a[24]=g("h3",{class:"section-subtitle"},"Summary",-1)),g("div",me,[g("div",ge,[a[17]||(a[17]=g("span",{class:"summary-label"},"Collections Attempted",-1)),g("span",fe,h(_.value.summary.collectionsAttempted),1)]),g("div",be,[a[18]||(a[18]=g("span",{class:"summary-label"},"Collections Success",-1)),g("span",he,h(_.value.summary.collectionsSuccess),1)]),g("div",ye,[a[19]||(a[19]=g("span",{class:"summary-label"},"Collections Failed",-1)),g("span",{class:y(["summary-value",{error:_.value.summary.collectionsFailed>0}])},h(_.value.summary.collectionsFailed),3)]),g("div",xe,[a[20]||(a[20]=g("span",{class:"summary-label"},"Items Restored",-1)),g("span",ke,h(_.value.summary.itemsRestored),1)]),g("div",we,[a[21]||(a[21]=g("span",{class:"summary-label"},"Media Files",-1)),g("span",_e,h(_.value.summary.mediaFilesRestored),1)]),g("div",ze,[a[22]||(a[22]=g("span",{class:"summary-label"},"Field Groups",-1)),g("span",Ce,h(_.value.summary.fieldGroupsRestored),1)]),g("div",Se,[a[23]||(a[23]=g("span",{class:"summary-label"},"Collection Views",-1)),g("span",Re,h(_.value.summary.collectionViewsRestored),1)])])]),_.value.errors.length>0?(r(),d("div",De,[g("h3",Be,"Errors ("+h(_.value.errors.length)+")",1),g("div",Ve,[(r(!0),d(i,null,c(_.value.errors,(e,a)=>(r(),d("div",{key:a,class:"error-detail"},[e.collection?(r(),d("span",Te,h(e.collection),1)):f("v-if",!0),g("span",Ee,h(e.message),1),g("span",Fe,h(B(e.timestamp)),1)]))),128))])])):f("v-if",!0)];var e}),_:1}),u(j,null,{default:s(()=>[u(p,{secondary:"",onClick:a[1]||(a[1]=e=>w.value=!1)},{default:s(()=>[...a[25]||(a[25]=[x("Close",-1)])]),_:1}),u(p,{danger:"",onClick:D},{default:s(()=>[...a[26]||(a[26]=[x("Delete Log",-1)])]),_:1})]),_:1})]),_:1})):f("v-if",!0)]),_:1},8,["modelValue"]),f(" Delete confirmation "),u(U,{modelValue:z.value,"onUpdate:modelValue":a[5]||(a[5]=e=>z.value=e),onEsc:a[6]||(a[6]=e=>z.value=!1)},{default:s(()=>[u(I,{class:"confirm-dialog"},{default:s(()=>[u(v,null,{default:s(()=>[g("div",je,[g("div",Ie,[u(t,{name:"delete_forever"})]),a[27]||(a[27]=g("span",null,"Delete Restore Log",-1))])]),_:1}),u(F,null,{default:s(()=>[u(L,{type:"danger"},{default:s(()=>[...a[28]||(a[28]=[x(" This will permanently delete the restore log. This action cannot be undone. ",-1)])]),_:1})]),_:1}),u(j,null,{default:s(()=>[u(p,{secondary:"",onClick:a[4]||(a[4]=e=>z.value=!1)},{default:s(()=>[...a[29]||(a[29]=[x("Cancel",-1)])]),_:1}),u(p,{danger:"",onClick:D,loading:C.value},{default:s(()=>[u(t,{name:"delete_forever",left:""}),a[30]||(a[30]=x(" Delete Permanently ",-1))]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1},8,["modelValue"])],64)}}}),Le=[],Ae=[];function Pe(e,a){if(e&&"undefined"!=typeof document){var t,n=!0===a.prepend?"prepend":"append",l=!0===a.singleTag,o="string"==typeof a.container?document.querySelector(a.container):document.getElementsByTagName("head")[0];if(l){var r=Le.indexOf(o);-1===r&&(r=Le.push(o)-1,Ae[r]={}),t=Ae[r]&&Ae[r][n]?Ae[r][n]:Ae[r][n]=s()}else t=s();65279===e.charCodeAt(0)&&(e=e.substring(1)),t.styleSheet?t.styleSheet.cssText+=e:t.appendChild(document.createTextNode(e))}function s(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),a.attributes)for(var t=Object.keys(a.attributes),l=0;l<t.length;l++)e.setAttribute(t[l],a.attributes[t[l]]);var r="prepend"===n?"afterbegin":"beforeend";return o.insertAdjacentElement(r,e),e}}Pe("\n.restore-logs-page[data-v-4d4855df] {\n\tpadding: 16px;\n\tmax-width: 1100px;\n}\n.section[data-v-4d4855df] {\n\tbackground: var(--theme--background);\n\tborder: 1px solid var(--theme--border-color);\n\tborder-radius: var(--theme--border-radius);\n\tpadding: 16px;\n}\n.section-header[data-v-4d4855df] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 16px;\n}\n.section-title[data-v-4d4855df] {\n\tfont-size: 18px;\n\tfont-weight: 600;\n\tmargin: 0;\n\tcolor: var(--theme--foreground);\n}\n.empty-state[data-v-4d4855df] {\n\ttext-align: center;\n\tpadding: 40px 20px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.empty-icon[data-v-4d4855df] {\n\tfont-size: 48px;\n\tmargin-bottom: 12px;\n\topacity: 0.5;\n}\n.empty-title[data-v-4d4855df] {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tmargin: 0 0 4px 0;\n}\n.empty-subtitle[data-v-4d4855df] {\n\tfont-size: 14px;\n\tmargin: 0;\n\tcolor: var(--theme--foreground-subdued);\n}\n.table-wrapper[data-v-4d4855df] {\n\toverflow-x: auto;\n}\n.logs-table[data-v-4d4855df] {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 14px;\n}\n.logs-table thead[data-v-4d4855df] {\n\tbackground: var(--theme--background-subdued);\n\tborder-bottom: 2px solid var(--theme--border-color);\n}\n.logs-table th[data-v-4d4855df] {\n\tpadding: 12px 16px;\n\ttext-align: left;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground-subdued);\n\twhite-space: nowrap;\n}\n.logs-table tbody tr[data-v-4d4855df] {\n\tborder-bottom: 1px solid var(--theme--border-color);\n\ttransition: background 0.2s;\n}\n.logs-table tbody tr[data-v-4d4855df]:hover {\n\tbackground: var(--theme--background-subdued);\n}\n.logs-table td[data-v-4d4855df] {\n\tpadding: 12px 16px;\n\tvertical-align: middle;\n}\n.col-date[data-v-4d4855df],\n.col-backup[data-v-4d4855df],\n.col-status[data-v-4d4855df],\n.col-duration[data-v-4d4855df],\n.col-items[data-v-4d4855df],\n.col-errors[data-v-4d4855df],\n.col-actions[data-v-4d4855df] {\n\ttext-align: left;\n}\n.date-primary[data-v-4d4855df] {\n\tdisplay: block;\n\tfont-weight: 500;\n\tcolor: var(--theme--foreground);\n}\n.date-relative[data-v-4d4855df] {\n\tdisplay: block;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.backup-id[data-v-4d4855df] {\n\tfont-family: monospace;\n\tfont-size: 12px;\n\tbackground: var(--theme--background-subdued);\n\tpadding: 2px 6px;\n\tborder-radius: 3px;\n}\n.status-badge[data-v-4d4855df] {\n\tdisplay: inline-block;\n\tpadding: 4px 8px;\n\tborder-radius: 3px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n.status-success[data-v-4d4855df] {\n\tbackground: rgba(34, 197, 94, 0.1);\n\tcolor: rgb(34, 197, 94);\n}\n.status-partial[data-v-4d4855df] {\n\tbackground: rgba(251, 146, 60, 0.1);\n\tcolor: rgb(251, 146, 60);\n}\n.status-failed[data-v-4d4855df] {\n\tbackground: rgba(239, 68, 68, 0.1);\n\tcolor: rgb(239, 68, 68);\n}\n.no-errors[data-v-4d4855df] {\n\tcolor: var(--theme--foreground-subdued);\n}\n.error-count[data-v-4d4855df] {\n\tcolor: rgb(239, 68, 68);\n\tfont-weight: 600;\n}\n.col-actions[data-v-4d4855df] {\n\ttext-align: right;\n\twhite-space: nowrap;\n}\n.detail-modal[data-v-4d4855df] {\n\tmax-width: 600px;\n}\n.detail-header[data-v-4d4855df] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n}\n.detail-section[data-v-4d4855df] {\n\tmargin-bottom: 24px;\n}\n.detail-section[data-v-4d4855df]:last-child {\n\tmargin-bottom: 0;\n}\n.detail-row[data-v-4d4855df] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tmargin-bottom: 8px;\n\tpadding: 8px;\n\tbackground: var(--theme--background-subdued);\n\tborder-radius: 3px;\n}\n.detail-label[data-v-4d4855df] {\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground-subdued);\n\tmin-width: 120px;\n}\n.detail-value[data-v-4d4855df] {\n\tcolor: var(--theme--foreground);\n\tword-break: break-all;\n}\n.detail-value.mono[data-v-4d4855df] {\n\tfont-family: monospace;\n\tfont-size: 12px;\n}\n.section-subtitle[data-v-4d4855df] {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground-subdued);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.5px;\n\tmargin: 0 0 12px 0;\n\tpadding-bottom: 8px;\n\tborder-bottom: 1px solid var(--theme--border-color);\n}\n.summary-grid[data-v-4d4855df] {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n\tgap: 12px;\n}\n.summary-item[data-v-4d4855df] {\n\tbackground: var(--theme--background-subdued);\n\tpadding: 12px;\n\tborder-radius: 3px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n.summary-label[data-v-4d4855df] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.summary-value[data-v-4d4855df] {\n\tfont-size: 18px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n}\n.summary-value.success[data-v-4d4855df] {\n\tcolor: rgb(34, 197, 94);\n}\n.summary-value.error[data-v-4d4855df] {\n\tcolor: rgb(239, 68, 68);\n}\n.errors-container[data-v-4d4855df] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmax-height: 400px;\n\toverflow-y: auto;\n}\n.error-detail[data-v-4d4855df] {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding: 8px 12px;\n\tbackground: rgba(239, 68, 68, 0.05);\n\tborder-left: 3px solid rgb(239, 68, 68);\n\tborder-radius: 3px;\n\tfont-size: 12px;\n}\n.error-collection[data-v-4d4855df] {\n\tcolor: rgb(239, 68, 68);\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n.error-message[data-v-4d4855df] {\n\tflex: 1;\n\tcolor: var(--theme--foreground);\n\tword-break: break-word;\n}\n.error-timestamp[data-v-4d4855df] {\n\tcolor: var(--theme--foreground-subdued);\n\twhite-space: nowrap;\n\tfont-size: 11px;\n}\n.confirm-dialog[data-v-4d4855df] {\n\tmax-width: 400px;\n}\n.dialog-title-row[data-v-4d4855df] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n.dialog-title-icon-wrap[data-v-4d4855df] {\n\twidth: 40px;\n\theight: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tborder-radius: 50%;\n\tbackground: rgba(239, 68, 68, 0.1);\n}\n.dialog-title-icon-wrap.danger-icon-wrap[data-v-4d4855df] {\n\tbackground: rgba(239, 68, 68, 0.1);\n\tcolor: rgb(239, 68, 68);\n}\n",{});var $e=(e,a)=>{const t=e.__vccOpts||e;for(const[e,n]of a)t[e]=n;return t},Me=$e(Ue,[["__scopeId","data-v-4d4855df"]]);const Ne={key:0,class:"backup-module-content"},Oe={class:"section"},Ge={class:"section-header"},He={key:0,class:"skeleton-rows"},We={class:"empty-state"},qe={class:"table-wrapper"},Ke={class:"backup-table"},Ye={class:"col-date"},Je={class:"date-primary"},Qe={class:"date-relative"},Xe={class:"col-type"},Ze={class:"type-cell"},ea={key:0,class:"media-badge"},aa={class:"col-collections"},ta={key:0,class:"all-collections"},na={key:1,class:"collection-list"},la={key:0,class:"more-collections"},oa={class:"col-size"},ra={class:"col-status"},sa={key:0,class:"status-badge status-restoring"},da={key:1,class:"status-badge status-valid"},ia={class:"col-actions"},ca={class:"action-buttons"},ua={key:3,class:"auto-refresh-notice"},pa={class:"backup-module-content"},va={class:"section"},ma={class:"schedule-card"},ga={key:0,class:"schedule-loading"},fa={class:"schedule-toggle-row"},ba={class:"cron-presets"},ha=["onClick","disabled"],ya={class:"cron-input-row"},xa={key:0,class:"last-run-row"},ka={key:0,class:"error-notice"},wa={class:"schedule-footer"},_a={class:"dialog-section"},za={class:"radio-group"},Ca={class:"radio-content"},Sa={class:"radio-content"},Ra={key:0,class:"dialog-section collection-selector"},Da={key:0,class:"collections-loading"},Ba={key:1,class:"collections-empty"},Va={key:2,class:"collection-list-scroll"},Ta=["value"],Ea={class:"collection-item-content"},Fa={class:"collection-name"},ja={key:0,class:"collection-count"},Ia={key:3,class:"validation-msg"},Ua={class:"dialog-section"},La={class:"toggle-row"},Aa={key:0,class:"running-state"},Pa={key:1,class:"error-notice"},$a={class:"dialog-title-row"},Ma={class:"dialog-title-icon-wrap restore-icon-wrap"},Na={key:0,class:"confirm-details"},Oa={class:"detail-row"},Ga={class:"detail-value"},Ha={class:"detail-row"},Wa={class:"detail-value"},qa={class:"detail-row"},Ka={class:"detail-value"},Ya={class:"detail-row"},Ja={class:"detail-value"},Qa={class:"detail-row"},Xa={class:"detail-value"},Za={class:"toggle-row restore-option-row"},et={key:1,class:"running-state restore-progress"},at={class:"progress-info"},tt={class:"progress-header"},nt={class:"running-title"},lt={class:"progress-percentage"},ot={class:"progress-details"},rt={class:"phase-text"},st={class:"elapsed-time"},dt={key:0,class:"errors-section"},it={class:"errors-details"},ct={class:"errors-summary"},ut={class:"errors-list"},pt={key:0,class:"error-collection"},vt={class:"error-message"},mt={key:2,class:"error-notice"},gt={class:"dialog-title-row"},ft={class:"dialog-title-icon-wrap danger-icon-wrap"},bt={key:0,class:"confirm-details"},ht={class:"detail-row"},yt={class:"detail-value"},xt={class:"detail-row"},kt={class:"detail-value"},wt={class:"detail-row"},_t={class:"detail-value"},zt={key:1,class:"error-notice"};var Ct=n({__name:"module",props:{page:{}},setup(e){const n=e,V=B(),T=k(()=>n.page??""),E=[{route:"",label:"Backups",icon:"backup"},{route:"schedule",label:"Schedule",icon:"schedule"},{route:"restore-logs",label:"Restore Logs",icon:"history"}],F=k(()=>{switch(T.value){case"schedule":return"Schedule";case"restore-logs":return"Restore Logs";default:return"Backups"}}),j=k(()=>[{name:"Backup",to:"/acuity-backup"},{name:F.value}]);w(T,e=>{""===e?G():"schedule"===e?Fe():"restore-logs"===e||V.replace("/acuity-backup")},{immediate:!1});const I=a(),{useNotificationsStore:U}=t(),A=U(),P=p([]),$=p(!1),M=p(null),N=k(()=>M.value?M.value.toLocaleTimeString():"Never");let O=null;async function G(){$.value=!0;try{const e=await I.get("/acuity-backup/list");P.value=e.data.data??[],M.value=new Date}catch(e){Pe("Failed to load backup history. "+Ae(e),"error")}finally{$.value=!1}}const H=p(!1),W=p("full"),q=p(!0),K=p(!1),Y=p(""),J=p([]),Q=p([]),X=p(!1);function Z(){Y.value="",W.value="full",q.value=!0,J.value=[],H.value=!0,async function(){if(Q.value.length>0)return;X.value=!0;try{const e=await I.get("/acuity-backup/collections");Q.value=e.data.data??[]}catch(e){Pe("Failed to load collections. "+Ae(e),"error")}finally{X.value=!1}}()}function ee(){K.value||(H.value=!1)}function ae(){J.value=Q.value.map(e=>e.collection)}function te(){J.value=[]}async function ne(){if("selective"!==W.value||0!==J.value.length){K.value=!0,Y.value="";try{await I.post("/acuity-backup/run",{type:W.value,collections:"selective"===W.value?J.value:void 0,includeMedia:q.value}),Pe("Backup completed successfully.","success"),H.value=!1,await G()}catch(e){Y.value="Backup failed: "+Ae(e)}finally{K.value=!1}}}const le=p(null),oe=p(!1);function re(){le.value?.click()}async function se(e){const a=e.target,t=a.files?.[0];if(a.value="",t)if(t.name.endsWith(".zip")){oe.value=!0;try{await I.post("/acuity-backup/upload",t,{headers:{"Content-Type":"application/zip"}}),Pe("Backup uploaded successfully.","success"),await G()}catch(e){Pe("Upload failed: "+Ae(e),"error")}finally{oe.value=!1}}else Pe("Please select a .zip backup file.","warning")}const de=p(!1),ie=p(null),ce=p(null),ue=p(!1),pe=p(""),ve=p(null),me=p({phase:"",progressPercentage:0,elapsedSeconds:0,errors:[]}),ge=p(!1);let fe=null;function be(){null===ce.value&&(de.value=!1)}function he(){de.value=!1,ce.value=null,ve.value=null,ge.value=!1,G()}async function ye(){if(ie.value){ce.value=ie.value.id,pe.value="",me.value={phase:"",progressPercentage:0,elapsedSeconds:0,errors:[]};try{const e=(await I.post("/acuity-backup/restore",{backupId:ie.value.id,truncateCollections:ue.value})).data.data.sessionId;ve.value=e;let a="";fe=window.setInterval(async()=>{try{const t=(await I.get(`/acuity-backup/status/${e}`)).data.data;me.value={phase:t.phase||"",progressPercentage:t.progressPercentage||0,elapsedSeconds:t.elapsedSeconds||0,errors:t.errors||[]},"running"!==t.status&&(fe&&(clearInterval(fe),fe=null),ge.value=!0,"completed"===t.status?Pe("Restore completed successfully.","success"):pe.value="Restore failed. Check errors above for details."),a=t.status}catch(e){"running"!==a&&fe&&(clearInterval(fe),fe=null)}},250)}catch(e){pe.value="Failed to initiate restore: "+Ae(e),ce.value=null,ve.value=null}}}const xe=p(!1),ke=p(null),we=p(null),_e=p("");async function ze(){if(ke.value){we.value=ke.value.id,_e.value="";try{await I.delete(`/acuity-backup/${ke.value.id}`),Pe("Backup deleted.","success"),xe.value=!1,P.value=P.value.filter(e=>e.id!==ke.value.id)}catch(e){_e.value="Delete failed: "+Ae(e)}finally{we.value=null}}}const Ce=[{label:"Daily",value:"0 0 * * *"},{label:"Weekly",value:"0 0 * * 0"},{label:"Monthly",value:"0 0 1 * *"},{label:"Disabled",value:""}],Se=p(!1),Re=p("0 0 * * *"),De=p(void 0),Be=p(!1),Ve=p(!1),Te=p("");let Ee=!1;async function Fe(){if(!Ee){Be.value=!0;try{const e=(await I.get("/acuity-backup/schedule")).data.data;Se.value=e.enabled,Re.value=e.cronExpression,De.value=e.lastRun,Ee=!0}catch(e){Pe("Failed to load schedule configuration. "+Ae(e),"error")}finally{Be.value=!1}}}async function je(){if(Te.value="",Se.value){const e=Re.value.trim();if(!e)return void(Te.value="A cron expression is required when automatic backups are enabled.");if(5!==e.split(/\s+/).length)return void(Te.value="The cron expression must have exactly 5 fields (minute hour day-of-month month day-of-week).")}Ve.value=!0;try{await I.post("/acuity-backup/schedule",{enabled:Se.value,cronExpression:Re.value.trim()||"0 0 * * *"}),Pe("Schedule saved successfully.","success")}catch(e){Te.value="Failed to save schedule: "+Ae(e)}finally{Ve.value=!1}}function Ie(e){try{return new Intl.DateTimeFormat(void 0,{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}).format(new Date(e))}catch{return e}}function Ue(e){try{const a=Date.now()-new Date(e).getTime(),t=Math.floor(a/1e3);return t<60?"just now":t<3600?`${Math.floor(t/60)}m ago`:t<86400?`${Math.floor(t/3600)}h ago`:t<604800?`${Math.floor(t/86400)}d ago`:`${Math.floor(t/604800)}w ago`}catch{return""}}function Le(e){if(!e||0===e)return"—";const a=["B","KB","MB","GB"];let t=e,n=0;for(;t>=1024&&n<a.length-1;)t/=1024,n++;return`${t.toFixed(t<10?1:0)} ${a[n]}`}function Ae(e){if(e&&"object"==typeof e){const a=e,t=a.response?.data?.errors?.[0]?.message;if(t)return t;if(a.message)return a.message}return String(e)}function Pe(e,a="success"){A.add({title:"success"===a?"Success":"error"===a?"Error":"Warning",text:e,type:a,persist:"error"===a})}return v(()=>{"schedule"===T.value?Fe():(G(),O=setInterval(G,3e4))}),_(()=>{null!==fe&&(clearInterval(fe),fe=null)}),z(()=>{null!==O&&(clearInterval(O),O=null)}),(e,a)=>{const t=l("v-breadcrumb"),n=l("v-icon"),p=l("v-button"),v=l("v-progress-circular"),k=l("v-switch"),w=l("v-input"),_=l("v-notice"),z=l("v-card-title"),B=l("v-card-text"),V=l("v-card-actions"),U=l("v-card"),A=l("v-dialog"),M=l("v-progress-linear"),O=l("private-view"),ve=m("tooltip");return r(),o(O,{title:F.value},{headline:s(()=>[u(t,{items:j.value},null,8,["items"])]),"title-outer:prepend":s(()=>[u(p,{class:"header-icon",rounded:"",icon:"",secondary:"",disabled:""},{default:s(()=>[u(n,{name:"cloud_download"})]),_:1})]),actions:s(()=>[""===T.value?(r(),o(p,{key:0,secondary:"",onClick:re,loading:oe.value},{default:s(()=>[u(n,{name:"upload_file",left:""}),a[12]||(a[12]=x(" Upload Backup ",-1))]),_:1},8,["loading"])):f("v-if",!0),""===T.value?(r(),o(p,{key:1,onClick:Z,loading:K.value},{default:s(()=>[u(n,{name:"add",left:""}),a[13]||(a[13]=x(" Run Backup ",-1))]),_:1},8,["loading"])):f("v-if",!0)]),navigation:s(()=>[u(L,{current:T.value,pages:E},null,8,["current"])]),default:s(()=>[""===T.value?(r(),d("div",Ne,[f(" Backup history table "),g("div",Oe,[g("div",Ge,[a[14]||(a[14]=g("h2",{class:"section-title"},"Backup History",-1)),b((r(),o(p,{small:"",secondary:"",icon:"",loading:$.value,onClick:G},{default:s(()=>[u(n,{name:"refresh"})]),_:1},8,["loading"])),[[ve,"Refresh list"]])]),f(" Loading skeleton "),$.value&&0===P.value.length?(r(),d("div",He,[(r(),d(i,null,c(3,e=>g("div",{key:e,class:"skeleton-row"},[...a[15]||(a[15]=[g("div",{class:"skeleton-cell skeleton-date"},null,-1),g("div",{class:"skeleton-cell skeleton-type"},null,-1),g("div",{class:"skeleton-cell skeleton-collections"},null,-1),g("div",{class:"skeleton-cell skeleton-size"},null,-1),g("div",{class:"skeleton-cell skeleton-status"},null,-1),g("div",{class:"skeleton-cell skeleton-actions"},null,-1)])])),64))])):$.value||0!==P.value.length?(r(),d(i,{key:2},[f(" Table "),g("div",qe,[g("table",Ke,[a[21]||(a[21]=g("thead",null,[g("tr",null,[g("th",{class:"col-date"},"Date"),g("th",{class:"col-type"},"Type"),g("th",{class:"col-collections"},"Collections"),g("th",{class:"col-size"},"Size"),g("th",{class:"col-status"},"Status"),g("th",{class:"col-actions"},"Actions")])],-1)),g("tbody",null,[(r(!0),d(i,null,c(P.value,e=>(r(),d("tr",{key:e.id,class:"backup-row"},[f(" Date "),g("td",Ye,[g("span",Je,h(Ie(e.timestamp)),1),g("span",Qe,h(Ue(e.timestamp)),1)]),f(" Type badge "),g("td",Xe,[g("div",Ze,[g("span",{class:y(["type-badge","full"===e.type?"type-full":"type-selective"])},h("full"===e.type?"Full":"Selective"),3),e.includeMedia?b((r(),d("span",ea,[u(n,{name:"image",small:""})])),[[ve,"Includes media files"]]):f("v-if",!0)])]),f(" Collections "),g("td",aa,["full"===e.type?(r(),d("span",ta,"All collections")):(r(),d("span",na,[x(h(e.collections.slice(0,3).join(", "))+" ",1),e.collections.length>3?(r(),d("span",la," +"+h(e.collections.length-3)+" more ",1)):f("v-if",!0)]))]),f(" Size "),g("td",oa,h(Le(e.fileSize)),1),f(" Status "),g("td",ra,[ce.value===e.id?(r(),d("span",sa,[u(v,{"x-small":"",indeterminate:"",class:"status-spinner"}),a[19]||(a[19]=x(" Restoring… ",-1))])):(r(),d("span",da,[u(n,{name:"check_circle","x-small":"",class:"status-icon"}),a[20]||(a[20]=x(" Ready ",-1))]))]),f(" Actions "),g("td",ia,[g("div",ca,[f(" Download "),b((r(),o(p,{"x-small":"",secondary:"",icon:"",onClick:C(a=>async function(e){try{const a=await I.get(`/acuity-backup/download/${e.id}`,{responseType:"blob"}),t=new Blob([a.data],{type:"application/zip"}),n=URL.createObjectURL(t),l=document.createElement("a");l.href=n,l.download=e.filename,l.click(),URL.revokeObjectURL(n)}catch{const a=document.createElement("a");a.href=function(e){return`/acuity-backup/download/${e.id}`}(e),a.download=e.filename,a.click()}}(e),["prevent"])},{default:s(()=>[u(n,{name:"download"})]),_:1},8,["onClick"])),[[ve,"Download backup archive"]]),f(" Restore — uses warning styling to signal impact "),b((r(),o(p,{"x-small":"",icon:"",class:"btn-restore",loading:ce.value===e.id,disabled:null!==ce.value&&ce.value!==e.id,onClick:a=>function(e){ie.value=e,ue.value=!1,pe.value="",ge.value=!1,de.value=!0}(e)},{default:s(()=>[u(n,{name:"settings_backup_restore"})]),_:1},8,["loading","disabled","onClick"])),[[ve,"Restore data from this backup"]]),f(" Delete "),b((r(),o(p,{"x-small":"",secondary:"",icon:"",class:"btn-danger",loading:we.value===e.id,disabled:null!==we.value&&we.value!==e.id,onClick:a=>function(e){ke.value=e,_e.value="",xe.value=!0}(e)},{default:s(()=>[u(n,{name:"delete_outline"})]),_:1},8,["loading","disabled","onClick"])),[[ve,"Permanently delete this backup"]])])])]))),128))])])])],2112)):(r(),d(i,{key:1},[f(" Empty state "),g("div",We,[u(n,{name:"cloud_off",class:"empty-icon"}),a[17]||(a[17]=g("p",{class:"empty-title"},"No backups yet",-1)),a[18]||(a[18]=g("p",{class:"empty-subtitle"},'Create your first backup by clicking "Run Backup" above.',-1)),u(p,{class:"empty-cta",onClick:Z},{default:s(()=>[u(n,{name:"add",left:""}),a[16]||(a[16]=x(" Run Backup ",-1))]),_:1})])],2112)),P.value.length>0?(r(),d("p",ua,[u(n,{name:"autorenew","x-small":"",class:"refresh-icon"}),x(" Auto-refreshes every 30 s — last updated: "+h(N.value),1)])):f("v-if",!0)])])):"schedule"===T.value?(r(),d(i,{key:1},[f(' ================================================================\n\t\t PAGE: SCHEDULE (route "schedule")\n\t\t ================================================================ '),g("div",pa,[g("div",va,[a[28]||(a[28]=g("div",{class:"section-header"},[g("h2",{class:"section-title"},"Automatic Backups")],-1)),g("div",ma,[Be.value?(r(),d("div",ga,[u(v,{indeterminate:""}),a[22]||(a[22]=g("span",null,"Loading schedule…",-1))])):(r(),d(i,{key:1},[g("div",fa,[a[23]||(a[23]=g("div",{class:"toggle-info"},[g("label",{class:"toggle-label",for:"schedule-enabled"},"Enable Automatic Backups"),g("span",{class:"toggle-desc"},"Runs a full backup on the schedule defined below")],-1)),u(k,{id:"schedule-enabled",modelValue:Se.value,"onUpdate:modelValue":a[0]||(a[0]=e=>Se.value=e)},null,8,["modelValue"])]),g("div",{class:y(["schedule-body",!Se.value&&"schedule-body--disabled"])},[g("div",ba,[a[24]||(a[24]=g("span",{class:"preset-label"},"Presets:",-1)),(r(),d(i,null,c(Ce,e=>g("button",{key:e.label,class:y(["preset-btn",Re.value===e.value&&"preset-btn--active"]),onClick:a=>function(e){"Disabled"===e.label?Se.value=!1:(Re.value=e.value,Se.value=!0)}(e),disabled:!Se.value},h(e.label),11,ha)),64))]),g("div",ya,[a[25]||(a[25]=g("label",{class:"field-label",for:"cron-expression"},"Cron Expression",-1)),u(w,{id:"cron-expression",modelValue:Re.value,"onUpdate:modelValue":a[1]||(a[1]=e=>Re.value=e),placeholder:"0 0 * * *",disabled:!Se.value,"font-mono":"",class:"cron-input"},null,8,["modelValue","disabled"]),a[26]||(a[26]=g("p",{class:"field-hint"},[x(" Five fields: minute hour day-of-month month day-of-week. "),g("a",{href:"https://crontab.guru/",target:"_blank",rel:"noopener noreferrer"},"crontab.guru")],-1))]),De.value?(r(),d("div",xa,[u(n,{name:"history",small:""}),g("span",null,"Last run: "+h(Ie(De.value))+" ("+h(Ue(De.value))+")",1)])):f("v-if",!0)],2),Te.value?(r(),d("div",ka,[u(_,{type:"danger"},{default:s(()=>[x(h(Te.value),1)]),_:1})])):f("v-if",!0),g("div",wa,[u(p,{onClick:je,loading:Ve.value},{default:s(()=>[u(n,{name:"save",left:""}),a[27]||(a[27]=x(" Save Schedule ",-1))]),_:1},8,["loading"])])],64))])])])],2112)):"restore-logs"===T.value?(r(),d(i,{key:2},[f(' ================================================================\n\t\t PAGE: RESTORE LOGS (route "restore-logs")\n\t\t ================================================================ '),u(Me)],2112)):f("v-if",!0),u(A,{modelValue:H.value,"onUpdate:modelValue":a[6]||(a[6]=e=>H.value=e),onEsc:ee},{default:s(()=>[u(U,{class:"backup-dialog"},{default:s(()=>[u(z,null,{default:s(()=>[u(n,{name:"add_circle",class:"dialog-title-icon"}),a[29]||(a[29]=x(" New Backup ",-1))]),_:1}),u(B,null,{default:s(()=>[f(" Backup type selection "),g("div",_a,[a[32]||(a[32]=g("label",{class:"field-label"},"Backup Type",-1)),g("div",za,[g("label",{class:y(["radio-option",{"radio-option--active":"full"===W.value}])},[b(g("input",{type:"radio","onUpdate:modelValue":a[2]||(a[2]=e=>W.value=e),value:"full"},null,512),[[S,W.value]]),g("div",Ca,[u(n,{name:"cloud_download"}),a[30]||(a[30]=g("div",null,[g("span",{class:"radio-title"},"Full Backup"),g("span",{class:"radio-desc"},"Back up all collections and optionally all media files")],-1))])],2),g("label",{class:y(["radio-option",{"radio-option--active":"selective"===W.value}])},[b(g("input",{type:"radio","onUpdate:modelValue":a[3]||(a[3]=e=>W.value=e),value:"selective"},null,512),[[S,W.value]]),g("div",Sa,[u(n,{name:"checklist"}),a[31]||(a[31]=g("div",null,[g("span",{class:"radio-title"},"Selected Collections"),g("span",{class:"radio-desc"},"Choose specific collections to include in this backup")],-1))])],2)])]),f(" Collection selector (visible only for selective backup) "),u(R,{name:"slide-down"},{default:s(()=>["selective"===W.value?(r(),d("div",Ra,[g("div",{class:"collection-selector-header"},[a[34]||(a[34]=g("label",{class:"field-label"},"Collections",-1)),g("div",{class:"selector-actions"},[g("button",{class:"text-btn",onClick:ae},"Select all"),a[33]||(a[33]=g("span",{class:"separator"},"·",-1)),g("button",{class:"text-btn",onClick:te},"Clear")])]),X.value?(r(),d("div",Da,[u(v,{indeterminate:"",small:""}),a[35]||(a[35]=g("span",null,"Loading collections…",-1))])):0===Q.value.length?(r(),d("div",Ba," No user collections found. ")):(r(),d("div",Va,[(r(!0),d(i,null,c(Q.value,e=>(r(),d("label",{key:e.collection,class:y(["collection-item",{"collection-item--selected":J.value.includes(e.collection)}])},[b(g("input",{type:"checkbox",value:e.collection,"onUpdate:modelValue":a[4]||(a[4]=e=>J.value=e)},null,8,Ta),[[D,J.value]]),g("div",Ea,[u(n,{name:e.icon||"table_chart",small:"",class:"collection-icon"},null,8,["name"]),g("span",Fa,h(e.collection),1),void 0!==e.itemCount?(r(),d("span",ja,h(e.itemCount.toLocaleString())+" items ",1)):f("v-if",!0)])],2))),128))])),"selective"===W.value&&0===J.value.length?(r(),d("p",Ia," Please select at least one collection. ")):f("v-if",!0)])):f("v-if",!0)]),_:1}),f(" Include media toggle "),g("div",Ua,[g("div",La,[a[36]||(a[36]=g("div",{class:"toggle-info"},[g("span",{class:"toggle-title"},"Include Media Files"),g("span",{class:"toggle-desc"},"Store metadata for all files in the media library")],-1)),u(k,{modelValue:q.value,"onUpdate:modelValue":a[5]||(a[5]=e=>q.value=e)},null,8,["modelValue"])])]),f(" Running state "),K.value?(r(),d("div",Aa,[u(v,{indeterminate:""}),a[37]||(a[37]=g("div",{class:"running-text"},[g("span",{class:"running-title"},"Backup in progress…"),g("span",{class:"running-desc"},"This may take a few moments. Please do not close this window.")],-1))])):f("v-if",!0),f(" Error from last attempt "),Y.value?(r(),d("div",Pa,[u(_,{type:"danger"},{default:s(()=>[x(h(Y.value),1)]),_:1})])):f("v-if",!0)]),_:1}),u(V,null,{default:s(()=>[u(p,{secondary:"",onClick:ee,disabled:K.value},{default:s(()=>[...a[38]||(a[38]=[x(" Cancel ",-1)])]),_:1},8,["disabled"]),u(p,{onClick:ne,loading:K.value,disabled:"selective"===W.value&&0===J.value.length},{default:s(()=>[u(n,{name:"play_arrow",left:""}),a[39]||(a[39]=x(" Start Backup ",-1))]),_:1},8,["loading","disabled"])]),_:1})]),_:1})]),_:1},8,["modelValue"]),u(A,{modelValue:de.value,"onUpdate:modelValue":a[8]||(a[8]=e=>de.value=e),onEsc:be},{default:s(()=>[u(U,{class:"confirm-dialog restore-dialog"},{default:s(()=>[u(z,{class:"restore-dialog-title"},{default:s(()=>[g("div",$a,[g("div",Ma,[u(n,{name:"settings_backup_restore"})]),a[40]||(a[40]=g("span",null,"Restore Backup",-1))])]),_:1}),u(B,null,{default:s(()=>[f(" Prominent warning "),u(_,{type:"warning",class:"restore-warning-notice"},{default:s(()=>[...a[41]||(a[41]=[g("strong",null,"Data will be overwritten.",-1),x(" Restoring replaces existing records in the selected collections with the backup data. This action cannot be undone — consider creating a new backup first. ",-1)])]),_:1}),f(" Backup details summary "),ie.value?(r(),d("div",Na,[g("div",Oa,[a[42]||(a[42]=g("span",{class:"detail-label"},"Backup date",-1)),g("span",Ga,h(Ie(ie.value.timestamp)),1)]),g("div",Ha,[a[43]||(a[43]=g("span",{class:"detail-label"},"Age",-1)),g("span",Wa,h(Ue(ie.value.timestamp)),1)]),g("div",qa,[a[44]||(a[44]=g("span",{class:"detail-label"},"Type",-1)),g("span",Ka,h("full"===ie.value.type?"Full Backup":"Selective Backup"),1)]),g("div",Ya,[a[45]||(a[45]=g("span",{class:"detail-label"},"Collections",-1)),g("span",Ja,h("full"===ie.value.type?"All collections":ie.value.collections.join(", ")),1)]),g("div",Qa,[a[46]||(a[46]=g("span",{class:"detail-label"},"File size",-1)),g("span",Xa,h(Le(ie.value.fileSize)),1)])])):f("v-if",!0),f(" Truncate toggle "),g("div",Za,[a[47]||(a[47]=g("div",{class:"toggle-info"},[g("span",{class:"toggle-title"},"Truncate before restore"),g("span",{class:"toggle-desc"},"Delete all existing records before importing — ensures a clean, exact restore")],-1)),u(k,{modelValue:ue.value,"onUpdate:modelValue":a[7]||(a[7]=e=>ue.value=e)},null,8,["modelValue"])]),f(" Restore progress "),null!==ce.value?(r(),d("div",et,[g("div",at,[g("div",tt,[g("span",nt,h(ge.value?"Restore completed":"Restoring backup…"),1),g("span",lt,h(me.value.progressPercentage)+"%",1)]),u(M,{value:me.value.progressPercentage,class:"progress-bar"},null,8,["value"]),g("div",ot,[g("span",rt,h(me.value.phase),1),g("span",st,h(me.value.elapsedSeconds)+"s",1)])]),me.value.errors.length>0?(r(),d("div",dt,[g("details",it,[g("summary",ct,h(me.value.errors.length)+" error(s) occurred",1),g("div",ut,[(r(!0),d(i,null,c(me.value.errors,(e,a)=>(r(),d("div",{key:a,class:"error-item"},[e.collection?(r(),d("span",pt,h(e.collection)+": ",1)):f("v-if",!0),g("span",vt,h(e.message),1)]))),128))])])])):f("v-if",!0)])):f("v-if",!0),f(" Error "),pe.value?(r(),d("div",mt,[u(_,{type:"danger"},{default:s(()=>[x(h(pe.value),1)]),_:1})])):f("v-if",!0)]),_:1}),u(V,null,{default:s(()=>[ge.value?f("v-if",!0):(r(),o(p,{key:0,secondary:"",onClick:be,disabled:null!==ce.value},{default:s(()=>[...a[48]||(a[48]=[x(" Cancel ",-1)])]),_:1},8,["disabled"])),ge.value?(r(),o(p,{key:1,onClick:he},{default:s(()=>[u(n,{name:"close",left:""}),a[49]||(a[49]=x(" Close ",-1))]),_:1})):(r(),o(p,{key:2,class:"btn-restore-confirm",onClick:ye,loading:null!==ce.value&&!ge.value},{default:s(()=>[u(n,{name:"settings_backup_restore",left:""}),a[50]||(a[50]=x(" Restore Now ",-1))]),_:1},8,["loading"]))]),_:1})]),_:1})]),_:1},8,["modelValue"]),u(A,{modelValue:xe.value,"onUpdate:modelValue":a[10]||(a[10]=e=>xe.value=e),onEsc:a[11]||(a[11]=e=>xe.value=!1)},{default:s(()=>[u(U,{class:"confirm-dialog"},{default:s(()=>[u(z,null,{default:s(()=>[g("div",gt,[g("div",ft,[u(n,{name:"delete_forever"})]),a[51]||(a[51]=g("span",null,"Delete Backup",-1))])]),_:1}),u(B,null,{default:s(()=>[u(_,{type:"danger"},{default:s(()=>[...a[52]||(a[52]=[x(" This will permanently delete the backup archive from disk. This action cannot be undone. ",-1)])]),_:1}),ke.value?(r(),d("div",bt,[g("div",ht,[a[53]||(a[53]=g("span",{class:"detail-label"},"Backup date",-1)),g("span",yt,h(Ie(ke.value.timestamp)),1)]),g("div",xt,[a[54]||(a[54]=g("span",{class:"detail-label"},"Type",-1)),g("span",kt,h("full"===ke.value.type?"Full Backup":"Selective Backup"),1)]),g("div",wt,[a[55]||(a[55]=g("span",{class:"detail-label"},"File size",-1)),g("span",_t,h(Le(ke.value.fileSize)),1)])])):f("v-if",!0),_e.value?(r(),d("div",zt,[u(_,{type:"danger"},{default:s(()=>[x(h(_e.value),1)]),_:1})])):f("v-if",!0)]),_:1}),u(V,null,{default:s(()=>[u(p,{secondary:"",onClick:a[9]||(a[9]=e=>xe.value=!1),disabled:null!==we.value},{default:s(()=>[...a[56]||(a[56]=[x(" Cancel ",-1)])]),_:1},8,["disabled"]),u(p,{danger:"",onClick:ze,loading:null!==we.value},{default:s(()=>[u(n,{name:"delete_forever",left:""}),a[57]||(a[57]=x(" Delete Permanently ",-1))]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1},8,["modelValue"]),g("input",{ref_key:"fileInputRef",ref:le,type:"file",accept:".zip",style:{display:"none"},onChange:se},null,544)]),_:1},8,["title"])}}});Pe('\n/* ============================================================\n Layout\n ============================================================ */\n.backup-module-content[data-v-827ae2a4] {\n\tpadding: var(--content-padding);\n\tpadding-bottom: var(--content-padding-bottom);\n\tmax-width: 1100px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 40px;\n}\n.header-icon[data-v-827ae2a4] {\n\t--v-button-background-color: var(--theme--primary-background);\n\t--v-button-color: var(--theme--primary);\n}\n\n/* ============================================================\n Sections\n ============================================================ */\n.section[data-v-827ae2a4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n.section-header[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n}\n.section-title[data-v-827ae2a4] {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n\tmargin: 0;\n}\n\n/* ============================================================\n Skeleton loader\n ============================================================ */\n.skeleton-rows[data-v-827ae2a4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tborder-radius: var(--theme--border-radius);\n\toverflow: hidden;\n\tborder: 1px solid var(--theme--border-color-subdued);\n}\n.skeleton-row[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 16px;\n\tpadding: 14px 16px;\n\tbackground: var(--theme--background);\n}\n.skeleton-cell[data-v-827ae2a4] {\n\theight: 14px;\n\tborder-radius: 4px;\n\tbackground: var(--theme--background-subdued);\n\tanimation: shimmer-827ae2a4 1.5s ease-in-out infinite;\n}\n.skeleton-date[data-v-827ae2a4] { width: 140px;\n}\n.skeleton-type[data-v-827ae2a4] { width: 70px;\n}\n.skeleton-collections[data-v-827ae2a4] { width: 200px; flex: 1;\n}\n.skeleton-size[data-v-827ae2a4] { width: 60px;\n}\n.skeleton-status[data-v-827ae2a4] { width: 80px;\n}\n.skeleton-actions[data-v-827ae2a4] { width: 110px;\n}\n@keyframes shimmer-827ae2a4 {\n0%, 100% { opacity: 1;\n}\n50% { opacity: 0.4;\n}\n}\n\n/* ============================================================\n Empty state\n ============================================================ */\n.empty-state[data-v-827ae2a4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 60px 24px;\n\tborder: 2px dashed var(--theme--border-color);\n\tborder-radius: var(--theme--border-radius);\n\ttext-align: center;\n\tgap: 8px;\n}\n.empty-icon[data-v-827ae2a4] {\n\t--v-icon-size: 48px;\n\t--v-icon-color: var(--theme--foreground-subdued);\n\tmargin-bottom: 8px;\n}\n.empty-title[data-v-827ae2a4] {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n\tmargin: 0;\n}\n.empty-subtitle[data-v-827ae2a4] {\n\tfont-size: 14px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin: 0 0 8px;\n}\n.empty-cta[data-v-827ae2a4] {\n\tmargin-top: 8px;\n}\n\n/* ============================================================\n Backup table\n ============================================================ */\n.table-wrapper[data-v-827ae2a4] {\n\toverflow-x: auto;\n\tborder: 1px solid var(--theme--border-color-subdued);\n\tborder-radius: var(--theme--border-radius);\n}\n.backup-table[data-v-827ae2a4] {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 14px;\n}\n.backup-table thead[data-v-827ae2a4] {\n\tbackground: var(--theme--background-subdued);\n}\n.backup-table th[data-v-827ae2a4] {\n\tpadding: 10px 16px;\n\ttext-align: left;\n\tfont-weight: 600;\n\tfont-size: 12px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n\tcolor: var(--theme--foreground-subdued);\n\tborder-bottom: 1px solid var(--theme--border-color-subdued);\n\twhite-space: nowrap;\n}\n.backup-table td[data-v-827ae2a4] {\n\tpadding: 12px 16px;\n\tvertical-align: middle;\n\tborder-bottom: 1px solid var(--theme--border-color-subdued);\n\tcolor: var(--theme--foreground);\n}\n.backup-row:last-child td[data-v-827ae2a4] {\n\tborder-bottom: none;\n}\n.backup-row:hover td[data-v-827ae2a4] {\n\tbackground: var(--theme--background-subdued);\n}\n\n/* Column widths */\n.col-date[data-v-827ae2a4] { min-width: 160px;\n}\n.col-type[data-v-827ae2a4] { min-width: 120px;\n}\n.col-collections[data-v-827ae2a4] { min-width: 180px;\n}\n.col-size[data-v-827ae2a4] { min-width: 80px; white-space: nowrap;\n}\n.col-status[data-v-827ae2a4] { min-width: 100px;\n}\n.col-actions[data-v-827ae2a4] { min-width: 130px; text-align: right;\n}\n.date-primary[data-v-827ae2a4] {\n\tdisplay: block;\n\tfont-weight: 500;\n}\n.date-relative[data-v-827ae2a4] {\n\tdisplay: block;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin-top: 2px;\n}\n\n/* Type badges */\n.type-cell[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n.type-badge[data-v-827ae2a4] {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 2px 8px;\n\tborder-radius: 100px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n}\n.type-full[data-v-827ae2a4] {\n\tbackground: var(--theme--primary-background);\n\tcolor: var(--theme--primary);\n}\n.type-selective[data-v-827ae2a4] {\n\tbackground: var(--theme--secondary-background, #f0f4ff);\n\tcolor: var(--theme--secondary, #6644dd);\n}\n.media-badge[data-v-827ae2a4] {\n\t--v-icon-color: var(--theme--foreground-subdued);\n\tdisplay: inline-flex;\n\talign-items: center;\n}\n\n/* Status badges */\n.status-badge[data-v-827ae2a4] {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 5px;\n\tpadding: 3px 8px;\n\tborder-radius: 100px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n.status-valid[data-v-827ae2a4] {\n\tbackground: color-mix(in srgb, var(--theme--success) 12%, transparent);\n\tcolor: var(--theme--success);\n}\n.status-restoring[data-v-827ae2a4] {\n\tbackground: color-mix(in srgb, var(--theme--warning) 12%, transparent);\n\tcolor: var(--theme--warning);\n}\n.status-icon[data-v-827ae2a4] {\n\t--v-icon-size: 14px;\n\t--v-icon-color: currentColor;\n}\n.status-spinner[data-v-827ae2a4] {\n\t--v-progress-circular-size: 14px;\n}\n\n/* ============================================================\n Action buttons\n ============================================================ */\n.action-buttons[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tgap: 6px;\n}\n\n/*\n * Restore button — amber/warning palette so it stands out from the\n * neutral Download button and the red Delete button.\n * Uses Directus warning tokens with a solid filled style.\n */\n.btn-restore[data-v-827ae2a4] {\n\t--v-button-background-color: color-mix(in srgb, var(--theme--warning) 15%, transparent);\n\t--v-button-color: var(--theme--warning);\n\t--v-button-border-color: color-mix(in srgb, var(--theme--warning) 40%, transparent);\n\t--v-button-background-color-hover: var(--theme--warning);\n\t--v-button-color-hover: var(--white);\n\t--v-button-border-color-hover: var(--theme--warning);\n}\n.btn-danger[data-v-827ae2a4] {\n\t--v-button-color: var(--theme--danger);\n\t--v-button-color-hover: var(--white);\n\t--v-button-background-color-hover: var(--theme--danger);\n\t--v-button-border-color-hover: var(--theme--danger);\n}\n.auto-refresh-notice[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 5px;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin: 0;\n}\n.refresh-icon[data-v-827ae2a4] {\n\t--v-icon-size: 13px;\n\t--v-icon-color: var(--theme--foreground-subdued);\n}\n\n/* ============================================================\n Schedule card\n ============================================================ */\n.schedule-card[data-v-827ae2a4] {\n\tbackground: var(--theme--background);\n\tborder: 1px solid var(--theme--border-color-subdued);\n\tborder-radius: var(--theme--border-radius);\n\tpadding: 24px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 20px;\n}\n.schedule-loading[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tfont-size: 14px;\n}\n.schedule-toggle-row[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 16px;\n}\n.toggle-label[data-v-827ae2a4] {\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n}\n.schedule-body[data-v-827ae2a4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\ttransition: opacity 0.2s ease;\n}\n.schedule-body--disabled[data-v-827ae2a4] {\n\topacity: 0.45;\n\tpointer-events: none;\n}\n.cron-presets[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n.preset-label[data-v-827ae2a4] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n\tfont-weight: 500;\n}\n.preset-btn[data-v-827ae2a4] {\n\tpadding: 4px 14px;\n\tborder-radius: 100px;\n\tborder: 1px solid var(--theme--border-color);\n\tbackground: var(--theme--background-subdued);\n\tcolor: var(--theme--foreground);\n\tfont-size: 13px;\n\tcursor: pointer;\n\ttransition: background 0.15s, border-color 0.15s, color 0.15s;\n}\n.preset-btn[data-v-827ae2a4]:hover:not(:disabled) {\n\tbackground: var(--theme--primary-background);\n\tborder-color: var(--theme--primary);\n\tcolor: var(--theme--primary);\n}\n.preset-btn--active[data-v-827ae2a4] {\n\tbackground: var(--theme--primary);\n\tborder-color: var(--theme--primary);\n\tcolor: var(--white);\n}\n.preset-btn--active[data-v-827ae2a4]:hover:not(:disabled) {\n\tbackground: var(--theme--primary);\n\tcolor: var(--white);\n}\n.preset-btn[data-v-827ae2a4]:disabled {\n\tcursor: not-allowed;\n}\n.cron-input-row[data-v-827ae2a4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n.field-label[data-v-827ae2a4] {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground-subdued);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n}\n.cron-input[data-v-827ae2a4] {\n\tmax-width: 320px;\n\tfont-family: var(--theme--fonts--mono--font-family, monospace);\n}\n.field-hint[data-v-827ae2a4] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin: 0;\n}\n.field-hint a[data-v-827ae2a4] {\n\tcolor: var(--theme--primary);\n\ttext-decoration: none;\n}\n.field-hint a[data-v-827ae2a4]:hover {\n\ttext-decoration: underline;\n}\n.last-run-row[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n\t--v-icon-color: var(--theme--foreground-subdued);\n}\n.schedule-footer[data-v-827ae2a4] {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tpadding-top: 4px;\n\tborder-top: 1px solid var(--theme--border-color-subdued);\n}\n\n/* ============================================================\n Dialogs — shared\n ============================================================ */\n.backup-dialog[data-v-827ae2a4],\n.confirm-dialog[data-v-827ae2a4] {\n\twidth: 560px;\n\tmax-width: calc(100vw - 32px);\n}\n.dialog-section[data-v-827ae2a4] {\n\tmargin-bottom: 20px;\n}\n.dialog-section[data-v-827ae2a4]:last-child {\n\tmargin-bottom: 0;\n}\n\n/* Dialog title with icon */\n.dialog-title-row[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n.dialog-title-icon-wrap[data-v-827ae2a4] {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tflex-shrink: 0;\n}\n.restore-icon-wrap[data-v-827ae2a4] {\n\tbackground: color-mix(in srgb, var(--theme--warning) 15%, transparent);\n\tcolor: var(--theme--warning);\n\t--v-icon-color: var(--theme--warning);\n}\n.danger-icon-wrap[data-v-827ae2a4] {\n\tbackground: color-mix(in srgb, var(--theme--danger) 12%, transparent);\n\tcolor: var(--theme--danger);\n\t--v-icon-color: var(--theme--danger);\n}\n\n/* ============================================================\n Restore dialog specifics\n ============================================================ */\n.restore-warning-notice[data-v-827ae2a4] {\n\tmargin-bottom: 0;\n}\n.restore-option-row[data-v-827ae2a4] {\n\tmargin-top: 16px;\n}\n.restore-progress[data-v-827ae2a4] {\n\tmargin-top: 16px;\n}\n\n/* Restore confirm button — warning/amber so it reads as impactful but not destructive */\n.btn-restore-confirm[data-v-827ae2a4] {\n\t--v-button-background-color: var(--theme--warning);\n\t--v-button-color: var(--white);\n\t--v-button-border-color: var(--theme--warning);\n\t--v-button-background-color-hover: color-mix(in srgb, var(--theme--warning) 85%, black);\n\t--v-button-color-hover: var(--white);\n\t--v-button-border-color-hover: color-mix(in srgb, var(--theme--warning) 85%, black);\n}\n\n/* ============================================================\n Radio group (New Backup dialog)\n ============================================================ */\n.radio-group[data-v-827ae2a4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin-top: 8px;\n}\n.radio-option[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 0;\n\tborder: 1px solid var(--theme--border-color);\n\tborder-radius: var(--theme--border-radius);\n\tpadding: 14px 16px;\n\tcursor: pointer;\n\ttransition: border-color 0.15s, background 0.15s;\n}\n.radio-option input[type="radio"][data-v-827ae2a4] {\n\tposition: absolute;\n\topacity: 0;\n\tpointer-events: none;\n}\n.radio-option--active[data-v-827ae2a4] {\n\tborder-color: var(--theme--primary);\n\tbackground: var(--theme--primary-background);\n}\n.radio-option[data-v-827ae2a4]:hover:not(.radio-option--active) {\n\tbackground: var(--theme--background-subdued);\n\tborder-color: var(--theme--border-color-subdued);\n}\n.radio-content[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\twidth: 100%;\n\t--v-icon-color: var(--theme--primary);\n}\n.radio-title[data-v-827ae2a4] {\n\tdisplay: block;\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tcolor: var(--theme--foreground);\n}\n.radio-desc[data-v-827ae2a4] {\n\tdisplay: block;\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin-top: 2px;\n}\n\n/* ============================================================\n Collection selector\n ============================================================ */\n.collection-selector[data-v-827ae2a4] {\n\tborder: 1px solid var(--theme--border-color-subdued);\n\tborder-radius: var(--theme--border-radius);\n\toverflow: hidden;\n}\n.collection-selector-header[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 10px 14px;\n\tbackground: var(--theme--background-subdued);\n\tborder-bottom: 1px solid var(--theme--border-color-subdued);\n}\n.selector-actions[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n.text-btn[data-v-827ae2a4] {\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n\tfont-size: 13px;\n\tcolor: var(--theme--primary);\n\tpadding: 0;\n}\n.text-btn[data-v-827ae2a4]:hover {\n\ttext-decoration: underline;\n}\n.separator[data-v-827ae2a4] {\n\tcolor: var(--theme--border-color);\n}\n.collections-loading[data-v-827ae2a4],\n.collections-empty[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 16px 14px;\n\tfont-size: 14px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.collection-list-scroll[data-v-827ae2a4] {\n\tmax-height: 240px;\n\toverflow-y: auto;\n}\n.collection-item[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tpadding: 8px 14px;\n\tcursor: pointer;\n\ttransition: background 0.1s;\n\tborder-bottom: 1px solid var(--theme--border-color-subdued);\n}\n.collection-item[data-v-827ae2a4]:last-child {\n\tborder-bottom: none;\n}\n.collection-item input[type="checkbox"][data-v-827ae2a4] {\n\twidth: 16px;\n\theight: 16px;\n\tflex-shrink: 0;\n\taccent-color: var(--theme--primary);\n\tcursor: pointer;\n}\n.collection-item--selected[data-v-827ae2a4] {\n\tbackground: var(--theme--primary-background);\n}\n.collection-item[data-v-827ae2a4]:hover:not(.collection-item--selected) {\n\tbackground: var(--theme--background-subdued);\n}\n.collection-item-content[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-left: 10px;\n\tflex: 1;\n}\n.collection-icon[data-v-827ae2a4] {\n\t--v-icon-color: var(--theme--foreground-subdued);\n}\n.collection-name[data-v-827ae2a4] {\n\tflex: 1;\n\tfont-size: 14px;\n\tcolor: var(--theme--foreground);\n\tfont-family: var(--theme--fonts--mono--font-family, monospace);\n}\n.collection-count[data-v-827ae2a4] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\twhite-space: nowrap;\n}\n\n/* ============================================================\n Toggle rows (shared between dialogs and schedule card)\n ============================================================ */\n.toggle-row[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 16px;\n\tpadding: 12px 0;\n\tborder-top: 1px solid var(--theme--border-color-subdued);\n}\n.toggle-info[data-v-827ae2a4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n.toggle-title[data-v-827ae2a4] {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n}\n.toggle-desc[data-v-827ae2a4] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n}\n\n/* ============================================================\n Running / progress state\n ============================================================ */\n.running-state[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 14px;\n\tpadding: 16px;\n\tbackground: var(--theme--primary-background);\n\tborder-radius: var(--theme--border-radius);\n\tborder: 1px solid var(--theme--primary);\n\tmargin-top: 16px;\n}\n.running-text[data-v-827ae2a4] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n.running-title[data-v-827ae2a4] {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--theme--primary);\n}\n.running-desc[data-v-827ae2a4] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.progress-info[data-v-827ae2a4] {\n\tflex: 1;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n.progress-header[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 16px;\n}\n.progress-percentage[data-v-827ae2a4] {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--theme--primary);\n\tmin-width: 50px;\n\ttext-align: right;\n}\n.progress-bar[data-v-827ae2a4] {\n\twidth: 100%;\n}\n.progress-details[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.phase-text[data-v-827ae2a4] {\n\tflex: 1;\n}\n.elapsed-time[data-v-827ae2a4] {\n\twhite-space: nowrap;\n\tmargin-left: 16px;\n}\n.errors-section[data-v-827ae2a4] {\n\tmargin-top: 8px;\n}\n.errors-details[data-v-827ae2a4] {\n\tcursor: pointer;\n}\n.errors-summary[data-v-827ae2a4] {\n\tfont-size: 12px;\n\tcolor: var(--theme--danger);\n\tfont-weight: 500;\n\tuser-select: none;\n\tpadding: 8px;\n\tmargin: -8px;\n\tborder-radius: var(--theme--border-radius);\n}\n.errors-summary[data-v-827ae2a4]:hover {\n\tbackground: var(--theme--background-subdued);\n}\n.errors-list[data-v-827ae2a4] {\n\tmargin-top: 8px;\n\tpadding-left: 12px;\n\tborder-left: 2px solid var(--theme--danger);\n\tmax-height: 200px;\n\toverflow-y: auto;\n}\n.error-item[data-v-827ae2a4] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tpadding: 4px 0;\n\tword-break: break-word;\n}\n.error-collection[data-v-827ae2a4] {\n\tcolor: var(--theme--danger);\n\tfont-weight: 500;\n}\n.error-message[data-v-827ae2a4] {\n\tcolor: var(--theme--foreground-subdued);\n}\n\n/* ============================================================\n Confirm dialog details panel\n ============================================================ */\n.confirm-details[data-v-827ae2a4] {\n\tmargin-top: 16px;\n\tbackground: var(--theme--background-subdued);\n\tborder-radius: var(--theme--border-radius);\n\tpadding: 12px 16px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n.detail-row[data-v-827ae2a4] {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n}\n.detail-label[data-v-827ae2a4] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n\tmin-width: 110px;\n\tflex-shrink: 0;\n}\n.detail-value[data-v-827ae2a4] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground);\n\tword-break: break-word;\n}\n\n/* ============================================================\n Error & validation notices\n ============================================================ */\n.error-notice[data-v-827ae2a4] {\n\tmargin-top: 12px;\n}\n.validation-msg[data-v-827ae2a4] {\n\tfont-size: 12px;\n\tcolor: var(--theme--danger);\n\tpadding: 6px 14px;\n\tmargin: 0;\n}\n\n/* ============================================================\n Transitions\n ============================================================ */\n.slide-down-enter-active[data-v-827ae2a4],\n.slide-down-leave-active[data-v-827ae2a4] {\n\ttransition: opacity 0.2s ease, transform 0.2s ease, max-height 0.25s ease;\n\tmax-height: 400px;\n\toverflow: hidden;\n}\n.slide-down-enter-from[data-v-827ae2a4],\n.slide-down-leave-to[data-v-827ae2a4] {\n\topacity: 0;\n\ttransform: translateY(-6px);\n\tmax-height: 0;\n}\n\n/* ============================================================\n Responsive\n ============================================================ */\n@media (max-width: 900px) {\n.col-collections[data-v-827ae2a4] { display: none;\n}\n}\n@media (max-width: 768px) {\n.backup-module-content[data-v-827ae2a4] {\n\t\tpadding: 16px;\n\t\tgap: 28px;\n}\n.col-size[data-v-827ae2a4] { display: none;\n}\n.col-status[data-v-827ae2a4] { display: none;\n}\n\n\t/* Collapse action buttons to icon-only on small viewports — already icon-only, but tighten gap */\n.action-buttons[data-v-827ae2a4] {\n\t\tgap: 4px;\n}\n.backup-dialog[data-v-827ae2a4],\n\t.confirm-dialog[data-v-827ae2a4] {\n\t\twidth: 100%;\n}\n.cron-input[data-v-827ae2a4] {\n\t\tmax-width: 100%;\n}\n.schedule-card[data-v-827ae2a4] {\n\t\tpadding: 16px;\n}\n.cron-presets[data-v-827ae2a4] {\n\t\tgap: 6px;\n}\n.dialog-title-row[data-v-827ae2a4] {\n\t\tgap: 8px;\n}\n}\n',{});var St=$e(Ct,[["__scopeId","data-v-827ae2a4"]]),Rt=Object.freeze({__proto__:null,default:St});export{T as displays,V as interfaces,E as layouts,F as modules,U as operations,j as panels,I as themes};
1
+ import{defineModule as e,useApi as t,useStores as a}from"@directus/extensions-sdk";import{defineComponent as n,resolveComponent as l,createBlock as o,openBlock as r,withCtx as s,createElementBlock as d,Fragment as i,renderList as c,createVNode as u,ref as p,onMounted as v,resolveDirective as m,createElementVNode as b,createCommentVNode as g,withDirectives as f,toDisplayString as h,normalizeClass as y,createTextVNode as x,computed as k,watch as w,onBeforeUnmount as _,onUnmounted as z,withModifiers as C,vModelRadio as S,Transition as R,vModelCheckbox as D}from"vue";import{useRouter as B}from"vue-router";const V=[],T=[],E=[],F=[e({id:"acuity-backup",name:"Backup",icon:"cloud_download",routes:[{path:"",props:!0,component:()=>Promise.resolve().then(function(){return Ra})},{path:":page",props:!0,component:()=>Promise.resolve().then(function(){return Ra})}]})],I=[],U=[],j=[];var L=n({__name:"navigation",props:{current:{},pages:{}},setup:e=>(t,a)=>{const n=l("v-icon"),p=l("v-list-item-icon"),v=l("v-text-overflow"),m=l("v-list-item-content"),b=l("v-list-item"),g=l("v-list");return r(),o(g,{nav:""},{default:s(()=>[(r(!0),d(i,null,c(e.pages,t=>(r(),o(b,{key:t.route,clickable:"",active:e.current===t.route,to:"/acuity-backup"+(t.route?"/"+t.route:"")},{default:s(()=>[u(p,null,{default:s(()=>[u(n,{name:t.icon},null,8,["name"])]),_:2},1024),u(m,null,{default:s(()=>[u(v,{text:t.label},null,8,["text"])]),_:2},1024)]),_:2},1032,["active","to"]))),128))]),_:1})}});const $={class:"restore-logs-page"},A={class:"section"},P={class:"section-header"},M={key:0,class:"empty-state"},N={class:"table-wrapper"},O={class:"logs-table"},G={class:"col-date"},H={class:"date-primary"},W={class:"date-relative"},q={class:"col-backup"},K={class:"backup-id"},Y={class:"col-status"},J={class:"col-duration"},Q={class:"col-items"},X={class:"col-errors"},Z={key:0,class:"no-errors"},ee={key:1,class:"error-count"},te={class:"col-actions"},ae={class:"detail-header"},ne={class:"detail-section"},le={class:"detail-row"},oe={class:"detail-value mono"},re={class:"detail-row"},se={class:"detail-value mono"},de={class:"detail-row"},ie={class:"detail-value"},ce={class:"detail-row"},ue={class:"detail-row"},pe={class:"detail-value"},ve={class:"detail-section"},me={class:"summary-grid"},be={class:"summary-item"},ge={class:"summary-value"},fe={class:"summary-item"},he={class:"summary-value success"},ye={class:"summary-item"},xe={class:"summary-item"},ke={class:"summary-value"},we={class:"summary-item"},_e={class:"summary-value"},ze={class:"summary-item"},Ce={class:"summary-value"},Se={class:"summary-item"},Re={class:"summary-value"},De={key:0,class:"detail-section"},Be={class:"section-subtitle"},Ve={class:"errors-container"},Te={key:0,class:"error-collection"},Ee={class:"error-message"},Fe={class:"error-timestamp"},Ie={class:"dialog-title-row"},Ue={class:"dialog-title-icon-wrap danger-icon-wrap"};var je=n({__name:"restore-logs",setup(e){const a=t(),n=p([]),k=p(!1),w=p(!1),_=p(null),z=p(!1),C=p(!1),S=p(null);async function R(){k.value=!0;try{const e=await a.get("/acuity-backup/restore-logs");n.value=e.data.data}catch(e){console.error("Failed to load restore logs:",e)}finally{k.value=!1}}async function D(){if(S.value){C.value=!0;try{await a.delete(`/acuity-backup/restore-logs/${S.value.id}`),n.value=n.value.filter(e=>e.id!==S.value.id),z.value=!1,w.value=!1}catch(e){console.error("Failed to delete restore log:",e)}finally{C.value=!1}}}function B(e){return new Date(e).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit"})}function V(e){const t=new Date(e),a=new Date,n=Math.floor((a.getTime()-t.getTime())/1e3);if(n<60)return"just now";const l=Math.floor(n/60);if(l<60)return`${l}m ago`;const o=Math.floor(l/60);if(o<24)return`${o}h ago`;return`${Math.floor(o/24)}d ago`}function T(e){const t=Math.floor(e/1e3);if(t<60)return`${t}s`;return`${Math.floor(t/60)}m ${t%60}s`}function E(e){switch(e){case"success":return"Success";case"partial":return"Partial";case"failed":return"Failed";default:return e}}return v(()=>{R()}),(e,t)=>{const a=l("v-icon"),p=l("v-button"),v=l("v-card-title"),F=l("v-card-text"),I=l("v-card-actions"),U=l("v-card"),j=l("v-dialog"),L=l("v-notice"),je=m("tooltip");return r(),d(i,null,[b("div",$,[b("div",A,[b("div",P,[t[7]||(t[7]=b("h2",{class:"section-title"},"Restore History",-1)),f((r(),o(p,{small:"",secondary:"",icon:"",loading:k.value,onClick:R},{default:s(()=>[u(a,{name:"refresh"})]),_:1},8,["loading"])),[[je,"Refresh list"]])]),g(" Empty state "),k.value||0!==n.value.length?(r(),d(i,{key:1},[g(" Table "),b("div",N,[b("table",O,[t[10]||(t[10]=b("thead",null,[b("tr",null,[b("th",{class:"col-date"},"Date"),b("th",{class:"col-backup"},"Backup ID"),b("th",{class:"col-status"},"Status"),b("th",{class:"col-duration"},"Duration"),b("th",{class:"col-items"},"Items"),b("th",{class:"col-errors"},"Errors"),b("th",{class:"col-actions"},"Actions")])],-1)),b("tbody",null,[(r(!0),d(i,null,c(n.value,e=>{return r(),d("tr",{key:e.id,class:"log-row"},[b("td",G,[b("span",H,h((t=e.timestamp,new Date(t).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}))),1),b("span",W,h(V(e.timestamp)),1)]),b("td",q,[b("code",K,h(e.backupId.substring(0,8)),1)]),b("td",Y,[b("span",{class:y(["status-badge",`status-${e.status}`])},h(E(e.status)),3)]),b("td",J,h(T(e.duration)),1),b("td",Q,h(e.summary.itemsRestored),1),b("td",X,[0===e.errors.length?(r(),d("span",Z,"—")):(r(),d("span",ee,h(e.errors.length),1))]),b("td",te,[f((r(),o(p,{small:"",icon:"",secondary:"",onClick:t=>function(e){_.value=e,w.value=!0}(e)},{default:s(()=>[u(a,{name:"info"})]),_:1},8,["onClick"])),[[je,"View details"]]),f((r(),o(p,{small:"",icon:"",secondary:"",danger:"",onClick:t=>function(e){S.value=e,z.value=!0}(e)},{default:s(()=>[u(a,{name:"delete"})]),_:1},8,["onClick"])),[[je,"Delete log"]])])]);var t}),128))])])])],2112)):(r(),d("div",M,[u(a,{name:"history",class:"empty-icon"}),t[8]||(t[8]=b("p",{class:"empty-title"},"No restore logs",-1)),t[9]||(t[9]=b("p",{class:"empty-subtitle"},"Restore operations will appear here.",-1))]))])]),g(" Detail Modal "),u(j,{modelValue:w.value,"onUpdate:modelValue":t[2]||(t[2]=e=>w.value=e),onEsc:t[3]||(t[3]=e=>w.value=!1)},{default:s(()=>[_.value?(r(),o(U,{key:0,class:"detail-modal"},{default:s(()=>[u(v,null,{default:s(()=>[b("div",ae,[t[11]||(t[11]=b("span",null,"Restore Log",-1)),u(p,{icon:"",secondary:"",small:"",onClick:t[0]||(t[0]=e=>w.value=!1)},{default:s(()=>[u(a,{name:"close"})]),_:1})])]),_:1}),u(F,null,{default:s(()=>{return[b("div",ne,[b("div",le,[t[12]||(t[12]=b("span",{class:"detail-label"},"Session ID",-1)),b("code",oe,h(_.value.sessionId),1)]),b("div",re,[t[13]||(t[13]=b("span",{class:"detail-label"},"Backup ID",-1)),b("code",se,h(_.value.backupId),1)]),b("div",de,[t[14]||(t[14]=b("span",{class:"detail-label"},"Date",-1)),b("span",ie,h((e=_.value.timestamp,new Date(e).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}))),1)]),b("div",ce,[t[15]||(t[15]=b("span",{class:"detail-label"},"Status",-1)),b("span",{class:y(["status-badge",`status-${_.value.status}`])},h(E(_.value.status)),3)]),b("div",ue,[t[16]||(t[16]=b("span",{class:"detail-label"},"Duration",-1)),b("span",pe,h(T(_.value.duration)),1)])]),b("div",ve,[t[24]||(t[24]=b("h3",{class:"section-subtitle"},"Summary",-1)),b("div",me,[b("div",be,[t[17]||(t[17]=b("span",{class:"summary-label"},"Collections Attempted",-1)),b("span",ge,h(_.value.summary.collectionsAttempted),1)]),b("div",fe,[t[18]||(t[18]=b("span",{class:"summary-label"},"Collections Success",-1)),b("span",he,h(_.value.summary.collectionsSuccess),1)]),b("div",ye,[t[19]||(t[19]=b("span",{class:"summary-label"},"Collections Failed",-1)),b("span",{class:y(["summary-value",{error:_.value.summary.collectionsFailed>0}])},h(_.value.summary.collectionsFailed),3)]),b("div",xe,[t[20]||(t[20]=b("span",{class:"summary-label"},"Items Restored",-1)),b("span",ke,h(_.value.summary.itemsRestored),1)]),b("div",we,[t[21]||(t[21]=b("span",{class:"summary-label"},"Media Files",-1)),b("span",_e,h(_.value.summary.mediaFilesRestored),1)]),b("div",ze,[t[22]||(t[22]=b("span",{class:"summary-label"},"Field Groups",-1)),b("span",Ce,h(_.value.summary.fieldGroupsRestored),1)]),b("div",Se,[t[23]||(t[23]=b("span",{class:"summary-label"},"Collection Views",-1)),b("span",Re,h(_.value.summary.collectionViewsRestored),1)])])]),_.value.errors.length>0?(r(),d("div",De,[b("h3",Be,"Errors ("+h(_.value.errors.length)+")",1),b("div",Ve,[(r(!0),d(i,null,c(_.value.errors,(e,t)=>(r(),d("div",{key:t,class:"error-detail"},[e.collection?(r(),d("span",Te,h(e.collection),1)):g("v-if",!0),b("span",Ee,h(e.message),1),b("span",Fe,h(B(e.timestamp)),1)]))),128))])])):g("v-if",!0)];var e}),_:1}),u(I,null,{default:s(()=>[u(p,{secondary:"",onClick:t[1]||(t[1]=e=>w.value=!1)},{default:s(()=>[...t[25]||(t[25]=[x("Close",-1)])]),_:1}),u(p,{danger:"",onClick:D},{default:s(()=>[...t[26]||(t[26]=[x("Delete Log",-1)])]),_:1})]),_:1})]),_:1})):g("v-if",!0)]),_:1},8,["modelValue"]),g(" Delete confirmation "),u(j,{modelValue:z.value,"onUpdate:modelValue":t[5]||(t[5]=e=>z.value=e),onEsc:t[6]||(t[6]=e=>z.value=!1)},{default:s(()=>[u(U,{class:"confirm-dialog"},{default:s(()=>[u(v,null,{default:s(()=>[b("div",Ie,[b("div",Ue,[u(a,{name:"delete_forever"})]),t[27]||(t[27]=b("span",null,"Delete Restore Log",-1))])]),_:1}),u(F,null,{default:s(()=>[u(L,{type:"danger"},{default:s(()=>[...t[28]||(t[28]=[x(" This will permanently delete the restore log. This action cannot be undone. ",-1)])]),_:1})]),_:1}),u(I,null,{default:s(()=>[u(p,{secondary:"",onClick:t[4]||(t[4]=e=>z.value=!1)},{default:s(()=>[...t[29]||(t[29]=[x("Cancel",-1)])]),_:1}),u(p,{danger:"",onClick:D,loading:C.value},{default:s(()=>[u(a,{name:"delete_forever",left:""}),t[30]||(t[30]=x(" Delete Permanently ",-1))]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1},8,["modelValue"])],64)}}}),Le=[],$e=[];function Ae(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=Le.indexOf(o);-1===r&&(r=Le.push(o)-1,$e[r]={}),a=$e[r]&&$e[r][n]?$e[r][n]:$e[r][n]=s()}else a=s();65279===e.charCodeAt(0)&&(e=e.substring(1)),a.styleSheet?a.styleSheet.cssText+=e:a.appendChild(document.createTextNode(e))}function s(){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}}Ae("\n.restore-logs-page[data-v-4d4855df] {\n\tpadding: 16px;\n\tmax-width: 1100px;\n}\n.section[data-v-4d4855df] {\n\tbackground: var(--theme--background);\n\tborder: 1px solid var(--theme--border-color);\n\tborder-radius: var(--theme--border-radius);\n\tpadding: 16px;\n}\n.section-header[data-v-4d4855df] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 16px;\n}\n.section-title[data-v-4d4855df] {\n\tfont-size: 18px;\n\tfont-weight: 600;\n\tmargin: 0;\n\tcolor: var(--theme--foreground);\n}\n.empty-state[data-v-4d4855df] {\n\ttext-align: center;\n\tpadding: 40px 20px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.empty-icon[data-v-4d4855df] {\n\tfont-size: 48px;\n\tmargin-bottom: 12px;\n\topacity: 0.5;\n}\n.empty-title[data-v-4d4855df] {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tmargin: 0 0 4px 0;\n}\n.empty-subtitle[data-v-4d4855df] {\n\tfont-size: 14px;\n\tmargin: 0;\n\tcolor: var(--theme--foreground-subdued);\n}\n.table-wrapper[data-v-4d4855df] {\n\toverflow-x: auto;\n}\n.logs-table[data-v-4d4855df] {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 14px;\n}\n.logs-table thead[data-v-4d4855df] {\n\tbackground: var(--theme--background-subdued);\n\tborder-bottom: 2px solid var(--theme--border-color);\n}\n.logs-table th[data-v-4d4855df] {\n\tpadding: 12px 16px;\n\ttext-align: left;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground-subdued);\n\twhite-space: nowrap;\n}\n.logs-table tbody tr[data-v-4d4855df] {\n\tborder-bottom: 1px solid var(--theme--border-color);\n\ttransition: background 0.2s;\n}\n.logs-table tbody tr[data-v-4d4855df]:hover {\n\tbackground: var(--theme--background-subdued);\n}\n.logs-table td[data-v-4d4855df] {\n\tpadding: 12px 16px;\n\tvertical-align: middle;\n}\n.col-date[data-v-4d4855df],\n.col-backup[data-v-4d4855df],\n.col-status[data-v-4d4855df],\n.col-duration[data-v-4d4855df],\n.col-items[data-v-4d4855df],\n.col-errors[data-v-4d4855df],\n.col-actions[data-v-4d4855df] {\n\ttext-align: left;\n}\n.date-primary[data-v-4d4855df] {\n\tdisplay: block;\n\tfont-weight: 500;\n\tcolor: var(--theme--foreground);\n}\n.date-relative[data-v-4d4855df] {\n\tdisplay: block;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.backup-id[data-v-4d4855df] {\n\tfont-family: monospace;\n\tfont-size: 12px;\n\tbackground: var(--theme--background-subdued);\n\tpadding: 2px 6px;\n\tborder-radius: 3px;\n}\n.status-badge[data-v-4d4855df] {\n\tdisplay: inline-block;\n\tpadding: 4px 8px;\n\tborder-radius: 3px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n.status-success[data-v-4d4855df] {\n\tbackground: rgba(34, 197, 94, 0.1);\n\tcolor: rgb(34, 197, 94);\n}\n.status-partial[data-v-4d4855df] {\n\tbackground: rgba(251, 146, 60, 0.1);\n\tcolor: rgb(251, 146, 60);\n}\n.status-failed[data-v-4d4855df] {\n\tbackground: rgba(239, 68, 68, 0.1);\n\tcolor: rgb(239, 68, 68);\n}\n.no-errors[data-v-4d4855df] {\n\tcolor: var(--theme--foreground-subdued);\n}\n.error-count[data-v-4d4855df] {\n\tcolor: rgb(239, 68, 68);\n\tfont-weight: 600;\n}\n.col-actions[data-v-4d4855df] {\n\ttext-align: right;\n\twhite-space: nowrap;\n}\n.detail-modal[data-v-4d4855df] {\n\tmax-width: 600px;\n}\n.detail-header[data-v-4d4855df] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n}\n.detail-section[data-v-4d4855df] {\n\tmargin-bottom: 24px;\n}\n.detail-section[data-v-4d4855df]:last-child {\n\tmargin-bottom: 0;\n}\n.detail-row[data-v-4d4855df] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tmargin-bottom: 8px;\n\tpadding: 8px;\n\tbackground: var(--theme--background-subdued);\n\tborder-radius: 3px;\n}\n.detail-label[data-v-4d4855df] {\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground-subdued);\n\tmin-width: 120px;\n}\n.detail-value[data-v-4d4855df] {\n\tcolor: var(--theme--foreground);\n\tword-break: break-all;\n}\n.detail-value.mono[data-v-4d4855df] {\n\tfont-family: monospace;\n\tfont-size: 12px;\n}\n.section-subtitle[data-v-4d4855df] {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground-subdued);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.5px;\n\tmargin: 0 0 12px 0;\n\tpadding-bottom: 8px;\n\tborder-bottom: 1px solid var(--theme--border-color);\n}\n.summary-grid[data-v-4d4855df] {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n\tgap: 12px;\n}\n.summary-item[data-v-4d4855df] {\n\tbackground: var(--theme--background-subdued);\n\tpadding: 12px;\n\tborder-radius: 3px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n.summary-label[data-v-4d4855df] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.summary-value[data-v-4d4855df] {\n\tfont-size: 18px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n}\n.summary-value.success[data-v-4d4855df] {\n\tcolor: rgb(34, 197, 94);\n}\n.summary-value.error[data-v-4d4855df] {\n\tcolor: rgb(239, 68, 68);\n}\n.errors-container[data-v-4d4855df] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmax-height: 400px;\n\toverflow-y: auto;\n}\n.error-detail[data-v-4d4855df] {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding: 8px 12px;\n\tbackground: rgba(239, 68, 68, 0.05);\n\tborder-left: 3px solid rgb(239, 68, 68);\n\tborder-radius: 3px;\n\tfont-size: 12px;\n}\n.error-collection[data-v-4d4855df] {\n\tcolor: rgb(239, 68, 68);\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n.error-message[data-v-4d4855df] {\n\tflex: 1;\n\tcolor: var(--theme--foreground);\n\tword-break: break-word;\n}\n.error-timestamp[data-v-4d4855df] {\n\tcolor: var(--theme--foreground-subdued);\n\twhite-space: nowrap;\n\tfont-size: 11px;\n}\n.confirm-dialog[data-v-4d4855df] {\n\tmax-width: 400px;\n}\n.dialog-title-row[data-v-4d4855df] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n.dialog-title-icon-wrap[data-v-4d4855df] {\n\twidth: 40px;\n\theight: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tborder-radius: 50%;\n\tbackground: rgba(239, 68, 68, 0.1);\n}\n.dialog-title-icon-wrap.danger-icon-wrap[data-v-4d4855df] {\n\tbackground: rgba(239, 68, 68, 0.1);\n\tcolor: rgb(239, 68, 68);\n}\n",{});var Pe=(e,t)=>{const a=e.__vccOpts||e;for(const[e,n]of t)a[e]=n;return a},Me=Pe(je,[["__scopeId","data-v-4d4855df"]]);const Ne={key:0,class:"backup-module-content"},Oe={class:"section"},Ge={class:"section-header"},He={key:0,class:"skeleton-rows"},We={class:"empty-state"},qe={class:"table-wrapper"},Ke={class:"backup-table"},Ye={class:"col-date"},Je={class:"date-primary"},Qe={class:"date-relative"},Xe={class:"col-type"},Ze={class:"type-cell"},et={key:0,class:"media-badge"},tt={class:"col-collections"},at={key:0,class:"all-collections"},nt={key:1,class:"collection-list"},lt={key:0,class:"more-collections"},ot={class:"col-size"},rt={class:"col-status"},st={key:0,class:"status-badge status-restoring"},dt={key:1,class:"status-badge status-valid"},it={class:"col-actions"},ct={class:"action-buttons"},ut={key:3,class:"auto-refresh-notice"},pt={class:"backup-module-content"},vt={class:"section"},mt={class:"schedule-card"},bt={key:0,class:"schedule-loading"},gt={class:"schedule-toggle-row"},ft={class:"cron-presets"},ht=["onClick","disabled"],yt={class:"cron-input-row"},xt={key:0,class:"last-run-row"},kt={key:0,class:"error-notice"},wt={class:"schedule-footer"},_t={class:"dialog-section"},zt={class:"radio-group"},Ct={class:"radio-content"},St={class:"radio-content"},Rt={key:0,class:"dialog-section collection-selector"},Dt={key:0,class:"collections-loading"},Bt={key:1,class:"collections-empty"},Vt={key:2,class:"collection-list-scroll"},Tt=["value"],Et={class:"collection-item-content"},Ft={class:"collection-name"},It={key:0,class:"collection-count"},Ut={key:3,class:"validation-msg"},jt={class:"dialog-section"},Lt={class:"toggle-row"},$t={key:0,class:"running-state"},At={key:1,class:"error-notice"},Pt={class:"dialog-title-row"},Mt={class:"dialog-title-icon-wrap restore-icon-wrap"},Nt={key:0,class:"confirm-details"},Ot={class:"detail-row"},Gt={class:"detail-value"},Ht={class:"detail-row"},Wt={class:"detail-value"},qt={class:"detail-row"},Kt={class:"detail-value"},Yt={class:"detail-row"},Jt={class:"detail-value"},Qt={class:"detail-row"},Xt={class:"detail-value"},Zt={class:"toggle-row restore-option-row"},ea={key:1,class:"running-state restore-progress"},ta={class:"progress-info"},aa={class:"progress-header"},na={class:"running-title"},la={class:"progress-percentage"},oa={class:"progress-details"},ra={class:"phase-text"},sa={class:"elapsed-time"},da={key:0,class:"errors-section"},ia={class:"errors-details"},ca={class:"errors-summary"},ua={class:"errors-list"},pa={key:0,class:"error-collection"},va={class:"error-message"},ma={key:2,class:"error-notice"},ba={class:"dialog-title-row"},ga={class:"dialog-title-icon-wrap danger-icon-wrap"},fa={key:0,class:"confirm-details"},ha={class:"detail-row"},ya={class:"detail-value"},xa={class:"detail-row"},ka={class:"detail-value"},wa={class:"detail-row"},_a={class:"detail-value"},za={key:1,class:"error-notice"};var Ca=n({__name:"module",props:{page:{}},setup(e){const n=e,V=B(),T=k(()=>n.page??""),E=[{route:"",label:"Backups",icon:"backup"},{route:"schedule",label:"Schedule",icon:"schedule"},{route:"restore-logs",label:"Restore Logs",icon:"history"}],F=k(()=>{switch(T.value){case"schedule":return"Schedule";case"restore-logs":return"Restore Logs";default:return"Backups"}}),I=k(()=>[{name:"Backup",to:"/acuity-backup"},{name:F.value}]);w(T,e=>{""===e?G():"schedule"===e?Fe():"restore-logs"===e||V.replace("/acuity-backup")},{immediate:!1});const U=t(),{useNotificationsStore:j}=a(),$=j(),A=p([]),P=p(!1),M=p(null),N=k(()=>M.value?M.value.toLocaleTimeString():"Never");let O=null;async function G(){P.value=!0;try{const e=await U.get("/acuity-backup/list");A.value=e.data.data??[],M.value=new Date}catch(e){Ae("Failed to load backup history. "+$e(e),"error")}finally{P.value=!1}}const H=p(!1),W=p("full"),q=p(!0),K=p(!1),Y=p(""),J=p([]),Q=p([]),X=p(!1);function Z(){Y.value="",W.value="full",q.value=!0,J.value=[],H.value=!0,async function(){if(Q.value.length>0)return;X.value=!0;try{const e=await U.get("/acuity-backup/collections");Q.value=e.data.data??[]}catch(e){Ae("Failed to load collections. "+$e(e),"error")}finally{X.value=!1}}()}function ee(){K.value||(H.value=!1)}function te(){J.value=Q.value.map(e=>e.collection)}function ae(){J.value=[]}async function ne(){if("selective"!==W.value||0!==J.value.length){K.value=!0,Y.value="";try{await U.post("/acuity-backup/run",{type:W.value,collections:"selective"===W.value?J.value:void 0,includeMedia:q.value}),Ae("Backup completed successfully.","success"),H.value=!1,await G()}catch(e){Y.value="Backup failed: "+$e(e)}finally{K.value=!1}}}const le=p(null),oe=p(!1);function re(){le.value?.click()}async function se(e){const t=e.target,a=t.files?.[0];if(t.value="",a)if(a.name.endsWith(".zip")){oe.value=!0;try{const e=new FormData;e.append("title","Backup Upload (temporary)"),e.append("file",a);const t=(await U.post("/files",e)).data.data.id;try{await U.post("/acuity-backup/upload",{fileId:t})}catch(e){try{await U.delete(`/files/${t}`)}catch{}throw e}Ae("Backup uploaded successfully.","success"),await G()}catch(e){Ae("Upload failed: "+$e(e),"error")}finally{oe.value=!1}}else Ae("Please select a .zip backup file.","warning")}const de=p(!1),ie=p(null),ce=p(null),ue=p(!1),pe=p(""),ve=p(null),me=p({phase:"",progressPercentage:0,elapsedSeconds:0,errors:[]}),be=p(!1);let ge=null;function fe(){null===ce.value&&(de.value=!1)}function he(){de.value=!1,ce.value=null,ve.value=null,be.value=!1,G()}async function ye(){if(ie.value){ce.value=ie.value.id,pe.value="",me.value={phase:"",progressPercentage:0,elapsedSeconds:0,errors:[]};try{const e=(await U.post("/acuity-backup/restore",{backupId:ie.value.id,truncateCollections:ue.value})).data.data.sessionId;ve.value=e;let t="";ge=window.setInterval(async()=>{try{const a=(await U.get(`/acuity-backup/status/${e}`)).data.data;me.value={phase:a.phase||"",progressPercentage:a.progressPercentage||0,elapsedSeconds:a.elapsedSeconds||0,errors:a.errors||[]},"running"!==a.status&&(ge&&(clearInterval(ge),ge=null),be.value=!0,"completed"===a.status?Ae("Restore completed successfully.","success"):pe.value="Restore failed. Check errors above for details."),t=a.status}catch(e){"running"!==t&&ge&&(clearInterval(ge),ge=null)}},250)}catch(e){pe.value="Failed to initiate restore: "+$e(e),ce.value=null,ve.value=null}}}const xe=p(!1),ke=p(null),we=p(null),_e=p("");async function ze(){if(ke.value){we.value=ke.value.id,_e.value="";try{await U.delete(`/acuity-backup/${ke.value.id}`),Ae("Backup deleted.","success"),xe.value=!1,A.value=A.value.filter(e=>e.id!==ke.value.id)}catch(e){_e.value="Delete failed: "+$e(e)}finally{we.value=null}}}const Ce=[{label:"Daily",value:"0 0 * * *"},{label:"Weekly",value:"0 0 * * 0"},{label:"Monthly",value:"0 0 1 * *"},{label:"Disabled",value:""}],Se=p(!1),Re=p("0 0 * * *"),De=p(void 0),Be=p(!1),Ve=p(!1),Te=p("");let Ee=!1;async function Fe(){if(!Ee){Be.value=!0;try{const e=(await U.get("/acuity-backup/schedule")).data.data;Se.value=e.enabled,Re.value=e.cronExpression,De.value=e.lastRun,Ee=!0}catch(e){Ae("Failed to load schedule configuration. "+$e(e),"error")}finally{Be.value=!1}}}async function Ie(){if(Te.value="",Se.value){const e=Re.value.trim();if(!e)return void(Te.value="A cron expression is required when automatic backups are enabled.");if(5!==e.split(/\s+/).length)return void(Te.value="The cron expression must have exactly 5 fields (minute hour day-of-month month day-of-week).")}Ve.value=!0;try{await U.post("/acuity-backup/schedule",{enabled:Se.value,cronExpression:Re.value.trim()||"0 0 * * *"}),Ae("Schedule saved successfully.","success")}catch(e){Te.value="Failed to save schedule: "+$e(e)}finally{Ve.value=!1}}function Ue(e){try{return new Intl.DateTimeFormat(void 0,{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}).format(new Date(e))}catch{return e}}function je(e){try{const t=Date.now()-new Date(e).getTime(),a=Math.floor(t/1e3);return a<60?"just now":a<3600?`${Math.floor(a/60)}m ago`:a<86400?`${Math.floor(a/3600)}h ago`:a<604800?`${Math.floor(a/86400)}d ago`:`${Math.floor(a/604800)}w ago`}catch{return""}}function Le(e){if(!e||0===e)return"—";const t=["B","KB","MB","GB"];let a=e,n=0;for(;a>=1024&&n<t.length-1;)a/=1024,n++;return`${a.toFixed(a<10?1:0)} ${t[n]}`}function $e(e){if(e&&"object"==typeof e){const t=e,a=t.response?.data?.errors?.[0]?.message;if(a)return a;if(t.message)return t.message}return String(e)}function Ae(e,t="success"){$.add({title:"success"===t?"Success":"error"===t?"Error":"Warning",text:e,type:t,persist:"error"===t})}return v(()=>{"schedule"===T.value?Fe():(G(),O=setInterval(G,3e4))}),_(()=>{null!==ge&&(clearInterval(ge),ge=null)}),z(()=>{null!==O&&(clearInterval(O),O=null)}),(e,t)=>{const a=l("v-breadcrumb"),n=l("v-icon"),p=l("v-button"),v=l("v-progress-circular"),k=l("v-switch"),w=l("v-input"),_=l("v-notice"),z=l("v-card-title"),B=l("v-card-text"),V=l("v-card-actions"),j=l("v-card"),$=l("v-dialog"),M=l("v-progress-linear"),O=l("private-view"),ve=m("tooltip");return r(),o(O,{title:F.value},{headline:s(()=>[u(a,{items:I.value},null,8,["items"])]),"title-outer:prepend":s(()=>[u(p,{class:"header-icon",rounded:"",icon:"",secondary:"",disabled:""},{default:s(()=>[u(n,{name:"cloud_download"})]),_:1})]),actions:s(()=>[""===T.value?(r(),o(p,{key:0,secondary:"",onClick:re,loading:oe.value},{default:s(()=>[u(n,{name:"upload_file",left:""}),t[12]||(t[12]=x(" Upload Backup ",-1))]),_:1},8,["loading"])):g("v-if",!0),""===T.value?(r(),o(p,{key:1,onClick:Z,loading:K.value},{default:s(()=>[u(n,{name:"add",left:""}),t[13]||(t[13]=x(" Run Backup ",-1))]),_:1},8,["loading"])):g("v-if",!0)]),navigation:s(()=>[u(L,{current:T.value,pages:E},null,8,["current"])]),default:s(()=>[""===T.value?(r(),d("div",Ne,[g(" Backup history table "),b("div",Oe,[b("div",Ge,[t[14]||(t[14]=b("h2",{class:"section-title"},"Backup History",-1)),f((r(),o(p,{small:"",secondary:"",icon:"",loading:P.value,onClick:G},{default:s(()=>[u(n,{name:"refresh"})]),_:1},8,["loading"])),[[ve,"Refresh list"]])]),g(" Loading skeleton "),P.value&&0===A.value.length?(r(),d("div",He,[(r(),d(i,null,c(3,e=>b("div",{key:e,class:"skeleton-row"},[...t[15]||(t[15]=[b("div",{class:"skeleton-cell skeleton-date"},null,-1),b("div",{class:"skeleton-cell skeleton-type"},null,-1),b("div",{class:"skeleton-cell skeleton-collections"},null,-1),b("div",{class:"skeleton-cell skeleton-size"},null,-1),b("div",{class:"skeleton-cell skeleton-status"},null,-1),b("div",{class:"skeleton-cell skeleton-actions"},null,-1)])])),64))])):P.value||0!==A.value.length?(r(),d(i,{key:2},[g(" Table "),b("div",qe,[b("table",Ke,[t[21]||(t[21]=b("thead",null,[b("tr",null,[b("th",{class:"col-date"},"Date"),b("th",{class:"col-type"},"Type"),b("th",{class:"col-collections"},"Collections"),b("th",{class:"col-size"},"Size"),b("th",{class:"col-status"},"Status"),b("th",{class:"col-actions"},"Actions")])],-1)),b("tbody",null,[(r(!0),d(i,null,c(A.value,e=>(r(),d("tr",{key:e.id,class:"backup-row"},[g(" Date "),b("td",Ye,[b("span",Je,h(Ue(e.timestamp)),1),b("span",Qe,h(je(e.timestamp)),1)]),g(" Type badge "),b("td",Xe,[b("div",Ze,[b("span",{class:y(["type-badge","full"===e.type?"type-full":"type-selective"])},h("full"===e.type?"Full":"Selective"),3),e.includeMedia?f((r(),d("span",et,[u(n,{name:"image",small:""})])),[[ve,"Includes media files"]]):g("v-if",!0)])]),g(" Collections "),b("td",tt,["full"===e.type?(r(),d("span",at,"All collections")):(r(),d("span",nt,[x(h(e.collections.slice(0,3).join(", "))+" ",1),e.collections.length>3?(r(),d("span",lt," +"+h(e.collections.length-3)+" more ",1)):g("v-if",!0)]))]),g(" Size "),b("td",ot,h(Le(e.fileSize)),1),g(" Status "),b("td",rt,[ce.value===e.id?(r(),d("span",st,[u(v,{"x-small":"",indeterminate:"",class:"status-spinner"}),t[19]||(t[19]=x(" Restoring… ",-1))])):(r(),d("span",dt,[u(n,{name:"check_circle","x-small":"",class:"status-icon"}),t[20]||(t[20]=x(" Ready ",-1))]))]),g(" Actions "),b("td",it,[b("div",ct,[g(" Download "),f((r(),o(p,{"x-small":"",secondary:"",icon:"",onClick:C(t=>async function(e){try{const t=await U.get(`/acuity-backup/download/${e.id}`,{responseType:"blob"}),a=new Blob([t.data],{type:"application/zip"}),n=URL.createObjectURL(a),l=document.createElement("a");l.href=n,l.download=e.filename,l.click(),URL.revokeObjectURL(n)}catch{const t=document.createElement("a");t.href=function(e){return`/acuity-backup/download/${e.id}`}(e),t.download=e.filename,t.click()}}(e),["prevent"])},{default:s(()=>[u(n,{name:"download"})]),_:1},8,["onClick"])),[[ve,"Download backup archive"]]),g(" Restore — uses warning styling to signal impact "),f((r(),o(p,{"x-small":"",icon:"",class:"btn-restore",loading:ce.value===e.id,disabled:null!==ce.value&&ce.value!==e.id,onClick:t=>function(e){ie.value=e,ue.value=!1,pe.value="",be.value=!1,de.value=!0}(e)},{default:s(()=>[u(n,{name:"settings_backup_restore"})]),_:1},8,["loading","disabled","onClick"])),[[ve,"Restore data from this backup"]]),g(" Delete "),f((r(),o(p,{"x-small":"",secondary:"",icon:"",class:"btn-danger",loading:we.value===e.id,disabled:null!==we.value&&we.value!==e.id,onClick:t=>function(e){ke.value=e,_e.value="",xe.value=!0}(e)},{default:s(()=>[u(n,{name:"delete_outline"})]),_:1},8,["loading","disabled","onClick"])),[[ve,"Permanently delete this backup"]])])])]))),128))])])])],2112)):(r(),d(i,{key:1},[g(" Empty state "),b("div",We,[u(n,{name:"cloud_off",class:"empty-icon"}),t[17]||(t[17]=b("p",{class:"empty-title"},"No backups yet",-1)),t[18]||(t[18]=b("p",{class:"empty-subtitle"},'Create your first backup by clicking "Run Backup" above.',-1)),u(p,{class:"empty-cta",onClick:Z},{default:s(()=>[u(n,{name:"add",left:""}),t[16]||(t[16]=x(" Run Backup ",-1))]),_:1})])],2112)),A.value.length>0?(r(),d("p",ut,[u(n,{name:"autorenew","x-small":"",class:"refresh-icon"}),x(" Auto-refreshes every 30 s — last updated: "+h(N.value),1)])):g("v-if",!0)])])):"schedule"===T.value?(r(),d(i,{key:1},[g(' ================================================================\n\t\t PAGE: SCHEDULE (route "schedule")\n\t\t ================================================================ '),b("div",pt,[b("div",vt,[t[28]||(t[28]=b("div",{class:"section-header"},[b("h2",{class:"section-title"},"Automatic Backups")],-1)),b("div",mt,[Be.value?(r(),d("div",bt,[u(v,{indeterminate:""}),t[22]||(t[22]=b("span",null,"Loading schedule…",-1))])):(r(),d(i,{key:1},[b("div",gt,[t[23]||(t[23]=b("div",{class:"toggle-info"},[b("label",{class:"toggle-label",for:"schedule-enabled"},"Enable Automatic Backups"),b("span",{class:"toggle-desc"},"Runs a full backup on the schedule defined below")],-1)),u(k,{id:"schedule-enabled",modelValue:Se.value,"onUpdate:modelValue":t[0]||(t[0]=e=>Se.value=e)},null,8,["modelValue"])]),b("div",{class:y(["schedule-body",!Se.value&&"schedule-body--disabled"])},[b("div",ft,[t[24]||(t[24]=b("span",{class:"preset-label"},"Presets:",-1)),(r(),d(i,null,c(Ce,e=>b("button",{key:e.label,class:y(["preset-btn",Re.value===e.value&&"preset-btn--active"]),onClick:t=>function(e){"Disabled"===e.label?Se.value=!1:(Re.value=e.value,Se.value=!0)}(e),disabled:!Se.value},h(e.label),11,ht)),64))]),b("div",yt,[t[25]||(t[25]=b("label",{class:"field-label",for:"cron-expression"},"Cron Expression",-1)),u(w,{id:"cron-expression",modelValue:Re.value,"onUpdate:modelValue":t[1]||(t[1]=e=>Re.value=e),placeholder:"0 0 * * *",disabled:!Se.value,"font-mono":"",class:"cron-input"},null,8,["modelValue","disabled"]),t[26]||(t[26]=b("p",{class:"field-hint"},[x(" Five fields: minute hour day-of-month month day-of-week. "),b("a",{href:"https://crontab.guru/",target:"_blank",rel:"noopener noreferrer"},"crontab.guru")],-1))]),De.value?(r(),d("div",xt,[u(n,{name:"history",small:""}),b("span",null,"Last run: "+h(Ue(De.value))+" ("+h(je(De.value))+")",1)])):g("v-if",!0)],2),Te.value?(r(),d("div",kt,[u(_,{type:"danger"},{default:s(()=>[x(h(Te.value),1)]),_:1})])):g("v-if",!0),b("div",wt,[u(p,{onClick:Ie,loading:Ve.value},{default:s(()=>[u(n,{name:"save",left:""}),t[27]||(t[27]=x(" Save Schedule ",-1))]),_:1},8,["loading"])])],64))])])])],2112)):"restore-logs"===T.value?(r(),d(i,{key:2},[g(' ================================================================\n\t\t PAGE: RESTORE LOGS (route "restore-logs")\n\t\t ================================================================ '),u(Me)],2112)):g("v-if",!0),u($,{modelValue:H.value,"onUpdate:modelValue":t[6]||(t[6]=e=>H.value=e),onEsc:ee},{default:s(()=>[u(j,{class:"backup-dialog"},{default:s(()=>[u(z,null,{default:s(()=>[u(n,{name:"add_circle",class:"dialog-title-icon"}),t[29]||(t[29]=x(" New Backup ",-1))]),_:1}),u(B,null,{default:s(()=>[g(" Backup type selection "),b("div",_t,[t[32]||(t[32]=b("label",{class:"field-label"},"Backup Type",-1)),b("div",zt,[b("label",{class:y(["radio-option",{"radio-option--active":"full"===W.value}])},[f(b("input",{type:"radio","onUpdate:modelValue":t[2]||(t[2]=e=>W.value=e),value:"full"},null,512),[[S,W.value]]),b("div",Ct,[u(n,{name:"cloud_download"}),t[30]||(t[30]=b("div",null,[b("span",{class:"radio-title"},"Full Backup"),b("span",{class:"radio-desc"},"Back up all collections and optionally all media files")],-1))])],2),b("label",{class:y(["radio-option",{"radio-option--active":"selective"===W.value}])},[f(b("input",{type:"radio","onUpdate:modelValue":t[3]||(t[3]=e=>W.value=e),value:"selective"},null,512),[[S,W.value]]),b("div",St,[u(n,{name:"checklist"}),t[31]||(t[31]=b("div",null,[b("span",{class:"radio-title"},"Selected Collections"),b("span",{class:"radio-desc"},"Choose specific collections to include in this backup")],-1))])],2)])]),g(" Collection selector (visible only for selective backup) "),u(R,{name:"slide-down"},{default:s(()=>["selective"===W.value?(r(),d("div",Rt,[b("div",{class:"collection-selector-header"},[t[34]||(t[34]=b("label",{class:"field-label"},"Collections",-1)),b("div",{class:"selector-actions"},[b("button",{class:"text-btn",onClick:te},"Select all"),t[33]||(t[33]=b("span",{class:"separator"},"·",-1)),b("button",{class:"text-btn",onClick:ae},"Clear")])]),X.value?(r(),d("div",Dt,[u(v,{indeterminate:"",small:""}),t[35]||(t[35]=b("span",null,"Loading collections…",-1))])):0===Q.value.length?(r(),d("div",Bt," No user collections found. ")):(r(),d("div",Vt,[(r(!0),d(i,null,c(Q.value,e=>(r(),d("label",{key:e.collection,class:y(["collection-item",{"collection-item--selected":J.value.includes(e.collection)}])},[f(b("input",{type:"checkbox",value:e.collection,"onUpdate:modelValue":t[4]||(t[4]=e=>J.value=e)},null,8,Tt),[[D,J.value]]),b("div",Et,[u(n,{name:e.icon||"table_chart",small:"",class:"collection-icon"},null,8,["name"]),b("span",Ft,h(e.collection),1),void 0!==e.itemCount?(r(),d("span",It,h(e.itemCount.toLocaleString())+" items ",1)):g("v-if",!0)])],2))),128))])),"selective"===W.value&&0===J.value.length?(r(),d("p",Ut," Please select at least one collection. ")):g("v-if",!0)])):g("v-if",!0)]),_:1}),g(" Include media toggle "),b("div",jt,[b("div",Lt,[t[36]||(t[36]=b("div",{class:"toggle-info"},[b("span",{class:"toggle-title"},"Include Media Files"),b("span",{class:"toggle-desc"},"Store metadata for all files in the media library")],-1)),u(k,{modelValue:q.value,"onUpdate:modelValue":t[5]||(t[5]=e=>q.value=e)},null,8,["modelValue"])])]),g(" Running state "),K.value?(r(),d("div",$t,[u(v,{indeterminate:""}),t[37]||(t[37]=b("div",{class:"running-text"},[b("span",{class:"running-title"},"Backup in progress…"),b("span",{class:"running-desc"},"This may take a few moments. Please do not close this window.")],-1))])):g("v-if",!0),g(" Error from last attempt "),Y.value?(r(),d("div",At,[u(_,{type:"danger"},{default:s(()=>[x(h(Y.value),1)]),_:1})])):g("v-if",!0)]),_:1}),u(V,null,{default:s(()=>[u(p,{secondary:"",onClick:ee,disabled:K.value},{default:s(()=>[...t[38]||(t[38]=[x(" Cancel ",-1)])]),_:1},8,["disabled"]),u(p,{onClick:ne,loading:K.value,disabled:"selective"===W.value&&0===J.value.length},{default:s(()=>[u(n,{name:"play_arrow",left:""}),t[39]||(t[39]=x(" Start Backup ",-1))]),_:1},8,["loading","disabled"])]),_:1})]),_:1})]),_:1},8,["modelValue"]),u($,{modelValue:de.value,"onUpdate:modelValue":t[8]||(t[8]=e=>de.value=e),onEsc:fe},{default:s(()=>[u(j,{class:"confirm-dialog restore-dialog"},{default:s(()=>[u(z,{class:"restore-dialog-title"},{default:s(()=>[b("div",Pt,[b("div",Mt,[u(n,{name:"settings_backup_restore"})]),t[40]||(t[40]=b("span",null,"Restore Backup",-1))])]),_:1}),u(B,null,{default:s(()=>[g(" Prominent warning "),u(_,{type:"warning",class:"restore-warning-notice"},{default:s(()=>[...t[41]||(t[41]=[b("strong",null,"Data will be overwritten.",-1),x(" Restoring replaces existing records in the selected collections with the backup data. This action cannot be undone — consider creating a new backup first. ",-1)])]),_:1}),g(" Backup details summary "),ie.value?(r(),d("div",Nt,[b("div",Ot,[t[42]||(t[42]=b("span",{class:"detail-label"},"Backup date",-1)),b("span",Gt,h(Ue(ie.value.timestamp)),1)]),b("div",Ht,[t[43]||(t[43]=b("span",{class:"detail-label"},"Age",-1)),b("span",Wt,h(je(ie.value.timestamp)),1)]),b("div",qt,[t[44]||(t[44]=b("span",{class:"detail-label"},"Type",-1)),b("span",Kt,h("full"===ie.value.type?"Full Backup":"Selective Backup"),1)]),b("div",Yt,[t[45]||(t[45]=b("span",{class:"detail-label"},"Collections",-1)),b("span",Jt,h("full"===ie.value.type?"All collections":ie.value.collections.join(", ")),1)]),b("div",Qt,[t[46]||(t[46]=b("span",{class:"detail-label"},"File size",-1)),b("span",Xt,h(Le(ie.value.fileSize)),1)])])):g("v-if",!0),g(" Truncate toggle "),b("div",Zt,[t[47]||(t[47]=b("div",{class:"toggle-info"},[b("span",{class:"toggle-title"},"Truncate before restore"),b("span",{class:"toggle-desc"},"Delete all existing records before importing — ensures a clean, exact restore")],-1)),u(k,{modelValue:ue.value,"onUpdate:modelValue":t[7]||(t[7]=e=>ue.value=e)},null,8,["modelValue"])]),g(" Restore progress "),null!==ce.value?(r(),d("div",ea,[b("div",ta,[b("div",aa,[b("span",na,h(be.value?"Restore completed":"Restoring backup…"),1),b("span",la,h(me.value.progressPercentage)+"%",1)]),u(M,{value:me.value.progressPercentage,class:"progress-bar"},null,8,["value"]),b("div",oa,[b("span",ra,h(me.value.phase),1),b("span",sa,h(me.value.elapsedSeconds)+"s",1)])]),me.value.errors.length>0?(r(),d("div",da,[b("details",ia,[b("summary",ca,h(me.value.errors.length)+" error(s) occurred",1),b("div",ua,[(r(!0),d(i,null,c(me.value.errors,(e,t)=>(r(),d("div",{key:t,class:"error-item"},[e.collection?(r(),d("span",pa,h(e.collection)+": ",1)):g("v-if",!0),b("span",va,h(e.message),1)]))),128))])])])):g("v-if",!0)])):g("v-if",!0),g(" Error "),pe.value?(r(),d("div",ma,[u(_,{type:"danger"},{default:s(()=>[x(h(pe.value),1)]),_:1})])):g("v-if",!0)]),_:1}),u(V,null,{default:s(()=>[be.value?g("v-if",!0):(r(),o(p,{key:0,secondary:"",onClick:fe,disabled:null!==ce.value},{default:s(()=>[...t[48]||(t[48]=[x(" Cancel ",-1)])]),_:1},8,["disabled"])),be.value?(r(),o(p,{key:1,onClick:he},{default:s(()=>[u(n,{name:"close",left:""}),t[49]||(t[49]=x(" Close ",-1))]),_:1})):(r(),o(p,{key:2,class:"btn-restore-confirm",onClick:ye,loading:null!==ce.value&&!be.value},{default:s(()=>[u(n,{name:"settings_backup_restore",left:""}),t[50]||(t[50]=x(" Restore Now ",-1))]),_:1},8,["loading"]))]),_:1})]),_:1})]),_:1},8,["modelValue"]),u($,{modelValue:xe.value,"onUpdate:modelValue":t[10]||(t[10]=e=>xe.value=e),onEsc:t[11]||(t[11]=e=>xe.value=!1)},{default:s(()=>[u(j,{class:"confirm-dialog"},{default:s(()=>[u(z,null,{default:s(()=>[b("div",ba,[b("div",ga,[u(n,{name:"delete_forever"})]),t[51]||(t[51]=b("span",null,"Delete Backup",-1))])]),_:1}),u(B,null,{default:s(()=>[u(_,{type:"danger"},{default:s(()=>[...t[52]||(t[52]=[x(" This will permanently delete the backup archive from disk. This action cannot be undone. ",-1)])]),_:1}),ke.value?(r(),d("div",fa,[b("div",ha,[t[53]||(t[53]=b("span",{class:"detail-label"},"Backup date",-1)),b("span",ya,h(Ue(ke.value.timestamp)),1)]),b("div",xa,[t[54]||(t[54]=b("span",{class:"detail-label"},"Type",-1)),b("span",ka,h("full"===ke.value.type?"Full Backup":"Selective Backup"),1)]),b("div",wa,[t[55]||(t[55]=b("span",{class:"detail-label"},"File size",-1)),b("span",_a,h(Le(ke.value.fileSize)),1)])])):g("v-if",!0),_e.value?(r(),d("div",za,[u(_,{type:"danger"},{default:s(()=>[x(h(_e.value),1)]),_:1})])):g("v-if",!0)]),_:1}),u(V,null,{default:s(()=>[u(p,{secondary:"",onClick:t[9]||(t[9]=e=>xe.value=!1),disabled:null!==we.value},{default:s(()=>[...t[56]||(t[56]=[x(" Cancel ",-1)])]),_:1},8,["disabled"]),u(p,{danger:"",onClick:ze,loading:null!==we.value},{default:s(()=>[u(n,{name:"delete_forever",left:""}),t[57]||(t[57]=x(" Delete Permanently ",-1))]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1},8,["modelValue"]),b("input",{ref_key:"fileInputRef",ref:le,type:"file",accept:".zip",style:{display:"none"},onChange:se},null,544)]),_:1},8,["title"])}}});Ae('\n/* ============================================================\n Layout\n ============================================================ */\n.backup-module-content[data-v-747b633d] {\n\tpadding: var(--content-padding);\n\tpadding-bottom: var(--content-padding-bottom);\n\tmax-width: 1100px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 40px;\n}\n.header-icon[data-v-747b633d] {\n\t--v-button-background-color: var(--theme--primary-background);\n\t--v-button-color: var(--theme--primary);\n}\n\n/* ============================================================\n Sections\n ============================================================ */\n.section[data-v-747b633d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n.section-header[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n}\n.section-title[data-v-747b633d] {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n\tmargin: 0;\n}\n\n/* ============================================================\n Skeleton loader\n ============================================================ */\n.skeleton-rows[data-v-747b633d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tborder-radius: var(--theme--border-radius);\n\toverflow: hidden;\n\tborder: 1px solid var(--theme--border-color-subdued);\n}\n.skeleton-row[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 16px;\n\tpadding: 14px 16px;\n\tbackground: var(--theme--background);\n}\n.skeleton-cell[data-v-747b633d] {\n\theight: 14px;\n\tborder-radius: 4px;\n\tbackground: var(--theme--background-subdued);\n\tanimation: shimmer-747b633d 1.5s ease-in-out infinite;\n}\n.skeleton-date[data-v-747b633d] { width: 140px;\n}\n.skeleton-type[data-v-747b633d] { width: 70px;\n}\n.skeleton-collections[data-v-747b633d] { width: 200px; flex: 1;\n}\n.skeleton-size[data-v-747b633d] { width: 60px;\n}\n.skeleton-status[data-v-747b633d] { width: 80px;\n}\n.skeleton-actions[data-v-747b633d] { width: 110px;\n}\n@keyframes shimmer-747b633d {\n0%, 100% { opacity: 1;\n}\n50% { opacity: 0.4;\n}\n}\n\n/* ============================================================\n Empty state\n ============================================================ */\n.empty-state[data-v-747b633d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 60px 24px;\n\tborder: 2px dashed var(--theme--border-color);\n\tborder-radius: var(--theme--border-radius);\n\ttext-align: center;\n\tgap: 8px;\n}\n.empty-icon[data-v-747b633d] {\n\t--v-icon-size: 48px;\n\t--v-icon-color: var(--theme--foreground-subdued);\n\tmargin-bottom: 8px;\n}\n.empty-title[data-v-747b633d] {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n\tmargin: 0;\n}\n.empty-subtitle[data-v-747b633d] {\n\tfont-size: 14px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin: 0 0 8px;\n}\n.empty-cta[data-v-747b633d] {\n\tmargin-top: 8px;\n}\n\n/* ============================================================\n Backup table\n ============================================================ */\n.table-wrapper[data-v-747b633d] {\n\toverflow-x: auto;\n\tborder: 1px solid var(--theme--border-color-subdued);\n\tborder-radius: var(--theme--border-radius);\n}\n.backup-table[data-v-747b633d] {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 14px;\n}\n.backup-table thead[data-v-747b633d] {\n\tbackground: var(--theme--background-subdued);\n}\n.backup-table th[data-v-747b633d] {\n\tpadding: 10px 16px;\n\ttext-align: left;\n\tfont-weight: 600;\n\tfont-size: 12px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n\tcolor: var(--theme--foreground-subdued);\n\tborder-bottom: 1px solid var(--theme--border-color-subdued);\n\twhite-space: nowrap;\n}\n.backup-table td[data-v-747b633d] {\n\tpadding: 12px 16px;\n\tvertical-align: middle;\n\tborder-bottom: 1px solid var(--theme--border-color-subdued);\n\tcolor: var(--theme--foreground);\n}\n.backup-row:last-child td[data-v-747b633d] {\n\tborder-bottom: none;\n}\n.backup-row:hover td[data-v-747b633d] {\n\tbackground: var(--theme--background-subdued);\n}\n\n/* Column widths */\n.col-date[data-v-747b633d] { min-width: 160px;\n}\n.col-type[data-v-747b633d] { min-width: 120px;\n}\n.col-collections[data-v-747b633d] { min-width: 180px;\n}\n.col-size[data-v-747b633d] { min-width: 80px; white-space: nowrap;\n}\n.col-status[data-v-747b633d] { min-width: 100px;\n}\n.col-actions[data-v-747b633d] { min-width: 130px; text-align: right;\n}\n.date-primary[data-v-747b633d] {\n\tdisplay: block;\n\tfont-weight: 500;\n}\n.date-relative[data-v-747b633d] {\n\tdisplay: block;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin-top: 2px;\n}\n\n/* Type badges */\n.type-cell[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n.type-badge[data-v-747b633d] {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 2px 8px;\n\tborder-radius: 100px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n}\n.type-full[data-v-747b633d] {\n\tbackground: var(--theme--primary-background);\n\tcolor: var(--theme--primary);\n}\n.type-selective[data-v-747b633d] {\n\tbackground: var(--theme--secondary-background, #f0f4ff);\n\tcolor: var(--theme--secondary, #6644dd);\n}\n.media-badge[data-v-747b633d] {\n\t--v-icon-color: var(--theme--foreground-subdued);\n\tdisplay: inline-flex;\n\talign-items: center;\n}\n\n/* Status badges */\n.status-badge[data-v-747b633d] {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 5px;\n\tpadding: 3px 8px;\n\tborder-radius: 100px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n.status-valid[data-v-747b633d] {\n\tbackground: color-mix(in srgb, var(--theme--success) 12%, transparent);\n\tcolor: var(--theme--success);\n}\n.status-restoring[data-v-747b633d] {\n\tbackground: color-mix(in srgb, var(--theme--warning) 12%, transparent);\n\tcolor: var(--theme--warning);\n}\n.status-icon[data-v-747b633d] {\n\t--v-icon-size: 14px;\n\t--v-icon-color: currentColor;\n}\n.status-spinner[data-v-747b633d] {\n\t--v-progress-circular-size: 14px;\n}\n\n/* ============================================================\n Action buttons\n ============================================================ */\n.action-buttons[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tgap: 6px;\n}\n\n/*\n * Restore button — amber/warning palette so it stands out from the\n * neutral Download button and the red Delete button.\n * Uses Directus warning tokens with a solid filled style.\n */\n.btn-restore[data-v-747b633d] {\n\t--v-button-background-color: color-mix(in srgb, var(--theme--warning) 15%, transparent);\n\t--v-button-color: var(--theme--warning);\n\t--v-button-border-color: color-mix(in srgb, var(--theme--warning) 40%, transparent);\n\t--v-button-background-color-hover: var(--theme--warning);\n\t--v-button-color-hover: var(--white);\n\t--v-button-border-color-hover: var(--theme--warning);\n}\n.btn-danger[data-v-747b633d] {\n\t--v-button-color: var(--theme--danger);\n\t--v-button-color-hover: var(--white);\n\t--v-button-background-color-hover: var(--theme--danger);\n\t--v-button-border-color-hover: var(--theme--danger);\n}\n.auto-refresh-notice[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 5px;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin: 0;\n}\n.refresh-icon[data-v-747b633d] {\n\t--v-icon-size: 13px;\n\t--v-icon-color: var(--theme--foreground-subdued);\n}\n\n/* ============================================================\n Schedule card\n ============================================================ */\n.schedule-card[data-v-747b633d] {\n\tbackground: var(--theme--background);\n\tborder: 1px solid var(--theme--border-color-subdued);\n\tborder-radius: var(--theme--border-radius);\n\tpadding: 24px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 20px;\n}\n.schedule-loading[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tfont-size: 14px;\n}\n.schedule-toggle-row[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 16px;\n}\n.toggle-label[data-v-747b633d] {\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n}\n.schedule-body[data-v-747b633d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\ttransition: opacity 0.2s ease;\n}\n.schedule-body--disabled[data-v-747b633d] {\n\topacity: 0.45;\n\tpointer-events: none;\n}\n.cron-presets[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n.preset-label[data-v-747b633d] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n\tfont-weight: 500;\n}\n.preset-btn[data-v-747b633d] {\n\tpadding: 4px 14px;\n\tborder-radius: 100px;\n\tborder: 1px solid var(--theme--border-color);\n\tbackground: var(--theme--background-subdued);\n\tcolor: var(--theme--foreground);\n\tfont-size: 13px;\n\tcursor: pointer;\n\ttransition: background 0.15s, border-color 0.15s, color 0.15s;\n}\n.preset-btn[data-v-747b633d]:hover:not(:disabled) {\n\tbackground: var(--theme--primary-background);\n\tborder-color: var(--theme--primary);\n\tcolor: var(--theme--primary);\n}\n.preset-btn--active[data-v-747b633d] {\n\tbackground: var(--theme--primary);\n\tborder-color: var(--theme--primary);\n\tcolor: var(--white);\n}\n.preset-btn--active[data-v-747b633d]:hover:not(:disabled) {\n\tbackground: var(--theme--primary);\n\tcolor: var(--white);\n}\n.preset-btn[data-v-747b633d]:disabled {\n\tcursor: not-allowed;\n}\n.cron-input-row[data-v-747b633d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n.field-label[data-v-747b633d] {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground-subdued);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n}\n.cron-input[data-v-747b633d] {\n\tmax-width: 320px;\n\tfont-family: var(--theme--fonts--mono--font-family, monospace);\n}\n.field-hint[data-v-747b633d] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin: 0;\n}\n.field-hint a[data-v-747b633d] {\n\tcolor: var(--theme--primary);\n\ttext-decoration: none;\n}\n.field-hint a[data-v-747b633d]:hover {\n\ttext-decoration: underline;\n}\n.last-run-row[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n\t--v-icon-color: var(--theme--foreground-subdued);\n}\n.schedule-footer[data-v-747b633d] {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tpadding-top: 4px;\n\tborder-top: 1px solid var(--theme--border-color-subdued);\n}\n\n/* ============================================================\n Dialogs — shared\n ============================================================ */\n.backup-dialog[data-v-747b633d],\n.confirm-dialog[data-v-747b633d] {\n\twidth: 560px;\n\tmax-width: calc(100vw - 32px);\n}\n.dialog-section[data-v-747b633d] {\n\tmargin-bottom: 20px;\n}\n.dialog-section[data-v-747b633d]:last-child {\n\tmargin-bottom: 0;\n}\n\n/* Dialog title with icon */\n.dialog-title-row[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n.dialog-title-icon-wrap[data-v-747b633d] {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tflex-shrink: 0;\n}\n.restore-icon-wrap[data-v-747b633d] {\n\tbackground: color-mix(in srgb, var(--theme--warning) 15%, transparent);\n\tcolor: var(--theme--warning);\n\t--v-icon-color: var(--theme--warning);\n}\n.danger-icon-wrap[data-v-747b633d] {\n\tbackground: color-mix(in srgb, var(--theme--danger) 12%, transparent);\n\tcolor: var(--theme--danger);\n\t--v-icon-color: var(--theme--danger);\n}\n\n/* ============================================================\n Restore dialog specifics\n ============================================================ */\n.restore-warning-notice[data-v-747b633d] {\n\tmargin-bottom: 0;\n}\n.restore-option-row[data-v-747b633d] {\n\tmargin-top: 16px;\n}\n.restore-progress[data-v-747b633d] {\n\tmargin-top: 16px;\n}\n\n/* Restore confirm button — warning/amber so it reads as impactful but not destructive */\n.btn-restore-confirm[data-v-747b633d] {\n\t--v-button-background-color: var(--theme--warning);\n\t--v-button-color: var(--white);\n\t--v-button-border-color: var(--theme--warning);\n\t--v-button-background-color-hover: color-mix(in srgb, var(--theme--warning) 85%, black);\n\t--v-button-color-hover: var(--white);\n\t--v-button-border-color-hover: color-mix(in srgb, var(--theme--warning) 85%, black);\n}\n\n/* ============================================================\n Radio group (New Backup dialog)\n ============================================================ */\n.radio-group[data-v-747b633d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin-top: 8px;\n}\n.radio-option[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 0;\n\tborder: 1px solid var(--theme--border-color);\n\tborder-radius: var(--theme--border-radius);\n\tpadding: 14px 16px;\n\tcursor: pointer;\n\ttransition: border-color 0.15s, background 0.15s;\n}\n.radio-option input[type="radio"][data-v-747b633d] {\n\tposition: absolute;\n\topacity: 0;\n\tpointer-events: none;\n}\n.radio-option--active[data-v-747b633d] {\n\tborder-color: var(--theme--primary);\n\tbackground: var(--theme--primary-background);\n}\n.radio-option[data-v-747b633d]:hover:not(.radio-option--active) {\n\tbackground: var(--theme--background-subdued);\n\tborder-color: var(--theme--border-color-subdued);\n}\n.radio-content[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\twidth: 100%;\n\t--v-icon-color: var(--theme--primary);\n}\n.radio-title[data-v-747b633d] {\n\tdisplay: block;\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tcolor: var(--theme--foreground);\n}\n.radio-desc[data-v-747b633d] {\n\tdisplay: block;\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n\tmargin-top: 2px;\n}\n\n/* ============================================================\n Collection selector\n ============================================================ */\n.collection-selector[data-v-747b633d] {\n\tborder: 1px solid var(--theme--border-color-subdued);\n\tborder-radius: var(--theme--border-radius);\n\toverflow: hidden;\n}\n.collection-selector-header[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tpadding: 10px 14px;\n\tbackground: var(--theme--background-subdued);\n\tborder-bottom: 1px solid var(--theme--border-color-subdued);\n}\n.selector-actions[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n.text-btn[data-v-747b633d] {\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n\tfont-size: 13px;\n\tcolor: var(--theme--primary);\n\tpadding: 0;\n}\n.text-btn[data-v-747b633d]:hover {\n\ttext-decoration: underline;\n}\n.separator[data-v-747b633d] {\n\tcolor: var(--theme--border-color);\n}\n.collections-loading[data-v-747b633d],\n.collections-empty[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 16px 14px;\n\tfont-size: 14px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.collection-list-scroll[data-v-747b633d] {\n\tmax-height: 240px;\n\toverflow-y: auto;\n}\n.collection-item[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tpadding: 8px 14px;\n\tcursor: pointer;\n\ttransition: background 0.1s;\n\tborder-bottom: 1px solid var(--theme--border-color-subdued);\n}\n.collection-item[data-v-747b633d]:last-child {\n\tborder-bottom: none;\n}\n.collection-item input[type="checkbox"][data-v-747b633d] {\n\twidth: 16px;\n\theight: 16px;\n\tflex-shrink: 0;\n\taccent-color: var(--theme--primary);\n\tcursor: pointer;\n}\n.collection-item--selected[data-v-747b633d] {\n\tbackground: var(--theme--primary-background);\n}\n.collection-item[data-v-747b633d]:hover:not(.collection-item--selected) {\n\tbackground: var(--theme--background-subdued);\n}\n.collection-item-content[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-left: 10px;\n\tflex: 1;\n}\n.collection-icon[data-v-747b633d] {\n\t--v-icon-color: var(--theme--foreground-subdued);\n}\n.collection-name[data-v-747b633d] {\n\tflex: 1;\n\tfont-size: 14px;\n\tcolor: var(--theme--foreground);\n\tfont-family: var(--theme--fonts--mono--font-family, monospace);\n}\n.collection-count[data-v-747b633d] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\twhite-space: nowrap;\n}\n\n/* ============================================================\n Toggle rows (shared between dialogs and schedule card)\n ============================================================ */\n.toggle-row[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 16px;\n\tpadding: 12px 0;\n\tborder-top: 1px solid var(--theme--border-color-subdued);\n}\n.toggle-info[data-v-747b633d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n.toggle-title[data-v-747b633d] {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--theme--foreground);\n}\n.toggle-desc[data-v-747b633d] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n}\n\n/* ============================================================\n Running / progress state\n ============================================================ */\n.running-state[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 14px;\n\tpadding: 16px;\n\tbackground: var(--theme--primary-background);\n\tborder-radius: var(--theme--border-radius);\n\tborder: 1px solid var(--theme--primary);\n\tmargin-top: 16px;\n}\n.running-text[data-v-747b633d] {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n.running-title[data-v-747b633d] {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--theme--primary);\n}\n.running-desc[data-v-747b633d] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.progress-info[data-v-747b633d] {\n\tflex: 1;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n.progress-header[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 16px;\n}\n.progress-percentage[data-v-747b633d] {\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--theme--primary);\n\tmin-width: 50px;\n\ttext-align: right;\n}\n.progress-bar[data-v-747b633d] {\n\twidth: 100%;\n}\n.progress-details[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n}\n.phase-text[data-v-747b633d] {\n\tflex: 1;\n}\n.elapsed-time[data-v-747b633d] {\n\twhite-space: nowrap;\n\tmargin-left: 16px;\n}\n.errors-section[data-v-747b633d] {\n\tmargin-top: 8px;\n}\n.errors-details[data-v-747b633d] {\n\tcursor: pointer;\n}\n.errors-summary[data-v-747b633d] {\n\tfont-size: 12px;\n\tcolor: var(--theme--danger);\n\tfont-weight: 500;\n\tuser-select: none;\n\tpadding: 8px;\n\tmargin: -8px;\n\tborder-radius: var(--theme--border-radius);\n}\n.errors-summary[data-v-747b633d]:hover {\n\tbackground: var(--theme--background-subdued);\n}\n.errors-list[data-v-747b633d] {\n\tmargin-top: 8px;\n\tpadding-left: 12px;\n\tborder-left: 2px solid var(--theme--danger);\n\tmax-height: 200px;\n\toverflow-y: auto;\n}\n.error-item[data-v-747b633d] {\n\tfont-size: 12px;\n\tcolor: var(--theme--foreground-subdued);\n\tpadding: 4px 0;\n\tword-break: break-word;\n}\n.error-collection[data-v-747b633d] {\n\tcolor: var(--theme--danger);\n\tfont-weight: 500;\n}\n.error-message[data-v-747b633d] {\n\tcolor: var(--theme--foreground-subdued);\n}\n\n/* ============================================================\n Confirm dialog details panel\n ============================================================ */\n.confirm-details[data-v-747b633d] {\n\tmargin-top: 16px;\n\tbackground: var(--theme--background-subdued);\n\tborder-radius: var(--theme--border-radius);\n\tpadding: 12px 16px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n.detail-row[data-v-747b633d] {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n}\n.detail-label[data-v-747b633d] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground-subdued);\n\tmin-width: 110px;\n\tflex-shrink: 0;\n}\n.detail-value[data-v-747b633d] {\n\tfont-size: 13px;\n\tcolor: var(--theme--foreground);\n\tword-break: break-word;\n}\n\n/* ============================================================\n Error & validation notices\n ============================================================ */\n.error-notice[data-v-747b633d] {\n\tmargin-top: 12px;\n}\n.validation-msg[data-v-747b633d] {\n\tfont-size: 12px;\n\tcolor: var(--theme--danger);\n\tpadding: 6px 14px;\n\tmargin: 0;\n}\n\n/* ============================================================\n Transitions\n ============================================================ */\n.slide-down-enter-active[data-v-747b633d],\n.slide-down-leave-active[data-v-747b633d] {\n\ttransition: opacity 0.2s ease, transform 0.2s ease, max-height 0.25s ease;\n\tmax-height: 400px;\n\toverflow: hidden;\n}\n.slide-down-enter-from[data-v-747b633d],\n.slide-down-leave-to[data-v-747b633d] {\n\topacity: 0;\n\ttransform: translateY(-6px);\n\tmax-height: 0;\n}\n\n/* ============================================================\n Responsive\n ============================================================ */\n@media (max-width: 900px) {\n.col-collections[data-v-747b633d] { display: none;\n}\n}\n@media (max-width: 768px) {\n.backup-module-content[data-v-747b633d] {\n\t\tpadding: 16px;\n\t\tgap: 28px;\n}\n.col-size[data-v-747b633d] { display: none;\n}\n.col-status[data-v-747b633d] { display: none;\n}\n\n\t/* Collapse action buttons to icon-only on small viewports — already icon-only, but tighten gap */\n.action-buttons[data-v-747b633d] {\n\t\tgap: 4px;\n}\n.backup-dialog[data-v-747b633d],\n\t.confirm-dialog[data-v-747b633d] {\n\t\twidth: 100%;\n}\n.cron-input[data-v-747b633d] {\n\t\tmax-width: 100%;\n}\n.schedule-card[data-v-747b633d] {\n\t\tpadding: 16px;\n}\n.cron-presets[data-v-747b633d] {\n\t\tgap: 6px;\n}\n.dialog-title-row[data-v-747b633d] {\n\t\tgap: 8px;\n}\n}\n',{});var Sa=Pe(Ca,[["__scopeId","data-v-747b633d"]]),Ra=Object.freeze({__proto__:null,default:Sa});export{T as displays,V as interfaces,E as layouts,F as modules,j as operations,I as panels,U as themes};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@acuity/directus-extension-acuity-backup",
3
3
  "description": "Backup Directus collections, schema, and media files to ZIP archives",
4
4
  "icon": "icon.png",
5
- "version": "2.0.0",
5
+ "version": "2.0.1",
6
6
  "license": "MIT",
7
7
  "author": {
8
8
  "name": "Acuity",