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 +166 -28
- package/dist/api.js +1 -1
- package/dist/app.js +1 -1
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -1,68 +1,177 @@
|
|
|
1
1
|
# Storage Manager
|
|
2
2
|
|
|
3
|
-
Browse,
|
|
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:**
|
|
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/
|
|
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,
|
|
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
|
|
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
|
|
20
|
-
-
|
|
21
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
|
42
|
-
-
|
|
43
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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.
|
|
55
|
-
5.
|
|
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
|
-
-
|
|
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
|