directus-extension-storage-manager 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,68 +1,177 @@
1
1
  # Storage Manager
2
2
 
3
- Browse, upload, and move files between your Directus storage locations (local disk, S3, Google Cloud, Azure, and more) — without losing the file’s identity in Directus.
3
+ Browse, create, and **move** files and physical folders across your Directus adapters (local disk, S3, Google Cloud, Azure, and more) — without losing the file’s identity in Directus.
4
4
 
5
- > **Important:** This extension moves or copies the **actual files** between storage backends and updates where Directus looks for them. Prefer **Copy** when you try a new storage for the first time; use **Move** only when you are sure the new location is correct.
5
+ > **Important:** Studio **moves** objects between storages and updates where Directus looks for them. The same `directus_files` UUID is kept. Prefer a **Dry Run** before a large move. Copy is available in the Flow operation only (it leaves an unregistered leftover on the source).
6
6
 
7
7
  ## Overview
8
8
 
9
- <img alt="Storage overview with local, S3, and GCS adapters" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_migration.png" width="800" />
9
+ <img alt="Storage Manager overview with local, local2, S3, and GCS adapters" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_manager.png" width="800" />
10
10
 
11
- Directus can use several storage locations at once, but it does not move existing files for you when you change storage. **Storage Manager** fills that gap: see what lives on each adapter, upload new files, find files on disk that are missing from Directus, and migrate selected files or whole folders between storages.
11
+ Directus can use several storage locations at once, but it does not move existing files for you when you change storage. **Storage Manager** fills that gap: see what lives on each adapter, optionally **mirror Directus folders** onto that adapter, browse physical folders on disk/bucket, upload files, find objects missing from Directus, **materialize** a virtual folder tree onto storage, and **move** selected files, folders, or a whole adapter.
12
12
 
13
- Files keep the same ID in Directus. Only their storage location changes. Image thumbnails are moved or copied with them when possible.
13
+ Files keep the same ID in Directus. Only their storage location (and optionally their path under that location) changes. Image thumbnails / transforms are moved with them when possible.
14
14
 
15
15
  ## Features
16
16
 
17
17
  ### Overview & adapters
18
18
 
19
- - See every configured storage location with file counts and usage
20
- - Open a location to browse its files (same card/table layouts as the File Library)
21
- - Drag and drop (or use **Upload**) to add files straight onto that storage
19
+ - See every configured storage location with file counts, folder counts, usage, and root / bucket
20
+ - Per-adapter **Mirror Directus Folders** toggle
21
+ - **Browse** opens that adapter’s files and folders
22
22
 
23
- ### Folders
23
+ ### Mirror Directus Folders
24
24
 
25
- - Browse the same virtual folders as the File Library
26
- - Migrate everything in a folder (optionally including subfolders), across any storage
25
+ When enabled on a storage card, that adapter follows the Directus virtual folder tree:
27
26
 
28
- ### Migrate
27
+ - **New uploads** land under the matching physical path (folder names)
28
+ - **Rename / delete** of a Directus folder updates physical paths **on this adapter only**
29
29
 
30
- <img alt="Migrate Files drawer choose target storage and Move or Copy" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_migration_files.png" width="800" />
30
+ A Directus folder is **virtual**: it can contain files from several adapters at once. Example: `Articles` has some files on `local` and some on `s3`. If both have Mirror on, a rename updates `local` and `s3` independently (each only its own objects). If only `local` has Mirror on, `s3` paths stay unchanged.
31
31
 
32
- - **Migrate selected** files, **all files on a storage**, or a **whole folder**
33
- - **Move** — after a successful transfer, remove the file from the old storage
34
- - **Copy** — leave a copy on the old storage (it will no longer be linked in Directus; you can clean it up or re-import with Detect)
35
- - Live progress: from → to, current file, amount transferred, and speed
32
+ Sibling folders with the same name use `name_<folder-uid>` so paths stay unique. The first folder to claim a plain name keeps it.
36
33
 
37
- <img alt="Migrate progress from GCS to local storage" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_migration_storage_gcs.png" width="800" />
34
+ Mirror does **not** rewrite files that are already on disk. Use **Move to Storage Folder** or **Materialize** for existing files.
35
+
36
+ ### Left navigation
37
+
38
+ 1. **Storage Manager** (overview)
39
+ 2. **Storage adapters** — expandable physical folder trees
40
+ 3. **Directus Folders** — virtual folder tree
41
+ 4. **Settings**
42
+
43
+ Trees expand down to the current folder / storage path on load and refresh.
44
+
45
+ ### Browse UI
46
+
47
+ - Same card / table layouts as the File Library
48
+ - Physical folder cards appear next to files when browsing a storage path
49
+ - File detail opens in-module (back stays in Storage Manager)
50
+ - Search, filter, and layout presets are remembered per browser
51
+ - **Upload** (header **+**, sidebar, empty states) opens a File Library–style **dropzone dialog**
52
+ - Window-level drag-and-drop onto the page still works
53
+ - In Directus Folders view, each file card shows which storage it lives on
54
+
55
+ ### Physical storage folders
56
+
57
+ Manage real folders on disk / in the bucket (not only Directus virtual folders):
58
+
59
+ - **Create Storage Folder** — local `mkdir`, or a `.keep` marker on cloud adapters so empty folders survive
60
+ - Right-click context menu (nav + folder cards):
61
+ - **Rename Folder** — rewrites nested registered files’ paths and moves objects on disk
62
+ - **Move to Folder** — reparent under another storage path
63
+ - **Delete Folder** — File Library–style dialog: move registered content one level up, or delete all content, then remove the folder
64
+ - **Delete** (header) — selected files via core `/files` delete; when storage folders are selected, same Delete Folder dialog as above
65
+ - Upload / drop into a nested path places the file under that folder after create
66
+
67
+ Example: create `My Test Folder` on `local`, upload while browsing it → object lands under `My Test Folder/` on disk and in `filename_disk`.
68
+
69
+ ### Move to Storage Folder
70
+
71
+ One dialog for selected files, selected physical folders, the current Directus folder (nothing selected), or **Move all** at a storage root.
72
+
73
+ - Pick a destination **adapter + physical path**
74
+ - **Dry Run** counts files, folders, empty folders, size, and destination conflicts (sample from → to paths)
75
+ - **Include empty folders** when moving a whole adapter or selected folders (local `mkdir` / cloud `.keep`)
76
+ - Selected **folders keep their name** (`local/hello` → `local2/hello`). If `hello/` already exists on the destination, contents are **merged into it**
77
+ - Loose **files** flatten to the basename under the destination path
78
+ - Whole-adapter moves **preserve nested paths**
79
+ - Studio always **moves** (no Copy in the UI)
80
+
81
+ <img alt="Move Files progress from local to S3" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_move_to_gcs.png" width="800" />
82
+
83
+ #### Conflicts
84
+
85
+ | Situation | What happens |
86
+ | --- | --- |
87
+ | Destination **folder** already exists | Contents are merged into it |
88
+ | Destination **path** already has another `directus_files` row | Incoming file is **skipped** — it stays on the source. Two files are never pointed at the same path |
89
+ | Destination **blob** exists but no other file row owns it | Treated as resume: this file is pointed at the dest path, then the source is removed |
90
+
91
+ ### Materialize
92
+
93
+ In **Directus Folders**, turn the virtual tree into physical storage paths:
94
+
95
+ - **Keep** — each file stays on its current adapter; only the folder path is created there. Optional **structure-only** (folders, no file moves)
96
+ - **Merge** — all files move onto one target adapter at their virtual folder path
97
+ - Recursive (include subfolders)
98
+ - Dry run with file / folder / conflict counts
99
+
100
+ The virtual folder tree in the File Library is never rewritten here.
101
+
102
+ <img alt="Materialize Folder drawer with Keep and Merge storage modes" src="https://raw.githubusercontent.com/domdus/directus-extension-storage-manager/main/docs/screenshot_storage_materialize.png" width="800" />
103
+
104
+ ### Thumbnails & transforms
105
+
106
+ Directus stores generated transforms at the **storage root** as `{stem}__{hash}.ext` (for example `uuid__7abd30….avif`).
107
+
108
+ - Move copies related transforms to the **target root** basename (where AssetsService expects them)
109
+ - Same-adapter folder rename / move keeps root transforms and cleans colocated orphans when needed
110
+ - **Detect** / orphan import / orphan delete skip generated thumbnails, dotfiles, and `directus-health-file`
38
111
 
39
112
  ### Detect files
40
113
 
41
- - Scan a storage for files that exist on disk/bucket but are **not** in Directus yet
42
- - Import them into the library (creates database entries only — files stay where they are)
43
- - Automatically skips Directus-generated image thumbnails
114
+ - Scan for objects on disk / bucket that are **not** in Directus yet
115
+ - At storage root: **Detect Files on {adapter}**
116
+ - Inside a physical folder: **Detect Files in this Folder** (scan scoped to that path and subfolders)
117
+ - Import creates database rows only — files stay where they are. Image width/height are read so Directus can generate thumbnails
118
+ - Delete selected orphans permanently (thumbnails are never deleted via this path)
119
+
120
+ ### Settings
121
+
122
+ - **Check now** for a published npm update
123
+ - **Export / Import** JSON backup of Mirror Directus Folders settings
124
+ - **Delete** stored `storage_manager` data from `directus_settings` (field is recreated empty on next start if the extension is still installed)
44
125
 
45
126
  ### Automation
46
127
 
47
- - Use the **Storage Manager** Flow operation to copy or move files in automations
128
+ - **Storage Manager** Flow operation copy or move files in automations (file IDs, source storage, and/or folder + recursive). Copy leaves an unregistered leftover on the source (it will show up under Detect).
48
129
 
49
130
  ## Getting started
50
131
 
51
- 1. Make sure your project has more than one storage location configured (for example `local` and `s3`).
132
+ 1. Configure more than one storage location if you plan to move files (for example `local` and `s3`).
52
133
  2. As an admin, open **Settings → Project Settings → Modules** and enable **Storage Manager**.
53
134
  3. Open **Storage Manager** from the left bar.
54
- 4. Pick a storage card (or **Folders**) and browse files.
55
- 5. Select files (or migrate all on that storage / folder), choose a **target storage**, then **Move** or **Copy**.
135
+ 4. On the overview, turn on **Mirror Directus Folders** per adapter if you want new uploads and folder rename/delete to follow the virtual tree.
136
+ 5. **Browse** a storage (or **Directus Folders**) and work with files / physical folders.
137
+ 6. Use **Move to Storage Folder** (or **Move all** at a storage root). Dry-run first, then Move.
56
138
 
57
139
  Tips:
58
140
 
59
- - Test with **Copy** first, then switch to **Move** once you are happy with the result.
60
- - After a **Copy**, leftovers on the old storage can show up under **Detect files** — that is expected.
141
+ - After a **Copy** (Flow operation only), leftovers on the old storage can show up under **Detect** — that is expected.
61
142
  - Cloud storages often cannot report a full disk quota; the UI then shows Directus file totals only.
143
+ - Virtual folders can span multiple storages; Mirror is always per-adapter.
144
+ - Same-name sibling Directus folders use `name_<folder-uid>` on disk so paths stay unique.
145
+
146
+ ## Readable URLs for paths with spaces
147
+
148
+ Disk and the database keep real spaces. Studio routes encode spaces as `_` and existing underscores as `__` so URLs stay readable and reversible.
149
+
150
+ | On disk / in DB | In the Studio URL |
151
+ | --- | --- |
152
+ | `My Test Folder` | `/storage-manager/storage/local/path/My_Test_Folder` |
153
+ | `already_underscored` | `…/already__underscored` |
154
+
155
+ Legacy `%20` segments still decode correctly.
156
+
157
+ ## Configuration
158
+
159
+ Uses standard Directus storage environment variables — no extension-specific env vars:
160
+
161
+ ```bash
162
+ STORAGE_LOCATIONS="local,s3"
163
+ STORAGE_LOCAL_DRIVER="local"
164
+ STORAGE_LOCAL_ROOT="./uploads"
165
+ STORAGE_S3_DRIVER="s3"
166
+ STORAGE_S3_BUCKET="…"
167
+ # …plus the usual key / region / endpoint settings for your driver
168
+ ```
169
+
170
+ Per-location Mirror settings are stored in **`directus_settings.storage_manager`** (JSON), editable from the overview (admin only).
62
171
 
63
172
  ## Installation
64
173
 
65
- Requires **Directus 9.26+ through 12.x**. The extension must run in the Directus API process (not the sandbox).
174
+ Requires **Directus 9.26+ through 12.x**. The extension must run in the Directus API process (not the sandbox). Admin-only module.
66
175
 
67
176
  ### npm
68
177
 
@@ -96,6 +205,35 @@ npm run build
96
205
  2. Enable **Storage Manager**
97
206
  3. Open **Storage Manager** from the left bar
98
207
 
208
+ ## Operator API (admin)
209
+
210
+ Base path: `/storage-manager`
211
+
212
+ | Method | Path | Purpose |
213
+ | --- | --- | --- |
214
+ | `GET` | `/storages` | List adapters + usage |
215
+ | `GET` | `/storages/:location/browse?path=` | Immediate physical folders |
216
+ | `GET` | `/storages/:location/folder-tree` | Nested physical tree |
217
+ | `POST` | `/storages/:location/folders` | Create folder (`name`, `parent_path`) |
218
+ | `PATCH` | `/storages/:location/folders` | Rename (`name`) and/or move (`parent_path`) |
219
+ | `DELETE` | `/storages/:location/folders` | Delete folders (`paths[]`, `mode: move\|delete`) |
220
+ | `POST` | `/storages/:location/move-files` | Relocate registered files on the same adapter |
221
+ | `POST` | `/storages/:location/place-file` | Nest a file under a path after upload |
222
+ | `GET` | `/storages/:location/orphans?path=` | Detect unknown objects (optional path scope) |
223
+ | `POST` | `/storages/:location/import-orphans` | Register orphans (reads image width/height) |
224
+ | `POST` | `/storages/:location/delete-orphans` | Delete orphans |
225
+ | `POST` | `/migrate/dry-run` | Count files/folders/conflicts without moving |
226
+ | `POST` | `/migrate` | Batch migrate (JSON result) |
227
+ | `POST` | `/migrate/stream` | SSE migrate progress |
228
+ | `POST` | `/materialize/dry-run` | Count materialize work |
229
+ | `POST` | `/materialize` | Materialize virtual folders onto storage |
230
+ | `POST` | `/materialize/stream` | SSE materialize progress |
231
+ | `GET` / `PATCH` | `/settings` | Read / merge settings |
232
+
233
+ **Move / migrate body (typical):** `target_storage`, `mode` (`move`; `copy` for the Flow operation), plus `file_ids` and/or `source_storage` (optional `source_path`, `preserve_paths`) and/or `folder_id` (optional `recursive`). Optional `target_path`, `source_folders`, `include_empty_folders`.
234
+
235
+ Same-adapter `move-files` skips a file when another registered row already owns the destination path.
236
+
99
237
  ## License
100
238
 
101
239
  MIT