strapi-content-sync-pro 1.0.1 → 1.0.3
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 +84 -25
- package/admin/src/components/ConfigTab.jsx +29 -6
- package/admin/src/components/HelpTab.jsx +131 -32
- package/admin/src/components/MediaTab.jsx +7 -0
- package/admin/src/components/StatsTab.jsx +470 -0
- package/admin/src/components/SyncProfilesTab.jsx +63 -5
- package/admin/src/components/SyncTab.jsx +51 -7
- package/admin/src/pages/App/index.jsx +3 -0
- package/docs/Screenshot 2026-04-20 160506.png +0 -0
- package/docs/Screenshot 2026-04-20 160558.png +0 -0
- package/docs/Screenshot 2026-04-20 175903.png +0 -0
- package/docs/Screenshot 2026-04-20 175931.png +0 -0
- package/docs/Screenshot 2026-04-20 180001.png +0 -0
- package/docs/Screenshot 2026-04-20 180041.png +0 -0
- package/docs/Screenshot 2026-04-20 180116.png +0 -0
- package/docs/Screenshot 2026-04-20 180135.png +0 -0
- package/docs/Screenshot 2026-04-20 180202.png +0 -0
- package/docs/Screenshot 2026-04-20 180228.png +0 -0
- package/docs/Screenshot 2026-04-20 180251.png +0 -0
- package/docs/Screenshot 2026-04-20 180301.png +0 -0
- package/docs/clipchamp-screen-recording-script.md +0 -0
- package/docs/logo-horizontal.svg +33 -0
- package/docs/logo-mark.svg +38 -0
- package/docs/logo-square.svg +27 -0
- package/docs/production-readiness-status.md +34 -0
- package/docs/production-readiness-test-matrix.md +151 -0
- package/docs/test-environments-setup-legacy.txt +60 -0
- package/package.json +2 -1
- package/server/src/content-types/index.js +2 -0
- package/server/src/content-types/sync-run-report/schema.json +26 -0
- package/server/src/controllers/config.js +48 -5
- package/server/src/controllers/index.js +2 -0
- package/server/src/controllers/sync-log.js +6 -0
- package/server/src/controllers/sync-media.js +19 -0
- package/server/src/controllers/sync-stats.js +51 -0
- package/server/src/controllers/sync.js +9 -3
- package/server/src/routes/index.js +13 -0
- package/server/src/services/config.js +18 -2
- package/server/src/services/index.js +2 -0
- package/server/src/services/sync-execution.js +102 -5
- package/server/src/services/sync-log.js +36 -0
- package/server/src/services/sync-media.js +224 -1
- package/server/src/services/sync-profiles.js +92 -4
- package/server/src/services/sync-stats.js +353 -0
- package/server/src/services/sync.js +324 -97
- package/server/src/utils/applier.js +120 -13
- package/server/src/utils/comparator.js +22 -6
- package/server/src/utils/fetcher.js +11 -2
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
# Content Sync Pro Plugin for Strapi
|
|
1
|
+
# Content Sync Pro Plugin for Strapi
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/logo-horizontal.svg" alt="Content Sync Pro" width="720" />
|
|
5
|
+
</p>
|
|
2
6
|
|
|
3
7
|
A powerful Strapi v5 plugin to copy, migrate, and live-sync content, media, and data between multiple Strapi environments.
|
|
4
8
|
|
|
@@ -10,7 +14,7 @@ A powerful Strapi v5 plugin to copy, migrate, and live-sync content, media, and
|
|
|
10
14
|
Plugin intro: https://youtu.be/hr3dD6dLgLQ
|
|
11
15
|
|
|
12
16
|
<a href="https://youtu.be/hr3dD6dLgLQ" target="_blank" rel="noopener noreferrer">
|
|
13
|
-
<img src="docs/Screenshot%202026-04-20%20160506.png" alt="Content Sync Pro — watch the intro video" width="100%" />
|
|
17
|
+
<img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20160506.png" alt="Content Sync Pro — watch the intro video" width="100%" />
|
|
14
18
|
</a>
|
|
15
19
|
|
|
16
20
|
## Screenshots
|
|
@@ -22,41 +26,44 @@ Plugin intro: https://youtu.be/hr3dD6dLgLQ
|
|
|
22
26
|
|
|
23
27
|
<table>
|
|
24
28
|
<tr>
|
|
25
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20160506.png" alt="Content Sync Pro - screenshot 1" width="100%" /></td>
|
|
26
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20160558.png" alt="Content Sync Pro - screenshot 2" width="100%" /></td>
|
|
27
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20175903.png" alt="Content Sync Pro - screenshot 3" width="100%" /></td>
|
|
29
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20160506.png" alt="Content Sync Pro - screenshot 1" width="100%" /></td>
|
|
30
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20160558.png" alt="Content Sync Pro - screenshot 2" width="100%" /></td>
|
|
31
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20175903.png" alt="Content Sync Pro - screenshot 3" width="100%" /></td>
|
|
28
32
|
</tr>
|
|
29
33
|
<tr>
|
|
30
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20175931.png" alt="Content Sync Pro - screenshot 4" width="100%" /></td>
|
|
31
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20180001.png" alt="Content Sync Pro - screenshot 5" width="100%" /></td>
|
|
32
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20180041.png" alt="Content Sync Pro - screenshot 6" width="100%" /></td>
|
|
34
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20175931.png" alt="Content Sync Pro - screenshot 4" width="100%" /></td>
|
|
35
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20180001.png" alt="Content Sync Pro - screenshot 5" width="100%" /></td>
|
|
36
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20180041.png" alt="Content Sync Pro - screenshot 6" width="100%" /></td>
|
|
33
37
|
</tr>
|
|
34
38
|
<tr>
|
|
35
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20180116.png" alt="Content Sync Pro - screenshot 7" width="100%" /></td>
|
|
36
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20180135.png" alt="Content Sync Pro - screenshot 8" width="100%" /></td>
|
|
37
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20180202.png" alt="Content Sync Pro - screenshot 9" width="100%" /></td>
|
|
39
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20180116.png" alt="Content Sync Pro - screenshot 7" width="100%" /></td>
|
|
40
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20180135.png" alt="Content Sync Pro - screenshot 8" width="100%" /></td>
|
|
41
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20180202.png" alt="Content Sync Pro - screenshot 9" width="100%" /></td>
|
|
38
42
|
</tr>
|
|
39
43
|
<tr>
|
|
40
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20180228.png" alt="Content Sync Pro - screenshot 10" width="100%" /></td>
|
|
41
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20180251.png" alt="Content Sync Pro - screenshot 11" width="100%" /></td>
|
|
42
|
-
<td width="33%"><img src="docs/Screenshot%202026-04-20%20180301.png" alt="Content Sync Pro - screenshot 12" width="100%" /></td>
|
|
44
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20180228.png" alt="Content Sync Pro - screenshot 10" width="100%" /></td>
|
|
45
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20180251.png" alt="Content Sync Pro - screenshot 11" width="100%" /></td>
|
|
46
|
+
<td width="33%"><img src="https://raw.githubusercontent.com/eharain/strapi-content-sync-pro/master/docs/Screenshot%202026-04-20%20180301.png" alt="Content Sync Pro - screenshot 12" width="100%" /></td>
|
|
43
47
|
</tr>
|
|
44
48
|
</table>
|
|
45
49
|
</details>
|
|
46
50
|
|
|
47
51
|
## Features
|
|
48
52
|
|
|
49
|
-
- **
|
|
53
|
+
- **Deployment Modes** - Paired mode (plugin on both servers) or Single-side mode (plugin only on local server).
|
|
54
|
+
- **Bi-directional Content Sync** - Push, pull, or sync both ways (Local wins, Remote wins, or Latest wins) in paired mode.
|
|
50
55
|
- **Media Sync** - Full media synchronization via HTTP (URL-based) or host-level file copy (`rsync`). Includes MIME type filtering and concurrency controls.
|
|
51
56
|
- **Sync Profiles** - Define WHAT to sync with field-level control (Advanced mode) or preset modes.
|
|
52
|
-
- **Execution Modes** - On-demand, Scheduled (interval, timeout, cron, or external scheduler),
|
|
57
|
+
- **Execution Modes** - On-demand, Scheduled (interval, timeout, cron, or external scheduler), Live (real-time), with per-profile execution controls.
|
|
53
58
|
- **Pagination & Large Dataset Support** - Built-in pagination ensures stable memory usage even when syncing thousands of records.
|
|
54
59
|
- **Dependency Analytics** - Automatically detects and syncs related entities and components in the correct order.
|
|
55
60
|
- **Enforcement Checks** - Pre-sync schema compatibility validation, version checks, and server time drift checks.
|
|
56
61
|
- **Alerts & Logging** - Detailed sync logs. Receive success/failure alerts via Email (using Strapi's email provider) or Webhooks.
|
|
62
|
+
- **Stats & Run Reports** - Local/remote counts and newest timestamps per content type, with before/after snapshots for each sync run.
|
|
63
|
+
- **Retention Controls** - Manual clear and automatic retention limits for logs and run reports.
|
|
57
64
|
- **Secure Communication** - API token authentication combined with HMAC-SHA256 request signing using a shared secret.
|
|
58
65
|
|
|
59
|
-
##
|
|
66
|
+
## Prerequisites
|
|
60
67
|
|
|
61
68
|
- Strapi v5.0.0 or higher
|
|
62
69
|
- Node.js 20.0.0 or higher
|
|
@@ -100,20 +107,29 @@ npm run develop
|
|
|
100
107
|
2. Go to **Configuration** tab
|
|
101
108
|
3. Enter your remote server details:
|
|
102
109
|
- **Base URL**: The remote Strapi instance URL (e.g., `https://api.example.com`)
|
|
103
|
-
- **API Token**: Generate from remote Strapi's Settings
|
|
110
|
+
- **API Token**: Generate from remote Strapi's Settings ? API Tokens
|
|
104
111
|
- **Instance ID**: Unique identifier for this instance
|
|
105
112
|
- **Shared Secret**: Same secret on both instances for HMAC signing
|
|
106
113
|
|
|
107
114
|
## Quick Start
|
|
108
115
|
|
|
109
|
-
### Step 1: Configure Connection
|
|
110
|
-
In
|
|
116
|
+
### Step 1: Choose Deployment Mode and Configure Connection
|
|
117
|
+
In **Configuration**, choose one mode:
|
|
118
|
+
- **Paired**: install and enable plugin on both local and remote servers.
|
|
119
|
+
- **Single-side**: install plugin only on local server (remote plugin routes not required).
|
|
120
|
+
|
|
121
|
+
Then configure Base URL, API Token, Instance ID, and Shared Secret.
|
|
111
122
|
|
|
112
123
|
### Step 2: Enable Content Types
|
|
113
124
|
In the **Content Types** tab, toggle on the content types you want to sync. Default profiles are auto-generated.
|
|
114
125
|
|
|
115
|
-
### Step 3:
|
|
116
|
-
In
|
|
126
|
+
### Step 3: Align Sync Settings on Both Servers
|
|
127
|
+
In **Content Types**, enable matching content types on both servers.
|
|
128
|
+
In **Sync Profiles**, set compatible direction/conflict strategy.
|
|
129
|
+
Then in **Sync**, configure execution mode and global page size.
|
|
130
|
+
|
|
131
|
+
### Step 4: Run Sync
|
|
132
|
+
In the **Sync** tab, click **Sync All Active Profiles** or run individual profiles.
|
|
117
133
|
|
|
118
134
|
## Sync Profiles
|
|
119
135
|
|
|
@@ -131,6 +147,20 @@ Configure individual field policies:
|
|
|
131
147
|
- **Pull** - Field only pulls from remote
|
|
132
148
|
- **Exclude** - Field is never synced
|
|
133
149
|
|
|
150
|
+
## Deployment Modes
|
|
151
|
+
|
|
152
|
+
### Paired mode
|
|
153
|
+
- Plugin installed on both local and remote servers.
|
|
154
|
+
- Supports push, pull, and bidirectional profiles.
|
|
155
|
+
- Supports on-demand, scheduled, and live execution modes.
|
|
156
|
+
- Connection test validates remote plugin endpoints.
|
|
157
|
+
|
|
158
|
+
### Single-side mode
|
|
159
|
+
- Plugin installed on local server only.
|
|
160
|
+
- Pull-only profiles are enforced.
|
|
161
|
+
- Live execution is disabled (use on-demand or scheduled).
|
|
162
|
+
- Connection test validates remote reachability and API token access without requiring remote plugin routes.
|
|
163
|
+
|
|
134
164
|
## Execution Modes
|
|
135
165
|
|
|
136
166
|
Configure **when** sync runs in the Sync tab:
|
|
@@ -158,6 +188,7 @@ Full media synchronization between Strapi instances:
|
|
|
158
188
|
- **rsync Strategy** — Host-level file copy using the `rsync` binary. Fastest for local-provider setups with SSH access.
|
|
159
189
|
- **Profile-based** — Create media sync profiles with direction, conflict strategy, MIME filters, filename patterns, and execution settings.
|
|
160
190
|
- **DB + File Sync** — Syncs both the `plugin::upload.file` database rows and the actual file bytes.
|
|
191
|
+
- **Morph Link Remapping** — Syncs `files_related_morphs` links by mapping file + related entities through documentId, then remapping to local numeric ids before insert.
|
|
161
192
|
|
|
162
193
|
## Enforcement
|
|
163
194
|
|
|
@@ -211,6 +242,24 @@ Sync products from a central catalog to multiple storefronts:
|
|
|
211
242
|
- Create "Full Pull" profile for `api::product.product`
|
|
212
243
|
- Set execution mode to "Scheduled" (every 5 minutes)
|
|
213
244
|
|
|
245
|
+
## Stats & Data Management
|
|
246
|
+
|
|
247
|
+
The **Stats** tab is split into two sub-tabs:
|
|
248
|
+
|
|
249
|
+
**Current Snapshot** — live local vs remote state per content type:
|
|
250
|
+
- Local vs remote record count
|
|
251
|
+
- Media files and media morph stats (local, plus remote where available)
|
|
252
|
+
- Newest record timestamp on each side and which side is newest (local, remote, equal)
|
|
253
|
+
- Search by UID, filter by type (content / media / media morph) or newest side, and paginate large result sets
|
|
254
|
+
|
|
255
|
+
**Run Reports** — before/after snapshots captured for each sync run:
|
|
256
|
+
- Filter by status (all / success / failed) and paginate server-side
|
|
257
|
+
- Expand any report to see the before and after row tables
|
|
258
|
+
|
|
259
|
+
A top action row (shared by both sub-tabs) provides:
|
|
260
|
+
- **Refresh Stats**, **Clear Logs**, **Clear Stats Reports**
|
|
261
|
+
- **Max Logs** / **Max Reports** retention limits with **Save & Apply Retention** (also enforced automatically after each sync run)
|
|
262
|
+
|
|
214
263
|
## Troubleshooting
|
|
215
264
|
|
|
216
265
|
### Common Issues
|
|
@@ -218,8 +267,10 @@ Sync products from a central catalog to multiple storefronts:
|
|
|
218
267
|
| Error | Solution |
|
|
219
268
|
|-------|----------|
|
|
220
269
|
| "Remote server not configured" | Add Base URL and API Token in Configuration |
|
|
221
|
-
| "401 Unauthorized" | Regenerate API token
|
|
222
|
-
| "HMAC verification failed" | Ensure shared secret matches on both instances |
|
|
270
|
+
| "401 Unauthorized / 403 Forbidden" | Regenerate API token and verify required permissions for synced content types (and Upload permissions for media) |
|
|
271
|
+
| "HMAC verification failed" | Ensure shared secret matches on both instances in paired mode |
|
|
272
|
+
| "Content type endpoint not found" | In paired mode, ensure matching content-type definitions and enabled API routes on both instances |
|
|
273
|
+
| "Live mode not available" | Switch to paired mode, or use on-demand/scheduled in single-side mode |
|
|
223
274
|
| "Schema mismatch" | Sync content type schemas or set enforcement to "compatible" |
|
|
224
275
|
|
|
225
276
|
### Viewing Logs
|
|
@@ -230,6 +281,14 @@ Check the **Logs** tab for detailed sync history including:
|
|
|
230
281
|
- Direction (push/pull)
|
|
231
282
|
- Status and error messages
|
|
232
283
|
|
|
284
|
+
## Security & Privacy
|
|
285
|
+
|
|
286
|
+
- **No usage tracking.** This plugin does not collect, transmit, or store any analytics or telemetry data.
|
|
287
|
+
- **Credential handling.** The optional "Generate Token" feature lets you authenticate to **your own** remote Strapi server to create an API token. Credentials are sent directly from your browser to your server via the plugin's backend proxy, used once, and **never stored** on disk, in the database, or in memory after the request completes.
|
|
288
|
+
- **API Tokens** are encrypted at rest using Strapi's built-in store.
|
|
289
|
+
- **HMAC-SHA256** signatures protect all inter-instance requests from tampering.
|
|
290
|
+
- **Masked secrets** — API tokens and shared secrets are masked (`••••••••`) in all API responses.
|
|
291
|
+
|
|
233
292
|
## Contributing
|
|
234
293
|
|
|
235
294
|
Contributions are welcome! Please open an issue or submit a pull request.
|
|
@@ -242,4 +301,4 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
242
301
|
|
|
243
302
|
**Ejaz Husain Arain**
|
|
244
303
|
- GitHub: [@eharain](https://github.com/eharain)
|
|
245
|
-
- Email: eharain@yahoo.com
|
|
304
|
+
- Email: eharain@yahoo.com
|
|
@@ -30,6 +30,7 @@ const ConfigTab = () => {
|
|
|
30
30
|
apiToken: '',
|
|
31
31
|
instanceId: '',
|
|
32
32
|
sharedSecret: '',
|
|
33
|
+
syncMode: 'paired',
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
// Login modal state
|
|
@@ -131,6 +132,7 @@ const ConfigTab = () => {
|
|
|
131
132
|
if (config.apiToken && config.apiToken !== '••••••••') payload.apiToken = config.apiToken;
|
|
132
133
|
if (config.instanceId) payload.instanceId = config.instanceId;
|
|
133
134
|
if (config.sharedSecret && config.sharedSecret !== '••••••••') payload.sharedSecret = config.sharedSecret;
|
|
135
|
+
if (config.syncMode) payload.syncMode = config.syncMode;
|
|
134
136
|
|
|
135
137
|
await post(`/${PLUGIN_ID}/config`, payload);
|
|
136
138
|
setMessage({ type: 'success', text: 'Connection configuration saved' });
|
|
@@ -349,6 +351,11 @@ const ConfigTab = () => {
|
|
|
349
351
|
{/* Connection Tab */}
|
|
350
352
|
<Tabs.Content value="connection">
|
|
351
353
|
<Box>
|
|
354
|
+
<Box paddingBottom={4}>
|
|
355
|
+
<Alert variant="info" title="Deployment mode">
|
|
356
|
+
In <strong>Paired</strong> mode, install Content Sync Pro on both local and remote servers. In <strong>Single-side</strong> mode, install on local only; remote plugin routes are not required. Connection test behavior and allowed sync/execution options follow the selected mode.
|
|
357
|
+
</Alert>
|
|
358
|
+
</Box>
|
|
352
359
|
<Flex gap={6}>
|
|
353
360
|
{/* LEFT COLUMN: Remote Server */}
|
|
354
361
|
<Box flex="1">
|
|
@@ -362,7 +369,7 @@ const ConfigTab = () => {
|
|
|
362
369
|
value={config.baseUrl}
|
|
363
370
|
onChange={(e) => setConfig((p) => ({ ...p, baseUrl: e.target.value }))}
|
|
364
371
|
/>
|
|
365
|
-
<Field.Hint>URL of the Strapi server
|
|
372
|
+
<Field.Hint>URL of the remote Strapi server where this plugin is also installed</Field.Hint>
|
|
366
373
|
</Field.Root>
|
|
367
374
|
|
|
368
375
|
<Field.Root>
|
|
@@ -378,7 +385,7 @@ const ConfigTab = () => {
|
|
|
378
385
|
</Box>
|
|
379
386
|
|
|
380
387
|
</Flex>
|
|
381
|
-
<Field.Hint>
|
|
388
|
+
<Field.Hint>Remote API token with permissions for this plugin routes and synced content types</Field.Hint>
|
|
382
389
|
</Field.Root>
|
|
383
390
|
<Field.Root>
|
|
384
391
|
<Button
|
|
@@ -404,7 +411,7 @@ const ConfigTab = () => {
|
|
|
404
411
|
value={config.instanceId}
|
|
405
412
|
onChange={(e) => setConfig((p) => ({ ...p, instanceId: e.target.value }))}
|
|
406
413
|
/>
|
|
407
|
-
<Field.Hint>
|
|
414
|
+
<Field.Hint>Unique local instance name used in logs and sync traceability</Field.Hint>
|
|
408
415
|
</Field.Root>
|
|
409
416
|
|
|
410
417
|
<Field.Root>
|
|
@@ -415,7 +422,21 @@ const ConfigTab = () => {
|
|
|
415
422
|
value={config.sharedSecret}
|
|
416
423
|
onChange={(e) => setConfig((p) => ({ ...p, sharedSecret: e.target.value }))}
|
|
417
424
|
/>
|
|
418
|
-
<Field.Hint>Must match on both
|
|
425
|
+
<Field.Hint>Must match exactly on both local and remote plugin configurations</Field.Hint>
|
|
426
|
+
</Field.Root>
|
|
427
|
+
|
|
428
|
+
<Field.Root>
|
|
429
|
+
<Field.Label>Sync Mode</Field.Label>
|
|
430
|
+
<SingleSelect
|
|
431
|
+
value={config.syncMode || 'paired'}
|
|
432
|
+
onChange={(value) => setConfig((p) => ({ ...p, syncMode: value }))}
|
|
433
|
+
>
|
|
434
|
+
<SingleSelectOption value="paired">Paired (plugin on both servers)</SingleSelectOption>
|
|
435
|
+
<SingleSelectOption value="single_side">Single-side (plugin only on local)</SingleSelectOption>
|
|
436
|
+
</SingleSelect>
|
|
437
|
+
<Field.Hint>
|
|
438
|
+
Paired mode supports push/pull/bidirectional and remote plugin validation. Single-side mode is pull-focused and does not require plugin routes on remote.
|
|
439
|
+
</Field.Hint>
|
|
419
440
|
</Field.Root>
|
|
420
441
|
</Flex>
|
|
421
442
|
</Box>
|
|
@@ -476,9 +497,11 @@ const ConfigTab = () => {
|
|
|
476
497
|
<Modal.Title>Generate API Token</Modal.Title>
|
|
477
498
|
</Modal.Header>
|
|
478
499
|
<Modal.Body>
|
|
479
|
-
<Typography variant="omega" textColor="neutral600" paddingBottom={
|
|
500
|
+
<Typography variant="omega" textColor="neutral600" paddingBottom={2}>
|
|
480
501
|
Log in to <strong>{config.baseUrl}</strong> to automatically create an API token.
|
|
481
|
-
|
|
502
|
+
</Typography>
|
|
503
|
+
<Typography variant="pi" textColor="neutral500" paddingBottom={4}>
|
|
504
|
+
Your credentials are sent directly to your remote server, used once to create a token, and never stored on disk, in the database, or in memory.
|
|
482
505
|
</Typography>
|
|
483
506
|
|
|
484
507
|
<Flex direction="column" gap={4}>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Box, Typography, Tabs, Divider } from '@strapi/design-system';
|
|
2
2
|
|
|
3
|
-
const INTRO_VIDEO_URL = 'https://
|
|
4
|
-
const
|
|
3
|
+
const INTRO_VIDEO_URL = 'https://www.youtube.com/watch?v=hr3dD6dLgLQ';
|
|
4
|
+
const INTRO_VIDEO_THUMBNAIL = 'https://img.youtube.com/vi/hr3dD6dLgLQ/hqdefault.jpg';
|
|
5
5
|
|
|
6
6
|
const HelpSection = ({ title, children }) => (
|
|
7
7
|
<Box paddingBottom={6}>
|
|
@@ -38,7 +38,7 @@ export const HelpTab = () => {
|
|
|
38
38
|
<Box paddingBottom={4}>
|
|
39
39
|
<Typography variant="beta" tag="h2">Plugin Documentation</Typography>
|
|
40
40
|
<Typography variant="omega" textColor="neutral600">
|
|
41
|
-
|
|
41
|
+
End-to-end guide for configuring, securing, and operating Content Sync Pro across Strapi environments.
|
|
42
42
|
</Typography>
|
|
43
43
|
</Box>
|
|
44
44
|
|
|
@@ -50,6 +50,7 @@ export const HelpTab = () => {
|
|
|
50
50
|
<Tabs.Trigger value="sync-profiles">Sync Profiles</Tabs.Trigger>
|
|
51
51
|
<Tabs.Trigger value="execution">Sync Execution</Tabs.Trigger>
|
|
52
52
|
<Tabs.Trigger value="media">Media</Tabs.Trigger>
|
|
53
|
+
<Tabs.Trigger value="stats">Stats</Tabs.Trigger>
|
|
53
54
|
<Tabs.Trigger value="enforcement">Enforcement</Tabs.Trigger>
|
|
54
55
|
<Tabs.Trigger value="alerts">Alerts</Tabs.Trigger>
|
|
55
56
|
<Tabs.Trigger value="troubleshooting">Troubleshooting</Tabs.Trigger>
|
|
@@ -60,9 +61,7 @@ export const HelpTab = () => {
|
|
|
60
61
|
<Box paddingTop={4}>
|
|
61
62
|
<HelpSection title="Video walkthrough">
|
|
62
63
|
<Typography variant="omega" paddingBottom={3}>
|
|
63
|
-
Watch the
|
|
64
|
-
{' '}
|
|
65
|
-
<DocLink href={INTRO_VIDEO_URL}>YouTube</DocLink>
|
|
64
|
+
Watch the help video to get started:
|
|
66
65
|
</Typography>
|
|
67
66
|
|
|
68
67
|
<Box
|
|
@@ -71,22 +70,46 @@ export const HelpTab = () => {
|
|
|
71
70
|
padding={3}
|
|
72
71
|
style={{ maxWidth: '960px' }}
|
|
73
72
|
>
|
|
74
|
-
<
|
|
75
|
-
<Box
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
<a href={INTRO_VIDEO_URL} target="_blank" rel="noopener noreferrer" style={{ display: 'block', textDecoration: 'none' }}>
|
|
74
|
+
<Box style={{ position: 'relative', paddingTop: '56.25%', overflow: 'hidden', borderRadius: '4px' }}>
|
|
75
|
+
<img
|
|
76
|
+
src={INTRO_VIDEO_THUMBNAIL}
|
|
77
|
+
alt="Content Sync Pro — plugin intro"
|
|
78
|
+
style={{
|
|
79
|
+
position: 'absolute',
|
|
80
|
+
top: 0,
|
|
81
|
+
left: 0,
|
|
82
|
+
width: '100%',
|
|
83
|
+
height: '100%',
|
|
84
|
+
objectFit: 'cover',
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
<Box
|
|
88
|
+
style={{
|
|
89
|
+
position: 'absolute',
|
|
90
|
+
top: '50%',
|
|
91
|
+
left: '50%',
|
|
92
|
+
transform: 'translate(-50%, -50%)',
|
|
93
|
+
width: '68px',
|
|
94
|
+
height: '48px',
|
|
95
|
+
backgroundColor: 'rgba(255,0,0,0.85)',
|
|
96
|
+
borderRadius: '12px',
|
|
97
|
+
display: 'flex',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
justifyContent: 'center',
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="white">
|
|
103
|
+
<polygon points="8,5 20,12 8,19" />
|
|
104
|
+
</svg>
|
|
105
|
+
</Box>
|
|
106
|
+
</Box>
|
|
107
|
+
</a>
|
|
108
|
+
<Box paddingTop={2}>
|
|
109
|
+
<Typography variant="pi" textColor="neutral500">
|
|
110
|
+
▶ Watch the help video on{' '}
|
|
111
|
+
<DocLink href={INTRO_VIDEO_URL}>YouTube</DocLink>
|
|
112
|
+
</Typography>
|
|
90
113
|
</Box>
|
|
91
114
|
</Box>
|
|
92
115
|
</HelpSection>
|
|
@@ -102,9 +125,10 @@ export const HelpTab = () => {
|
|
|
102
125
|
<ul style={{ paddingLeft: '20px', marginTop: '8px', lineHeight: '1.8' }}>
|
|
103
126
|
<li><Typography variant="omega">Bi-directional sync (push, pull, or both)</Typography></li>
|
|
104
127
|
<li><Typography variant="omega">Sync Profiles for defining WHAT to sync</Typography></li>
|
|
105
|
-
<li><Typography variant="omega">Execution modes: On-demand, Scheduled, or
|
|
128
|
+
<li><Typography variant="omega">Execution modes: On-demand, Scheduled, Live, or External scheduler</Typography></li>
|
|
106
129
|
<li><Typography variant="omega">Field-level sync policies (Advanced mode)</Typography></li>
|
|
107
130
|
<li><Typography variant="omega">Conflict resolution strategies (Latest, Local, Remote wins)</Typography></li>
|
|
131
|
+
<li><Typography variant="omega">Pagination support for large datasets with bounded memory usage</Typography></li>
|
|
108
132
|
<li><Typography variant="omega">Dependency resolution - sync related entities automatically</Typography></li>
|
|
109
133
|
<li><Typography variant="omega">Enforcement checks - schema, version, and time validation</Typography></li>
|
|
110
134
|
<li><Typography variant="omega">Configurable alerts via email, webhook, or Strapi logs</Typography></li>
|
|
@@ -126,10 +150,10 @@ export const HelpTab = () => {
|
|
|
126
150
|
|
|
127
151
|
<HelpSection title="Quick Start">
|
|
128
152
|
<ol style={{ paddingLeft: '20px', lineHeight: '2' }}>
|
|
129
|
-
<li><Typography variant="omega"><strong>Configuration Tab</strong> - Set up remote server URL, API token, and shared secret</Typography></li>
|
|
153
|
+
<li><Typography variant="omega"><strong>Configuration Tab</strong> - Set up remote server URL, API token, instance ID, and shared secret</Typography></li>
|
|
130
154
|
<li><Typography variant="omega"><strong>Content Types Tab</strong> - Enable content types for sync (auto-generates default profiles)</Typography></li>
|
|
131
155
|
<li><Typography variant="omega"><strong>Sync Profiles Tab</strong> - Customize sync behavior or use defaults</Typography></li>
|
|
132
|
-
<li><Typography variant="omega"><strong>Sync Tab</strong> - Configure execution settings and run sync operations</Typography></li>
|
|
156
|
+
<li><Typography variant="omega"><strong>Sync Execution Tab</strong> - Configure execution settings, page size, and run sync operations</Typography></li>
|
|
133
157
|
</ol>
|
|
134
158
|
</HelpSection>
|
|
135
159
|
|
|
@@ -148,6 +172,14 @@ export const HelpTab = () => {
|
|
|
148
172
|
<Typography variant="omega" paddingBottom={2}>
|
|
149
173
|
Configure the connection to the remote Strapi instance in the <strong>Connection</strong> sub-tab.
|
|
150
174
|
</Typography>
|
|
175
|
+
<Typography variant="omega" paddingBottom={2}>
|
|
176
|
+
<strong>Deployment modes:</strong> Use <strong>Paired</strong> mode when the plugin is installed on both servers,
|
|
177
|
+
or <strong>Single-side</strong> mode when the plugin is installed only on local server.
|
|
178
|
+
</Typography>
|
|
179
|
+
<Typography variant="omega" paddingBottom={2}>
|
|
180
|
+
In paired mode, connection test validates remote plugin reachability and token access. In single-side mode,
|
|
181
|
+
test validates remote reachability and content API token access without requiring remote plugin endpoints.
|
|
182
|
+
</Typography>
|
|
151
183
|
|
|
152
184
|
<Box background="neutral100" padding={4} hasRadius marginBottom={4}>
|
|
153
185
|
<Typography variant="sigma" textColor="neutral800">Base URL</Typography>
|
|
@@ -273,6 +305,9 @@ http://localhost:1337</CodeBlock>
|
|
|
273
305
|
Sync Profiles define <strong>WHAT</strong> to sync and <strong>HOW</strong> conflicts are resolved.
|
|
274
306
|
They do NOT control when sync runs - that's configured in the Sync tab (Execution).
|
|
275
307
|
</Typography>
|
|
308
|
+
<Typography variant="omega" paddingTop={2}>
|
|
309
|
+
In <strong>Single-side</strong> mode, profiles are automatically restricted to <strong>Pull Only</strong>.
|
|
310
|
+
</Typography>
|
|
276
311
|
<Typography variant="omega" paddingTop={2}>
|
|
277
312
|
Each profile specifies:
|
|
278
313
|
</Typography>
|
|
@@ -503,7 +538,7 @@ http://localhost:1337</CodeBlock>
|
|
|
503
538
|
Uses lifecycle hooks to detect changes.
|
|
504
539
|
</Typography>
|
|
505
540
|
<Typography variant="pi" textColor="warning600" paddingTop={2}>
|
|
506
|
-
Note: Increases server load. Use for critical content only.
|
|
541
|
+
Note: Increases server load. Use for critical content only. Live mode is available in paired mode and disabled in single-side mode.
|
|
507
542
|
</Typography>
|
|
508
543
|
</Box>
|
|
509
544
|
</HelpSection>
|
|
@@ -610,6 +645,10 @@ http://localhost:1337</CodeBlock>
|
|
|
610
645
|
<Typography variant="omega">
|
|
611
646
|
Each profile can sync two distinct aspects of media:
|
|
612
647
|
</Typography>
|
|
648
|
+
<Typography variant="omega" paddingTop={2}>
|
|
649
|
+
For entity-linked media consistency, this plugin also syncs media morph links using <strong>documentId-based remapping</strong>
|
|
650
|
+
(file documentId and related entity documentId are resolved to local numeric ids before insert).
|
|
651
|
+
</Typography>
|
|
613
652
|
<Box background="neutral100" padding={4} hasRadius marginTop={2} marginBottom={2}>
|
|
614
653
|
<Typography variant="sigma" textColor="neutral800">DB Rows (Metadata)</Typography>
|
|
615
654
|
<Typography variant="omega" paddingTop={1}>
|
|
@@ -630,6 +669,11 @@ http://localhost:1337</CodeBlock>
|
|
|
630
669
|
If you only need metadata references (e.g., both sides use the same S3 bucket), you can
|
|
631
670
|
sync DB rows only.
|
|
632
671
|
</Typography>
|
|
672
|
+
<Typography variant="pi" textColor="neutral600" paddingTop={2}>
|
|
673
|
+
Note: Strapi components, repeatable components, and dynamic zones are already tracked by
|
|
674
|
+
document service sync using stable documentIds, so they do not require id-to-documentId remapping
|
|
675
|
+
like upload morph tables do.
|
|
676
|
+
</Typography>
|
|
633
677
|
</HelpSection>
|
|
634
678
|
|
|
635
679
|
<HelpSection title="URL strategy (HTTP)">
|
|
@@ -698,6 +742,61 @@ http://localhost:1337</CodeBlock>
|
|
|
698
742
|
</Box>
|
|
699
743
|
</Tabs.Content>
|
|
700
744
|
|
|
745
|
+
{/* Stats Tab */}
|
|
746
|
+
<Tabs.Content value="stats">
|
|
747
|
+
<Box paddingTop={4}>
|
|
748
|
+
<HelpSection title="Database Stats Overview">
|
|
749
|
+
<Typography variant="omega">
|
|
750
|
+
The Stats tab compares local and remote data state per content type, including content
|
|
751
|
+
entries, media files, and media morph (relation) links. The view is split into two
|
|
752
|
+
sub-tabs: <strong>Current Snapshot</strong> and <strong>Run Reports</strong>.
|
|
753
|
+
</Typography>
|
|
754
|
+
</HelpSection>
|
|
755
|
+
|
|
756
|
+
<HelpSection title="Current Snapshot Tab">
|
|
757
|
+
<Typography variant="omega">
|
|
758
|
+
Shows the latest live counts and newest timestamps per content type, with the newest
|
|
759
|
+
side (local / remote / equal) highlighted. Use the controls above the table to drill in:
|
|
760
|
+
</Typography>
|
|
761
|
+
<ul style={{ paddingLeft: '20px', marginTop: '8px', lineHeight: '1.8' }}>
|
|
762
|
+
<li><Typography variant="omega"><strong>Search</strong> - Filter rows by UID substring.</Typography></li>
|
|
763
|
+
<li><Typography variant="omega"><strong>Type</strong> - Filter by Content, Media, or Media Morph.</Typography></li>
|
|
764
|
+
<li><Typography variant="omega"><strong>Newest side</strong> - Show only rows where local, remote, or both are newest.</Typography></li>
|
|
765
|
+
<li><Typography variant="omega"><strong>Page size / pagination</strong> - Browse large snapshots without scrolling.</Typography></li>
|
|
766
|
+
</ul>
|
|
767
|
+
</HelpSection>
|
|
768
|
+
|
|
769
|
+
<HelpSection title="Run Reports Tab (Before vs After)">
|
|
770
|
+
<Typography variant="omega">
|
|
771
|
+
Before every sync run, the plugin captures a pre-run snapshot. After the run completes,
|
|
772
|
+
it captures a post-run snapshot and stores both in a report so you can review sync impact
|
|
773
|
+
and trends over time.
|
|
774
|
+
</Typography>
|
|
775
|
+
<ul style={{ paddingLeft: '20px', marginTop: '8px', lineHeight: '1.8' }}>
|
|
776
|
+
<li><Typography variant="omega"><strong>Status filter</strong> - Show all runs, or only success / failed.</Typography></li>
|
|
777
|
+
<li><Typography variant="omega"><strong>Page size / pagination</strong> - Reports are paginated server-side.</Typography></li>
|
|
778
|
+
<li><Typography variant="omega"><strong>Show details</strong> - Expand a report card to see the before/after row tables (first {25} rows per side).</Typography></li>
|
|
779
|
+
</ul>
|
|
780
|
+
</HelpSection>
|
|
781
|
+
|
|
782
|
+
<HelpSection title="Top Action Row: Refresh, Clear & Retention">
|
|
783
|
+
<Typography variant="omega">
|
|
784
|
+
The top of the Stats tab exposes the controls that apply to both sub-tabs:
|
|
785
|
+
</Typography>
|
|
786
|
+
<ul style={{ paddingLeft: '20px', marginTop: '8px', lineHeight: '1.8' }}>
|
|
787
|
+
<li><Typography variant="omega"><strong>Refresh Stats</strong> - Reload the snapshot and reports.</Typography></li>
|
|
788
|
+
<li><Typography variant="omega"><strong>Clear Logs</strong> - Remove stored sync logs.</Typography></li>
|
|
789
|
+
<li><Typography variant="omega"><strong>Clear Stats Reports</strong> - Remove all before/after run reports.</Typography></li>
|
|
790
|
+
<li><Typography variant="omega"><strong>Max Logs / Max Reports</strong> - Retention limits; older entries are pruned when exceeded.</Typography></li>
|
|
791
|
+
<li><Typography variant="omega"><strong>Save & Apply Retention</strong> - Persists the limits and immediately prunes old data.</Typography></li>
|
|
792
|
+
</ul>
|
|
793
|
+
<Typography variant="pi" textColor="neutral600" paddingTop={2}>
|
|
794
|
+
Retention is also enforced automatically after each sync run.
|
|
795
|
+
</Typography>
|
|
796
|
+
</HelpSection>
|
|
797
|
+
</Box>
|
|
798
|
+
</Tabs.Content>
|
|
799
|
+
|
|
701
800
|
{/* Enforcement Tab */}
|
|
702
801
|
<Tabs.Content value="enforcement">
|
|
703
802
|
<Box paddingTop={4}>
|
|
@@ -875,10 +974,10 @@ http://localhost:1337</CodeBlock>
|
|
|
875
974
|
</Box>
|
|
876
975
|
|
|
877
976
|
<Box background="danger100" padding={4} hasRadius marginBottom={4}>
|
|
878
|
-
<Typography variant="sigma" textColor="danger700">401 Unauthorized</Typography>
|
|
977
|
+
<Typography variant="sigma" textColor="danger700">401 Unauthorized / 403 Forbidden</Typography>
|
|
879
978
|
<Typography variant="omega" paddingTop={1}>
|
|
880
|
-
The API token is invalid or
|
|
881
|
-
|
|
979
|
+
The API token is invalid, expired, or missing required permissions. Generate a new token on the remote
|
|
980
|
+
server and ensure it can access the synced content types (and Upload permissions for media sync).
|
|
882
981
|
</Typography>
|
|
883
982
|
</Box>
|
|
884
983
|
|
|
@@ -891,10 +990,10 @@ http://localhost:1337</CodeBlock>
|
|
|
891
990
|
</Box>
|
|
892
991
|
|
|
893
992
|
<Box background="danger100" padding={4} hasRadius marginBottom={4}>
|
|
894
|
-
<Typography variant="sigma" textColor="danger700">Content type not found on remote</Typography>
|
|
993
|
+
<Typography variant="sigma" textColor="danger700">Content type endpoint not found on remote</Typography>
|
|
895
994
|
<Typography variant="omega" paddingTop={1}>
|
|
896
|
-
The content type exists locally but
|
|
897
|
-
have matching content type definitions.
|
|
995
|
+
The content type exists locally but the remote REST endpoint is missing or named differently.
|
|
996
|
+
Ensure both instances have matching content type definitions and API routes are enabled.
|
|
898
997
|
</Typography>
|
|
899
998
|
</Box>
|
|
900
999
|
|
|
@@ -400,6 +400,13 @@ const MediaTab = () => {
|
|
|
400
400
|
{status?.lastResult && (
|
|
401
401
|
<Box paddingTop={3} background="neutral0" padding={4} hasRadius shadow="tableShadow">
|
|
402
402
|
<Typography variant="sigma">Last Run Result</Typography>
|
|
403
|
+
{(status.lastResult.morphLinksApplied !== undefined || status.lastResult.morphLinksSkipped !== undefined) && (
|
|
404
|
+
<Box paddingTop={2} paddingBottom={2}>
|
|
405
|
+
<Typography variant="omega" textColor="neutral700">
|
|
406
|
+
Morph links synced: applied {status.lastResult.morphLinksApplied || 0}, skipped {status.lastResult.morphLinksSkipped || 0}
|
|
407
|
+
</Typography>
|
|
408
|
+
</Box>
|
|
409
|
+
)}
|
|
403
410
|
<Typography variant="pi" style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}>
|
|
404
411
|
{JSON.stringify(status.lastResult, null, 2)}
|
|
405
412
|
</Typography>
|