From 103a954376f94e0fa2a7d9d3f50ba052ee4a333d Mon Sep 17 00:00:00 2001 From: Ramon Guilherme Date: Sun, 10 May 2026 09:39:18 -0300 Subject: [PATCH 01/27] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 3e10269..3ba70e2 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ It started from my own need to migrate forums between hosts without the manual mysqldump-and-zip dance, and grew into a complete suite covering encryption, cross-server transfer, per-extension picking, and automatic URL rewriting. -> 🚧 **Active development** — first stable release coming soon! - --- ### ✨ Highlights From 65e30f81b20346ec880689b0dd488f299c041042 Mon Sep 17 00:00:00 2001 From: Ramon Guilherme Date: Sun, 10 May 2026 16:47:43 -0300 Subject: [PATCH 02/27] =?UTF-8?q?feat:=20Atualizar=20PostgresEmitter=20par?= =?UTF-8?q?a=20incluir=20novas=20considera=C3=A7=C3=B5es=20sobre=20chaves?= =?UTF-8?q?=20estrangeiras,=20=C3=ADndices=20e=20colunas=20ENUM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Database/Emitter/PostgresEmitter.php | 99 +++++++++++++++++++----- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/src/Database/Emitter/PostgresEmitter.php b/src/Database/Emitter/PostgresEmitter.php index d957940..84571dd 100644 --- a/src/Database/Emitter/PostgresEmitter.php +++ b/src/Database/Emitter/PostgresEmitter.php @@ -8,18 +8,24 @@ use Ramon\Backup\Database\Schema\Table; /** - * Emitter for PostgreSQL 10+. Three notable choices, each driven by - * what PG 10 actually permits without superuser on a managed host: + * Emitter for PostgreSQL 10+. Five notable choices, each driven by + * what PG 10 actually permits without superuser on a managed host AND + * by the reality that source MySQL data is rarely "clean": * - * - Foreign keys are emitted as separate `ALTER TABLE ADD CONSTRAINT` - * statements AFTER all data is loaded, never inline in CREATE - * TABLE. Two reasons: (a) PG validates parent-table existence at - * CREATE TABLE time, and our tables are emitted alphabetically — - * `discussions` would reference `users` before `users` exists. - * (b) Adding the constraint after data lets us load rows in any - * order without FK violations, then validate in one pass at the - * end. This sidesteps `SET session_replication_role = 'replica'`, - * which requires superuser and so fails on RDS/Neon/Supabase. + * - Foreign keys are emitted as separate `ALTER TABLE ADD CONSTRAINT + * ... NOT VALID` statements AFTER all data is loaded, never inline + * in CREATE TABLE. Three reasons: (a) PG validates parent-table + * existence at CREATE TABLE time, and our tables are emitted + * alphabetically — `discussions` would reference `users` before + * `users` exists. (b) Adding the constraint after data lets us + * load rows in any order without FK violations. (c) `NOT VALID` + * skips checking pre-existing rows: MySQL routinely permits + * orphan rows when `FOREIGN_KEY_CHECKS=0` is set during inserts, + * so a strict re-validation against PG would refuse to install + * the constraint at all and the migration would lose the FK + * entirely. New writes still get checked. This also sidesteps + * `SET session_replication_role = 'replica'`, which needs + * superuser and fails on RDS/Neon/Supabase. * * - Auto-increment columns are emitted as `GENERATED BY DEFAULT AS * IDENTITY` (PG 10+, SQL-standard). Inserts include explicit IDs @@ -28,6 +34,22 @@ * don't collide. `pg_get_serial_sequence` works for IDENTITY * columns since PG 10 (docs explicitly cover this case). * + * - Index names are namespaced per-schema in PG (vs. per-table in + * MySQL/MariaDB). MySQL forums frequently end up with `card_id` + * as an index on multiple tables; emitting them verbatim collides. + * We prefix every non-PK index with `__` when it's not + * already prefixed, which preserves uniqueness without inventing + * long synthesised names. + * + * - ENUM columns are emitted as plain TEXT, NOT TEXT + CHECK. MySQL + * silently coerces invalid ENUM values to `''` on insert and + * stores them — production MySQL data routinely contains values + * outside the declared member list. A CHECK on the destination + * would reject those rows entirely (we measured 8 rows lost on a + * real forum due to a single dirty `product_type` column). The + * introspector still records the original member list as a + * translation note so the admin knows the constraint is gone. + * * - All session prep is plain client setup (encoding, string * conformance) — no privileged GUCs are touched. */ @@ -89,17 +111,40 @@ public function emitSchema(Table $table): string $sql = 'DROP TABLE IF EXISTS ' . $name . ' CASCADE' . $this->delimiter() . $create . $this->delimiter(); - // Indexes are separate statements in PG. + // Indexes are separate statements in PG and live in a single + // schema-wide namespace — MySQL allows two tables to both have + // a `card_id` index, PG would error "relation already exists". + // Prefix with the table name when the source name doesn't + // already incorporate it (Laravel's default index naming + // convention is `
__index`, so most names already + // pass this check). foreach ($table->indexes as $idx) { if ($idx->primary) continue; - $unique = $idx->unique ? 'UNIQUE ' : ''; - $sql .= 'CREATE ' . $unique . 'INDEX ' . $this->quoteIdent($idx->name) + $unique = $idx->unique ? 'UNIQUE ' : ''; + $idxName = $this->namespacedIndexName($table->name, $idx->name); + $sql .= 'CREATE ' . $unique . 'INDEX ' . $this->quoteIdent($idxName) . ' ON ' . $name . ' (' . $this->columnList($idx->columns) . ')' . $this->delimiter(); } return $sql; } + /** + * MySQL index names are unique per-table; Postgres index names + * are unique per-schema. When the source name doesn't already + * include the table prefix (as Laravel-generated names do — + * `
__index`), prepend `
__` so multiple tables + * sharing a short index name (`card_id`, `user_id`, …) don't + * collide on the destination. + */ + private function namespacedIndexName(string $table, string $idxName): string + { + if (str_starts_with($idxName, $table . '_')) { + return $idxName; + } + return $table . '__' . $idxName; + } + public function emitInserts(Table $table, array $rows): string { if (empty($rows)) return ''; @@ -149,7 +194,17 @@ public function emitPostDataFixups(Table $table): string . ' (' . $this->columnList($fk->refColumns) . ')'; if ($fk->onDelete) $sql .= ' ON DELETE ' . $fk->onDelete; if ($fk->onUpdate) $sql .= ' ON UPDATE ' . $fk->onUpdate; - $sql .= $this->delimiter(); + // `NOT VALID` skips the consistency check against rows + // already present. MySQL routinely permits orphan rows + // (any insert under `SET FOREIGN_KEY_CHECKS=0`), so the + // pre-existing data on the source may not satisfy the FK. + // Without `NOT VALID` the migration would lose either the + // constraint or the orphan rows; with it, the constraint + // installs cleanly and only NEW writes are validated. + // Operators who want full validation can run + // `ALTER TABLE ... VALIDATE CONSTRAINT ...` after fixing + // the orphan rows. + $sql .= ' NOT VALID' . $this->delimiter(); } foreach ($table->autoIncrementColumns() as $col) { @@ -180,10 +235,16 @@ private function columnDdl(Column $col): string if (! $col->autoIncrement && ($col->default !== null || $col->defaultIsExpression)) { $sql .= ' DEFAULT ' . $this->renderDefault($col); } - if ($col->type === ColumnType::ENUM && ! empty($col->enumValues)) { - $list = implode(',', array_map(fn ($v) => $this->quoteString((string) $v), $col->enumValues)); - $sql .= ' CHECK (' . $this->quoteIdent($col->name) . ' IN (' . $list . '))'; - } + // ENUM is intentionally emitted as plain TEXT here — no CHECK. + // MySQL silently coerces out-of-range values to `''` and + // stores them, so production data routinely contains values + // outside the declared enum member list. A CHECK constraint + // on the destination would reject those rows in bulk and the + // entire INSERT batch would fail (we measured 8 rows lost on + // a real forum's `marketplace_order_items.product_type` + // because of exactly this). The introspector records the + // original member list as a translation note so the admin + // knows the constraint isn't being recreated. return $sql; } From 17adc08645f37ba0d5c9e254f0b788cbeb1515e4 Mon Sep 17 00:00:00 2001 From: Ramon Guilherme Date: Sun, 10 May 2026 17:05:22 -0300 Subject: [PATCH 03/27] =?UTF-8?q?feat:=20Adicionar=20suporte=20a=20notas?= =?UTF-8?q?=20de=20tradu=C3=A7=C3=A3o=20em=20emissores,=20incluindo=20avis?= =?UTF-8?q?os=20sobre=20=C3=ADndices=20n=C3=A3o=20suportados=20no=20Postgr?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Database/DatabaseDumper.php | 16 +++-- src/Database/Emitter/AbstractEmitter.php | 12 ++++ src/Database/Emitter/PostgresEmitter.php | 91 +++++++++++++++++++++++- src/Database/Emitter/SqlEmitter.php | 9 +++ 4 files changed, 121 insertions(+), 7 deletions(-) diff --git a/src/Database/DatabaseDumper.php b/src/Database/DatabaseDumper.php index a1ddb31..c21ae02 100644 --- a/src/Database/DatabaseDumper.php +++ b/src/Database/DatabaseDumper.php @@ -68,17 +68,21 @@ public function targetTag(): string } /** - * Lossy-translation notes accumulated by the introspector during - * THIS instance's lifetime — i.e. the warnings raised by tables - * described in this tick. The caller (ExportJob) merges them - * into the persistent job state so the UI can show the union - * across all ticks at the end. + * Lossy-translation notes accumulated during THIS instance's + * lifetime — both from the introspector (unsupported source types, + * generated columns, etc.) and from the emitter (e.g. PG skipping + * a FULLTEXT or oversize-btree index). The caller (ExportJob) + * merges them into the persistent job state so the UI can show + * the union across all ticks at the end. * * @return list */ public function warnings(): array { - return $this->introspector->warnings(); + return array_values(array_unique(array_merge( + $this->introspector->warnings(), + $this->emitter->warnings(), + ))); } /** diff --git a/src/Database/Emitter/AbstractEmitter.php b/src/Database/Emitter/AbstractEmitter.php index e4373b5..b0b7f40 100644 --- a/src/Database/Emitter/AbstractEmitter.php +++ b/src/Database/Emitter/AbstractEmitter.php @@ -20,6 +20,18 @@ public function emitPostDataFixups(\Ramon\Backup\Database\Schema\Table $table): return ''; } + /** + * Default: no notes. Concrete emitters that record translation + * notes (e.g. PG skipping FULLTEXT or oversized btree indexes) + * override this. + * + * @return list + */ + public function warnings(): array + { + return []; + } + /** * Quote an identifier (table name, column name, index name…). The * built-in escape for both backticks and double-quotes is to diff --git a/src/Database/Emitter/PostgresEmitter.php b/src/Database/Emitter/PostgresEmitter.php index 84571dd..68dcd16 100644 --- a/src/Database/Emitter/PostgresEmitter.php +++ b/src/Database/Emitter/PostgresEmitter.php @@ -8,7 +8,7 @@ use Ramon\Backup\Database\Schema\Table; /** - * Emitter for PostgreSQL 10+. Five notable choices, each driven by + * Emitter for PostgreSQL 10+. Six notable choices, each driven by * what PG 10 actually permits without superuser on a managed host AND * by the reality that source MySQL data is rarely "clean": * @@ -50,11 +50,41 @@ * introspector still records the original member list as a * translation note so the admin knows the constraint is gone. * + * - Indexes that would blow PG's btree row-size limit are SKIPPED. + * Two cases trigger this: (a) MySQL FULLTEXT/SPATIAL indexes, + * which have no portable PG equivalent (a real translation + * would mean GIN + tsvector + a separate populated column, well + * outside what a backup tool should be doing), and (b) plain + * btree indexes that include any TEXT/MEDIUMTEXT/LONGTEXT/BLOB + * column — MySQL silently takes a key prefix on these, while PG + * refuses any row whose indexed value exceeds ~2704 bytes (1/3 + * of an 8KB page) at INSERT time. We measured this on a real + * forum: posts.content with a 2920-byte post lost the entire + * INSERT batch. Skipping the index preserves all rows; an admin + * who needs full-text search uses Flarum's search extension + * anyway, which builds its own GIN/Meilisearch indexes. + * * - All session prep is plain client setup (encoding, string * conformance) — no privileged GUCs are touched. */ class PostgresEmitter extends AbstractEmitter { + /** @var list Cross-engine translation notes from this emitter. */ + private array $warnings = []; + + /** + * Notes about lossy translations the emitter applied — currently + * just "skipped index X because PG can't support it". Surfaced + * alongside the introspector's warnings on the export progress + * screen so the admin sees the full picture. + * + * @return list + */ + public function warnings(): array + { + return $this->warnings; + } + protected function identQuote(): string { return '"'; @@ -118,8 +148,27 @@ public function emitSchema(Table $table): string // already incorporate it (Laravel's default index naming // convention is `
__index`, so most names already // pass this check). + // + // Two index shapes are silently dropped here, with a warning, + // because PG can't replicate them faithfully: + // - FULLTEXT / SPATIAL — no portable equivalent. + // - btree on TEXT/BLOB columns — MySQL stores a key prefix, + // PG would refuse any row > 2704 bytes (1/3 of a page). + // The DATA still arrives intact; only the index is gone. foreach ($table->indexes as $idx) { if ($idx->primary) continue; + + $skipReason = $this->indexSkipReason($table, $idx); + if ($skipReason !== null) { + $this->warnings[] = sprintf( + 'Index `%s`.`%s` skipped on PostgreSQL: %s. The underlying data is preserved; if you rely on this index for queries, recreate it manually with a tailored expression (e.g. `md5(col)` for equality lookups, or a GIN tsvector index for full-text search).', + $table->name, + $idx->name, + $skipReason, + ); + continue; + } + $unique = $idx->unique ? 'UNIQUE ' : ''; $idxName = $this->namespacedIndexName($table->name, $idx->name); $sql .= 'CREATE ' . $unique . 'INDEX ' . $this->quoteIdent($idxName) @@ -129,6 +178,46 @@ public function emitSchema(Table $table): string return $sql; } + /** + * Return the human-readable reason the index can't be created on + * PG, or `null` if it's safe to emit. Reasons: + * - FULLTEXT / SPATIAL: no btree-equivalent on PG. + * - btree over a TEXT/BLOB column: PG btree refuses rows whose + * indexed values exceed ~2704 bytes; MySQL silently takes a + * key prefix instead, so the source is fine but the + * destination would lose any insert that exceeds the limit. + */ + private function indexSkipReason(Table $table, \Ramon\Backup\Database\Schema\Index $idx): ?string + { + $kind = strtoupper((string) ($idx->kind ?? '')); + if ($kind === 'FULLTEXT') { + return 'FULLTEXT indexes are MySQL-specific'; + } + if ($kind === 'SPATIAL') { + return 'SPATIAL indexes require PostGIS, not portable'; + } + + // The unbounded text/blob types — TEXT/MEDIUMTEXT/LONGTEXT and + // their BLOB siblings — can hold values that exceed PG's btree + // page-third limit (~2704 bytes). VARCHAR/CHAR are bounded by + // their declared length and stay safe; ENUM is short by + // construction. + $unsafe = [ + ColumnType::TEXT, ColumnType::MEDIUMTEXT, ColumnType::LONGTEXT, + ColumnType::BLOB, ColumnType::MEDIUMBLOB, ColumnType::LONGBLOB, + ]; + foreach ($idx->columns as $colName) { + $col = $table->column($colName); + if ($col !== null && in_array($col->type, $unsafe, true)) { + return sprintf( + 'btree indexes over %s columns can exceed PG\'s 2704-byte index-tuple limit', + strtoupper($col->type->value) + ); + } + } + return null; + } + /** * MySQL index names are unique per-table; Postgres index names * are unique per-schema. When the source name doesn't already diff --git a/src/Database/Emitter/SqlEmitter.php b/src/Database/Emitter/SqlEmitter.php index 9ab7d59..1548596 100644 --- a/src/Database/Emitter/SqlEmitter.php +++ b/src/Database/Emitter/SqlEmitter.php @@ -42,4 +42,13 @@ public function emitPostDataFixups(Table $table): string; /** The dialect tag stored in the archive meta header. */ public function targetTag(): string; + + /** + * Cross-engine translation notes the emitter accumulated — e.g. + * "skipped FULLTEXT index because PG doesn't support it". Empty + * for emitters that always render a faithful 1:1 of the source. + * + * @return list + */ + public function warnings(): array; } From d84fe4b0e643fcbc5c8103236e9ff28e359b06be Mon Sep 17 00:00:00 2001 From: Ramon Guilherme Date: Sun, 10 May 2026 17:16:41 -0300 Subject: [PATCH 04/27] =?UTF-8?q?feat:=20Melhorar=20o=20controle=20de=20do?= =?UTF-8?q?wnload=20de=20backups=20com=20suporte=20a=20faixas=20de=20bytes?= =?UTF-8?q?=20e=20gerenciamento=20de=20mem=C3=B3ria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/DownloadBackupController.php | 174 ++++++++++++++++-- 1 file changed, 159 insertions(+), 15 deletions(-) diff --git a/src/Api/Controller/DownloadBackupController.php b/src/Api/Controller/DownloadBackupController.php index 3bbaa4e..b52197a 100644 --- a/src/Api/Controller/DownloadBackupController.php +++ b/src/Api/Controller/DownloadBackupController.php @@ -3,8 +3,7 @@ namespace Ramon\Backup\Api\Controller; use Flarum\Foundation\ValidationException; -use Laminas\Diactoros\Response; -use Laminas\Diactoros\Stream; +use Laminas\Diactoros\Response\EmptyResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -12,16 +11,37 @@ use Ramon\Backup\StoragePaths; /** - * Streams a saved backup file to the browser. Path resolution goes - * through `StoragePaths::backupFilePath()`, which whitelists the - * filename pattern and verifies the resolved absolute path lives - * under the canonical backups directory — so a malicious id that - * resolves to a row with a tampered filename can't escape. + * Streams a saved backup file to the browser. + * + * Path resolution goes through `StoragePaths::backupFilePath()`, + * which whitelists the filename pattern and verifies the resolved + * absolute path lives under the canonical backups directory — so a + * malicious id that resolves to a row with a tampered filename can't + * escape. + * + * Why we bypass PSR-7 emission for the body: + * + * Wrapping the file in a `Stream` and returning a PSR-7 `Response` + * *should* let the framework's SAPI emitter pump bytes through in + * chunks. In practice, several middlewares in the Flarum stack + * (request/response logging, CORS, CSP) do `(string) $stream` + * somewhere along the chain — which materialises the whole body in + * memory. With a 1+ GB `.flarum` archive that promptly hits + * `memory_limit`, PHP-FPM dies with a 502, and the browser ends up + * on `chrome-error://chromewebdata/` (the symptom this fix targets). + * + * So we send headers + body straight to the SAPI output and call + * `exit` — no middleware ever gets to read the body. Plain HTTP/1.1 + * `Range` is honoured so a dropped connection on a multi-GB + * download can resume from the last byte transferred. */ class DownloadBackupController implements RequestHandlerInterface { use AdminOnlyController; + /** Bytes per `fread`/`echo` cycle. 8 MB balances syscalls vs RAM. */ + private const STREAM_CHUNK_BYTES = 8 * 1024 * 1024; + public function __construct( protected StoragePaths $paths ) { @@ -50,16 +70,140 @@ public function handle(ServerRequestInterface $request): ResponseInterface ]); } - $fh = fopen($abs, 'rb'); + $size = filesize($abs) ?: 0; + $range = $this->parseRange($request->getHeaderLine('Range'), $size); + + $this->emitFile($abs, $size, $range, $backup->filename); + + // Unreachable in practice (emitFile() exits). Returning a + // bare empty response keeps the type contract honest in + // case a test harness ever calls this without the SAPI. + return new EmptyResponse(200); + } + + /** + * Stream the (possibly partial) file body straight to the SAPI + * output and `exit`. After this call, no further middleware runs + * and PHP-FPM doesn't try to materialise the response body. + * + * @param array{0:int,1:int}|null $range Byte range [first, last] inclusive, or null for whole file. + */ + private function emitFile(string $abs, int $size, ?array $range, string $filename): never + { + // Drain any output buffers Flarum / middlewares started. + // Without this, every fread/echo gets buffered (defeats the + // chunked-emit purpose) and may also crash on memory_limit. + while (ob_get_level() > 0) { @ob_end_clean(); } + + // Long downloads may run for many minutes on slow links; + // ignore_user_abort is intentionally OFF so a closed tab + // stops the script immediately and frees the FPM worker. + @set_time_limit(0); + + if ($range === null) { + $start = 0; + $end = max(0, $size - 1); + $status = 200; + $contentLength = $size; + } else { + [$start, $end] = $range; + $status = 206; + $contentLength = $end - $start + 1; + } + + // Headers must precede the first byte of body — at this point + // ob is closed, so each header() goes out as soon as we send + // the first byte. Quote the filename to survive spaces / non- + // ASCII (RFC 5987 'filename*' covers UTF-8 properly). + $safeAscii = preg_replace('/[^A-Za-z0-9._-]/', '_', $filename); + $utf8 = rawurlencode($filename); + + http_response_code($status); + header('Content-Type: application/octet-stream'); + header('Content-Length: '.$contentLength); + header('Content-Disposition: attachment; filename="'.$safeAscii.'"; filename*=UTF-8\'\''.$utf8); + header('Accept-Ranges: bytes'); + header('X-Content-Type-Options: nosniff'); + header('Cache-Control: no-store'); + // nginx (the most common reverse proxy in front of Flarum) + // buffers proxied responses to disk by default. With a multi- + // GB body that means a long pause at the start, and an even + // longer one at the end before bytes reach the browser. This + // header asks nginx to passthrough chunks as PHP emits them. + // Apache + most other servers ignore the header harmlessly. + header('X-Accel-Buffering: no'); + if ($status === 206) { + header(sprintf('Content-Range: bytes %d-%d/%d', $start, $end, $size)); + } + + $fh = @fopen($abs, 'rb'); if ($fh === false) { - throw new ValidationException(['file' => 'Could not open backup file.']); + // Headers already flushed — best we can do is end the + // connection cleanly. The browser will treat it as a + // truncated download and (because we set Accept-Ranges) + // can retry the missing range. + exit; + } + + try { + if ($start > 0 && fseek($fh, $start) !== 0) { + exit; // could not seek — give up cleanly + } + + $remaining = $contentLength; + while ($remaining > 0 && ! feof($fh)) { + if (connection_aborted()) break; + + $want = (int) min(self::STREAM_CHUNK_BYTES, $remaining); + $chunk = fread($fh, $want); + if ($chunk === false || $chunk === '') break; + + echo $chunk; + @flush(); + + $remaining -= strlen($chunk); + } + } finally { + fclose($fh); + } + + exit; + } + + /** + * Parse a single-range `Range: bytes=START-END` header. Returns + * `[start, end]` (inclusive) or null when the header is absent / + * malformed / unsatisfiable. Multi-range requests are intentionally + * not supported — they require multipart/byteranges responses, + * which add complexity for marginal benefit on a single big + * archive download. + * + * @return array{0:int,1:int}|null + */ + private function parseRange(string $header, int $size): ?array + { + if ($header === '' || $size <= 0) return null; + if (! preg_match('/^bytes=(\d*)-(\d*)$/i', trim($header), $m)) return null; + + $startStr = $m[1]; + $endStr = $m[2]; + + if ($startStr === '' && $endStr === '') return null; + + if ($startStr === '') { + // Suffix range "-N" = last N bytes. + $suffix = (int) $endStr; + if ($suffix <= 0) return null; + $start = max(0, $size - $suffix); + $end = $size - 1; + } else { + $start = (int) $startStr; + $end = $endStr === '' ? $size - 1 : (int) $endStr; } - return (new Response(new Stream($fh))) - ->withHeader('Content-Type', 'application/octet-stream') - ->withHeader('Content-Length', (string) filesize($abs)) - ->withHeader('Content-Disposition', 'attachment; filename="'.$backup->filename.'"') - ->withHeader('X-Content-Type-Options', 'nosniff') - ->withHeader('Cache-Control', 'no-store'); + if ($start > $end || $start >= $size) return null; + if ($end >= $size) $end = $size - 1; + + return [$start, $end]; } } From 8eb60c8494a92cc1a6e0e028eeb6b6f6b5ea12b1 Mon Sep 17 00:00:00 2001 From: Ramon Guilherme Date: Sun, 10 May 2026 17:42:54 -0300 Subject: [PATCH 05/27] feat: Implement chunked upload for backup imports - Introduced a new chunked upload mechanism in ImportModal.tsx to handle large .flarum archive uploads. - Added fallback chunk size and retry logic for failed chunks. - Created new API endpoints for handling chunk uploads and inspecting the uploaded files. - Updated UploadImportController to initialize chunked uploads and validate file sizes. - Added ChunkImportController to append chunks to the staging file and ensure idempotency. - Implemented InspectImportController to validate the completeness of the uploaded file and retrieve metadata. --- extend.php | 14 +- js/dist/admin.js | 2 +- js/dist/admin.js.map | 2 +- js/src/admin/components/ImportModal.tsx | 124 ++++++++---- src/Api/Controller/ChunkImportController.php | 176 ++++++++++++++++++ .../Controller/InspectImportController.php | 96 ++++++++++ src/Api/Controller/UploadImportController.php | 111 +++++++---- 7 files changed, 448 insertions(+), 77 deletions(-) create mode 100644 src/Api/Controller/ChunkImportController.php create mode 100644 src/Api/Controller/InspectImportController.php diff --git a/extend.php b/extend.php index afb770b..0769d6b 100644 --- a/extend.php +++ b/extend.php @@ -5,10 +5,12 @@ use Flarum\Extend; use Ramon\Backup\Api\Controller\CancelExportController; use Ramon\Backup\Api\Controller\CancelImportController; +use Ramon\Backup\Api\Controller\ChunkImportController; use Ramon\Backup\Api\Controller\DeleteBackupController; use Ramon\Backup\Api\Controller\DownloadBackupController; use Ramon\Backup\Api\Controller\EncryptionStatusController; use Ramon\Backup\Api\Controller\GenerateKeypairController; +use Ramon\Backup\Api\Controller\InspectImportController; use Ramon\Backup\Api\Controller\ListBackupsController; use Ramon\Backup\Api\Controller\ListExtensionsController; use Ramon\Backup\Api\Controller\StartExportController; @@ -40,10 +42,14 @@ ->post('/backup/exports/{id:[a-f0-9]+}/tick', 'backup.export.tick', TickExportController::class) ->delete('/backup/exports/{id:[a-f0-9]+}', 'backup.export.cancel', CancelExportController::class) - ->post('/backup/imports', 'backup.import.upload', UploadImportController::class) - ->post('/backup/imports/{id:[a-f0-9]+}/start', 'backup.import.start', StartImportController::class) - ->post('/backup/imports/{id:[a-f0-9]+}/tick', 'backup.import.tick', TickImportController::class) - ->delete('/backup/imports/{id:[a-f0-9]+}', 'backup.import.cancel', CancelImportController::class) + // Chunked upload protocol — see UploadImportController docblock + // for why this replaced the old single multipart POST. + ->post('/backup/imports', 'backup.import.upload', UploadImportController::class) + ->post('/backup/imports/{id:[a-f0-9]+}/chunk', 'backup.import.chunk', ChunkImportController::class) + ->post('/backup/imports/{id:[a-f0-9]+}/inspect', 'backup.import.inspect', InspectImportController::class) + ->post('/backup/imports/{id:[a-f0-9]+}/start', 'backup.import.start', StartImportController::class) + ->post('/backup/imports/{id:[a-f0-9]+}/tick', 'backup.import.tick', TickImportController::class) + ->delete('/backup/imports/{id:[a-f0-9]+}', 'backup.import.cancel', CancelImportController::class) ->get('/backup/encryption/status', 'backup.encryption.status', EncryptionStatusController::class) ->post('/backup/encryption/generate-keypair','backup.encryption.generate', GenerateKeypairController::class) diff --git a/js/dist/admin.js b/js/dist/admin.js index c4dc416..ba8e911 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()(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 M 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 D("title")}content(){return"form"===this.stage?this.formContent():this.progressContent()}formContent(){return m("div",{className:"Modal-body"},m("p",{className:"helpText"},D("intro")),m("fieldset",{className:"BackupExport-fieldset"},m("legend",null,D("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,D("target_title")),m("p",{className:"helpText"},D("target_help")),m("select",{className:"FormControl BackupExport-targetSelect",value:this.targetDialect,onchange:t=>{this.targetDialect=t.target.value}},m("option",{value:""},D("target_same")),m("option",{value:"mysql"},D("target_mysql")),m("option",{value:"mariadb"},D("target_mariadb")),m("option",{value:"postgres"},D("target_postgres")),m("option",{value:"sqlite"},D("target_sqlite")))),m("fieldset",{className:"BackupExport-fieldset"},m("legend",null,D("encryption_title")),m("label",{className:"BackupExport-checkbox"},m("input",{type:"checkbox",checked:this.encryptionEnabled,onchange:t=>{this.encryptionEnabled=t.target.checked}})," ",m("span",null,D("encryption_enable"))),m("p",{className:"helpText"},D("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,D("encryption_external"))),this.encryptionUseExternal&&m("[",null,m("p",{className:"helpText"},D("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()},D("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"},D("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)},D("extensions_select_all")),m("span",null," · "),m("button",{type:"button",className:"BackupExport-extLink",onclick:()=>this.toggleAllExtensions(!1)},D("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"},D("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)},D("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(D("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"},D("content_"+t)),m("span",{className:"BackupExport-checkbox-help helpText"},D("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,D("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,D("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"})," ",D("warnings_title",{count:a.warnings.length})),m("p",{className:"helpText"},D("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()},D("cancel_button")),(n||r)&&m(p(),{className:"Button Button--primary",onclick:()=>this.close()},D("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(D("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(D("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"},D("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"},D("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 q 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()}}}uploadWithProgress(t,e){return new Promise((a,n)=>{var r;const o=new FormData;o.append("archive",t);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",t=>{l=Date.now(),t.lengthComputable&&e(Math.round(t.loaded/Math.max(t.total,1)*100))}),i.upload.addEventListener("load",()=>{l=Date.now(),e(100)}),i.upload.addEventListener("error",()=>{m(),n({detail:F("upload_failed")})}),i.addEventListener("load",()=>{if(m(),i.status>=200&&i.status<300)try{a(JSON.parse(i.responseText))}catch(t){n({detail:F("upload_failed")})}else{let e;try{var t;const s=JSON.parse(i.responseText);e=null==s||null==(t=s.errors)||null==(t=t[0])?void 0:t.detail}catch(t){}n({detail:e||"".concat(i.status," ").concat(i.statusText)})}}),i.addEventListener("error",()=>{m(),n({detail:F("upload_failed")})}),i.addEventListener("abort",()=>{m(),n({detail:F("upload_idle_timeout")})}),i.open("POST","".concat(k(),"/backup/imports"),!0),i.withCredentials=!0;const u=null==(r=s().session)?void 0:r.csrfToken;u&&i.setRequestHeader("X-CSRF-Token",u),i.send(o)})}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]=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 s().request({method:"POST",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id,"/tick")});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 s().request({method:"DELETE",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id)})}catch(t){console.error("[backup] import cancel failed",t),s().alerts.show({type:"warning"},F("cancel_failed_warn"))}this.close()}}close(){this.hide()}}function U(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 z(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 J=(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,J("panel.actions_title")),m("p",{className:"helpText"},J("panel.actions_help")),m("div",{className:"BackupPanel-actionButtons"},m(p(),{className:"Button Button--primary",icon:"fas fa-download",onclick:()=>this.openExport()},J("panel.create_button")),m(p(),{className:"Button",icon:"fas fa-upload",onclick:()=>this.openImport()},J("panel.import_button")))),m(E,null),m("section",{className:"BackupPanel-list"},m("h3",null,J("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,J("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()},J("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(M,{onComplete:()=>this.refresh()})}openImport(){s().modal.show(q,{onComplete:()=>this.refresh()})}async delete(t){var e;if(await(e={title:J("list.confirm_delete_title"),body:J("list.confirm_delete"),confirmLabel:J("list.delete_title"),danger:!0},new Promise(t=>{let a=!1;const n=e=>{a||(a=!0,t(e))};s().modal.show(G,z(z({},e),{},{onConfirm:()=>n(!0),onCancel:()=>n(!1)}))})))try{await x({method:"DELETE",url:"".concat(k(),"/backup/backups/").concat(t),surface:!1,fallbackMessage:String(J("list.delete_failed"))}),s().alerts.show({type:"success"},J("list.deleted")),this.refresh()}catch(t){s().alerts.show({type:"error"},v(t,String(J("list.delete_failed"))))}}}flarum.reg.add("ramon-backup","admin/components/BackupPanel",W);const X="ramon-backup";(0,a.override)(r().prototype,"submitButton",function(t){return this.extension&&this.extension.id===X?null:t()}),s().initializers.add(X,()=>{s().registry.for(X).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={}})(); +(()=>{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 x(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 v(t){try{return await s().request(t)}catch(e){const a=x(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:x});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,v({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=x(t)}).then(()=>{m.redraw()})}async generate(t){try{const e=await v({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"},x(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()(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 v({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"},x(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 v({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"},x(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 v({method:"POST",url:"".concat(k(),"/backup/exports/").concat(this.jobId,"/tick"),surface:!1});this.status=t,m.redraw()}catch(t){const e=x(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 v({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=x(t,String(F("upload_failed")))}finally{this.uploading=!1,m.redraw()}}}async uploadWithProgress(t,e){const s=await v({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),v({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:F("upload_failed")})}),i.addEventListener("abort",()=>{m(),r({detail: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]=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 v({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"},x(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 s().request({method:"POST",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id,"/tick")});this.status=t,m.redraw()}catch(t){console.error("[backup] import tick failed",t);const e=x(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 s().request({method:"DELETE",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id)})}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,v({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=x(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 v({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"},x(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={}})(); //# 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 33b8945..982fe4d 100644 --- a/js/dist/admin.js.map +++ b/js/dist/admin.js.map @@ -1 +1 @@ -{"version":3,"file":"admin.js","mappings":"MACA,IAAAA,EAAA,CCAAA,EAAAC,IACA,IAAAC,EAAAD,GAAAA,EAAAE,WACA,IAAAF,EAAA,QACA,MAEA,OADAD,EAAAI,EAAAF,EAAA,CAAiCG,EAAAH,IACjCA,GCLAF,EAAA,CAAAM,EAAAC,KACA,QAAAC,KAAAD,EACAP,EAAAS,EAAAF,EAAAC,KAAAR,EAAAS,EAAAH,EAAAE,IACAE,OAAAC,eAAAL,EAAAE,EAAA,CAAyCI,YAAA,EAAAC,IAAAN,EAAAC,MCJzCR,EAAA,CAAAc,EAAAC,IAAAL,OAAAM,UAAAC,eAAAC,KAAAJ,EAAAC,uBCAA,MAAMI,EAA4BC,OAAAC,IAAAR,IAAA,iCCAlC,MAAMS,EAA4BF,OAAAC,IAAAR,IAAA,wBCA5BU,EAA4BH,OAAAC,IAAAR,IAAA,sDCAlC,SAASW,EAAQf,GAGf,OAAOe,EAAU,mBAAqBC,QAAU,iBAAmBA,OAAOC,SAAW,SAAUjB,GAC7F,cAAcA,CAChB,EAAI,SAAUA,GACZ,OAAOA,GAAK,mBAAqBgB,QAAUhB,EAAEkB,cAAgBF,QAAUhB,IAAMgB,OAAOT,UAAY,gBAAkBP,CACpH,EAAGe,EAAQf,EACb,CCPA,SAASmB,EAAgBC,EAAGC,EAAGC,GAC7B,OAAQD,ECAV,SAAuBC,GACrB,IAAIC,ECFN,SAAqBD,GACnB,GAAI,UAAYP,EAAQO,KAAOA,EAAG,OAAOA,EACzC,IAAIF,EAAIE,EAAEN,OAAOQ,aACjB,QAAS,IAAMJ,EAAG,CAChB,IAAIG,EAAIH,EAAEX,KAAKa,EAAGD,UAClB,GAAI,UAAYN,EAAQQ,GAAI,OAAOA,EACnC,MAAM,IAAIE,UAAU,+CACtB,CACA,OAAyBC,OAAiBJ,EAC5C,CDPUE,CAAYF,GACpB,MAAO,UAAYP,EAAQQ,GAAKA,EAAIA,EAAI,EAC1C,CDHcI,CAAcN,MAAOD,EAAInB,OAAOC,eAAekB,EAAGC,EAAG,CAC/DO,MAAON,EACPnB,YAAY,EACZ0B,cAAc,EACdC,UAAU,IACPV,EAAEC,GAAKC,EAAGF,CACjB,CGRA,MAAMW,EAA4BpB,OAAAC,IAAAR,IAAA,wCCAlC,MAAM4B,EAA4BrB,OAAAC,IAAAR,IAAA,gDCAlC,MAAM6B,EAA4BtB,OAAAC,IAAAR,IAAA,0DCAlC,MAAM8B,EAA4BvB,OAAAC,IAAAR,IAAA,+CCAlC,MAAM+B,EAA4BxB,OAAAC,IAAAR,IAAA,gDCE3B,SAAAgC,IACP,OAAUC,IAAAC,MAASC,UAAA,mBAAAC,QAAA,UACnB,CACO,SAAAC,EAAAC,GACP,IAAAC,OAAAC,SAAAF,IAAAA,GAAA,cACA,MAAAG,EAAA,0BACA,IAAAtB,EAAA,EACAuB,EAAAJ,EACA,KAAAI,GAAA,MAAAvB,EAAAsB,EAAAE,OAAA,GACAD,GAAA,KACAvB,IAEA,OAAAuB,EAAAE,QAAAF,GAAA,SAAAvB,EAAA,SAAAsB,EAAAtB,EACA,CAGO,SAAA0B,EAAAC,EAAAC,GACP,IAAAC,EAAAC,EAAAC,EACA,MAAAC,EAAA,OAAAH,EAAA,OAAAC,EAAA,MAAAH,GAAA,OAAAI,EAAAJ,EAAAM,WAAA,OAAAF,EAAAA,EAAAG,SAAA,OAAAH,EAAAA,EAAA,WAAAA,EAAAC,QAAAF,EAAA,MAAAH,OAAA,EAAAA,EAAAK,QAAAH,EAAA,uBAAAF,OAAA,EAAAA,EAAAQ,SAAAR,EAAAQ,aAAAC,EACA,OAAAJ,EAAA7B,OAAA6B,GACAJ,GACAzB,OAAgBW,IAAAuB,WAAcC,MAAA,qCAC9B,CAUOC,eAAAC,EAAAC,GACP,IACA,aAAiB3B,IAAA4B,QAAWD,EAC5B,CAAI,MAAAd,GACJ,MAAAK,EAAAN,EAAAC,EAAAc,EAAAE,iBAOA,GANAC,QAAAC,MAAA,qBAAAJ,EAAAK,OAAAL,EAAAM,IAAApB,IACA,IAAAc,EAAAO,SACMlC,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACOnB,GAEPL,GAAA,iBAAAA,IAAAA,EAAAK,OACA,IACAL,EAAAK,OAAAA,CACA,CAAQ,MAAAoB,GAER,CAEA,MAAAzB,CACA,CACA,CACAvC,OAAAC,IAAAgE,IAAA,kCAAoDxC,OAAAA,EAAAK,SAAAA,EAAAQ,YAAAA,IC/CpD,MAAAY,EAAA,CAAA9D,EAAA8E,IAA+BxC,IAAAuB,WAAcC,MAAA,iCAAAiB,OAAA/E,GAAA,MAAA8E,EAAAA,EAAA,IAC7C,MAAAE,UAAiCC,KACjC,WAAA9D,GACA+D,SAAAC,WACI/D,EAAegE,KAAA,YACnB,CACA,SAAAC,GACA,uCACA,CACA,KAAAC,GACA,OAAAxB,EAAA,qBACA,CACA,OAAAyB,GACA,MAAAC,EAAAJ,KAAAK,MACAC,EAAAF,EAAAE,WACAC,EAAAH,EAAAG,UACAC,EAAA,IAAAb,OAAAY,EAAA,UAAAZ,OAAAW,EAAA,MACA,OAAAG,EAAA,OACAR,UAAA,cACKQ,EAAA,SAAA/B,EAAA,uBAAA+B,EAAA,OACLR,UAAA,sBACKQ,EAAA,cAAA/B,EAAA,+BAAA+B,EAAA,SAAA/B,EAAA,+BAAA+B,EAAA,SACLR,UAAA,sBACKvB,EAAA,+BAAA+B,EAAA,OACLR,UAAA,wBACKQ,EAAA,YAAAD,IAAAC,EAAA,OACLR,UAAA,mCACKQ,EAAIC,IAAM,CACfT,UAAA,SACAU,KAAA,cACAC,QAAA,IAAAZ,KAAAa,KAAAL,IACKR,KAAAc,OAAApC,EAAA,uBAAAA,EAAA,6BAAA+B,EAAqFC,IAAM,CAChGT,UAAA,yBACAW,QAAA,IAAAZ,KAAAe,QACKrC,EAAA,wBACL,CACA,IAAAmC,CAAAL,GACAQ,UAAAC,UAMAD,UAAAC,UAAAC,UAAAV,GAAAW,KAAA,KACAnB,KAAAc,QAAA,EACAL,EAAAW,SACAC,WAAA,KACArB,KAAAc,QAAA,EACAL,EAAAW,UACO,OACFE,MAAAC,IACLvC,QAAAC,MAAA,sCAAAsC,GACMrE,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACOb,EAAA,uBAhBDxB,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACOb,EAAA,yBAgBP,EAEA,MAAA8C,UAAqC3B,KACrC,WAAA9D,GACA+D,SAAAC,WACI/D,EAAegE,KAAA,mBACfhE,EAAegE,KAAA,gBACnB,CACA,SAAAC,GACA,2CACA,CACA,KAAAC,GACA,OAAAxB,EAAA,yBACA,CACA,OAAAyB,GACA,OAAAM,EAAA,OACAR,UAAA,cACKQ,EAAA,OACLR,UAAA,sBACKQ,EAAA,SAAA/B,EAAA,8BAAA+B,EAAA,SACLR,UAAA,4BACKQ,EAAA,SACLlB,KAAA,WACAkC,QAAAzB,KAAA0B,aACAC,SAAA1F,IACA+D,KAAA0B,aAAAzF,EAAA2F,OAAAH,WAEK,IAAA/C,EAAA,iCAAA+B,EAAA,OACLR,UAAA,cACKQ,EAAIC,IAAM,CACfT,UAAA,yBACA4B,QAAA7B,KAAA8B,WACAC,UAAA/B,KAAA0B,cAAA1B,KAAA8B,WACAlB,QAAA,IAAAZ,KAAAgC,UACKtD,EAAA,6BACL,CACA,YAAAsD,GACAhC,KAAA8B,YAAA,EACArB,EAAAW,SACA,UACApB,KAAAK,MAAA4B,YACAjC,KAAAe,MACA,CAAM,MAAAvB,GAGNQ,KAAA8B,YAAA,EACArB,EAAAW,QACA,CACA,EAEe,MAAAc,UAA6BC,KAC5C,WAAApG,GACA+D,SAAAC,WACI/D,EAAegE,KAAA,eACfhE,EAAegE,KAAA,uBACfhE,EAAegE,KAAA,kBACfhE,EAAegE,KAAA,kBACnB,CACA,MAAAoC,CAAAC,GACAvC,MAAAsC,OAAAC,GACArC,KAAAsC,SACA,CACA,IAAAC,GACA,OAAA9B,EAAA,WACAR,UAAA,wBACKQ,EAAA,cAAAA,EAAA,UAAA/B,EAAA,kBAAA+B,EAAA,KACLR,UAAA,YACKvB,EAAA,8BAAAsB,KAAAwC,WAAA/B,EAA6DgC,IAAgB,gBAAAzC,KAAAwC,WAAA/B,EAAA,OAClFR,UAAA,iDACKQ,EAAA,SAAA/B,EAAA,uBAAAsB,KAAA0C,WAAAjC,EAAA,KACLR,UAAA,YACKQ,EAAA,YAAAT,KAAA0C,YAAAjC,EAAsCC,IAAM,CACjDT,UAAA,SACAU,KAAA,gBACAC,QAAA,IAAAZ,KAAAsC,WACK5D,EAAA,yBAAAsB,KAAAwC,WAAAxC,KAAA2C,OACL,CACA,IAAAA,GACA,IAAA3C,KAAA4C,OAAA,OAAAnC,EAAA,KACAR,UAAA,YACKvB,EAAA,mBACL,MAAAmE,EAAA7C,KAAA4C,OACA,OAAAC,EAAAC,UAKArC,EAAA,SAAAA,EAAA,OACAR,UAAA,8BACKD,KAAA+C,YAAA,SAAAF,EAAAG,gBAAAhD,KAAA+C,YAAA,UAAAF,EAAAI,sBAAAJ,EAAAK,SAAAzC,EAAA,OACLR,UAAA,wBACKvB,EAAA,oBAAAmE,EAAAG,iBAAAH,EAAAI,qBAAAxC,EAAA,WAAAA,EAAA,KACLR,UAAA,YACKvB,EAAA,qBAAA+B,EAAgCC,IAAM,CAC3CT,UAAA,yBACAU,KAAA,aACAC,QAAA,IAAAZ,KAAAmD,UAAA,IACKzE,EAAA,sBAAAmE,EAAAG,gBAAAH,EAAAI,sBAAA,IAAAJ,EAAAO,YAAA3C,EAAA,OACLR,UAAA,sBACKQ,EAAA,cAAA/B,EAAA,0BAAA+B,EAAA,SAAA/B,EAAA,yBAAA+B,EAAA,SAAAA,EAAA,gBAAAoC,EAAAQ,WAAA,OAAAR,EAAAG,iBAAAH,EAAAI,qBAAAxC,EAAA,OACLR,UAAA,sBACKQ,EAAA,cAAA/B,EAAA,iCAAA+B,EAAA,SAAA/B,EAAA,gCAAA+B,EAAA,SAAAA,EAAA,gBAAAoC,EAAAQ,WAAA,OAAAR,EAAAG,gBAAAhD,KAAAsD,eAAAT,EAAAU,YAAA,GAAAV,EAAAK,UAlBLzC,EAAA,OACAR,UAAA,sBACOvB,EAAA,4BAiBP,CACA,cAAA4E,CAAAE,EAAAN,GACA,OAAAzC,EAAA,OACAR,UAAA,8BACKQ,EAAA,aAAA/B,EAAA,qBAAA+B,EAAA,OACLR,UAAA,iCACKQ,EAAA,WAAAA,EAAA,YAAA+C,IAAA/C,EAAgDC,IAAM,CAC3DT,UAAA,sBACAU,KAAA,cACAT,MAAauD,IAAW/E,EAAA,0BACxBkC,QAAA,IAAAZ,KAAA0D,WAAAF,IACKxD,KAAA2D,aAAsBF,IAAW/E,EAAA,2BAAA+B,EAAA,KACtCR,UAAA,YACKvB,EAAAwE,EAAA,qDAAAzC,EAAmFC,IAAM,CAC9FT,UAAA,wBACAU,KAAA,gBACAC,QAAA,IAAAZ,KAAA4D,kBACKlF,EAAA,6BACL,CACA,WAAAqE,CAAAc,EAAAC,GACA,OAAArD,EAAA,OACAR,UAAA,kDAAAN,OAAAmE,EAAA,iBACKrD,EAAA,KACLR,UAAA,eAAAN,OAAAmE,EAAA,mBACKrD,EAAA,YAAA/B,EAAA,UAAAiB,OAAAkE,EAAA,gBAAApD,EAAA,QACLR,UAAA,+BACKvB,EAAA,UAAAiB,OAAAmE,EAAA,sBACL,CACA,UAAAJ,CAAAF,GACAA,IACAxC,UAAAC,UAMAD,UAAAC,UAAAC,UAAAsC,GAAArC,KAAA,KACAnB,KAAA2D,cAAA,EACAlD,EAAAW,SACAC,WAAA,KACArB,KAAA2D,cAAA,EACAlD,EAAAW,UACO,OACFE,MAAAC,IACLvC,QAAAC,MAAA,sCAAAsC,GACMrE,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACOb,EAAA,uBAhBDxB,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACOb,EAAA,0BAgBP,CACA,OAAA4D,GAGA,OAFAtC,KAAAwC,UAAA,UACAxC,KAAA0C,UAAA,KACW9D,EAAU,CACrBM,OAAA,MACAC,IAAA,GAAAQ,OAAqB1C,IAAM,6BAC3BmC,SAAA,IACK+B,KAAA4C,IACL/D,KAAA4C,OAAAmB,EACA/D,KAAAwC,UAAA,OACKlB,MAAArF,IACL+D,KAAA4C,OAAA,KACA5C,KAAAwC,UAAA,QACAxC,KAAA0C,UAAuB5E,EAAW7B,KAC7BkF,KAAA,KACLV,EAAAW,UAEA,CACA,cAAA+B,CAAAa,GACA,IACA,MAAAD,QAAwBnF,EAAU,CAClCM,OAAA,OACAC,IAAA,GAAAQ,OAAuB1C,IAAM,uCAC7B0F,KAAA,CACAsB,iBAAAD,GAEA5E,SAAA,UAEAY,KAAAsC,UACMpF,IAAAgH,MAAS5E,KAAAM,EAAA,CACfU,WAAAyD,EAAAI,YACA5D,UAAAwD,EAAAV,YAEA,CAAM,MAAApH,GAIN,MAHMiB,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACSzB,EAAW7B,EAAAM,OAAAmC,EAAA,8BACpBzC,CACA,CACA,CACA,cAAA2H,GACI1G,IAAAgH,MAAS5E,KAAAkC,EAAA,CACbS,UAAA,IAAAjC,KAAAmD,UAAA,IAEA,EAEA3H,OAAAC,IAAAgE,IAAA,iDAAAyC,GCtQA,MAAMkC,EAA4B5I,OAAAC,IAAAR,IAAA,gDCKlC,MAAAoJ,EAAA,CACAC,MAAA,QACAC,QAAA,UACAC,SAAA,aACAC,OAAA,UAEMC,EAAK,CAAA9J,EAAA8E,IAAoBxC,IAAAuB,WAAcC,MAAA,2BAAAiB,OAAA/E,GAAA,MAAA8E,EAAAA,EAAA,IAC9B,MAAAiF,UAAyBxC,KACxC,IAAAI,CAAAF,GACA,MAAAuC,EAAAvC,EAAAhC,MACAwE,EAAAD,EAAAC,QACAC,EAAAF,EAAAE,SACA,OAAAD,EAAAjH,OAKA6C,EAAA,SACAR,UAAA,oBACKQ,EAAA,aAAAA,EAAA,UAAAA,EAAA,UAA+CiE,EAAK,aAAAjE,EAAA,UAA6BiE,EAAK,aAAAjE,EAAA,UAA6BiE,EAAK,iBAAAjE,EAAA,UAAiCiE,EAAK,eAAAjE,EAAA,aAAAA,EAAA,aAAAoE,EAAAE,IAAAC,GAAAvE,EAAA,MACnK7F,IAAAoK,EAAAC,GACAhF,UAAA,kBACKQ,EAAA,UAAAA,EAAA,OACLR,UAAA,mBACK+E,EAAAE,WAAiBC,IAASH,EAAAE,YAAA,KAAAzE,EAAA,OAC/BR,UAAA,uBACK+E,EAAAI,UAAAJ,EAAAK,gBAIL5E,EAAA,OACAR,UAAA,wCAAAN,OAAAqF,EAAAK,gBACAnF,MAAA3D,OAAoBmI,EAAK,kBACzBY,OAAAjB,EAAAW,EAAAK,iBAAAL,EAAAK,mBAEK5E,EAAA,KACLR,UAAA,uCACK,IAAQyE,EAAK,cAClBY,OAAAjB,EAAAW,EAAAK,iBAAAL,EAAAK,mBACK5E,EAAA,UAAmBnD,EAAQ0H,EAAAO,aAAA9E,EAAA,UAAAuE,EAAAQ,SAAAT,IAAAU,GAAAhF,EAAA,QAChCR,UAAA,kCAAAN,OAAA8F,IACOf,EAAK,WAAAe,MAAAhF,EAAA,UAAAuE,EAAAU,UAAAjF,EAAA,QACZR,UAAA,mDACKQ,EAAA,KACLR,UAAA,qBACK,IAAQyE,EAAK,cAAAjE,EAAA,QAClBR,UAAA,oDACKQ,EAAA,KACLR,UAAA,0BACK,IAAQyE,EAAK,WAAAjE,EAAA,MAClBR,UAAA,sBACKQ,EAAA,KACLR,UAAA,sBACA0F,KAAA,GAAAhG,OAAsB1C,IAAM,oBAAA0C,OAAAqF,EAAAC,GAAA,aAC5BrD,OAAA,SACA1B,MAAA3D,OAAoBmI,EAAK,oBACpBjE,EAAA,KACLR,UAAA,0BACKQ,EAAMC,IAAM,CACjBT,UAAA,qCACAU,KAAA,eACAT,MAAawE,EAAK,gBAClB9D,QAAA,IAAAkE,EAAAE,EAAAC,WAjDAxE,EAAA,KACAR,UAAA,6BACSyE,EAAK,SAiDd,ECrEA,SAASkB,EAAkB1J,EAAGzB,IAC3B,MAAQA,GAAKA,EAAIyB,EAAE0B,UAAYnD,EAAIyB,EAAE0B,QACtC,IAAK,IAAI3B,EAAI,EAAG0B,EAAIkI,MAAMpL,GAAIwB,EAAIxB,EAAGwB,IAAK0B,EAAE1B,GAAKC,EAAED,GACnD,OAAO0B,CACT,CCAA,SAASmI,EAAe5J,EAAGD,GACzB,OCLF,SAAyBC,GACvB,GAAI2J,MAAME,QAAQ7J,GAAI,OAAOA,CAC/B,CDGS8J,CAAe9J,IELxB,SAA+BA,EAAG+J,GAChC,IAAI9J,EAAI,MAAQD,EAAI,KAAO,oBAAsBL,QAAUK,EAAEL,OAAOC,WAAaI,EAAE,cACnF,GAAI,MAAQC,EAAG,CACb,IAAIF,EACF0B,EACAvB,EACA8J,EACAzL,EAAI,GACJ0L,GAAI,EACJtL,GAAI,EACN,IACE,GAAIuB,GAAKD,EAAIA,EAAEb,KAAKY,IAAIkK,KAAM,IAAMH,EAAG,CACrC,GAAInL,OAAOqB,KAAOA,EAAG,OACrBgK,GAAI,CACN,MAAO,OAASA,GAAKlK,EAAIG,EAAEd,KAAKa,IAAIkK,QAAU5L,EAAE6L,KAAKrK,EAAEQ,OAAQhC,EAAEmD,SAAWqI,GAAIE,GAAI,GACtF,CAAE,MAAOjK,GACPrB,GAAI,EAAI8C,EAAIzB,CACd,CAAC,QACC,IACE,IAAKiK,GAAK,MAAQhK,EAAU,SAAM+J,EAAI/J,EAAU,SAAKrB,OAAOoL,KAAOA,GAAI,MACzE,CAAC,QACC,GAAIrL,EAAG,MAAM8C,CACf,CACF,CACA,OAAOlD,CACT,CACF,CFrB8B8L,CAAqBrK,EAAGD,IGJtD,SAAqCC,EAAGzB,GACtC,GAAIyB,EAAG,CACL,GAAI,iBAAmBA,EAAG,OAAOsK,EAAiBtK,EAAGzB,GACrD,IAAI0B,EAAI,CAAC,EAAEsK,SAASnL,KAAKY,GAAGwK,MAAM,GAAI,GACtC,MAAO,WAAavK,GAAKD,EAAEH,cAAgBI,EAAID,EAAEH,YAAY4K,MAAO,QAAUxK,GAAK,QAAUA,EAAI0J,MAAMe,KAAK1K,GAAK,cAAgBC,GAAK,2CAA2C0K,KAAK1K,GAAKqK,EAAiBtK,EAAGzB,QAAU,CAC3N,CACF,CHF4DqM,CAA2B5K,EAAGD,IIL1F,WACE,MAAM,IAAIK,UAAU,4IACtB,CJGgGyK,EAChG,CKJA,SAAAC,EAAA/K,EAAAC,GAAyB,IAAAC,EAAArB,OAAAmM,KAAAhL,GAAwB,GAAAnB,OAAAoM,sBAAA,CAAoC,IAAArM,EAAAC,OAAAoM,sBAAAjL,GAAyCC,IAAArB,EAAAA,EAAAsM,OAAA,SAAAjL,GAAkC,OAAApB,OAAAsM,yBAAAnL,EAAAC,GAAAlB,UAAA,IAA0DmB,EAAAmK,KAAAe,MAAAlL,EAAAtB,EAAA,CAA0B,OAAAsB,CAAA,CACpP,SAAAmL,EAAArL,GAA4B,QAAAC,EAAA,EAAgBA,EAAA6D,UAAAnC,OAAsB1B,IAAA,CAAO,IAAAC,EAAA,MAAA4D,UAAA7D,GAAA6D,UAAA7D,GAAA,GAAkDA,EAAA,EAAA8K,EAAAlM,OAAAqB,IAAA,GAAAoL,QAAA,SAAArL,GAAsDF,EAAeC,EAAAC,EAAAC,EAAAD,GAAA,GAAepB,OAAA0M,0BAAA1M,OAAA2M,iBAAAxL,EAAAnB,OAAA0M,0BAAArL,IAAA6K,EAAAlM,OAAAqB,IAAAoL,QAAA,SAAArL,GAAmJpB,OAAAC,eAAAkB,EAAAC,EAAApB,OAAAsM,yBAAAjL,EAAAD,GAAA,EAAqE,CAAK,OAAAD,CAAA,CPoE5aT,OAAAC,IAAAgE,IAAA,6CAAAkF,GO9DA,MAAM+C,EAAK,CAAA9M,EAAA8E,IAAoBxC,IAAAuB,WAAcC,MAAA,mCAAAiB,OAAA/E,GAAA,MAAA8E,EAAAA,EAAA,IAc9B,MAAAiI,UAA0B9H,KACzC,WAAA9D,GACA+D,SAAAC,WACI/D,EAAegE,KAAA,gBACfhE,EAAegE,KAAA,gBACfhE,EAAegE,KAAA,oBACfhE,EAAegE,KAAA,qBACfhE,EAAegE,KAAA,wBAIfhE,EAAegE,KAAA,wBACfhE,EAAegE,KAAA,uBACfhE,EAAegE,KAAA,iBACfhE,EAAegE,KAAA,wBACfhE,EAAegE,KAAA,wBACfhE,EAAegE,KAAA,4BACfhE,EAAegE,KAAA,wBAMfhE,EAAegE,KAAA,oBACfhE,EAAegE,KAAA,eACfhE,EAAegE,KAAA,cACfhE,EAAegE,KAAA,eACfhE,EAAegE,KAAA,aACnB,CACA,SAAAC,GACA,uCACA,CACA,KAAAC,GACA,OAAWwH,EAAK,QAChB,CACA,OAAAvH,GACA,eAAAH,KAAA4H,MAAA5H,KAAA6H,cACA7H,KAAA8H,iBACA,CAIA,WAAAD,GACA,OAAApH,EAAA,OACAR,UAAA,cACKQ,EAAA,KACLR,UAAA,YACOyH,EAAK,UAAAjH,EAAA,YACZR,UAAA,yBACKQ,EAAA,cAAoBiH,EAAK,mBAAA1H,KAAA+H,SAAA,SAAA/H,KAAAgI,UAAAC,GAAAjI,KAAAgI,UAAAC,GAAAjI,KAAA+H,SAAA,aAAA/H,KAAAkI,cAAAD,GAAAjI,KAAAkI,cAAAD,GAAAjI,KAAA+H,SAAA,cAAA/H,KAAAmI,eAAAF,GAAAjI,KAAAmI,eAAAF,GAAAjI,KAAA+H,SAAA,iBAAA/H,KAAAoI,kBAAAH,IAC9BjI,KAAAoI,kBAAAH,EAIAA,IAAAjI,KAAAqI,kBAAArI,KAAAsI,mBACKtI,KAAAoI,mBAAApI,KAAAuI,iBAAAvI,KAAAgI,WAAAvH,EAAA,YACLR,UAAA,yBACKQ,EAAA,cAAoBiH,EAAK,iBAAAjH,EAAA,KAC9BR,UAAA,YACOyH,EAAK,gBAAAjH,EAAA,UACZR,UAAA,wCACAxD,MAAAuD,KAAAwI,cACA7G,SAAA1F,IACA+D,KAAAwI,cAAAvM,EAAA2F,OAAAnF,QAEKgE,EAAA,UACLhE,MAAA,IACOiL,EAAK,gBAAAjH,EAAA,UACZhE,MAAA,SACOiL,EAAK,iBAAAjH,EAAA,UACZhE,MAAA,WACOiL,EAAK,mBAAAjH,EAAA,UACZhE,MAAA,YACOiL,EAAK,oBAAAjH,EAAA,UACZhE,MAAA,UACOiL,EAAK,oBAAAjH,EAAA,YACZR,UAAA,yBACKQ,EAAA,cAAoBiH,EAAK,qBAAAjH,EAAA,SAC9BR,UAAA,yBACKQ,EAAA,SACLlB,KAAA,WACAkC,QAAAzB,KAAAyI,kBACA9G,SAAA1F,IACA+D,KAAAyI,kBAAAxM,EAAA2F,OAAAH,WAEK,IAAAhB,EAAA,YAAwBiH,EAAK,uBAAAjH,EAAA,KAClCR,UAAA,YACOyH,EAAK,oBAAA1H,KAAAyI,mBAAAhI,EAAA,SAAAA,EAAA,SACZR,UAAA,yBACKQ,EAAA,SACLlB,KAAA,WACAkC,QAAAzB,KAAA0I,sBACA/G,SAAA1F,IACA+D,KAAA0I,sBAAAzM,EAAA2F,OAAAH,WAEK,IAAAhB,EAAA,YAAwBiH,EAAK,yBAAA1H,KAAA0I,uBAAAjI,EAAA,SAAAA,EAAA,KAClCR,UAAA,YACOyH,EAAK,6BAAAjH,EAAA,YACZR,UAAA,oCACA0I,KAAA,EACAC,YAAA,oBACAnM,MAAAuD,KAAA6I,kBACAC,QAAA7M,IACA+D,KAAA6I,kBAAA5M,EAAA2F,OAAAnF,YAEKgE,EAAA,OACLR,UAAA,cACKQ,EAAIC,IAAM,CACfT,UAAA,yBACA4B,QAAA7B,KAAA+I,SACAhH,SAAA/B,KAAA+I,WAAA/I,KAAAgJ,WACApI,QAAA,IAAAZ,KAAAiJ,SACOvB,EAAK,kBACZ,CACA,aAAAa,GACA,GAAAvI,KAAAkJ,kBACA,OAAAzI,EAAA,OACAR,UAAA,2BACOQ,EAAIgC,IAAgB,OAE3B,IAAAzC,KAAAmJ,WAAAvL,OACA,OAAA6C,EAAA,KACAR,UAAA,kCACSyH,EAAK,oBAEd,MAAA0B,EAAA,CACAC,UAAA,GACAC,OAAA,GACAC,QAAA,IAEA,UAAAC,KAAAxJ,KAAAmJ,WAAA,CACA,IAAAM,EACA,OAAAA,EAAAL,EAAAI,EAAAE,YAAAD,EAAAnD,KAAAkD,EACA,CACA,OAAA/I,EAAA,OACAR,UAAA,wBACKQ,EAAA,OACLR,UAAA,2BACKQ,EAAA,UACLlB,KAAA,SACAU,UAAA,uBACAW,QAAA,IAAAZ,KAAA2J,qBAAA,IACOjC,EAAK,0BAAAjH,EAAA,mBAAAA,EAAA,UACZlB,KAAA,SACAU,UAAA,uBACAW,QAAA,IAAAZ,KAAA2J,qBAAA,IACOjC,EAAK,6DAAAP,OAAAyC,GAAAR,EAAAQ,GAAAhM,OAAA,GAAAmH,IAAA6E,GAAAnJ,EAAA,OACZR,UAAA,wBACArF,IAAAgP,GACKnJ,EAAA,OACLR,UAAA,+BACOyH,EAAK,oBAAAkC,GAAA,IAAAnJ,EAAA,QACZR,UAAA,YACK,IAAAmJ,EAAAQ,GAAAhM,OAAA,MAAAwL,EAAAQ,GAAA7E,IAAAyE,GAAA/I,EAAA,SACLR,UAAA,sBACArF,IAAA4O,EAAAvE,IACKxE,EAAA,SACLlB,KAAA,WACAkC,UAAAzB,KAAA6J,kBAAAL,EAAAvE,IACAtD,SAAA1F,IACA+D,KAAA6J,kBAAAL,EAAAvE,IAAAhJ,EAAA2F,OAAAH,WAEK,IAAAhB,EAAA,QACLR,UAAA,yBACKuJ,EAAAtJ,OAAA,IAAAO,EAAA,QACLR,UAAA,wBACKuJ,EAAA7C,MAAA6C,EAAAvE,IAAAxE,EAAA,QACLR,UAAA,4CAAAN,OAAA6J,EAAAE,WACOhC,EAAK,kBAAA8B,EAAAE,eACZ,CACA,mBAAAC,CAAAlN,GACA,UAAA+M,KAAAxJ,KAAAmJ,WAAAnJ,KAAA6J,kBAAAL,EAAAvE,IAAAxI,CACA,CACA,oBAAA6L,GACAtI,KAAAkJ,mBAAA,EACA,IACA,MAAAnF,QAAwBnF,EAAU,CAClCM,OAAA,MACAC,IAAA,GAAAQ,OAAuB1C,IAAM,sBAC7BmC,SAAA,IAEAY,KAAAmJ,WAAApF,EAAAoF,YAAA,GACAnJ,KAAAqI,kBAAA,EAGA,UAAAmB,KAAAxJ,KAAAmJ,WAAAnJ,KAAA6J,kBAAAL,EAAAvE,KAAA,CACA,CAAM,MAAAhJ,GACAiB,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACSzB,EAAW7B,EAAAM,OAAWmL,EAAK,4BACpC,CAAM,QACN1H,KAAAkJ,mBAAA,EACAzI,EAAAW,QACA,CACA,CACA,QAAA2G,CAAAnN,EAAAK,EAAA6O,GACA,OAAArJ,EAAA,SACAR,UAAA,yBACKQ,EAAA,SACLlB,KAAA,WACAkC,QAAAxG,IACA0G,SAAA1F,IACA6N,EAAA7N,EAAA2F,OAAAH,YAEK,IAAAhB,EAAA,QACLR,UAAA,+BACOyH,EAAK,WAAA9M,IAAA6F,EAAA,QACZR,UAAA,uCACOyH,EAAK,WAAA9M,EAAA,UACZ,CACA,QAAAoO,GACA,UAAAhJ,KAAAgI,WAAAhI,KAAAkI,eAAAlI,KAAAmI,gBAAAnI,KAAAoI,oBAGApI,KAAAyI,mBAAAzI,KAAA0I,wBAAA1I,KAAA6I,kBAAAkB,OAIA,CAIA,eAAAjC,GACA,IAAAkC,EAAAC,EAAAC,EACA,MAAArH,EAAA7C,KAAA4C,OACA,IAAAC,EAAA,OAAApC,EAAqBgC,IAAgB,MACrC,MAAA0H,EAAA,SAAAtH,EAAAuH,MACAC,EAAA,UAAAxH,EAAAuH,MACAE,EAAAC,KAAAC,IAAA,EAAAD,KAAAE,IAAA,YAAAT,EAAAnH,EAAA6H,eAAA,EAAAV,EAAAW,UAAA,IACA,OAAAlK,EAAA,OACAR,UAAA,oCACKQ,EAAA,OACLR,UAAA,4CAAAN,OAAAkD,EAAAuH,QACK3J,EAAA,cAAoBiH,EAAK,SAAA7E,EAAAuH,QAAA3J,EAAA,SAAAoC,EAAAtE,WAAA8L,GAAA5J,EAAA,SAAAA,EAAA,OAC9BR,UAAA,oBACKQ,EAAA,OACLR,UAAA,wBACA2K,MAAA,CACAC,MAAA,GAAAlL,OAAAwK,EAAA,IAAAG,EAAA,MAEAQ,KAAA,cACA,gBAAAR,EACA,kBACA,uBACK7J,EAAA,OACLR,UAAA,sBACKQ,EAAA,YAAkBnD,EAAQuF,EAAA6H,SAAAK,iBAAA,MAAqCzN,EAAQuF,EAAA6H,SAAAM,aAAAnI,EAAA6H,SAAAK,kBAAAlI,EAAA6H,SAAAO,YAAA,GAAAxK,EAAA,YAAuGiH,EAAK,eACxLrB,KAAAxD,EAAA6H,SAAAQ,gBACAC,MAAAtI,EAAA6H,SAAAO,kBACK,OAAAhB,EAAA,OAAAC,EAAArH,EAAAuI,eAAA,EAAAlB,EAAAtM,QAAAqM,EAAA,MAAAxJ,EAAA,OACLR,UAAA,wBACA6K,KAAA,SACKrK,EAAA,OACLR,UAAA,+BACKQ,EAAA,KACLR,UAAA,qCACK,IAAQyH,EAAK,kBAClB2D,MAAAxI,EAAAuI,SAAAxN,UACK6C,EAAA,KACLR,UAAA,YACOyH,EAAK,kBAAAjH,EAAA,MACZR,UAAA,8BACK4C,EAAAuI,SAAArG,IAAA,CAAAuG,EAAAC,IAAA9K,EAAA,MACL7F,IAAA2Q,GACKD,MAAA7K,EAAA,OACLR,UAAA,6CACKkK,IAAAE,GAAA5J,EAA2BC,IAAM,CACtCT,UAAA,SACAW,QAAA,IAAAZ,KAAAwL,UACO9D,EAAK,mBAAAyC,GAAAE,IAAA5J,EAA6CC,IAAM,CAC/DT,UAAA,yBACAW,QAAA,IAAAZ,KAAAyL,SACO/D,EAAK,kBACZ,CAIA,WAAAuB,GACAjJ,KAAA+I,UAAA,EACA,IAMA,IAAA2C,GAAA,EACA1L,KAAAoI,oBAaAsD,GAZA1L,KAAAqI,kBAGAvN,OAAA6Q,QAAA3L,KAAA6J,mBAAA1C,OAAAlJ,GACwB6H,EAAc7H,EAAA,GACtC,IAEW8G,IAAA6G,GACa9F,EAAc8F,EAAA,GACtC,KAMA,MAAA7H,QAAwBnF,EAAU,CAClCM,OAAA,OACAC,IAAA,GAAAQ,OAAuB1C,IAAM,mBAC7B0F,KAAA,CACA6C,SAAA,CACAqG,GAAA7L,KAAAgI,UACA8D,OAAA9L,KAAAkI,cACA6D,QAAA/L,KAAAmI,eACAgB,WAAAuC,GAEAM,WAAA,CACAC,QAAAjM,KAAAyI,kBACAlF,WAAAvD,KAAA0I,sBAAA1I,KAAA6I,kBAAAkB,OAAA,MAKA1E,eAAArF,KAAAwI,eAAA,MAEApJ,SAAA,IAEAY,KAAAkM,MAAAnI,EAAAoI,OACAnM,KAAA4H,MAAA,WACA5H,KAAA4C,OAAA,CACAwH,MAAArG,EAAAqG,MACA7L,QAAAwF,EAAAxF,QACAmM,SAAA,CACAM,YAAA,EACAD,gBAAA,EACAE,YAAA,EACAC,gBAAA,EACAP,QAAA,IAGA3K,KAAA+I,UAAA,EACAtI,EAAAW,SACApB,KAAAoM,MACA,CAAM,MAAAnQ,GACN+D,KAAA+I,UAAA,EACM7L,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACSzB,EAAW7B,EAAAM,OAAWmL,EAAK,mBACpCjH,EAAAW,QACA,CACA,CACA,UAAAgL,GACA,IAAApM,KAAAqM,SAAArM,KAAAkM,MAAA,CACAlM,KAAAqM,SAAA,EACA,IAGA,IAFA,IAAAC,EAEAtM,KAAAkM,OAAAlM,KAAA4C,QAAA,SAAA5C,KAAA4C,OAAAwH,OAAA,UAAApK,KAAA4C,OAAAwH,OACA,IACA,MAAArG,QAA4BnF,EAAU,CACtCM,OAAA,OACAC,IAAA,GAAAQ,OAA2B1C,IAAM,oBAAA0C,OAAAK,KAAAkM,MAAA,SACjC9M,SAAA,IAEAY,KAAA4C,OAAAmB,EACAtD,EAAAW,QACA,CAAU,MAAAnF,GAIV,MAAAmC,EAAyBN,EAAW7B,EAAAM,OAAWmL,EAAK,yBACpD1H,KAAA4C,OAAA0E,EAAAA,EAAA,GAAsDtH,KAAA4C,QAAA,GAAkB,CACxEwH,MAAA,QACA7L,QAAAH,IAEAqC,EAAAW,SACA,KACA,CAEA,iBAAAkL,EAAAtM,KAAA4C,aAAA,EAAA0J,EAAAlC,SACQlN,IAAAmC,OAAUC,KAAA,CAClBC,KAAA,WACWmI,EAAK,cAChB1H,KAAAK,MAAAkM,aAEA,CAAM,QACNvM,KAAAqM,SAAA,CACA,CAnCA,CAoCA,CACA,YAAAb,GACA,GAAAxL,KAAAkM,MAAA,CACA,UACYtN,EAAU,CACtBM,OAAA,SACAC,IAAA,GAAAQ,OAAuB1C,IAAM,oBAAA0C,OAAAK,KAAAkM,OAC7B9M,SAAA,GAEA,CAAM,MAAAnD,GAGN+C,QAAAwN,KAAA,gCAAAvQ,GACMiB,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,WACSmI,EAAK,sBACd,CACA1H,KAAAyL,OAfA,CAgBA,CACA,KAAAA,GACAzL,KAAAkM,MAAA,KACAlM,KAAAe,MACA,EC3aA,SAAS0L,EAAOxQ,EAAAC,GAAS,IAAAC,EAAArB,OAAAmM,KAAAhL,GAAwB,GAAAnB,OAAAoM,sBAAA,CAAoC,IAAArM,EAAAC,OAAAoM,sBAAAjL,GAAyCC,IAAArB,EAAAA,EAAAsM,OAAA,SAAAjL,GAAkC,OAAApB,OAAAsM,yBAAAnL,EAAAC,GAAAlB,UAAA,IAA0DmB,EAAAmK,KAAAe,MAAAlL,EAAAtB,EAAA,CAA0B,OAAAsB,CAAA,CACpP,SAASuQ,EAAazQ,GAAM,QAAAC,EAAA,EAAgBA,EAAA6D,UAAAnC,OAAsB1B,IAAA,CAAO,IAAAC,EAAA,MAAA4D,UAAA7D,GAAA6D,UAAA7D,GAAA,GAAkDA,EAAA,EAAQuQ,EAAO3R,OAAAqB,IAAA,GAAAoL,QAAA,SAAArL,GAAuCF,EAAeC,EAAAC,EAAAC,EAAAD,GAAA,GAAepB,OAAA0M,0BAAA1M,OAAA2M,iBAAAxL,EAAAnB,OAAA0M,0BAAArL,IAAyGsQ,EAAO3R,OAAAqB,IAAAoL,QAAA,SAAArL,GAAmCpB,OAAAC,eAAAkB,EAAAC,EAAApB,OAAAsM,yBAAAjL,EAAAD,GAAA,EAAqE,CAAK,OAAAD,CAAA,CD4a5aT,OAAAC,IAAAgE,IAAA,8CAAAkI,GCpaA,MACMgF,EAAK,CAAA/R,EAAA8E,IAAoBxC,IAAAuB,WAAcC,MAAA,mCAAAiB,OAAA/E,GAAA,MAAA8E,EAAAA,EAAA,IAa9B,MAAAkN,UAA0B/M,KACzC,WAAA9D,GACA+D,SAAAC,WACI/D,EAAegE,KAAA,kBACfhE,EAAegE,KAAA,aACfhE,EAAegE,KAAA,gBACfhE,EAAegE,KAAA,oBACfhE,EAAegE,KAAA,0BACfhE,EAAegE,KAAA,oBACfhE,EAAegE,KAAA,gBACfhE,EAAegE,KAAA,iBACfhE,EAAegE,KAAA,qBACfhE,EAAegE,KAAA,eAIfhE,EAAegE,KAAA,gBACfhE,EAAegE,KAAA,oBACfhE,EAAegE,KAAA,qBACfhE,EAAegE,KAAA,wBAEfhE,EAAegE,KAAA,uBACfhE,EAAegE,KAAA,eACfhE,EAAegE,KAAA,aACnB,CACA,SAAAC,GACA,uCACA,CACA,KAAAC,GACA,IAAAoM,EACA,mBAAAtM,KAAA4H,OAAA,iBAAA0E,EAAAtM,KAAA4C,aAAA,EAAA0J,EAAAlC,OACApK,KAAA6M,UAA8BF,EAAK,gBAAmBA,EAAK,cAEhDA,EAAK,QAChB,CACA,OAAAxM,GACA,iBAAAH,KAAA4H,MAAA5H,KAAA8M,gBACA,cAAA9M,KAAA4H,MAAA5H,KAAA+M,mBACA/M,KAAA8H,iBACA,CAIA,aAAAgF,GACA,OAAArM,EAAA,OACAR,UAAA,cACKQ,EAAA,OACLR,UAAA,wBACKQ,EAAA,cAAoBkM,EAAK,kBAAAlM,EAAA,SAAiCkM,EAAK,kBAAAlM,EAAA,SACpER,UAAA,0BACKQ,EAAA,SACLlB,KAAA,OACAyN,OAAA,UACArL,SAAA1F,IACA,IAAAgR,EACA,MAAA9G,GAAA,OAAA8G,EAAAhR,EAAA2F,OAAAsL,YAAA,EAAAD,EAAA,UACAjN,KAAAmN,KAAAhH,KAEKnG,KAAAmN,KAAA1M,EAAA,YAAAT,KAAAmN,KAAAxG,KAAA,IAAAlG,EAAA,QACLR,UAAA,YACK,IAAO3C,EAAQ0C,KAAAmN,KAAAC,MAAA,MAAA3M,EAAA,QACpBR,UAAA,YACO0M,EAAK,iBAAA3M,KAAAqN,aAAA5M,EAAA,OACZR,UAAA,sBACKD,KAAAqN,aAAArN,KAAAsN,WAAA7M,EAAA,OACLR,UAAA,+BACKQ,EAAA,OACLR,UAAA,oBACKQ,EAAA,OACLR,UAAA,yBAAAD,KAAAuN,oBAAA,4CACA3C,MAAA5K,KAAAuN,yBAAA/O,EAAA,CACAqM,MAAA,GAAAlL,OAAA4K,KAAAC,IAAA,EAAAxK,KAAAwN,gBAAA,SAEK/M,EAAA,OACLR,UAAA,sCACKD,KAAAuN,oBAA6BZ,EAAK,sBAAyBA,EAAK,iBACrErC,IAAAtK,KAAAwN,mBACK/M,EAAA,OACLR,UAAA,cACKQ,EAAIC,IAAM,CACfT,UAAA,yBACA4B,QAAA7B,KAAAsN,UACAvL,SAAA/B,KAAAsN,YAAAtN,KAAAmN,KACAvM,QAAA,IAAAZ,KAAAyN,UACOd,EAAK,mBACZ,CACA,YAAAc,GACA,GAAAzN,KAAAmN,KAAA,CACAnN,KAAAsN,WAAA,EACAtN,KAAAwN,eAAA,EACAxN,KAAAuN,qBAAA,EACAvN,KAAAqN,YAAA,KACA,IACA,IAAAK,EAOA,MAAA3J,QAAA/D,KAAA2N,mBAAA3N,KAAAmN,KAAA7C,IACAtK,KAAAwN,eAAAlD,EACAA,GAAA,MAAAtK,KAAAuN,qBAAA,GACA9M,EAAAW,WAEApB,KAAA4N,QAAA7J,EAKA,MAAAyB,EAAAzB,EAAA8J,KAAArI,UAAA,GACAxF,KAAA6M,UAAArH,EAAAsI,SAAA,MACA9N,KAAA+N,cAAAvI,EAAAsI,SAAA,UACA9N,KAAAgO,eAAAxI,EAAAsI,SAAA,WACA9N,KAAAiO,kBAAAzI,EAAAsI,SAAA,cAIA,MAAAI,GAAA,OAAAR,EAAA3J,EAAA8J,KAAAM,eAAA,EAAAT,EAAAvE,aAAA,GACAnJ,KAAAoO,iBAAA,GACA,UAAAnS,KAAAiS,EAAA,CACA,MAAAjJ,EAAA,iBAAAhJ,EAAAA,EAAAA,EAAAgJ,GACAA,IAAAjF,KAAAoO,iBAAAnJ,IAAA,EACA,CACAjF,KAAA4H,MAAA,WACA,CAAM,MAAA3L,GACN+C,QAAAC,MAAA,iCAAAhD,GACA+D,KAAAqN,YAAyBvP,EAAW7B,EAAAM,OAAWoQ,EAAK,kBACpD,CAAM,QACN3M,KAAAsN,WAAA,EACA7M,EAAAW,QACA,CA5CA,CA6CA,CAYA,kBAAAuM,CAAAR,EAAAkB,GACA,WAAAC,QAAA,CAAAC,EAAAC,KACA,IAAAC,EACA,MAAAC,EAAA,IAAAC,SACAD,EAAAE,OAAA,UAAAzB,GACA,MAAA0B,EAAA,IAAAC,eACA,IAAAC,EAAAC,KAAAC,MACA,MAAAC,EAAAC,YAAA,KACAH,KAAAC,MAAAF,EAtKA,MAuKAK,cAAAF,GACAL,EAAAQ,UAEO,KACPC,EAAA,IAAAF,cAAAF,GACAL,EAAApB,OAAA8B,iBAAA,WAAAtT,IACA8S,EAAAC,KAAAC,MACAhT,EAAAuT,kBACAnB,EAAA9D,KAAAkF,MAAAxT,EAAAyT,OAAAnF,KAAAC,IAAAvO,EAAAkP,MAAA,WAGA0D,EAAApB,OAAA8B,iBAAA,YACAR,EAAAC,KAAAC,MACAZ,EAAA,OAEAQ,EAAApB,OAAA8B,iBAAA,aACAD,IACAd,EAAA,CACApQ,OAAkBuO,EAAK,qBAGvBkC,EAAAU,iBAAA,YAEA,GADAD,IACAT,EAAAjM,QAAA,KAAAiM,EAAAjM,OAAA,IACA,IACA2L,EAAAoB,KAAAC,MAAAf,EAAAgB,cACA,CAAY,MAAArQ,GACZgP,EAAA,CACApQ,OAAsBuO,EAAK,kBAE3B,KACU,CACV,IAAAvO,EACA,IACA,IAAA0R,EACA,MAAAnN,EAAAgN,KAAAC,MAAAf,EAAAgB,cACAzR,EAAA,MAAAuE,GAAA,OAAAmN,EAAAnN,EAAArE,SAAA,OAAAwR,EAAAA,EAAA,WAAAA,EAAA1R,MACA,CAAY,MAAA2R,GAEZ,CACAvB,EAAA,CACApQ,OAAAA,GAAA,GAAAuB,OAAAkP,EAAAjM,OAAA,KAAAjD,OAAAkP,EAAAmB,aAEA,IAEAnB,EAAAU,iBAAA,aACAD,IACAd,EAAA,CACApQ,OAAkBuO,EAAK,qBAGvBkC,EAAAU,iBAAA,aACAD,IACAd,EAAA,CACApQ,OAAkBuO,EAAK,2BAGvBkC,EAAAoB,KAAA,UAAAtQ,OAAiC1C,IAAM,uBACvC4R,EAAAqB,iBAAA,EAEA,MAAAC,EAA0C,OAA1C1B,EAA+BvR,IAAAkT,cAAW,EAAA3B,EAAA4B,UAC1CF,GAAAtB,EAAAyB,iBAAA,eAAAH,GACAtB,EAAA0B,KAAA7B,IAEA,CAIA,gBAAA3B,GACA,MAAA3Q,EAAA4D,KAAA4N,QACA,OAAAnN,EAAA,OACAR,UAAA,cACKQ,EAAA,UAAgBkM,EAAK,kBAAAlM,EAAA,MAC1BR,UAAA,qBACK7D,EAAAyR,KAAA3I,YAAAzE,EAAA,SAAAA,EAAA,UAAkDkM,EAAK,cAAAlM,EAAA,UAAArE,EAAAyR,KAAA3I,aAAA9I,EAAAyR,KAAA2C,gBAAA/P,EAAA,SAAAA,EAAA,UAAuGkM,EAAK,gBAAAlM,EAAA,UAAArE,EAAAyR,KAAA2C,iBAAApU,EAAAyR,KAAArI,UAAA/E,EAAA,SAAAA,EAAA,UAAuGkM,EAAK,kBAAAlM,EAAA,UAAArE,EAAAyR,KAAArI,SAAAiL,KAAA,QAAArU,EAAAyR,KAAA6C,YAAAjQ,EAAA,SAAAA,EAAA,UAAgHkM,EAAK,oBAAAlM,EAAA,UAAAA,EAAA,YAAArE,EAAAyR,KAAA6C,cAAAjQ,EAAA,UAAwFkM,EAAK,cAAAlM,EAAA,UAA8BnD,EAAQlB,EAAAgR,QAAA3M,EAAA,OAC5gBR,UAAA,0CACKQ,EAAA,KACLR,UAAA,4BACK,IAAQ0M,EAAK,qBAAA3M,KAAA2Q,kBAAAvU,GAAAA,EAAAwU,cAAAnQ,EAAA,YAClBR,UAAA,yBACKQ,EAAA,cAAoBkM,EAAK,cAAAlM,EAAA,KAC9BR,UAAA,YACO0M,EAAK,aAAAlM,EAAA,YACZR,UAAA,oCACA0I,KAAA,EACAC,YAAA,qBACAnM,MAAAuD,KAAAM,WACAwI,QAAA7M,IACA+D,KAAAM,WAAArE,EAAA2F,OAAAnF,SAEKgE,EAAA,KACLR,UAAA,iCACO0M,EAAK,oBAAAlM,EAAA,OACZR,UAAA,gDACKQ,EAAA,cAAoBkM,EAAK,kBAAAlM,EAAA,SAAiCkM,EAAK,iBAAAlM,EAAA,SACpER,UAAA,wBACKQ,EAAA,SACLlB,KAAA,WACAkC,QAAAzB,KAAA6Q,eACAlP,SAAA1F,IACA+D,KAAA6Q,eAAA5U,EAAA2F,OAAAH,WAEK,IAAAhB,EAAA,YAAwBkM,EAAK,oBAAAlM,EAAA,OAClCR,UAAA,cACKQ,EAAIC,IAAM,CACfT,UAAA,yBACA4B,QAAA7B,KAAA+I,SACAhH,SAAA/B,KAAA+I,WAAA/I,KAAA6Q,eACAjQ,QAAA,IAAAZ,KAAA8Q,gBACOnE,EAAK,kBACZ,CACA,iBAAAgE,CAAAvU,GACA,MAAAoJ,EAAApJ,EAAAyR,KAAArI,UAAA,GACA2I,EAAA/R,EAAAyR,KAAAM,UAAA,GACA4C,EAAAvL,EAAAsI,SAAA,MACAkD,EAAAxL,EAAAsI,SAAA,UACAmD,EAAAzL,EAAAsI,SAAA,WACAoD,EAAA1L,EAAAsI,SAAA,cAIAqD,GAHAhD,EAAAhF,YAAA,IAGApE,IAAA9I,GAAA,iBAAAA,EAAA,CACAgJ,GAAAhJ,EACAyN,SAAA,aACMzN,GACN,OAAAwE,EAAA,YACAR,UAAA,yBACKQ,EAAA,cAAoBkM,EAAK,oBAAAlM,EAAA,KAC9BR,UAAA,YACO0M,EAAK,mBAAAoE,GAAA/Q,KAAAoR,WAAA,KAAApR,KAAA6M,UAAA5E,GAAAjI,KAAA6M,UAAA5E,GAAA+I,GAAAhR,KAAAoR,WAAA,SAAApR,KAAA+N,cAAA9F,GAAAjI,KAAA+N,cAAA9F,EAAAkG,EAAAkD,aAAAJ,GAAAjR,KAAAoR,WAAA,UAAApR,KAAAgO,eAAA/F,GAAAjI,KAAAgO,eAAA/F,EAAAkG,EAAAmD,eAAAJ,GAAAzQ,EAAA,SAAAT,KAAAoR,WAAA,aAAApR,KAAAiO,kBAAAhG,IACZjI,KAAAiO,kBAAAhG,EAGA,UAAAtB,KAAAwK,EAAAnR,KAAAoO,iBAAAzH,GAAAsB,GACKkG,EAAAoD,iBAAAvR,KAAAiO,mBAAAE,EAAAqD,cAAA/Q,EAAA,OACLR,UAAA,sCACKQ,EAAA,KACLR,UAAA,qBACK,IAAQ0M,EAAK,6BAAA3M,KAAAiO,mBAAAkD,EAAAvT,OAAA,GAAA6C,EAAA,OAClBR,UAAA,wBACKkR,EAAApM,IAAAyE,GAAA/I,EAAA,SACLR,UAAA,sBACArF,IAAA4O,EAAAvE,IACKxE,EAAA,SACLlB,KAAA,WACAkC,UAAAzB,KAAAoO,iBAAA5E,EAAAvE,IACAtD,SAAA1F,IACA+D,KAAAoO,iBAAA5E,EAAAvE,IAAAhJ,EAAA2F,OAAAH,WAEK,IAAAhB,EAAA,QACLR,UAAA,yBACKuJ,EAAAtJ,OAAAsJ,EAAAvE,IAAA,IAAAuE,EAAA7C,MAAA6C,EAAA7C,OAAA6C,EAAAvE,IAAAxE,EAAA,QACLR,UAAA,wBACKuJ,EAAA7C,MAAA6C,EAAAE,UAAAjJ,EAAA,QACLR,UAAA,4CAAAN,OAAA6J,EAAAE,WACOiD,EAAK,kBAAAnD,EAAAE,eACZ,CACA,UAAA0H,CAAAxW,EAAA6G,EAAAqI,EAAAuB,GACA,OAAA5K,EAAA,SACAR,UAAA,2BACKQ,EAAA,SACLlB,KAAA,WACAkC,QAAAA,EACAE,SAAA1F,GAAA6N,EAAA7N,EAAA2F,OAAAH,WACK,IAAAhB,EAAA,QACLR,UAAA,6BACO0M,EAAK,WAAA/R,SAAA4D,IAAA6M,GAAAA,EAAA,GAAA5K,EAAA,QACZR,UAAA,sCACK,QAAY0M,EAAK,iBACtBtB,UACK,KACL,CACA,cAAAoG,GAKA,MAAAC,EAAA5W,OAAA6Q,QAAA3L,KAAAoO,kBACAuD,EAAAD,EAAA9T,OAAA,GAAA8T,EAAAE,MAAA3T,GACkB6H,EAAc7H,EAAA,GAChC,IAGAyN,IAAA1L,KAAAiO,sBAAA0D,GAAAD,EAAAvK,OAAAyE,GACkB9F,EAAc8F,EAAA,GAChC,IAEK7G,IAAA8M,GACa/L,EAAc+L,EAAA,GAChC,KAGA,OACAhG,GAAA7L,KAAA6M,UACAf,OAAA9L,KAAA+N,cACAhC,QAAA/L,KAAAgO,eACA7E,WAAAuC,EAEA,CACA,kBAAAoF,GACA,GAAA9Q,KAAA4N,QAAA,CACA5N,KAAA+I,UAAA,EACA,IACA,MAAAhF,QAAwBnF,EAAU,CAClCM,OAAA,OACAC,IAAA,GAAAQ,OAAuB1C,IAAM,oBAAA0C,OAAAK,KAAA4N,QAAAzB,OAAA,UAC7B/M,SAAA,EACAuD,KAAA,CACAwB,YAAAnE,KAAAM,WAAAyJ,QAAA,KACA+H,gBAAA9R,KAAA6Q,eACAkB,UAAA/R,KAAAyR,oBAGAzR,KAAA4H,MAAA,WACA5H,KAAA4C,OAAA,CACAwH,MAAArG,EAAAqG,MACA7L,QAAAwF,EAAAxF,QACAmM,SAAA,CACAM,YAAAhL,KAAA4N,QAAAR,KACArC,gBAAA,EACAiH,kBAAA,EACAC,oBAAA,EACAtH,QAAA,IAGAlK,EAAAW,SACApB,KAAAoM,MACA,CAAM,MAAAnQ,GACAiB,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACSzB,EAAW7B,EAAAM,OAAWoQ,EAAK,kBACpC,CAAM,QACN3M,KAAA+I,UAAA,CACA,CAjCA,CAkCA,CAIA,eAAAjB,GACA,IAAAkC,EACA,MAAAnH,EAAA7C,KAAA4C,OACA,IAAAC,EAAA,OAAApC,EAAqBgC,IAAgB,MAMrC,YAAAI,EAAAuH,MAAA,OAAApK,KAAAkS,mBACA,MAAA7H,EAAA,UAAAxH,EAAAuH,MACAE,EAAAC,KAAAC,IAAA,EAAAD,KAAAE,IAAA,YAAAT,EAAAnH,EAAA6H,eAAA,EAAAV,EAAAW,UAAA,IACA,OAAAlK,EAAA,OACAR,UAAA,oCACKQ,EAAA,OACLR,UAAA,4CAAAN,OAAAkD,EAAAuH,QACK3J,EAAA,cAAoBkM,EAAK,SAAA9J,EAAAuH,QAAA3J,EAAA,SAAAoC,EAAAtE,WAAA8L,GAAA5J,EAAA,OAC9BR,UAAA,oBACKQ,EAAA,OACLR,UAAA,wBACA2K,MAAA,CACAC,MAAA,GAAAlL,OAAA2K,EAAA,SAEK7J,EAAA,OACLR,UAAA,6CACKoK,GAAA5J,EAAgBC,IAAM,CAC3BT,UAAA,SACAW,QAAA,IAAAZ,KAAAwL,UACOmB,EAAK,kBAAAtC,GAAA5J,EAAiCC,IAAM,CACnDT,UAAA,yBACAW,QAAA,IAAAZ,KAAAyL,SACOkB,EAAK,kBACZ,CAcA,gBAAAuF,GACA,OAAAlS,KAAA6M,UACApM,EAAA,OACAR,UAAA,oEACOQ,EAAA,OACPR,UAAA,8BACOQ,EAAA,KACPR,UAAA,+BACOQ,EAAA,MACPR,UAAA,+BACS0M,EAAK,iBAAAlM,EAAA,KACdR,UAAA,8BACS0M,EAAK,gBAAAlM,EAAA,MACdR,UAAA,+BACOQ,EAAA,UAAgBkM,EAAK,uBAAAlM,EAAA,UAAuCkM,EAAK,uBAAAlM,EAA2BC,IAAM,CACzGT,UAAA,sDACAU,KAAA,gBACAC,QAAA,IAAAuR,OAAAzI,SAAA0I,UACSzF,EAAK,mBAEdlM,EAAA,OACAR,UAAA,qCACKQ,EAAA,OACLR,UAAA,kEACKQ,EAAA,KACLR,UAAA,yBACKQ,EAAA,MACLR,UAAA,+BACO0M,EAAK,eAAAlM,EAAA,KACZR,UAAA,8BACO0M,EAAK,cAAAlM,EAAkBC,IAAM,CACpCT,UAAA,yBACAW,QAAA,IAAAZ,KAAAyL,SACOkB,EAAK,iBACZ,CACA,UAAAP,GACA,IAAApM,KAAAqM,SAAArM,KAAA4N,QAAA,CACA5N,KAAAqM,SAAA,EACA,IAEA,IADA,IAAAgG,EACArS,KAAA4N,SAAA5N,KAAA4C,QAAA,SAAA5C,KAAA4C,OAAAwH,OAAA,UAAApK,KAAA4C,OAAAwH,OACA,IACA,MAAArG,QAA4B7G,IAAA4B,QAAW,CACvCI,OAAA,OACAC,IAAA,GAAAQ,OAA2B1C,IAAM,oBAAA0C,OAAAK,KAAA4N,QAAAzB,OAAA,WAEjCnM,KAAA4C,OAAAmB,EACAtD,EAAAW,QACA,CAAU,MAAAnF,GAKV+C,QAAAC,MAAA,8BAAAhD,GACA,MAAAmC,EAAyBN,EAAW7B,EAAAM,OAAWoQ,EAAK,yBACpD3M,KAAA4C,OAAwB8J,EAAcA,EAAa,GAAG1M,KAAA4C,QAAA,GAAkB,CACxEwH,MAAA,QACA7L,QAAAH,IAEAqC,EAAAW,SACA,KACA,CAEA,iBAAAiR,EAAArS,KAAA4C,aAAA,EAAAyP,EAAAjI,SAOQlN,IAAAmC,OAAUC,KAAA,CAClBC,KAAA,WACWoN,EAAK,cAChB3M,KAAA6M,WAAA7M,KAAAK,MAAAkM,aAEA,CAAM,QACNvM,KAAAqM,SAAA,CACA,CAzCA,CA0CA,CACA,YAAAb,GACA,GAAAxL,KAAA4N,QAAA,CACA,UACY1Q,IAAA4B,QAAW,CACvBI,OAAA,SACAC,IAAA,GAAAQ,OAAuB1C,IAAM,oBAAA0C,OAAAK,KAAA4N,QAAAzB,SAE7B,CAAM,MAAAlQ,GACN+C,QAAAC,MAAA,gCAAAhD,GACMiB,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,WACSoN,EAAK,sBACd,CACA3M,KAAAyL,OAZA,CAaA,CACA,KAAAA,GACAzL,KAAAe,MACA,EC7iBA,SAASuR,EAAOrW,EAAAC,GAAS,IAAAC,EAAArB,OAAAmM,KAAAhL,GAAwB,GAAAnB,OAAAoM,sBAAA,CAAoC,IAAArM,EAAAC,OAAAoM,sBAAAjL,GAAyCC,IAAArB,EAAAA,EAAAsM,OAAA,SAAAjL,GAAkC,OAAApB,OAAAsM,yBAAAnL,EAAAC,GAAAlB,UAAA,IAA0DmB,EAAAmK,KAAAe,MAAAlL,EAAAtB,EAAA,CAA0B,OAAAsB,CAAA,CACpP,SAASoW,EAAatW,GAAM,QAAAC,EAAA,EAAgBA,EAAA6D,UAAAnC,OAAsB1B,IAAA,CAAO,IAAAC,EAAA,MAAA4D,UAAA7D,GAAA6D,UAAA7D,GAAA,GAAkDA,EAAA,EAAQoW,EAAOxX,OAAAqB,IAAA,GAAAoL,QAAA,SAAArL,GAAuCF,EAAeC,EAAAC,EAAAC,EAAAD,GAAA,GAAepB,OAAA0M,0BAAA1M,OAAA2M,iBAAAxL,EAAAnB,OAAA0M,0BAAArL,IAAyGmW,EAAOxX,OAAAqB,IAAAoL,QAAA,SAAArL,GAAmCpB,OAAAC,eAAAkB,EAAAC,EAAApB,OAAAsM,yBAAAjL,EAAAD,GAAA,EAAqE,CAAK,OAAAD,CAAA,CD8iB5aT,OAAAC,IAAAgE,IAAA,8CAAAmN,GCjiBe,MAAA4F,UAA2B3S,KAC1C,WAAA9D,GACA+D,SAAAC,WACI/D,EAAegE,KAAA,cACnB,CACA,SAAAC,GACA,uCACA,CACA,KAAAC,GACA,OAAAF,KAAAK,MAAAH,KACA,CACA,OAAAC,GACA,IAAAsS,EAAAC,EACA,MAAAC,EAAA,OAAAF,EAAAzS,KAAAK,MAAAsS,cAAAF,EAA6GvV,IAAAuB,WAAcC,MAAA,6CAC3HkU,EAAA,OAAAF,EAAA1S,KAAAK,MAAAuS,aAAAF,EAA2GxV,IAAAuB,WAAcC,MAAA,4CACzH,OAAA+B,EAAA,OACAR,UAAA,cACKQ,EAAA,OACLR,UAAA,2BACKD,KAAAK,MAAAsC,MAAAlC,EAAA,OACLR,UAAA,yCACKQ,EAAIC,IAAM,CACfT,UAAA,WAAAD,KAAAK,MAAAwS,OAAA,oCACAjS,QAAA,IAAAZ,KAAA8S,QAAA,IACKH,GAAAlS,EAAmBC,IAAM,CAC9BT,UAAA,SACAW,QAAA,IAAAZ,KAAA8S,QAAA,IACKF,IACL,CACA,MAAAE,CAAAC,GACA,IAAA9U,EACA+B,KAAAgT,WACAhT,KAAAgT,UAAA,EACA,OAAA/U,EAAA8U,EAAA/S,KAAAK,MAAA4B,UAAAjC,KAAAK,MAAA4S,WAAAhV,IACA+B,KAAAe,OACA,CAKA,cAAAmS,CAAA7Q,GAEA,IAAA8Q,EAAA/S,EAIA,OALAJ,KAAAgT,WAEAhT,KAAAgT,UAAA,EACA,OAAAG,GAAA/S,EAAAJ,KAAAK,OAAA4S,WAAAE,EAAA7X,KAAA8E,IAEAN,MAAAoT,eAAA7Q,EACA,EAkBA7G,OAAAC,IAAAgE,IAAA,+CAAA+S,GC7EA,MAAMY,EAAKxY,GAAUsC,IAAAuB,WAAcC,MAAA,6BAAAiB,OAAA/E,IAW5B,MAAAyY,UAA4BlR,KACnC,WAAApG,GACA+D,SAAAC,WACI/D,EAAegE,KAAA,aACfhE,EAAegE,KAAA,iBACnB,CACA,IAAAuC,CAAAF,GACA,GAAArC,KAAAsT,OAAA,CACA,MAAAC,EAAA,KACAvT,KAAAsT,QAAA,EACAtT,KAAAwT,UAAA,KACA/S,EAAAW,UAEA,OAAAiB,EAAAhC,MAAArC,SAAAqE,EAAAhC,MAAArC,SAAAgC,KAAAwT,UAAAD,GACA9S,EAAA,OACAR,UAAA,0CACOQ,EAAA,cAAoB2S,EAAK,mBAAA3S,EAAA,SAAkC2S,EAAK,kBAAA3S,EAAA,UACvElB,KAAA,SACAU,UAAA,SACAW,QAAA2S,GACSH,EAAK,mBACd,CACA,IACA,OAAA/Q,EAAAoR,QACA,CAAM,MAAAlS,GACN,IAAAmS,EAAA9O,EAKA,OAJA5E,KAAAsT,QAAA,EACAtT,KAAAwT,UAAAjS,EACA,OAAAmS,GAAA9O,EAAAvC,EAAAhC,OAAAsT,UAAAD,EAAApY,KAAAsJ,EAAArD,GACAvC,QAAAC,MAAA,kCAAAsC,GACA,IACA,CACA,EAEA/F,OAAAC,IAAAgE,IAAA,4CAA8D4T,cAAAA,ICpC9D,MAAMO,EAAK,CAAAhZ,EAAA8E,IAAoBxC,IAAAuB,WAAcC,MAAA,sBAAAiB,OAAA/E,GAAA,MAAA8E,EAAAA,EAAA,IAC9B,MAAAmU,UAA0B1R,KACzC,WAAApG,GACA+D,SAAAC,WACI/D,EAAegE,KAAA,uBACfhE,EAAegE,KAAA,kBACfhE,EAAegE,KAAA,aACnB,CACA,MAAAoC,CAAAC,GACAvC,MAAAsC,OAAAC,GACArC,KAAAsC,SACA,CACA,IAAAC,GACA,OAAA9B,EAAA,OACAR,UAAA,eACKQ,EAAI4S,EAAa,CACtBM,QAAA1X,GAAA+C,QAAAC,MAAA,wBAAAhD,IACKwE,EAAA,WACLR,UAAA,uBACKQ,EAAA,UAAgBmT,EAAK,wBAAAnT,EAAA,KAC1BR,UAAA,YACO2T,EAAK,uBAAAnT,EAAA,OACZR,UAAA,6BACKQ,EAAIC,IAAM,CACfT,UAAA,yBACAU,KAAA,kBACAC,QAAA,IAAAZ,KAAA8T,cACOF,EAAK,wBAAAnT,EAA4BC,IAAM,CAC9CT,UAAA,SACAU,KAAA,gBACAC,QAAA,IAAAZ,KAAA+T,cACOH,EAAK,0BAAAnT,EAA8ByB,EAAc,MAAAzB,EAAA,WACxDR,UAAA,oBACKQ,EAAA,UAAgBmT,EAAK,qBAAA5T,KAAAgU,eAC1B,CACA,UAAAA,GACA,kBAAAhU,KAAAiU,UAAAxT,EAA+CgC,IAAgB,MAC/D,UAAAzC,KAAAiU,UACAxT,EAAA,OACAR,UAAA,4CACOQ,EAAA,SAAemT,EAAK,qBAAA5T,KAAAkU,WAAAzT,EAAA,KAC3BR,UAAA,YACOQ,EAAA,YAAAT,KAAAkU,YAAAzT,EAAsCC,IAAM,CACnDT,UAAA,SACAU,KAAA,gBACAC,QAAA,IAAAZ,KAAAsC,WACSsR,EAAK,gBAEdnT,EAAakE,EAAU,CACvBE,QAAA7E,KAAA6E,QACAC,SAAAG,GAAAjF,KAAAmU,OAAAlP,GACAmP,UAAA,IAAApU,KAAAsC,WAEA,CACA,OAAAA,GAGA,OAFAtC,KAAAiU,UAAA,UACAjU,KAAAkU,UAAA,KACWtV,EAAU,CACrBM,OAAA,MACAC,IAAA,GAAAQ,OAAqB1C,IAAM,mBAC3BmC,SAAA,IACK+B,KAAA4C,IACL/D,KAAA6E,QAAAd,EAAAc,SAAA,GACA7E,KAAAiU,UAAA,OACK3S,MAAArF,IACL+D,KAAA6E,QAAA,GACA7E,KAAAiU,UAAA,QACAjU,KAAAkU,UAAuBpW,EAAW7B,KAC7BkF,KAAA,KACLV,EAAAW,UAEA,CACA,UAAA0S,GACI5W,IAAAgH,MAAS5E,KAAMqI,EAAW,CAC9B4E,WAAA,IAAAvM,KAAAsC,WAEA,CACA,UAAAyR,GACI7W,IAAAgH,MAAS5E,KAAMsN,EAAW,CAC9BL,WAAA,IAAAvM,KAAAsC,WAEA,CACA,aAAA2C,GF5BO,IAAA5E,EEmCP,SFnCOA,EE6B0B,CACjCH,MAAa0T,EAAK,6BAClBjR,KAAYiR,EAAK,uBACjBjB,aAAoBiB,EAAK,qBACzBf,QAAA,GFhCA,IAAAvE,QAAAC,IACA,IAAA8F,GAAA,EACA,MAAAC,EAAArM,IACAoM,IACAA,GAAA,EACA9F,EAAAtG,KAEI/K,IAAAgH,MAAS5E,KAAAkT,EAAoBD,EAAcA,EAAa,GAAGlS,GAAA,GAAY,CAC3E4B,UAAA,IAAAqS,GAAA,GACArB,SAAA,IAAAqB,GAAA,SE0BA,UACY1V,EAAU,CACtBM,OAAA,SACAC,IAAA,GAAAQ,OAAuB1C,IAAM,oBAAA0C,OAAAsF,GAC7B7F,SAAA,EACAL,gBAAAxC,OAAgCqX,EAAK,yBAE/B1W,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,WACSqU,EAAK,iBACd5T,KAAAsC,SACA,CAAM,MAAArG,GACAiB,IAAAmC,OAAUC,KAAA,CAChBC,KAAA,SACSzB,EAAW7B,EAAAM,OAAWqX,EAAK,wBACpC,CACA,EAEApY,OAAAC,IAAAgE,IAAA,8CAAAoU,GCpHA,MAAAU,EAAA,gBAKA,EAAA7Y,EAAA8Y,UAASC,IAAArZ,UAAuB,wBAAAsZ,GAChC,OAAA1U,KAAA2U,WAAA3U,KAAA2U,UAAA1P,KAAAsP,EAAA,KACAG,GACA,GACAxX,IAAA0X,aAAgBnV,IAAA8U,EAAA,KACdrX,IAAA2X,SAAYC,IAAAP,GAAAQ,gBAAA,IAAAtU,EAAqCoT,EAAW,gCAAAmB,mBAAA,CAC9DrU,KAAA,sBACAsU,MAAW/X,IAAAuB,WAAcC,MAAA,+CACzBwW,WAAA,iBACG","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/**\r\n * Wrapper around `app.request` that:\r\n * - logs every failure to console.error with action + URL;\r\n * - extracts the JSON:API error detail when present;\r\n * - shows a Flarum alert (unless `surface: false`);\r\n * - rethrows so the caller can still branch on the failure.\r\n *\r\n * Centralised so we don't reinvent error extraction at every call-site.\r\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(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/**\r\n * Two-stage modal:\r\n *\r\n * 1. Form — admin picks what to include and whether to encrypt.\r\n * 2. Progress — chunked-tick polling drives a progress bar until the\r\n * server reports phase=done (or phase=error).\r\n *\r\n * The \"encryption to a foreign key\" path lets the operator paste a\r\n * public key from another Flarum install — useful when preparing an\r\n * archive for transfer to a different server whose keypair is not the\r\n * one in *this* config.php.\r\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;\nconst trans = (key, params) => app.translator.trans(\"ramon-backup.admin.import_modal.\".concat(key), params != null ? params : {});\n\n/**\r\n * Three-stage modal:\r\n * 1. upload — operator picks a `.flarum` file. We POST to /imports\r\n * and the server validates the header without\r\n * decrypting.\r\n * 2. configure — depending on the inspect result we ask for the\r\n * private key (only when the archive is encrypted)\r\n * AND a confirm-replace checkbox in all cases. The\r\n * user agreed in setup that we'd ask at this point.\r\n * 3. progress — chunked-tick polling drives a progress bar.\r\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 /**\r\n * XHR-based upload with progress reporting. Flarum's session is\r\n * cookie-based, so credentials carry automatically; we just need to\r\n * forward the CSRF token the same way app.request does.\r\n *\r\n * We don't set `xhr.timeout` — large archives over slow links are\r\n * legitimate. Instead we watch for *idle* sockets (no progress event\r\n * for {@link UPLOAD_IDLE_TIMEOUT_MS} ms) and abort, which covers the\r\n * \"TCP didn't notice the network died\" failure mode.\r\n */\n uploadWithProgress(file, onProgress) {\n return new Promise((resolve, reject) => {\n var _session;\n const fd = new FormData();\n fd.append('archive', file);\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', e => {\n lastProgress = Date.now();\n if (e.lengthComputable) {\n onProgress(Math.round(e.loaded / Math.max(e.total, 1) * 100));\n }\n });\n xhr.upload.addEventListener('load', () => {\n lastProgress = Date.now();\n onProgress(100);\n });\n xhr.upload.addEventListener('error', () => {\n stopIdleTimer();\n reject({\n detail: trans('upload_failed')\n });\n });\n xhr.addEventListener('load', () => {\n stopIdleTimer();\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n resolve(JSON.parse(xhr.responseText));\n } catch (_unused) {\n reject({\n detail: trans('upload_failed')\n });\n }\n } else {\n let detail;\n try {\n var _body$errors;\n const body = JSON.parse(xhr.responseText);\n detail = body == null || (_body$errors = body.errors) == null || (_body$errors = _body$errors[0]) == null ? void 0 : _body$errors.detail;\n } catch (_unused2) {\n // non-JSON error body — fall back to status text\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: trans('upload_failed')\n });\n });\n xhr.addEventListener('abort', () => {\n stopIdleTimer();\n reject({\n detail: trans('upload_idle_timeout')\n });\n });\n xhr.open('POST', \"\".concat(apiUrl(), \"/backup/imports\"), true);\n xhr.withCredentials = true;\n // CSRF token — Flarum exposes it on the `app.session`.\n const csrf = (_session = app.session) == null ? void 0 : _session.csrfToken;\n if (csrf) xhr.setRequestHeader('X-CSRF-Token', csrf);\n xhr.send(fd);\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 name of extList) this.extensionsByName[name] = 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 /**\r\n * Replaces the progress UI as soon as `phase === 'done'`. Two\r\n * shapes:\r\n *\r\n * - DB restored — the admin's session was just wiped together\r\n * with the rest of the `users` / `sessions` tables. We make\r\n * this very clear and offer a single primary action: reload.\r\n * Anything else (refreshing the list, dismissing) would race\r\n * against an invalidated cookie and surface a confusing 401.\r\n *\r\n * - Files only — the session is fine, just close.\r\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 app.request({\n method: 'POST',\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id, \"/tick\")\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 app.request({\n method: 'DELETE',\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id)\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","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","Promise","resolve","reject","_session","fd","FormData","append","xhr","XMLHttpRequest","lastProgress","Date","now","idleTimer","setInterval","clearInterval","abort","stopIdleTimer","addEventListener","lengthComputable","round","loaded","JSON","parse","responseText","_body$errors","_unused2","statusText","open","withCredentials","csrf","session","csrfToken","setRequestHeader","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\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/**\r\n * Wrapper around `app.request` that:\r\n * - logs every failure to console.error with action + URL;\r\n * - extracts the JSON:API error detail when present;\r\n * - shows a Flarum alert (unless `surface: false`);\r\n * - rethrows so the caller can still branch on the failure.\r\n *\r\n * Centralised so we don't reinvent error extraction at every call-site.\r\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(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/**\r\n * Two-stage modal:\r\n *\r\n * 1. Form — admin picks what to include and whether to encrypt.\r\n * 2. Progress — chunked-tick polling drives a progress bar until the\r\n * server reports phase=done (or phase=error).\r\n *\r\n * The \"encryption to a foreign key\" path lets the operator paste a\r\n * public key from another Flarum install — useful when preparing an\r\n * archive for transfer to a different server whose keypair is not the\r\n * one in *this* config.php.\r\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/**\r\n * Fallback chunk size if /backup/imports init doesn't return one.\r\n * Server-recommended is 4 MB (see UploadImportController::RECOMMENDED_CHUNK_BYTES).\r\n */\nconst FALLBACK_CHUNK_BYTES = 4 * 1024 * 1024;\n\n/**\r\n * How many times to retry a single failed chunk before giving up on\r\n * the whole upload. Each retry uses the same offset so it overwrites\r\n * (idempotent). Two retries cover a transient hiccup without\r\n * spinning forever on a real outage.\r\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/**\r\n * Three-stage modal:\r\n * 1. upload — operator picks a `.flarum` file. We POST to /imports\r\n * and the server validates the header without\r\n * decrypting.\r\n * 2. configure — depending on the inspect result we ask for the\r\n * private key (only when the archive is encrypted)\r\n * AND a confirm-replace checkbox in all cases. The\r\n * user agreed in setup that we'd ask at this point.\r\n * 3. progress — chunked-tick polling drives a progress bar.\r\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 /**\r\n * Chunked upload + inspect.\r\n *\r\n * Single multipart POSTs of multi-GB archives reliably hit server\r\n * caps (`upload_max_filesize`, `post_max_size`, nginx\r\n * `client_max_body_size`, `memory_limit` during multipart parsing)\r\n * and surface as 500s. Instead we do three small requests:\r\n *\r\n * 1. POST /backup/imports — init, gets job_id + chunk_size\r\n * 2. POST /backup/imports/{id}/chunk* — append each slice (loop)\r\n * 3. POST /backup/imports/{id}/inspect — finalise, return meta\r\n *\r\n * Progress is computed from `bytesSent / file.size` across all chunk\r\n * requests so the bar advances smoothly through the entire file\r\n * even though each individual request only carries a few MB.\r\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 /**\r\n * Single-chunk upload as a raw octet-stream POST. We use XHR\r\n * (rather than fetch) so the per-chunk idle timeout works the\r\n * same way it did for the old monolithic upload, and so we can\r\n * pull a detailed error message off any non-2xx response body.\r\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: trans('upload_failed')\n });\n });\n xhr.addEventListener('abort', () => {\n stopIdleTimer();\n reject({\n detail: 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 name of extList) this.extensionsByName[name] = 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 /**\r\n * Replaces the progress UI as soon as `phase === 'done'`. Two\r\n * shapes:\r\n *\r\n * - DB restored — the admin's session was just wiped together\r\n * with the rest of the `users` / `sessions` tables. We make\r\n * this very clear and offer a single primary action: reload.\r\n * Anything else (refreshing the list, dismissing) would race\r\n * against an invalidated cookie and surface a confusing 401.\r\n *\r\n * - Files only — the session is fine, just close.\r\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 app.request({\n method: 'POST',\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id, \"/tick\")\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 app.request({\n method: 'DELETE',\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id)\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","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","Date","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 diff --git a/js/src/admin/components/ImportModal.tsx b/js/src/admin/components/ImportModal.tsx index a6db72b..5a46486 100644 --- a/js/src/admin/components/ImportModal.tsx +++ b/js/src/admin/components/ImportModal.tsx @@ -8,6 +8,20 @@ import { apiRequest, apiUrl, errorDetail, fmtBytes } from '../utils/api'; /** Abort the upload XHR if no progress event fires for this long. */ const UPLOAD_IDLE_TIMEOUT_MS = 60_000; +/** + * Fallback chunk size if /backup/imports init doesn't return one. + * Server-recommended is 4 MB (see UploadImportController::RECOMMENDED_CHUNK_BYTES). + */ +const FALLBACK_CHUNK_BYTES = 4 * 1024 * 1024; + +/** + * How many times to retry a single failed chunk before giving up on + * the whole upload. Each retry uses the same offset so it overwrites + * (idempotent). Two retries cover a transient hiccup without + * spinning forever on a real outage. + */ +const CHUNK_RETRY_LIMIT = 2; + export interface ImportModalAttrs extends IInternalModalAttrs { onComplete: () => void; } @@ -234,21 +248,78 @@ export default class ImportModal extends Modal { } /** - * XHR-based upload with progress reporting. Flarum's session is - * cookie-based, so credentials carry automatically; we just need to - * forward the CSRF token the same way app.request does. + * Chunked upload + inspect. + * + * Single multipart POSTs of multi-GB archives reliably hit server + * caps (`upload_max_filesize`, `post_max_size`, nginx + * `client_max_body_size`, `memory_limit` during multipart parsing) + * and surface as 500s. Instead we do three small requests: * - * We don't set `xhr.timeout` — large archives over slow links are - * legitimate. Instead we watch for *idle* sockets (no progress event - * for {@link UPLOAD_IDLE_TIMEOUT_MS} ms) and abort, which covers the - * "TCP didn't notice the network died" failure mode. + * 1. POST /backup/imports — init, gets job_id + chunk_size + * 2. POST /backup/imports/{id}/chunk* — append each slice (loop) + * 3. POST /backup/imports/{id}/inspect — finalise, return meta + * + * Progress is computed from `bytesSent / file.size` across all chunk + * requests so the bar advances smoothly through the entire file + * even though each individual request only carries a few MB. */ - private uploadWithProgress(file: File, onProgress: (pct: number) => void): Promise { - return new Promise((resolve, reject) => { - const fd = new FormData(); - fd.append('archive', file); + private async uploadWithProgress(file: File, onProgress: (pct: number) => void): Promise { + // ─── 1. init ────────────────────────────────────────────────── + const init = await apiRequest<{ job_id: string; chunk_size: number }>({ + method: 'POST', + url: `${apiUrl()}/backup/imports`, + body: { filename: file.name, size: file.size }, + surface: false, + }); + const jobId = init.job_id; + const chunkSize = init.chunk_size > 0 ? init.chunk_size : FALLBACK_CHUNK_BYTES; + + // ─── 2. chunk loop ──────────────────────────────────────────── + let offset = 0; + while (offset < file.size) { + const end = Math.min(offset + chunkSize, file.size); + const slice = file.slice(offset, end); + + let attempt = 0; + // eslint-disable-next-line no-constant-condition + while (true) { + try { + await this.sendChunk(jobId, offset, slice); + break; + } catch (e) { + attempt++; + if (attempt > CHUNK_RETRY_LIMIT) throw e; + // Back off briefly before retrying — gives a transient + // hiccup a moment to clear without spamming the server. + await new Promise((r) => setTimeout(r, 750 * attempt)); + } + } + + offset = end; + const pct = Math.min(99, Math.round((offset / file.size) * 100)); + onProgress(pct); + } + + // ─── 3. inspect ─────────────────────────────────────────────── + onProgress(100); + return apiRequest({ + method: 'POST', + url: `${apiUrl()}/backup/imports/${jobId}/inspect`, + surface: false, + }); + } + + /** + * Single-chunk upload as a raw octet-stream POST. We use XHR + * (rather than fetch) so the per-chunk idle timeout works the + * same way it did for the old monolithic upload, and so we can + * pull a detailed error message off any non-2xx response body. + */ + private sendChunk(jobId: string, offset: number, slice: Blob): Promise { + return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); + let lastProgress = Date.now(); const idleTimer = setInterval(() => { if (Date.now() - lastProgress > UPLOAD_IDLE_TIMEOUT_MS) { @@ -258,36 +329,20 @@ export default class ImportModal extends Modal { }, 5_000); const stopIdleTimer = () => clearInterval(idleTimer); - xhr.upload.addEventListener('progress', (e: ProgressEvent) => { - lastProgress = Date.now(); - if (e.lengthComputable) { - onProgress(Math.round((e.loaded / Math.max(e.total, 1)) * 100)); - } - }); - xhr.upload.addEventListener('load', () => { + xhr.upload.addEventListener('progress', () => { lastProgress = Date.now(); - onProgress(100); - }); - xhr.upload.addEventListener('error', () => { - stopIdleTimer(); - reject({ detail: trans('upload_failed') as string }); }); xhr.addEventListener('load', () => { stopIdleTimer(); if (xhr.status >= 200 && xhr.status < 300) { - try { - resolve(JSON.parse(xhr.responseText) as InspectResult); - } catch { - reject({ detail: trans('upload_failed') as string }); - } + resolve(); } else { let detail: string | undefined; try { - const body = JSON.parse(xhr.responseText); - detail = body?.errors?.[0]?.detail; + detail = JSON.parse(xhr.responseText)?.errors?.[0]?.detail; } catch { - // non-JSON error body — fall back to status text + /* non-JSON body */ } reject({ detail: detail || `${xhr.status} ${xhr.statusText}` }); } @@ -301,12 +356,13 @@ export default class ImportModal extends Modal { reject({ detail: trans('upload_idle_timeout') as string }); }); - xhr.open('POST', `${apiUrl()}/backup/imports`, true); + xhr.open('POST', `${apiUrl()}/backup/imports/${jobId}/chunk`, true); xhr.withCredentials = true; - // CSRF token — Flarum exposes it on the `app.session`. + xhr.setRequestHeader('Content-Type', 'application/octet-stream'); + xhr.setRequestHeader('X-Chunk-Offset', String(offset)); const csrf = (app as any).session?.csrfToken; if (csrf) xhr.setRequestHeader('X-CSRF-Token', csrf); - xhr.send(fd); + xhr.send(slice); }); } diff --git a/src/Api/Controller/ChunkImportController.php b/src/Api/Controller/ChunkImportController.php new file mode 100644 index 0000000..99b6f78 --- /dev/null +++ b/src/Api/Controller/ChunkImportController.php @@ -0,0 +1,176 @@ +assertCanManage($request); + + $jobId = (string) ($request->getQueryParams()['id'] ?? ''); + if (! preg_match('/^[a-f0-9]{16}$/', $jobId)) { + throw new ValidationException(['id' => 'Invalid job id.']); + } + + $offsetHeader = trim($request->getHeaderLine('X-Chunk-Offset')); + if ($offsetHeader === '' || ! preg_match('/^\d+$/', $offsetHeader)) { + throw new ValidationException([ + 'chunk' => 'Missing or invalid X-Chunk-Offset header.', + ]); + } + $offset = (int) $offsetHeader; + + $dir = $this->paths->importJobDir($jobId); + $meta = $this->loadMeta($dir); + $dest = $dir.DIRECTORY_SEPARATOR.'upload.flarum'; + + if (! is_file($dest)) { + throw new ValidationException([ + 'chunk' => 'Upload session not initialised — call POST /backup/imports first.', + ]); + } + + $expected = (int) ($meta['expected_size'] ?? 0); + + // Stream the request body straight to disk in fixed-size + // reads so we never hold more than 64 KB of the chunk in + // memory regardless of the chunk's total size. + $body = $request->getBody(); + $body->rewind(); + + if (fseek_or_truncate($dest, $offset) === false) { + throw new ValidationException([ + 'chunk' => 'Failed to position staging file at offset '.$offset.'.', + ]); + } + + $fh = @fopen($dest, 'cb'); // c = open for read+write, don't truncate, create if missing + if ($fh === false) { + throw new ValidationException(['chunk' => 'Could not open staging file.']); + } + + try { + if (fseek($fh, $offset) !== 0) { + throw new ValidationException([ + 'chunk' => 'Failed to seek to offset '.$offset.' in staging file.', + ]); + } + + $written = 0; + while (! $body->eof()) { + $piece = $body->read(64 * 1024); + if ($piece === '') break; + + $written += strlen($piece); + if ($written > self::MAX_CHUNK_BYTES) { + throw new ValidationException([ + 'chunk' => sprintf( + 'Chunk exceeds per-request limit (%d bytes).', + self::MAX_CHUNK_BYTES + ), + ]); + } + if ($expected > 0 && $offset + $written > $expected) { + throw new ValidationException([ + 'chunk' => 'Chunk would write past the declared upload size.', + ]); + } + + $bytes = fwrite($fh, $piece); + if ($bytes === false || $bytes !== strlen($piece)) { + throw new ValidationException([ + 'chunk' => 'Write failed at offset '.($offset + $written - strlen($piece)).' — disk full?', + ]); + } + } + } finally { + fclose($fh); + } + + $received = filesize($dest) ?: 0; + return new JsonResponse([ + 'received' => $received, + 'expected' => $expected, + ]); + } + + /** @return array{expected_size?: int, filename?: string} */ + private function loadMeta(string $dir): array + { + $path = $dir.DIRECTORY_SEPARATOR.'upload.meta.json'; + if (! is_file($path)) return []; + $raw = @file_get_contents($path); + if ($raw === false) return []; + $data = json_decode($raw, true); + return is_array($data) ? $data : []; + } +} + +/** + * Pre-create / pre-extend the staging file to at least `$offset` + * bytes. Without this, fseek on an `cb` mode handle won't grow the + * file, and writes can land at the wrong offset. + */ +function fseek_or_truncate(string $path, int $offset): int|false +{ + clearstatcache(true, $path); + $size = filesize($path); + if ($size === false) return false; + if ($size >= $offset) return $size; + + // Extend with zero bytes up to the offset. We rarely take this + // path — only when chunks arrive out of order, which the client + // doesn't normally do but is allowed by the protocol. + $fh = @fopen($path, 'ab'); + if ($fh === false) return false; + try { + $pad = $offset - $size; + $chunk = str_repeat("\0", min(64 * 1024, $pad)); + while ($pad > 0) { + $w = fwrite($fh, substr($chunk, 0, min(strlen($chunk), $pad))); + if ($w === false || $w === 0) return false; + $pad -= $w; + } + } finally { + fclose($fh); + } + return $offset; +} diff --git a/src/Api/Controller/InspectImportController.php b/src/Api/Controller/InspectImportController.php new file mode 100644 index 0000000..b6f5fee --- /dev/null +++ b/src/Api/Controller/InspectImportController.php @@ -0,0 +1,96 @@ +assertCanManage($request); + + $jobId = (string) ($request->getQueryParams()['id'] ?? ''); + if (! preg_match('/^[a-f0-9]{16}$/', $jobId)) { + throw new ValidationException(['id' => 'Invalid job id.']); + } + + $dir = $this->paths->importJobDir($jobId); + $dest = $dir.DIRECTORY_SEPARATOR.'upload.flarum'; + + if (! is_file($dest)) { + throw new ValidationException([ + 'archive' => 'Upload session not initialised or already cleaned up.', + ]); + } + + $meta = $this->loadMeta($dir); + $expected = (int) ($meta['expected_size'] ?? 0); + $actual = filesize($dest) ?: 0; + + if ($expected > 0 && $actual < $expected) { + throw new ValidationException([ + 'archive' => sprintf( + 'Upload incomplete: %d / %d bytes received. Re-send missing chunks before inspect.', + $actual, $expected + ), + ]); + } + + try { + $reader = ArchiveReader::openHeader($dest); + $isEncrypted = $reader->isEncrypted(); + $archiveMeta = $reader->meta(); + $reader->close(); + } catch (\Throwable $e) { + $this->paths->deleteDir($dir); + throw new ValidationException([ + 'archive' => 'Not a valid Flarum backup: '.$e->getMessage(), + ]); + } + + return new JsonResponse([ + 'job_id' => $jobId, + 'is_encrypted' => $isEncrypted, + 'meta' => $archiveMeta, + 'size' => $actual, + ]); + } + + /** @return array{expected_size?: int, filename?: string} */ + private function loadMeta(string $dir): array + { + $path = $dir.DIRECTORY_SEPARATOR.'upload.meta.json'; + if (! is_file($path)) return []; + $raw = @file_get_contents($path); + if ($raw === false) return []; + $data = json_decode($raw, true); + return is_array($data) ? $data : []; + } +} diff --git a/src/Api/Controller/UploadImportController.php b/src/Api/Controller/UploadImportController.php index 7e81edd..f5bedb0 100644 --- a/src/Api/Controller/UploadImportController.php +++ b/src/Api/Controller/UploadImportController.php @@ -7,30 +7,54 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use Ramon\Backup\Archive\ArchiveReader; use Ramon\Backup\StoragePaths; /** * POST /api/backup/imports * - * Accepts an uploaded `.flarum` archive (multipart). Stages it under - * the per-job tmp dir as `upload.flarum`, reads the header WITHOUT - * decrypting, and returns: - * - job_id - * - is_encrypted (so the UI can prompt for the private key) - * - meta (creation date, contents, format version) + * Initialise a chunked upload. Body: `{ "filename": "...flarum", + * "size": 1234567890 }`. * - * The actual restore is kicked off by /api/backup/imports/{id}/start. + * Why chunked instead of one big multipart POST: a single multipart + * upload of a multi-GB `.flarum` archive routinely fails with a 500 + * on real servers because every layer in front of PHP has its own + * size cap (`upload_max_filesize` / `post_max_size` / nginx + * `client_max_body_size` / `memory_limit` while parsing multipart + * boundaries). Splitting the file into ~4 MB chunks side-steps every + * one of those — each chunk request is well below any default cap, + * and a flaky network can be retried per-chunk without restarting + * the whole upload. * - * Note on chunked uploads: most PHP / nginx setups cap upload size. - * We don't try to be cleverer than the server here — if the upload - * fails, the operator should raise upload_max_filesize or use the - * import-from-existing-backup-id flow. + * The protocol (mirrors the export-tick pattern): + * + * 1. POST /backup/imports — this controller. Creates + * an empty staging file and returns the job_id + chunk_size. + * 2. POST /backup/imports/{id}/chunk — append raw bytes at the + * offset given via header. Idempotent: same offset on retry. + * 3. POST /backup/imports/{id}/inspect — read the header (no + * decryption), return is_encrypted + meta. The actual restore + * is kicked off by /backup/imports/{id}/start, unchanged. */ class UploadImportController implements RequestHandlerInterface { use AdminOnlyController; + /** + * Server-recommended chunk size returned to the client. 4 MB is + * comfortably below every common ceiling (PHP's default + * `post_max_size` is 8 M, nginx's `client_max_body_size` is 1 M + * but most prod configs raise it to at least 16 M, etc.) and + * keeps per-chunk RAM cost trivial. + */ + public const RECOMMENDED_CHUNK_BYTES = 4 * 1024 * 1024; + + /** + * Hard cap on the claimed total size of an upload. 8 GB is + * already more than any realistic Flarum backup; refusing + * larger up front avoids filling the disk by accident. + */ + public const MAX_TOTAL_BYTES = 8 * 1024 * 1024 * 1024; + public function __construct( protected StoragePaths $paths ) { @@ -40,41 +64,54 @@ public function handle(ServerRequestInterface $request): ResponseInterface { $this->assertCanManage($request); - $files = $request->getUploadedFiles(); - $upload = $files['archive'] ?? null; - if ($upload === null || $upload->getError() !== UPLOAD_ERR_OK) { - throw new ValidationException(['archive' => 'No archive uploaded.']); - } + $body = (array) $request->getParsedBody(); + + $filename = trim((string) ($body['filename'] ?? '')); + $size = (int) ($body['size'] ?? 0); - $clientName = (string) $upload->getClientFilename(); - if ($clientName !== '' && ! str_ends_with(strtolower($clientName), '.flarum')) { + if ($filename !== '' && ! str_ends_with(strtolower($filename), '.flarum')) { throw new ValidationException(['archive' => 'File must have a .flarum extension.']); } + if ($size <= 0) { + throw new ValidationException(['archive' => 'Upload size must be greater than zero.']); + } + if ($size > self::MAX_TOTAL_BYTES) { + throw new ValidationException([ + 'archive' => sprintf( + 'Upload exceeds the per-file ceiling (%d bytes > %d).', + $size, self::MAX_TOTAL_BYTES + ), + ]); + } $jobId = bin2hex(random_bytes(8)); - $dir = $this->paths->importJobDir($jobId); + $dir = $this->paths->importJobDir($jobId); + + if (! is_writable($dir)) { + throw new ValidationException([ + 'archive' => 'Staging directory is not writable: '.$dir, + ]); + } + $dest = $dir.DIRECTORY_SEPARATOR.'upload.flarum'; - $upload->moveTo($dest); - - // Validate it's actually a Flarum backup before we tell the - // user "ready to restore". We open ONLY the header — no - // decryption, no key required at this stage. - try { - $reader = ArchiveReader::openHeader($dest); - $isEncrypted = $reader->isEncrypted(); - $meta = $reader->meta(); - $reader->close(); - } catch (\Throwable $e) { - $this->paths->deleteDir($dir); - throw new ValidationException(['archive' => 'Not a valid Flarum backup: '.$e->getMessage()]); + // Create the empty staging file and persist the expected + // size so the chunk endpoint can validate offsets and the + // inspect endpoint can refuse a truncated upload. + $fh = @fopen($dest, 'wb'); + if ($fh === false) { + throw new ValidationException(['archive' => 'Could not create staging file.']); } + fclose($fh); + + @file_put_contents( + $dir.DIRECTORY_SEPARATOR.'upload.meta.json', + json_encode(['expected_size' => $size, 'filename' => $filename]) + ); return new JsonResponse([ - 'job_id' => $jobId, - 'is_encrypted' => $isEncrypted, - 'meta' => $meta, - 'size' => filesize($dest) ?: 0, + 'job_id' => $jobId, + 'chunk_size' => self::RECOMMENDED_CHUNK_BYTES, ]); } } From d8aa2d14ba20f4b39797b510c5f02c505fc19427 Mon Sep 17 00:00:00 2001 From: Ramon Guilherme Date: Sun, 10 May 2026 18:06:11 -0300 Subject: [PATCH 06/27] =?UTF-8?q?feat:=20Adicionar=20ressincroniza=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20sequ=C3=AAncias=20do=20Postgres=20ap=C3=B3s=20re?= =?UTF-8?q?staura=C3=A7=C3=A3o=20em=20massa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Job/ImportJob.php | 89 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/Job/ImportJob.php b/src/Job/ImportJob.php index 1501e01..237c075 100644 --- a/src/Job/ImportJob.php +++ b/src/Job/ImportJob.php @@ -382,12 +382,101 @@ private function runRestore(JobState $state): void // would get a fresh connection anyway. DatabaseRestorer::enableForeignKeys($this->db); + // Belt-and-braces sequence resync on Postgres. Our dump + // already emits `setval(...)` in its epilogue, but: (a) + // archives generated before that code shipped don't carry + // those statements; (b) a managed-host quirk could + // silently swallow the call; (c) an admin who manually + // ran `psql -f dump.sql` wouldn't have triggered our + // emitter at all. Running it again on the live database + // is harmless — `setval(seq, MAX(id)+1, false)` always + // converges to the right next value — so this protects + // every path that ends with "PG database newly populated". + $this->resyncPostgresSequences($state); + $state->set('phase', 'rewrite'); } $state->save(); } + /** + * After a bulk restore, bump every Postgres sequence past its + * column's `MAX(id)`. Without this the next `INSERT … RETURNING + * id` reuses a value already present and the user sees a + * `discussions_pkey` (or similar) unique-constraint violation as + * soon as they post their first new discussion. + * + * Sweeps every column in the current schema that has a backing + * sequence (covers both `SERIAL` and PG 10+ `IDENTITY`). Each + * `setval` is its own statement so one bad table doesn't abort + * the rest. + */ + private function resyncPostgresSequences(JobState $state): void + { + try { + if (Dialect::detect($this->db) !== Dialect::POSTGRES) return; + } catch (Throwable) { + return; + } + + // pg_get_serial_sequence returns the sequence name for both + // legacy SERIAL columns and PG 10+ IDENTITY columns. Filtering + // by `sequence IS NOT NULL` cheaply picks out exactly the + // columns we need to fix. + try { + $rows = $this->db->select( + "SELECT n.nspname AS schema_name, c.relname AS table_name, + a.attname AS column_name, + pg_get_serial_sequence( + quote_ident(n.nspname)||'.'||quote_ident(c.relname), + a.attname + ) AS seq + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + JOIN pg_attribute a ON a.attrelid = c.oid + WHERE c.relkind = 'r' + AND n.nspname = current_schema() + AND a.attnum > 0 + AND NOT a.attisdropped" + ); + } catch (Throwable) { + return; + } + + $bumped = 0; + foreach ($rows as $r) { + $arr = (array) $r; + $seq = $arr['seq'] ?? null; + if (! is_string($seq) || $seq === '') continue; + + $tbl = (string) ($arr['table_name'] ?? ''); + $col = (string) ($arr['column_name'] ?? ''); + if ($tbl === '' || $col === '') continue; + + $tblQ = '"' . str_replace('"', '""', $tbl) . '"'; + $colQ = '"' . str_replace('"', '""', $col) . '"'; + + try { + $this->db->select( + "SELECT setval(?, COALESCE((SELECT MAX($colQ) FROM $tblQ), 0) + 1, false)", + [$seq] + ); + $bumped++; + } catch (Throwable) { + // Skip individual failures — keep going so one weird + // table (e.g. permission missing on a system view we + // shouldn't have picked up) doesn't break the rest. + } + } + + if ($bumped > 0) { + $progress = (array) $state->get('progress'); + $progress['sequences_resynced'] = $bumped; + $state->set('progress', $progress); + } + } + /** * Phase 4 — URL rewrite. Backups taken from one Flarum and * restored onto another would otherwise leave the OLD forum URL From 9ae3eb4e5f738b2dfa422cb33f0cff0090c9109a Mon Sep 17 00:00:00 2001 From: Ramon Guilherme Date: Wed, 13 May 2026 09:53:43 -0300 Subject: [PATCH 07/27] =?UTF-8?q?feat:=20Adicionar=20workflows=20de=20CI,?= =?UTF-8?q?=20limpeza=20de=20releases=20e=20sincroniza=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20branches,=20al=C3=A9m=20de=20melhorias=20na=20valida=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20identificadores=20e=20na=20estrutura=20do=20proj?= =?UTF-8?q?eto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/pr-labeler.yml | 10 ++ .github/release-drafter.yml | 55 +++++++++++ .github/workflows/ci.yml | 74 +++++++++++++++ .github/workflows/cleanup-releases.yml | 56 ++++++++++++ .github/workflows/pr-labeler.yml | 20 ++++ .github/workflows/publish-to-flarum.yml | 92 +++++++++++++++++++ .github/workflows/release-management.yml | 112 +++++++++++++++++++++++ .github/workflows/sync-branches.yml | 54 +++++++++++ composer.json | 1 + src/Database/DatabaseDumper.php | 17 ++++ src/Database/Emitter/AbstractEmitter.php | 8 ++ 11 files changed, 499 insertions(+) create mode 100644 .github/pr-labeler.yml create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/cleanup-releases.yml create mode 100644 .github/workflows/pr-labeler.yml create mode 100644 .github/workflows/publish-to-flarum.yml create mode 100644 .github/workflows/release-management.yml create mode 100644 .github/workflows/sync-branches.yml diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml new file mode 100644 index 0000000..cfafe77 --- /dev/null +++ b/.github/pr-labeler.yml @@ -0,0 +1,10 @@ +BC: bc/* +melhoria: melhoria/* +correcao: ['correcao/*', 'conserto/*', 'ajuste/*'] +dependencias: dependencias/* +documentacao: ['docs/*', 'documentacao/*'] +manutencao: manutencao/* +performance: performance/* +traducao: traducao/* +refatoracao: refatoracao/* +'pular changelog': release/* diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..f7715b7 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,55 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +exclude-labels: + - 'pular changelog' +categories: + - title: '⚠️ Alterações Críticas (Breaking Changes)' + labels: + - 'BC' + - title: '🔨 Melhoria' + labels: + - 'melhoria' + - title: '🐞 Correções de Erros' + labels: + - 'correcao' + - title: '🚀 Performance' + labels: + - 'performance' + - title: '📖 Documentação' + labels: + - 'documentacao' + - title: '♻️ Refatoração' + labels: + - 'refatoracao' + - title: '📦 Dependências' + labels: + - 'dependencias' + - title: '🌍 Traduções' + labels: + - 'traducao' + - title: '🔧 Manutenção' + labels: + - 'manutencao' +version-resolver: + major: + labels: + - 'BC' + minor: + labels: + - 'melhoria' + patch: + labels: + - 'correcao' + default: patch +change-template: '- $TITLE (PR #$NUMBER) por @$AUTHOR' +template: | + ## O que mudou na extensão Backup & Migration 🌟 + $CHANGES + + ## 📦 Como atualizar + + ```bash + composer require ramon/backup:$RESOLVED_VERSION + php flarum cache:clear + php flarum assets:publish + ``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7956ab8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +# Avoid stacking redundant runs for the same PR or branch. +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + php: + name: PHP ${{ matrix.php }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.2', '8.3', '8.4'] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + tools: composer:v2 + + - name: Validar composer.json + run: composer validate --strict --no-check-publish + + - name: Lint PHP (php -l) + run: | + set -euo pipefail + mapfile -d '' files < <(find src migrations extend.php -name '*.php' -print0 2>/dev/null) + [ "${#files[@]}" -gt 0 ] || { echo "Nenhum arquivo PHP encontrado"; exit 1; } + printf '%s\0' "${files[@]}" | xargs -0 -n1 -P4 php -l + + js: + name: JS (typecheck, format, build) + runs-on: ubuntu-latest + defaults: + run: + working-directory: js + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: js/package-lock.json + + - name: Instalar dependências + run: npm ci + + - name: Verificar formatação (Prettier) + run: npm run format-check + + - name: Type-check (TypeScript) + run: npm run check-typings + + - name: Build (webpack production) + run: npm run build diff --git a/.github/workflows/cleanup-releases.yml b/.github/workflows/cleanup-releases.yml new file mode 100644 index 0000000..cb8524d --- /dev/null +++ b/.github/workflows/cleanup-releases.yml @@ -0,0 +1,56 @@ +name: Limpar Releases Antigas + +on: + workflow_dispatch: + +jobs: + cleanup: + name: Manter apenas as 5 últimas releases + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Apagar releases antigas + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const KEEP = 5; + + const releases = await github.paginate( + github.rest.repos.listReleases, + { owner: context.repo.owner, repo: context.repo.repo, per_page: 100 } + ); + + // Sort by published_at descending (newest first) + releases.sort((a, b) => new Date(b.published_at) - new Date(a.published_at)); + + const toDelete = releases.slice(KEEP); + + console.log(`Total de releases: ${releases.length}`); + console.log(`Mantendo as ${KEEP} mais recentes, apagando ${toDelete.length}`); + + for (const release of toDelete) { + console.log(`Apagando release: ${release.tag_name} (id: ${release.id})`); + + // Delete the release + await github.rest.repos.deleteRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id, + }); + + // Delete the associated tag + try { + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${release.tag_name}`, + }); + console.log(` Tag ${release.tag_name} apagada`); + } catch (e) { + console.log(` Tag ${release.tag_name} não encontrada ou já apagada`); + } + } + + console.log('✅ Limpeza concluída!'); diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 0000000..0fc1674 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,20 @@ +name: PR Labeler + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + label_pr: + name: Aplicar Labels no PR + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Aplicar label baseada na branch + uses: TimonVS/pr-labeler-action@f9c084306ce8b3f488a8f3ee1ccedc6da131d1af # v5.0.0 + with: + configuration-path: .github/pr-labeler.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-to-flarum.yml b/.github/workflows/publish-to-flarum.yml new file mode 100644 index 0000000..ccaae04 --- /dev/null +++ b/.github/workflows/publish-to-flarum.yml @@ -0,0 +1,92 @@ +name: Publicar Release no Flarum + +on: + release: + types: [published] + workflow_dispatch: + inputs: + release_tag: + description: 'Tag da release (ex: v2.1.19-beta)' + required: true + release_body: + description: 'Corpo/descrição da release' + required: false + default: '' + release_url: + description: 'URL da release no GitHub' + required: false + default: '' + +permissions: {} + +jobs: + publish_to_flarum: + name: Publicar no Fórum + runs-on: ubuntu-latest + steps: + - name: Validar FLARUM_DISCUSSION_ID + env: + FLARUM_DISCUSSION_ID: ${{ vars.FLARUM_DISCUSSION_ID }} + run: | + if [ -z "$FLARUM_DISCUSSION_ID" ]; then + echo "❌ Erro: FLARUM_DISCUSSION_ID não está configurado nas variáveis do repositório." + echo "Configure em: Settings > Secrets and variables > Actions > Variables" + exit 1 + fi + echo "✅ FLARUM_DISCUSSION_ID configurado: $FLARUM_DISCUSSION_ID" + + - name: Postar comentário de release no Flarum + env: + FLARUM_API_KEY: ${{ secrets.FLARUM_API_KEY }} + FLARUM_DISCUSSION_ID: ${{ vars.FLARUM_DISCUSSION_ID }} + RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }} + RELEASE_BODY: ${{ github.event.release.body || inputs.release_body }} + RELEASE_URL: ${{ github.event.release.html_url || inputs.release_url }} + run: | + # Extract version from tag (remove leading 'v' if present) + VERSION="${RELEASE_TAG#v}" + + # Build the JSON payload using jq to safely handle all special characters + PAYLOAD=$(jq -n \ + --arg tag "$RELEASE_TAG" \ + --arg body "$RELEASE_BODY" \ + --arg url "$RELEASE_URL" \ + --arg version "$VERSION" \ + --arg discussion_id "$FLARUM_DISCUSSION_ID" \ + '{ + "data": { + "type": "posts", + "attributes": { + "content": ("## 🚀 " + $tag + "\n\n" + $body + "\n\n**Instalação:**\n```\ncomposer require ramon/backup:" + $version + "\n```\n\n[Ver release no GitHub](" + $url + ")") + }, + "relationships": { + "discussion": { + "data": { + "type": "discussions", + "id": $discussion_id + } + } + } + } + }') + + echo "Payload preview:" + echo "$PAYLOAD" | jq '.' + + HTTP_STATUS=$(curl -s -o /tmp/flarum_response.json -w "%{http_code}" \ + -X POST "https://ramonguilherme.com.br/api/posts" \ + -H "Authorization: Token ${FLARUM_API_KEY}" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + + echo "HTTP Status: $HTTP_STATUS" + + if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then + echo "✅ Comentário publicado com sucesso no Flarum!" + echo "Post ID: $(jq -r '.data.id // empty' /tmp/flarum_response.json)" + else + echo "❌ Falha ao publicar comentário no Flarum (HTTP $HTTP_STATUS)" + echo "Response body:" + jq '.' /tmp/flarum_response.json + exit 1 + fi diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml new file mode 100644 index 0000000..3c316b2 --- /dev/null +++ b/.github/workflows/release-management.yml @@ -0,0 +1,112 @@ +name: Release Workflow + +on: + push: + branches: + - main + +jobs: + build_and_release: + name: Build e Publicação Automática + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + steps: + - name: Checkout do código + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configurar Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: js/package-lock.json + + - name: Instalar Dependências JS + run: | + cd js + npm ci + + - name: Build JS (Flarum) + run: | + cd js + npm run build + + - name: Verificar versão no composer.json + id: check_version + uses: EndBug/version-check@d17247dd94ca7b39d0b0691399be8d7c510622c9 # v2 + with: + file-name: composer.json + diff-search: true + + - name: Publicar Release em Português + id: create_release + if: steps.check_version.outputs.changed == 'true' + uses: release-drafter/release-drafter@6a93d829887aa2e0748befe2e808c66c0ec6e4c7 # v6 + with: + publish: true + tag: v${{ steps.check_version.outputs.version }} + name: v${{ steps.check_version.outputs.version }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publicar Release no Flarum + if: steps.check_version.outputs.changed == 'true' && steps.create_release.outcome == 'success' && steps.create_release.outputs.html_url && vars.FLARUM_DISCUSSION_ID != '' + env: + FLARUM_API_KEY: ${{ secrets.FLARUM_API_KEY }} + FLARUM_DISCUSSION_ID: ${{ vars.FLARUM_DISCUSSION_ID }} + RELEASE_TAG: ${{ steps.create_release.outputs.tag_name }} + RELEASE_BODY: ${{ steps.create_release.outputs.body }} + RELEASE_URL: ${{ steps.create_release.outputs.html_url }} + run: | + # Extract version from tag (remove leading 'v' if present) + VERSION="${RELEASE_TAG#v}" + + PAYLOAD=$(jq -n \ + --arg tag "$RELEASE_TAG" \ + --arg body "$RELEASE_BODY" \ + --arg url "$RELEASE_URL" \ + --arg version "$VERSION" \ + --arg discussion_id "$FLARUM_DISCUSSION_ID" \ + '{ + "data": { + "type": "posts", + "attributes": { + "content": ("## 🚀 " + $tag + "\n\n" + $body + "\n\n**Instalação:**\n```\ncomposer require ramon/backup:" + $version + "\n```\n\n[Ver release no GitHub](" + $url + ")") + }, + "relationships": { + "discussion": { + "data": { + "type": "discussions", + "id": $discussion_id + } + } + } + } + }') + + HTTP_STATUS=$(curl -s -o /tmp/flarum_response.json -w "%{http_code}" \ + -X POST "https://ramonguilherme.com.br/api/posts" \ + -H "Authorization: Token ${FLARUM_API_KEY}" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + + echo "HTTP Status: $HTTP_STATUS" + + if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then + echo "✅ Comentário publicado com sucesso no Flarum!" + echo "Post ID: $(jq -r '.data.id // empty' /tmp/flarum_response.json)" + else + echo "❌ Falha ao publicar comentário no Flarum (HTTP $HTTP_STATUS)" + echo "Response body:" + jq '.' /tmp/flarum_response.json + exit 1 + fi + + - name: Log de Status + run: | + echo "Versão alterada: ${{ steps.check_version.outputs.changed }}" + echo "Nova versão: ${{ steps.check_version.outputs.version }}" diff --git a/.github/workflows/sync-branches.yml b/.github/workflows/sync-branches.yml new file mode 100644 index 0000000..094ba0d --- /dev/null +++ b/.github/workflows/sync-branches.yml @@ -0,0 +1,54 @@ +name: Sincronizar Branches com Main + +on: + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: sync-branches + cancel-in-progress: false + +jobs: + sync: + name: Sincronizar branches com main + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout do repositório + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configurar Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Sincronizar branches com main + run: | + git fetch --all + + BRANCHES=$(git branch -r \ + | grep -v 'HEAD' \ + | grep -v 'origin/main' \ + | grep -v 'origin/copilot/' \ + | sed 's|origin/||' \ + | tr -d ' ') + + for branch in $BRANCHES; do + echo "▶ Sincronizando branch: $branch" + + git checkout -B "$branch" "origin/$branch" + + if git merge origin/main --no-edit -m "chore: sync with main"; then + git push origin "$branch" + echo "✅ Branch '$branch' sincronizada com sucesso" + else + echo "⚠️ Conflito ao sincronizar '$branch' com main — pulando" + git merge --abort + fi + done diff --git a/composer.json b/composer.json index 0289dbb..328cd17 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "ramon/backup", + "version": "2.0.14", "description": "All-in-one backup, export and import for Flarum, with optional asymmetric encryption.", "keywords": [ "flarum", diff --git a/src/Database/DatabaseDumper.php b/src/Database/DatabaseDumper.php index c21ae02..99a82ca 100644 --- a/src/Database/DatabaseDumper.php +++ b/src/Database/DatabaseDumper.php @@ -128,6 +128,7 @@ public function epilogue(): string */ public function dumpSchema(string $table): string { + $this->assertSafeIdent($table); $described = $this->describe($table); return $this->emitter->emitSchema($described); } @@ -143,6 +144,7 @@ public function dumpSchema(string $table): string */ public function dumpDataBatch(string $table, int $offset): array { + $this->assertSafeIdent($table); $described = $this->describe($table); // Stable-ordered SELECT so OFFSET is meaningful across ticks. @@ -172,6 +174,7 @@ public function dumpDataBatch(string $table, int $offset): array */ private function quoteIdentForRead(string $ident): string { + $this->assertSafeIdent($ident); $source = Dialect::detect($this->db); if ($source->usesBackticks()) { return '`' . str_replace('`', '``', $ident) . '`'; @@ -179,6 +182,20 @@ private function quoteIdentForRead(string $ident): string return '"' . str_replace('"', '""', $ident) . '"'; } + /** + * Reject identifiers that don't match a strict ASCII allowlist. + * Tables and primary-key columns reaching this point originate from + * schema introspection (`information_schema` / `sqlite_master`), so + * normal data never trips this — it's a hard stop for a corrupted + * catalog or a future code path that forwards request input. + */ + private function assertSafeIdent(string $ident): void + { + if (! preg_match('/^[A-Za-z0-9_]+$/', $ident)) { + throw new RuntimeException('Invalid input'); + } + } + private function buildOrderBy(Table $table): string { if (empty($table->primaryKey)) return ''; diff --git a/src/Database/Emitter/AbstractEmitter.php b/src/Database/Emitter/AbstractEmitter.php index b0b7f40..eec34d4 100644 --- a/src/Database/Emitter/AbstractEmitter.php +++ b/src/Database/Emitter/AbstractEmitter.php @@ -37,9 +37,17 @@ public function warnings(): array * built-in escape for both backticks and double-quotes is to * double the quote character — that's the SQL standard for `"` and * MySQL's documented escape for `` ` ``. + * + * Defense-in-depth: identifiers always come from schema introspection + * (never request input), but we still reject NUL and control bytes + * so a malformed catalog can't smuggle a statement terminator into + * the dump. */ protected function quoteIdent(string $ident): string { + if ($ident === '' || preg_match('/[\x00-\x1F\x7F]/', $ident)) { + throw new \InvalidArgumentException('Unsafe identifier rejected.'); + } $q = $this->identQuote(); return $q . str_replace($q, $q . $q, $ident) . $q; } From 95d66518b275e27572e81e1eb1f4f1ba993ad1e1 Mon Sep 17 00:00:00 2001 From: Ramon Guilherme Date: Wed, 13 May 2026 09:59:18 -0300 Subject: [PATCH 08/27] Refactor ImportModal and related components for consistency and readability - Standardized string quotes from single to double across ImportModal.tsx, index.tsx, api.ts, errorBoundary.tsx, and other files. - Improved formatting and indentation for better code clarity. - Updated error handling messages to ensure consistent usage of translation functions. - Removed unnecessary comments and streamlined code logic in various functions. - Enhanced the user interface by ensuring consistent alert messages and button labels. --- .github/workflows/ci.yml | 5 +- js/dist/admin.js | 2 +- js/dist/admin.js.map | 2 +- js/src/@types/shims.d.ts | 2 +- js/src/admin/components/BackupList.tsx | 58 ++-- js/src/admin/components/BackupPanel.tsx | 91 +++--- js/src/admin/components/ConfirmModal.tsx | 26 +- js/src/admin/components/EncryptionCard.tsx | 201 ++++++++----- js/src/admin/components/ExportModal.tsx | 284 ++++++++++++------- js/src/admin/components/ImportModal.tsx | 314 +++++++++++++-------- js/src/admin/index.tsx | 34 ++- js/src/admin/utils/api.ts | 22 +- js/src/admin/utils/errorBoundary.tsx | 20 +- 13 files changed, 659 insertions(+), 402 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7956ab8..cd9f674 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,10 @@ jobs: tools: composer:v2 - name: Validar composer.json - run: composer validate --strict --no-check-publish + # `--no-check-version` silencia o aviso "version field is present" — + # mantemos o campo de propósito (lido pelo EndBug/version-check no + # workflow de release). Todos os outros warnings continuam fatais. + run: composer validate --strict --no-check-publish --no-check-version - name: Lint PHP (php -l) run: | diff --git a/js/dist/admin.js b/js/dist/admin.js index ba8e911..9737175 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 x(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 v(t){try{return await s().request(t)}catch(e){const a=x(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:x});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,v({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=x(t)}).then(()=>{m.redraw()})}async generate(t){try{const e=await v({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"},x(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()(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 v({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"},x(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 v({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"},x(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 v({method:"POST",url:"".concat(k(),"/backup/exports/").concat(this.jobId,"/tick"),surface:!1});this.status=t,m.redraw()}catch(t){const e=x(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 v({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=x(t,String(F("upload_failed")))}finally{this.uploading=!1,m.redraw()}}}async uploadWithProgress(t,e){const s=await v({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),v({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:F("upload_failed")})}),i.addEventListener("abort",()=>{m(),r({detail: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]=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 v({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"},x(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 s().request({method:"POST",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id,"/tick")});this.status=t,m.redraw()}catch(t){console.error("[backup] import tick failed",t);const e=x(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 s().request({method:"DELETE",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id)})}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,v({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=x(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 v({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"},x(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={}})(); +(()=>{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 x(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 v(t){try{return await s().request(t)}catch(e){const a=x(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:x});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,v({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=x(t)}).then(()=>{m.redraw()})}async generate(t){try{const e=await v({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"},x(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 v({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"},x(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 v({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"},x(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 v({method:"POST",url:"".concat(k(),"/backup/exports/").concat(this.jobId,"/tick"),surface:!1});this.status=t,m.redraw()}catch(t){const e=x(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 v({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=x(t,String(F("upload_failed")))}finally{this.uploading=!1,m.redraw()}}}async uploadWithProgress(t,e){const s=await v({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),v({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 v({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"},x(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 s().request({method:"POST",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id,"/tick")});this.status=t,m.redraw()}catch(t){console.error("[backup] import tick failed",t);const e=x(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 s().request({method:"DELETE",url:"".concat(k(),"/backup/imports/").concat(this.inspect.job_id)})}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,v({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=x(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 v({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"},x(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={}})(); //# 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 982fe4d..6780448 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/**\r\n * Wrapper around `app.request` that:\r\n * - logs every failure to console.error with action + URL;\r\n * - extracts the JSON:API error detail when present;\r\n * - shows a Flarum alert (unless `surface: false`);\r\n * - rethrows so the caller can still branch on the failure.\r\n *\r\n * Centralised so we don't reinvent error extraction at every call-site.\r\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(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/**\r\n * Two-stage modal:\r\n *\r\n * 1. Form — admin picks what to include and whether to encrypt.\r\n * 2. Progress — chunked-tick polling drives a progress bar until the\r\n * server reports phase=done (or phase=error).\r\n *\r\n * The \"encryption to a foreign key\" path lets the operator paste a\r\n * public key from another Flarum install — useful when preparing an\r\n * archive for transfer to a different server whose keypair is not the\r\n * one in *this* config.php.\r\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/**\r\n * Fallback chunk size if /backup/imports init doesn't return one.\r\n * Server-recommended is 4 MB (see UploadImportController::RECOMMENDED_CHUNK_BYTES).\r\n */\nconst FALLBACK_CHUNK_BYTES = 4 * 1024 * 1024;\n\n/**\r\n * How many times to retry a single failed chunk before giving up on\r\n * the whole upload. Each retry uses the same offset so it overwrites\r\n * (idempotent). Two retries cover a transient hiccup without\r\n * spinning forever on a real outage.\r\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/**\r\n * Three-stage modal:\r\n * 1. upload — operator picks a `.flarum` file. We POST to /imports\r\n * and the server validates the header without\r\n * decrypting.\r\n * 2. configure — depending on the inspect result we ask for the\r\n * private key (only when the archive is encrypted)\r\n * AND a confirm-replace checkbox in all cases. The\r\n * user agreed in setup that we'd ask at this point.\r\n * 3. progress — chunked-tick polling drives a progress bar.\r\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 /**\r\n * Chunked upload + inspect.\r\n *\r\n * Single multipart POSTs of multi-GB archives reliably hit server\r\n * caps (`upload_max_filesize`, `post_max_size`, nginx\r\n * `client_max_body_size`, `memory_limit` during multipart parsing)\r\n * and surface as 500s. Instead we do three small requests:\r\n *\r\n * 1. POST /backup/imports — init, gets job_id + chunk_size\r\n * 2. POST /backup/imports/{id}/chunk* — append each slice (loop)\r\n * 3. POST /backup/imports/{id}/inspect — finalise, return meta\r\n *\r\n * Progress is computed from `bytesSent / file.size` across all chunk\r\n * requests so the bar advances smoothly through the entire file\r\n * even though each individual request only carries a few MB.\r\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 /**\r\n * Single-chunk upload as a raw octet-stream POST. We use XHR\r\n * (rather than fetch) so the per-chunk idle timeout works the\r\n * same way it did for the old monolithic upload, and so we can\r\n * pull a detailed error message off any non-2xx response body.\r\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: trans('upload_failed')\n });\n });\n xhr.addEventListener('abort', () => {\n stopIdleTimer();\n reject({\n detail: 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 name of extList) this.extensionsByName[name] = 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 /**\r\n * Replaces the progress UI as soon as `phase === 'done'`. Two\r\n * shapes:\r\n *\r\n * - DB restored — the admin's session was just wiped together\r\n * with the rest of the `users` / `sessions` tables. We make\r\n * this very clear and offer a single primary action: reload.\r\n * Anything else (refreshing the list, dismissing) would race\r\n * against an invalidated cookie and surface a confusing 401.\r\n *\r\n * - Files only — the session is fine, just close.\r\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 app.request({\n method: 'POST',\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id, \"/tick\")\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 app.request({\n method: 'DELETE',\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id)\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","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","Date","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\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 app.request({\n method: \"POST\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id, \"/tick\")\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 app.request({\n method: \"DELETE\",\n url: \"\".concat(apiUrl(), \"/backup/imports/\").concat(this.inspect.job_id)\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 diff --git a/js/src/@types/shims.d.ts b/js/src/@types/shims.d.ts index 47a60e1..8ad9253 100644 --- a/js/src/@types/shims.d.ts +++ b/js/src/@types/shims.d.ts @@ -1,6 +1,6 @@ // Allow .less imports to be referenced from TS without an explicit // type — the bundler stubs them out at build time. -declare module '*.less' { +declare module "*.less" { const content: string; export default content; } diff --git a/js/src/admin/components/BackupList.tsx b/js/src/admin/components/BackupList.tsx index 4b00328..8a2f8f0 100644 --- a/js/src/admin/components/BackupList.tsx +++ b/js/src/admin/components/BackupList.tsx @@ -1,10 +1,10 @@ -import app from 'flarum/admin/app'; -import Component, { ComponentAttrs } from 'flarum/common/Component'; -import Button from 'flarum/common/components/Button'; -import humanTime from 'flarum/common/helpers/humanTime'; -import type Mithril from 'mithril'; +import app from "flarum/admin/app"; +import Component, { ComponentAttrs } from "flarum/common/Component"; +import Button from "flarum/common/components/Button"; +import humanTime from "flarum/common/helpers/humanTime"; +import type Mithril from "mithril"; -import { apiUrl, fmtBytes } from '../utils/api'; +import { apiUrl, fmtBytes } from "../utils/api"; export interface BackupRow { id: number; @@ -26,10 +26,10 @@ export interface BackupRow { } const DIALECT_LABEL: Record = { - mysql: 'MySQL', - mariadb: 'MariaDB', - postgres: 'PostgreSQL', - sqlite: 'SQLite', + mysql: "MySQL", + mariadb: "MariaDB", + postgres: "PostgreSQL", + sqlite: "SQLite", }; export interface BackupListAttrs extends ComponentAttrs { @@ -46,17 +46,17 @@ export default class BackupList extends Component { const { backups, onDelete } = vnode.attrs; if (!backups.length) { - return

{trans('empty')}

; + return

{trans("empty")}

; } return (
- - - - + + + + @@ -65,7 +65,7 @@ export default class BackupList extends Component { @@ -103,14 +113,14 @@ export default class BackupList extends Component { className="Button Button--icon" href={`${apiUrl()}/backup/backups/${b.id}/download`} target="_blank" - title={String(trans('download_title'))} + title={String(trans("download_title"))} > - @@ -46,7 +56,7 @@ export default class BackupPanel extends Component {
-

{trans('panel.list_title')}

+

{trans("panel.list_title")}

{this.renderList()}
@@ -55,18 +65,22 @@ export default class BackupPanel extends Component { } renderList(): Mithril.Children { - if (this.listState === 'loading') return ; - if (this.listState === 'error') { + if (this.listState === "loading") return ; + if (this.listState === "error") { return (
-

{trans('list.load_failed')}

+

{trans("list.load_failed")}

{this.listError && (

{this.listError}

)} -
); @@ -81,20 +95,20 @@ export default class BackupPanel extends Component { } refresh(): Promise { - this.listState = 'loading'; + this.listState = "loading"; this.listError = null; return apiRequest<{ backups: BackupRow[] }>({ - method: 'GET', + method: "GET", url: `${apiUrl()}/backup/backups`, surface: false, }) .then((res) => { this.backups = res.backups || []; - this.listState = 'ok'; + this.listState = "ok"; }) .catch((e) => { this.backups = []; - this.listState = 'error'; + this.listState = "error"; this.listError = errorDetail(e); }) .then(() => { @@ -116,23 +130,26 @@ export default class BackupPanel extends Component { async delete(id: number) { const ok = await confirmAsync({ - title: trans('list.confirm_delete_title'), - body: trans('list.confirm_delete'), - confirmLabel: trans('list.delete_title'), + title: trans("list.confirm_delete_title"), + body: trans("list.confirm_delete"), + confirmLabel: trans("list.delete_title"), danger: true, }); if (!ok) return; try { await apiRequest({ - method: 'DELETE', + method: "DELETE", url: `${apiUrl()}/backup/backups/${id}`, surface: false, - fallbackMessage: String(trans('list.delete_failed')), + fallbackMessage: String(trans("list.delete_failed")), }); - app.alerts.show({ type: 'success' }, trans('list.deleted')); + app.alerts.show({ type: "success" }, trans("list.deleted")); this.refresh(); } catch (e) { - app.alerts.show({ type: 'error' }, errorDetail(e, String(trans('list.delete_failed')))); + app.alerts.show( + { type: "error" }, + errorDetail(e, String(trans("list.delete_failed"))) + ); } } } diff --git a/js/src/admin/components/ConfirmModal.tsx b/js/src/admin/components/ConfirmModal.tsx index e1561ac..8b17a1f 100644 --- a/js/src/admin/components/ConfirmModal.tsx +++ b/js/src/admin/components/ConfirmModal.tsx @@ -1,7 +1,7 @@ -import app from 'flarum/admin/app'; -import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal'; -import Button from 'flarum/common/components/Button'; -import type Mithril from 'mithril'; +import app from "flarum/admin/app"; +import Modal, { IInternalModalAttrs } from "flarum/common/components/Modal"; +import Button from "flarum/common/components/Button"; +import type Mithril from "mithril"; export interface ConfirmModalAttrs extends IInternalModalAttrs { title: Mithril.Children; @@ -26,7 +26,7 @@ export default class ConfirmModal extends Modal { protected resolved = false; className() { - return 'BackupConfirmModal Modal--small'; + return "BackupConfirmModal Modal--small"; } title() { @@ -35,16 +35,21 @@ export default class ConfirmModal extends Modal { content() { const confirmLabel = - this.attrs.confirmLabel ?? app.translator.trans('ramon-backup.admin.errors.confirm_default'); + this.attrs.confirmLabel ?? + app.translator.trans("ramon-backup.admin.errors.confirm_default"); const cancelLabel = - this.attrs.cancelLabel ?? app.translator.trans('ramon-backup.admin.errors.cancel_default'); + this.attrs.cancelLabel ?? + app.translator.trans("ramon-backup.admin.errors.cancel_default"); return (
{this.attrs.body}
-
@@ -70,7 +81,7 @@ class KeypairRevealModal extends Modal { copy(snippet: string) { if (!navigator.clipboard) { - app.alerts.show({ type: 'error' }, trans('clipboard_unavailable')); + app.alerts.show({ type: "error" }, trans("clipboard_unavailable")); return; } navigator.clipboard @@ -84,8 +95,8 @@ class KeypairRevealModal extends Modal { }, 2000); }) .catch((err) => { - console.error('[backup] clipboard writeText failed', err); - app.alerts.show({ type: 'error' }, trans('clipboard_failed')); + console.error("[backup] clipboard writeText failed", err); + app.alerts.show({ type: "error" }, trans("clipboard_failed")); }); } } @@ -99,18 +110,18 @@ class RegenerateConfirmModal extends Modal { protected submitting = false; className() { - return 'BackupRegenerateModal Modal--medium'; + return "BackupRegenerateModal Modal--medium"; } title() { - return trans('regenerate_modal.title'); + return trans("regenerate_modal.title"); } content() { return (
-

{trans('regenerate_modal.warning')}

+

{trans("regenerate_modal.warning")}

@@ -131,7 +142,7 @@ class RegenerateConfirmModal extends Modal { disabled={!this.acknowledged || this.submitting} onclick={() => this.submit()} > - {trans('regenerate_modal.submit')} + {trans("regenerate_modal.submit")}
@@ -155,7 +166,7 @@ class RegenerateConfirmModal extends Modal { export default class EncryptionCard extends Component { protected status: EncryptionStatus | null = null; - protected loadState: 'loading' | 'ok' | 'error' = 'loading'; + protected loadState: "loading" | "ok" | "error" = "loading"; protected loadError: string | null = null; protected publicCopied = false; @@ -168,76 +179,93 @@ export default class EncryptionCard extends Component { return (
-

{trans('section_title')}

-

{trans('section_help')}

+

{trans("section_title")}

+

{trans("section_help")}

- {this.loadState === 'loading' && } - {this.loadState === 'error' && ( + {this.loadState === "loading" && } + {this.loadState === "error" && (
-

{trans('status.load_failed')}

+

{trans("status.load_failed")}

{this.loadError && (

{this.loadError}

)} -
)} - {this.loadState === 'ok' && this.body()} + {this.loadState === "ok" && this.body()}
); } body() { - if (!this.status) return

{trans('status.unknown')}

; + if (!this.status) + return

{trans("status.unknown")}

; const s = this.status; if (!s.available) { - return
{trans('status.libsodium_missing')}
; + return ( +
+ {trans("status.libsodium_missing")} +
+ ); } return ( <>
- {this.statusBadge('public', s.has_public_key)} - {this.statusBadge('private', s.private_key_present)} + {this.statusBadge("public", s.has_public_key)} + {this.statusBadge("private", s.private_key_present)}
- {s.healthy &&
{trans('status.healthy')}
} + {s.healthy && ( +
{trans("status.healthy")}
+ )} {!s.has_public_key && !s.private_key_present && (
-

{trans('status.not_setup')}

-
)} - {s.has_public_key && s.private_key_present && s.keys_match === false && ( -
- {trans('status.mismatch_title')} -

{trans('status.mismatch_body')}

-

- '{s.config_key}' -

-
- )} + {s.has_public_key && + s.private_key_present && + s.keys_match === false && ( +
+ {trans("status.mismatch_title")} +

{trans("status.mismatch_body")}

+

+ '{s.config_key}' +

+
+ )} {s.has_public_key && !s.private_key_present && (
- {trans('status.private_missing_title')} -

{trans('status.private_missing_body')}

+ {trans("status.private_missing_title")} +

{trans("status.private_missing_body")}

'{s.config_key}'

)} - {s.has_public_key && this.publicKeyPanel(s.public_key || '', s.healthy)} + {s.has_public_key && this.publicKeyPanel(s.public_key || "", s.healthy)} ); } @@ -245,7 +273,7 @@ export default class EncryptionCard extends Component { publicKeyPanel(publicKey: string, healthy: boolean) { return (
- +
             {publicKey}
@@ -253,26 +281,40 @@ export default class EncryptionCard extends Component {
           
         
-

{healthy ? trans('public_key.help_healthy') : trans('public_key.help_broken')}

-
); } - statusBadge(kind: 'public' | 'private', present: boolean) { + statusBadge(kind: "public" | "private", present: boolean) { return ( -
- +
+ {trans(`status.${kind}_key_label`)} - {trans(`status.${present ? 'present' : 'absent'}`)} + + {trans(`status.${present ? "present" : "absent"}`)} +
); } @@ -280,7 +322,7 @@ export default class EncryptionCard extends Component { copyPublic(publicKey: string) { if (!publicKey) return; if (!navigator.clipboard) { - app.alerts.show({ type: 'error' }, trans('clipboard_unavailable')); + app.alerts.show({ type: "error" }, trans("clipboard_unavailable")); return; } navigator.clipboard @@ -294,26 +336,26 @@ export default class EncryptionCard extends Component { }, 2000); }) .catch((err) => { - console.error('[backup] clipboard writeText failed', err); - app.alerts.show({ type: 'error' }, trans('clipboard_failed')); + console.error("[backup] clipboard writeText failed", err); + app.alerts.show({ type: "error" }, trans("clipboard_failed")); }); } refresh(): Promise { - this.loadState = 'loading'; + this.loadState = "loading"; this.loadError = null; return apiRequest({ - method: 'GET', + method: "GET", url: `${apiUrl()}/backup/encryption/status`, surface: false, }) .then((res) => { this.status = res; - this.loadState = 'ok'; + this.loadState = "ok"; }) .catch((e) => { this.status = null; - this.loadState = 'error'; + this.loadState = "error"; this.loadError = errorDetail(e); }) .then(() => { @@ -323,8 +365,12 @@ export default class EncryptionCard extends Component { async generate(acknowledgeLoss: boolean) { try { - const res = await apiRequest<{ public_key: string; private_key: string; config_key: string }>({ - method: 'POST', + const res = await apiRequest<{ + public_key: string; + private_key: string; + config_key: string; + }>({ + method: "POST", url: `${apiUrl()}/backup/encryption/generate-keypair`, body: { acknowledge_loss: acknowledgeLoss }, surface: false, @@ -335,7 +381,10 @@ export default class EncryptionCard extends Component { configKey: res.config_key, }); } catch (e) { - app.alerts.show({ type: 'error' }, errorDetail(e, String(trans('actions.generate_failed')))); + app.alerts.show( + { type: "error" }, + errorDetail(e, String(trans("actions.generate_failed"))) + ); throw e; } } diff --git a/js/src/admin/components/ExportModal.tsx b/js/src/admin/components/ExportModal.tsx index 89704ee..a29d7ea 100644 --- a/js/src/admin/components/ExportModal.tsx +++ b/js/src/admin/components/ExportModal.tsx @@ -1,10 +1,10 @@ -import app from 'flarum/admin/app'; -import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal'; -import Button from 'flarum/common/components/Button'; -import LoadingIndicator from 'flarum/common/components/LoadingIndicator'; -import type Mithril from 'mithril'; +import app from "flarum/admin/app"; +import Modal, { IInternalModalAttrs } from "flarum/common/components/Modal"; +import Button from "flarum/common/components/Button"; +import LoadingIndicator from "flarum/common/components/LoadingIndicator"; +import type Mithril from "mithril"; -import { apiRequest, apiUrl, errorDetail, fmtBytes } from '../utils/api'; +import { apiRequest, apiUrl, errorDetail, fmtBytes } from "../utils/api"; export interface ExportModalAttrs extends IInternalModalAttrs { onComplete: () => void; @@ -15,14 +15,14 @@ interface ExtensionEntry { name: string; title: string; version: string; - location: 'workbench' | 'vendor' | 'unknown'; + location: "workbench" | "vendor" | "unknown"; path: string; relative: string; enabled: boolean; } interface ExportProgress { - phase: 'scan' | 'db_dump' | 'bundle' | 'finalize' | 'done' | 'error'; + phase: "scan" | "db_dump" | "bundle" | "finalize" | "done" | "error"; message: string; progress: { total_bytes: number; @@ -62,7 +62,7 @@ const trans = (key: string, params?: Record) => * one in *this* config.php. */ export default class ExportModal extends Modal { - protected stage: 'form' | 'progress' = 'form'; + protected stage: "form" | "progress" = "form"; protected includeDb = true; protected includeAssets = true; @@ -79,14 +79,15 @@ export default class ExportModal extends Modal { protected encryptionEnabled = false; protected encryptionUseExternal = false; - protected externalPublicKey = ''; + protected externalPublicKey = ""; // Target engine the dump should be generated for. Empty string = // "same as source" (the most common case — backing up to restore // onto the same install / a clone of it). The non-empty values // make this a cross-engine migration: e.g. dump from MySQL, // restore onto Postgres. - protected targetDialect: '' | 'mysql' | 'mariadb' | 'postgres' | 'sqlite' = ''; + protected targetDialect: "" | "mysql" | "mariadb" | "postgres" | "sqlite" = + ""; protected starting = false; protected jobId: string | null = null; @@ -94,15 +95,15 @@ export default class ExportModal extends Modal { protected polling = false; className() { - return 'BackupExportModal Modal--medium'; + return "BackupExportModal Modal--medium"; } title() { - return trans('title'); + return trans("title"); } content() { - if (this.stage === 'form') return this.formContent(); + if (this.stage === "form") return this.formContent(); return this.progressContent(); } @@ -111,47 +112,64 @@ export default class ExportModal extends Modal { formContent() { return (
-

{trans('intro')}

+

{trans("intro")}

- {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) => { - this.includeExtensions = v; - // Lazy-load the extension inventory the first time - // someone ticks the box. The list comes back fast (no - // disk walking — just metadata from the ExtensionManager). - if (v && !this.extensionsLoaded) this.loadExtensions(); - })} + {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) => { + this.includeExtensions = v; + // Lazy-load the extension inventory the first time + // someone ticks the box. The list comes back fast (no + // disk walking — just metadata from the ExtensionManager). + if (v && !this.extensionsLoaded) this.loadExtensions(); + } + )} {this.includeExtensions && this.extensionList()}
{this.includeDb && (
- {trans('target_title')} -

{trans('target_help')}

+ {trans("target_title")} +

{trans("target_help")}

)}
- {trans('encryption_title')} + {trans("encryption_title")} -

{trans('encryption_help')}

+

{trans("encryption_help")}

{this.encryptionEnabled && ( <> @@ -172,21 +190,27 @@ export default class ExportModal extends Modal { type="checkbox" checked={this.encryptionUseExternal} onchange={(e: Event) => { - this.encryptionUseExternal = (e.target as HTMLInputElement).checked; + this.encryptionUseExternal = ( + e.target as HTMLInputElement + ).checked; }} - />{' '} - {trans('encryption_external')} + />{" "} + {trans("encryption_external")} {this.encryptionUseExternal && ( <> -

{trans('encryption_external_help')}

+

+ {trans("encryption_external_help")} +

{trans('col_when')}{trans('col_size')}{trans('col_contents')}{trans('col_status')}{trans("col_when")}{trans("col_size")}{trans("col_contents")}{trans("col_status")}
- {b.created_at ? humanTime(b.created_at) : '—'} + {b.created_at ? humanTime(new Date(b.created_at)) : "—"}
{b.filename}
{b.target_dialect && ( @@ -74,27 +74,37 @@ export default class BackupList extends Component { // target_dialect and don't need the visual noise.
- {' '} - {trans('target_for', { engine: DIALECT_LABEL[b.target_dialect] || b.target_dialect })} + {" "} + {trans("target_for", { + engine: + DIALECT_LABEL[b.target_dialect] || b.target_dialect, + })}
)}
{fmtBytes(b.size_bytes)} {b.contents.map((c) => ( - {trans('content_' + c)} + + {trans("content_" + c)} + ))} {b.encrypted ? ( - {trans('encrypted')} + {trans("encrypted")} ) : ( - {trans('plain')} + {trans("plain")} )}