@trieb.work/payload-plugin-backup-mongodb 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -50
- package/dist/components/BackupDashboard/BackupListCollapsible.client.js.map +1 -1
- package/dist/components/BackupDashboard/BackupSettingsModal.client.js.map +1 -1
- package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js.map +1 -1
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.js.map +1 -1
- package/dist/components/BackupDashboard/TaskActionButton.client.js.map +1 -1
- package/dist/components/BackupDashboard/index.js.map +1 -1
- package/dist/core/archive.js.map +1 -1
- package/dist/core/backup.js.map +1 -1
- package/dist/core/backupBlobIO.js.map +1 -1
- package/dist/core/backupSettings.js.map +1 -1
- package/dist/core/blobTokenValidate.js.map +1 -1
- package/dist/core/restore.js.map +1 -1
- package/dist/core/restorePreview.js.map +1 -1
- package/dist/core/vercelBackupCron.js.map +1 -1
- package/dist/endpoints/paths/admin-manual.js.map +1 -1
- package/dist/endpoints/paths/admin-restore.js.map +1 -1
- package/dist/endpoints/paths/admin-settings.js.map +1 -1
- package/dist/endpoints/shared.js.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/utils/blobName.js.map +1 -1
- package/dist/utils/dashboardRoleAccess.js.map +1 -1
- package/dist/utils/dialogBackdrop.js.map +1 -1
- package/dist/utils/hostname.js.map +1 -1
- package/dist/utils/maskBlobToken.js.map +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,32 +1,67 @@
|
|
|
1
|
-
> Experimental. Originated in the
|
|
1
|
+
> Experimental. Originated in the
|
|
2
|
+
> [Payblocks](https://www.shadcnblocks.com/payload-cms) template, now maintained
|
|
3
|
+
> as a standalone package. Running in production at 20+ projects from TRWK
|
|
4
|
+
> agency. Feedback and PRs welcome.
|
|
2
5
|
|
|
3
6
|
# @trieb.work/payload-plugin-backup-mongodb
|
|
4
7
|
|
|
5
8
|
[](https://www.npmjs.com/package/@trieb.work/payload-plugin-backup-mongodb)
|
|
6
9
|
|
|
7
|
-
A **Payload CMS v3** plugin for **MongoDB only** — not Postgres, SQLite, or
|
|
10
|
+
A **Payload CMS v3** plugin for **MongoDB only** — not Postgres, SQLite, or
|
|
11
|
+
other database adapters. It handles Mongo + media backup, restore, and scheduled
|
|
12
|
+
retention with zero meta-database and a built-in admin UI. Backups live directly
|
|
13
|
+
in Vercel Blob Storage, so a fresh install can list and restore any prior backup
|
|
14
|
+
without bootstrapping a database first.
|
|
8
15
|
|
|
9
16
|

|
|
10
17
|
|
|
11
|
-
A first-class **Backups** section lives right below the Payload dashboard:
|
|
12
|
-
sorted by creation time, see at a glance which host and
|
|
13
|
-
|
|
18
|
+
A first-class **Backups** section lives right below the Payload dashboard:
|
|
19
|
+
browse every archive sorted by creation time, see at a glance which host and
|
|
20
|
+
database it belongs to, whether media is bundled and how big it is, then
|
|
21
|
+
download, restore or delete any backup with one click. Read more on how it
|
|
22
|
+
originated at
|
|
23
|
+
[TRWK> Case Study](https://trwk.de/case-studies/payload-plugin-mongodb-backup-restore).
|
|
14
24
|
|
|
15
25
|
---
|
|
16
26
|
|
|
17
27
|
## Highlights
|
|
18
28
|
|
|
19
|
-
- **No meta database.** Every backup is self-describing in its blob name
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
29
|
+
- **No meta database.** Every backup is self-describing in its blob name
|
|
30
|
+
(`type---db---host---{collectionCount}-{timestampMs}.{ext}`). A brand new
|
|
31
|
+
project can list and restore any backup straight from blob storage.
|
|
32
|
+
- **Scheduled (cron) and on-demand backups.** Wire
|
|
33
|
+
`/api/backup-mongodb/cron/run` to Vercel Cron or any HTTP scheduler. Retention
|
|
34
|
+
is configurable (N most recent cron backups are kept; older ones are pruned
|
|
35
|
+
automatically).
|
|
36
|
+
- **Full database coverage.** Dumps **every** MongoDB collection via the Payload
|
|
37
|
+
mongoose adapter — including hidden system collections like `users`,
|
|
38
|
+
`payload-preferences`, `payload-migrations`. Individual collections can be
|
|
39
|
+
excluded per backup from the UI or via API.
|
|
40
|
+
- **Optional media bundling.** Cron or manual backups can include Payload
|
|
41
|
+
`media` blobs in a `.tar.gz` archive alongside the MongoDB dump.
|
|
42
|
+
- **Restore with filters.** Restore any backup with an optional collection
|
|
43
|
+
blacklist and `mergeData` upsert mode. Partial restores keep the running admin
|
|
44
|
+
session / tasks collection intact.
|
|
45
|
+
- **Native admin dashboard.** Adds a `BackupDashboard` widget to the Payload
|
|
46
|
+
admin (`afterDashboard`): list / sort / filter / search backups, trigger
|
|
47
|
+
manual backups or restores, configure retention and storage, and live-poll
|
|
48
|
+
long-running tasks.
|
|
49
|
+
- **Payload REST, not Next.js route files.** All endpoints are registered as
|
|
50
|
+
Payload custom endpoints and served by the default `/api/[...slug]` handler —
|
|
51
|
+
you do not add `app/.../route.ts` files for this plugin.
|
|
52
|
+
- **Pluggable blob storage.** Uses `BLOB_READ_WRITE_TOKEN` by default (the same
|
|
53
|
+
store you often use with `@payloadcms/storage-vercel-blob`), or point backups
|
|
54
|
+
at a dedicated Vercel Blob store in the admin. Both **public** and **private**
|
|
55
|
+
access stores are supported; a validation step detects which modes the store
|
|
56
|
+
accepts.
|
|
57
|
+
- **Resumable long-running tasks.** Manual backups, restores, and seed runs are
|
|
58
|
+
tracked in a hidden `backup-tasks` collection (TTL, 30 min). The UI polls
|
|
59
|
+
progress with a short-lived `pollSecret` so long jobs stay observable even
|
|
60
|
+
across reloads.
|
|
61
|
+
- **Demo/seed support.** Optional `seedDemoDumpUrl` registers a one-click seed
|
|
62
|
+
endpoint for templates and starters.
|
|
63
|
+
- **Tested.** Vitest covers archive, backup, restore, task progress, blob I/O,
|
|
64
|
+
endpoint auth, cron parsing, and blob-name helpers.
|
|
30
65
|
|
|
31
66
|
---
|
|
32
67
|
|
|
@@ -35,7 +70,8 @@ bundled and how big it is, then download, restore or delete any backup with one
|
|
|
35
70
|
- Payload **v3+** (same major as your other `@payloadcms/*` packages).
|
|
36
71
|
- MongoDB with Payload’s mongoose adapter (`@payloadcms/db-mongodb`).
|
|
37
72
|
- MongoDB server (any version supported by that adapter).
|
|
38
|
-
- A Vercel Blob read/write token (`BLOB_READ_WRITE_TOKEN`). Vercel hosting is
|
|
73
|
+
- A Vercel Blob read/write token (`BLOB_READ_WRITE_TOKEN`). Vercel hosting is
|
|
74
|
+
**not** required — any Node runtime that can reach Vercel Blob works.
|
|
39
75
|
- Next.js **15+** and React **19+** (the usual Payload 3 + App Router stack).
|
|
40
76
|
|
|
41
77
|
### Environment variables
|
|
@@ -53,7 +89,8 @@ bundled and how big it is, then download, restore or delete any backup with one
|
|
|
53
89
|
|
|
54
90
|
## Installation and getting started
|
|
55
91
|
|
|
56
|
-
Use this flow when **adding the plugin to an existing Payload 3 + Next.js app**
|
|
92
|
+
Use this flow when **adding the plugin to an existing Payload 3 + Next.js app**
|
|
93
|
+
that already has MongoDB and (typically) Vercel Blob configured.
|
|
57
94
|
|
|
58
95
|
### 1. Add the package
|
|
59
96
|
|
|
@@ -63,7 +100,9 @@ pnpm add @trieb.work/payload-plugin-backup-mongodb
|
|
|
63
100
|
# yarn add @trieb.work/payload-plugin-backup-mongodb
|
|
64
101
|
```
|
|
65
102
|
|
|
66
|
-
Peer dependencies: **`@payloadcms/db-mongodb`**, **`payload`**,
|
|
103
|
+
Peer dependencies: **`@payloadcms/db-mongodb`**, **`payload`**,
|
|
104
|
+
**`@payloadcms/ui`**, **`next`**, and **`react`**. (all expected in a Payload
|
|
105
|
+
3 + MongoDB + Next.js app).
|
|
67
106
|
|
|
68
107
|
### 2. Register the plugin
|
|
69
108
|
|
|
@@ -82,7 +121,10 @@ export default buildConfig({
|
|
|
82
121
|
})
|
|
83
122
|
```
|
|
84
123
|
|
|
85
|
-
The plugin will register the backup collections, mount the dashboard, and add
|
|
124
|
+
The plugin will register the backup collections, mount the dashboard, and add
|
|
125
|
+
`/api/backup-mongodb/*` endpoints. Import paths resolve to a **published or
|
|
126
|
+
linked** `dist/` build; after local changes to the package, run `pnpm build` in
|
|
127
|
+
the plugin repo (or reinstall) so `dist/` is up to date.
|
|
86
128
|
|
|
87
129
|
### 3. Regenerate types and the import map
|
|
88
130
|
|
|
@@ -95,7 +137,8 @@ pnpm payload generate:importmap
|
|
|
95
137
|
|
|
96
138
|
### 4. (Optional) Schedule cron backups
|
|
97
139
|
|
|
98
|
-
**Vercel:** add a cron in `vercel.json`. Path must match your API; default is
|
|
140
|
+
**Vercel:** add a cron in `vercel.json`. Path must match your API; default is
|
|
141
|
+
under `/api/…`:
|
|
99
142
|
|
|
100
143
|
```json
|
|
101
144
|
{
|
|
@@ -108,7 +151,9 @@ pnpm payload generate:importmap
|
|
|
108
151
|
}
|
|
109
152
|
```
|
|
110
153
|
|
|
111
|
-
Vercel Cron sends header `Authorization: Bearer $CRON_SECRET`. **Other hosts**
|
|
154
|
+
Vercel Cron sends header `Authorization: Bearer $CRON_SECRET`. **Other hosts**
|
|
155
|
+
(k8s, GitHub Actions, etc.): `GET` the same URL on your schedule with that same
|
|
156
|
+
authorization header.
|
|
112
157
|
|
|
113
158
|
### Checklist
|
|
114
159
|
|
|
@@ -116,33 +161,49 @@ Vercel Cron sends header `Authorization: Bearer $CRON_SECRET`. **Other hosts** (
|
|
|
116
161
|
- [ ] `backupMongodbPlugin` in `plugins` array
|
|
117
162
|
- [ ] `generate:types` and `generate:importmap` run
|
|
118
163
|
- [ ] `MONGODB_URI` and `BLOB_READ_WRITE_TOKEN` set in the environment
|
|
119
|
-
- [ ] (Optional) `CRON_SECRET` and cron job (vercel.json) if you want scheduled
|
|
164
|
+
- [ ] (Optional) `CRON_SECRET` and cron job (vercel.json) if you want scheduled
|
|
165
|
+
backups
|
|
120
166
|
|
|
121
|
-
Start your dev server, open `/admin`, sign in as an admin, and you should see
|
|
167
|
+
Start your dev server, open `/admin`, sign in as an admin, and you should see
|
|
168
|
+
**Backups** below the dashboard.
|
|
122
169
|
|
|
123
170
|
---
|
|
124
171
|
|
|
125
172
|
## Admin UI
|
|
126
173
|
|
|
127
|
-
After the first login as an admin user, a **Backups** section appears below the
|
|
128
|
-
dashboard. Day-to-day tasks — create, download, restore, delete,
|
|
174
|
+
After the first login as an admin user, a **Backups** section appears below the
|
|
175
|
+
default Payload dashboard. Day-to-day tasks — create, download, restore, delete,
|
|
176
|
+
schedule — all happen in the UI below.
|
|
129
177
|
|
|
130
178
|
**Who can see it?** The visibility rules, in precedence order:
|
|
131
179
|
|
|
132
|
-
1. If the plugin is registered with an `access` function, it wins (see
|
|
133
|
-
|
|
134
|
-
|
|
180
|
+
1. If the plugin is registered with an `access` function, it wins (see
|
|
181
|
+
[Plugin options](#plugin-options)).
|
|
182
|
+
2. Otherwise, if `PAYLOAD_BACKUP_ALLOWED_ROLES` is set, it is used as a
|
|
183
|
+
comma-separated allow-list of role slugs (case-insensitive). Use `*` for "any
|
|
184
|
+
authenticated user". Example:
|
|
185
|
+
`PAYLOAD_BACKUP_ALLOWED_ROLES=admin,superadmin`.
|
|
186
|
+
3. Otherwise, the historical default applies: users with a `role` whose slug is
|
|
187
|
+
`admin` see the dashboard; projects that don't use a `roles` field at all get
|
|
188
|
+
the dashboard for any authenticated user.
|
|
135
189
|
|
|
136
190
|
### Scheduled (cron) vs on-demand (manual) backups
|
|
137
191
|
|
|
138
|
-
- **Scheduled / cron** backups follow shared settings: retention, media toggle,
|
|
139
|
-
|
|
192
|
+
- **Scheduled / cron** backups follow shared settings: retention, media toggle,
|
|
193
|
+
and collections to skip. Older cron archives are pruned when the keep-count is
|
|
194
|
+
exceeded. They appear in the list as **CRON BACKUP** (or similar).
|
|
195
|
+
- **Manual / on-demand** backups are configured **per run** — per-collection
|
|
196
|
+
toggles, optional media in a `.tar.gz` archive. Manual backups are not
|
|
197
|
+
auto-pruned; use them as checkpoints before migrations.
|
|
140
198
|
|
|
141
199
|

|
|
142
200
|
|
|
143
201
|
### Selective restore with per-collection preview
|
|
144
202
|
|
|
145
|
-
**Restore** opens a preview of the archive before any write. You can restore
|
|
203
|
+
**Restore** opens a preview of the archive before any write. You can restore
|
|
204
|
+
everything, cherry-pick collections, or skip media from `.tar.gz` when you only
|
|
205
|
+
need database rows. Restoring from a different host or database is supported;
|
|
206
|
+
filters help when cloning production into staging.
|
|
146
207
|
|
|
147
208
|

|
|
148
209
|
|
|
@@ -152,10 +213,16 @@ Retention, storage token, and cron collection skip list live in one modal:
|
|
|
152
213
|
|
|
153
214
|

|
|
154
215
|
|
|
155
|
-
- **Schedule:** when `vercel.json` is present, the plugin can show a
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
- **
|
|
216
|
+
- **Schedule:** when `vercel.json` is present, the plugin can show a
|
|
217
|
+
human-readable cron description (via
|
|
218
|
+
[cronstrue](https://github.com/bradymholt/cronstrue)).
|
|
219
|
+
- **Retention:** how many **cron** archives to keep; manual backups are not
|
|
220
|
+
pruned by this.
|
|
221
|
+
- **Dedicated backup storage (optional):** a separate Vercel Blob read/write
|
|
222
|
+
token so backups can live in a different store than media. The UI can validate
|
|
223
|
+
the token and optionally copy existing `backups/*` objects to the new store.
|
|
224
|
+
- **Collection selection for cron:** defaults to all collections, with
|
|
225
|
+
per-collection opt-out.
|
|
159
226
|
|
|
160
227
|
---
|
|
161
228
|
|
|
@@ -208,7 +275,10 @@ backupMongodbPlugin({
|
|
|
208
275
|
|
|
209
276
|
## HTTP API (Payload REST)
|
|
210
277
|
|
|
211
|
-
All routes are served by Payload’s `/api/[...slug]` handler under
|
|
278
|
+
All routes are served by Payload’s `/api/[...slug]` handler under
|
|
279
|
+
**`/api/backup-mongodb/…`**. The `backup-mongodb` prefix avoids clashing with a
|
|
280
|
+
collection slug. The admin UI uses the same URLs; use the exported
|
|
281
|
+
`backupPluginPublicApiPaths` helper in client code.
|
|
212
282
|
|
|
213
283
|
### Cron / external (Bearer `CRON_SECRET`)
|
|
214
284
|
|
|
@@ -241,7 +311,11 @@ Authorization: Bearer <CRON_SECRET>
|
|
|
241
311
|
For scripts, hooks, and tests:
|
|
242
312
|
|
|
243
313
|
```ts
|
|
244
|
-
import {
|
|
314
|
+
import {
|
|
315
|
+
createBackup,
|
|
316
|
+
listBackups,
|
|
317
|
+
restoreBackup,
|
|
318
|
+
} from '@trieb.work/payload-plugin-backup-mongodb'
|
|
245
319
|
|
|
246
320
|
// Example: manual backup from a one-off script
|
|
247
321
|
await createBackup(payload, { cron: false, includeMedia: true })
|
|
@@ -266,16 +340,23 @@ backups/{type}---{dbName}---{hostname}---{collectionCount}-{timestampMs}.{json|t
|
|
|
266
340
|
```
|
|
267
341
|
|
|
268
342
|
- `type`: `cron` or `manual` (and legacy-style labels where applicable).
|
|
269
|
-
- `dbName` / `hostname`: derived from your MongoDB URL and public server URL;
|
|
270
|
-
-
|
|
343
|
+
- `dbName` / `hostname`: derived from your MongoDB URL and public server URL;
|
|
344
|
+
URL-encoded in the name.
|
|
345
|
+
- `collectionCount` + `timestampMs`: sort key for listing; legacy blob names are
|
|
346
|
+
still supported.
|
|
271
347
|
|
|
272
348
|
### Public vs private stores
|
|
273
349
|
|
|
274
|
-
A validation step probes the bucket and records whether the store behaves as
|
|
350
|
+
A validation step probes the bucket and records whether the store behaves as
|
|
351
|
+
public or private. New uploads use the right access mode. Restore and preview
|
|
352
|
+
flows handle both.
|
|
275
353
|
|
|
276
354
|
### Overriding the backup store
|
|
277
355
|
|
|
278
|
-
To keep backups in a **different** Vercel Blob project than media, open **Backup
|
|
356
|
+
To keep backups in a **different** Vercel Blob project than media, open **Backup
|
|
357
|
+
settings** in the admin, paste a dedicated `BLOB_READ_WRITE_TOKEN`, validate,
|
|
358
|
+
and optionally migrate existing `backups/*` objects to the new store before
|
|
359
|
+
switching.
|
|
279
360
|
|
|
280
361
|
---
|
|
281
362
|
|
|
@@ -287,29 +368,49 @@ pnpm test:int
|
|
|
287
368
|
|
|
288
369
|
### E2E (Playwright)
|
|
289
370
|
|
|
290
|
-
End-to-end tests live in `tests/e2e/` and target the `dev/` Next + Payload app.
|
|
371
|
+
End-to-end tests live in `tests/e2e/` and target the `dev/` Next + Payload app.
|
|
372
|
+
**Locally**, start Mongo (or rely on the in-memory replica set when
|
|
373
|
+
`DATABASE_URL` / `MONGODB_URI` are unset), then either run `pnpm dev` and
|
|
374
|
+
`pnpm test:e2e` in another shell (Playwright reuses the server when not in
|
|
375
|
+
`CI`), or run only `pnpm test:e2e` so Playwright starts `pnpm dev` for you.
|
|
291
376
|
|
|
292
|
-
**CI** (`.github/workflows/e2e.yml`, same pattern as the reference `e2e.yml` in
|
|
377
|
+
**CI** (`.github/workflows/e2e.yml`, same pattern as the reference `e2e.yml` in
|
|
378
|
+
executive-search: MongoDB 7 service, `pnpm run build` then `pnpm run build:dev`,
|
|
379
|
+
then Playwright against `next start`). Optional secret
|
|
380
|
+
**`BLOB_READ_WRITE_TOKEN`** for fuller blob behaviour.
|
|
293
381
|
|
|
294
382
|
### Optional: deploy `dev/` to Vercel
|
|
295
383
|
|
|
296
|
-
`.github/workflows/deploy-dev-vercel.yml` runs when `dev/` (or related paths)
|
|
384
|
+
`.github/workflows/deploy-dev-vercel.yml` runs when `dev/` (or related paths)
|
|
385
|
+
change on `main`, or via **workflow_dispatch**, **only if** `VERCEL_TOKEN`,
|
|
386
|
+
`VERCEL_ORG_ID`, and `VERCEL_PROJECT_ID` are set. Configure the Vercel project
|
|
387
|
+
**Root Directory** to `dev` (`dev/vercel.json` wires install/build from the repo
|
|
388
|
+
root).
|
|
297
389
|
|
|
298
|
-
The repo includes a `dev/` Payload + Next app (MongoDB Memory Server when no URI
|
|
390
|
+
The repo includes a `dev/` Payload + Next app (MongoDB Memory Server when no URI
|
|
391
|
+
is set): `pnpm dev` → `http://localhost:3000/admin` (login in
|
|
392
|
+
`dev/helpers/credentials.ts`). In unit tests, external services like
|
|
393
|
+
`@vercel/blob` and `bson` are often mocked with `vi.mock()`.
|
|
299
394
|
|
|
300
395
|
---
|
|
301
396
|
|
|
302
397
|
## Publishing (npm)
|
|
303
398
|
|
|
304
|
-
Versioning uses [Changesets](https://github.com/changesets/changesets): add a
|
|
399
|
+
Versioning uses [Changesets](https://github.com/changesets/changesets): add a
|
|
400
|
+
file with `pnpm changeset`, open a PR to `main`, and merge. The **Release**
|
|
401
|
+
workflow opens a “version packages” PR or runs `pnpm run release` (`build` +
|
|
402
|
+
`changeset publish`) when the set of changesets is ready. PRs need a new
|
|
403
|
+
`.changeset/*.md` unless you add the **`no-changeset`** label (e.g. docs-only).
|
|
305
404
|
|
|
306
405
|
---
|
|
307
406
|
|
|
308
407
|
## Roadmap / ideas
|
|
309
408
|
|
|
310
|
-
- Multipart / very large backup uploads (Vercel Blob supports large objects; may
|
|
409
|
+
- Multipart / very large backup uploads (Vercel Blob supports large objects; may
|
|
410
|
+
need to switch past single-part limits).
|
|
311
411
|
- Additional storage adapters (S3, R2, filesystem, etc.).
|
|
312
|
-
- Scheduler-agnostic display when not using Vercel (`vercel.json` is currently
|
|
412
|
+
- Scheduler-agnostic display when not using Vercel (`vercel.json` is currently
|
|
413
|
+
used for the schedule summary where available).
|
|
313
414
|
- Streaming restore for very large databases.
|
|
314
415
|
- Configurable `backups/` prefix or bucket layout.
|
|
315
416
|
- add support for tenant plugin and partial backups per tenant
|
|
@@ -322,4 +423,5 @@ MIT
|
|
|
322
423
|
|
|
323
424
|
---
|
|
324
425
|
|
|
325
|
-
Built and maintained by [TRWK>](https://trwk.de), formerly
|
|
426
|
+
Built and maintained by [TRWK>](https://trwk.de), formerly
|
|
427
|
+
[trieb.work](https://trieb.work).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/BackupDashboard/BackupListCollapsible.client.tsx"],"sourcesContent":["'use client'\n\nimport { Button, Collapsible } from '@payloadcms/ui'\nimport { useCallback, useId, useMemo, useRef, useState } from 'react'\n\nimport type { TransformBlobNameResult } from '../../utils/index'\n\nimport { closeNativeDialogOnBackdropPointer } from '../../utils/dialogBackdrop'\nimport { formatBytes, getBackupSortTimeMs, transformBlobName } from '../../utils/index'\nimport { BackupItemActions } from './BackupItemActions.client'\n\nexport interface SerializableBackupBlob {\n downloadUrl: string\n pathname: string\n size: number\n uploadedAt: string\n url: string\n}\n\nexport interface BackupListCollapsibleProps {\n blobs: SerializableBackupBlob[]\n countOtherDb: number\n countOtherHostname: number\n currentDbName: string\n currentHostname: string\n i18nLanguage: string\n}\n\ninterface BackupListFilterState {\n dateFrom: string\n dateTo: string\n label: string\n media: 'all' | 'with' | 'without'\n showOtherDb: boolean\n showOtherHostname: boolean\n source: 'all' | 'cron' | 'manual'\n}\n\nconst defaultFilterState: BackupListFilterState = {\n dateFrom: '',\n dateTo: '',\n label: '',\n media: 'all',\n showOtherDb: true,\n showOtherHostname: false,\n source: 'all',\n}\n\nfunction parseDateInputStartMs(isoDate: string): null | number {\n const parts = isoDate.split('-').map(Number)\n if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {\n return null\n }\n const [y, m, d] = parts\n const t = new Date(y, m - 1, d, 0, 0, 0, 0).getTime()\n return Number.isFinite(t) ? t : null\n}\n\nfunction parseDateInputEndMs(isoDate: string): null | number {\n const parts = isoDate.split('-').map(Number)\n if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {\n return null\n }\n const [y, m, d] = parts\n const t = new Date(y, m - 1, d, 23, 59, 59, 999).getTime()\n return Number.isFinite(t) ? t : null\n}\n\nfunction passesDateMediaSource(\n parsed: TransformBlobNameResult,\n displayMs: number,\n filters: BackupListFilterState,\n): boolean {\n if (filters.dateFrom) {\n const from = parseDateInputStartMs(filters.dateFrom)\n if (from != null && displayMs < from) {\n return false\n }\n }\n if (filters.dateTo) {\n const to = parseDateInputEndMs(filters.dateTo)\n if (to != null && displayMs > to) {\n return false\n }\n }\n if (filters.media === 'with' && parsed.fileType !== 'tar.gz') {\n return false\n }\n if (filters.media === 'without' && parsed.fileType !== 'json') {\n return false\n }\n if (filters.source === 'manual' && parsed.type !== 'manual') {\n return false\n }\n if (filters.source === 'cron' && parsed.type !== 'cron') {\n return false\n }\n const labelQuery = filters.label.trim().toLowerCase()\n if (labelQuery) {\n const blobLabel = (parsed.label ?? '').toLowerCase()\n if (!blobLabel.includes(labelQuery)) {\n return false\n }\n }\n return true\n}\n\nfunction hasNonDefaultFilters(f: BackupListFilterState): boolean {\n return (\n f.dateFrom !== defaultFilterState.dateFrom ||\n f.dateTo !== defaultFilterState.dateTo ||\n f.label !== defaultFilterState.label ||\n f.media !== defaultFilterState.media ||\n f.source !== defaultFilterState.source ||\n f.showOtherDb !== defaultFilterState.showOtherDb ||\n f.showOtherHostname !== defaultFilterState.showOtherHostname\n )\n}\n\nexport const BackupListCollapsible: React.FC<BackupListCollapsibleProps> = ({\n blobs,\n countOtherDb,\n countOtherHostname,\n currentDbName,\n currentHostname,\n i18nLanguage,\n}) => {\n const uid = useId()\n const idDb = `${uid}-show-other-db`\n const idHost = `${uid}-show-other-host`\n const dialogRef = useRef<HTMLDialogElement>(null)\n const [filters, setFilters] = useState<BackupListFilterState>(defaultFilterState)\n\n const visibleRows = useMemo(() => {\n const rows: Array<{\n blob: SerializableBackupBlob\n displayAt: Date\n displayMs: number\n parsed: TransformBlobNameResult\n }> = []\n\n for (const blob of blobs) {\n const parsed = transformBlobName(blob.pathname)\n const { dbName, hostname } = parsed\n const isCurrentDb = currentDbName === dbName\n const isCurrentHostname = currentHostname === hostname\n if (!(filters.showOtherDb || isCurrentDb)) {\n continue\n }\n if (!(filters.showOtherHostname || isCurrentHostname)) {\n continue\n }\n\n const uploadedAt = new Date(blob.uploadedAt)\n const displayMs = getBackupSortTimeMs(parsed, uploadedAt)\n if (!passesDateMediaSource(parsed, displayMs, filters)) {\n continue\n }\n\n rows.push({\n blob,\n displayAt: new Date(displayMs),\n displayMs,\n parsed,\n })\n }\n\n return rows\n }, [blobs, currentDbName, currentHostname, filters])\n\n const openFilterDialog = useCallback(() => {\n dialogRef.current?.showModal()\n }, [])\n\n const closeFilterDialog = useCallback(() => {\n dialogRef.current?.close()\n }, [])\n\n const clearFilters = useCallback(() => {\n setFilters(defaultFilterState)\n }, [])\n\n const hasScopeOptions = countOtherDb > 0 || countOtherHostname > 0\n const filtersActive = hasNonDefaultFilters(filters)\n\n const filtersButton = (\n <Button\n buttonStyle=\"secondary\"\n className={\n filtersActive\n ? 'backup-dashboard__filter-btn backup-dashboard__filter-btn--active'\n : 'backup-dashboard__filter-btn'\n }\n onClick={openFilterDialog}\n type=\"button\"\n >\n Filters\n </Button>\n )\n\n return (\n <>\n <Collapsible\n header={<span className=\"backup-dashboard__collapsible-title\">Backup list</span>}\n initCollapsed={true}\n >\n <div className=\"backup-dashboard__backup-list-toolbar\">{filtersButton}</div>\n {blobs.length === 0 ? (\n <p className=\"backup-dashboard__list-empty\">No backups yet</p>\n ) : visibleRows.length === 0 ? (\n <p className=\"backup-dashboard__list-empty\">No backups match the current filters</p>\n ) : (\n visibleRows.map(({ blob, displayAt, parsed }) => {\n const { type, collectionCount, dbName, fileType, hostname, label } = parsed\n const isCurrentDb = currentDbName === dbName\n const isCurrentHostname = currentHostname === hostname\n\n return (\n <div className=\"backup-item\" key={blob.pathname}>\n <div className=\"backup-item__main\">\n <div className=\"backup-item__primary\">\n <time\n className=\"backup-item__time\"\n dateTime={displayAt.toISOString()}\n suppressHydrationWarning\n >\n {displayAt.toLocaleString(i18nLanguage)}\n </time>\n <span\n className={`backup-item__pill backup-item__pill--kind backup-item__pill--${type === 'cron' ? 'cron' : 'manual'}`}\n >\n {type === 'cron' ? 'Cron backup' : 'Manual backup'}\n </span>\n {label ? (\n <span\n className=\"backup-item__pill backup-item__pill--label\"\n title={`Label: ${label}`}\n >\n <span className=\"backup-item__pill-key\">Label</span>\n {label}\n </span>\n ) : null}\n </div>\n <div className=\"backup-item__meta\">\n <span\n className={`backup-item__pill backup-item__pill--db${isCurrentDb ? '' : ' backup-item__pill--warn'}`}\n >\n <span className=\"backup-item__pill-key\">\n {isCurrentDb ? 'DB' : 'other DB'}\n </span>\n {dbName || 'Unknown'}\n </span>\n <span\n className={`backup-item__pill backup-item__pill--host${isCurrentHostname ? '' : ' backup-item__pill--warn'}`}\n >\n <span className=\"backup-item__pill-key\">Host</span>\n {hostname || 'Unknown'}\n </span>\n {typeof collectionCount === 'number' ? (\n <span className=\"backup-item__pill backup-item__pill--collections\">\n <span className=\"backup-item__pill-key\">Collections</span>\n {collectionCount}\n </span>\n ) : null}\n {fileType === 'tar.gz' ? (\n <span className=\"backup-item__pill backup-item__pill--media\">With media</span>\n ) : fileType === 'json' ? (\n <span className=\"backup-item__pill backup-item__pill--media\">\n Without media\n </span>\n ) : null}\n <span\n className=\"backup-item__pill backup-item__pill--size\"\n title={`${blob.size.toLocaleString(i18nLanguage)} bytes`}\n >\n <span className=\"backup-item__pill-key\">Size</span>\n <span className=\"backup-item__size-value\">{formatBytes(blob.size)}</span>\n </span>\n </div>\n </div>\n <BackupItemActions\n downloadUrl={blob.downloadUrl}\n pathname={blob.pathname}\n url={blob.url}\n />\n </div>\n )\n })\n )}\n </Collapsible>\n\n <dialog\n className=\"backup-confirm-dialog backup-confirm-dialog--backup-list-filters\"\n onPointerDown={(e) => closeNativeDialogOnBackdropPointer(e, dialogRef)}\n ref={dialogRef}\n >\n <h3 className=\"backup-confirm-dialog__title\">Filter backups</h3>\n <p className=\"backup-confirm-dialog__subtitle\">\n All filters apply in the browser only. Current DB and hostname are always shown; enable\n the options below to include backups from other databases or hosts.\n </p>\n <div className=\"backup-confirm-dialog__body backup-list-filters__body\">\n {hasScopeOptions ? (\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Scope</legend>\n {countOtherDb > 0 ? (\n <div className=\"field-type checkbox backup-dashboard__collapsible-checkbox backup-list-filters__scope-row\">\n <input\n aria-label={`Show other databases (${countOtherDb} in list)`}\n checked={filters.showOtherDb}\n className=\"checkbox-input__input\"\n id={idDb}\n onChange={(e) => setFilters((f) => ({ ...f, showOtherDb: e.target.checked }))}\n type=\"checkbox\"\n />\n <label className=\"field-label\" htmlFor={idDb}>\n Show other DBs\n <span className=\"backup-list-filters__hint\"> ({countOtherDb} in list)</span>\n </label>\n </div>\n ) : null}\n {countOtherHostname > 0 ? (\n <div className=\"field-type checkbox backup-dashboard__collapsible-checkbox backup-list-filters__scope-row\">\n <input\n aria-label={`Show other hostnames (${countOtherHostname} in list)`}\n checked={filters.showOtherHostname}\n className=\"checkbox-input__input\"\n id={idHost}\n onChange={(e) =>\n setFilters((f) => ({ ...f, showOtherHostname: e.target.checked }))\n }\n type=\"checkbox\"\n />\n <label className=\"field-label\" htmlFor={idHost}>\n Show other Hostnames\n <span className=\"backup-list-filters__hint\">\n {' '}\n ({countOtherHostname} in list)\n </span>\n </label>\n </div>\n ) : null}\n </fieldset>\n ) : null}\n\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Label</legend>\n <div className=\"backup-list-filters__row backup-list-filters__row--label\">\n <label className=\"backup-list-filters__label\" htmlFor={`${uid}-label`}>\n Search\n </label>\n <input\n aria-label=\"Filter backups by label text\"\n autoComplete=\"off\"\n className=\"backup-list-filters__text\"\n id={`${uid}-label`}\n onChange={(e) => setFilters((f) => ({ ...f, label: e.target.value }))}\n placeholder=\"Filter by label text\"\n spellCheck={false}\n type=\"text\"\n value={filters.label}\n />\n </div>\n </fieldset>\n\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Date range</legend>\n <div className=\"backup-list-filters__row\">\n <label className=\"backup-list-filters__label\" htmlFor={`${uid}-from`}>\n From\n </label>\n <input\n aria-label=\"Filter from date\"\n className=\"backup-list-filters__date\"\n id={`${uid}-from`}\n onChange={(e) => setFilters((f) => ({ ...f, dateFrom: e.target.value }))}\n type=\"date\"\n value={filters.dateFrom}\n />\n </div>\n <div className=\"backup-list-filters__row\">\n <label className=\"backup-list-filters__label\" htmlFor={`${uid}-to`}>\n To\n </label>\n <input\n aria-label=\"Filter to date\"\n className=\"backup-list-filters__date\"\n id={`${uid}-to`}\n onChange={(e) => setFilters((f) => ({ ...f, dateTo: e.target.value }))}\n type=\"date\"\n value={filters.dateTo}\n />\n </div>\n </fieldset>\n\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Media</legend>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Media filter: all backups\"\n checked={filters.media === 'all'}\n className=\"radio-input__input\"\n id={`${uid}-media-all`}\n name={`${uid}-media`}\n onChange={() => setFilters((f) => ({ ...f, media: 'all' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-media-all`}>\n All\n </label>\n </div>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Media filter: with media\"\n checked={filters.media === 'with'}\n className=\"radio-input__input\"\n id={`${uid}-media-with`}\n name={`${uid}-media`}\n onChange={() => setFilters((f) => ({ ...f, media: 'with' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-media-with`}>\n With media\n </label>\n </div>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Media filter: without media\"\n checked={filters.media === 'without'}\n className=\"radio-input__input\"\n id={`${uid}-media-without`}\n name={`${uid}-media`}\n onChange={() => setFilters((f) => ({ ...f, media: 'without' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-media-without`}>\n Without media\n </label>\n </div>\n </fieldset>\n\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Source</legend>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Source filter: all\"\n checked={filters.source === 'all'}\n className=\"radio-input__input\"\n id={`${uid}-source-all`}\n name={`${uid}-source`}\n onChange={() => setFilters((f) => ({ ...f, source: 'all' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-source-all`}>\n All\n </label>\n </div>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Source filter: manual backups\"\n checked={filters.source === 'manual'}\n className=\"radio-input__input\"\n id={`${uid}-source-manual`}\n name={`${uid}-source`}\n onChange={() => setFilters((f) => ({ ...f, source: 'manual' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-source-manual`}>\n Manual\n </label>\n </div>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Source filter: cron backups\"\n checked={filters.source === 'cron'}\n className=\"radio-input__input\"\n id={`${uid}-source-cron`}\n name={`${uid}-source`}\n onChange={() => setFilters((f) => ({ ...f, source: 'cron' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-source-cron`}>\n Cron\n </label>\n </div>\n </fieldset>\n </div>\n <div className=\"backup-confirm-dialog__actions\">\n <Button buttonStyle=\"secondary\" onClick={clearFilters} type=\"button\">\n Clear filters\n </Button>\n <Button onClick={closeFilterDialog} type=\"button\">\n Done\n </Button>\n </div>\n </dialog>\n </>\n )\n}\n"],"names":["Button","Collapsible","useCallback","useId","useMemo","useRef","useState","closeNativeDialogOnBackdropPointer","formatBytes","getBackupSortTimeMs","transformBlobName","BackupItemActions","defaultFilterState","dateFrom","dateTo","label","media","showOtherDb","showOtherHostname","source","parseDateInputStartMs","isoDate","parts","split","map","Number","length","some","n","isFinite","y","m","d","t","Date","getTime","parseDateInputEndMs","passesDateMediaSource","parsed","displayMs","filters","from","to","fileType","type","labelQuery","trim","toLowerCase","blobLabel","includes","hasNonDefaultFilters","f","BackupListCollapsible","blobs","countOtherDb","countOtherHostname","currentDbName","currentHostname","i18nLanguage","uid","idDb","idHost","dialogRef","setFilters","visibleRows","rows","blob","pathname","dbName","hostname","isCurrentDb","isCurrentHostname","uploadedAt","push","displayAt","openFilterDialog","current","showModal","closeFilterDialog","close","clearFilters","hasScopeOptions","filtersActive","filtersButton","buttonStyle","className","onClick","header","span","initCollapsed","div","p","collectionCount","time","dateTime","toISOString","suppressHydrationWarning","toLocaleString","title","size","downloadUrl","url","dialog","onPointerDown","e","ref","h3","fieldset","legend","input","aria-label","checked","id","onChange","target","htmlFor","autoComplete","value","placeholder","spellCheck","name"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/BackupDashboard/BackupListCollapsible.client.tsx"],"sourcesContent":["'use client'\n\nimport { Button, Collapsible } from '@payloadcms/ui'\nimport { useCallback, useId, useMemo, useRef, useState } from 'react'\n\nimport type { TransformBlobNameResult } from '../../utils/index'\n\nimport { closeNativeDialogOnBackdropPointer } from '../../utils/dialogBackdrop'\nimport { formatBytes, getBackupSortTimeMs, transformBlobName } from '../../utils/index'\nimport { BackupItemActions } from './BackupItemActions.client'\n\nexport interface SerializableBackupBlob {\n downloadUrl: string\n pathname: string\n size: number\n uploadedAt: string\n url: string\n}\n\nexport interface BackupListCollapsibleProps {\n blobs: SerializableBackupBlob[]\n countOtherDb: number\n countOtherHostname: number\n currentDbName: string\n currentHostname: string\n i18nLanguage: string\n}\n\ninterface BackupListFilterState {\n dateFrom: string\n dateTo: string\n label: string\n media: 'all' | 'with' | 'without'\n showOtherDb: boolean\n showOtherHostname: boolean\n source: 'all' | 'cron' | 'manual'\n}\n\nconst defaultFilterState: BackupListFilterState = {\n dateFrom: '',\n dateTo: '',\n label: '',\n media: 'all',\n showOtherDb: true,\n showOtherHostname: false,\n source: 'all',\n}\n\nfunction parseDateInputStartMs(isoDate: string): null | number {\n const parts = isoDate.split('-').map(Number)\n if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {\n return null\n }\n const [y, m, d] = parts\n const t = new Date(y, m - 1, d, 0, 0, 0, 0).getTime()\n return Number.isFinite(t) ? t : null\n}\n\nfunction parseDateInputEndMs(isoDate: string): null | number {\n const parts = isoDate.split('-').map(Number)\n if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {\n return null\n }\n const [y, m, d] = parts\n const t = new Date(y, m - 1, d, 23, 59, 59, 999).getTime()\n return Number.isFinite(t) ? t : null\n}\n\nfunction passesDateMediaSource(\n parsed: TransformBlobNameResult,\n displayMs: number,\n filters: BackupListFilterState,\n): boolean {\n if (filters.dateFrom) {\n const from = parseDateInputStartMs(filters.dateFrom)\n if (from != null && displayMs < from) {\n return false\n }\n }\n if (filters.dateTo) {\n const to = parseDateInputEndMs(filters.dateTo)\n if (to != null && displayMs > to) {\n return false\n }\n }\n if (filters.media === 'with' && parsed.fileType !== 'tar.gz') {\n return false\n }\n if (filters.media === 'without' && parsed.fileType !== 'json') {\n return false\n }\n if (filters.source === 'manual' && parsed.type !== 'manual') {\n return false\n }\n if (filters.source === 'cron' && parsed.type !== 'cron') {\n return false\n }\n const labelQuery = filters.label.trim().toLowerCase()\n if (labelQuery) {\n const blobLabel = (parsed.label ?? '').toLowerCase()\n if (!blobLabel.includes(labelQuery)) {\n return false\n }\n }\n return true\n}\n\nfunction hasNonDefaultFilters(f: BackupListFilterState): boolean {\n return (\n f.dateFrom !== defaultFilterState.dateFrom ||\n f.dateTo !== defaultFilterState.dateTo ||\n f.label !== defaultFilterState.label ||\n f.media !== defaultFilterState.media ||\n f.source !== defaultFilterState.source ||\n f.showOtherDb !== defaultFilterState.showOtherDb ||\n f.showOtherHostname !== defaultFilterState.showOtherHostname\n )\n}\n\nexport const BackupListCollapsible: React.FC<BackupListCollapsibleProps> = ({\n blobs,\n countOtherDb,\n countOtherHostname,\n currentDbName,\n currentHostname,\n i18nLanguage,\n}) => {\n const uid = useId()\n const idDb = `${uid}-show-other-db`\n const idHost = `${uid}-show-other-host`\n const dialogRef = useRef<HTMLDialogElement>(null)\n const [filters, setFilters] = useState<BackupListFilterState>(defaultFilterState)\n\n const visibleRows = useMemo(() => {\n const rows: Array<{\n blob: SerializableBackupBlob\n displayAt: Date\n displayMs: number\n parsed: TransformBlobNameResult\n }> = []\n\n for (const blob of blobs) {\n const parsed = transformBlobName(blob.pathname)\n const { dbName, hostname } = parsed\n const isCurrentDb = currentDbName === dbName\n const isCurrentHostname = currentHostname === hostname\n if (!(filters.showOtherDb || isCurrentDb)) {\n continue\n }\n if (!(filters.showOtherHostname || isCurrentHostname)) {\n continue\n }\n\n const uploadedAt = new Date(blob.uploadedAt)\n const displayMs = getBackupSortTimeMs(parsed, uploadedAt)\n if (!passesDateMediaSource(parsed, displayMs, filters)) {\n continue\n }\n\n rows.push({\n blob,\n displayAt: new Date(displayMs),\n displayMs,\n parsed,\n })\n }\n\n return rows\n }, [blobs, currentDbName, currentHostname, filters])\n\n const openFilterDialog = useCallback(() => {\n dialogRef.current?.showModal()\n }, [])\n\n const closeFilterDialog = useCallback(() => {\n dialogRef.current?.close()\n }, [])\n\n const clearFilters = useCallback(() => {\n setFilters(defaultFilterState)\n }, [])\n\n const hasScopeOptions = countOtherDb > 0 || countOtherHostname > 0\n const filtersActive = hasNonDefaultFilters(filters)\n\n const filtersButton = (\n <Button\n buttonStyle=\"secondary\"\n className={\n filtersActive ?\n 'backup-dashboard__filter-btn backup-dashboard__filter-btn--active'\n : 'backup-dashboard__filter-btn'\n }\n onClick={openFilterDialog}\n type=\"button\"\n >\n Filters\n </Button>\n )\n\n return (\n <>\n <Collapsible\n header={<span className=\"backup-dashboard__collapsible-title\">Backup list</span>}\n initCollapsed={true}\n >\n <div className=\"backup-dashboard__backup-list-toolbar\">{filtersButton}</div>\n {blobs.length === 0 ?\n <p className=\"backup-dashboard__list-empty\">No backups yet</p>\n : visibleRows.length === 0 ?\n <p className=\"backup-dashboard__list-empty\">No backups match the current filters</p>\n : visibleRows.map(({ blob, displayAt, parsed }) => {\n const { type, collectionCount, dbName, fileType, hostname, label } = parsed\n const isCurrentDb = currentDbName === dbName\n const isCurrentHostname = currentHostname === hostname\n\n return (\n <div className=\"backup-item\" key={blob.pathname}>\n <div className=\"backup-item__main\">\n <div className=\"backup-item__primary\">\n <time\n className=\"backup-item__time\"\n dateTime={displayAt.toISOString()}\n suppressHydrationWarning\n >\n {displayAt.toLocaleString(i18nLanguage)}\n </time>\n <span\n className={`backup-item__pill backup-item__pill--kind backup-item__pill--${type === 'cron' ? 'cron' : 'manual'}`}\n >\n {type === 'cron' ? 'Cron backup' : 'Manual backup'}\n </span>\n {label ?\n <span\n className=\"backup-item__pill backup-item__pill--label\"\n title={`Label: ${label}`}\n >\n <span className=\"backup-item__pill-key\">Label</span>\n {label}\n </span>\n : null}\n </div>\n <div className=\"backup-item__meta\">\n <span\n className={`backup-item__pill backup-item__pill--db${isCurrentDb ? '' : ' backup-item__pill--warn'}`}\n >\n <span className=\"backup-item__pill-key\">\n {isCurrentDb ? 'DB' : 'other DB'}\n </span>\n {dbName || 'Unknown'}\n </span>\n <span\n className={`backup-item__pill backup-item__pill--host${isCurrentHostname ? '' : ' backup-item__pill--warn'}`}\n >\n <span className=\"backup-item__pill-key\">Host</span>\n {hostname || 'Unknown'}\n </span>\n {typeof collectionCount === 'number' ?\n <span className=\"backup-item__pill backup-item__pill--collections\">\n <span className=\"backup-item__pill-key\">Collections</span>\n {collectionCount}\n </span>\n : null}\n {fileType === 'tar.gz' ?\n <span className=\"backup-item__pill backup-item__pill--media\">With media</span>\n : fileType === 'json' ?\n <span className=\"backup-item__pill backup-item__pill--media\">\n Without media\n </span>\n : null}\n <span\n className=\"backup-item__pill backup-item__pill--size\"\n title={`${blob.size.toLocaleString(i18nLanguage)} bytes`}\n >\n <span className=\"backup-item__pill-key\">Size</span>\n <span className=\"backup-item__size-value\">{formatBytes(blob.size)}</span>\n </span>\n </div>\n </div>\n <BackupItemActions\n downloadUrl={blob.downloadUrl}\n pathname={blob.pathname}\n url={blob.url}\n />\n </div>\n )\n })\n }\n </Collapsible>\n\n <dialog\n className=\"backup-confirm-dialog backup-confirm-dialog--backup-list-filters\"\n onPointerDown={(e) => closeNativeDialogOnBackdropPointer(e, dialogRef)}\n ref={dialogRef}\n >\n <h3 className=\"backup-confirm-dialog__title\">Filter backups</h3>\n <p className=\"backup-confirm-dialog__subtitle\">\n All filters apply in the browser only. Current DB and hostname are always shown; enable\n the options below to include backups from other databases or hosts.\n </p>\n <div className=\"backup-confirm-dialog__body backup-list-filters__body\">\n {hasScopeOptions ?\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Scope</legend>\n {countOtherDb > 0 ?\n <div className=\"field-type checkbox backup-dashboard__collapsible-checkbox backup-list-filters__scope-row\">\n <input\n aria-label={`Show other databases (${countOtherDb} in list)`}\n checked={filters.showOtherDb}\n className=\"checkbox-input__input\"\n id={idDb}\n onChange={(e) => setFilters((f) => ({ ...f, showOtherDb: e.target.checked }))}\n type=\"checkbox\"\n />\n <label className=\"field-label\" htmlFor={idDb}>\n Show other DBs\n <span className=\"backup-list-filters__hint\"> ({countOtherDb} in list)</span>\n </label>\n </div>\n : null}\n {countOtherHostname > 0 ?\n <div className=\"field-type checkbox backup-dashboard__collapsible-checkbox backup-list-filters__scope-row\">\n <input\n aria-label={`Show other hostnames (${countOtherHostname} in list)`}\n checked={filters.showOtherHostname}\n className=\"checkbox-input__input\"\n id={idHost}\n onChange={(e) =>\n setFilters((f) => ({ ...f, showOtherHostname: e.target.checked }))\n }\n type=\"checkbox\"\n />\n <label className=\"field-label\" htmlFor={idHost}>\n Show other Hostnames\n <span className=\"backup-list-filters__hint\">\n {' '}\n ({countOtherHostname} in list)\n </span>\n </label>\n </div>\n : null}\n </fieldset>\n : null}\n\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Label</legend>\n <div className=\"backup-list-filters__row backup-list-filters__row--label\">\n <label className=\"backup-list-filters__label\" htmlFor={`${uid}-label`}>\n Search\n </label>\n <input\n aria-label=\"Filter backups by label text\"\n autoComplete=\"off\"\n className=\"backup-list-filters__text\"\n id={`${uid}-label`}\n onChange={(e) => setFilters((f) => ({ ...f, label: e.target.value }))}\n placeholder=\"Filter by label text\"\n spellCheck={false}\n type=\"text\"\n value={filters.label}\n />\n </div>\n </fieldset>\n\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Date range</legend>\n <div className=\"backup-list-filters__row\">\n <label className=\"backup-list-filters__label\" htmlFor={`${uid}-from`}>\n From\n </label>\n <input\n aria-label=\"Filter from date\"\n className=\"backup-list-filters__date\"\n id={`${uid}-from`}\n onChange={(e) => setFilters((f) => ({ ...f, dateFrom: e.target.value }))}\n type=\"date\"\n value={filters.dateFrom}\n />\n </div>\n <div className=\"backup-list-filters__row\">\n <label className=\"backup-list-filters__label\" htmlFor={`${uid}-to`}>\n To\n </label>\n <input\n aria-label=\"Filter to date\"\n className=\"backup-list-filters__date\"\n id={`${uid}-to`}\n onChange={(e) => setFilters((f) => ({ ...f, dateTo: e.target.value }))}\n type=\"date\"\n value={filters.dateTo}\n />\n </div>\n </fieldset>\n\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Media</legend>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Media filter: all backups\"\n checked={filters.media === 'all'}\n className=\"radio-input__input\"\n id={`${uid}-media-all`}\n name={`${uid}-media`}\n onChange={() => setFilters((f) => ({ ...f, media: 'all' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-media-all`}>\n All\n </label>\n </div>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Media filter: with media\"\n checked={filters.media === 'with'}\n className=\"radio-input__input\"\n id={`${uid}-media-with`}\n name={`${uid}-media`}\n onChange={() => setFilters((f) => ({ ...f, media: 'with' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-media-with`}>\n With media\n </label>\n </div>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Media filter: without media\"\n checked={filters.media === 'without'}\n className=\"radio-input__input\"\n id={`${uid}-media-without`}\n name={`${uid}-media`}\n onChange={() => setFilters((f) => ({ ...f, media: 'without' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-media-without`}>\n Without media\n </label>\n </div>\n </fieldset>\n\n <fieldset className=\"backup-list-filters__fieldset\">\n <legend className=\"backup-list-filters__legend\">Source</legend>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Source filter: all\"\n checked={filters.source === 'all'}\n className=\"radio-input__input\"\n id={`${uid}-source-all`}\n name={`${uid}-source`}\n onChange={() => setFilters((f) => ({ ...f, source: 'all' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-source-all`}>\n All\n </label>\n </div>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Source filter: manual backups\"\n checked={filters.source === 'manual'}\n className=\"radio-input__input\"\n id={`${uid}-source-manual`}\n name={`${uid}-source`}\n onChange={() => setFilters((f) => ({ ...f, source: 'manual' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-source-manual`}>\n Manual\n </label>\n </div>\n <div className=\"field-type radio backup-list-filters__radio-row\">\n <input\n aria-label=\"Source filter: cron backups\"\n checked={filters.source === 'cron'}\n className=\"radio-input__input\"\n id={`${uid}-source-cron`}\n name={`${uid}-source`}\n onChange={() => setFilters((f) => ({ ...f, source: 'cron' }))}\n type=\"radio\"\n />\n <label className=\"field-label\" htmlFor={`${uid}-source-cron`}>\n Cron\n </label>\n </div>\n </fieldset>\n </div>\n <div className=\"backup-confirm-dialog__actions\">\n <Button buttonStyle=\"secondary\" onClick={clearFilters} type=\"button\">\n Clear filters\n </Button>\n <Button onClick={closeFilterDialog} type=\"button\">\n Done\n </Button>\n </div>\n </dialog>\n </>\n )\n}\n"],"names":["Button","Collapsible","useCallback","useId","useMemo","useRef","useState","closeNativeDialogOnBackdropPointer","formatBytes","getBackupSortTimeMs","transformBlobName","BackupItemActions","defaultFilterState","dateFrom","dateTo","label","media","showOtherDb","showOtherHostname","source","parseDateInputStartMs","isoDate","parts","split","map","Number","length","some","n","isFinite","y","m","d","t","Date","getTime","parseDateInputEndMs","passesDateMediaSource","parsed","displayMs","filters","from","to","fileType","type","labelQuery","trim","toLowerCase","blobLabel","includes","hasNonDefaultFilters","f","BackupListCollapsible","blobs","countOtherDb","countOtherHostname","currentDbName","currentHostname","i18nLanguage","uid","idDb","idHost","dialogRef","setFilters","visibleRows","rows","blob","pathname","dbName","hostname","isCurrentDb","isCurrentHostname","uploadedAt","push","displayAt","openFilterDialog","current","showModal","closeFilterDialog","close","clearFilters","hasScopeOptions","filtersActive","filtersButton","buttonStyle","className","onClick","header","span","initCollapsed","div","p","collectionCount","time","dateTime","toISOString","suppressHydrationWarning","toLocaleString","title","size","downloadUrl","url","dialog","onPointerDown","e","ref","h3","fieldset","legend","input","aria-label","checked","id","onChange","target","htmlFor","autoComplete","value","placeholder","spellCheck","name"],"mappings":"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"}
|