diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 373f2da..15c77c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,9 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.2', '8.3', '8.4'] + # Espelha a constraint ">=8.3 <8.6" do composer.json. 8.2 saiu + # (abaixo do piso do flarum/core 2.x) e 8.5 entrou. + php: ['8.3', '8.4', '8.5'] steps: - name: Harden runner uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 diff --git a/composer.json b/composer.json index ea9d910..c34515d 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ } ], "require": { + "php": ">=8.3 <8.6", "flarum/core": "^2.0.0" }, "require-dev": { diff --git a/js/dist/admin.js b/js/dist/admin.js index 88314fb..cedfc1b 100644 --- a/js/dist/admin.js +++ b/js/dist/admin.js @@ -1,2 +1,2 @@ -(()=>{var t={n:e=>{var s=e&&e.__esModule?()=>e.default:()=>e;return t.d(s,{a:s}),s},d:(e,s)=>{for(var a in s)t.o(s,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:s[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};(()=>{"use strict";const e=flarum.reg.get("core","admin/app");var s=t.n(e);const a=flarum.reg.get("core","common/extend"),n=flarum.reg.get("core","admin/components/ExtensionPage");var r=t.n(n);function o(t){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},o(t)}function i(t,e,s){return(e=function(t){var e=function(t){if("object"!=o(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var s=e.call(t,"string");if("object"!=o(s))return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==o(e)?e:e+""}(e))in t?Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[e]=s,t}const l=flarum.reg.get("core","common/Component");var c=t.n(l);const u=flarum.reg.get("core","common/components/Button");var p=t.n(u);const d=flarum.reg.get("core","common/components/LoadingIndicator");var h=t.n(d);const g=flarum.reg.get("core","common/components/Modal");var b=t.n(g);const f=flarum.reg.get("core","common/utils/extractText");var y=t.n(f);function k(){return(s().forum.attribute("apiUrl")||"/api").replace(/\/+$/,"")}function _(t){if(!Number.isFinite(t)||t<=0)return"0 B";const e=["B","KB","MB","GB","TB"];let s=0,a=t;for(;a>=1024&&s=100||0===s?0:1)+" "+e[s]}function v(t,e){var a,n,r;const o=null!=(a=null!=(n=null==t||null==(r=t.response)||null==(r=r.errors)||null==(r=r[0])?void 0:r.detail)?n:null==t?void 0:t.detail)?a:"string"==typeof(null==t?void 0:t.message)?t.message:void 0;return o?String(o):e||String(s().translator.trans("ramon-backup.admin.errors.generic"))}async function x(t){try{return await s().request(t)}catch(e){const a=v(e,t.fallbackMessage);if(console.error("[backup] api error",t.method,t.url,e),!1!==t.surface&&s().alerts.show({type:"error"},a),e&&"object"==typeof e&&!e.detail)try{e.detail=a}catch(t){}throw e}}flarum.reg.add("ramon-backup","admin/utils/api",{apiUrl:k,fmtBytes:_,errorDetail:v});const B=(t,e)=>s().translator.trans("ramon-backup.admin.encryption.".concat(t),null!=e?e:{});class N extends(b()){constructor(){super(...arguments),i(this,"copied",!1)}className(){return"BackupRevealModal Modal--medium"}title(){return B("reveal_modal.title")}content(){const t=this.attrs,e=t.privateKey,s=t.configKey,a="'".concat(s,"' => '").concat(e,"',");return m("div",{className:"Modal-body"},m("p",null,B("reveal_modal.intro")),m("div",{className:"Alert Alert--error"},m("strong",null,B("reveal_modal.warning_title")),m("p",null,B("reveal_modal.warning_body"))),m("label",{className:"BackupReveal-label"},B("reveal_modal.snippet_label")),m("pre",{className:"BackupReveal-snippet"},m("code",null,a)),m("div",{className:"Form-group BackupReveal-actions"},m(p(),{className:"Button",icon:"fas fa-copy",onclick:()=>this.copy(a)},this.copied?B("reveal_modal.copied"):B("reveal_modal.copy_button")),m(p(),{className:"Button Button--primary",onclick:()=>this.hide()},B("reveal_modal.close"))))}copy(t){navigator.clipboard?navigator.clipboard.writeText(t).then(()=>{this.copied=!0,m.redraw(),setTimeout(()=>{this.copied=!1,m.redraw()},2e3)}).catch(t=>{console.error("[backup] clipboard writeText failed",t),s().alerts.show({type:"error"},B("clipboard_failed"))}):s().alerts.show({type:"error"},B("clipboard_unavailable"))}}class w extends(b()){constructor(){super(...arguments),i(this,"acknowledged",!1),i(this,"submitting",!1)}className(){return"BackupRegenerateModal Modal--medium"}title(){return B("regenerate_modal.title")}content(){return m("div",{className:"Modal-body"},m("div",{className:"Alert Alert--error"},m("p",null,B("regenerate_modal.warning"))),m("label",{className:"BackupRegenerate-confirm"},m("input",{type:"checkbox",checked:this.acknowledged,onchange:t=>{this.acknowledged=t.target.checked}})," ",B("regenerate_modal.acknowledge")),m("div",{className:"Form-group"},m(p(),{className:"Button Button--primary",loading:this.submitting,disabled:!this.acknowledged||this.submitting,onclick:()=>this.submit()},B("regenerate_modal.submit"))))}async submit(){this.submitting=!0,m.redraw();try{await this.attrs.onConfirm(),this.hide()}catch(t){this.submitting=!1,m.redraw()}}}class E extends(c()){constructor(){super(...arguments),i(this,"status",null),i(this,"loadState","loading"),i(this,"loadError",null),i(this,"publicCopied",!1)}oninit(t){super.oninit(t),this.refresh()}view(){return m("section",{className:"BackupEncryptionCard"},m("header",null,m("h3",null,B("section_title")),m("p",{className:"helpText"},B("section_help"))),"loading"===this.loadState&&m(h(),null),"error"===this.loadState&&m("div",{className:"Alert Alert--error BackupEncryption-loadError"},m("p",null,B("status.load_failed")),this.loadError&&m("p",{className:"helpText"},m("code",null,this.loadError)),m(p(),{className:"Button",icon:"fas fa-rotate",onclick:()=>this.refresh()},B("status.retry"))),"ok"===this.loadState&&this.body())}body(){if(!this.status)return m("p",{className:"helpText"},B("status.unknown"));const t=this.status;return t.available?m("[",null,m("div",{className:"BackupEncryption-statusRow"},this.statusBadge("public",t.has_public_key),this.statusBadge("private",t.private_key_present)),t.healthy&&m("div",{className:"Alert Alert--success"},B("status.healthy")),!t.has_public_key&&!t.private_key_present&&m("div",null,m("p",{className:"helpText"},B("status.not_setup")),m(p(),{className:"Button Button--primary",icon:"fas fa-key",onclick:()=>this.generate(!1)},B("actions.generate"))),t.has_public_key&&t.private_key_present&&!1===t.keys_match&&m("div",{className:"Alert Alert--error"},m("strong",null,B("status.mismatch_title")),m("p",null,B("status.mismatch_body")),m("p",null,m("code",null,"'",t.config_key,"'"))),t.has_public_key&&!t.private_key_present&&m("div",{className:"Alert Alert--error"},m("strong",null,B("status.private_missing_title")),m("p",null,B("status.private_missing_body")),m("p",null,m("code",null,"'",t.config_key,"'"))),t.has_public_key&&this.publicKeyPanel(t.public_key||"",t.healthy)):m("div",{className:"Alert Alert--error"},B("status.libsodium_missing"))}publicKeyPanel(t,e){return m("div",{className:"BackupEncryption-publicKey"},m("label",null,B("public_key.label")),m("div",{className:"BackupEncryption-publicKeyRow"},m("pre",null,m("code",null,t)),m(p(),{className:"Button Button--icon",icon:"fas fa-copy",title:y()(B("public_key.copy_title")),onclick:()=>this.copyPublic(t)},this.publicCopied?y()(B("public_key.copied")):"")),m("p",{className:"helpText"},B(e?"public_key.help_healthy":"public_key.help_broken")),m(p(),{className:"Button Button--danger",icon:"fas fa-rotate",onclick:()=>this.openRegenerate()},B("public_key.remove_button")))}statusBadge(t,e){return m("div",{className:"BackupEncryption-badge BackupEncryption-badge--".concat(e?"ok":"missing")},m("i",{className:"icon fas fa-".concat(e?"check":"times")}),m("span",null,B("status.".concat(t,"_key_label"))),m("span",{className:"BackupEncryption-badgeState"},B("status.".concat(e?"present":"absent"))))}copyPublic(t){t&&(navigator.clipboard?navigator.clipboard.writeText(t).then(()=>{this.publicCopied=!0,m.redraw(),setTimeout(()=>{this.publicCopied=!1,m.redraw()},2e3)}).catch(t=>{console.error("[backup] clipboard writeText failed",t),s().alerts.show({type:"error"},B("clipboard_failed"))}):s().alerts.show({type:"error"},B("clipboard_unavailable")))}refresh(){return this.loadState="loading",this.loadError=null,x({method:"GET",url:"".concat(k(),"/backup/encryption/status"),surface:!1}).then(t=>{this.status=t,this.loadState="ok"}).catch(t=>{this.status=null,this.loadState="error",this.loadError=v(t)}).then(()=>{m.redraw()})}async generate(t){try{const e=await x({method:"POST",url:"".concat(k(),"/backup/encryption/generate-keypair"),body:{acknowledge_loss:t},surface:!1});await this.refresh(),s().modal.show(N,{privateKey:e.private_key,configKey:e.config_key})}catch(t){throw s().alerts.show({type:"error"},v(t,String(B("actions.generate_failed")))),t}}openRegenerate(){s().modal.show(w,{onConfirm:()=>this.generate(!0)})}}flarum.reg.add("ramon-backup","admin/components/EncryptionCard",E);const S=flarum.reg.get("core","common/helpers/humanTime");var I=t.n(S);const O={mysql:"MySQL",mariadb:"MariaDB",postgres:"PostgreSQL",sqlite:"SQLite"},P=(t,e)=>s().translator.trans("ramon-backup.admin.list.".concat(t),null!=e?e:{});class T extends(c()){view(t){const e=t.attrs,s=e.backups,a=e.onDelete;return s.length?m("table",{className:"BackupList Table"},m("thead",null,m("tr",null,m("th",null,P("col_when")),m("th",null,P("col_size")),m("th",null,P("col_contents")),m("th",null,P("col_status")),m("th",null))),m("tbody",null,s.map(t=>m("tr",{key:t.id,className:"BackupList-row"},m("td",null,m("div",{className:"BackupList-when"},t.created_at?I()(new Date(t.created_at)):"—"),m("div",{className:"BackupList-filename"},t.filename),t.target_dialect&&m("div",{className:"BackupList-target BackupList-target--".concat(t.target_dialect),title:String(P("target_tooltip",{engine:O[t.target_dialect]||t.target_dialect}))},m("i",{className:"icon fas fa-arrow-right-arrow-left"})," ",P("target_for",{engine:O[t.target_dialect]||t.target_dialect}))),m("td",null,_(t.size_bytes)),m("td",null,t.contents.map(t=>m("span",{className:"BackupList-tag BackupList-tag--".concat(t)},P("content_"+t)))),m("td",null,t.encrypted?m("span",{className:"BackupList-encryption BackupList-encryption--on"},m("i",{className:"icon fas fa-lock"})," ",P("encrypted")):m("span",{className:"BackupList-encryption BackupList-encryption--off"},m("i",{className:"icon fas fa-lock-open"})," ",P("plain"))),m("td",{className:"BackupList-actions"},m("a",{className:"Button Button--icon",href:"".concat(k(),"/backup/backups/").concat(t.id,"/download"),target:"_blank",title:String(P("download_title"))},m("i",{className:"icon fas fa-download"})),m(p(),{className:"Button Button--icon Button--danger",icon:"fas fa-trash",title:P("delete_title"),onclick:()=>a(t.id)})))))):m("p",{className:"BackupList-empty helpText"},P("empty"))}}function j(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,a=Array(e);ss().translator.trans("ramon-backup.admin.export_modal.".concat(t),null!=e?e:{});class D extends(b()){constructor(){super(...arguments),i(this,"stage","form"),i(this,"includeDb",!0),i(this,"includeAssets",!0),i(this,"includeStorage",!1),i(this,"includeExtensions",!1),i(this,"extensionsLoading",!1),i(this,"extensionsLoaded",!1),i(this,"extensions",[]),i(this,"extensionSelected",{}),i(this,"encryptionEnabled",!1),i(this,"encryptionUseExternal",!1),i(this,"externalPublicKey",""),i(this,"targetDialect",""),i(this,"starting",!1),i(this,"jobId",null),i(this,"status",null),i(this,"polling",!1)}className(){return"BackupExportModal Modal--medium"}title(){return M("title")}content(){return"form"===this.stage?this.formContent():this.progressContent()}formContent(){return m("div",{className:"Modal-body"},m("p",{className:"helpText"},M("intro")),m("fieldset",{className:"BackupExport-fieldset"},m("legend",null,M("contents_title")),this.checkbox("db",()=>this.includeDb,t=>this.includeDb=t),this.checkbox("assets",()=>this.includeAssets,t=>this.includeAssets=t),this.checkbox("storage",()=>this.includeStorage,t=>this.includeStorage=t),this.checkbox("extensions",()=>this.includeExtensions,t=>{this.includeExtensions=t,t&&!this.extensionsLoaded&&this.loadExtensions()}),this.includeExtensions&&this.extensionList()),this.includeDb&&m("fieldset",{className:"BackupExport-fieldset"},m("legend",null,M("target_title")),m("p",{className:"helpText"},M("target_help")),m("select",{className:"FormControl BackupExport-targetSelect",value:this.targetDialect,onchange:t=>{this.targetDialect=t.target.value}},m("option",{value:""},M("target_same")),m("option",{value:"mysql"},M("target_mysql")),m("option",{value:"mariadb"},M("target_mariadb")),m("option",{value:"postgres"},M("target_postgres")),m("option",{value:"sqlite"},M("target_sqlite")))),m("fieldset",{className:"BackupExport-fieldset"},m("legend",null,M("encryption_title")),m("label",{className:"BackupExport-checkbox"},m("input",{type:"checkbox",checked:this.encryptionEnabled,onchange:t=>{this.encryptionEnabled=t.target.checked}})," ",m("span",null,M("encryption_enable"))),m("p",{className:"helpText"},M("encryption_help")),this.encryptionEnabled&&m("[",null,m("label",{className:"BackupExport-checkbox"},m("input",{type:"checkbox",checked:this.encryptionUseExternal,onchange:t=>{this.encryptionUseExternal=t.target.checked}})," ",m("span",null,M("encryption_external"))),this.encryptionUseExternal&&m("[",null,m("p",{className:"helpText"},M("encryption_external_help")),m("textarea",{className:"FormControl BackupExport-keyInput",rows:3,placeholder:"base64 public key",value:this.externalPublicKey,oninput:t=>{this.externalPublicKey=t.target.value}})))),m("div",{className:"Form-group"},m(p(),{className:"Button Button--primary",loading:this.starting,disabled:this.starting||!this.canStart(),onclick:()=>this.start()},M("start_button"))))}extensionList(){if(this.extensionsLoading)return m("div",{className:"BackupExport-extLoading"},m(h(),null));if(!this.extensions.length)return m("p",{className:"helpText BackupExport-extEmpty"},M("extensions_none"));const t={workbench:[],vendor:[],unknown:[]};for(const s of this.extensions){var e;null==(e=t[s.location])||e.push(s)}return m("div",{className:"BackupExport-extList"},m("div",{className:"BackupExport-extActions"},m("button",{type:"button",className:"BackupExport-extLink",onclick:()=>this.toggleAllExtensions(!0)},M("extensions_select_all")),m("span",null," · "),m("button",{type:"button",className:"BackupExport-extLink",onclick:()=>this.toggleAllExtensions(!1)},M("extensions_select_none"))),["workbench","vendor","unknown"].filter(e=>t[e].length>0).map(e=>m("div",{className:"BackupExport-extGroup",key:e},m("div",{className:"BackupExport-extGroupHeader"},M("extensions_group_"+e)," ",m("span",{className:"helpText"},"(",t[e].length,")")),t[e].map(t=>m("label",{className:"BackupExport-extRow",key:t.id},m("input",{type:"checkbox",checked:!!this.extensionSelected[t.id],onchange:e=>{this.extensionSelected[t.id]=e.target.checked}})," ",m("span",{className:"BackupExport-extTitle"},t.title)," ",m("code",{className:"BackupExport-extName"},t.name||t.id),m("span",{className:"BackupExport-extTag BackupExport-extTag--".concat(t.location)},M("extensions_tag_"+t.location)))))))}toggleAllExtensions(t){for(const e of this.extensions)this.extensionSelected[e.id]=t}async loadExtensions(){this.extensionsLoading=!0;try{const t=await x({method:"GET",url:"".concat(k(),"/backup/extensions"),surface:!1});this.extensions=t.extensions||[],this.extensionsLoaded=!0;for(const t of this.extensions)this.extensionSelected[t.id]=!0}catch(t){s().alerts.show({type:"error"},v(t,String(M("extensions_load_failed"))))}finally{this.extensionsLoading=!1,m.redraw()}}checkbox(t,e,s){return m("label",{className:"BackupExport-checkbox"},m("input",{type:"checkbox",checked:e(),onchange:t=>{s(t.target.checked)}})," ",m("span",{className:"BackupExport-checkbox-label"},M("content_"+t)),m("span",{className:"BackupExport-checkbox-help helpText"},M("content_"+t+"_help")))}canStart(){return!(!(this.includeDb||this.includeAssets||this.includeStorage||this.includeExtensions)||this.encryptionEnabled&&this.encryptionUseExternal&&!this.externalPublicKey.trim())}progressContent(){var t,e,s;const a=this.status;if(!a)return m(h(),null);const n="done"===a.phase,r="error"===a.phase,o=Math.max(0,Math.min(100,(null==(t=a.progress)?void 0:t.percent)||0));return m("div",{className:"Modal-body BackupExport-progress"},m("div",{className:"BackupExport-status BackupExport-status--".concat(a.phase)},m("strong",null,M("phase_"+a.phase)),m("p",null,a.message)),!r&&m("[",null,m("div",{className:"BackupExport-bar"},m("div",{className:"BackupExport-bar-fill",style:{width:"".concat(n?100:o,"%")},role:"progressbar","aria-valuenow":o,"aria-valuemin":0,"aria-valuemax":100})),m("div",{className:"BackupExport-stats"},m("span",null,_(a.progress.processed_bytes)," /"," ",_(a.progress.total_bytes||a.progress.processed_bytes)),a.progress.total_files>0&&m("span",null,M("files_count",{done:a.progress.processed_files,total:a.progress.total_files})))),(null!=(e=null==(s=a.warnings)?void 0:s.length)?e:0)>0&&m("div",{className:"BackupExport-warnings",role:"alert"},m("div",{className:"BackupExport-warnings-title"},m("i",{className:"icon fas fa-triangle-exclamation"})," ",M("warnings_title",{count:a.warnings.length})),m("p",{className:"helpText"},M("warnings_help")),m("ul",{className:"BackupExport-warnings-list"},a.warnings.map((t,e)=>m("li",{key:e},t)))),m("div",{className:"Form-group BackupExport-progress-actions"},!n&&!r&&m(p(),{className:"Button",onclick:()=>this.cancel()},M("cancel_button")),(n||r)&&m(p(),{className:"Button Button--primary",onclick:()=>this.close()},M("close_button"))))}async start(){this.starting=!0;try{let t=!1;this.includeExtensions&&(t=!this.extensionsLoaded||Object.entries(this.extensionSelected).filter(t=>L(t,2)[1]).map(t=>L(t,1)[0]));const e=await x({method:"POST",url:"".concat(k(),"/backup/exports"),body:{contents:{db:this.includeDb,assets:this.includeAssets,storage:this.includeStorage,extensions:t},encryption:{enabled:this.encryptionEnabled,public_key:this.encryptionUseExternal?this.externalPublicKey.trim():null},target_dialect:this.targetDialect||null},surface:!1});this.jobId=e.job_id,this.stage="progress",this.status={phase:e.phase,message:e.message,progress:{total_bytes:0,processed_bytes:0,total_files:0,processed_files:0,percent:0}},this.starting=!1,m.redraw(),this.pump()}catch(t){this.starting=!1,s().alerts.show({type:"error"},v(t,String(M("start_failed")))),m.redraw()}}async pump(){if(!this.polling&&this.jobId){this.polling=!0;try{for(var t;this.jobId&&this.status&&"done"!==this.status.phase&&"error"!==this.status.phase;)try{const t=await x({method:"POST",url:"".concat(k(),"/backup/exports/").concat(this.jobId,"/tick"),surface:!1});this.status=t,m.redraw()}catch(t){const e=v(t,String(M("phase_error_network")));this.status=C(C({},this.status),{},{phase:"error",message:e}),m.redraw();break}"done"===(null==(t=this.status)?void 0:t.phase)&&(s().alerts.show({type:"success"},M("completed")),this.attrs.onComplete())}finally{this.polling=!1}}}async cancel(){if(this.jobId){try{await x({method:"DELETE",url:"".concat(k(),"/backup/exports/").concat(this.jobId),surface:!1})}catch(t){console.warn("[backup] export cancel failed",t),s().alerts.show({type:"warning"},M("cancel_failed_warn"))}this.close()}}close(){this.jobId=null,this.hide()}}function R(t,e){var s=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),s.push.apply(s,a)}return s}function K(t){for(var e=1;es().translator.trans("ramon-backup.admin.import_modal.".concat(t),null!=e?e:{});class z extends(b()){constructor(){super(...arguments),i(this,"stage","upload"),i(this,"file",null),i(this,"uploading",!1),i(this,"uploadProgress",0),i(this,"uploadIndeterminate",!1),i(this,"uploadError",null),i(this,"inspect",null),i(this,"privateKey",""),i(this,"confirmReplace",!1),i(this,"starting",!1),i(this,"sectionDb",!1),i(this,"sectionAssets",!1),i(this,"sectionStorage",!1),i(this,"sectionExtensions",!1),i(this,"extensionsByName",{}),i(this,"status",null),i(this,"polling",!1)}className(){return"BackupImportModal Modal--medium"}title(){var t;return"progress"===this.stage&&"done"===(null==(t=this.status)?void 0:t.phase)?this.sectionDb?F("logout_title"):F("done_title"):F("title")}content(){return"upload"===this.stage?this.uploadContent():"configure"===this.stage?this.configureContent():this.progressContent()}uploadContent(){return m("div",{className:"Modal-body"},m("div",{className:"Alert Alert--warning"},m("strong",null,F("warning_title")),m("p",null,F("warning_body"))),m("label",{className:"BackupImport-fileLabel"},m("input",{type:"file",accept:".flarum",onchange:t=>{var e;const s=(null==(e=t.target.files)?void 0:e[0])||null;this.file=s}}),this.file?m("span",null,this.file.name," ",m("span",{className:"helpText"},"(",_(this.file.size),")")):m("span",{className:"helpText"},F("choose_file"))),this.uploadError&&m("div",{className:"Alert Alert--error"},this.uploadError),this.uploading&&m("div",{className:"BackupImport-uploadProgress"},m("div",{className:"BackupImport-bar"},m("div",{className:"BackupImport-bar-fill"+(this.uploadIndeterminate?" BackupImport-bar-fill--indeterminate":""),style:this.uploadIndeterminate?void 0:{width:"".concat(Math.max(2,this.uploadProgress),"%")}})),m("div",{className:"BackupImport-uploadStatus helpText"},this.uploadIndeterminate?F("inspecting_archive"):F("uploading_pct",{pct:this.uploadProgress}))),m("div",{className:"Form-group"},m(p(),{className:"Button Button--primary",loading:this.uploading,disabled:this.uploading||!this.file,onclick:()=>this.upload()},F("upload_button"))))}async upload(){if(this.file){this.uploading=!0,this.uploadProgress=0,this.uploadIndeterminate=!1,this.uploadError=null;try{var t;const e=await this.uploadWithProgress(this.file,t=>{this.uploadProgress=t,t>=100&&(this.uploadIndeterminate=!0),m.redraw()});this.inspect=e;const s=e.meta.contents||[];this.sectionDb=s.includes("db"),this.sectionAssets=s.includes("assets"),this.sectionStorage=s.includes("storage"),this.sectionExtensions=s.includes("extensions");const a=(null==(t=e.meta.manifest)?void 0:t.extensions)||[];this.extensionsByName={};for(const t of a){const e="string"==typeof t?t:t.id;e&&(this.extensionsByName[e]=!0)}this.stage="configure"}catch(t){console.error("[backup] archive upload failed",t),this.uploadError=v(t,String(F("upload_failed")))}finally{this.uploading=!1,m.redraw()}}}async uploadWithProgress(t,e){const s=await x({method:"POST",url:"".concat(k(),"/backup/imports"),body:{filename:t.name,size:t.size},surface:!1}),a=s.job_id,n=s.chunk_size>0?s.chunk_size:4194304;let r=0;for(;r2)throw t;await new Promise(t=>setTimeout(t,750*i))}r=s,e(Math.min(99,Math.round(r/t.size*100)))}return e(100),x({method:"POST",url:"".concat(k(),"/backup/imports/").concat(a,"/inspect"),surface:!1})}sendChunk(t,e,a){return new Promise((n,r)=>{var o;const i=new XMLHttpRequest;let l=Date.now();const c=setInterval(()=>{Date.now()-l>6e4&&(clearInterval(c),i.abort())},5e3),m=()=>clearInterval(c);i.upload.addEventListener("progress",()=>{l=Date.now()}),i.addEventListener("load",()=>{if(m(),i.status>=200&&i.status<300)n();else{let e;try{var t;e=null==(t=JSON.parse(i.responseText))||null==(t=t.errors)||null==(t=t[0])?void 0:t.detail}catch(t){}r({detail:e||"".concat(i.status," ").concat(i.statusText)})}}),i.addEventListener("error",()=>{m(),r({detail:String(F("upload_failed"))})}),i.addEventListener("abort",()=>{m(),r({detail:String(F("upload_idle_timeout"))})}),i.open("POST","".concat(k(),"/backup/imports/").concat(t,"/chunk"),!0),i.withCredentials=!0,i.setRequestHeader("Content-Type","application/octet-stream"),i.setRequestHeader("X-Chunk-Offset",String(e));const u=null==(o=s().session)?void 0:o.csrfToken;u&&i.setRequestHeader("X-CSRF-Token",u),i.send(a)})}configureContent(){const t=this.inspect;return m("div",{className:"Modal-body"},m("h4",null,F("inspect_title")),m("dl",{className:"BackupImport-meta"},t.meta.created_at&&m("[",null,m("dt",null,F("meta_when")),m("dd",null,t.meta.created_at)),t.meta.flarum_version&&m("[",null,m("dt",null,F("meta_flarum")),m("dd",null,t.meta.flarum_version)),t.meta.contents&&m("[",null,m("dt",null,F("meta_contents")),m("dd",null,t.meta.contents.join(", "))),t.meta.source_url&&m("[",null,m("dt",null,F("meta_source_url")),m("dd",null,m("code",null,t.meta.source_url))),m("dt",null,F("meta_size")),m("dd",null,_(t.size))),m("div",{className:"Alert Alert--info BackupImport-urlNote"},m("i",{className:"icon fas fa-info-circle"})," ",F("url_rewrite_note")),this.selectionFieldset(t),t.is_encrypted&&m("fieldset",{className:"BackupImport-fieldset"},m("legend",null,F("key_title")),m("p",{className:"helpText"},F("key_help")),m("textarea",{className:"FormControl BackupImport-keyInput",rows:3,placeholder:"base64 private key",value:this.privateKey,oninput:t=>{this.privateKey=t.target.value}}),m("p",{className:"helpText BackupImport-keyHint"},F("key_hint_local"))),m("div",{className:"Alert Alert--error BackupImport-confirmAlert"},m("strong",null,F("confirm_title")),m("p",null,F("confirm_body")),m("label",{className:"BackupImport-confirm"},m("input",{type:"checkbox",checked:this.confirmReplace,onchange:t=>{this.confirmReplace=t.target.checked}})," ",m("span",null,F("confirm_check")))),m("div",{className:"Form-group"},m(p(),{className:"Button Button--primary",loading:this.starting,disabled:this.starting||!this.confirmReplace,onclick:()=>this.startRestore()},F("start_button"))))}selectionFieldset(t){const e=t.meta.contents||[],s=t.meta.manifest||{},a=e.includes("db"),n=e.includes("assets"),r=e.includes("storage"),o=e.includes("extensions"),i=(s.extensions||[]).map(t=>"string"==typeof t?{id:t,location:"workbench"}:t);return m("fieldset",{className:"BackupImport-fieldset"},m("legend",null,F("selection_title")),m("p",{className:"helpText"},F("selection_help")),a&&this.sectionRow("db",this.sectionDb,t=>this.sectionDb=t),n&&this.sectionRow("assets",this.sectionAssets,t=>this.sectionAssets=t,s.asset_count),r&&this.sectionRow("storage",this.sectionStorage,t=>this.sectionStorage=t,s.storage_count),o&&m("[",null,this.sectionRow("extensions",this.sectionExtensions,t=>{this.sectionExtensions=t;for(const e of i)this.extensionsByName[e.id]=t},s.extension_count),this.sectionExtensions&&s.has_composer&&m("div",{className:"BackupImport-composerNote helpText"},m("i",{className:"icon fas fa-cube"})," ",F("extensions_composer_note")),this.sectionExtensions&&i.length>0&&m("div",{className:"BackupImport-extList"},i.map(t=>m("label",{className:"BackupImport-extRow",key:t.id},m("input",{type:"checkbox",checked:!!this.extensionsByName[t.id],onchange:e=>{this.extensionsByName[t.id]=e.target.checked}})," ",m("span",{className:"BackupImport-extTitle"},t.title||t.id)," ",t.name&&t.name!==t.id&&m("code",{className:"BackupImport-extName"},t.name),t.location&&m("span",{className:"BackupImport-extTag BackupImport-extTag--".concat(t.location)},F("extensions_tag_"+t.location)))))))}sectionRow(t,e,s,a){return m("label",{className:"BackupImport-sectionRow"},m("input",{type:"checkbox",checked:e,onchange:t=>s(t.target.checked)})," ",m("span",{className:"BackupImport-sectionLabel"},F("section_"+t)),void 0!==a&&a>0&&m("span",{className:"BackupImport-sectionCount helpText"}," ","(",F("section_count",{count:a}),")"))}buildSelection(){const t=Object.entries(this.extensionsByName),e=t.length>0&&t.every(t=>L(t,2)[1]),s=!!this.sectionExtensions&&(!!e||t.filter(t=>L(t,2)[1]).map(t=>L(t,1)[0]));return{db:this.sectionDb,assets:this.sectionAssets,storage:this.sectionStorage,extensions:s}}async startRestore(){if(this.inspect){this.starting=!0;try{const t=await x({method:"POST",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id,"/start"),surface:!1,body:{private_key:this.privateKey.trim()||null,confirm_replace:this.confirmReplace,selection:this.buildSelection()}});this.stage="progress",this.status={phase:t.phase,message:t.message,progress:{total_bytes:this.inspect.size,processed_bytes:0,extracted_entries:0,restored_statements:0,percent:0}},m.redraw(),this.pump()}catch(t){s().alerts.show({type:"error"},v(t,String(F("start_failed"))))}finally{this.starting=!1}}}progressContent(){var t;const e=this.status;if(!e)return m(h(),null);if("done"===e.phase)return this.completedContent();const s="error"===e.phase,a=Math.max(0,Math.min(100,(null==(t=e.progress)?void 0:t.percent)||0));return m("div",{className:"Modal-body BackupImport-progress"},m("div",{className:"BackupImport-status BackupImport-status--".concat(e.phase)},m("strong",null,F("phase_"+e.phase)),m("p",null,e.message)),!s&&m("div",{className:"BackupImport-bar"},m("div",{className:"BackupImport-bar-fill",style:{width:"".concat(a,"%")}})),m("div",{className:"Form-group BackupImport-progress-actions"},!s&&m(p(),{className:"Button",onclick:()=>this.cancel()},F("cancel_button")),s&&m(p(),{className:"Button Button--primary",onclick:()=>this.close()},F("close_button"))))}completedContent(){return this.sectionDb?m("div",{className:"Modal-body BackupImport-completed BackupImport-completed--logout"},m("div",{className:"BackupImport-completedIcon"},m("i",{className:"fas fa-right-from-bracket"})),m("h3",{className:"BackupImport-completedTitle"},F("logout_title")),m("p",{className:"BackupImport-completedBody"},F("logout_body")),m("ol",{className:"BackupImport-completedSteps"},m("li",null,F("logout_step_reload")),m("li",null,F("logout_step_login"))),m(p(),{className:"Button Button--primary BackupImport-completedAction",icon:"fas fa-rotate",onclick:()=>window.location.reload()},F("logout_button"))):m("div",{className:"Modal-body BackupImport-completed"},m("div",{className:"BackupImport-completedIcon BackupImport-completedIcon--success"},m("i",{className:"fas fa-circle-check"})),m("h3",{className:"BackupImport-completedTitle"},F("done_title")),m("p",{className:"BackupImport-completedBody"},F("done_body")),m(p(),{className:"Button Button--primary",onclick:()=>this.close()},F("close_button")))}async pump(){if(!this.polling&&this.inspect){this.polling=!0;try{for(var t;this.inspect&&this.status&&"done"!==this.status.phase&&"error"!==this.status.phase;)try{const t=await x({method:"POST",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id,"/tick"),surface:!1,body:{private_key:this.privateKey.trim()||null}});this.status=t,m.redraw()}catch(t){console.error("[backup] import tick failed",t);const e=v(t,String(F("phase_error_network")));this.status=K(K({},this.status),{},{phase:"error",message:e}),m.redraw();break}"done"===(null==(t=this.status)?void 0:t.phase)&&(s().alerts.show({type:"success"},F("completed")),this.sectionDb||this.attrs.onComplete())}finally{this.polling=!1}}}async cancel(){if(this.inspect){try{await x({method:"DELETE",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id),surface:!1})}catch(t){console.error("[backup] import cancel failed",t),s().alerts.show({type:"warning"},F("cancel_failed_warn"))}this.close()}}close(){this.hide()}}function q(t,e){var s=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),s.push.apply(s,a)}return s}function U(t){for(var e=1;ethis.decide(!0)},a),m(p(),{className:"Button",onclick:()=>this.decide(!1)},n)))}decide(t){var e;this.resolved||(this.resolved=!0,null==(e=t?this.attrs.onConfirm:this.attrs.onCancel)||e(),this.hide())}onbeforeremove(t){var e,s;return this.resolved||(this.resolved=!0,null==(e=(s=this.attrs).onCancel)||e.call(s)),super.onbeforeremove(t)}}flarum.reg.add("ramon-backup","admin/components/ConfirmModal",G);const H=t=>s().translator.trans("ramon-backup.admin.errors.".concat(t));class Q extends(c()){constructor(){super(...arguments),i(this,"failed",!1),i(this,"lastError",null)}view(t){if(this.failed){const e=()=>{this.failed=!1,this.lastError=null,m.redraw()};return t.attrs.fallback?t.attrs.fallback(this.lastError,e):m("div",{className:"Alert Alert--error BackupErrorBoundary"},m("strong",null,H("boundary_title")),m("p",null,H("boundary_body")),m("button",{type:"button",className:"Button",onclick:e},H("boundary_retry")))}try{return t.children}catch(a){var e,s;return this.failed=!0,this.lastError=a,null==(e=(s=t.attrs).onError)||e.call(s,a),console.error("[backup] render boundary caught",a),null}}}flarum.reg.add("ramon-backup","admin/utils/errorBoundary",{ErrorBoundary:Q});const X=(t,e)=>s().translator.trans("ramon-backup.admin.".concat(t),null!=e?e:{});class W extends(c()){constructor(){super(...arguments),i(this,"listState","loading"),i(this,"listError",null),i(this,"backups",[])}oninit(t){super.oninit(t),this.refresh()}view(){return m("div",{className:"BackupPanel"},m(Q,{onError:t=>console.error("[backup] panel render",t)},m("section",{className:"BackupPanel-actions"},m("h3",null,X("panel.actions_title")),m("p",{className:"helpText"},X("panel.actions_help")),m("div",{className:"BackupPanel-actionButtons"},m(p(),{className:"Button Button--primary",icon:"fas fa-download",onclick:()=>this.openExport()},X("panel.create_button")),m(p(),{className:"Button",icon:"fas fa-upload",onclick:()=>this.openImport()},X("panel.import_button")))),m(E,null),m("section",{className:"BackupPanel-list"},m("h3",null,X("panel.list_title")),this.renderList())))}renderList(){return"loading"===this.listState?m(h(),null):"error"===this.listState?m("div",{className:"Alert Alert--error BackupPanel-listError"},m("p",null,X("list.load_failed")),this.listError&&m("p",{className:"helpText"},m("code",null,this.listError)),m(p(),{className:"Button",icon:"fas fa-rotate",onclick:()=>this.refresh()},X("list.retry"))):m(T,{backups:this.backups,onDelete:t=>this.delete(t),onRefresh:()=>this.refresh()})}refresh(){return this.listState="loading",this.listError=null,x({method:"GET",url:"".concat(k(),"/backup/backups"),surface:!1}).then(t=>{this.backups=t.backups||[],this.listState="ok"}).catch(t=>{this.backups=[],this.listState="error",this.listError=v(t)}).then(()=>{m.redraw()})}openExport(){s().modal.show(D,{onComplete:()=>this.refresh()})}openImport(){s().modal.show(z,{onComplete:()=>this.refresh()})}async delete(t){var e;if(await(e={title:X("list.confirm_delete_title"),body:X("list.confirm_delete"),confirmLabel:X("list.delete_title"),danger:!0},new Promise(t=>{let a=!1;const n=e=>{a||(a=!0,t(e))};s().modal.show(G,U(U({},e),{},{onConfirm:()=>n(!0),onCancel:()=>n(!1)}))})))try{await x({method:"DELETE",url:"".concat(k(),"/backup/backups/").concat(t),surface:!1,fallbackMessage:String(X("list.delete_failed"))}),s().alerts.show({type:"success"},X("list.deleted")),this.refresh()}catch(t){s().alerts.show({type:"error"},v(t,String(X("list.delete_failed"))))}}}flarum.reg.add("ramon-backup","admin/components/BackupPanel",W);const $="ramon-backup";(0,a.override)(r().prototype,"submitButton",function(t){return this.extension&&this.extension.id===$?null:t()}),s().initializers.add($,()=>{s().registry.for($).registerSetting(()=>m(W,null),100,"ramon-backup.panel").registerPermission({icon:"fas fa-file-archive",label:s().translator.trans("ramon-backup.admin.permissions.manage_label"),permission:"backup.manage"},"moderate")})})(),module.exports={}})(); +(()=>{const t={n:e=>{const s=e&&e.__esModule?()=>e.default:()=>e;return t.d(s,{a:s}),s},d:(e,s)=>{if(Array.isArray(s))for(var a=0;aObject.prototype.hasOwnProperty.call(t,e)};(()=>{"use strict";const e=flarum.reg.get("core","admin/app");var s=t.n(e);const a=flarum.reg.get("core","common/extend"),n=flarum.reg.get("core","admin/components/ExtensionPage");var r=t.n(n);function o(t){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},o(t)}function i(t,e,s){return(e=function(t){var e=function(t){if("object"!=o(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var s=e.call(t,"string");if("object"!=o(s))return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==o(e)?e:e+""}(e))in t?Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[e]=s,t}const l=flarum.reg.get("core","common/Component");var c=t.n(l);const u=flarum.reg.get("core","common/components/Button");var p=t.n(u);const d=flarum.reg.get("core","common/components/LoadingIndicator");var h=t.n(d);const g=flarum.reg.get("core","common/components/Modal");var b=t.n(g);const f=flarum.reg.get("core","common/utils/extractText");var y=t.n(f);function k(){return(s().forum.attribute("apiUrl")||"/api").replace(/\/+$/,"")}function _(t){if(!Number.isFinite(t)||t<=0)return"0 B";const e=["B","KB","MB","GB","TB"];let s=0,a=t;for(;a>=1024&&s=100||0===s?0:1)+" "+e[s]}function v(t,e){var a,n,r;const o=null!=(a=null!=(n=null==t||null==(r=t.response)||null==(r=r.errors)||null==(r=r[0])?void 0:r.detail)?n:null==t?void 0:t.detail)?a:"string"==typeof(null==t?void 0:t.message)?t.message:void 0;return o?String(o):e||String(s().translator.trans("ramon-backup.admin.errors.generic"))}async function x(t){try{return await s().request(t)}catch(e){const a=v(e,t.fallbackMessage);if(console.error("[backup] api error",t.method,t.url,e),!1!==t.surface&&s().alerts.show({type:"error"},a),e&&"object"==typeof e&&!e.detail)try{e.detail=a}catch(t){}throw e}}flarum.reg.add("ramon-backup","admin/utils/api",{apiUrl:k,fmtBytes:_,errorDetail:v});const B=(t,e)=>s().translator.trans("ramon-backup.admin.encryption.".concat(t),null!=e?e:{});class N extends(b()){constructor(){super(...arguments),i(this,"copied",!1)}className(){return"BackupRevealModal Modal--medium"}title(){return B("reveal_modal.title")}content(){const t=this.attrs,e=t.privateKey,s=t.configKey,a="'".concat(s,"' => '").concat(e,"',");return m("div",{className:"Modal-body"},m("p",null,B("reveal_modal.intro")),m("div",{className:"Alert Alert--error"},m("strong",null,B("reveal_modal.warning_title")),m("p",null,B("reveal_modal.warning_body"))),m("label",{className:"BackupReveal-label"},B("reveal_modal.snippet_label")),m("pre",{className:"BackupReveal-snippet"},m("code",null,a)),m("div",{className:"Form-group BackupReveal-actions"},m(p(),{className:"Button",icon:"fas fa-copy",onclick:()=>this.copy(a)},this.copied?B("reveal_modal.copied"):B("reveal_modal.copy_button")),m(p(),{className:"Button Button--primary",onclick:()=>this.hide()},B("reveal_modal.close"))))}copy(t){navigator.clipboard?navigator.clipboard.writeText(t).then(()=>{this.copied=!0,m.redraw(),setTimeout(()=>{this.copied=!1,m.redraw()},2e3)}).catch(t=>{console.error("[backup] clipboard writeText failed",t),s().alerts.show({type:"error"},B("clipboard_failed"))}):s().alerts.show({type:"error"},B("clipboard_unavailable"))}}class w extends(b()){constructor(){super(...arguments),i(this,"acknowledged",!1),i(this,"submitting",!1)}className(){return"BackupRegenerateModal Modal--medium"}title(){return B("regenerate_modal.title")}content(){return m("div",{className:"Modal-body"},m("div",{className:"Alert Alert--error"},m("p",null,B("regenerate_modal.warning"))),m("label",{className:"BackupRegenerate-confirm"},m("input",{type:"checkbox",checked:this.acknowledged,onchange:t=>{this.acknowledged=t.target.checked}})," ",B("regenerate_modal.acknowledge")),m("div",{className:"Form-group"},m(p(),{className:"Button Button--primary",loading:this.submitting,disabled:!this.acknowledged||this.submitting,onclick:()=>this.submit()},B("regenerate_modal.submit"))))}async submit(){this.submitting=!0,m.redraw();try{await this.attrs.onConfirm(),this.hide()}catch(t){this.submitting=!1,m.redraw()}}}class E extends(c()){constructor(){super(...arguments),i(this,"status",null),i(this,"loadState","loading"),i(this,"loadError",null),i(this,"publicCopied",!1)}oninit(t){super.oninit(t),this.refresh()}view(){return m("section",{className:"BackupEncryptionCard"},m("header",null,m("h3",null,B("section_title")),m("p",{className:"helpText"},B("section_help"))),"loading"===this.loadState&&m(h(),null),"error"===this.loadState&&m("div",{className:"Alert Alert--error BackupEncryption-loadError"},m("p",null,B("status.load_failed")),this.loadError&&m("p",{className:"helpText"},m("code",null,this.loadError)),m(p(),{className:"Button",icon:"fas fa-rotate",onclick:()=>this.refresh()},B("status.retry"))),"ok"===this.loadState&&this.body())}body(){if(!this.status)return m("p",{className:"helpText"},B("status.unknown"));const t=this.status;return t.available?m("[",null,m("div",{className:"BackupEncryption-statusRow"},this.statusBadge("public",t.has_public_key),this.statusBadge("private",t.private_key_present)),t.healthy&&m("div",{className:"Alert Alert--success"},B("status.healthy")),!t.has_public_key&&!t.private_key_present&&m("div",null,m("p",{className:"helpText"},B("status.not_setup")),m(p(),{className:"Button Button--primary",icon:"fas fa-key",onclick:()=>this.generate(!1)},B("actions.generate"))),t.has_public_key&&t.private_key_present&&!1===t.keys_match&&m("div",{className:"Alert Alert--error"},m("strong",null,B("status.mismatch_title")),m("p",null,B("status.mismatch_body")),m("p",null,m("code",null,"'",t.config_key,"'"))),t.has_public_key&&!t.private_key_present&&m("div",{className:"Alert Alert--error"},m("strong",null,B("status.private_missing_title")),m("p",null,B("status.private_missing_body")),m("p",null,m("code",null,"'",t.config_key,"'"))),t.has_public_key&&this.publicKeyPanel(t.public_key||"",t.healthy)):m("div",{className:"Alert Alert--error"},B("status.libsodium_missing"))}publicKeyPanel(t,e){return m("div",{className:"BackupEncryption-publicKey"},m("label",null,B("public_key.label")),m("div",{className:"BackupEncryption-publicKeyRow"},m("pre",null,m("code",null,t)),m(p(),{className:"Button Button--icon",icon:"fas fa-copy",title:y()(B("public_key.copy_title")),onclick:()=>this.copyPublic(t)},this.publicCopied?y()(B("public_key.copied")):"")),m("p",{className:"helpText"},B(e?"public_key.help_healthy":"public_key.help_broken")),m(p(),{className:"Button Button--danger",icon:"fas fa-rotate",onclick:()=>this.openRegenerate()},B("public_key.remove_button")))}statusBadge(t,e){return m("div",{className:"BackupEncryption-badge BackupEncryption-badge--".concat(e?"ok":"missing")},m("i",{className:"icon fas fa-".concat(e?"check":"times")}),m("span",null,B("status.".concat(t,"_key_label"))),m("span",{className:"BackupEncryption-badgeState"},B("status.".concat(e?"present":"absent"))))}copyPublic(t){t&&(navigator.clipboard?navigator.clipboard.writeText(t).then(()=>{this.publicCopied=!0,m.redraw(),setTimeout(()=>{this.publicCopied=!1,m.redraw()},2e3)}).catch(t=>{console.error("[backup] clipboard writeText failed",t),s().alerts.show({type:"error"},B("clipboard_failed"))}):s().alerts.show({type:"error"},B("clipboard_unavailable")))}refresh(){return this.loadState="loading",this.loadError=null,x({method:"GET",url:"".concat(k(),"/backup/encryption/status"),surface:!1}).then(t=>{this.status=t,this.loadState="ok"}).catch(t=>{this.status=null,this.loadState="error",this.loadError=v(t)}).then(()=>{m.redraw()})}async generate(t){try{const e=await x({method:"POST",url:"".concat(k(),"/backup/encryption/generate-keypair"),body:{acknowledge_loss:t},surface:!1});await this.refresh(),s().modal.show(N,{privateKey:e.private_key,configKey:e.config_key})}catch(t){throw s().alerts.show({type:"error"},v(t,String(B("actions.generate_failed")))),t}}openRegenerate(){s().modal.show(w,{onConfirm:()=>this.generate(!0)})}}flarum.reg.add("ramon-backup","admin/components/EncryptionCard",E);const S=flarum.reg.get("core","common/helpers/humanTime");var I=t.n(S);const O={mysql:"MySQL",mariadb:"MariaDB",postgres:"PostgreSQL",sqlite:"SQLite"},P=(t,e)=>s().translator.trans("ramon-backup.admin.list.".concat(t),null!=e?e:{});class T extends(c()){view(t){const e=t.attrs,s=e.backups,a=e.onDelete;return s.length?m("table",{className:"BackupList Table"},m("thead",null,m("tr",null,m("th",null,P("col_when")),m("th",null,P("col_size")),m("th",null,P("col_contents")),m("th",null,P("col_status")),m("th",null))),m("tbody",null,s.map(t=>m("tr",{key:t.id,className:"BackupList-row"},m("td",null,m("div",{className:"BackupList-when"},t.created_at?I()(new Date(t.created_at)):"—"),m("div",{className:"BackupList-filename"},t.filename),t.target_dialect&&m("div",{className:"BackupList-target BackupList-target--".concat(t.target_dialect),title:String(P("target_tooltip",{engine:O[t.target_dialect]||t.target_dialect}))},m("i",{className:"icon fas fa-arrow-right-arrow-left"})," ",P("target_for",{engine:O[t.target_dialect]||t.target_dialect}))),m("td",null,_(t.size_bytes)),m("td",null,t.contents.map(t=>m("span",{className:"BackupList-tag BackupList-tag--".concat(t)},P("content_"+t)))),m("td",null,t.encrypted?m("span",{className:"BackupList-encryption BackupList-encryption--on"},m("i",{className:"icon fas fa-lock"})," ",P("encrypted")):m("span",{className:"BackupList-encryption BackupList-encryption--off"},m("i",{className:"icon fas fa-lock-open"})," ",P("plain"))),m("td",{className:"BackupList-actions"},m("a",{className:"Button Button--icon",href:"".concat(k(),"/backup/backups/").concat(t.id,"/download"),target:"_blank",title:String(P("download_title"))},m("i",{className:"icon fas fa-download"})),m(p(),{className:"Button Button--icon Button--danger",icon:"fas fa-trash",title:P("delete_title"),onclick:()=>a(t.id)})))))):m("p",{className:"BackupList-empty helpText"},P("empty"))}}function j(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,a=Array(e);ss().translator.trans("ramon-backup.admin.export_modal.".concat(t),null!=e?e:{});class D extends(b()){constructor(){super(...arguments),i(this,"stage","form"),i(this,"includeDb",!0),i(this,"includeAssets",!0),i(this,"includeStorage",!1),i(this,"includeExtensions",!1),i(this,"extensionsLoading",!1),i(this,"extensionsLoaded",!1),i(this,"extensions",[]),i(this,"extensionSelected",{}),i(this,"encryptionEnabled",!1),i(this,"encryptionUseExternal",!1),i(this,"externalPublicKey",""),i(this,"targetDialect",""),i(this,"starting",!1),i(this,"jobId",null),i(this,"status",null),i(this,"polling",!1)}className(){return"BackupExportModal Modal--medium"}title(){return M("title")}content(){return"form"===this.stage?this.formContent():this.progressContent()}formContent(){return m("div",{className:"Modal-body"},m("p",{className:"helpText"},M("intro")),m("fieldset",{className:"BackupExport-fieldset"},m("legend",null,M("contents_title")),this.checkbox("db",()=>this.includeDb,t=>this.includeDb=t),this.checkbox("assets",()=>this.includeAssets,t=>this.includeAssets=t),this.checkbox("storage",()=>this.includeStorage,t=>this.includeStorage=t),this.checkbox("extensions",()=>this.includeExtensions,t=>{this.includeExtensions=t,t&&!this.extensionsLoaded&&this.loadExtensions()}),this.includeExtensions&&this.extensionList()),this.includeDb&&m("fieldset",{className:"BackupExport-fieldset"},m("legend",null,M("target_title")),m("p",{className:"helpText"},M("target_help")),m("select",{className:"FormControl BackupExport-targetSelect",value:this.targetDialect,onchange:t=>{this.targetDialect=t.target.value}},m("option",{value:""},M("target_same")),m("option",{value:"mysql"},M("target_mysql")),m("option",{value:"mariadb"},M("target_mariadb")),m("option",{value:"postgres"},M("target_postgres")),m("option",{value:"sqlite"},M("target_sqlite")))),m("fieldset",{className:"BackupExport-fieldset"},m("legend",null,M("encryption_title")),m("label",{className:"BackupExport-checkbox"},m("input",{type:"checkbox",checked:this.encryptionEnabled,onchange:t=>{this.encryptionEnabled=t.target.checked}})," ",m("span",null,M("encryption_enable"))),m("p",{className:"helpText"},M("encryption_help")),this.encryptionEnabled&&m("[",null,m("label",{className:"BackupExport-checkbox"},m("input",{type:"checkbox",checked:this.encryptionUseExternal,onchange:t=>{this.encryptionUseExternal=t.target.checked}})," ",m("span",null,M("encryption_external"))),this.encryptionUseExternal&&m("[",null,m("p",{className:"helpText"},M("encryption_external_help")),m("textarea",{className:"FormControl BackupExport-keyInput",rows:3,placeholder:"base64 public key",value:this.externalPublicKey,oninput:t=>{this.externalPublicKey=t.target.value}})))),m("div",{className:"Form-group"},m(p(),{className:"Button Button--primary",loading:this.starting,disabled:this.starting||!this.canStart(),onclick:()=>this.start()},M("start_button"))))}extensionList(){if(this.extensionsLoading)return m("div",{className:"BackupExport-extLoading"},m(h(),null));if(!this.extensions.length)return m("p",{className:"helpText BackupExport-extEmpty"},M("extensions_none"));const t={workbench:[],vendor:[],unknown:[]};for(const s of this.extensions){var e;null==(e=t[s.location])||e.push(s)}return m("div",{className:"BackupExport-extList"},m("div",{className:"BackupExport-extActions"},m("button",{type:"button",className:"BackupExport-extLink",onclick:()=>this.toggleAllExtensions(!0)},M("extensions_select_all")),m("span",null," · "),m("button",{type:"button",className:"BackupExport-extLink",onclick:()=>this.toggleAllExtensions(!1)},M("extensions_select_none"))),["workbench","vendor","unknown"].filter(e=>t[e].length>0).map(e=>m("div",{className:"BackupExport-extGroup",key:e},m("div",{className:"BackupExport-extGroupHeader"},M("extensions_group_"+e)," ",m("span",{className:"helpText"},"(",t[e].length,")")),t[e].map(t=>m("label",{className:"BackupExport-extRow",key:t.id},m("input",{type:"checkbox",checked:!!this.extensionSelected[t.id],onchange:e=>{this.extensionSelected[t.id]=e.target.checked}})," ",m("span",{className:"BackupExport-extTitle"},t.title)," ",m("code",{className:"BackupExport-extName"},t.name||t.id),m("span",{className:"BackupExport-extTag BackupExport-extTag--".concat(t.location)},M("extensions_tag_"+t.location)))))))}toggleAllExtensions(t){for(const e of this.extensions)this.extensionSelected[e.id]=t}async loadExtensions(){this.extensionsLoading=!0;try{const t=await x({method:"GET",url:"".concat(k(),"/backup/extensions"),surface:!1});this.extensions=t.extensions||[],this.extensionsLoaded=!0;for(const t of this.extensions)this.extensionSelected[t.id]=!0}catch(t){s().alerts.show({type:"error"},v(t,String(M("extensions_load_failed"))))}finally{this.extensionsLoading=!1,m.redraw()}}checkbox(t,e,s){return m("label",{className:"BackupExport-checkbox"},m("input",{type:"checkbox",checked:e(),onchange:t=>{s(t.target.checked)}})," ",m("span",{className:"BackupExport-checkbox-label"},M("content_"+t)),m("span",{className:"BackupExport-checkbox-help helpText"},M("content_"+t+"_help")))}canStart(){return!(!(this.includeDb||this.includeAssets||this.includeStorage||this.includeExtensions)||this.encryptionEnabled&&this.encryptionUseExternal&&!this.externalPublicKey.trim())}progressContent(){var t,e,s;const a=this.status;if(!a)return m(h(),null);const n="done"===a.phase,r="error"===a.phase,o=Math.max(0,Math.min(100,(null==(t=a.progress)?void 0:t.percent)||0));return m("div",{className:"Modal-body BackupExport-progress"},m("div",{className:"BackupExport-status BackupExport-status--".concat(a.phase)},m("strong",null,M("phase_"+a.phase)),m("p",null,a.message)),!r&&m("[",null,m("div",{className:"BackupExport-bar"},m("div",{className:"BackupExport-bar-fill",style:{width:"".concat(n?100:o,"%")},role:"progressbar","aria-valuenow":o,"aria-valuemin":0,"aria-valuemax":100})),m("div",{className:"BackupExport-stats"},m("span",null,_(a.progress.processed_bytes)," /"," ",_(a.progress.total_bytes||a.progress.processed_bytes)),a.progress.total_files>0&&m("span",null,M("files_count",{done:a.progress.processed_files,total:a.progress.total_files})))),(null!=(e=null==(s=a.warnings)?void 0:s.length)?e:0)>0&&m("div",{className:"BackupExport-warnings",role:"alert"},m("div",{className:"BackupExport-warnings-title"},m("i",{className:"icon fas fa-triangle-exclamation"})," ",M("warnings_title",{count:a.warnings.length})),m("p",{className:"helpText"},M("warnings_help")),m("ul",{className:"BackupExport-warnings-list"},a.warnings.map((t,e)=>m("li",{key:e},t)))),m("div",{className:"Form-group BackupExport-progress-actions"},!n&&!r&&m(p(),{className:"Button",onclick:()=>this.cancel()},M("cancel_button")),(n||r)&&m(p(),{className:"Button Button--primary",onclick:()=>this.close()},M("close_button"))))}async start(){this.starting=!0;try{let t=!1;this.includeExtensions&&(t=!this.extensionsLoaded||Object.entries(this.extensionSelected).filter(t=>A(t,2)[1]).map(t=>A(t,1)[0]));const e=await x({method:"POST",url:"".concat(k(),"/backup/exports"),body:{contents:{db:this.includeDb,assets:this.includeAssets,storage:this.includeStorage,extensions:t},encryption:{enabled:this.encryptionEnabled,public_key:this.encryptionUseExternal?this.externalPublicKey.trim():null},target_dialect:this.targetDialect||null},surface:!1});this.jobId=e.job_id,this.stage="progress",this.status={phase:e.phase,message:e.message,progress:{total_bytes:0,processed_bytes:0,total_files:0,processed_files:0,percent:0}},this.starting=!1,m.redraw(),this.pump()}catch(t){this.starting=!1,s().alerts.show({type:"error"},v(t,String(M("start_failed")))),m.redraw()}}async pump(){if(!this.polling&&this.jobId){this.polling=!0;try{for(var t;this.jobId&&this.status&&"done"!==this.status.phase&&"error"!==this.status.phase;)try{const t=await x({method:"POST",url:"".concat(k(),"/backup/exports/").concat(this.jobId,"/tick"),surface:!1});this.status=t,m.redraw()}catch(t){const e=v(t,String(M("phase_error_network")));this.status=C(C({},this.status),{},{phase:"error",message:e}),m.redraw();break}"done"===(null==(t=this.status)?void 0:t.phase)&&(s().alerts.show({type:"success"},M("completed")),this.attrs.onComplete())}finally{this.polling=!1}}}async cancel(){if(this.jobId){try{await x({method:"DELETE",url:"".concat(k(),"/backup/exports/").concat(this.jobId),surface:!1})}catch(t){console.warn("[backup] export cancel failed",t),s().alerts.show({type:"warning"},M("cancel_failed_warn"))}this.close()}}close(){this.jobId=null,this.hide()}}function R(t,e){var s=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),s.push.apply(s,a)}return s}function K(t){for(var e=1;es().translator.trans("ramon-backup.admin.import_modal.".concat(t),null!=e?e:{});class z extends(b()){constructor(){super(...arguments),i(this,"stage","upload"),i(this,"file",null),i(this,"uploading",!1),i(this,"uploadProgress",0),i(this,"uploadIndeterminate",!1),i(this,"uploadError",null),i(this,"inspect",null),i(this,"privateKey",""),i(this,"confirmReplace",!1),i(this,"starting",!1),i(this,"sectionDb",!1),i(this,"sectionAssets",!1),i(this,"sectionStorage",!1),i(this,"sectionExtensions",!1),i(this,"extensionsByName",{}),i(this,"status",null),i(this,"polling",!1)}className(){return"BackupImportModal Modal--medium"}title(){var t;return"progress"===this.stage&&"done"===(null==(t=this.status)?void 0:t.phase)?this.sectionDb?F("logout_title"):F("done_title"):F("title")}content(){return"upload"===this.stage?this.uploadContent():"configure"===this.stage?this.configureContent():this.progressContent()}uploadContent(){return m("div",{className:"Modal-body"},m("div",{className:"Alert Alert--warning"},m("strong",null,F("warning_title")),m("p",null,F("warning_body"))),m("label",{className:"BackupImport-fileLabel"},m("input",{type:"file",accept:".flarum",onchange:t=>{var e;const s=(null==(e=t.target.files)?void 0:e[0])||null;this.file=s}}),this.file?m("span",null,this.file.name," ",m("span",{className:"helpText"},"(",_(this.file.size),")")):m("span",{className:"helpText"},F("choose_file"))),this.uploadError&&m("div",{className:"Alert Alert--error"},this.uploadError),this.uploading&&m("div",{className:"BackupImport-uploadProgress"},m("div",{className:"BackupImport-bar"},m("div",{className:"BackupImport-bar-fill"+(this.uploadIndeterminate?" BackupImport-bar-fill--indeterminate":""),style:this.uploadIndeterminate?void 0:{width:"".concat(Math.max(2,this.uploadProgress),"%")}})),m("div",{className:"BackupImport-uploadStatus helpText"},this.uploadIndeterminate?F("inspecting_archive"):F("uploading_pct",{pct:this.uploadProgress}))),m("div",{className:"Form-group"},m(p(),{className:"Button Button--primary",loading:this.uploading,disabled:this.uploading||!this.file,onclick:()=>this.upload()},F("upload_button"))))}async upload(){if(this.file){this.uploading=!0,this.uploadProgress=0,this.uploadIndeterminate=!1,this.uploadError=null;try{var t;const e=await this.uploadWithProgress(this.file,t=>{this.uploadProgress=t,t>=100&&(this.uploadIndeterminate=!0),m.redraw()});this.inspect=e;const s=e.meta.contents||[];this.sectionDb=s.includes("db"),this.sectionAssets=s.includes("assets"),this.sectionStorage=s.includes("storage"),this.sectionExtensions=s.includes("extensions");const a=(null==(t=e.meta.manifest)?void 0:t.extensions)||[];this.extensionsByName={};for(const t of a){const e="string"==typeof t?t:t.id;e&&(this.extensionsByName[e]=!0)}this.stage="configure"}catch(t){console.error("[backup] archive upload failed",t),this.uploadError=v(t,String(F("upload_failed")))}finally{this.uploading=!1,m.redraw()}}}async uploadWithProgress(t,e){const s=await x({method:"POST",url:"".concat(k(),"/backup/imports"),body:{filename:t.name,size:t.size},surface:!1}),a=s.job_id,n=s.chunk_size>0?s.chunk_size:4194304;let r=0;for(;r2)throw t;await new Promise(t=>setTimeout(t,750*i))}r=s,e(Math.min(99,Math.round(r/t.size*100)))}return e(100),x({method:"POST",url:"".concat(k(),"/backup/imports/").concat(a,"/inspect"),surface:!1})}sendChunk(t,e,a){return new Promise((n,r)=>{var o;const i=new XMLHttpRequest;let l=Date.now();const c=setInterval(()=>{Date.now()-l>6e4&&(clearInterval(c),i.abort())},5e3),m=()=>clearInterval(c);i.upload.addEventListener("progress",()=>{l=Date.now()}),i.addEventListener("load",()=>{if(m(),i.status>=200&&i.status<300)n();else{let e;try{var t;e=null==(t=JSON.parse(i.responseText))||null==(t=t.errors)||null==(t=t[0])?void 0:t.detail}catch(t){}r({detail:e||"".concat(i.status," ").concat(i.statusText)})}}),i.addEventListener("error",()=>{m(),r({detail:String(F("upload_failed"))})}),i.addEventListener("abort",()=>{m(),r({detail:String(F("upload_idle_timeout"))})}),i.open("POST","".concat(k(),"/backup/imports/").concat(t,"/chunk"),!0),i.withCredentials=!0,i.setRequestHeader("Content-Type","application/octet-stream"),i.setRequestHeader("X-Chunk-Offset",String(e));const u=null==(o=s().session)?void 0:o.csrfToken;u&&i.setRequestHeader("X-CSRF-Token",u),i.send(a)})}configureContent(){const t=this.inspect;return m("div",{className:"Modal-body"},m("h4",null,F("inspect_title")),m("dl",{className:"BackupImport-meta"},t.meta.created_at&&m("[",null,m("dt",null,F("meta_when")),m("dd",null,t.meta.created_at)),t.meta.flarum_version&&m("[",null,m("dt",null,F("meta_flarum")),m("dd",null,t.meta.flarum_version)),t.meta.contents&&m("[",null,m("dt",null,F("meta_contents")),m("dd",null,t.meta.contents.join(", "))),t.meta.source_url&&m("[",null,m("dt",null,F("meta_source_url")),m("dd",null,m("code",null,t.meta.source_url))),m("dt",null,F("meta_size")),m("dd",null,_(t.size))),m("div",{className:"Alert Alert--info BackupImport-urlNote"},m("i",{className:"icon fas fa-info-circle"})," ",F("url_rewrite_note")),this.selectionFieldset(t),t.is_encrypted&&m("fieldset",{className:"BackupImport-fieldset"},m("legend",null,F("key_title")),m("p",{className:"helpText"},F("key_help")),m("textarea",{className:"FormControl BackupImport-keyInput",rows:3,placeholder:"base64 private key",value:this.privateKey,oninput:t=>{this.privateKey=t.target.value}}),m("p",{className:"helpText BackupImport-keyHint"},F("key_hint_local"))),m("div",{className:"Alert Alert--error BackupImport-confirmAlert"},m("strong",null,F("confirm_title")),m("p",null,F("confirm_body")),m("label",{className:"BackupImport-confirm"},m("input",{type:"checkbox",checked:this.confirmReplace,onchange:t=>{this.confirmReplace=t.target.checked}})," ",m("span",null,F("confirm_check")))),m("div",{className:"Form-group"},m(p(),{className:"Button Button--primary",loading:this.starting,disabled:this.starting||!this.confirmReplace,onclick:()=>this.startRestore()},F("start_button"))))}selectionFieldset(t){const e=t.meta.contents||[],s=t.meta.manifest||{},a=e.includes("db"),n=e.includes("assets"),r=e.includes("storage"),o=e.includes("extensions"),i=(s.extensions||[]).map(t=>"string"==typeof t?{id:t,location:"workbench"}:t);return m("fieldset",{className:"BackupImport-fieldset"},m("legend",null,F("selection_title")),m("p",{className:"helpText"},F("selection_help")),a&&this.sectionRow("db",this.sectionDb,t=>this.sectionDb=t),n&&this.sectionRow("assets",this.sectionAssets,t=>this.sectionAssets=t,s.asset_count),r&&this.sectionRow("storage",this.sectionStorage,t=>this.sectionStorage=t,s.storage_count),o&&m("[",null,this.sectionRow("extensions",this.sectionExtensions,t=>{this.sectionExtensions=t;for(const e of i)this.extensionsByName[e.id]=t},s.extension_count),this.sectionExtensions&&s.has_composer&&m("div",{className:"BackupImport-composerNote helpText"},m("i",{className:"icon fas fa-cube"})," ",F("extensions_composer_note")),this.sectionExtensions&&i.length>0&&m("div",{className:"BackupImport-extList"},i.map(t=>m("label",{className:"BackupImport-extRow",key:t.id},m("input",{type:"checkbox",checked:!!this.extensionsByName[t.id],onchange:e=>{this.extensionsByName[t.id]=e.target.checked}})," ",m("span",{className:"BackupImport-extTitle"},t.title||t.id)," ",t.name&&t.name!==t.id&&m("code",{className:"BackupImport-extName"},t.name),t.location&&m("span",{className:"BackupImport-extTag BackupImport-extTag--".concat(t.location)},F("extensions_tag_"+t.location)))))))}sectionRow(t,e,s,a){return m("label",{className:"BackupImport-sectionRow"},m("input",{type:"checkbox",checked:e,onchange:t=>s(t.target.checked)})," ",m("span",{className:"BackupImport-sectionLabel"},F("section_"+t)),void 0!==a&&a>0&&m("span",{className:"BackupImport-sectionCount helpText"}," ","(",F("section_count",{count:a}),")"))}buildSelection(){const t=Object.entries(this.extensionsByName),e=t.length>0&&t.every(t=>A(t,2)[1]),s=!!this.sectionExtensions&&(!!e||t.filter(t=>A(t,2)[1]).map(t=>A(t,1)[0]));return{db:this.sectionDb,assets:this.sectionAssets,storage:this.sectionStorage,extensions:s}}async startRestore(){if(this.inspect){this.starting=!0;try{const t=await x({method:"POST",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id,"/start"),surface:!1,body:{private_key:this.privateKey.trim()||null,confirm_replace:this.confirmReplace,selection:this.buildSelection()}});this.stage="progress",this.status={phase:t.phase,message:t.message,progress:{total_bytes:this.inspect.size,processed_bytes:0,extracted_entries:0,restored_statements:0,percent:0}},m.redraw(),this.pump()}catch(t){s().alerts.show({type:"error"},v(t,String(F("start_failed"))))}finally{this.starting=!1}}}progressContent(){var t;const e=this.status;if(!e)return m(h(),null);if("done"===e.phase)return this.completedContent();const s="error"===e.phase,a=Math.max(0,Math.min(100,(null==(t=e.progress)?void 0:t.percent)||0));return m("div",{className:"Modal-body BackupImport-progress"},m("div",{className:"BackupImport-status BackupImport-status--".concat(e.phase)},m("strong",null,F("phase_"+e.phase)),m("p",null,e.message)),!s&&m("div",{className:"BackupImport-bar"},m("div",{className:"BackupImport-bar-fill",style:{width:"".concat(a,"%")}})),m("div",{className:"Form-group BackupImport-progress-actions"},!s&&m(p(),{className:"Button",onclick:()=>this.cancel()},F("cancel_button")),s&&m(p(),{className:"Button Button--primary",onclick:()=>this.close()},F("close_button"))))}completedWarnings(){var t,e;const s=null!=(t=null==(e=this.status)?void 0:e.warnings)?t:[];return s.length?m("div",{className:"Alert Alert--error BackupImport-completedWarnings"},m("strong",null,F("incomplete_title")),m("ul",null,s.map(t=>m("li",null,t)))):null}completedContent(){var t;if(this.sectionDb)return m("div",{className:"Modal-body BackupImport-completed BackupImport-completed--logout"},this.completedWarnings(),m("div",{className:"BackupImport-completedIcon"},m("i",{className:"fas fa-right-from-bracket"})),m("h3",{className:"BackupImport-completedTitle"},F("logout_title")),m("p",{className:"BackupImport-completedBody"},F("logout_body")),m("ol",{className:"BackupImport-completedSteps"},m("li",null,F("logout_step_reload")),m("li",null,F("logout_step_login"))),m(p(),{className:"Button Button--primary BackupImport-completedAction",icon:"fas fa-rotate",onclick:()=>window.location.reload()},F("logout_button")));const e=!(null==(t=this.status)||!t.incomplete);return m("div",{className:"Modal-body BackupImport-completed"},this.completedWarnings(),m("div",{className:"BackupImport-completedIcon BackupImport-completedIcon--".concat(e?"warning":"success")},m("i",{className:e?"fas fa-triangle-exclamation":"fas fa-circle-check"})),m("h3",{className:"BackupImport-completedTitle"},F(e?"incomplete_title":"done_title")),m("p",{className:"BackupImport-completedBody"},F(e?"incomplete_body":"done_body")),m(p(),{className:"Button Button--primary",onclick:()=>this.close()},F("close_button")))}async pump(){if(!this.polling&&this.inspect){this.polling=!0;try{for(var t;this.inspect&&this.status&&"done"!==this.status.phase&&"error"!==this.status.phase;)try{const t=await x({method:"POST",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id,"/tick"),surface:!1,body:{private_key:this.privateKey.trim()||null}});this.status=t,m.redraw()}catch(t){console.error("[backup] import tick failed",t);const e=v(t,String(F("phase_error_network")));this.status=K(K({},this.status),{},{phase:"error",message:e}),m.redraw();break}"done"===(null==(t=this.status)?void 0:t.phase)&&(s().alerts.show({type:"success"},F("completed")),this.sectionDb||this.attrs.onComplete())}finally{this.polling=!1}}}async cancel(){if(this.inspect){try{await x({method:"DELETE",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id),surface:!1})}catch(t){console.error("[backup] import cancel failed",t),s().alerts.show({type:"warning"},F("cancel_failed_warn"))}this.close()}}close(){this.hide()}}function q(t,e){var s=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),s.push.apply(s,a)}return s}function U(t){for(var e=1;ethis.decide(!0)},a),m(p(),{className:"Button",onclick:()=>this.decide(!1)},n)))}decide(t){var e;this.resolved||(this.resolved=!0,null==(e=t?this.attrs.onConfirm:this.attrs.onCancel)||e(),this.hide())}onbeforeremove(t){var e,s;return this.resolved||(this.resolved=!0,null==(e=(s=this.attrs).onCancel)||e.call(s)),super.onbeforeremove(t)}}flarum.reg.add("ramon-backup","admin/components/ConfirmModal",G);const H=t=>s().translator.trans("ramon-backup.admin.errors.".concat(t));class W extends(c()){constructor(){super(...arguments),i(this,"failed",!1),i(this,"lastError",null)}view(t){if(this.failed){const e=()=>{this.failed=!1,this.lastError=null,m.redraw()};return t.attrs.fallback?t.attrs.fallback(this.lastError,e):m("div",{className:"Alert Alert--error BackupErrorBoundary"},m("strong",null,H("boundary_title")),m("p",null,H("boundary_body")),m("button",{type:"button",className:"Button",onclick:e},H("boundary_retry")))}try{return t.children}catch(a){var e,s;return this.failed=!0,this.lastError=a,null==(e=(s=t.attrs).onError)||e.call(s,a),console.error("[backup] render boundary caught",a),null}}}flarum.reg.add("ramon-backup","admin/utils/errorBoundary",{ErrorBoundary:W});const Q=(t,e)=>s().translator.trans("ramon-backup.admin.".concat(t),null!=e?e:{});class X extends(c()){constructor(){super(...arguments),i(this,"listState","loading"),i(this,"listError",null),i(this,"backups",[])}oninit(t){super.oninit(t),this.refresh()}view(){return m("div",{className:"BackupPanel"},m(W,{onError:t=>console.error("[backup] panel render",t)},m("section",{className:"BackupPanel-actions"},m("h3",null,Q("panel.actions_title")),m("p",{className:"helpText"},Q("panel.actions_help")),m("div",{className:"BackupPanel-actionButtons"},m(p(),{className:"Button Button--primary",icon:"fas fa-download",onclick:()=>this.openExport()},Q("panel.create_button")),m(p(),{className:"Button",icon:"fas fa-upload",onclick:()=>this.openImport()},Q("panel.import_button")))),m(E,null),m("section",{className:"BackupPanel-list"},m("h3",null,Q("panel.list_title")),this.renderList())))}renderList(){return"loading"===this.listState?m(h(),null):"error"===this.listState?m("div",{className:"Alert Alert--error BackupPanel-listError"},m("p",null,Q("list.load_failed")),this.listError&&m("p",{className:"helpText"},m("code",null,this.listError)),m(p(),{className:"Button",icon:"fas fa-rotate",onclick:()=>this.refresh()},Q("list.retry"))):m(T,{backups:this.backups,onDelete:t=>this.delete(t),onRefresh:()=>this.refresh()})}refresh(){return this.listState="loading",this.listError=null,x({method:"GET",url:"".concat(k(),"/backup/backups"),surface:!1}).then(t=>{this.backups=t.backups||[],this.listState="ok"}).catch(t=>{this.backups=[],this.listState="error",this.listError=v(t)}).then(()=>{m.redraw()})}openExport(){s().modal.show(D,{onComplete:()=>this.refresh()})}openImport(){s().modal.show(z,{onComplete:()=>this.refresh()})}async delete(t){var e;if(await(e={title:Q("list.confirm_delete_title"),body:Q("list.confirm_delete"),confirmLabel:Q("list.delete_title"),danger:!0},new Promise(t=>{let a=!1;const n=e=>{a||(a=!0,t(e))};s().modal.show(G,U(U({},e),{},{onConfirm:()=>n(!0),onCancel:()=>n(!1)}))})))try{await x({method:"DELETE",url:"".concat(k(),"/backup/backups/").concat(t),surface:!1,fallbackMessage:String(Q("list.delete_failed"))}),s().alerts.show({type:"success"},Q("list.deleted")),this.refresh()}catch(t){s().alerts.show({type:"error"},v(t,String(Q("list.delete_failed"))))}}}flarum.reg.add("ramon-backup","admin/components/BackupPanel",X);const $="ramon-backup";(0,a.override)(r().prototype,"submitButton",function(t){return this.extension&&this.extension.id===$?null:t()}),s().initializers.add($,()=>{s().registry.for($).registerSetting(()=>m(X,null),100,"ramon-backup.panel").registerPermission({icon:"fas fa-file-archive",label:s().translator.trans("ramon-backup.admin.permissions.manage_label"),permission:"backup.manage"},"moderate")})})(),module.exports={}})(); //# sourceMappingURL=admin.js.map \ No newline at end of file diff --git a/js/dist/admin.js.map b/js/dist/admin.js.map index 3bd9bbc..69d996e 100644 --- a/js/dist/admin.js.map +++ b/js/dist/admin.js.map @@ -1 +1 @@ -{"version":3,"file":"admin.js","mappings":"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","sources":["webpack://@ramon/backup/webpack/bootstrap","webpack://@ramon/backup/webpack/runtime/compat get default export","webpack://@ramon/backup/webpack/runtime/define property getters","webpack://@ramon/backup/webpack/runtime/hasOwnProperty shorthand","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'admin/app')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/extend')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'admin/components/ExtensionPage')\"","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/typeof.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/defineProperty.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/toPropertyKey.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/toPrimitive.js","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/Component')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/components/Button')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/components/LoadingIndicator')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/components/Modal')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/utils/extractText')\"","webpack://@ramon/backup/./src/admin/utils/api.ts","webpack://@ramon/backup/./src/admin/components/EncryptionCard.tsx","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/helpers/humanTime')\"","webpack://@ramon/backup/./src/admin/components/BackupList.tsx","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/arrayLikeToArray.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/slicedToArray.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/arrayWithHoles.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/iterableToArrayLimit.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/unsupportedIterableToArray.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/nonIterableRest.js","webpack://@ramon/backup/./src/admin/components/ExportModal.tsx","webpack://@ramon/backup/./src/admin/components/ImportModal.tsx","webpack://@ramon/backup/./src/admin/components/ConfirmModal.tsx","webpack://@ramon/backup/./src/admin/utils/errorBoundary.tsx","webpack://@ramon/backup/./src/admin/components/BackupPanel.tsx","webpack://@ramon/backup/./src/admin/index.tsx"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'admin/app');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/extend');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'admin/components/ExtensionPage');","function _typeof(o) {\n \"@babel/helpers - typeof\";\n\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n return typeof o;\n } : function (o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\nexport { _typeof as default };","import toPropertyKey from \"./toPropertyKey.js\";\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\nexport { _defineProperty as default };","import _typeof from \"./typeof.js\";\nimport toPrimitive from \"./toPrimitive.js\";\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\nexport { toPropertyKey as default };","import _typeof from \"./typeof.js\";\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\nexport { toPrimitive as default };","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/Component');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Button');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/LoadingIndicator');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Modal');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/utils/extractText');","import app from \"flarum/admin/app\";\n/** Trim trailing slashes off the configured API URL. */\nexport function apiUrl() {\n return (app.forum.attribute(\"apiUrl\") || \"/api\").replace(/\\/+$/, \"\");\n}\nexport function fmtBytes(bytes) {\n if (!Number.isFinite(bytes) || bytes <= 0) return \"0 B\";\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let i = 0;\n let n = bytes;\n while (n >= 1024 && i < units.length - 1) {\n n /= 1024;\n i++;\n }\n return n.toFixed(n >= 100 || i === 0 ? 0 : 1) + \" \" + units[i];\n}\n\n/** Best-effort extraction of a human-readable detail from a RequestError or thrown object. */\nexport function errorDetail(raw, fallback) {\n var _ref, _raw$response$errors$, _raw$response;\n const detail = (_ref = (_raw$response$errors$ = raw == null || (_raw$response = raw.response) == null || (_raw$response = _raw$response.errors) == null || (_raw$response = _raw$response[0]) == null ? void 0 : _raw$response.detail) != null ? _raw$response$errors$ : raw == null ? void 0 : raw.detail) != null ? _ref : typeof (raw == null ? void 0 : raw.message) === \"string\" ? raw.message : undefined;\n if (detail) return String(detail);\n if (fallback) return fallback;\n return String(app.translator.trans(\"ramon-backup.admin.errors.generic\"));\n}\n/**\n * Wrapper around `app.request` that:\n * - logs every failure to console.error with action + URL;\n * - extracts the JSON:API error detail when present;\n * - shows a Flarum alert (unless `surface: false`);\n * - rethrows so the caller can still branch on the failure.\n *\n * Centralised so we don't reinvent error extraction at every call-site.\n */\nexport async function apiRequest(opts) {\n try {\n return await app.request(opts);\n } catch (raw) {\n const detail = errorDetail(raw, opts.fallbackMessage);\n console.error(\"[backup] api error\", opts.method, opts.url, raw);\n if (opts.surface !== false) {\n app.alerts.show({\n type: \"error\"\n }, detail);\n }\n if (raw && typeof raw === \"object\" && !raw.detail) {\n try {\n raw.detail = detail;\n } catch (_unused) {\n /* read-only, ignore */\n }\n }\n throw raw;\n }\n}\nflarum.reg.add('ramon-backup', 'admin/utils/api', { apiUrl: apiUrl,fmtBytes: fmtBytes,errorDetail: errorDetail, });","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport app from \"flarum/admin/app\";\nimport Component from \"flarum/common/Component\";\nimport Button from \"flarum/common/components/Button\";\nimport Modal from \"flarum/common/components/Modal\";\nimport LoadingIndicator from \"flarum/common/components/LoadingIndicator\";\nimport extractText from \"flarum/common/utils/extractText\";\nimport { apiRequest, apiUrl, errorDetail } from \"../utils/api\";\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.encryption.\".concat(key), params != null ? params : {});\nclass KeypairRevealModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"copied\", false);\n }\n className() {\n return \"BackupRevealModal Modal--medium\";\n }\n title() {\n return trans(\"reveal_modal.title\");\n }\n content() {\n const _this$attrs = this.attrs,\n privateKey = _this$attrs.privateKey,\n configKey = _this$attrs.configKey;\n const snippet = \"'\".concat(configKey, \"' => '\").concat(privateKey, \"',\");\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"p\", null, trans(\"reveal_modal.intro\")), m(\"div\", {\n className: \"Alert Alert--error\"\n }, m(\"strong\", null, trans(\"reveal_modal.warning_title\")), m(\"p\", null, trans(\"reveal_modal.warning_body\"))), m(\"label\", {\n className: \"BackupReveal-label\"\n }, trans(\"reveal_modal.snippet_label\")), m(\"pre\", {\n className: \"BackupReveal-snippet\"\n }, m(\"code\", null, snippet)), m(\"div\", {\n className: \"Form-group BackupReveal-actions\"\n }, m(Button, {\n className: \"Button\",\n icon: \"fas fa-copy\",\n onclick: () => this.copy(snippet)\n }, this.copied ? trans(\"reveal_modal.copied\") : trans(\"reveal_modal.copy_button\")), m(Button, {\n className: \"Button Button--primary\",\n onclick: () => this.hide()\n }, trans(\"reveal_modal.close\"))));\n }\n copy(snippet) {\n if (!navigator.clipboard) {\n app.alerts.show({\n type: \"error\"\n }, trans(\"clipboard_unavailable\"));\n return;\n }\n navigator.clipboard.writeText(snippet).then(() => {\n this.copied = true;\n m.redraw();\n setTimeout(() => {\n this.copied = false;\n m.redraw();\n }, 2000);\n }).catch(err => {\n console.error(\"[backup] clipboard writeText failed\", err);\n app.alerts.show({\n type: \"error\"\n }, trans(\"clipboard_failed\"));\n });\n }\n}\nclass RegenerateConfirmModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"acknowledged\", false);\n _defineProperty(this, \"submitting\", false);\n }\n className() {\n return \"BackupRegenerateModal Modal--medium\";\n }\n title() {\n return trans(\"regenerate_modal.title\");\n }\n content() {\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"div\", {\n className: \"Alert Alert--error\"\n }, m(\"p\", null, trans(\"regenerate_modal.warning\"))), m(\"label\", {\n className: \"BackupRegenerate-confirm\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: this.acknowledged,\n onchange: e => {\n this.acknowledged = e.target.checked;\n }\n }), \" \", trans(\"regenerate_modal.acknowledge\")), m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--primary\",\n loading: this.submitting,\n disabled: !this.acknowledged || this.submitting,\n onclick: () => this.submit()\n }, trans(\"regenerate_modal.submit\"))));\n }\n async submit() {\n this.submitting = true;\n m.redraw();\n try {\n await this.attrs.onConfirm();\n this.hide();\n } catch (_unused) {\n // Parent already showed an error toast — keep the modal open so\n // the user can retry without re-acknowledging the warning.\n this.submitting = false;\n m.redraw();\n }\n }\n}\nexport default class EncryptionCard extends Component {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"status\", null);\n _defineProperty(this, \"loadState\", \"loading\");\n _defineProperty(this, \"loadError\", null);\n _defineProperty(this, \"publicCopied\", false);\n }\n oninit(vnode) {\n super.oninit(vnode);\n this.refresh();\n }\n view() {\n return m(\"section\", {\n className: \"BackupEncryptionCard\"\n }, m(\"header\", null, m(\"h3\", null, trans(\"section_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"section_help\"))), this.loadState === \"loading\" && m(LoadingIndicator, null), this.loadState === \"error\" && m(\"div\", {\n className: \"Alert Alert--error BackupEncryption-loadError\"\n }, m(\"p\", null, trans(\"status.load_failed\")), this.loadError && m(\"p\", {\n className: \"helpText\"\n }, m(\"code\", null, this.loadError)), m(Button, {\n className: \"Button\",\n icon: \"fas fa-rotate\",\n onclick: () => this.refresh()\n }, trans(\"status.retry\"))), this.loadState === \"ok\" && this.body());\n }\n body() {\n if (!this.status) return m(\"p\", {\n className: \"helpText\"\n }, trans(\"status.unknown\"));\n const s = this.status;\n if (!s.available) {\n return m(\"div\", {\n className: \"Alert Alert--error\"\n }, trans(\"status.libsodium_missing\"));\n }\n return m('[', null, m(\"div\", {\n className: \"BackupEncryption-statusRow\"\n }, this.statusBadge(\"public\", s.has_public_key), this.statusBadge(\"private\", s.private_key_present)), s.healthy && m(\"div\", {\n className: \"Alert Alert--success\"\n }, trans(\"status.healthy\")), !s.has_public_key && !s.private_key_present && m(\"div\", null, m(\"p\", {\n className: \"helpText\"\n }, trans(\"status.not_setup\")), m(Button, {\n className: \"Button Button--primary\",\n icon: \"fas fa-key\",\n onclick: () => this.generate(false)\n }, trans(\"actions.generate\"))), s.has_public_key && s.private_key_present && s.keys_match === false && m(\"div\", {\n className: \"Alert Alert--error\"\n }, m(\"strong\", null, trans(\"status.mismatch_title\")), m(\"p\", null, trans(\"status.mismatch_body\")), m(\"p\", null, m(\"code\", null, \"'\", s.config_key, \"'\"))), s.has_public_key && !s.private_key_present && m(\"div\", {\n className: \"Alert Alert--error\"\n }, m(\"strong\", null, trans(\"status.private_missing_title\")), m(\"p\", null, trans(\"status.private_missing_body\")), m(\"p\", null, m(\"code\", null, \"'\", s.config_key, \"'\"))), s.has_public_key && this.publicKeyPanel(s.public_key || \"\", s.healthy));\n }\n publicKeyPanel(publicKey, healthy) {\n return m(\"div\", {\n className: \"BackupEncryption-publicKey\"\n }, m(\"label\", null, trans(\"public_key.label\")), m(\"div\", {\n className: \"BackupEncryption-publicKeyRow\"\n }, m(\"pre\", null, m(\"code\", null, publicKey)), m(Button, {\n className: \"Button Button--icon\",\n icon: \"fas fa-copy\",\n title: extractText(trans(\"public_key.copy_title\")),\n onclick: () => this.copyPublic(publicKey)\n }, this.publicCopied ? extractText(trans(\"public_key.copied\")) : \"\")), m(\"p\", {\n className: \"helpText\"\n }, healthy ? trans(\"public_key.help_healthy\") : trans(\"public_key.help_broken\")), m(Button, {\n className: \"Button Button--danger\",\n icon: \"fas fa-rotate\",\n onclick: () => this.openRegenerate()\n }, trans(\"public_key.remove_button\")));\n }\n statusBadge(kind, present) {\n return m(\"div\", {\n className: \"BackupEncryption-badge BackupEncryption-badge--\".concat(present ? \"ok\" : \"missing\")\n }, m(\"i\", {\n className: \"icon fas fa-\".concat(present ? \"check\" : \"times\")\n }), m(\"span\", null, trans(\"status.\".concat(kind, \"_key_label\"))), m(\"span\", {\n className: \"BackupEncryption-badgeState\"\n }, trans(\"status.\".concat(present ? \"present\" : \"absent\"))));\n }\n copyPublic(publicKey) {\n if (!publicKey) return;\n if (!navigator.clipboard) {\n app.alerts.show({\n type: \"error\"\n }, trans(\"clipboard_unavailable\"));\n return;\n }\n navigator.clipboard.writeText(publicKey).then(() => {\n this.publicCopied = true;\n m.redraw();\n setTimeout(() => {\n this.publicCopied = false;\n m.redraw();\n }, 2000);\n }).catch(err => {\n console.error(\"[backup] clipboard writeText failed\", err);\n app.alerts.show({\n type: \"error\"\n }, trans(\"clipboard_failed\"));\n });\n }\n refresh() {\n this.loadState = \"loading\";\n this.loadError = null;\n return apiRequest({\n method: \"GET\",\n url: \"\".concat(apiUrl(), \"/backup/encryption/status\"),\n surface: false\n }).then(res => {\n this.status = res;\n this.loadState = \"ok\";\n }).catch(e => {\n this.status = null;\n this.loadState = \"error\";\n this.loadError = errorDetail(e);\n }).then(() => {\n m.redraw();\n });\n }\n async generate(acknowledgeLoss) {\n try {\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/encryption/generate-keypair\"),\n body: {\n acknowledge_loss: acknowledgeLoss\n },\n surface: false\n });\n await this.refresh();\n app.modal.show(KeypairRevealModal, {\n privateKey: res.private_key,\n configKey: res.config_key\n });\n } catch (e) {\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"actions.generate_failed\"))));\n throw e;\n }\n }\n openRegenerate() {\n app.modal.show(RegenerateConfirmModal, {\n onConfirm: () => this.generate(true)\n });\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/EncryptionCard', EncryptionCard);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/helpers/humanTime');","import app from \"flarum/admin/app\";\nimport Component from \"flarum/common/Component\";\nimport Button from \"flarum/common/components/Button\";\nimport humanTime from \"flarum/common/helpers/humanTime\";\nimport { apiUrl, fmtBytes } from \"../utils/api\";\nconst DIALECT_LABEL = {\n mysql: \"MySQL\",\n mariadb: \"MariaDB\",\n postgres: \"PostgreSQL\",\n sqlite: \"SQLite\"\n};\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.list.\".concat(key), params != null ? params : {});\nexport default class BackupList extends Component {\n view(vnode) {\n const _vnode$attrs = vnode.attrs,\n backups = _vnode$attrs.backups,\n onDelete = _vnode$attrs.onDelete;\n if (!backups.length) {\n return m(\"p\", {\n className: \"BackupList-empty helpText\"\n }, trans(\"empty\"));\n }\n return m(\"table\", {\n className: \"BackupList Table\"\n }, m(\"thead\", null, m(\"tr\", null, m(\"th\", null, trans(\"col_when\")), m(\"th\", null, trans(\"col_size\")), m(\"th\", null, trans(\"col_contents\")), m(\"th\", null, trans(\"col_status\")), m(\"th\", null))), m(\"tbody\", null, backups.map(b => m(\"tr\", {\n key: b.id,\n className: \"BackupList-row\"\n }, m(\"td\", null, m(\"div\", {\n className: \"BackupList-when\"\n }, b.created_at ? humanTime(new Date(b.created_at)) : \"—\"), m(\"div\", {\n className: \"BackupList-filename\"\n }, b.filename), b.target_dialect &&\n // Only shown when the admin retargeted the dump at\n // export time — same-engine backups have a NULL\n // target_dialect and don't need the visual noise.\n m(\"div\", {\n className: \"BackupList-target BackupList-target--\".concat(b.target_dialect),\n title: String(trans(\"target_tooltip\", {\n engine: DIALECT_LABEL[b.target_dialect] || b.target_dialect\n }))\n }, m(\"i\", {\n className: \"icon fas fa-arrow-right-arrow-left\"\n }), \" \", trans(\"target_for\", {\n engine: DIALECT_LABEL[b.target_dialect] || b.target_dialect\n }))), m(\"td\", null, fmtBytes(b.size_bytes)), m(\"td\", null, b.contents.map(c => m(\"span\", {\n className: \"BackupList-tag BackupList-tag--\".concat(c)\n }, trans(\"content_\" + c)))), m(\"td\", null, b.encrypted ? m(\"span\", {\n className: \"BackupList-encryption BackupList-encryption--on\"\n }, m(\"i\", {\n className: \"icon fas fa-lock\"\n }), \" \", trans(\"encrypted\")) : m(\"span\", {\n className: \"BackupList-encryption BackupList-encryption--off\"\n }, m(\"i\", {\n className: \"icon fas fa-lock-open\"\n }), \" \", trans(\"plain\"))), m(\"td\", {\n className: \"BackupList-actions\"\n }, m(\"a\", {\n className: \"Button Button--icon\",\n href: \"\".concat(apiUrl(), \"/backup/backups/\").concat(b.id, \"/download\"),\n target: \"_blank\",\n title: String(trans(\"download_title\"))\n }, m(\"i\", {\n className: \"icon fas fa-download\"\n })), m(Button, {\n className: \"Button Button--icon Button--danger\",\n icon: \"fas fa-trash\",\n title: trans(\"delete_title\"),\n onclick: () => onDelete(b.id)\n }))))));\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/BackupList', BackupList);","function _arrayLikeToArray(r, a) {\n (null == a || a > r.length) && (a = r.length);\n for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];\n return n;\n}\nexport { _arrayLikeToArray as default };","import arrayWithHoles from \"./arrayWithHoles.js\";\nimport iterableToArrayLimit from \"./iterableToArrayLimit.js\";\nimport unsupportedIterableToArray from \"./unsupportedIterableToArray.js\";\nimport nonIterableRest from \"./nonIterableRest.js\";\nfunction _slicedToArray(r, e) {\n return arrayWithHoles(r) || iterableToArrayLimit(r, e) || unsupportedIterableToArray(r, e) || nonIterableRest();\n}\nexport { _slicedToArray as default };","function _arrayWithHoles(r) {\n if (Array.isArray(r)) return r;\n}\nexport { _arrayWithHoles as default };","function _iterableToArrayLimit(r, l) {\n var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"];\n if (null != t) {\n var e,\n n,\n i,\n u,\n a = [],\n f = !0,\n o = !1;\n try {\n if (i = (t = t.call(r)).next, 0 === l) {\n if (Object(t) !== t) return;\n f = !1;\n } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);\n } catch (r) {\n o = !0, n = r;\n } finally {\n try {\n if (!f && null != t[\"return\"] && (u = t[\"return\"](), Object(u) !== u)) return;\n } finally {\n if (o) throw n;\n }\n }\n return a;\n }\n}\nexport { _iterableToArrayLimit as default };","import arrayLikeToArray from \"./arrayLikeToArray.js\";\nfunction _unsupportedIterableToArray(r, a) {\n if (r) {\n if (\"string\" == typeof r) return arrayLikeToArray(r, a);\n var t = {}.toString.call(r).slice(8, -1);\n return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? arrayLikeToArray(r, a) : void 0;\n }\n}\nexport { _unsupportedIterableToArray as default };","function _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\nexport { _nonIterableRest as default };","import _slicedToArray from \"@babel/runtime/helpers/esm/slicedToArray\";\nimport _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\nimport app from \"flarum/admin/app\";\nimport Modal from \"flarum/common/components/Modal\";\nimport Button from \"flarum/common/components/Button\";\nimport LoadingIndicator from \"flarum/common/components/LoadingIndicator\";\nimport { apiRequest, apiUrl, errorDetail, fmtBytes } from \"../utils/api\";\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.export_modal.\".concat(key), params != null ? params : {});\n\n/**\n * Two-stage modal:\n *\n * 1. Form — admin picks what to include and whether to encrypt.\n * 2. Progress — chunked-tick polling drives a progress bar until the\n * server reports phase=done (or phase=error).\n *\n * The \"encryption to a foreign key\" path lets the operator paste a\n * public key from another Flarum install — useful when preparing an\n * archive for transfer to a different server whose keypair is not the\n * one in *this* config.php.\n */\nexport default class ExportModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"stage\", \"form\");\n _defineProperty(this, \"includeDb\", true);\n _defineProperty(this, \"includeAssets\", true);\n _defineProperty(this, \"includeStorage\", false);\n _defineProperty(this, \"includeExtensions\", false);\n // Per-extension selection. Loaded lazily when the user ticks\n // \"Extensions\" for the first time so we don't fire an extra request\n // for admins who never use the feature.\n _defineProperty(this, \"extensionsLoading\", false);\n _defineProperty(this, \"extensionsLoaded\", false);\n _defineProperty(this, \"extensions\", []);\n _defineProperty(this, \"extensionSelected\", {});\n _defineProperty(this, \"encryptionEnabled\", false);\n _defineProperty(this, \"encryptionUseExternal\", false);\n _defineProperty(this, \"externalPublicKey\", \"\");\n // Target engine the dump should be generated for. Empty string =\n // \"same as source\" (the most common case — backing up to restore\n // onto the same install / a clone of it). The non-empty values\n // make this a cross-engine migration: e.g. dump from MySQL,\n // restore onto Postgres.\n _defineProperty(this, \"targetDialect\", \"\");\n _defineProperty(this, \"starting\", false);\n _defineProperty(this, \"jobId\", null);\n _defineProperty(this, \"status\", null);\n _defineProperty(this, \"polling\", false);\n }\n className() {\n return \"BackupExportModal Modal--medium\";\n }\n title() {\n return trans(\"title\");\n }\n content() {\n if (this.stage === \"form\") return this.formContent();\n return this.progressContent();\n }\n\n // ────────────────────────────────────────── form\n\n formContent() {\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"p\", {\n className: \"helpText\"\n }, trans(\"intro\")), m(\"fieldset\", {\n className: \"BackupExport-fieldset\"\n }, m(\"legend\", null, trans(\"contents_title\")), this.checkbox(\"db\", () => this.includeDb, v => this.includeDb = v), this.checkbox(\"assets\", () => this.includeAssets, v => this.includeAssets = v), this.checkbox(\"storage\", () => this.includeStorage, v => this.includeStorage = v), this.checkbox(\"extensions\", () => this.includeExtensions, v => {\n this.includeExtensions = v;\n // Lazy-load the extension inventory the first time\n // someone ticks the box. The list comes back fast (no\n // disk walking — just metadata from the ExtensionManager).\n if (v && !this.extensionsLoaded) this.loadExtensions();\n }), this.includeExtensions && this.extensionList()), this.includeDb && m(\"fieldset\", {\n className: \"BackupExport-fieldset\"\n }, m(\"legend\", null, trans(\"target_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"target_help\")), m(\"select\", {\n className: \"FormControl BackupExport-targetSelect\",\n value: this.targetDialect,\n onchange: e => {\n this.targetDialect = e.target.value;\n }\n }, m(\"option\", {\n value: \"\"\n }, trans(\"target_same\")), m(\"option\", {\n value: \"mysql\"\n }, trans(\"target_mysql\")), m(\"option\", {\n value: \"mariadb\"\n }, trans(\"target_mariadb\")), m(\"option\", {\n value: \"postgres\"\n }, trans(\"target_postgres\")), m(\"option\", {\n value: \"sqlite\"\n }, trans(\"target_sqlite\")))), m(\"fieldset\", {\n className: \"BackupExport-fieldset\"\n }, m(\"legend\", null, trans(\"encryption_title\")), m(\"label\", {\n className: \"BackupExport-checkbox\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: this.encryptionEnabled,\n onchange: e => {\n this.encryptionEnabled = e.target.checked;\n }\n }), \" \", m(\"span\", null, trans(\"encryption_enable\"))), m(\"p\", {\n className: \"helpText\"\n }, trans(\"encryption_help\")), this.encryptionEnabled && m('[', null, m(\"label\", {\n className: \"BackupExport-checkbox\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: this.encryptionUseExternal,\n onchange: e => {\n this.encryptionUseExternal = e.target.checked;\n }\n }), \" \", m(\"span\", null, trans(\"encryption_external\"))), this.encryptionUseExternal && m('[', null, m(\"p\", {\n className: \"helpText\"\n }, trans(\"encryption_external_help\")), m(\"textarea\", {\n className: \"FormControl BackupExport-keyInput\",\n rows: 3,\n placeholder: \"base64 public key\",\n value: this.externalPublicKey,\n oninput: e => {\n this.externalPublicKey = e.target.value;\n }\n })))), m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--primary\",\n loading: this.starting,\n disabled: this.starting || !this.canStart(),\n onclick: () => this.start()\n }, trans(\"start_button\"))));\n }\n extensionList() {\n if (this.extensionsLoading) {\n return m(\"div\", {\n className: \"BackupExport-extLoading\"\n }, m(LoadingIndicator, null));\n }\n if (!this.extensions.length) {\n return m(\"p\", {\n className: \"helpText BackupExport-extEmpty\"\n }, trans(\"extensions_none\"));\n }\n const groups = {\n workbench: [],\n vendor: [],\n unknown: []\n };\n for (const ext of this.extensions) {\n var _groups$ext$location;\n (_groups$ext$location = groups[ext.location]) == null || _groups$ext$location.push(ext);\n }\n return m(\"div\", {\n className: \"BackupExport-extList\"\n }, m(\"div\", {\n className: \"BackupExport-extActions\"\n }, m(\"button\", {\n type: \"button\",\n className: \"BackupExport-extLink\",\n onclick: () => this.toggleAllExtensions(true)\n }, trans(\"extensions_select_all\")), m(\"span\", null, \" \\xB7 \"), m(\"button\", {\n type: \"button\",\n className: \"BackupExport-extLink\",\n onclick: () => this.toggleAllExtensions(false)\n }, trans(\"extensions_select_none\"))), [\"workbench\", \"vendor\", \"unknown\"].filter(loc => groups[loc].length > 0).map(loc => m(\"div\", {\n className: \"BackupExport-extGroup\",\n key: loc\n }, m(\"div\", {\n className: \"BackupExport-extGroupHeader\"\n }, trans(\"extensions_group_\" + loc), \" \", m(\"span\", {\n className: \"helpText\"\n }, \"(\", groups[loc].length, \")\")), groups[loc].map(ext => m(\"label\", {\n className: \"BackupExport-extRow\",\n key: ext.id\n }, m(\"input\", {\n type: \"checkbox\",\n checked: !!this.extensionSelected[ext.id],\n onchange: e => {\n this.extensionSelected[ext.id] = e.target.checked;\n }\n }), \" \", m(\"span\", {\n className: \"BackupExport-extTitle\"\n }, ext.title), \" \", m(\"code\", {\n className: \"BackupExport-extName\"\n }, ext.name || ext.id), m(\"span\", {\n className: \"BackupExport-extTag BackupExport-extTag--\".concat(ext.location)\n }, trans(\"extensions_tag_\" + ext.location)))))));\n }\n toggleAllExtensions(value) {\n for (const ext of this.extensions) this.extensionSelected[ext.id] = value;\n }\n async loadExtensions() {\n this.extensionsLoading = true;\n try {\n const res = await apiRequest({\n method: \"GET\",\n url: \"\".concat(apiUrl(), \"/backup/extensions\"),\n surface: false\n });\n this.extensions = res.extensions || [];\n this.extensionsLoaded = true;\n // Default: every extension ticked. The admin un-ticks the\n // ones they don't want.\n for (const ext of this.extensions) this.extensionSelected[ext.id] = true;\n } catch (e) {\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"extensions_load_failed\"))));\n } finally {\n this.extensionsLoading = false;\n m.redraw();\n }\n }\n checkbox(key, get, set) {\n return m(\"label\", {\n className: \"BackupExport-checkbox\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: get(),\n onchange: e => {\n set(e.target.checked);\n }\n }), \" \", m(\"span\", {\n className: \"BackupExport-checkbox-label\"\n }, trans(\"content_\" + key)), m(\"span\", {\n className: \"BackupExport-checkbox-help helpText\"\n }, trans(\"content_\" + key + \"_help\")));\n }\n canStart() {\n if (!this.includeDb && !this.includeAssets && !this.includeStorage && !this.includeExtensions) {\n return false;\n }\n if (this.encryptionEnabled && this.encryptionUseExternal && !this.externalPublicKey.trim()) {\n return false;\n }\n return true;\n }\n\n // ────────────────────────────────────────── progress\n\n progressContent() {\n var _s$progress, _s$warnings$length, _s$warnings;\n const s = this.status;\n if (!s) return m(LoadingIndicator, null);\n const isDone = s.phase === \"done\";\n const isError = s.phase === \"error\";\n const pct = Math.max(0, Math.min(100, ((_s$progress = s.progress) == null ? void 0 : _s$progress.percent) || 0));\n return m(\"div\", {\n className: \"Modal-body BackupExport-progress\"\n }, m(\"div\", {\n className: \"BackupExport-status BackupExport-status--\".concat(s.phase)\n }, m(\"strong\", null, trans(\"phase_\" + s.phase)), m(\"p\", null, s.message)), !isError && m('[', null, m(\"div\", {\n className: \"BackupExport-bar\"\n }, m(\"div\", {\n className: \"BackupExport-bar-fill\",\n style: {\n width: \"\".concat(isDone ? 100 : pct, \"%\")\n },\n role: \"progressbar\",\n \"aria-valuenow\": pct,\n \"aria-valuemin\": 0,\n \"aria-valuemax\": 100\n })), m(\"div\", {\n className: \"BackupExport-stats\"\n }, m(\"span\", null, fmtBytes(s.progress.processed_bytes), \" /\", \" \", fmtBytes(s.progress.total_bytes || s.progress.processed_bytes)), s.progress.total_files > 0 && m(\"span\", null, trans(\"files_count\", {\n done: s.progress.processed_files,\n total: s.progress.total_files\n })))), ((_s$warnings$length = (_s$warnings = s.warnings) == null ? void 0 : _s$warnings.length) != null ? _s$warnings$length : 0) > 0 && m(\"div\", {\n className: \"BackupExport-warnings\",\n role: \"alert\"\n }, m(\"div\", {\n className: \"BackupExport-warnings-title\"\n }, m(\"i\", {\n className: \"icon fas fa-triangle-exclamation\"\n }), \" \", trans(\"warnings_title\", {\n count: s.warnings.length\n })), m(\"p\", {\n className: \"helpText\"\n }, trans(\"warnings_help\")), m(\"ul\", {\n className: \"BackupExport-warnings-list\"\n }, s.warnings.map((w, idx) => m(\"li\", {\n key: idx\n }, w)))), m(\"div\", {\n className: \"Form-group BackupExport-progress-actions\"\n }, !isDone && !isError && m(Button, {\n className: \"Button\",\n onclick: () => this.cancel()\n }, trans(\"cancel_button\")), (isDone || isError) && m(Button, {\n className: \"Button Button--primary\",\n onclick: () => this.close()\n }, trans(\"close_button\"))));\n }\n\n // ────────────────────────────────────────── api calls\n\n async start() {\n this.starting = true;\n try {\n // The backend accepts `extensions` as bool OR string[]. When\n // every box is checked we still send the array (explicit),\n // unless the inventory hasn't even loaded yet — which means\n // the admin ticked the section but never opened the list and\n // implicitly wants \"all\".\n let extensionsField = false;\n if (this.includeExtensions) {\n if (!this.extensionsLoaded) {\n extensionsField = true;\n } else {\n const ids = Object.entries(this.extensionSelected).filter(_ref => {\n let _ref2 = _slicedToArray(_ref, 2),\n v = _ref2[1];\n return v;\n }).map(_ref3 => {\n let _ref4 = _slicedToArray(_ref3, 1),\n k = _ref4[0];\n return k;\n });\n extensionsField = ids;\n }\n }\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/exports\"),\n body: {\n contents: {\n db: this.includeDb,\n assets: this.includeAssets,\n storage: this.includeStorage,\n extensions: extensionsField\n },\n encryption: {\n enabled: this.encryptionEnabled,\n public_key: this.encryptionUseExternal ? this.externalPublicKey.trim() : null\n },\n // Empty string = \"same as source\"; the backend treats null\n // and \"\" identically so this carries the user's choice\n // through unambiguously.\n target_dialect: this.targetDialect || null\n },\n surface: false\n });\n this.jobId = res.job_id;\n this.stage = \"progress\";\n this.status = {\n phase: res.phase,\n message: res.message,\n progress: {\n total_bytes: 0,\n processed_bytes: 0,\n total_files: 0,\n processed_files: 0,\n percent: 0\n }\n };\n this.starting = false;\n m.redraw();\n this.pump();\n } catch (e) {\n this.starting = false;\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"start_failed\"))));\n m.redraw();\n }\n }\n async pump() {\n if (this.polling || !this.jobId) return;\n this.polling = true;\n try {\n var _this$status;\n // Sequential ticks — each /tick call performs ~4MB of work.\n while (this.jobId && this.status && this.status.phase !== \"done\" && this.status.phase !== \"error\") {\n try {\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/exports/\").concat(this.jobId, \"/tick\"),\n surface: false\n });\n this.status = res;\n m.redraw();\n } catch (e) {\n // Convert tick failures into a synthetic error phase so the\n // existing UI shows the close button and a meaningful\n // message instead of freezing on the last %.\n const detail = errorDetail(e, String(trans(\"phase_error_network\")));\n this.status = _objectSpread(_objectSpread({}, this.status), {}, {\n phase: \"error\",\n message: detail\n });\n m.redraw();\n break;\n }\n }\n if (((_this$status = this.status) == null ? void 0 : _this$status.phase) === \"done\") {\n app.alerts.show({\n type: \"success\"\n }, trans(\"completed\"));\n this.attrs.onComplete();\n }\n } finally {\n this.polling = false;\n }\n }\n async cancel() {\n if (!this.jobId) return;\n try {\n await apiRequest({\n method: \"DELETE\",\n url: \"\".concat(apiUrl(), \"/backup/exports/\").concat(this.jobId),\n surface: false\n });\n } catch (e) {\n // The job may still be holding a server-side lock — let the user\n // know so they understand if the next export complains.\n console.warn(\"[backup] export cancel failed\", e);\n app.alerts.show({\n type: \"warning\"\n }, trans(\"cancel_failed_warn\"));\n }\n this.close();\n }\n close() {\n this.jobId = null;\n this.hide();\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/ExportModal', ExportModal);","import _slicedToArray from \"@babel/runtime/helpers/esm/slicedToArray\";\nimport _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\nimport app from \"flarum/admin/app\";\nimport Modal from \"flarum/common/components/Modal\";\nimport Button from \"flarum/common/components/Button\";\nimport LoadingIndicator from \"flarum/common/components/LoadingIndicator\";\nimport { apiRequest, apiUrl, errorDetail, fmtBytes } from \"../utils/api\";\n\n/** Abort the upload XHR if no progress event fires for this long. */\nconst UPLOAD_IDLE_TIMEOUT_MS = 60000;\n\n/**\n * Fallback chunk size if /backup/imports init doesn't return one.\n * Server-recommended is 4 MB (see UploadImportController::RECOMMENDED_CHUNK_BYTES).\n */\nconst FALLBACK_CHUNK_BYTES = 4 * 1024 * 1024;\n\n/**\n * How many times to retry a single failed chunk before giving up on\n * the whole upload. Each retry uses the same offset so it overwrites\n * (idempotent). Two retries cover a transient hiccup without\n * spinning forever on a real outage.\n */\nconst CHUNK_RETRY_LIMIT = 2;\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.import_modal.\".concat(key), params != null ? params : {});\n\n/**\n * Three-stage modal:\n * 1. upload — operator picks a `.flarum` file. We POST to /imports\n * and the server validates the header without\n * decrypting.\n * 2. configure — depending on the inspect result we ask for the\n * private key (only when the archive is encrypted)\n * AND a confirm-replace checkbox in all cases. The\n * user agreed in setup that we'd ask at this point.\n * 3. progress — chunked-tick polling drives a progress bar.\n */\nexport default class ImportModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"stage\", \"upload\");\n _defineProperty(this, \"file\", null);\n _defineProperty(this, \"uploading\", false);\n _defineProperty(this, \"uploadProgress\", 0);\n _defineProperty(this, \"uploadIndeterminate\", false);\n _defineProperty(this, \"uploadError\", null);\n _defineProperty(this, \"inspect\", null);\n _defineProperty(this, \"privateKey\", \"\");\n _defineProperty(this, \"confirmReplace\", false);\n _defineProperty(this, \"starting\", false);\n // Section toggles. Default to \"everything that's actually inside the\n // archive\" — the user explicitly opts OUT of pieces they don't want\n // to overwrite. Initialised when the inspect result arrives.\n _defineProperty(this, \"sectionDb\", false);\n _defineProperty(this, \"sectionAssets\", false);\n _defineProperty(this, \"sectionStorage\", false);\n _defineProperty(this, \"sectionExtensions\", false);\n // Per-extension toggles, keyed by directory name from the manifest.\n _defineProperty(this, \"extensionsByName\", {});\n _defineProperty(this, \"status\", null);\n _defineProperty(this, \"polling\", false);\n }\n className() {\n return \"BackupImportModal Modal--medium\";\n }\n title() {\n var _this$status;\n if (this.stage === \"progress\" && ((_this$status = this.status) == null ? void 0 : _this$status.phase) === \"done\") {\n return this.sectionDb ? trans(\"logout_title\") : trans(\"done_title\");\n }\n return trans(\"title\");\n }\n content() {\n if (this.stage === \"upload\") return this.uploadContent();\n if (this.stage === \"configure\") return this.configureContent();\n return this.progressContent();\n }\n\n // ────────────────────────────────────────── upload stage\n\n uploadContent() {\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"div\", {\n className: \"Alert Alert--warning\"\n }, m(\"strong\", null, trans(\"warning_title\")), m(\"p\", null, trans(\"warning_body\"))), m(\"label\", {\n className: \"BackupImport-fileLabel\"\n }, m(\"input\", {\n type: \"file\",\n accept: \".flarum\",\n onchange: e => {\n var _files;\n const f = ((_files = e.target.files) == null ? void 0 : _files[0]) || null;\n this.file = f;\n }\n }), this.file ? m(\"span\", null, this.file.name, \" \", m(\"span\", {\n className: \"helpText\"\n }, \"(\", fmtBytes(this.file.size), \")\")) : m(\"span\", {\n className: \"helpText\"\n }, trans(\"choose_file\"))), this.uploadError && m(\"div\", {\n className: \"Alert Alert--error\"\n }, this.uploadError), this.uploading && m(\"div\", {\n className: \"BackupImport-uploadProgress\"\n }, m(\"div\", {\n className: \"BackupImport-bar\"\n }, m(\"div\", {\n className: \"BackupImport-bar-fill\" + (this.uploadIndeterminate ? \" BackupImport-bar-fill--indeterminate\" : \"\"),\n style: this.uploadIndeterminate ? undefined : {\n width: \"\".concat(Math.max(2, this.uploadProgress), \"%\")\n }\n })), m(\"div\", {\n className: \"BackupImport-uploadStatus helpText\"\n }, this.uploadIndeterminate ? trans(\"inspecting_archive\") : trans(\"uploading_pct\", {\n pct: this.uploadProgress\n }))), m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--primary\",\n loading: this.uploading,\n disabled: this.uploading || !this.file,\n onclick: () => this.upload()\n }, trans(\"upload_button\"))));\n }\n async upload() {\n if (!this.file) return;\n this.uploading = true;\n this.uploadProgress = 0;\n this.uploadIndeterminate = false;\n this.uploadError = null;\n try {\n var _res$meta$manifest;\n // Use a raw XHR so we can show upload progress — Flarum's\n // app.request (mithril's m.request) does not expose\n // `xhr.upload.onprogress`. Once 100% has been sent the server\n // still has to read the header + validate the archive, so we\n // flip to an indeterminate \"Inspecting…\" state until the\n // response comes back.\n const res = await this.uploadWithProgress(this.file, pct => {\n this.uploadProgress = pct;\n if (pct >= 100) this.uploadIndeterminate = true;\n m.redraw();\n });\n this.inspect = res;\n\n // Seed section toggles from what the archive actually contains.\n // Anything missing from the archive can't be ticked anyway, so\n // there's no value in defaulting it to true.\n const contents = res.meta.contents || [];\n this.sectionDb = contents.includes(\"db\");\n this.sectionAssets = contents.includes(\"assets\");\n this.sectionStorage = contents.includes(\"storage\");\n this.sectionExtensions = contents.includes(\"extensions\");\n\n // Normalise to {id} regardless of which manifest version the\n // archive was packed with (string[] vs ArchiveExtensionEntry[]).\n const exts = ((_res$meta$manifest = res.meta.manifest) == null ? void 0 : _res$meta$manifest.extensions) || [];\n this.extensionsByName = {};\n for (const e of exts) {\n const id = typeof e === \"string\" ? e : e.id;\n if (id) this.extensionsByName[id] = true;\n }\n this.stage = \"configure\";\n } catch (e) {\n console.error(\"[backup] archive upload failed\", e);\n this.uploadError = errorDetail(e, String(trans(\"upload_failed\")));\n } finally {\n this.uploading = false;\n m.redraw();\n }\n }\n\n /**\n * Chunked upload + inspect.\n *\n * Single multipart POSTs of multi-GB archives reliably hit server\n * caps (`upload_max_filesize`, `post_max_size`, nginx\n * `client_max_body_size`, `memory_limit` during multipart parsing)\n * and surface as 500s. Instead we do three small requests:\n *\n * 1. POST /backup/imports — init, gets job_id + chunk_size\n * 2. POST /backup/imports/{id}/chunk* — append each slice (loop)\n * 3. POST /backup/imports/{id}/inspect — finalise, return meta\n *\n * Progress is computed from `bytesSent / file.size` across all chunk\n * requests so the bar advances smoothly through the entire file\n * even though each individual request only carries a few MB.\n */\n async uploadWithProgress(file, onProgress) {\n // ─── 1. init ──────────────────────────────────────────────────\n const init = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports\"),\n body: {\n filename: file.name,\n size: file.size\n },\n surface: false\n });\n const jobId = init.job_id;\n const chunkSize = init.chunk_size > 0 ? init.chunk_size : FALLBACK_CHUNK_BYTES;\n\n // ─── 2. chunk loop ────────────────────────────────────────────\n let offset = 0;\n while (offset < file.size) {\n const end = Math.min(offset + chunkSize, file.size);\n const slice = file.slice(offset, end);\n let attempt = 0;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n await this.sendChunk(jobId, offset, slice);\n break;\n } catch (e) {\n attempt++;\n if (attempt > CHUNK_RETRY_LIMIT) throw e;\n // Back off briefly before retrying — gives a transient\n // hiccup a moment to clear without spamming the server.\n await new Promise(r => setTimeout(r, 750 * attempt));\n }\n }\n offset = end;\n const pct = Math.min(99, Math.round(offset / file.size * 100));\n onProgress(pct);\n }\n\n // ─── 3. inspect ───────────────────────────────────────────────\n onProgress(100);\n return apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(jobId, \"/inspect\"),\n surface: false\n });\n }\n\n /**\n * Single-chunk upload as a raw octet-stream POST. We use XHR\n * (rather than fetch) so the per-chunk idle timeout works the\n * same way it did for the old monolithic upload, and so we can\n * pull a detailed error message off any non-2xx response body.\n */\n sendChunk(jobId, offset, slice) {\n return new Promise((resolve, reject) => {\n var _session;\n const xhr = new XMLHttpRequest();\n let lastProgress = Date.now();\n const idleTimer = setInterval(() => {\n if (Date.now() - lastProgress > UPLOAD_IDLE_TIMEOUT_MS) {\n clearInterval(idleTimer);\n xhr.abort();\n }\n }, 5000);\n const stopIdleTimer = () => clearInterval(idleTimer);\n xhr.upload.addEventListener(\"progress\", () => {\n lastProgress = Date.now();\n });\n xhr.addEventListener(\"load\", () => {\n stopIdleTimer();\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n let detail;\n try {\n var _JSON$parse;\n detail = (_JSON$parse = JSON.parse(xhr.responseText)) == null || (_JSON$parse = _JSON$parse.errors) == null || (_JSON$parse = _JSON$parse[0]) == null ? void 0 : _JSON$parse.detail;\n } catch (_unused) {\n /* non-JSON body */\n }\n reject({\n detail: detail || \"\".concat(xhr.status, \" \").concat(xhr.statusText)\n });\n }\n });\n xhr.addEventListener(\"error\", () => {\n stopIdleTimer();\n reject({\n detail: String(trans(\"upload_failed\"))\n });\n });\n xhr.addEventListener(\"abort\", () => {\n stopIdleTimer();\n reject({\n detail: String(trans(\"upload_idle_timeout\"))\n });\n });\n xhr.open(\"POST\", \"\".concat(apiUrl(), \"/backup/imports/\").concat(jobId, \"/chunk\"), true);\n xhr.withCredentials = true;\n xhr.setRequestHeader(\"Content-Type\", \"application/octet-stream\");\n xhr.setRequestHeader(\"X-Chunk-Offset\", String(offset));\n const csrf = (_session = app.session) == null ? void 0 : _session.csrfToken;\n if (csrf) xhr.setRequestHeader(\"X-CSRF-Token\", csrf);\n xhr.send(slice);\n });\n }\n\n // ────────────────────────────────────────── configure stage\n\n configureContent() {\n const i = this.inspect;\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"h4\", null, trans(\"inspect_title\")), m(\"dl\", {\n className: \"BackupImport-meta\"\n }, i.meta.created_at && m('[', null, m(\"dt\", null, trans(\"meta_when\")), m(\"dd\", null, i.meta.created_at)), i.meta.flarum_version && m('[', null, m(\"dt\", null, trans(\"meta_flarum\")), m(\"dd\", null, i.meta.flarum_version)), i.meta.contents && m('[', null, m(\"dt\", null, trans(\"meta_contents\")), m(\"dd\", null, i.meta.contents.join(\", \"))), i.meta.source_url && m('[', null, m(\"dt\", null, trans(\"meta_source_url\")), m(\"dd\", null, m(\"code\", null, i.meta.source_url))), m(\"dt\", null, trans(\"meta_size\")), m(\"dd\", null, fmtBytes(i.size))), m(\"div\", {\n className: \"Alert Alert--info BackupImport-urlNote\"\n }, m(\"i\", {\n className: \"icon fas fa-info-circle\"\n }), \" \", trans(\"url_rewrite_note\")), this.selectionFieldset(i), i.is_encrypted && m(\"fieldset\", {\n className: \"BackupImport-fieldset\"\n }, m(\"legend\", null, trans(\"key_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"key_help\")), m(\"textarea\", {\n className: \"FormControl BackupImport-keyInput\",\n rows: 3,\n placeholder: \"base64 private key\",\n value: this.privateKey,\n oninput: e => {\n this.privateKey = e.target.value;\n }\n }), m(\"p\", {\n className: \"helpText BackupImport-keyHint\"\n }, trans(\"key_hint_local\"))), m(\"div\", {\n className: \"Alert Alert--error BackupImport-confirmAlert\"\n }, m(\"strong\", null, trans(\"confirm_title\")), m(\"p\", null, trans(\"confirm_body\")), m(\"label\", {\n className: \"BackupImport-confirm\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: this.confirmReplace,\n onchange: e => {\n this.confirmReplace = e.target.checked;\n }\n }), \" \", m(\"span\", null, trans(\"confirm_check\")))), m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--primary\",\n loading: this.starting,\n disabled: this.starting || !this.confirmReplace,\n onclick: () => this.startRestore()\n }, trans(\"start_button\"))));\n }\n selectionFieldset(i) {\n const contents = i.meta.contents || [];\n const manifest = i.meta.manifest || {};\n const hasDb = contents.includes(\"db\");\n const hasAssets = contents.includes(\"assets\");\n const hasStorage = contents.includes(\"storage\");\n const hasExtensions = contents.includes(\"extensions\");\n const rawExtList = manifest.extensions || [];\n // Normalise to a uniform shape so the renderer can stay simple,\n // regardless of which manifest version the archive used.\n const extList = rawExtList.map(e => typeof e === \"string\" ? {\n id: e,\n location: \"workbench\"\n } : e);\n return m(\"fieldset\", {\n className: \"BackupImport-fieldset\"\n }, m(\"legend\", null, trans(\"selection_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"selection_help\")), hasDb && this.sectionRow(\"db\", this.sectionDb, v => this.sectionDb = v), hasAssets && this.sectionRow(\"assets\", this.sectionAssets, v => this.sectionAssets = v, manifest.asset_count), hasStorage && this.sectionRow(\"storage\", this.sectionStorage, v => this.sectionStorage = v, manifest.storage_count), hasExtensions && m('[', null, this.sectionRow(\"extensions\", this.sectionExtensions, v => {\n this.sectionExtensions = v;\n // Cascade: turning the section off / on flips every\n // child to match. The user can then untick individuals.\n for (const ext of extList) this.extensionsByName[ext.id] = v;\n }, manifest.extension_count), this.sectionExtensions && manifest.has_composer && m(\"div\", {\n className: \"BackupImport-composerNote helpText\"\n }, m(\"i\", {\n className: \"icon fas fa-cube\"\n }), \" \", trans(\"extensions_composer_note\")), this.sectionExtensions && extList.length > 0 && m(\"div\", {\n className: \"BackupImport-extList\"\n }, extList.map(ext => m(\"label\", {\n className: \"BackupImport-extRow\",\n key: ext.id\n }, m(\"input\", {\n type: \"checkbox\",\n checked: !!this.extensionsByName[ext.id],\n onchange: e => {\n this.extensionsByName[ext.id] = e.target.checked;\n }\n }), \" \", m(\"span\", {\n className: \"BackupImport-extTitle\"\n }, ext.title || ext.id), \" \", ext.name && ext.name !== ext.id && m(\"code\", {\n className: \"BackupImport-extName\"\n }, ext.name), ext.location && m(\"span\", {\n className: \"BackupImport-extTag BackupImport-extTag--\".concat(ext.location)\n }, trans(\"extensions_tag_\" + ext.location)))))));\n }\n sectionRow(key, checked, set, count) {\n return m(\"label\", {\n className: \"BackupImport-sectionRow\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: checked,\n onchange: e => set(e.target.checked)\n }), \" \", m(\"span\", {\n className: \"BackupImport-sectionLabel\"\n }, trans(\"section_\" + key)), count !== undefined && count > 0 && m(\"span\", {\n className: \"BackupImport-sectionCount helpText\"\n }, \" \", \"(\", trans(\"section_count\", {\n count\n }), \")\"));\n }\n buildSelection() {\n // The backend treats `extensions: true` as \"all\" and an array as\n // a whitelist of directory names. When every box is checked, send\n // `true` so the user's intent isn't lost if a new extension shows\n // up between inspect and apply (it shouldn't, but be defensive).\n const extEntries = Object.entries(this.extensionsByName);\n const allChecked = extEntries.length > 0 && extEntries.every(_ref => {\n let _ref2 = _slicedToArray(_ref, 2),\n v = _ref2[1];\n return v;\n });\n const extensionsField = !this.sectionExtensions ? false : allChecked ? true : extEntries.filter(_ref3 => {\n let _ref4 = _slicedToArray(_ref3, 2),\n v = _ref4[1];\n return v;\n }).map(_ref5 => {\n let _ref6 = _slicedToArray(_ref5, 1),\n k = _ref6[0];\n return k;\n });\n return {\n db: this.sectionDb,\n assets: this.sectionAssets,\n storage: this.sectionStorage,\n extensions: extensionsField\n };\n }\n async startRestore() {\n if (!this.inspect) return;\n this.starting = true;\n try {\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id, \"/start\"),\n surface: false,\n body: {\n private_key: this.privateKey.trim() || null,\n confirm_replace: this.confirmReplace,\n selection: this.buildSelection()\n }\n });\n this.stage = \"progress\";\n this.status = {\n phase: res.phase,\n message: res.message,\n progress: {\n total_bytes: this.inspect.size,\n processed_bytes: 0,\n extracted_entries: 0,\n restored_statements: 0,\n percent: 0\n }\n };\n m.redraw();\n this.pump();\n } catch (e) {\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"start_failed\"))));\n } finally {\n this.starting = false;\n }\n }\n\n // ────────────────────────────────────────── progress stage\n\n progressContent() {\n var _s$progress;\n const s = this.status;\n if (!s) return m(LoadingIndicator, null);\n\n // Once the server reports phase=done we hand the screen over to a\n // dedicated completion view: the user has finished waiting and\n // now needs to know what to do next (which differs depending on\n // whether the DB was actually replaced).\n if (s.phase === \"done\") return this.completedContent();\n const isError = s.phase === \"error\";\n const pct = Math.max(0, Math.min(100, ((_s$progress = s.progress) == null ? void 0 : _s$progress.percent) || 0));\n return m(\"div\", {\n className: \"Modal-body BackupImport-progress\"\n }, m(\"div\", {\n className: \"BackupImport-status BackupImport-status--\".concat(s.phase)\n }, m(\"strong\", null, trans(\"phase_\" + s.phase)), m(\"p\", null, s.message)), !isError && m(\"div\", {\n className: \"BackupImport-bar\"\n }, m(\"div\", {\n className: \"BackupImport-bar-fill\",\n style: {\n width: \"\".concat(pct, \"%\")\n }\n })), m(\"div\", {\n className: \"Form-group BackupImport-progress-actions\"\n }, !isError && m(Button, {\n className: \"Button\",\n onclick: () => this.cancel()\n }, trans(\"cancel_button\")), isError && m(Button, {\n className: \"Button Button--primary\",\n onclick: () => this.close()\n }, trans(\"close_button\"))));\n }\n\n /**\n * Replaces the progress UI as soon as `phase === 'done'`. Two\n * shapes:\n *\n * - DB restored — the admin's session was just wiped together\n * with the rest of the `users` / `sessions` tables. We make\n * this very clear and offer a single primary action: reload.\n * Anything else (refreshing the list, dismissing) would race\n * against an invalidated cookie and surface a confusing 401.\n *\n * - Files only — the session is fine, just close.\n */\n completedContent() {\n if (this.sectionDb) {\n return m(\"div\", {\n className: \"Modal-body BackupImport-completed BackupImport-completed--logout\"\n }, m(\"div\", {\n className: \"BackupImport-completedIcon\"\n }, m(\"i\", {\n className: \"fas fa-right-from-bracket\"\n })), m(\"h3\", {\n className: \"BackupImport-completedTitle\"\n }, trans(\"logout_title\")), m(\"p\", {\n className: \"BackupImport-completedBody\"\n }, trans(\"logout_body\")), m(\"ol\", {\n className: \"BackupImport-completedSteps\"\n }, m(\"li\", null, trans(\"logout_step_reload\")), m(\"li\", null, trans(\"logout_step_login\"))), m(Button, {\n className: \"Button Button--primary BackupImport-completedAction\",\n icon: \"fas fa-rotate\",\n onclick: () => window.location.reload()\n }, trans(\"logout_button\")));\n }\n return m(\"div\", {\n className: \"Modal-body BackupImport-completed\"\n }, m(\"div\", {\n className: \"BackupImport-completedIcon BackupImport-completedIcon--success\"\n }, m(\"i\", {\n className: \"fas fa-circle-check\"\n })), m(\"h3\", {\n className: \"BackupImport-completedTitle\"\n }, trans(\"done_title\")), m(\"p\", {\n className: \"BackupImport-completedBody\"\n }, trans(\"done_body\")), m(Button, {\n className: \"Button Button--primary\",\n onclick: () => this.close()\n }, trans(\"close_button\")));\n }\n async pump() {\n if (this.polling || !this.inspect) return;\n this.polling = true;\n try {\n var _this$status2;\n while (this.inspect && this.status && this.status.phase !== \"done\" && this.status.phase !== \"error\") {\n try {\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id, \"/tick\"),\n surface: false,\n // The server never stores the decryption key on disk; we\n // re-send it each tick so the inspect/extract phases can\n // decrypt. Harmless (ignored) for unencrypted archives and\n // once extraction is done.\n body: {\n private_key: this.privateKey.trim() || null\n }\n });\n this.status = res;\n m.redraw();\n } catch (e) {\n // Restore is more dangerous to leave hanging than export —\n // the server may still be mid-write. Surface a synthetic\n // error phase, and explicitly tell the user to verify\n // server state before retrying.\n console.error(\"[backup] import tick failed\", e);\n const detail = errorDetail(e, String(trans(\"phase_error_network\")));\n this.status = _objectSpread(_objectSpread({}, this.status), {}, {\n phase: \"error\",\n message: detail\n });\n m.redraw();\n break;\n }\n }\n if (((_this$status2 = this.status) == null ? void 0 : _this$status2.phase) === \"done\") {\n // Don't refresh the parent panel — when the backup includes\n // the database, restoring it has just replaced the sessions\n // table this admin is authenticated against. Any further API\n // call from this stale session would fail (401 / CSRF) and\n // surface as a confusing \"Oops!\" toast. The user clicks\n // Reload below and gets a clean session.\n app.alerts.show({\n type: \"success\"\n }, trans(\"completed\"));\n if (!this.sectionDb) this.attrs.onComplete();\n }\n } finally {\n this.polling = false;\n }\n }\n async cancel() {\n if (!this.inspect) return;\n try {\n await apiRequest({\n method: \"DELETE\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id),\n surface: false\n });\n } catch (e) {\n console.error(\"[backup] import cancel failed\", e);\n app.alerts.show({\n type: \"warning\"\n }, trans(\"cancel_failed_warn\"));\n }\n this.close();\n }\n close() {\n this.hide();\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/ImportModal', ImportModal);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\nimport app from \"flarum/admin/app\";\nimport Modal from \"flarum/common/components/Modal\";\nimport Button from \"flarum/common/components/Button\";\n/**\n * Dropin replacement for `window.confirm()`. Native confirm breaks the\n * Flarum dark-mode chrome and is silently auto-rejected by some\n * locked-down corporate browsers — this modal stays inside the SPA.\n *\n * Usage:\n * const ok = await confirmAsync({ title, body, danger: true });\n * if (!ok) return;\n */\nexport default class ConfirmModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"resolved\", false);\n }\n className() {\n return \"BackupConfirmModal Modal--small\";\n }\n title() {\n return this.attrs.title;\n }\n content() {\n var _this$attrs$confirmLa, _this$attrs$cancelLab;\n const confirmLabel = (_this$attrs$confirmLa = this.attrs.confirmLabel) != null ? _this$attrs$confirmLa : app.translator.trans(\"ramon-backup.admin.errors.confirm_default\");\n const cancelLabel = (_this$attrs$cancelLab = this.attrs.cancelLabel) != null ? _this$attrs$cancelLab : app.translator.trans(\"ramon-backup.admin.errors.cancel_default\");\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"div\", {\n className: \"BackupConfirmModal-body\"\n }, this.attrs.body), m(\"div\", {\n className: \"Form-group BackupConfirmModal-actions\"\n }, m(Button, {\n className: \"Button \" + (this.attrs.danger ? \"Button--danger\" : \"Button--primary\"),\n onclick: () => this.decide(true)\n }, confirmLabel), m(Button, {\n className: \"Button\",\n onclick: () => this.decide(false)\n }, cancelLabel)));\n }\n decide(confirmed) {\n var _ref;\n if (this.resolved) return;\n this.resolved = true;\n (_ref = confirmed ? this.attrs.onConfirm : this.attrs.onCancel) == null || _ref();\n this.hide();\n }\n\n // Esc key / backdrop click / X button all funnel through Mithril's\n // remove lifecycle. If the user dismissed without picking a button,\n // treat that as a cancel so the awaiting Promise actually resolves.\n onbeforeremove(vnode) {\n if (!this.resolved) {\n var _this$attrs$onCancel, _this$attrs;\n this.resolved = true;\n (_this$attrs$onCancel = (_this$attrs = this.attrs).onCancel) == null || _this$attrs$onCancel.call(_this$attrs);\n }\n return super.onbeforeremove(vnode);\n }\n}\n\n/** Promise wrapper so callers can `await confirmAsync(...)`. */\nexport function confirmAsync(attrs) {\n return new Promise(resolve => {\n let settled = false;\n const settle = v => {\n if (settled) return;\n settled = true;\n resolve(v);\n };\n app.modal.show(ConfirmModal, _objectSpread(_objectSpread({}, attrs), {}, {\n onConfirm: () => settle(true),\n onCancel: () => settle(false)\n }));\n });\n}\nflarum.reg.add('ramon-backup', 'admin/components/ConfirmModal', ConfirmModal);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport app from \"flarum/admin/app\";\nimport Component from \"flarum/common/Component\";\nconst trans = key => app.translator.trans(\"ramon-backup.admin.errors.\".concat(key));\n/**\n * Mithril doesn't have React's Error Boundary — but a tiny vnode\n * wrapper that try/catches `children` rendering covers the same\n * 90% case: a single throw inside a render path won't blow up the\n * whole admin SPA.\n *\n * Limitation: only catches synchronous exceptions during render. Async\n * Promise rejections still need their own try/catch — this is not a\n * substitute for handling failures in event handlers or API calls.\n */\nexport class ErrorBoundary extends Component {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"failed\", false);\n _defineProperty(this, \"lastError\", null);\n }\n view(vnode) {\n if (this.failed) {\n const retry = () => {\n this.failed = false;\n this.lastError = null;\n m.redraw();\n };\n if (vnode.attrs.fallback) return vnode.attrs.fallback(this.lastError, retry);\n return m(\"div\", {\n className: \"Alert Alert--error BackupErrorBoundary\"\n }, m(\"strong\", null, trans(\"boundary_title\")), m(\"p\", null, trans(\"boundary_body\")), m(\"button\", {\n type: \"button\",\n className: \"Button\",\n onclick: retry\n }, trans(\"boundary_retry\")));\n }\n try {\n return vnode.children;\n } catch (err) {\n var _vnode$attrs$onError, _vnode$attrs;\n this.failed = true;\n this.lastError = err;\n (_vnode$attrs$onError = (_vnode$attrs = vnode.attrs).onError) == null || _vnode$attrs$onError.call(_vnode$attrs, err);\n console.error(\"[backup] render boundary caught\", err);\n return null;\n }\n }\n}\nflarum.reg.add('ramon-backup', 'admin/utils/errorBoundary', { ErrorBoundary: ErrorBoundary, });","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport app from \"flarum/admin/app\";\nimport Component from \"flarum/common/Component\";\nimport Button from \"flarum/common/components/Button\";\nimport LoadingIndicator from \"flarum/common/components/LoadingIndicator\";\nimport EncryptionCard from \"./EncryptionCard\";\nimport BackupList from \"./BackupList\";\nimport ExportModal from \"./ExportModal\";\nimport ImportModal from \"./ImportModal\";\nimport { confirmAsync } from \"./ConfirmModal\";\nimport { apiRequest, apiUrl, errorDetail } from \"../utils/api\";\nimport { ErrorBoundary } from \"../utils/errorBoundary\";\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.\".concat(key), params != null ? params : {});\nexport default class BackupPanel extends Component {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"listState\", \"loading\");\n _defineProperty(this, \"listError\", null);\n _defineProperty(this, \"backups\", []);\n }\n oninit(vnode) {\n super.oninit(vnode);\n this.refresh();\n }\n view() {\n return m(\"div\", {\n className: \"BackupPanel\"\n }, m(ErrorBoundary, {\n onError: e => console.error(\"[backup] panel render\", e)\n }, m(\"section\", {\n className: \"BackupPanel-actions\"\n }, m(\"h3\", null, trans(\"panel.actions_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"panel.actions_help\")), m(\"div\", {\n className: \"BackupPanel-actionButtons\"\n }, m(Button, {\n className: \"Button Button--primary\",\n icon: \"fas fa-download\",\n onclick: () => this.openExport()\n }, trans(\"panel.create_button\")), m(Button, {\n className: \"Button\",\n icon: \"fas fa-upload\",\n onclick: () => this.openImport()\n }, trans(\"panel.import_button\")))), m(EncryptionCard, null), m(\"section\", {\n className: \"BackupPanel-list\"\n }, m(\"h3\", null, trans(\"panel.list_title\")), this.renderList())));\n }\n renderList() {\n if (this.listState === \"loading\") return m(LoadingIndicator, null);\n if (this.listState === \"error\") {\n return m(\"div\", {\n className: \"Alert Alert--error BackupPanel-listError\"\n }, m(\"p\", null, trans(\"list.load_failed\")), this.listError && m(\"p\", {\n className: \"helpText\"\n }, m(\"code\", null, this.listError)), m(Button, {\n className: \"Button\",\n icon: \"fas fa-rotate\",\n onclick: () => this.refresh()\n }, trans(\"list.retry\")));\n }\n return m(BackupList, {\n backups: this.backups,\n onDelete: id => this.delete(id),\n onRefresh: () => this.refresh()\n });\n }\n refresh() {\n this.listState = \"loading\";\n this.listError = null;\n return apiRequest({\n method: \"GET\",\n url: \"\".concat(apiUrl(), \"/backup/backups\"),\n surface: false\n }).then(res => {\n this.backups = res.backups || [];\n this.listState = \"ok\";\n }).catch(e => {\n this.backups = [];\n this.listState = \"error\";\n this.listError = errorDetail(e);\n }).then(() => {\n m.redraw();\n });\n }\n openExport() {\n app.modal.show(ExportModal, {\n onComplete: () => this.refresh()\n });\n }\n openImport() {\n app.modal.show(ImportModal, {\n onComplete: () => this.refresh()\n });\n }\n async delete(id) {\n const ok = await confirmAsync({\n title: trans(\"list.confirm_delete_title\"),\n body: trans(\"list.confirm_delete\"),\n confirmLabel: trans(\"list.delete_title\"),\n danger: true\n });\n if (!ok) return;\n try {\n await apiRequest({\n method: \"DELETE\",\n url: \"\".concat(apiUrl(), \"/backup/backups/\").concat(id),\n surface: false,\n fallbackMessage: String(trans(\"list.delete_failed\"))\n });\n app.alerts.show({\n type: \"success\"\n }, trans(\"list.deleted\"));\n this.refresh();\n } catch (e) {\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"list.delete_failed\"))));\n }\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/BackupPanel', BackupPanel);","import app from \"flarum/admin/app\";\nimport { override } from \"flarum/common/extend\";\nimport ExtensionPage from \"flarum/admin/components/ExtensionPage\";\nimport BackupPanel from \"./components/BackupPanel\";\nconst EXT_ID = \"ramon-backup\";\n\n// Hide the default \"Save changes\" submit button on our settings page —\n// every action here happens through dedicated buttons (export now, key\n// rotation, etc.), there's no batch settings save to perform.\noverride(ExtensionPage.prototype, \"submitButton\", function (original) {\n if (this.extension && this.extension.id === EXT_ID) return null;\n return original();\n});\napp.initializers.add(EXT_ID, () => {\n app.registry.for(EXT_ID).registerSetting(() => m(BackupPanel, null), 100, \"ramon-backup.panel\").registerPermission({\n icon: \"fas fa-file-archive\",\n label: app.translator.trans(\"ramon-backup.admin.permissions.manage_label\"),\n permission: \"backup.manage\"\n }, \"moderate\");\n});"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","app_namespaceObject","flarum","reg","extend_namespaceObject","ExtensionPage_namespaceObject","_typeof","Symbol","iterator","constructor","_defineProperty","e","r","t","i","toPrimitive","TypeError","String","toPropertyKey","value","configurable","writable","Component_namespaceObject","Button_namespaceObject","LoadingIndicator_namespaceObject","Modal_namespaceObject","extractText_namespaceObject","apiUrl","app_default","forum","attribute","replace","fmtBytes","bytes","Number","isFinite","units","n","length","toFixed","errorDetail","raw","fallback","_ref","_raw$response$errors$","_raw$response","detail","response","errors","message","undefined","translator","trans","async","apiRequest","opts","request","fallbackMessage","console","error","method","url","surface","alerts","show","type","_unused","add","params","concat","KeypairRevealModal","Modal_default","super","arguments","this","className","title","content","_this$attrs","attrs","privateKey","configKey","snippet","m","Button_default","icon","onclick","copy","copied","hide","navigator","clipboard","writeText","then","redraw","setTimeout","catch","err","RegenerateConfirmModal","checked","acknowledged","onchange","target","loading","submitting","disabled","submit","onConfirm","EncryptionCard","Component_default","oninit","vnode","refresh","view","loadState","LoadingIndicator_default","loadError","body","status","s","available","statusBadge","has_public_key","private_key_present","healthy","generate","keys_match","config_key","publicKeyPanel","public_key","publicKey","extractText_default","copyPublic","publicCopied","openRegenerate","kind","present","res","acknowledgeLoss","acknowledge_loss","modal","private_key","humanTime_namespaceObject","DIALECT_LABEL","mysql","mariadb","postgres","sqlite","BackupList_trans","BackupList","_vnode$attrs","backups","onDelete","map","b","id","created_at","humanTime_default","Date","filename","target_dialect","engine","size_bytes","contents","c","encrypted","href","_arrayLikeToArray","Array","_slicedToArray","isArray","arrayWithHoles","l","u","f","next","done","push","iterableToArrayLimit","arrayLikeToArray","toString","slice","name","from","test","unsupportedIterableToArray","nonIterableRest","ownKeys","keys","getOwnPropertySymbols","filter","getOwnPropertyDescriptor","apply","_objectSpread","forEach","getOwnPropertyDescriptors","defineProperties","ExportModal_trans","ExportModal","stage","formContent","progressContent","checkbox","includeDb","v","includeAssets","includeStorage","includeExtensions","extensionsLoaded","loadExtensions","extensionList","targetDialect","encryptionEnabled","encryptionUseExternal","rows","placeholder","externalPublicKey","oninput","starting","canStart","start","extensionsLoading","extensions","groups","workbench","vendor","unknown","ext","_groups$ext$location","location","toggleAllExtensions","loc","extensionSelected","set","trim","_s$progress","_s$warnings$length","_s$warnings","isDone","phase","isError","pct","Math","max","min","progress","percent","style","width","role","processed_bytes","total_bytes","total_files","processed_files","total","warnings","count","w","idx","cancel","close","extensionsField","entries","_ref3","db","assets","storage","encryption","enabled","jobId","job_id","pump","polling","_this$status","onComplete","warn","ImportModal_ownKeys","ImportModal_objectSpread","ImportModal_trans","ImportModal","sectionDb","uploadContent","configureContent","accept","_files","files","file","size","uploadError","uploading","uploadIndeterminate","uploadProgress","upload","_res$meta$manifest","uploadWithProgress","inspect","meta","includes","sectionAssets","sectionStorage","sectionExtensions","exts","manifest","extensionsByName","onProgress","init","chunkSize","chunk_size","offset","end","attempt","sendChunk","Promise","round","resolve","reject","_session","xhr","XMLHttpRequest","lastProgress","now","idleTimer","setInterval","clearInterval","abort","stopIdleTimer","addEventListener","_JSON$parse","JSON","parse","responseText","statusText","open","withCredentials","setRequestHeader","csrf","session","csrfToken","send","flarum_version","join","source_url","selectionFieldset","is_encrypted","confirmReplace","startRestore","hasDb","hasAssets","hasStorage","hasExtensions","extList","sectionRow","asset_count","storage_count","extension_count","has_composer","buildSelection","extEntries","allChecked","every","_ref5","confirm_replace","selection","extracted_entries","restored_statements","completedContent","window","reload","_this$status2","ConfirmModal_ownKeys","ConfirmModal_objectSpread","ConfirmModal","_this$attrs$confirmLa","_this$attrs$cancelLab","confirmLabel","cancelLabel","danger","decide","confirmed","resolved","onCancel","onbeforeremove","_this$attrs$onCancel","errorBoundary_trans","ErrorBoundary","failed","retry","lastError","children","_vnode$attrs$onError","onError","BackupPanel_trans","BackupPanel","openExport","openImport","renderList","listState","listError","delete","onRefresh","settled","settle","EXT_ID","override","ExtensionPage_default","original","extension","initializers","registry","for","registerSetting","registerPermission","label","permission"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"admin.js","mappings":"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","sources":["webpack://@ramon/backup/webpack/bootstrap","webpack://@ramon/backup/webpack/runtime/compat get default export","webpack://@ramon/backup/webpack/runtime/define property getters","webpack://@ramon/backup/webpack/runtime/hasOwnProperty shorthand","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'admin/app')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/extend')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'admin/components/ExtensionPage')\"","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/typeof.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/defineProperty.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/toPropertyKey.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/toPrimitive.js","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/Component')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/components/Button')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/components/LoadingIndicator')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/components/Modal')\"","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/utils/extractText')\"","webpack://@ramon/backup/./src/admin/utils/api.ts","webpack://@ramon/backup/./src/admin/components/EncryptionCard.tsx","webpack://@ramon/backup/external root \"flarum.reg.get('core', 'common/helpers/humanTime')\"","webpack://@ramon/backup/./src/admin/components/BackupList.tsx","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/arrayLikeToArray.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/slicedToArray.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/arrayWithHoles.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/iterableToArrayLimit.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/unsupportedIterableToArray.js","webpack://@ramon/backup/./node_modules/@babel/runtime/helpers/esm/nonIterableRest.js","webpack://@ramon/backup/./src/admin/components/ExportModal.tsx","webpack://@ramon/backup/./src/admin/components/ImportModal.tsx","webpack://@ramon/backup/./src/admin/components/ConfirmModal.tsx","webpack://@ramon/backup/./src/admin/utils/errorBoundary.tsx","webpack://@ramon/backup/./src/admin/components/BackupPanel.tsx","webpack://@ramon/backup/./src/admin/index.tsx"],"sourcesContent":["// The require scope\nconst __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tconst getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter/value functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tif(Array.isArray(definition)) {\n\t\tvar i = 0;\n\t\twhile(i < definition.length) {\n\t\t\tvar key = definition[i++];\n\t\t\tvar binding = definition[i++];\n\t\t\tif(!__webpack_require__.o(exports, key)) {\n\t\t\t\tif(binding === 0) {\n\t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, value: definition[i++] });\n\t\t\t\t} else {\n\t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: binding });\n\t\t\t\t}\n\t\t\t} else if(binding === 0) { i++; }\n\t\t}\n\t} else {\n\t\tfor(var key in definition) {\n\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t\t}\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'admin/app');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/extend');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'admin/components/ExtensionPage');","function _typeof(o) {\n \"@babel/helpers - typeof\";\n\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n return typeof o;\n } : function (o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\nexport { _typeof as default };","import toPropertyKey from \"./toPropertyKey.js\";\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\nexport { _defineProperty as default };","import _typeof from \"./typeof.js\";\nimport toPrimitive from \"./toPrimitive.js\";\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\nexport { toPropertyKey as default };","import _typeof from \"./typeof.js\";\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\nexport { toPrimitive as default };","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/Component');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Button');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/LoadingIndicator');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Modal');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/utils/extractText');","import app from \"flarum/admin/app\";\n/** Trim trailing slashes off the configured API URL. */\nexport function apiUrl() {\n return (app.forum.attribute(\"apiUrl\") || \"/api\").replace(/\\/+$/, \"\");\n}\nexport function fmtBytes(bytes) {\n if (!Number.isFinite(bytes) || bytes <= 0) return \"0 B\";\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let i = 0;\n let n = bytes;\n while (n >= 1024 && i < units.length - 1) {\n n /= 1024;\n i++;\n }\n return n.toFixed(n >= 100 || i === 0 ? 0 : 1) + \" \" + units[i];\n}\n\n/** Best-effort extraction of a human-readable detail from a RequestError or thrown object. */\nexport function errorDetail(raw, fallback) {\n var _ref, _raw$response$errors$, _raw$response;\n const detail = (_ref = (_raw$response$errors$ = raw == null || (_raw$response = raw.response) == null || (_raw$response = _raw$response.errors) == null || (_raw$response = _raw$response[0]) == null ? void 0 : _raw$response.detail) != null ? _raw$response$errors$ : raw == null ? void 0 : raw.detail) != null ? _ref : typeof (raw == null ? void 0 : raw.message) === \"string\" ? raw.message : undefined;\n if (detail) return String(detail);\n if (fallback) return fallback;\n return String(app.translator.trans(\"ramon-backup.admin.errors.generic\"));\n}\n/**\n * Wrapper around `app.request` that:\n * - logs every failure to console.error with action + URL;\n * - extracts the JSON:API error detail when present;\n * - shows a Flarum alert (unless `surface: false`);\n * - rethrows so the caller can still branch on the failure.\n *\n * Centralised so we don't reinvent error extraction at every call-site.\n */\nexport async function apiRequest(opts) {\n try {\n return await app.request(opts);\n } catch (raw) {\n const detail = errorDetail(raw, opts.fallbackMessage);\n console.error(\"[backup] api error\", opts.method, opts.url, raw);\n if (opts.surface !== false) {\n app.alerts.show({\n type: \"error\"\n }, detail);\n }\n if (raw && typeof raw === \"object\" && !raw.detail) {\n try {\n raw.detail = detail;\n } catch (_unused) {\n /* read-only, ignore */\n }\n }\n throw raw;\n }\n}\nflarum.reg.add('ramon-backup', 'admin/utils/api', { apiUrl: apiUrl,fmtBytes: fmtBytes,errorDetail: errorDetail, });","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport app from \"flarum/admin/app\";\nimport Component from \"flarum/common/Component\";\nimport Button from \"flarum/common/components/Button\";\nimport Modal from \"flarum/common/components/Modal\";\nimport LoadingIndicator from \"flarum/common/components/LoadingIndicator\";\nimport extractText from \"flarum/common/utils/extractText\";\nimport { apiRequest, apiUrl, errorDetail } from \"../utils/api\";\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.encryption.\".concat(key), params != null ? params : {});\nclass KeypairRevealModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"copied\", false);\n }\n className() {\n return \"BackupRevealModal Modal--medium\";\n }\n title() {\n return trans(\"reveal_modal.title\");\n }\n content() {\n const _this$attrs = this.attrs,\n privateKey = _this$attrs.privateKey,\n configKey = _this$attrs.configKey;\n const snippet = \"'\".concat(configKey, \"' => '\").concat(privateKey, \"',\");\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"p\", null, trans(\"reveal_modal.intro\")), m(\"div\", {\n className: \"Alert Alert--error\"\n }, m(\"strong\", null, trans(\"reveal_modal.warning_title\")), m(\"p\", null, trans(\"reveal_modal.warning_body\"))), m(\"label\", {\n className: \"BackupReveal-label\"\n }, trans(\"reveal_modal.snippet_label\")), m(\"pre\", {\n className: \"BackupReveal-snippet\"\n }, m(\"code\", null, snippet)), m(\"div\", {\n className: \"Form-group BackupReveal-actions\"\n }, m(Button, {\n className: \"Button\",\n icon: \"fas fa-copy\",\n onclick: () => this.copy(snippet)\n }, this.copied ? trans(\"reveal_modal.copied\") : trans(\"reveal_modal.copy_button\")), m(Button, {\n className: \"Button Button--primary\",\n onclick: () => this.hide()\n }, trans(\"reveal_modal.close\"))));\n }\n copy(snippet) {\n if (!navigator.clipboard) {\n app.alerts.show({\n type: \"error\"\n }, trans(\"clipboard_unavailable\"));\n return;\n }\n navigator.clipboard.writeText(snippet).then(() => {\n this.copied = true;\n m.redraw();\n setTimeout(() => {\n this.copied = false;\n m.redraw();\n }, 2000);\n }).catch(err => {\n console.error(\"[backup] clipboard writeText failed\", err);\n app.alerts.show({\n type: \"error\"\n }, trans(\"clipboard_failed\"));\n });\n }\n}\nclass RegenerateConfirmModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"acknowledged\", false);\n _defineProperty(this, \"submitting\", false);\n }\n className() {\n return \"BackupRegenerateModal Modal--medium\";\n }\n title() {\n return trans(\"regenerate_modal.title\");\n }\n content() {\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"div\", {\n className: \"Alert Alert--error\"\n }, m(\"p\", null, trans(\"regenerate_modal.warning\"))), m(\"label\", {\n className: \"BackupRegenerate-confirm\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: this.acknowledged,\n onchange: e => {\n this.acknowledged = e.target.checked;\n }\n }), \" \", trans(\"regenerate_modal.acknowledge\")), m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--primary\",\n loading: this.submitting,\n disabled: !this.acknowledged || this.submitting,\n onclick: () => this.submit()\n }, trans(\"regenerate_modal.submit\"))));\n }\n async submit() {\n this.submitting = true;\n m.redraw();\n try {\n await this.attrs.onConfirm();\n this.hide();\n } catch (_unused) {\n // Parent already showed an error toast — keep the modal open so\n // the user can retry without re-acknowledging the warning.\n this.submitting = false;\n m.redraw();\n }\n }\n}\nexport default class EncryptionCard extends Component {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"status\", null);\n _defineProperty(this, \"loadState\", \"loading\");\n _defineProperty(this, \"loadError\", null);\n _defineProperty(this, \"publicCopied\", false);\n }\n oninit(vnode) {\n super.oninit(vnode);\n this.refresh();\n }\n view() {\n return m(\"section\", {\n className: \"BackupEncryptionCard\"\n }, m(\"header\", null, m(\"h3\", null, trans(\"section_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"section_help\"))), this.loadState === \"loading\" && m(LoadingIndicator, null), this.loadState === \"error\" && m(\"div\", {\n className: \"Alert Alert--error BackupEncryption-loadError\"\n }, m(\"p\", null, trans(\"status.load_failed\")), this.loadError && m(\"p\", {\n className: \"helpText\"\n }, m(\"code\", null, this.loadError)), m(Button, {\n className: \"Button\",\n icon: \"fas fa-rotate\",\n onclick: () => this.refresh()\n }, trans(\"status.retry\"))), this.loadState === \"ok\" && this.body());\n }\n body() {\n if (!this.status) return m(\"p\", {\n className: \"helpText\"\n }, trans(\"status.unknown\"));\n const s = this.status;\n if (!s.available) {\n return m(\"div\", {\n className: \"Alert Alert--error\"\n }, trans(\"status.libsodium_missing\"));\n }\n return m('[', null, m(\"div\", {\n className: \"BackupEncryption-statusRow\"\n }, this.statusBadge(\"public\", s.has_public_key), this.statusBadge(\"private\", s.private_key_present)), s.healthy && m(\"div\", {\n className: \"Alert Alert--success\"\n }, trans(\"status.healthy\")), !s.has_public_key && !s.private_key_present && m(\"div\", null, m(\"p\", {\n className: \"helpText\"\n }, trans(\"status.not_setup\")), m(Button, {\n className: \"Button Button--primary\",\n icon: \"fas fa-key\",\n onclick: () => this.generate(false)\n }, trans(\"actions.generate\"))), s.has_public_key && s.private_key_present && s.keys_match === false && m(\"div\", {\n className: \"Alert Alert--error\"\n }, m(\"strong\", null, trans(\"status.mismatch_title\")), m(\"p\", null, trans(\"status.mismatch_body\")), m(\"p\", null, m(\"code\", null, \"'\", s.config_key, \"'\"))), s.has_public_key && !s.private_key_present && m(\"div\", {\n className: \"Alert Alert--error\"\n }, m(\"strong\", null, trans(\"status.private_missing_title\")), m(\"p\", null, trans(\"status.private_missing_body\")), m(\"p\", null, m(\"code\", null, \"'\", s.config_key, \"'\"))), s.has_public_key && this.publicKeyPanel(s.public_key || \"\", s.healthy));\n }\n publicKeyPanel(publicKey, healthy) {\n return m(\"div\", {\n className: \"BackupEncryption-publicKey\"\n }, m(\"label\", null, trans(\"public_key.label\")), m(\"div\", {\n className: \"BackupEncryption-publicKeyRow\"\n }, m(\"pre\", null, m(\"code\", null, publicKey)), m(Button, {\n className: \"Button Button--icon\",\n icon: \"fas fa-copy\",\n title: extractText(trans(\"public_key.copy_title\")),\n onclick: () => this.copyPublic(publicKey)\n }, this.publicCopied ? extractText(trans(\"public_key.copied\")) : \"\")), m(\"p\", {\n className: \"helpText\"\n }, healthy ? trans(\"public_key.help_healthy\") : trans(\"public_key.help_broken\")), m(Button, {\n className: \"Button Button--danger\",\n icon: \"fas fa-rotate\",\n onclick: () => this.openRegenerate()\n }, trans(\"public_key.remove_button\")));\n }\n statusBadge(kind, present) {\n return m(\"div\", {\n className: \"BackupEncryption-badge BackupEncryption-badge--\".concat(present ? \"ok\" : \"missing\")\n }, m(\"i\", {\n className: \"icon fas fa-\".concat(present ? \"check\" : \"times\")\n }), m(\"span\", null, trans(\"status.\".concat(kind, \"_key_label\"))), m(\"span\", {\n className: \"BackupEncryption-badgeState\"\n }, trans(\"status.\".concat(present ? \"present\" : \"absent\"))));\n }\n copyPublic(publicKey) {\n if (!publicKey) return;\n if (!navigator.clipboard) {\n app.alerts.show({\n type: \"error\"\n }, trans(\"clipboard_unavailable\"));\n return;\n }\n navigator.clipboard.writeText(publicKey).then(() => {\n this.publicCopied = true;\n m.redraw();\n setTimeout(() => {\n this.publicCopied = false;\n m.redraw();\n }, 2000);\n }).catch(err => {\n console.error(\"[backup] clipboard writeText failed\", err);\n app.alerts.show({\n type: \"error\"\n }, trans(\"clipboard_failed\"));\n });\n }\n refresh() {\n this.loadState = \"loading\";\n this.loadError = null;\n return apiRequest({\n method: \"GET\",\n url: \"\".concat(apiUrl(), \"/backup/encryption/status\"),\n surface: false\n }).then(res => {\n this.status = res;\n this.loadState = \"ok\";\n }).catch(e => {\n this.status = null;\n this.loadState = \"error\";\n this.loadError = errorDetail(e);\n }).then(() => {\n m.redraw();\n });\n }\n async generate(acknowledgeLoss) {\n try {\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/encryption/generate-keypair\"),\n body: {\n acknowledge_loss: acknowledgeLoss\n },\n surface: false\n });\n await this.refresh();\n app.modal.show(KeypairRevealModal, {\n privateKey: res.private_key,\n configKey: res.config_key\n });\n } catch (e) {\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"actions.generate_failed\"))));\n throw e;\n }\n }\n openRegenerate() {\n app.modal.show(RegenerateConfirmModal, {\n onConfirm: () => this.generate(true)\n });\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/EncryptionCard', EncryptionCard);","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/helpers/humanTime');","import app from \"flarum/admin/app\";\nimport Component from \"flarum/common/Component\";\nimport Button from \"flarum/common/components/Button\";\nimport humanTime from \"flarum/common/helpers/humanTime\";\nimport { apiUrl, fmtBytes } from \"../utils/api\";\nconst DIALECT_LABEL = {\n mysql: \"MySQL\",\n mariadb: \"MariaDB\",\n postgres: \"PostgreSQL\",\n sqlite: \"SQLite\"\n};\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.list.\".concat(key), params != null ? params : {});\nexport default class BackupList extends Component {\n view(vnode) {\n const _vnode$attrs = vnode.attrs,\n backups = _vnode$attrs.backups,\n onDelete = _vnode$attrs.onDelete;\n if (!backups.length) {\n return m(\"p\", {\n className: \"BackupList-empty helpText\"\n }, trans(\"empty\"));\n }\n return m(\"table\", {\n className: \"BackupList Table\"\n }, m(\"thead\", null, m(\"tr\", null, m(\"th\", null, trans(\"col_when\")), m(\"th\", null, trans(\"col_size\")), m(\"th\", null, trans(\"col_contents\")), m(\"th\", null, trans(\"col_status\")), m(\"th\", null))), m(\"tbody\", null, backups.map(b => m(\"tr\", {\n key: b.id,\n className: \"BackupList-row\"\n }, m(\"td\", null, m(\"div\", {\n className: \"BackupList-when\"\n }, b.created_at ? humanTime(new Date(b.created_at)) : \"—\"), m(\"div\", {\n className: \"BackupList-filename\"\n }, b.filename), b.target_dialect &&\n // Only shown when the admin retargeted the dump at\n // export time — same-engine backups have a NULL\n // target_dialect and don't need the visual noise.\n m(\"div\", {\n className: \"BackupList-target BackupList-target--\".concat(b.target_dialect),\n title: String(trans(\"target_tooltip\", {\n engine: DIALECT_LABEL[b.target_dialect] || b.target_dialect\n }))\n }, m(\"i\", {\n className: \"icon fas fa-arrow-right-arrow-left\"\n }), \" \", trans(\"target_for\", {\n engine: DIALECT_LABEL[b.target_dialect] || b.target_dialect\n }))), m(\"td\", null, fmtBytes(b.size_bytes)), m(\"td\", null, b.contents.map(c => m(\"span\", {\n className: \"BackupList-tag BackupList-tag--\".concat(c)\n }, trans(\"content_\" + c)))), m(\"td\", null, b.encrypted ? m(\"span\", {\n className: \"BackupList-encryption BackupList-encryption--on\"\n }, m(\"i\", {\n className: \"icon fas fa-lock\"\n }), \" \", trans(\"encrypted\")) : m(\"span\", {\n className: \"BackupList-encryption BackupList-encryption--off\"\n }, m(\"i\", {\n className: \"icon fas fa-lock-open\"\n }), \" \", trans(\"plain\"))), m(\"td\", {\n className: \"BackupList-actions\"\n }, m(\"a\", {\n className: \"Button Button--icon\",\n href: \"\".concat(apiUrl(), \"/backup/backups/\").concat(b.id, \"/download\"),\n target: \"_blank\",\n title: String(trans(\"download_title\"))\n }, m(\"i\", {\n className: \"icon fas fa-download\"\n })), m(Button, {\n className: \"Button Button--icon Button--danger\",\n icon: \"fas fa-trash\",\n title: trans(\"delete_title\"),\n onclick: () => onDelete(b.id)\n }))))));\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/BackupList', BackupList);","function _arrayLikeToArray(r, a) {\n (null == a || a > r.length) && (a = r.length);\n for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];\n return n;\n}\nexport { _arrayLikeToArray as default };","import arrayWithHoles from \"./arrayWithHoles.js\";\nimport iterableToArrayLimit from \"./iterableToArrayLimit.js\";\nimport unsupportedIterableToArray from \"./unsupportedIterableToArray.js\";\nimport nonIterableRest from \"./nonIterableRest.js\";\nfunction _slicedToArray(r, e) {\n return arrayWithHoles(r) || iterableToArrayLimit(r, e) || unsupportedIterableToArray(r, e) || nonIterableRest();\n}\nexport { _slicedToArray as default };","function _arrayWithHoles(r) {\n if (Array.isArray(r)) return r;\n}\nexport { _arrayWithHoles as default };","function _iterableToArrayLimit(r, l) {\n var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"];\n if (null != t) {\n var e,\n n,\n i,\n u,\n a = [],\n f = !0,\n o = !1;\n try {\n if (i = (t = t.call(r)).next, 0 === l) {\n if (Object(t) !== t) return;\n f = !1;\n } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);\n } catch (r) {\n o = !0, n = r;\n } finally {\n try {\n if (!f && null != t[\"return\"] && (u = t[\"return\"](), Object(u) !== u)) return;\n } finally {\n if (o) throw n;\n }\n }\n return a;\n }\n}\nexport { _iterableToArrayLimit as default };","import arrayLikeToArray from \"./arrayLikeToArray.js\";\nfunction _unsupportedIterableToArray(r, a) {\n if (r) {\n if (\"string\" == typeof r) return arrayLikeToArray(r, a);\n var t = {}.toString.call(r).slice(8, -1);\n return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? arrayLikeToArray(r, a) : void 0;\n }\n}\nexport { _unsupportedIterableToArray as default };","function _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\nexport { _nonIterableRest as default };","import _slicedToArray from \"@babel/runtime/helpers/esm/slicedToArray\";\nimport _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\nimport app from \"flarum/admin/app\";\nimport Modal from \"flarum/common/components/Modal\";\nimport Button from \"flarum/common/components/Button\";\nimport LoadingIndicator from \"flarum/common/components/LoadingIndicator\";\nimport { apiRequest, apiUrl, errorDetail, fmtBytes } from \"../utils/api\";\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.export_modal.\".concat(key), params != null ? params : {});\n\n/**\n * Two-stage modal:\n *\n * 1. Form — admin picks what to include and whether to encrypt.\n * 2. Progress — chunked-tick polling drives a progress bar until the\n * server reports phase=done (or phase=error).\n *\n * The \"encryption to a foreign key\" path lets the operator paste a\n * public key from another Flarum install — useful when preparing an\n * archive for transfer to a different server whose keypair is not the\n * one in *this* config.php.\n */\nexport default class ExportModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"stage\", \"form\");\n _defineProperty(this, \"includeDb\", true);\n _defineProperty(this, \"includeAssets\", true);\n _defineProperty(this, \"includeStorage\", false);\n _defineProperty(this, \"includeExtensions\", false);\n // Per-extension selection. Loaded lazily when the user ticks\n // \"Extensions\" for the first time so we don't fire an extra request\n // for admins who never use the feature.\n _defineProperty(this, \"extensionsLoading\", false);\n _defineProperty(this, \"extensionsLoaded\", false);\n _defineProperty(this, \"extensions\", []);\n _defineProperty(this, \"extensionSelected\", {});\n _defineProperty(this, \"encryptionEnabled\", false);\n _defineProperty(this, \"encryptionUseExternal\", false);\n _defineProperty(this, \"externalPublicKey\", \"\");\n // Target engine the dump should be generated for. Empty string =\n // \"same as source\" (the most common case — backing up to restore\n // onto the same install / a clone of it). The non-empty values\n // make this a cross-engine migration: e.g. dump from MySQL,\n // restore onto Postgres.\n _defineProperty(this, \"targetDialect\", \"\");\n _defineProperty(this, \"starting\", false);\n _defineProperty(this, \"jobId\", null);\n _defineProperty(this, \"status\", null);\n _defineProperty(this, \"polling\", false);\n }\n className() {\n return \"BackupExportModal Modal--medium\";\n }\n title() {\n return trans(\"title\");\n }\n content() {\n if (this.stage === \"form\") return this.formContent();\n return this.progressContent();\n }\n\n // ────────────────────────────────────────── form\n\n formContent() {\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"p\", {\n className: \"helpText\"\n }, trans(\"intro\")), m(\"fieldset\", {\n className: \"BackupExport-fieldset\"\n }, m(\"legend\", null, trans(\"contents_title\")), this.checkbox(\"db\", () => this.includeDb, v => this.includeDb = v), this.checkbox(\"assets\", () => this.includeAssets, v => this.includeAssets = v), this.checkbox(\"storage\", () => this.includeStorage, v => this.includeStorage = v), this.checkbox(\"extensions\", () => this.includeExtensions, v => {\n this.includeExtensions = v;\n // Lazy-load the extension inventory the first time\n // someone ticks the box. The list comes back fast (no\n // disk walking — just metadata from the ExtensionManager).\n if (v && !this.extensionsLoaded) this.loadExtensions();\n }), this.includeExtensions && this.extensionList()), this.includeDb && m(\"fieldset\", {\n className: \"BackupExport-fieldset\"\n }, m(\"legend\", null, trans(\"target_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"target_help\")), m(\"select\", {\n className: \"FormControl BackupExport-targetSelect\",\n value: this.targetDialect,\n onchange: e => {\n this.targetDialect = e.target.value;\n }\n }, m(\"option\", {\n value: \"\"\n }, trans(\"target_same\")), m(\"option\", {\n value: \"mysql\"\n }, trans(\"target_mysql\")), m(\"option\", {\n value: \"mariadb\"\n }, trans(\"target_mariadb\")), m(\"option\", {\n value: \"postgres\"\n }, trans(\"target_postgres\")), m(\"option\", {\n value: \"sqlite\"\n }, trans(\"target_sqlite\")))), m(\"fieldset\", {\n className: \"BackupExport-fieldset\"\n }, m(\"legend\", null, trans(\"encryption_title\")), m(\"label\", {\n className: \"BackupExport-checkbox\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: this.encryptionEnabled,\n onchange: e => {\n this.encryptionEnabled = e.target.checked;\n }\n }), \" \", m(\"span\", null, trans(\"encryption_enable\"))), m(\"p\", {\n className: \"helpText\"\n }, trans(\"encryption_help\")), this.encryptionEnabled && m('[', null, m(\"label\", {\n className: \"BackupExport-checkbox\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: this.encryptionUseExternal,\n onchange: e => {\n this.encryptionUseExternal = e.target.checked;\n }\n }), \" \", m(\"span\", null, trans(\"encryption_external\"))), this.encryptionUseExternal && m('[', null, m(\"p\", {\n className: \"helpText\"\n }, trans(\"encryption_external_help\")), m(\"textarea\", {\n className: \"FormControl BackupExport-keyInput\",\n rows: 3,\n placeholder: \"base64 public key\",\n value: this.externalPublicKey,\n oninput: e => {\n this.externalPublicKey = e.target.value;\n }\n })))), m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--primary\",\n loading: this.starting,\n disabled: this.starting || !this.canStart(),\n onclick: () => this.start()\n }, trans(\"start_button\"))));\n }\n extensionList() {\n if (this.extensionsLoading) {\n return m(\"div\", {\n className: \"BackupExport-extLoading\"\n }, m(LoadingIndicator, null));\n }\n if (!this.extensions.length) {\n return m(\"p\", {\n className: \"helpText BackupExport-extEmpty\"\n }, trans(\"extensions_none\"));\n }\n const groups = {\n workbench: [],\n vendor: [],\n unknown: []\n };\n for (const ext of this.extensions) {\n var _groups$ext$location;\n (_groups$ext$location = groups[ext.location]) == null || _groups$ext$location.push(ext);\n }\n return m(\"div\", {\n className: \"BackupExport-extList\"\n }, m(\"div\", {\n className: \"BackupExport-extActions\"\n }, m(\"button\", {\n type: \"button\",\n className: \"BackupExport-extLink\",\n onclick: () => this.toggleAllExtensions(true)\n }, trans(\"extensions_select_all\")), m(\"span\", null, \" \\xB7 \"), m(\"button\", {\n type: \"button\",\n className: \"BackupExport-extLink\",\n onclick: () => this.toggleAllExtensions(false)\n }, trans(\"extensions_select_none\"))), [\"workbench\", \"vendor\", \"unknown\"].filter(loc => groups[loc].length > 0).map(loc => m(\"div\", {\n className: \"BackupExport-extGroup\",\n key: loc\n }, m(\"div\", {\n className: \"BackupExport-extGroupHeader\"\n }, trans(\"extensions_group_\" + loc), \" \", m(\"span\", {\n className: \"helpText\"\n }, \"(\", groups[loc].length, \")\")), groups[loc].map(ext => m(\"label\", {\n className: \"BackupExport-extRow\",\n key: ext.id\n }, m(\"input\", {\n type: \"checkbox\",\n checked: !!this.extensionSelected[ext.id],\n onchange: e => {\n this.extensionSelected[ext.id] = e.target.checked;\n }\n }), \" \", m(\"span\", {\n className: \"BackupExport-extTitle\"\n }, ext.title), \" \", m(\"code\", {\n className: \"BackupExport-extName\"\n }, ext.name || ext.id), m(\"span\", {\n className: \"BackupExport-extTag BackupExport-extTag--\".concat(ext.location)\n }, trans(\"extensions_tag_\" + ext.location)))))));\n }\n toggleAllExtensions(value) {\n for (const ext of this.extensions) this.extensionSelected[ext.id] = value;\n }\n async loadExtensions() {\n this.extensionsLoading = true;\n try {\n const res = await apiRequest({\n method: \"GET\",\n url: \"\".concat(apiUrl(), \"/backup/extensions\"),\n surface: false\n });\n this.extensions = res.extensions || [];\n this.extensionsLoaded = true;\n // Default: every extension ticked. The admin un-ticks the\n // ones they don't want.\n for (const ext of this.extensions) this.extensionSelected[ext.id] = true;\n } catch (e) {\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"extensions_load_failed\"))));\n } finally {\n this.extensionsLoading = false;\n m.redraw();\n }\n }\n checkbox(key, get, set) {\n return m(\"label\", {\n className: \"BackupExport-checkbox\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: get(),\n onchange: e => {\n set(e.target.checked);\n }\n }), \" \", m(\"span\", {\n className: \"BackupExport-checkbox-label\"\n }, trans(\"content_\" + key)), m(\"span\", {\n className: \"BackupExport-checkbox-help helpText\"\n }, trans(\"content_\" + key + \"_help\")));\n }\n canStart() {\n if (!this.includeDb && !this.includeAssets && !this.includeStorage && !this.includeExtensions) {\n return false;\n }\n if (this.encryptionEnabled && this.encryptionUseExternal && !this.externalPublicKey.trim()) {\n return false;\n }\n return true;\n }\n\n // ────────────────────────────────────────── progress\n\n progressContent() {\n var _s$progress, _s$warnings$length, _s$warnings;\n const s = this.status;\n if (!s) return m(LoadingIndicator, null);\n const isDone = s.phase === \"done\";\n const isError = s.phase === \"error\";\n const pct = Math.max(0, Math.min(100, ((_s$progress = s.progress) == null ? void 0 : _s$progress.percent) || 0));\n return m(\"div\", {\n className: \"Modal-body BackupExport-progress\"\n }, m(\"div\", {\n className: \"BackupExport-status BackupExport-status--\".concat(s.phase)\n }, m(\"strong\", null, trans(\"phase_\" + s.phase)), m(\"p\", null, s.message)), !isError && m('[', null, m(\"div\", {\n className: \"BackupExport-bar\"\n }, m(\"div\", {\n className: \"BackupExport-bar-fill\",\n style: {\n width: \"\".concat(isDone ? 100 : pct, \"%\")\n },\n role: \"progressbar\",\n \"aria-valuenow\": pct,\n \"aria-valuemin\": 0,\n \"aria-valuemax\": 100\n })), m(\"div\", {\n className: \"BackupExport-stats\"\n }, m(\"span\", null, fmtBytes(s.progress.processed_bytes), \" /\", \" \", fmtBytes(s.progress.total_bytes || s.progress.processed_bytes)), s.progress.total_files > 0 && m(\"span\", null, trans(\"files_count\", {\n done: s.progress.processed_files,\n total: s.progress.total_files\n })))), ((_s$warnings$length = (_s$warnings = s.warnings) == null ? void 0 : _s$warnings.length) != null ? _s$warnings$length : 0) > 0 && m(\"div\", {\n className: \"BackupExport-warnings\",\n role: \"alert\"\n }, m(\"div\", {\n className: \"BackupExport-warnings-title\"\n }, m(\"i\", {\n className: \"icon fas fa-triangle-exclamation\"\n }), \" \", trans(\"warnings_title\", {\n count: s.warnings.length\n })), m(\"p\", {\n className: \"helpText\"\n }, trans(\"warnings_help\")), m(\"ul\", {\n className: \"BackupExport-warnings-list\"\n }, s.warnings.map((w, idx) => m(\"li\", {\n key: idx\n }, w)))), m(\"div\", {\n className: \"Form-group BackupExport-progress-actions\"\n }, !isDone && !isError && m(Button, {\n className: \"Button\",\n onclick: () => this.cancel()\n }, trans(\"cancel_button\")), (isDone || isError) && m(Button, {\n className: \"Button Button--primary\",\n onclick: () => this.close()\n }, trans(\"close_button\"))));\n }\n\n // ────────────────────────────────────────── api calls\n\n async start() {\n this.starting = true;\n try {\n // The backend accepts `extensions` as bool OR string[]. When\n // every box is checked we still send the array (explicit),\n // unless the inventory hasn't even loaded yet — which means\n // the admin ticked the section but never opened the list and\n // implicitly wants \"all\".\n let extensionsField = false;\n if (this.includeExtensions) {\n if (!this.extensionsLoaded) {\n extensionsField = true;\n } else {\n const ids = Object.entries(this.extensionSelected).filter(_ref => {\n let _ref2 = _slicedToArray(_ref, 2),\n v = _ref2[1];\n return v;\n }).map(_ref3 => {\n let _ref4 = _slicedToArray(_ref3, 1),\n k = _ref4[0];\n return k;\n });\n extensionsField = ids;\n }\n }\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/exports\"),\n body: {\n contents: {\n db: this.includeDb,\n assets: this.includeAssets,\n storage: this.includeStorage,\n extensions: extensionsField\n },\n encryption: {\n enabled: this.encryptionEnabled,\n public_key: this.encryptionUseExternal ? this.externalPublicKey.trim() : null\n },\n // Empty string = \"same as source\"; the backend treats null\n // and \"\" identically so this carries the user's choice\n // through unambiguously.\n target_dialect: this.targetDialect || null\n },\n surface: false\n });\n this.jobId = res.job_id;\n this.stage = \"progress\";\n this.status = {\n phase: res.phase,\n message: res.message,\n progress: {\n total_bytes: 0,\n processed_bytes: 0,\n total_files: 0,\n processed_files: 0,\n percent: 0\n }\n };\n this.starting = false;\n m.redraw();\n this.pump();\n } catch (e) {\n this.starting = false;\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"start_failed\"))));\n m.redraw();\n }\n }\n async pump() {\n if (this.polling || !this.jobId) return;\n this.polling = true;\n try {\n var _this$status;\n // Sequential ticks — each /tick call performs ~4MB of work.\n while (this.jobId && this.status && this.status.phase !== \"done\" && this.status.phase !== \"error\") {\n try {\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/exports/\").concat(this.jobId, \"/tick\"),\n surface: false\n });\n this.status = res;\n m.redraw();\n } catch (e) {\n // Convert tick failures into a synthetic error phase so the\n // existing UI shows the close button and a meaningful\n // message instead of freezing on the last %.\n const detail = errorDetail(e, String(trans(\"phase_error_network\")));\n this.status = _objectSpread(_objectSpread({}, this.status), {}, {\n phase: \"error\",\n message: detail\n });\n m.redraw();\n break;\n }\n }\n if (((_this$status = this.status) == null ? void 0 : _this$status.phase) === \"done\") {\n app.alerts.show({\n type: \"success\"\n }, trans(\"completed\"));\n this.attrs.onComplete();\n }\n } finally {\n this.polling = false;\n }\n }\n async cancel() {\n if (!this.jobId) return;\n try {\n await apiRequest({\n method: \"DELETE\",\n url: \"\".concat(apiUrl(), \"/backup/exports/\").concat(this.jobId),\n surface: false\n });\n } catch (e) {\n // The job may still be holding a server-side lock — let the user\n // know so they understand if the next export complains.\n console.warn(\"[backup] export cancel failed\", e);\n app.alerts.show({\n type: \"warning\"\n }, trans(\"cancel_failed_warn\"));\n }\n this.close();\n }\n close() {\n this.jobId = null;\n this.hide();\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/ExportModal', ExportModal);","import _slicedToArray from \"@babel/runtime/helpers/esm/slicedToArray\";\nimport _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\nimport app from \"flarum/admin/app\";\nimport Modal from \"flarum/common/components/Modal\";\nimport Button from \"flarum/common/components/Button\";\nimport LoadingIndicator from \"flarum/common/components/LoadingIndicator\";\nimport { apiRequest, apiUrl, errorDetail, fmtBytes } from \"../utils/api\";\n\n/** Abort the upload XHR if no progress event fires for this long. */\nconst UPLOAD_IDLE_TIMEOUT_MS = 60000;\n\n/**\n * Fallback chunk size if /backup/imports init doesn't return one.\n * Server-recommended is 4 MB (see UploadImportController::RECOMMENDED_CHUNK_BYTES).\n */\nconst FALLBACK_CHUNK_BYTES = 4 * 1024 * 1024;\n\n/**\n * How many times to retry a single failed chunk before giving up on\n * the whole upload. Each retry uses the same offset so it overwrites\n * (idempotent). Two retries cover a transient hiccup without\n * spinning forever on a real outage.\n */\nconst CHUNK_RETRY_LIMIT = 2;\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.import_modal.\".concat(key), params != null ? params : {});\n\n/**\n * Three-stage modal:\n * 1. upload — operator picks a `.flarum` file. We POST to /imports\n * and the server validates the header without\n * decrypting.\n * 2. configure — depending on the inspect result we ask for the\n * private key (only when the archive is encrypted)\n * AND a confirm-replace checkbox in all cases. The\n * user agreed in setup that we'd ask at this point.\n * 3. progress — chunked-tick polling drives a progress bar.\n */\nexport default class ImportModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"stage\", \"upload\");\n _defineProperty(this, \"file\", null);\n _defineProperty(this, \"uploading\", false);\n _defineProperty(this, \"uploadProgress\", 0);\n _defineProperty(this, \"uploadIndeterminate\", false);\n _defineProperty(this, \"uploadError\", null);\n _defineProperty(this, \"inspect\", null);\n _defineProperty(this, \"privateKey\", \"\");\n _defineProperty(this, \"confirmReplace\", false);\n _defineProperty(this, \"starting\", false);\n // Section toggles. Default to \"everything that's actually inside the\n // archive\" — the user explicitly opts OUT of pieces they don't want\n // to overwrite. Initialised when the inspect result arrives.\n _defineProperty(this, \"sectionDb\", false);\n _defineProperty(this, \"sectionAssets\", false);\n _defineProperty(this, \"sectionStorage\", false);\n _defineProperty(this, \"sectionExtensions\", false);\n // Per-extension toggles, keyed by directory name from the manifest.\n _defineProperty(this, \"extensionsByName\", {});\n _defineProperty(this, \"status\", null);\n _defineProperty(this, \"polling\", false);\n }\n className() {\n return \"BackupImportModal Modal--medium\";\n }\n title() {\n var _this$status;\n if (this.stage === \"progress\" && ((_this$status = this.status) == null ? void 0 : _this$status.phase) === \"done\") {\n return this.sectionDb ? trans(\"logout_title\") : trans(\"done_title\");\n }\n return trans(\"title\");\n }\n content() {\n if (this.stage === \"upload\") return this.uploadContent();\n if (this.stage === \"configure\") return this.configureContent();\n return this.progressContent();\n }\n\n // ────────────────────────────────────────── upload stage\n\n uploadContent() {\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"div\", {\n className: \"Alert Alert--warning\"\n }, m(\"strong\", null, trans(\"warning_title\")), m(\"p\", null, trans(\"warning_body\"))), m(\"label\", {\n className: \"BackupImport-fileLabel\"\n }, m(\"input\", {\n type: \"file\",\n accept: \".flarum\",\n onchange: e => {\n var _files;\n const f = ((_files = e.target.files) == null ? void 0 : _files[0]) || null;\n this.file = f;\n }\n }), this.file ? m(\"span\", null, this.file.name, \" \", m(\"span\", {\n className: \"helpText\"\n }, \"(\", fmtBytes(this.file.size), \")\")) : m(\"span\", {\n className: \"helpText\"\n }, trans(\"choose_file\"))), this.uploadError && m(\"div\", {\n className: \"Alert Alert--error\"\n }, this.uploadError), this.uploading && m(\"div\", {\n className: \"BackupImport-uploadProgress\"\n }, m(\"div\", {\n className: \"BackupImport-bar\"\n }, m(\"div\", {\n className: \"BackupImport-bar-fill\" + (this.uploadIndeterminate ? \" BackupImport-bar-fill--indeterminate\" : \"\"),\n style: this.uploadIndeterminate ? undefined : {\n width: \"\".concat(Math.max(2, this.uploadProgress), \"%\")\n }\n })), m(\"div\", {\n className: \"BackupImport-uploadStatus helpText\"\n }, this.uploadIndeterminate ? trans(\"inspecting_archive\") : trans(\"uploading_pct\", {\n pct: this.uploadProgress\n }))), m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--primary\",\n loading: this.uploading,\n disabled: this.uploading || !this.file,\n onclick: () => this.upload()\n }, trans(\"upload_button\"))));\n }\n async upload() {\n if (!this.file) return;\n this.uploading = true;\n this.uploadProgress = 0;\n this.uploadIndeterminate = false;\n this.uploadError = null;\n try {\n var _res$meta$manifest;\n // Use a raw XHR so we can show upload progress — Flarum's\n // app.request (mithril's m.request) does not expose\n // `xhr.upload.onprogress`. Once 100% has been sent the server\n // still has to read the header + validate the archive, so we\n // flip to an indeterminate \"Inspecting…\" state until the\n // response comes back.\n const res = await this.uploadWithProgress(this.file, pct => {\n this.uploadProgress = pct;\n if (pct >= 100) this.uploadIndeterminate = true;\n m.redraw();\n });\n this.inspect = res;\n\n // Seed section toggles from what the archive actually contains.\n // Anything missing from the archive can't be ticked anyway, so\n // there's no value in defaulting it to true.\n const contents = res.meta.contents || [];\n this.sectionDb = contents.includes(\"db\");\n this.sectionAssets = contents.includes(\"assets\");\n this.sectionStorage = contents.includes(\"storage\");\n this.sectionExtensions = contents.includes(\"extensions\");\n\n // Normalise to {id} regardless of which manifest version the\n // archive was packed with (string[] vs ArchiveExtensionEntry[]).\n const exts = ((_res$meta$manifest = res.meta.manifest) == null ? void 0 : _res$meta$manifest.extensions) || [];\n this.extensionsByName = {};\n for (const e of exts) {\n const id = typeof e === \"string\" ? e : e.id;\n if (id) this.extensionsByName[id] = true;\n }\n this.stage = \"configure\";\n } catch (e) {\n console.error(\"[backup] archive upload failed\", e);\n this.uploadError = errorDetail(e, String(trans(\"upload_failed\")));\n } finally {\n this.uploading = false;\n m.redraw();\n }\n }\n\n /**\n * Chunked upload + inspect.\n *\n * Single multipart POSTs of multi-GB archives reliably hit server\n * caps (`upload_max_filesize`, `post_max_size`, nginx\n * `client_max_body_size`, `memory_limit` during multipart parsing)\n * and surface as 500s. Instead we do three small requests:\n *\n * 1. POST /backup/imports — init, gets job_id + chunk_size\n * 2. POST /backup/imports/{id}/chunk* — append each slice (loop)\n * 3. POST /backup/imports/{id}/inspect — finalise, return meta\n *\n * Progress is computed from `bytesSent / file.size` across all chunk\n * requests so the bar advances smoothly through the entire file\n * even though each individual request only carries a few MB.\n */\n async uploadWithProgress(file, onProgress) {\n // ─── 1. init ──────────────────────────────────────────────────\n const init = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports\"),\n body: {\n filename: file.name,\n size: file.size\n },\n surface: false\n });\n const jobId = init.job_id;\n const chunkSize = init.chunk_size > 0 ? init.chunk_size : FALLBACK_CHUNK_BYTES;\n\n // ─── 2. chunk loop ────────────────────────────────────────────\n let offset = 0;\n while (offset < file.size) {\n const end = Math.min(offset + chunkSize, file.size);\n const slice = file.slice(offset, end);\n let attempt = 0;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n await this.sendChunk(jobId, offset, slice);\n break;\n } catch (e) {\n attempt++;\n if (attempt > CHUNK_RETRY_LIMIT) throw e;\n // Back off briefly before retrying — gives a transient\n // hiccup a moment to clear without spamming the server.\n await new Promise(r => setTimeout(r, 750 * attempt));\n }\n }\n offset = end;\n const pct = Math.min(99, Math.round(offset / file.size * 100));\n onProgress(pct);\n }\n\n // ─── 3. inspect ───────────────────────────────────────────────\n onProgress(100);\n return apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(jobId, \"/inspect\"),\n surface: false\n });\n }\n\n /**\n * Single-chunk upload as a raw octet-stream POST. We use XHR\n * (rather than fetch) so the per-chunk idle timeout works the\n * same way it did for the old monolithic upload, and so we can\n * pull a detailed error message off any non-2xx response body.\n */\n sendChunk(jobId, offset, slice) {\n return new Promise((resolve, reject) => {\n var _session;\n const xhr = new XMLHttpRequest();\n let lastProgress = Date.now();\n const idleTimer = setInterval(() => {\n if (Date.now() - lastProgress > UPLOAD_IDLE_TIMEOUT_MS) {\n clearInterval(idleTimer);\n xhr.abort();\n }\n }, 5000);\n const stopIdleTimer = () => clearInterval(idleTimer);\n xhr.upload.addEventListener(\"progress\", () => {\n lastProgress = Date.now();\n });\n xhr.addEventListener(\"load\", () => {\n stopIdleTimer();\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n let detail;\n try {\n var _JSON$parse;\n detail = (_JSON$parse = JSON.parse(xhr.responseText)) == null || (_JSON$parse = _JSON$parse.errors) == null || (_JSON$parse = _JSON$parse[0]) == null ? void 0 : _JSON$parse.detail;\n } catch (_unused) {\n /* non-JSON body */\n }\n reject({\n detail: detail || \"\".concat(xhr.status, \" \").concat(xhr.statusText)\n });\n }\n });\n xhr.addEventListener(\"error\", () => {\n stopIdleTimer();\n reject({\n detail: String(trans(\"upload_failed\"))\n });\n });\n xhr.addEventListener(\"abort\", () => {\n stopIdleTimer();\n reject({\n detail: String(trans(\"upload_idle_timeout\"))\n });\n });\n xhr.open(\"POST\", \"\".concat(apiUrl(), \"/backup/imports/\").concat(jobId, \"/chunk\"), true);\n xhr.withCredentials = true;\n xhr.setRequestHeader(\"Content-Type\", \"application/octet-stream\");\n xhr.setRequestHeader(\"X-Chunk-Offset\", String(offset));\n const csrf = (_session = app.session) == null ? void 0 : _session.csrfToken;\n if (csrf) xhr.setRequestHeader(\"X-CSRF-Token\", csrf);\n xhr.send(slice);\n });\n }\n\n // ────────────────────────────────────────── configure stage\n\n configureContent() {\n const i = this.inspect;\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"h4\", null, trans(\"inspect_title\")), m(\"dl\", {\n className: \"BackupImport-meta\"\n }, i.meta.created_at && m('[', null, m(\"dt\", null, trans(\"meta_when\")), m(\"dd\", null, i.meta.created_at)), i.meta.flarum_version && m('[', null, m(\"dt\", null, trans(\"meta_flarum\")), m(\"dd\", null, i.meta.flarum_version)), i.meta.contents && m('[', null, m(\"dt\", null, trans(\"meta_contents\")), m(\"dd\", null, i.meta.contents.join(\", \"))), i.meta.source_url && m('[', null, m(\"dt\", null, trans(\"meta_source_url\")), m(\"dd\", null, m(\"code\", null, i.meta.source_url))), m(\"dt\", null, trans(\"meta_size\")), m(\"dd\", null, fmtBytes(i.size))), m(\"div\", {\n className: \"Alert Alert--info BackupImport-urlNote\"\n }, m(\"i\", {\n className: \"icon fas fa-info-circle\"\n }), \" \", trans(\"url_rewrite_note\")), this.selectionFieldset(i), i.is_encrypted && m(\"fieldset\", {\n className: \"BackupImport-fieldset\"\n }, m(\"legend\", null, trans(\"key_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"key_help\")), m(\"textarea\", {\n className: \"FormControl BackupImport-keyInput\",\n rows: 3,\n placeholder: \"base64 private key\",\n value: this.privateKey,\n oninput: e => {\n this.privateKey = e.target.value;\n }\n }), m(\"p\", {\n className: \"helpText BackupImport-keyHint\"\n }, trans(\"key_hint_local\"))), m(\"div\", {\n className: \"Alert Alert--error BackupImport-confirmAlert\"\n }, m(\"strong\", null, trans(\"confirm_title\")), m(\"p\", null, trans(\"confirm_body\")), m(\"label\", {\n className: \"BackupImport-confirm\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: this.confirmReplace,\n onchange: e => {\n this.confirmReplace = e.target.checked;\n }\n }), \" \", m(\"span\", null, trans(\"confirm_check\")))), m(\"div\", {\n className: \"Form-group\"\n }, m(Button, {\n className: \"Button Button--primary\",\n loading: this.starting,\n disabled: this.starting || !this.confirmReplace,\n onclick: () => this.startRestore()\n }, trans(\"start_button\"))));\n }\n selectionFieldset(i) {\n const contents = i.meta.contents || [];\n const manifest = i.meta.manifest || {};\n const hasDb = contents.includes(\"db\");\n const hasAssets = contents.includes(\"assets\");\n const hasStorage = contents.includes(\"storage\");\n const hasExtensions = contents.includes(\"extensions\");\n const rawExtList = manifest.extensions || [];\n // Normalise to a uniform shape so the renderer can stay simple,\n // regardless of which manifest version the archive used.\n const extList = rawExtList.map(e => typeof e === \"string\" ? {\n id: e,\n location: \"workbench\"\n } : e);\n return m(\"fieldset\", {\n className: \"BackupImport-fieldset\"\n }, m(\"legend\", null, trans(\"selection_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"selection_help\")), hasDb && this.sectionRow(\"db\", this.sectionDb, v => this.sectionDb = v), hasAssets && this.sectionRow(\"assets\", this.sectionAssets, v => this.sectionAssets = v, manifest.asset_count), hasStorage && this.sectionRow(\"storage\", this.sectionStorage, v => this.sectionStorage = v, manifest.storage_count), hasExtensions && m('[', null, this.sectionRow(\"extensions\", this.sectionExtensions, v => {\n this.sectionExtensions = v;\n // Cascade: turning the section off / on flips every\n // child to match. The user can then untick individuals.\n for (const ext of extList) this.extensionsByName[ext.id] = v;\n }, manifest.extension_count), this.sectionExtensions && manifest.has_composer && m(\"div\", {\n className: \"BackupImport-composerNote helpText\"\n }, m(\"i\", {\n className: \"icon fas fa-cube\"\n }), \" \", trans(\"extensions_composer_note\")), this.sectionExtensions && extList.length > 0 && m(\"div\", {\n className: \"BackupImport-extList\"\n }, extList.map(ext => m(\"label\", {\n className: \"BackupImport-extRow\",\n key: ext.id\n }, m(\"input\", {\n type: \"checkbox\",\n checked: !!this.extensionsByName[ext.id],\n onchange: e => {\n this.extensionsByName[ext.id] = e.target.checked;\n }\n }), \" \", m(\"span\", {\n className: \"BackupImport-extTitle\"\n }, ext.title || ext.id), \" \", ext.name && ext.name !== ext.id && m(\"code\", {\n className: \"BackupImport-extName\"\n }, ext.name), ext.location && m(\"span\", {\n className: \"BackupImport-extTag BackupImport-extTag--\".concat(ext.location)\n }, trans(\"extensions_tag_\" + ext.location)))))));\n }\n sectionRow(key, checked, set, count) {\n return m(\"label\", {\n className: \"BackupImport-sectionRow\"\n }, m(\"input\", {\n type: \"checkbox\",\n checked: checked,\n onchange: e => set(e.target.checked)\n }), \" \", m(\"span\", {\n className: \"BackupImport-sectionLabel\"\n }, trans(\"section_\" + key)), count !== undefined && count > 0 && m(\"span\", {\n className: \"BackupImport-sectionCount helpText\"\n }, \" \", \"(\", trans(\"section_count\", {\n count\n }), \")\"));\n }\n buildSelection() {\n // The backend treats `extensions: true` as \"all\" and an array as\n // a whitelist of directory names. When every box is checked, send\n // `true` so the user's intent isn't lost if a new extension shows\n // up between inspect and apply (it shouldn't, but be defensive).\n const extEntries = Object.entries(this.extensionsByName);\n const allChecked = extEntries.length > 0 && extEntries.every(_ref => {\n let _ref2 = _slicedToArray(_ref, 2),\n v = _ref2[1];\n return v;\n });\n const extensionsField = !this.sectionExtensions ? false : allChecked ? true : extEntries.filter(_ref3 => {\n let _ref4 = _slicedToArray(_ref3, 2),\n v = _ref4[1];\n return v;\n }).map(_ref5 => {\n let _ref6 = _slicedToArray(_ref5, 1),\n k = _ref6[0];\n return k;\n });\n return {\n db: this.sectionDb,\n assets: this.sectionAssets,\n storage: this.sectionStorage,\n extensions: extensionsField\n };\n }\n async startRestore() {\n if (!this.inspect) return;\n this.starting = true;\n try {\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id, \"/start\"),\n surface: false,\n body: {\n private_key: this.privateKey.trim() || null,\n confirm_replace: this.confirmReplace,\n selection: this.buildSelection()\n }\n });\n this.stage = \"progress\";\n this.status = {\n phase: res.phase,\n message: res.message,\n progress: {\n total_bytes: this.inspect.size,\n processed_bytes: 0,\n extracted_entries: 0,\n restored_statements: 0,\n percent: 0\n }\n };\n m.redraw();\n this.pump();\n } catch (e) {\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"start_failed\"))));\n } finally {\n this.starting = false;\n }\n }\n\n // ────────────────────────────────────────── progress stage\n\n progressContent() {\n var _s$progress;\n const s = this.status;\n if (!s) return m(LoadingIndicator, null);\n\n // Once the server reports phase=done we hand the screen over to a\n // dedicated completion view: the user has finished waiting and\n // now needs to know what to do next (which differs depending on\n // whether the DB was actually replaced).\n if (s.phase === \"done\") return this.completedContent();\n const isError = s.phase === \"error\";\n const pct = Math.max(0, Math.min(100, ((_s$progress = s.progress) == null ? void 0 : _s$progress.percent) || 0));\n return m(\"div\", {\n className: \"Modal-body BackupImport-progress\"\n }, m(\"div\", {\n className: \"BackupImport-status BackupImport-status--\".concat(s.phase)\n }, m(\"strong\", null, trans(\"phase_\" + s.phase)), m(\"p\", null, s.message)), !isError && m(\"div\", {\n className: \"BackupImport-bar\"\n }, m(\"div\", {\n className: \"BackupImport-bar-fill\",\n style: {\n width: \"\".concat(pct, \"%\")\n }\n })), m(\"div\", {\n className: \"Form-group BackupImport-progress-actions\"\n }, !isError && m(Button, {\n className: \"Button\",\n onclick: () => this.cancel()\n }, trans(\"cancel_button\")), isError && m(Button, {\n className: \"Button Button--primary\",\n onclick: () => this.close()\n }, trans(\"close_button\"))));\n }\n\n /**\n * Replaces the progress UI as soon as `phase === 'done'`. Two\n * shapes:\n *\n * - DB restored — the admin's session was just wiped together\n * with the rest of the `users` / `sessions` tables. We make\n * this very clear and offer a single primary action: reload.\n * Anything else (refreshing the list, dismissing) would race\n * against an invalidated cookie and surface a confusing 401.\n *\n * - Files only — the session is fine, just close.\n */\n /**\n * Warning block for a restore that finished but lost entries, or that\n * carries a stack advisory. Rendered on the completed screen in both\n * shapes — an incomplete restore must never present as clean success,\n * which is the failure mode this whole path exists to remove.\n */\n completedWarnings() {\n var _this$status$warnings, _this$status2;\n const warnings = (_this$status$warnings = (_this$status2 = this.status) == null ? void 0 : _this$status2.warnings) != null ? _this$status$warnings : [];\n if (!warnings.length) return null;\n return m(\"div\", {\n className: \"Alert Alert--error BackupImport-completedWarnings\"\n }, m(\"strong\", null, trans(\"incomplete_title\")), m(\"ul\", null, warnings.map(w => m(\"li\", null, w))));\n }\n completedContent() {\n var _this$status3;\n if (this.sectionDb) {\n return m(\"div\", {\n className: \"Modal-body BackupImport-completed BackupImport-completed--logout\"\n }, this.completedWarnings(), m(\"div\", {\n className: \"BackupImport-completedIcon\"\n }, m(\"i\", {\n className: \"fas fa-right-from-bracket\"\n })), m(\"h3\", {\n className: \"BackupImport-completedTitle\"\n }, trans(\"logout_title\")), m(\"p\", {\n className: \"BackupImport-completedBody\"\n }, trans(\"logout_body\")), m(\"ol\", {\n className: \"BackupImport-completedSteps\"\n }, m(\"li\", null, trans(\"logout_step_reload\")), m(\"li\", null, trans(\"logout_step_login\"))), m(Button, {\n className: \"Button Button--primary BackupImport-completedAction\",\n icon: \"fas fa-rotate\",\n onclick: () => window.location.reload()\n }, trans(\"logout_button\")));\n }\n const incomplete = !!((_this$status3 = this.status) != null && _this$status3.incomplete);\n return m(\"div\", {\n className: \"Modal-body BackupImport-completed\"\n }, this.completedWarnings(), m(\"div\", {\n className: \"BackupImport-completedIcon BackupImport-completedIcon--\".concat(incomplete ? \"warning\" : \"success\")\n }, m(\"i\", {\n className: incomplete ? \"fas fa-triangle-exclamation\" : \"fas fa-circle-check\"\n })), m(\"h3\", {\n className: \"BackupImport-completedTitle\"\n }, trans(incomplete ? \"incomplete_title\" : \"done_title\")), m(\"p\", {\n className: \"BackupImport-completedBody\"\n }, trans(incomplete ? \"incomplete_body\" : \"done_body\")), m(Button, {\n className: \"Button Button--primary\",\n onclick: () => this.close()\n }, trans(\"close_button\")));\n }\n async pump() {\n if (this.polling || !this.inspect) return;\n this.polling = true;\n try {\n var _this$status4;\n while (this.inspect && this.status && this.status.phase !== \"done\" && this.status.phase !== \"error\") {\n try {\n const res = await apiRequest({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id, \"/tick\"),\n surface: false,\n // The server never stores the decryption key on disk; we\n // re-send it each tick so the inspect/extract phases can\n // decrypt. Harmless (ignored) for unencrypted archives and\n // once extraction is done.\n body: {\n private_key: this.privateKey.trim() || null\n }\n });\n this.status = res;\n m.redraw();\n } catch (e) {\n // Restore is more dangerous to leave hanging than export —\n // the server may still be mid-write. Surface a synthetic\n // error phase, and explicitly tell the user to verify\n // server state before retrying.\n console.error(\"[backup] import tick failed\", e);\n const detail = errorDetail(e, String(trans(\"phase_error_network\")));\n this.status = _objectSpread(_objectSpread({}, this.status), {}, {\n phase: \"error\",\n message: detail\n });\n m.redraw();\n break;\n }\n }\n if (((_this$status4 = this.status) == null ? void 0 : _this$status4.phase) === \"done\") {\n // Don't refresh the parent panel — when the backup includes\n // the database, restoring it has just replaced the sessions\n // table this admin is authenticated against. Any further API\n // call from this stale session would fail (401 / CSRF) and\n // surface as a confusing \"Oops!\" toast. The user clicks\n // Reload below and gets a clean session.\n app.alerts.show({\n type: \"success\"\n }, trans(\"completed\"));\n if (!this.sectionDb) this.attrs.onComplete();\n }\n } finally {\n this.polling = false;\n }\n }\n async cancel() {\n if (!this.inspect) return;\n try {\n await apiRequest({\n method: \"DELETE\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id),\n surface: false\n });\n } catch (e) {\n console.error(\"[backup] import cancel failed\", e);\n app.alerts.show({\n type: \"warning\"\n }, trans(\"cancel_failed_warn\"));\n }\n this.close();\n }\n close() {\n this.hide();\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/ImportModal', ImportModal);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nfunction ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }\nfunction _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }\nimport app from \"flarum/admin/app\";\nimport Modal from \"flarum/common/components/Modal\";\nimport Button from \"flarum/common/components/Button\";\n/**\n * Dropin replacement for `window.confirm()`. Native confirm breaks the\n * Flarum dark-mode chrome and is silently auto-rejected by some\n * locked-down corporate browsers — this modal stays inside the SPA.\n *\n * Usage:\n * const ok = await confirmAsync({ title, body, danger: true });\n * if (!ok) return;\n */\nexport default class ConfirmModal extends Modal {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"resolved\", false);\n }\n className() {\n return \"BackupConfirmModal Modal--small\";\n }\n title() {\n return this.attrs.title;\n }\n content() {\n var _this$attrs$confirmLa, _this$attrs$cancelLab;\n const confirmLabel = (_this$attrs$confirmLa = this.attrs.confirmLabel) != null ? _this$attrs$confirmLa : app.translator.trans(\"ramon-backup.admin.errors.confirm_default\");\n const cancelLabel = (_this$attrs$cancelLab = this.attrs.cancelLabel) != null ? _this$attrs$cancelLab : app.translator.trans(\"ramon-backup.admin.errors.cancel_default\");\n return m(\"div\", {\n className: \"Modal-body\"\n }, m(\"div\", {\n className: \"BackupConfirmModal-body\"\n }, this.attrs.body), m(\"div\", {\n className: \"Form-group BackupConfirmModal-actions\"\n }, m(Button, {\n className: \"Button \" + (this.attrs.danger ? \"Button--danger\" : \"Button--primary\"),\n onclick: () => this.decide(true)\n }, confirmLabel), m(Button, {\n className: \"Button\",\n onclick: () => this.decide(false)\n }, cancelLabel)));\n }\n decide(confirmed) {\n var _ref;\n if (this.resolved) return;\n this.resolved = true;\n (_ref = confirmed ? this.attrs.onConfirm : this.attrs.onCancel) == null || _ref();\n this.hide();\n }\n\n // Esc key / backdrop click / X button all funnel through Mithril's\n // remove lifecycle. If the user dismissed without picking a button,\n // treat that as a cancel so the awaiting Promise actually resolves.\n onbeforeremove(vnode) {\n if (!this.resolved) {\n var _this$attrs$onCancel, _this$attrs;\n this.resolved = true;\n (_this$attrs$onCancel = (_this$attrs = this.attrs).onCancel) == null || _this$attrs$onCancel.call(_this$attrs);\n }\n return super.onbeforeremove(vnode);\n }\n}\n\n/** Promise wrapper so callers can `await confirmAsync(...)`. */\nexport function confirmAsync(attrs) {\n return new Promise(resolve => {\n let settled = false;\n const settle = v => {\n if (settled) return;\n settled = true;\n resolve(v);\n };\n app.modal.show(ConfirmModal, _objectSpread(_objectSpread({}, attrs), {}, {\n onConfirm: () => settle(true),\n onCancel: () => settle(false)\n }));\n });\n}\nflarum.reg.add('ramon-backup', 'admin/components/ConfirmModal', ConfirmModal);","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport app from \"flarum/admin/app\";\nimport Component from \"flarum/common/Component\";\nconst trans = key => app.translator.trans(\"ramon-backup.admin.errors.\".concat(key));\n/**\n * Mithril doesn't have React's Error Boundary — but a tiny vnode\n * wrapper that try/catches `children` rendering covers the same\n * 90% case: a single throw inside a render path won't blow up the\n * whole admin SPA.\n *\n * Limitation: only catches synchronous exceptions during render. Async\n * Promise rejections still need their own try/catch — this is not a\n * substitute for handling failures in event handlers or API calls.\n */\nexport class ErrorBoundary extends Component {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"failed\", false);\n _defineProperty(this, \"lastError\", null);\n }\n view(vnode) {\n if (this.failed) {\n const retry = () => {\n this.failed = false;\n this.lastError = null;\n m.redraw();\n };\n if (vnode.attrs.fallback) return vnode.attrs.fallback(this.lastError, retry);\n return m(\"div\", {\n className: \"Alert Alert--error BackupErrorBoundary\"\n }, m(\"strong\", null, trans(\"boundary_title\")), m(\"p\", null, trans(\"boundary_body\")), m(\"button\", {\n type: \"button\",\n className: \"Button\",\n onclick: retry\n }, trans(\"boundary_retry\")));\n }\n try {\n return vnode.children;\n } catch (err) {\n var _vnode$attrs$onError, _vnode$attrs;\n this.failed = true;\n this.lastError = err;\n (_vnode$attrs$onError = (_vnode$attrs = vnode.attrs).onError) == null || _vnode$attrs$onError.call(_vnode$attrs, err);\n console.error(\"[backup] render boundary caught\", err);\n return null;\n }\n }\n}\nflarum.reg.add('ramon-backup', 'admin/utils/errorBoundary', { ErrorBoundary: ErrorBoundary, });","import _defineProperty from \"@babel/runtime/helpers/esm/defineProperty\";\nimport app from \"flarum/admin/app\";\nimport Component from \"flarum/common/Component\";\nimport Button from \"flarum/common/components/Button\";\nimport LoadingIndicator from \"flarum/common/components/LoadingIndicator\";\nimport EncryptionCard from \"./EncryptionCard\";\nimport BackupList from \"./BackupList\";\nimport ExportModal from \"./ExportModal\";\nimport ImportModal from \"./ImportModal\";\nimport { confirmAsync } from \"./ConfirmModal\";\nimport { apiRequest, apiUrl, errorDetail } from \"../utils/api\";\nimport { ErrorBoundary } from \"../utils/errorBoundary\";\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.\".concat(key), params != null ? params : {});\nexport default class BackupPanel extends Component {\n constructor() {\n super(...arguments);\n _defineProperty(this, \"listState\", \"loading\");\n _defineProperty(this, \"listError\", null);\n _defineProperty(this, \"backups\", []);\n }\n oninit(vnode) {\n super.oninit(vnode);\n this.refresh();\n }\n view() {\n return m(\"div\", {\n className: \"BackupPanel\"\n }, m(ErrorBoundary, {\n onError: e => console.error(\"[backup] panel render\", e)\n }, m(\"section\", {\n className: \"BackupPanel-actions\"\n }, m(\"h3\", null, trans(\"panel.actions_title\")), m(\"p\", {\n className: \"helpText\"\n }, trans(\"panel.actions_help\")), m(\"div\", {\n className: \"BackupPanel-actionButtons\"\n }, m(Button, {\n className: \"Button Button--primary\",\n icon: \"fas fa-download\",\n onclick: () => this.openExport()\n }, trans(\"panel.create_button\")), m(Button, {\n className: \"Button\",\n icon: \"fas fa-upload\",\n onclick: () => this.openImport()\n }, trans(\"panel.import_button\")))), m(EncryptionCard, null), m(\"section\", {\n className: \"BackupPanel-list\"\n }, m(\"h3\", null, trans(\"panel.list_title\")), this.renderList())));\n }\n renderList() {\n if (this.listState === \"loading\") return m(LoadingIndicator, null);\n if (this.listState === \"error\") {\n return m(\"div\", {\n className: \"Alert Alert--error BackupPanel-listError\"\n }, m(\"p\", null, trans(\"list.load_failed\")), this.listError && m(\"p\", {\n className: \"helpText\"\n }, m(\"code\", null, this.listError)), m(Button, {\n className: \"Button\",\n icon: \"fas fa-rotate\",\n onclick: () => this.refresh()\n }, trans(\"list.retry\")));\n }\n return m(BackupList, {\n backups: this.backups,\n onDelete: id => this.delete(id),\n onRefresh: () => this.refresh()\n });\n }\n refresh() {\n this.listState = \"loading\";\n this.listError = null;\n return apiRequest({\n method: \"GET\",\n url: \"\".concat(apiUrl(), \"/backup/backups\"),\n surface: false\n }).then(res => {\n this.backups = res.backups || [];\n this.listState = \"ok\";\n }).catch(e => {\n this.backups = [];\n this.listState = \"error\";\n this.listError = errorDetail(e);\n }).then(() => {\n m.redraw();\n });\n }\n openExport() {\n app.modal.show(ExportModal, {\n onComplete: () => this.refresh()\n });\n }\n openImport() {\n app.modal.show(ImportModal, {\n onComplete: () => this.refresh()\n });\n }\n async delete(id) {\n const ok = await confirmAsync({\n title: trans(\"list.confirm_delete_title\"),\n body: trans(\"list.confirm_delete\"),\n confirmLabel: trans(\"list.delete_title\"),\n danger: true\n });\n if (!ok) return;\n try {\n await apiRequest({\n method: \"DELETE\",\n url: \"\".concat(apiUrl(), \"/backup/backups/\").concat(id),\n surface: false,\n fallbackMessage: String(trans(\"list.delete_failed\"))\n });\n app.alerts.show({\n type: \"success\"\n }, trans(\"list.deleted\"));\n this.refresh();\n } catch (e) {\n app.alerts.show({\n type: \"error\"\n }, errorDetail(e, String(trans(\"list.delete_failed\"))));\n }\n }\n}\nflarum.reg.add('ramon-backup', 'admin/components/BackupPanel', BackupPanel);","import app from \"flarum/admin/app\";\nimport { override } from \"flarum/common/extend\";\nimport ExtensionPage from \"flarum/admin/components/ExtensionPage\";\nimport BackupPanel from \"./components/BackupPanel\";\nconst EXT_ID = \"ramon-backup\";\n\n// Hide the default \"Save changes\" submit button on our settings page —\n// every action here happens through dedicated buttons (export now, key\n// rotation, etc.), there's no batch settings save to perform.\noverride(ExtensionPage.prototype, \"submitButton\", function (original) {\n if (this.extension && this.extension.id === EXT_ID) return null;\n return original();\n});\napp.initializers.add(EXT_ID, () => {\n app.registry.for(EXT_ID).registerSetting(() => m(BackupPanel, null), 100, \"ramon-backup.panel\").registerPermission({\n icon: \"fas fa-file-archive\",\n label: app.translator.trans(\"ramon-backup.admin.permissions.manage_label\"),\n permission: \"backup.manage\"\n }, \"moderate\");\n});"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","Array","isArray","i","length","key","binding","o","Object","defineProperty","enumerable","value","get","obj","prop","prototype","hasOwnProperty","call","app_namespaceObject","flarum","reg","extend_namespaceObject","ExtensionPage_namespaceObject","_typeof","Symbol","iterator","constructor","_defineProperty","e","r","t","toPrimitive","TypeError","String","toPropertyKey","configurable","writable","Component_namespaceObject","Button_namespaceObject","LoadingIndicator_namespaceObject","Modal_namespaceObject","extractText_namespaceObject","apiUrl","app_default","forum","attribute","replace","fmtBytes","bytes","Number","isFinite","units","n","toFixed","errorDetail","raw","fallback","_ref","_raw$response$errors$","_raw$response","detail","response","errors","message","undefined","translator","trans","async","apiRequest","opts","request","fallbackMessage","console","error","method","url","surface","alerts","show","type","_unused","add","params","concat","KeypairRevealModal","Modal_default","super","arguments","this","className","title","content","_this$attrs","attrs","privateKey","configKey","snippet","m","Button_default","icon","onclick","copy","copied","hide","navigator","clipboard","writeText","then","redraw","setTimeout","catch","err","RegenerateConfirmModal","checked","acknowledged","onchange","target","loading","submitting","disabled","submit","onConfirm","EncryptionCard","Component_default","oninit","vnode","refresh","view","loadState","LoadingIndicator_default","loadError","body","status","s","available","statusBadge","has_public_key","private_key_present","healthy","generate","keys_match","config_key","publicKeyPanel","public_key","publicKey","extractText_default","copyPublic","publicCopied","openRegenerate","kind","present","res","acknowledgeLoss","acknowledge_loss","modal","private_key","humanTime_namespaceObject","DIALECT_LABEL","mysql","mariadb","postgres","sqlite","BackupList_trans","BackupList","_vnode$attrs","backups","onDelete","map","b","id","created_at","humanTime_default","Date","filename","target_dialect","engine","size_bytes","contents","c","encrypted","href","_arrayLikeToArray","_slicedToArray","arrayWithHoles","l","u","f","next","done","push","iterableToArrayLimit","arrayLikeToArray","toString","slice","name","from","test","unsupportedIterableToArray","nonIterableRest","ownKeys","keys","getOwnPropertySymbols","filter","getOwnPropertyDescriptor","apply","_objectSpread","forEach","getOwnPropertyDescriptors","defineProperties","ExportModal_trans","ExportModal","stage","formContent","progressContent","checkbox","includeDb","v","includeAssets","includeStorage","includeExtensions","extensionsLoaded","loadExtensions","extensionList","targetDialect","encryptionEnabled","encryptionUseExternal","rows","placeholder","externalPublicKey","oninput","starting","canStart","start","extensionsLoading","extensions","groups","workbench","vendor","unknown","ext","_groups$ext$location","location","toggleAllExtensions","loc","extensionSelected","set","trim","_s$progress","_s$warnings$length","_s$warnings","isDone","phase","isError","pct","Math","max","min","progress","percent","style","width","role","processed_bytes","total_bytes","total_files","processed_files","total","warnings","count","w","idx","cancel","close","extensionsField","entries","_ref3","db","assets","storage","encryption","enabled","jobId","job_id","pump","polling","_this$status","onComplete","warn","ImportModal_ownKeys","ImportModal_objectSpread","ImportModal_trans","ImportModal","sectionDb","uploadContent","configureContent","accept","_files","files","file","size","uploadError","uploading","uploadIndeterminate","uploadProgress","upload","_res$meta$manifest","uploadWithProgress","inspect","meta","includes","sectionAssets","sectionStorage","sectionExtensions","exts","manifest","extensionsByName","onProgress","init","chunkSize","chunk_size","offset","end","attempt","sendChunk","Promise","round","resolve","reject","_session","xhr","XMLHttpRequest","lastProgress","now","idleTimer","setInterval","clearInterval","abort","stopIdleTimer","addEventListener","_JSON$parse","JSON","parse","responseText","statusText","open","withCredentials","setRequestHeader","csrf","session","csrfToken","send","flarum_version","join","source_url","selectionFieldset","is_encrypted","confirmReplace","startRestore","hasDb","hasAssets","hasStorage","hasExtensions","extList","sectionRow","asset_count","storage_count","extension_count","has_composer","buildSelection","extEntries","allChecked","every","_ref5","confirm_replace","selection","extracted_entries","restored_statements","completedContent","completedWarnings","_this$status$warnings","_this$status2","_this$status3","window","reload","incomplete","_this$status4","ConfirmModal_ownKeys","ConfirmModal_objectSpread","ConfirmModal","_this$attrs$confirmLa","_this$attrs$cancelLab","confirmLabel","cancelLabel","danger","decide","confirmed","resolved","onCancel","onbeforeremove","_this$attrs$onCancel","errorBoundary_trans","ErrorBoundary","failed","retry","lastError","children","_vnode$attrs$onError","onError","BackupPanel_trans","BackupPanel","openExport","openImport","renderList","listState","listError","delete","onRefresh","settled","settle","EXT_ID","override","ExtensionPage_default","original","extension","initializers","registry","for","registerSetting","registerPermission","label","permission"],"sourceRoot":""} \ No newline at end of file diff --git a/js/package-lock.json b/js/package-lock.json index 9fd0279..ae844e8 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -18,13 +18,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -33,9 +33,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -43,21 +43,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -74,14 +74,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -104,14 +104,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -178,9 +178,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -202,29 +202,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -307,9 +307,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -317,9 +317,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -327,9 +327,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -352,27 +352,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1725,33 +1725,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -1759,14 +1759,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2670,9 +2670,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "dev": true, "funding": [ { diff --git a/js/src/admin/components/ImportModal.tsx b/js/src/admin/components/ImportModal.tsx index 53d57b0..b1a1c13 100644 --- a/js/src/admin/components/ImportModal.tsx +++ b/js/src/admin/components/ImportModal.tsx @@ -75,9 +75,16 @@ interface ImportProgress { total_bytes: number; processed_bytes: number; extracted_entries: number; + skipped_entries?: number; + unresolved_entries?: number; restored_statements: number; percent: number; }; + // A restore that lost entries still ends in `done`. These two are + // what stop the completed screen from painting clean success over + // an incomplete restore. + warnings?: string[]; + incomplete?: boolean; } const trans = (key: string, params?: Record) => @@ -716,10 +723,33 @@ export default class ImportModal extends Modal { * * - Files only — the session is fine, just close. */ + /** + * Warning block for a restore that finished but lost entries, or that + * carries a stack advisory. Rendered on the completed screen in both + * shapes — an incomplete restore must never present as clean success, + * which is the failure mode this whole path exists to remove. + */ + completedWarnings() { + const warnings = this.status?.warnings ?? []; + if (!warnings.length) return null; + + return ( +
+ {trans("incomplete_title")} +
    + {warnings.map((w) => ( +
  • {w}
  • + ))} +
+
+ ); + } + completedContent() { if (this.sectionDb) { return (
+ {this.completedWarnings()}
@@ -744,13 +774,28 @@ export default class ImportModal extends Modal { ); } + const incomplete = !!this.status?.incomplete; + return (
-
- + {this.completedWarnings()} +
+
-

{trans("done_title")}

-

{trans("done_body")}

+

+ {trans(incomplete ? "incomplete_title" : "done_title")} +

+

+ {trans(incomplete ? "incomplete_body" : "done_body")} +

diff --git a/less/admin.less b/less/admin.less index 42480e8..a04a7cd 100644 --- a/less/admin.less +++ b/less/admin.less @@ -497,6 +497,21 @@ background: rgba(46, 204, 113, 0.14); color: #2ecc71; } +// Restauração que terminou sem gravar tudo. Repete o âmbar do estado +// base de propósito: um restore incompleto NUNCA pode herdar o verde. +.BackupImport-completedIcon--warning { + background: rgba(231, 159, 23, 0.12); + color: #e79f17; +} +.BackupImport-completedWarnings { + text-align: left; + width: 100%; + + ul { + margin: 6px 0 0; + padding-left: 18px; + } +} .BackupImport-completedTitle { margin: 0; font-size: 18px; diff --git a/locale/en.yml b/locale/en.yml index 66036c6..cc3ffb4 100644 --- a/locale/en.yml +++ b/locale/en.yml @@ -180,6 +180,8 @@ ramon-backup: reload_button: "Reload page" done_title: "Restore complete" done_body: "All selected files have been restored. The database was not modified, so your current session is unchanged." + incomplete_title: "Restore finished incomplete" + incomplete_body: "The restore ran to the end, but not everything selected was written. Review the details below before treating this migration as done." logout_title: "You've been logged out" logout_body: "Restoring the database replaced this server's `users` and `sessions` tables, so the account you were signed in with no longer exists here. Sign in again with the credentials of the imported forum to continue." logout_step_reload: "Reload the page below to clear the stale session cookie" diff --git a/locale/pt-BR.yml b/locale/pt-BR.yml index fa88ac4..5c078b2 100644 --- a/locale/pt-BR.yml +++ b/locale/pt-BR.yml @@ -180,6 +180,8 @@ ramon-backup: reload_button: "Recarregar página" done_title: "Restauração concluída" done_body: "Todos os arquivos selecionados foram restaurados. O banco não foi alterado, então sua sessão atual continua válida." + incomplete_title: "Restauração terminou incompleta" + incomplete_body: "A restauração rodou até o fim, mas não gravou tudo o que foi selecionado. Confira os detalhes abaixo antes de considerar esta migração concluída." logout_title: "Você foi desconectado" logout_body: "A restauração do banco substituiu as tabelas `users` e `sessions` deste servidor — a conta com que você estava logado não existe mais aqui. Faça login novamente com as credenciais do fórum de origem para continuar." logout_step_reload: "Recarregue a página abaixo para limpar o cookie de sessão antigo" diff --git a/src/Api/Controller/InspectImportController.php b/src/Api/Controller/InspectImportController.php index 73b751b..495c551 100644 --- a/src/Api/Controller/InspectImportController.php +++ b/src/Api/Controller/InspectImportController.php @@ -8,6 +8,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use Ramon\Backup\Archive\ArchiveReader; +use Ramon\Backup\Environment\StackSnapshot; use Ramon\Backup\StoragePaths; /** @@ -75,11 +76,24 @@ public function handle(ServerRequestInterface $request): ResponseInterface ]); } + // Stack gate at inspect time — the operator learns the restore + // is impossible BEFORE ticking "confirm replace", instead of + // after committing. ImportJob re-checks it as the authoritative + // backstop for the CLI and for direct API callers that skip + // inspect. The staged upload is left in place so a retry after + // a PHP upgrade doesn't need a re-upload; PruneStaleJobsCommand + // sweeps it if the operator walks away. + $blocking = StackSnapshot::blockingReason($archiveMeta); + if ($blocking !== null) { + throw new ValidationException(['archive' => $blocking]); + } + return new JsonResponse([ 'job_id' => $jobId, 'is_encrypted' => $isEncrypted, 'meta' => $archiveMeta, 'size' => $actual, + 'advisories' => StackSnapshot::advisories($archiveMeta), ]); } diff --git a/src/Api/Controller/TickImportController.php b/src/Api/Controller/TickImportController.php index 1bac27f..9f27e1b 100644 --- a/src/Api/Controller/TickImportController.php +++ b/src/Api/Controller/TickImportController.php @@ -56,6 +56,11 @@ public function handle(ServerRequestInterface $request): ResponseInterface 'phase' => $state->get('phase'), 'message' => $state->get('message'), 'progress' => $state->get('progress'), + // Restauração que perdeu entradas termina em `done`, então o + // frontend precisa destes dois para não pintar a tela de + // sucesso limpo por cima de um restore incompleto. + 'warnings' => $state->get('warnings', []), + 'incomplete' => (bool) $state->get('incomplete', false), ]); } } diff --git a/src/Environment/StackSnapshot.php b/src/Environment/StackSnapshot.php new file mode 100644 index 0000000..da1e000 --- /dev/null +++ b/src/Environment/StackSnapshot.php @@ -0,0 +1,224 @@ +} + */ + public static function capture(): array + { + $loaded = array_map('strtolower', get_loaded_extensions()); + sort($loaded, SORT_STRING); + + return [ + 'php_version' => PHP_VERSION, + 'php_minor' => self::minor(PHP_VERSION), + 'php_extensions' => $loaded, + ]; + } + + /** + * Reduz uma versão completa ao par `major.minor`. Devolve string + * vazia quando o valor não tem forma de versão — o chamador trata + * isso como "sem dado" e não bloqueia. + */ + public static function minor(string $version): string + { + if (preg_match('/\A(\d+)\.(\d+)/', $version, $m) !== 1) { + return ''; + } + + return $m[1].'.'.$m[2]; + } + + /** + * Motivo pelo qual este servidor NÃO pode receber o arquivo, ou + * null quando a restauração pode seguir. + * + * O bloqueio vale para qualquer seleção, inclusive só-banco: a + * linha `extensions_enabled` da tabela `settings` reativa no + * destino extensões que exigem a stack da origem, então restaurar + * apenas o banco derruba o fórum do mesmo jeito. + * + * Arquivos gerados antes deste campo existir ainda são cobertos + * pelo `php_version` que o meta de format_version 2 já gravava. + * Sem nenhum dos dois não há o que comparar e o import segue. + * + * @param array $meta + * @param list|null $hereExtensions + */ + public static function blockingReason(array $meta, ?array $hereExtensions = null): ?string + { + $source = self::sourceMinor($meta); + $here = self::minor(PHP_VERSION); + + if ($source !== '' && $here !== '' && version_compare($here, $source, '<')) { + return sprintf( + 'Backup gerado em PHP %s; este servidor roda PHP %s. Não é possível continuar: ' + .'o código que o arquivo carrega (vendor/, extensões e o composer.lock que as ' + .'resolveu) foi montado para PHP %s e pode usar sintaxe que o parser do PHP %s ' + .'recusa — a restauração deixaria o Flarum sem subir. Atualize este servidor ' + .'para PHP %s ou superior, ou gere o backup a partir de uma instalação em PHP %s.', + $source, + $here, + $source, + $here, + $source, + $here + ); + } + + $missing = self::missingCriticalExtensions($meta, $hereExtensions); + if ($missing !== []) { + return sprintf( + 'Não é possível continuar: este servidor não tem %s que a origem do backup ' + .'tinha — %s. Sem %s o Flarum não sobe depois da restauração. Habilite-%s no ' + .'PHP deste servidor e tente novamente.', + count($missing) === 1 ? 'uma extensão PHP obrigatória' : 'extensões PHP obrigatórias', + implode(', ', $missing), + count($missing) === 1 ? 'ela' : 'elas', + count($missing) === 1 ? 'a' : 'as' + ); + } + + return null; + } + + /** + * Extensões críticas presentes na origem e ausentes aqui. + * + * `$hereExtensions` existe para o teste poder simular um destino + * incompleto; em produção fica null e lê o SAPI corrente. Vale + * notar que a lista do CLI e a do FPM divergem no mesmo host — + * é por isso que a constante é curta e só tem itens sem os quais + * o Flarum não sobe em nenhum SAPI. + * + * @param array $meta + * @param list|null $hereExtensions + * @return list + */ + public static function missingCriticalExtensions(array $meta, ?array $hereExtensions = null): array + { + $sourceExts = self::stack($meta)['php_extensions'] ?? null; + if (! is_array($sourceExts)) { + return []; + } + + $source = array_map(fn ($e) => strtolower((string) $e), $sourceExts); + $here = array_map( + fn ($e) => strtolower((string) $e), + $hereExtensions ?? get_loaded_extensions() + ); + + $missing = []; + foreach (self::CRITICAL_EXTENSIONS as $ext) { + if (in_array($ext, $source, true) && ! in_array($ext, $here, true)) { + $missing[] = $ext; + } + } + + return $missing; + } + + /** + * Avisos não bloqueantes sobre a diferença de stack. Cobre o caso + * inverso do bloqueio — subir de 8.3 para 8.5 é seguro de rodar, + * mas o `composer.lock` restaurado continua resolvido para a + * versão antiga e precisa de um `composer update` no destino. + * + * @param array $meta + * @return list + */ + public static function advisories(array $meta): array + { + $source = self::sourceMinor($meta); + $here = self::minor(PHP_VERSION); + + if ($source === '' || $here === '' || $source === $here) { + return []; + } + + return [sprintf( + 'O composer.lock deste backup foi resolvido em PHP %s e este servidor roda PHP %s. ' + .'Rode `composer update` na raiz da instalação para reresolver as dependências ' + .'para a versão local.', + $source, + $here + )]; + } + + /** + * @param array $meta + * @return array + */ + private static function stack(array $meta): array + { + $stack = $meta[self::META_KEY] ?? null; + + return is_array($stack) ? $stack : []; + } + + /** + * `major.minor` da origem, aceitando tanto o retrato novo quanto o + * `php_version` solto dos arquivos de format_version 2. + * + * @param array $meta + */ + private static function sourceMinor(array $meta): string + { + $stack = self::stack($meta); + + $candidates = [ + $stack['php_minor'] ?? null, + $stack['php_version'] ?? null, + $meta['php_version'] ?? null, + ]; + + foreach ($candidates as $candidate) { + if (is_string($candidate) && self::minor($candidate) !== '') { + return self::minor($candidate); + } + } + + return ''; + } +} diff --git a/src/Job/ExportJob.php b/src/Job/ExportJob.php index ddd3387..254a3d2 100644 --- a/src/Job/ExportJob.php +++ b/src/Job/ExportJob.php @@ -11,6 +11,7 @@ use Ramon\Backup\Crypto\BackupCipher; use Ramon\Backup\Database\DatabaseDumper; use Ramon\Backup\Database\Dialect; +use Ramon\Backup\Environment\StackSnapshot; use Ramon\Backup\Extensions\Inventory; use Ramon\Backup\Models\Backup; use Ramon\Backup\StoragePaths; @@ -958,10 +959,14 @@ private function buildMeta(JobState $state): array } catch (Throwable) { /* leave blank if unknown */ } return [ - 'format_version' => 2, // 2 = adds source/target dialect tags + 'format_version' => 3, // 2 = source/target dialect tags; 3 = stack snapshot 'created_at' => gmdate('c'), 'flarum_version' => $this->detectFlarumVersion(), 'php_version' => PHP_VERSION, + // Retrato da stack desta origem. O import compara com a + // dele e recusa restaurar uma stack mais nova sobre uma + // mais antiga — ver StackSnapshot::blockingReason(). + StackSnapshot::META_KEY => StackSnapshot::capture(), 'contents' => array_keys(array_filter($contents)), 'source_dialect' => $sourceDialect, // The target the SQL was generated for. Equal to source on diff --git a/src/Job/ImportJob.php b/src/Job/ImportJob.php index 2e5d682..68d3e4a 100644 --- a/src/Job/ImportJob.php +++ b/src/Job/ImportJob.php @@ -11,6 +11,7 @@ use Ramon\Backup\Crypto\BackupCipher; use Ramon\Backup\Database\DatabaseRestorer; use Ramon\Backup\Database\Dialect; +use Ramon\Backup\Environment\StackSnapshot; use Ramon\Backup\StoragePaths; use RuntimeException; use Throwable; @@ -38,6 +39,9 @@ class ImportJob { public const BUDGET_BYTES = 4_194_304; + /** Nomes de entradas não resolvidas guardados para diagnóstico. */ + public const UNRESOLVED_SAMPLE_LIMIT = 20; + /** * Decryption private key for the current run. Held ONLY in memory * and NEVER written to the job-state file: persisting a user-pasted @@ -125,10 +129,22 @@ public function start(string $jobId, ?string $privateKey, bool $confirmReplace, 'selection' => $this->normaliseSelection($selection), ], 'archive_meta' => null, + 'warnings' => [], + // Amostra limitada dos nomes que não resolveram destino, + // para o operador saber O QUE ficou fora sem carregar o + // state com dezenas de milhares de caminhos. + 'unresolved_sample' => [], 'progress' => [ 'total_bytes' => filesize($upload) ?: 0, 'processed_bytes' => 0, 'extracted_entries' => 0, + // Entradas que o arquivo trazia e o operador desmarcou. + 'skipped_entries' => 0, + // Entradas aceitas na seleção cujo destino não pôde ser + // resolvido — restauração INCOMPLETA. Separado de + // `skipped_entries` porque um valor > 0 aqui é defeito, + // não escolha. + 'unresolved_entries' => 0, 'restored_statements' => 0, 'percent' => 0.0, ], @@ -195,6 +211,27 @@ private function runInspect(JobState $state): void $meta = $reader->meta(); $state->set('archive_meta', $meta); + // Stack guard — FIRST check, before the engine guard and + // before a single byte is written. A backup taken on a + // newer PHP cannot be replayed onto an older one: the + // restored vendor/ tree and composer.lock were resolved + // for the source's version. Unconditional (not gated on + // the file selection) because a DB-only restore also + // rewrites `extensions_enabled`, re-enabling extensions + // this PHP cannot parse. + $blocking = StackSnapshot::blockingReason($meta); + if ($blocking !== null) { + throw new RuntimeException($blocking); + } + + $advisories = StackSnapshot::advisories($meta); + if ($advisories !== []) { + $state->set('warnings', array_values(array_unique(array_merge( + (array) $state->get('warnings', []), + $advisories + )))); + } + // Cross-engine guard: archives from format_version >= 2 // record which engine the SQL was generated for. If that // disagrees with the live destination engine, abort early @@ -289,6 +326,14 @@ private function runExtract(JobState $state): void ? $this->resolveDestination($entry['name'], $state) : null; + // Aceita na seleção mas sem destino resolvível (id + // de extensão fora do mapa do manifesto, raiz não + // permitida). Os bytes são drenados igual, mas isso + // é perda de dado — nunca pode passar por "extraído". + $unresolved = $accept + && $entry['type'] === Format::TYPE_FILE + && $dest === null; + // Open a sink: SQL dump file, target file on disk, // or null (drain into the void). We always consume // the bytes — skipping just means not writing them. @@ -322,7 +367,14 @@ private function runExtract(JobState $state): void } } - $progress['extracted_entries']++; + if ($unresolved) { + $progress['unresolved_entries']++; + $this->noteUnresolved($state, (string) $entry['name']); + } elseif ($accept) { + $progress['extracted_entries']++; + } else { + $progress['skipped_entries']++; + } $budget -= $entry['length']; } } finally { @@ -618,13 +670,65 @@ private function runRewrite(JobState $state): void $state->save(); } + /** + * Registra, no máximo LIMITE vezes, o nome de uma entrada aceita + * cujo destino não resolveu. A amostra é limitada de propósito: o + * contador em `progress.unresolved_entries` é que diz o tamanho do + * estrago, a lista só serve para identificar o padrão. + */ + private function noteUnresolved(JobState $state, string $name): void + { + $sample = (array) $state->get('unresolved_sample', []); + if (count($sample) >= self::UNRESOLVED_SAMPLE_LIMIT) { + return; + } + + $sample[] = $name; + $state->set('unresolved_sample', array_values($sample)); + } + + /** + * Fecha o job. Uma restauração que perdeu entradas termina em + * `done` — os dados já estão no disco, abortar aqui não desfaz + * nada — mas NUNCA com a mensagem de sucesso liso: o contador de + * não-resolvidas vira aviso explícito, que é justamente o que + * faltava quando um import incompleto se apresentava como pronto. + */ private function runFinalize(JobState $state): void { $paths = $state->get('paths'); @unlink($paths['dump']); @unlink($paths['archive']); + + $progress = (array) $state->get('progress'); + $unresolved = (int) ($progress['unresolved_entries'] ?? 0); + $warnings = (array) $state->get('warnings', []); + + if ($unresolved > 0) { + $sample = array_map( + fn ($n) => (string) $n, + array_slice(array_values((array) $state->get('unresolved_sample', [])), 0, 5) + ); + $warnings[] = sprintf( + 'Restauração INCOMPLETA: %d entrada(s) selecionada(s) não pôde(ram) ser ' + .'gravada(s) porque o destino não resolveu%s. Confira a integridade do ' + .'arquivo e o manifesto de extensões antes de considerar esta migração ' + .'concluída.', + $unresolved, + $sample === [] ? '' : ' — por exemplo: '.implode(', ', $sample) + ); + } + + $state->set('warnings', array_values(array_unique(array_map( + fn ($w) => (string) $w, + $warnings + )))); + $state->set('incomplete', $unresolved > 0); $state->set('phase', 'done'); - $state->set('message', 'Restore complete.'); + $state->set('message', $unresolved > 0 + ? sprintf('Restore finished with %d unrestored entr%s.', $unresolved, $unresolved === 1 ? 'y' : 'ies') + : 'Restore complete.' + ); $state->save(); } diff --git a/tests/Unit/StackSnapshotTest.php b/tests/Unit/StackSnapshotTest.php new file mode 100644 index 0000000..7602fb6 --- /dev/null +++ b/tests/Unit/StackSnapshotTest.php @@ -0,0 +1,154 @@ + $minor, 'php_version' => $minor.'.0']; + if ($extensions !== null) { + $stack['php_extensions'] = $extensions; + } + + return [StackSnapshot::META_KEY => $stack]; + } + + private function nextMinorUp(): string + { + [$major, $minor] = explode('.', StackSnapshot::minor(PHP_VERSION)); + + return $major.'.'.((int) $minor + 1); + } + + private function previousMinorDown(): string + { + [$major, $minor] = explode('.', StackSnapshot::minor(PHP_VERSION)); + + return $major.'.'.max(0, (int) $minor - 1); + } + + public function test_minor_reduces_full_versions(): void + { + $this->assertSame('8.5', StackSnapshot::minor('8.5.4')); + $this->assertSame('8.3', StackSnapshot::minor('8.3.8-nts')); + $this->assertSame('8.4', StackSnapshot::minor('8.4')); + $this->assertSame('', StackSnapshot::minor('not-a-version')); + $this->assertSame('', StackSnapshot::minor('')); + } + + /** The headline case: newer origin onto older destination must not continue. */ + public function test_blocks_when_archive_comes_from_a_newer_php(): void + { + $reason = StackSnapshot::blockingReason($this->metaV3($this->nextMinorUp())); + + $this->assertNotNull($reason); + $this->assertStringContainsString($this->nextMinorUp(), $reason); + $this->assertStringContainsString(StackSnapshot::minor(PHP_VERSION), $reason); + } + + /** + * Archives written before the stack snapshot existed still carry a + * bare `php_version`, so the gate covers them too — that is exactly + * the archive the original incident was restored from. + */ + public function test_blocks_on_legacy_v2_meta_with_only_php_version(): void + { + $this->assertNotNull(StackSnapshot::blockingReason([ + 'php_version' => $this->nextMinorUp().'.1', + ])); + } + + public function test_allows_same_minor_and_reports_no_advisory(): void + { + $meta = $this->metaV3(StackSnapshot::minor(PHP_VERSION)); + + $this->assertNull(StackSnapshot::blockingReason($meta)); + $this->assertSame([], StackSnapshot::advisories($meta)); + } + + /** Older origin runs fine, but the restored composer.lock needs re-resolving. */ + public function test_allows_older_origin_with_composer_lock_advisory(): void + { + $meta = $this->metaV3($this->previousMinorDown()); + + $this->assertNull(StackSnapshot::blockingReason($meta)); + $advisories = StackSnapshot::advisories($meta); + $this->assertCount(1, $advisories); + $this->assertStringContainsString('composer update', $advisories[0]); + } + + /** + * No usable version data means no comparison — an ancient archive + * must keep importing exactly as it did before the gate shipped, + * rather than being blocked by a missing field. + */ + public function test_absent_or_malformed_metadata_never_blocks(): void + { + $this->assertNull(StackSnapshot::blockingReason([])); + $this->assertNull(StackSnapshot::blockingReason(['php_version' => 42])); + $this->assertNull(StackSnapshot::blockingReason([StackSnapshot::META_KEY => 'nonsense'])); + $this->assertSame([], StackSnapshot::advisories([])); + } + + public function test_blocks_when_destination_lost_a_critical_extension(): void + { + $meta = $this->metaV3( + StackSnapshot::minor(PHP_VERSION), + ['pdo', 'mbstring', 'json', 'openssl', 'fileinfo', 'tokenizer', 'dom'] + ); + + $missing = StackSnapshot::missingCriticalExtensions($meta, ['mbstring', 'json']); + $this->assertContains('pdo', $missing); + $this->assertContains('dom', $missing); + + $reason = StackSnapshot::blockingReason($meta, ['mbstring', 'json']); + $this->assertNotNull($reason); + $this->assertStringContainsString('pdo', $reason); + } + + /** A host-specific extension the source happened to have is not a blocker. */ + public function test_optional_extensions_are_not_blockers(): void + { + $meta = $this->metaV3( + StackSnapshot::minor(PHP_VERSION), + ['pdo', 'mbstring', 'redis', 'imagick', 'xdebug'] + ); + + $this->assertSame([], StackSnapshot::missingCriticalExtensions($meta, ['pdo', 'mbstring'])); + $this->assertNull(StackSnapshot::blockingReason($meta, ['pdo', 'mbstring'])); + } + + /** Case differences between SAPI listings must not read as missing. */ + public function test_extension_comparison_is_case_insensitive(): void + { + $meta = $this->metaV3(StackSnapshot::minor(PHP_VERSION), ['PDO', 'Mbstring']); + + $this->assertSame([], StackSnapshot::missingCriticalExtensions($meta, ['pdo', 'mbstring'])); + } + + public function test_capture_records_a_comparable_snapshot(): void + { + $snapshot = StackSnapshot::capture(); + + $this->assertSame(PHP_VERSION, $snapshot['php_version']); + $this->assertSame(StackSnapshot::minor(PHP_VERSION), $snapshot['php_minor']); + $this->assertContains('pdo', $snapshot['php_extensions']); + $this->assertSame(array_values($snapshot['php_extensions']), $snapshot['php_extensions']); + $this->assertNull(StackSnapshot::blockingReason([StackSnapshot::META_KEY => $snapshot])); + } +}