strapi-bulk-publish 1.0.0 → 1.1.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 +35 -8
- package/dist/_chunks/{HomePage-C-cyV6ij.mjs → HomePage-CCD8Pl7z.mjs} +1 -1
- package/dist/_chunks/{HomePage-DBJToziQ.js → HomePage-Rymin2Ze.js} +1 -1
- package/dist/_chunks/SettingsPage-B99xmYKO.js +200 -0
- package/dist/_chunks/SettingsPage-r2PCnoy5.mjs +200 -0
- package/dist/_chunks/{en-ua_GGXUg.mjs → en-BqCcy0LI.mjs} +21 -4
- package/dist/_chunks/{en-BU8SCgAJ.js → en-BuqYI-YP.js} +21 -4
- package/dist/_chunks/{index-BBZKQRB2.js → index-CPTlV111.js} +3 -3
- package/dist/_chunks/{index-CBDMXg1c.mjs → index-otKZLiQd.mjs} +3 -3
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +121 -38
- package/dist/server/index.mjs +121 -38
- package/package.json +1 -1
- package/dist/_chunks/SettingsPage-BpvQogmY.mjs +0 -75
- package/dist/_chunks/SettingsPage-D6yyZjsD.js +0 -75
package/README.md
CHANGED
|
@@ -11,7 +11,9 @@ Strapi 5 plugin to bulk publish content across all locales with a single webhook
|
|
|
11
11
|
- Batch selection and one-click publish across all locales
|
|
12
12
|
- Configurable content type and display field
|
|
13
13
|
- Auto-detection of default locale from Strapi i18n settings
|
|
14
|
-
-
|
|
14
|
+
- Flexible webhook with preset formats: **Generic JSON** and **GitLab Pipeline Trigger**
|
|
15
|
+
- Token-based authentication for webhooks
|
|
16
|
+
- Configurable key-value variables sent with webhook requests
|
|
15
17
|
- SSRF protection for webhook URLs
|
|
16
18
|
- Confirmation dialog before bulk actions
|
|
17
19
|
- Custom admin permissions
|
|
@@ -40,7 +42,6 @@ export default ({ env }) => ({
|
|
|
40
42
|
config: {
|
|
41
43
|
contentType: 'api::blog-post.blog-post', // required — your content type UID
|
|
42
44
|
titleField: 'title', // optional, default: 'title'
|
|
43
|
-
webhookUrl: '', // optional, can also be set via Settings UI
|
|
44
45
|
},
|
|
45
46
|
},
|
|
46
47
|
});
|
|
@@ -52,13 +53,14 @@ export default ({ env }) => ({
|
|
|
52
53
|
|--------|------|----------|---------|-------------|
|
|
53
54
|
| `contentType` | `string` | **yes** | — | Strapi content type UID (e.g. `api::article.article`) |
|
|
54
55
|
| `titleField` | `string` | no | `'title'` | Field name used as the display title in the admin list |
|
|
55
|
-
| `webhookUrl` | `string` | no | `''` | Initial webhook URL; can be changed later in Settings UI |
|
|
56
|
-
|
|
57
|
-
The `webhookUrl` set in config serves as the initial seed value. Once changed through the admin Settings page, the UI value takes precedence.
|
|
58
56
|
|
|
59
57
|
## Webhook
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
Configure the webhook in **Settings > Bulk Publish > Webhook**. Two formats are supported:
|
|
60
|
+
|
|
61
|
+
### Generic JSON
|
|
62
|
+
|
|
63
|
+
Sends a `POST` request with `Content-Type: application/json`:
|
|
62
64
|
|
|
63
65
|
```json
|
|
64
66
|
{
|
|
@@ -68,7 +70,32 @@ After publishing, a single POST request is sent to the configured webhook URL:
|
|
|
68
70
|
}
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
If a token is configured, it is sent as an `Authorization: Bearer <token>` header. Any custom variables are merged into the JSON body as top-level keys.
|
|
74
|
+
|
|
75
|
+
### GitLab Pipeline Trigger
|
|
76
|
+
|
|
77
|
+
Sends a `POST` request with `Content-Type: multipart/form-data`, matching the [GitLab pipeline trigger API](https://docs.gitlab.com/ee/ci/triggers/):
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
token=<trigger-token>
|
|
81
|
+
ref=<branch>
|
|
82
|
+
variables[BULK_PUBLISH_EVENT]=bulk-publish
|
|
83
|
+
variables[BULK_PUBLISH_POSTS]=docId1,docId2
|
|
84
|
+
variables[BULK_PUBLISH_DATE]=2026-05-08T12:00:00.000Z
|
|
85
|
+
variables[YOUR_CUSTOM_VAR]=value
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Webhook Settings
|
|
89
|
+
|
|
90
|
+
| Field | Description |
|
|
91
|
+
|-------|-------------|
|
|
92
|
+
| **Preset** | `Generic JSON` or `GitLab Pipeline Trigger` |
|
|
93
|
+
| **URL** | Webhook endpoint URL |
|
|
94
|
+
| **Token** | Auth token (Bearer header for generic, trigger token for GitLab) |
|
|
95
|
+
| **Branch Ref** | Git branch for GitLab pipeline trigger (GitLab only) |
|
|
96
|
+
| **Variables** | Key-value pairs sent as extra fields with every request |
|
|
97
|
+
|
|
98
|
+
The webhook request has a 10-second timeout. Private/internal URLs (localhost, private IP ranges) are blocked for SSRF protection.
|
|
72
99
|
|
|
73
100
|
## Permissions
|
|
74
101
|
|
|
@@ -77,7 +104,7 @@ Configure in Settings > Roles:
|
|
|
77
104
|
| Action | Purpose |
|
|
78
105
|
|--------|---------|
|
|
79
106
|
| `plugin::bulk-publish.publish` | Access bulk publish page and publish documents |
|
|
80
|
-
| `plugin::bulk-publish.settings` | View and edit webhook
|
|
107
|
+
| `plugin::bulk-publish.settings` | View and edit webhook settings |
|
|
81
108
|
|
|
82
109
|
## Prerequisites
|
|
83
110
|
|
|
@@ -4,7 +4,7 @@ import { u as useIntl } from "./index-CEh8vkxY.mjs";
|
|
|
4
4
|
import { Flex, Badge, Main, Box, Typography, Dialog, Button, Loader, Table, Thead, Tr, Th, Checkbox, Tbody, Td } from "@strapi/design-system";
|
|
5
5
|
import { WarningCircle } from "@strapi/icons";
|
|
6
6
|
import { useFetchClient, useNotification, Page } from "@strapi/strapi/admin";
|
|
7
|
-
import { P as PLUGIN_ID, p as pluginPermissions } from "./index-
|
|
7
|
+
import { P as PLUGIN_ID, p as pluginPermissions } from "./index-otKZLiQd.mjs";
|
|
8
8
|
const statusColors = {
|
|
9
9
|
draft: { textColor: "success700", backgroundColor: "success100" },
|
|
10
10
|
published: { textColor: "primary700", backgroundColor: "primary100" },
|
|
@@ -6,7 +6,7 @@ const index = require("./index-DkTxsEqL.js");
|
|
|
6
6
|
const designSystem = require("@strapi/design-system");
|
|
7
7
|
const icons = require("@strapi/icons");
|
|
8
8
|
const admin = require("@strapi/strapi/admin");
|
|
9
|
-
const index$1 = require("./index-
|
|
9
|
+
const index$1 = require("./index-CPTlV111.js");
|
|
10
10
|
const statusColors = {
|
|
11
11
|
draft: { textColor: "success700", backgroundColor: "success100" },
|
|
12
12
|
published: { textColor: "primary700", backgroundColor: "primary100" },
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const index = require("./index-DkTxsEqL.js");
|
|
6
|
+
const designSystem = require("@strapi/design-system");
|
|
7
|
+
const icons = require("@strapi/icons");
|
|
8
|
+
const admin = require("@strapi/strapi/admin");
|
|
9
|
+
const index$1 = require("./index-CPTlV111.js");
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
preset: "generic",
|
|
12
|
+
url: "",
|
|
13
|
+
token: "",
|
|
14
|
+
ref: "main",
|
|
15
|
+
variables: []
|
|
16
|
+
};
|
|
17
|
+
const SettingsPage = () => {
|
|
18
|
+
const { formatMessage } = index.useIntl();
|
|
19
|
+
const { get, put } = admin.useFetchClient();
|
|
20
|
+
const { toggleNotification } = admin.useNotification();
|
|
21
|
+
const [config, setConfig] = React.useState(DEFAULT_CONFIG);
|
|
22
|
+
const [initialConfig, setInitialConfig] = React.useState("");
|
|
23
|
+
const [loading, setLoading] = React.useState(true);
|
|
24
|
+
const [saving, setSaving] = React.useState(false);
|
|
25
|
+
const fetchSettings = React.useCallback(async () => {
|
|
26
|
+
try {
|
|
27
|
+
const { data } = await get(`/${index$1.PLUGIN_ID}/settings`);
|
|
28
|
+
const fetched = {
|
|
29
|
+
preset: data.data?.preset || "generic",
|
|
30
|
+
url: data.data?.url || "",
|
|
31
|
+
token: data.data?.token || "",
|
|
32
|
+
ref: data.data?.ref || "main",
|
|
33
|
+
variables: data.data?.variables || []
|
|
34
|
+
};
|
|
35
|
+
setConfig(fetched);
|
|
36
|
+
setInitialConfig(JSON.stringify(fetched));
|
|
37
|
+
} catch {
|
|
38
|
+
toggleNotification({
|
|
39
|
+
type: "danger",
|
|
40
|
+
message: formatMessage({ id: `${index$1.PLUGIN_ID}.notification.settings.error` })
|
|
41
|
+
});
|
|
42
|
+
} finally {
|
|
43
|
+
setLoading(false);
|
|
44
|
+
}
|
|
45
|
+
}, [get, toggleNotification, formatMessage]);
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
fetchSettings();
|
|
48
|
+
}, [fetchSettings]);
|
|
49
|
+
const handleSave = async () => {
|
|
50
|
+
try {
|
|
51
|
+
setSaving(true);
|
|
52
|
+
await put(`/${index$1.PLUGIN_ID}/settings`, config);
|
|
53
|
+
setInitialConfig(JSON.stringify(config));
|
|
54
|
+
toggleNotification({
|
|
55
|
+
type: "success",
|
|
56
|
+
message: formatMessage({ id: `${index$1.PLUGIN_ID}.notification.settings.success` })
|
|
57
|
+
});
|
|
58
|
+
} catch {
|
|
59
|
+
toggleNotification({
|
|
60
|
+
type: "danger",
|
|
61
|
+
message: formatMessage({ id: `${index$1.PLUGIN_ID}.notification.settings.error` })
|
|
62
|
+
});
|
|
63
|
+
} finally {
|
|
64
|
+
setSaving(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const updateField = (key, value) => {
|
|
68
|
+
setConfig((prev) => ({ ...prev, [key]: value }));
|
|
69
|
+
};
|
|
70
|
+
const addVariable = () => {
|
|
71
|
+
setConfig((prev) => ({
|
|
72
|
+
...prev,
|
|
73
|
+
variables: [...prev.variables, { key: "", value: "" }]
|
|
74
|
+
}));
|
|
75
|
+
};
|
|
76
|
+
const updateVariable = (index2, field, val) => {
|
|
77
|
+
setConfig((prev) => ({
|
|
78
|
+
...prev,
|
|
79
|
+
variables: prev.variables.map((v, i) => i === index2 ? { ...v, [field]: val } : v)
|
|
80
|
+
}));
|
|
81
|
+
};
|
|
82
|
+
const removeVariable = (index2) => {
|
|
83
|
+
setConfig((prev) => ({
|
|
84
|
+
...prev,
|
|
85
|
+
variables: prev.variables.filter((_, i) => i !== index2)
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
const hasChanged = JSON.stringify(config) !== initialConfig;
|
|
89
|
+
const isGitlab = config.preset === "gitlab";
|
|
90
|
+
const msg = (id) => formatMessage({ id: `${index$1.PLUGIN_ID}.${id}` });
|
|
91
|
+
return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Protect, { permissions: index$1.pluginPermissions.settings, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { children: loading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", paddingTop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: msg("loading.settings") }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
92
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 8, paddingBottom: 4, paddingLeft: 10, paddingRight: 10, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
93
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
94
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: msg("settings.title") }),
|
|
95
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: msg("settings.subtitle") })
|
|
96
|
+
] }),
|
|
97
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleSave, disabled: !hasChanged || saving, loading: saving, children: msg("button.save") })
|
|
98
|
+
] }) }),
|
|
99
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 10, paddingRight: 10, paddingBottom: 10, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", padding: 6, shadow: "tableShadow", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, children: [
|
|
100
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "preset", children: [
|
|
101
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: msg("webhook.preset.label") }),
|
|
102
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
103
|
+
designSystem.SingleSelect,
|
|
104
|
+
{
|
|
105
|
+
value: config.preset,
|
|
106
|
+
onChange: (value) => updateField("preset", value),
|
|
107
|
+
children: [
|
|
108
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "generic", children: msg("webhook.preset.generic") }),
|
|
109
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "gitlab", children: msg("webhook.preset.gitlab") })
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
] }),
|
|
114
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
115
|
+
designSystem.TextInput,
|
|
116
|
+
{
|
|
117
|
+
label: msg("webhook.url.label"),
|
|
118
|
+
placeholder: isGitlab ? msg("webhook.url.placeholder.gitlab") : msg("webhook.url.placeholder"),
|
|
119
|
+
hint: msg("webhook.url.hint"),
|
|
120
|
+
name: "url",
|
|
121
|
+
value: config.url,
|
|
122
|
+
onChange: (e) => updateField("url", e.target.value)
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
126
|
+
designSystem.TextInput,
|
|
127
|
+
{
|
|
128
|
+
label: msg("webhook.token.label"),
|
|
129
|
+
placeholder: msg("webhook.token.placeholder"),
|
|
130
|
+
hint: isGitlab ? msg("webhook.token.hint.gitlab") : msg("webhook.token.hint"),
|
|
131
|
+
name: "token",
|
|
132
|
+
type: "password",
|
|
133
|
+
value: config.token,
|
|
134
|
+
onChange: (e) => updateField("token", e.target.value)
|
|
135
|
+
}
|
|
136
|
+
),
|
|
137
|
+
isGitlab && /* @__PURE__ */ jsxRuntime.jsx(
|
|
138
|
+
designSystem.TextInput,
|
|
139
|
+
{
|
|
140
|
+
label: msg("webhook.ref.label"),
|
|
141
|
+
placeholder: msg("webhook.ref.placeholder"),
|
|
142
|
+
hint: msg("webhook.ref.hint"),
|
|
143
|
+
name: "ref",
|
|
144
|
+
value: config.ref,
|
|
145
|
+
onChange: (e) => updateField("ref", e.target.value)
|
|
146
|
+
}
|
|
147
|
+
),
|
|
148
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
149
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 2, children: [
|
|
150
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
151
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: msg("webhook.variables.label") }),
|
|
152
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", display: "block", children: isGitlab ? msg("webhook.variables.hint.gitlab") : msg("webhook.variables.hint") })
|
|
153
|
+
] }),
|
|
154
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
155
|
+
designSystem.Button,
|
|
156
|
+
{
|
|
157
|
+
variant: "tertiary",
|
|
158
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
|
|
159
|
+
onClick: addVariable,
|
|
160
|
+
size: "S",
|
|
161
|
+
children: msg("webhook.variables.add")
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
] }),
|
|
165
|
+
config.variables.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: config.variables.map((variable, index2) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 2, children: [
|
|
166
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 5, s: 12, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
167
|
+
designSystem.TextInput,
|
|
168
|
+
{
|
|
169
|
+
"aria-label": msg("webhook.variables.key"),
|
|
170
|
+
placeholder: msg("webhook.variables.key"),
|
|
171
|
+
name: `var-key-${index2}`,
|
|
172
|
+
value: variable.key,
|
|
173
|
+
onChange: (e) => updateVariable(index2, "key", e.target.value)
|
|
174
|
+
}
|
|
175
|
+
) }),
|
|
176
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
177
|
+
designSystem.TextInput,
|
|
178
|
+
{
|
|
179
|
+
"aria-label": msg("webhook.variables.value"),
|
|
180
|
+
placeholder: msg("webhook.variables.value"),
|
|
181
|
+
name: `var-value-${index2}`,
|
|
182
|
+
value: variable.value,
|
|
183
|
+
onChange: (e) => updateVariable(index2, "value", e.target.value)
|
|
184
|
+
}
|
|
185
|
+
) }),
|
|
186
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 1, s: 12, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
187
|
+
designSystem.IconButton,
|
|
188
|
+
{
|
|
189
|
+
onClick: () => removeVariable(index2),
|
|
190
|
+
label: "Delete",
|
|
191
|
+
variant: "ghost",
|
|
192
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
193
|
+
}
|
|
194
|
+
) })
|
|
195
|
+
] }, index2)) })
|
|
196
|
+
] })
|
|
197
|
+
] }) }) })
|
|
198
|
+
] }) }) });
|
|
199
|
+
};
|
|
200
|
+
exports.SettingsPage = SettingsPage;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback, useEffect } from "react";
|
|
3
|
+
import { u as useIntl } from "./index-CEh8vkxY.mjs";
|
|
4
|
+
import { Main, Flex, Loader, Box, Typography, Button, Field, SingleSelect, SingleSelectOption, TextInput, Grid, IconButton } from "@strapi/design-system";
|
|
5
|
+
import { Plus, Trash } from "@strapi/icons";
|
|
6
|
+
import { useFetchClient, useNotification, Page } from "@strapi/strapi/admin";
|
|
7
|
+
import { P as PLUGIN_ID, p as pluginPermissions } from "./index-otKZLiQd.mjs";
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
preset: "generic",
|
|
10
|
+
url: "",
|
|
11
|
+
token: "",
|
|
12
|
+
ref: "main",
|
|
13
|
+
variables: []
|
|
14
|
+
};
|
|
15
|
+
const SettingsPage = () => {
|
|
16
|
+
const { formatMessage } = useIntl();
|
|
17
|
+
const { get, put } = useFetchClient();
|
|
18
|
+
const { toggleNotification } = useNotification();
|
|
19
|
+
const [config, setConfig] = useState(DEFAULT_CONFIG);
|
|
20
|
+
const [initialConfig, setInitialConfig] = useState("");
|
|
21
|
+
const [loading, setLoading] = useState(true);
|
|
22
|
+
const [saving, setSaving] = useState(false);
|
|
23
|
+
const fetchSettings = useCallback(async () => {
|
|
24
|
+
try {
|
|
25
|
+
const { data } = await get(`/${PLUGIN_ID}/settings`);
|
|
26
|
+
const fetched = {
|
|
27
|
+
preset: data.data?.preset || "generic",
|
|
28
|
+
url: data.data?.url || "",
|
|
29
|
+
token: data.data?.token || "",
|
|
30
|
+
ref: data.data?.ref || "main",
|
|
31
|
+
variables: data.data?.variables || []
|
|
32
|
+
};
|
|
33
|
+
setConfig(fetched);
|
|
34
|
+
setInitialConfig(JSON.stringify(fetched));
|
|
35
|
+
} catch {
|
|
36
|
+
toggleNotification({
|
|
37
|
+
type: "danger",
|
|
38
|
+
message: formatMessage({ id: `${PLUGIN_ID}.notification.settings.error` })
|
|
39
|
+
});
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
}, [get, toggleNotification, formatMessage]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
fetchSettings();
|
|
46
|
+
}, [fetchSettings]);
|
|
47
|
+
const handleSave = async () => {
|
|
48
|
+
try {
|
|
49
|
+
setSaving(true);
|
|
50
|
+
await put(`/${PLUGIN_ID}/settings`, config);
|
|
51
|
+
setInitialConfig(JSON.stringify(config));
|
|
52
|
+
toggleNotification({
|
|
53
|
+
type: "success",
|
|
54
|
+
message: formatMessage({ id: `${PLUGIN_ID}.notification.settings.success` })
|
|
55
|
+
});
|
|
56
|
+
} catch {
|
|
57
|
+
toggleNotification({
|
|
58
|
+
type: "danger",
|
|
59
|
+
message: formatMessage({ id: `${PLUGIN_ID}.notification.settings.error` })
|
|
60
|
+
});
|
|
61
|
+
} finally {
|
|
62
|
+
setSaving(false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const updateField = (key, value) => {
|
|
66
|
+
setConfig((prev) => ({ ...prev, [key]: value }));
|
|
67
|
+
};
|
|
68
|
+
const addVariable = () => {
|
|
69
|
+
setConfig((prev) => ({
|
|
70
|
+
...prev,
|
|
71
|
+
variables: [...prev.variables, { key: "", value: "" }]
|
|
72
|
+
}));
|
|
73
|
+
};
|
|
74
|
+
const updateVariable = (index, field, val) => {
|
|
75
|
+
setConfig((prev) => ({
|
|
76
|
+
...prev,
|
|
77
|
+
variables: prev.variables.map((v, i) => i === index ? { ...v, [field]: val } : v)
|
|
78
|
+
}));
|
|
79
|
+
};
|
|
80
|
+
const removeVariable = (index) => {
|
|
81
|
+
setConfig((prev) => ({
|
|
82
|
+
...prev,
|
|
83
|
+
variables: prev.variables.filter((_, i) => i !== index)
|
|
84
|
+
}));
|
|
85
|
+
};
|
|
86
|
+
const hasChanged = JSON.stringify(config) !== initialConfig;
|
|
87
|
+
const isGitlab = config.preset === "gitlab";
|
|
88
|
+
const msg = (id) => formatMessage({ id: `${PLUGIN_ID}.${id}` });
|
|
89
|
+
return /* @__PURE__ */ jsx(Page.Protect, { permissions: pluginPermissions.settings, children: /* @__PURE__ */ jsx(Main, { children: loading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", paddingTop: 8, children: /* @__PURE__ */ jsx(Loader, { children: msg("loading.settings") }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
90
|
+
/* @__PURE__ */ jsx(Box, { paddingTop: 8, paddingBottom: 4, paddingLeft: 10, paddingRight: 10, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
91
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
92
|
+
/* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: msg("settings.title") }),
|
|
93
|
+
/* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", children: msg("settings.subtitle") })
|
|
94
|
+
] }),
|
|
95
|
+
/* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: !hasChanged || saving, loading: saving, children: msg("button.save") })
|
|
96
|
+
] }) }),
|
|
97
|
+
/* @__PURE__ */ jsx(Box, { paddingLeft: 10, paddingRight: 10, paddingBottom: 10, children: /* @__PURE__ */ jsx(Box, { background: "neutral0", padding: 6, shadow: "tableShadow", hasRadius: true, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, children: [
|
|
98
|
+
/* @__PURE__ */ jsxs(Field.Root, { name: "preset", children: [
|
|
99
|
+
/* @__PURE__ */ jsx(Field.Label, { children: msg("webhook.preset.label") }),
|
|
100
|
+
/* @__PURE__ */ jsxs(
|
|
101
|
+
SingleSelect,
|
|
102
|
+
{
|
|
103
|
+
value: config.preset,
|
|
104
|
+
onChange: (value) => updateField("preset", value),
|
|
105
|
+
children: [
|
|
106
|
+
/* @__PURE__ */ jsx(SingleSelectOption, { value: "generic", children: msg("webhook.preset.generic") }),
|
|
107
|
+
/* @__PURE__ */ jsx(SingleSelectOption, { value: "gitlab", children: msg("webhook.preset.gitlab") })
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
] }),
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
TextInput,
|
|
114
|
+
{
|
|
115
|
+
label: msg("webhook.url.label"),
|
|
116
|
+
placeholder: isGitlab ? msg("webhook.url.placeholder.gitlab") : msg("webhook.url.placeholder"),
|
|
117
|
+
hint: msg("webhook.url.hint"),
|
|
118
|
+
name: "url",
|
|
119
|
+
value: config.url,
|
|
120
|
+
onChange: (e) => updateField("url", e.target.value)
|
|
121
|
+
}
|
|
122
|
+
),
|
|
123
|
+
/* @__PURE__ */ jsx(
|
|
124
|
+
TextInput,
|
|
125
|
+
{
|
|
126
|
+
label: msg("webhook.token.label"),
|
|
127
|
+
placeholder: msg("webhook.token.placeholder"),
|
|
128
|
+
hint: isGitlab ? msg("webhook.token.hint.gitlab") : msg("webhook.token.hint"),
|
|
129
|
+
name: "token",
|
|
130
|
+
type: "password",
|
|
131
|
+
value: config.token,
|
|
132
|
+
onChange: (e) => updateField("token", e.target.value)
|
|
133
|
+
}
|
|
134
|
+
),
|
|
135
|
+
isGitlab && /* @__PURE__ */ jsx(
|
|
136
|
+
TextInput,
|
|
137
|
+
{
|
|
138
|
+
label: msg("webhook.ref.label"),
|
|
139
|
+
placeholder: msg("webhook.ref.placeholder"),
|
|
140
|
+
hint: msg("webhook.ref.hint"),
|
|
141
|
+
name: "ref",
|
|
142
|
+
value: config.ref,
|
|
143
|
+
onChange: (e) => updateField("ref", e.target.value)
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
147
|
+
/* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 2, children: [
|
|
148
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
149
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: msg("webhook.variables.label") }),
|
|
150
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", display: "block", children: isGitlab ? msg("webhook.variables.hint.gitlab") : msg("webhook.variables.hint") })
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ jsx(
|
|
153
|
+
Button,
|
|
154
|
+
{
|
|
155
|
+
variant: "tertiary",
|
|
156
|
+
startIcon: /* @__PURE__ */ jsx(Plus, {}),
|
|
157
|
+
onClick: addVariable,
|
|
158
|
+
size: "S",
|
|
159
|
+
children: msg("webhook.variables.add")
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
] }),
|
|
163
|
+
config.variables.length > 0 && /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 2, children: config.variables.map((variable, index) => /* @__PURE__ */ jsxs(Grid.Root, { gap: 2, children: [
|
|
164
|
+
/* @__PURE__ */ jsx(Grid.Item, { col: 5, s: 12, children: /* @__PURE__ */ jsx(
|
|
165
|
+
TextInput,
|
|
166
|
+
{
|
|
167
|
+
"aria-label": msg("webhook.variables.key"),
|
|
168
|
+
placeholder: msg("webhook.variables.key"),
|
|
169
|
+
name: `var-key-${index}`,
|
|
170
|
+
value: variable.key,
|
|
171
|
+
onChange: (e) => updateVariable(index, "key", e.target.value)
|
|
172
|
+
}
|
|
173
|
+
) }),
|
|
174
|
+
/* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsx(
|
|
175
|
+
TextInput,
|
|
176
|
+
{
|
|
177
|
+
"aria-label": msg("webhook.variables.value"),
|
|
178
|
+
placeholder: msg("webhook.variables.value"),
|
|
179
|
+
name: `var-value-${index}`,
|
|
180
|
+
value: variable.value,
|
|
181
|
+
onChange: (e) => updateVariable(index, "value", e.target.value)
|
|
182
|
+
}
|
|
183
|
+
) }),
|
|
184
|
+
/* @__PURE__ */ jsx(Grid.Item, { col: 1, s: 12, children: /* @__PURE__ */ jsx(
|
|
185
|
+
IconButton,
|
|
186
|
+
{
|
|
187
|
+
onClick: () => removeVariable(index),
|
|
188
|
+
label: "Delete",
|
|
189
|
+
variant: "ghost",
|
|
190
|
+
children: /* @__PURE__ */ jsx(Trash, {})
|
|
191
|
+
}
|
|
192
|
+
) })
|
|
193
|
+
] }, index)) })
|
|
194
|
+
] })
|
|
195
|
+
] }) }) })
|
|
196
|
+
] }) }) });
|
|
197
|
+
};
|
|
198
|
+
export {
|
|
199
|
+
SettingsPage
|
|
200
|
+
};
|
|
@@ -5,7 +5,7 @@ const en = {
|
|
|
5
5
|
"page.title": "Bulk Publish",
|
|
6
6
|
"page.subtitle": "Publish documents across all locales with a single action",
|
|
7
7
|
"settings.title": "Bulk Publish Settings",
|
|
8
|
-
"settings.subtitle": "Configure webhook
|
|
8
|
+
"settings.subtitle": "Configure webhook triggered after publishing",
|
|
9
9
|
"table.title": "Title",
|
|
10
10
|
"table.locales": "Locales",
|
|
11
11
|
"table.status": "Status",
|
|
@@ -34,9 +34,26 @@ const en = {
|
|
|
34
34
|
"notification.settings.success": "Settings saved successfully",
|
|
35
35
|
"notification.settings.error": "Failed to save settings",
|
|
36
36
|
"notification.load.error": "Failed to load posts",
|
|
37
|
-
"webhook.label": "Webhook
|
|
38
|
-
"webhook.
|
|
39
|
-
"webhook.
|
|
37
|
+
"webhook.preset.label": "Webhook Format",
|
|
38
|
+
"webhook.preset.generic": "Generic JSON",
|
|
39
|
+
"webhook.preset.gitlab": "GitLab Pipeline Trigger",
|
|
40
|
+
"webhook.url.label": "Webhook URL",
|
|
41
|
+
"webhook.url.placeholder": "https://example.com/api/rebuild",
|
|
42
|
+
"webhook.url.placeholder.gitlab": "https://gitlab.example.com/api/v4/projects/ID/trigger/pipeline",
|
|
43
|
+
"webhook.url.hint": "POST request will be sent to this URL after publishing",
|
|
44
|
+
"webhook.token.label": "Token",
|
|
45
|
+
"webhook.token.placeholder": "Enter token",
|
|
46
|
+
"webhook.token.hint": "Sent as Authorization Bearer header",
|
|
47
|
+
"webhook.token.hint.gitlab": "GitLab pipeline trigger token",
|
|
48
|
+
"webhook.ref.label": "Branch Ref",
|
|
49
|
+
"webhook.ref.placeholder": "main",
|
|
50
|
+
"webhook.ref.hint": "Git branch to trigger the pipeline on",
|
|
51
|
+
"webhook.variables.label": "Variables",
|
|
52
|
+
"webhook.variables.add": "Add Variable",
|
|
53
|
+
"webhook.variables.key": "Key",
|
|
54
|
+
"webhook.variables.value": "Value",
|
|
55
|
+
"webhook.variables.hint": "Extra fields sent with webhook requests",
|
|
56
|
+
"webhook.variables.hint.gitlab": "Passed as CI/CD variables to the pipeline",
|
|
40
57
|
"time.just-now": "just now",
|
|
41
58
|
"time.minutes-ago": "{count}m ago",
|
|
42
59
|
"time.hours-ago": "{count}h ago",
|
|
@@ -7,7 +7,7 @@ const en = {
|
|
|
7
7
|
"page.title": "Bulk Publish",
|
|
8
8
|
"page.subtitle": "Publish documents across all locales with a single action",
|
|
9
9
|
"settings.title": "Bulk Publish Settings",
|
|
10
|
-
"settings.subtitle": "Configure webhook
|
|
10
|
+
"settings.subtitle": "Configure webhook triggered after publishing",
|
|
11
11
|
"table.title": "Title",
|
|
12
12
|
"table.locales": "Locales",
|
|
13
13
|
"table.status": "Status",
|
|
@@ -36,9 +36,26 @@ const en = {
|
|
|
36
36
|
"notification.settings.success": "Settings saved successfully",
|
|
37
37
|
"notification.settings.error": "Failed to save settings",
|
|
38
38
|
"notification.load.error": "Failed to load posts",
|
|
39
|
-
"webhook.label": "Webhook
|
|
40
|
-
"webhook.
|
|
41
|
-
"webhook.
|
|
39
|
+
"webhook.preset.label": "Webhook Format",
|
|
40
|
+
"webhook.preset.generic": "Generic JSON",
|
|
41
|
+
"webhook.preset.gitlab": "GitLab Pipeline Trigger",
|
|
42
|
+
"webhook.url.label": "Webhook URL",
|
|
43
|
+
"webhook.url.placeholder": "https://example.com/api/rebuild",
|
|
44
|
+
"webhook.url.placeholder.gitlab": "https://gitlab.example.com/api/v4/projects/ID/trigger/pipeline",
|
|
45
|
+
"webhook.url.hint": "POST request will be sent to this URL after publishing",
|
|
46
|
+
"webhook.token.label": "Token",
|
|
47
|
+
"webhook.token.placeholder": "Enter token",
|
|
48
|
+
"webhook.token.hint": "Sent as Authorization Bearer header",
|
|
49
|
+
"webhook.token.hint.gitlab": "GitLab pipeline trigger token",
|
|
50
|
+
"webhook.ref.label": "Branch Ref",
|
|
51
|
+
"webhook.ref.placeholder": "main",
|
|
52
|
+
"webhook.ref.hint": "Git branch to trigger the pipeline on",
|
|
53
|
+
"webhook.variables.label": "Variables",
|
|
54
|
+
"webhook.variables.add": "Add Variable",
|
|
55
|
+
"webhook.variables.key": "Key",
|
|
56
|
+
"webhook.variables.value": "Value",
|
|
57
|
+
"webhook.variables.hint": "Extra fields sent with webhook requests",
|
|
58
|
+
"webhook.variables.hint.gitlab": "Passed as CI/CD variables to the pipeline",
|
|
42
59
|
"time.just-now": "just now",
|
|
43
60
|
"time.minutes-ago": "{count}m ago",
|
|
44
61
|
"time.hours-ago": "{count}h ago",
|
|
@@ -31,7 +31,7 @@ const index = {
|
|
|
31
31
|
defaultMessage: "Bulk Publish"
|
|
32
32
|
},
|
|
33
33
|
Component: async () => {
|
|
34
|
-
const { HomePage } = await Promise.resolve().then(() => require("./HomePage-
|
|
34
|
+
const { HomePage } = await Promise.resolve().then(() => require("./HomePage-Rymin2Ze.js"));
|
|
35
35
|
return HomePage;
|
|
36
36
|
},
|
|
37
37
|
permissions: pluginPermissions.publish,
|
|
@@ -54,7 +54,7 @@ const index = {
|
|
|
54
54
|
id: "webhook",
|
|
55
55
|
to: `${PLUGIN_ID}/webhook`,
|
|
56
56
|
Component: async () => {
|
|
57
|
-
const { SettingsPage } = await Promise.resolve().then(() => require("./SettingsPage-
|
|
57
|
+
const { SettingsPage } = await Promise.resolve().then(() => require("./SettingsPage-B99xmYKO.js"));
|
|
58
58
|
return SettingsPage;
|
|
59
59
|
},
|
|
60
60
|
permissions: pluginPermissions.settings
|
|
@@ -70,7 +70,7 @@ const index = {
|
|
|
70
70
|
return Promise.all(
|
|
71
71
|
locales.map(async (locale) => {
|
|
72
72
|
try {
|
|
73
|
-
const data = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-
|
|
73
|
+
const data = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-BuqYI-YP.js")) }), `./translations/${locale}.json`, 3);
|
|
74
74
|
return {
|
|
75
75
|
data: prefixPluginTranslations(data.default, PLUGIN_ID),
|
|
76
76
|
locale
|
|
@@ -30,7 +30,7 @@ const index = {
|
|
|
30
30
|
defaultMessage: "Bulk Publish"
|
|
31
31
|
},
|
|
32
32
|
Component: async () => {
|
|
33
|
-
const { HomePage } = await import("./HomePage-
|
|
33
|
+
const { HomePage } = await import("./HomePage-CCD8Pl7z.mjs");
|
|
34
34
|
return HomePage;
|
|
35
35
|
},
|
|
36
36
|
permissions: pluginPermissions.publish,
|
|
@@ -53,7 +53,7 @@ const index = {
|
|
|
53
53
|
id: "webhook",
|
|
54
54
|
to: `${PLUGIN_ID}/webhook`,
|
|
55
55
|
Component: async () => {
|
|
56
|
-
const { SettingsPage } = await import("./SettingsPage-
|
|
56
|
+
const { SettingsPage } = await import("./SettingsPage-r2PCnoy5.mjs");
|
|
57
57
|
return SettingsPage;
|
|
58
58
|
},
|
|
59
59
|
permissions: pluginPermissions.settings
|
|
@@ -69,7 +69,7 @@ const index = {
|
|
|
69
69
|
return Promise.all(
|
|
70
70
|
locales.map(async (locale) => {
|
|
71
71
|
try {
|
|
72
|
-
const data = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-
|
|
72
|
+
const data = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-BqCcy0LI.mjs") }), `./translations/${locale}.json`, 3);
|
|
73
73
|
return {
|
|
74
74
|
data: prefixPluginTranslations(data.default, PLUGIN_ID),
|
|
75
75
|
locale
|
package/dist/admin/index.js
CHANGED
package/dist/admin/index.mjs
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const register = (_args) => {
|
|
3
3
|
};
|
|
4
|
+
const WEBHOOK_PRESETS = ["generic", "gitlab"];
|
|
5
|
+
const DEFAULT_WEBHOOK_CONFIG = {
|
|
6
|
+
preset: "generic",
|
|
7
|
+
url: "",
|
|
8
|
+
token: "",
|
|
9
|
+
ref: "main",
|
|
10
|
+
variables: []
|
|
11
|
+
};
|
|
4
12
|
const bootstrap = async ({ strapi }) => {
|
|
5
13
|
strapi.log.debug("Bulk Publish plugin bootstrapped");
|
|
6
14
|
await strapi.service("admin::permission").actionProvider.registerMany([
|
|
@@ -18,17 +26,30 @@ const bootstrap = async ({ strapi }) => {
|
|
|
18
26
|
}
|
|
19
27
|
]);
|
|
20
28
|
const store = strapi.store({ type: "plugin", name: "bulk-publish" });
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
const existingConfig = await store.get({ key: "webhookConfig" });
|
|
30
|
+
if (existingConfig) return;
|
|
31
|
+
const legacyUrl = await store.get({ key: "webhookUrl" });
|
|
32
|
+
if (legacyUrl && typeof legacyUrl === "string") {
|
|
33
|
+
const migrated = {
|
|
34
|
+
...DEFAULT_WEBHOOK_CONFIG,
|
|
35
|
+
url: legacyUrl
|
|
36
|
+
};
|
|
37
|
+
await store.set({ key: "webhookConfig", value: migrated });
|
|
38
|
+
await store.set({ key: "webhookUrl", value: null });
|
|
39
|
+
strapi.log.info("bulk-publish: migrated webhookUrl to webhookConfig");
|
|
40
|
+
return;
|
|
25
41
|
}
|
|
42
|
+
const configUrl = strapi.plugin("bulk-publish").config("webhookUrl") || "";
|
|
43
|
+
const seeded = {
|
|
44
|
+
...DEFAULT_WEBHOOK_CONFIG,
|
|
45
|
+
url: typeof configUrl === "string" ? configUrl : ""
|
|
46
|
+
};
|
|
47
|
+
await store.set({ key: "webhookConfig", value: seeded });
|
|
26
48
|
};
|
|
27
49
|
const config = {
|
|
28
50
|
default: {
|
|
29
51
|
contentType: "",
|
|
30
|
-
titleField: "title"
|
|
31
|
-
webhookUrl: ""
|
|
52
|
+
titleField: "title"
|
|
32
53
|
},
|
|
33
54
|
validator: (config2) => {
|
|
34
55
|
if (!config2.contentType || typeof config2.contentType !== "string") {
|
|
@@ -39,9 +60,6 @@ const config = {
|
|
|
39
60
|
if (config2.titleField && typeof config2.titleField !== "string") {
|
|
40
61
|
throw new Error("bulk-publish: titleField must be a string");
|
|
41
62
|
}
|
|
42
|
-
if (config2.webhookUrl && typeof config2.webhookUrl !== "string") {
|
|
43
|
-
throw new Error("bulk-publish: webhookUrl must be a string");
|
|
44
|
-
}
|
|
45
63
|
}
|
|
46
64
|
};
|
|
47
65
|
const admin = {
|
|
@@ -144,40 +162,80 @@ function isAllowedWebhookUrl(urlStr) {
|
|
|
144
162
|
return false;
|
|
145
163
|
}
|
|
146
164
|
}
|
|
165
|
+
function buildGenericRequest(config2, documentIds) {
|
|
166
|
+
const payload = {
|
|
167
|
+
event: "bulk-publish",
|
|
168
|
+
posts: documentIds,
|
|
169
|
+
publishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
170
|
+
};
|
|
171
|
+
for (const { key, value } of config2.variables) {
|
|
172
|
+
if (key) payload[key] = value;
|
|
173
|
+
}
|
|
174
|
+
const headers = { "Content-Type": "application/json" };
|
|
175
|
+
if (config2.token) {
|
|
176
|
+
headers["Authorization"] = `Bearer ${config2.token}`;
|
|
177
|
+
}
|
|
178
|
+
return { headers, body: JSON.stringify(payload) };
|
|
179
|
+
}
|
|
180
|
+
function buildGitlabRequest(config2, documentIds) {
|
|
181
|
+
const form = new FormData();
|
|
182
|
+
form.append("token", config2.token);
|
|
183
|
+
form.append("ref", config2.ref || "main");
|
|
184
|
+
form.append("variables[BULK_PUBLISH_EVENT]", "bulk-publish");
|
|
185
|
+
form.append("variables[BULK_PUBLISH_POSTS]", documentIds.join(","));
|
|
186
|
+
form.append("variables[BULK_PUBLISH_DATE]", (/* @__PURE__ */ new Date()).toISOString());
|
|
187
|
+
for (const { key, value } of config2.variables) {
|
|
188
|
+
if (key) form.append(`variables[${key}]`, value);
|
|
189
|
+
}
|
|
190
|
+
return form;
|
|
191
|
+
}
|
|
192
|
+
function logTruncated(strapi, status, text) {
|
|
193
|
+
const truncated = text.length > MAX_LOG_BODY_LENGTH ? text.slice(0, MAX_LOG_BODY_LENGTH) + "..." : text;
|
|
194
|
+
strapi.log.warn(`bulk-publish webhook returned ${status}: ${truncated}`);
|
|
195
|
+
}
|
|
147
196
|
const webhook = ({ strapi }) => {
|
|
148
197
|
const getStore = () => strapi.store({ type: "plugin", name: "bulk-publish" });
|
|
149
198
|
return {
|
|
150
|
-
async
|
|
151
|
-
const
|
|
152
|
-
|
|
199
|
+
async getConfig() {
|
|
200
|
+
const stored = await getStore().get({ key: "webhookConfig" });
|
|
201
|
+
if (stored && typeof stored === "object") {
|
|
202
|
+
return stored;
|
|
203
|
+
}
|
|
204
|
+
return { ...DEFAULT_WEBHOOK_CONFIG };
|
|
153
205
|
},
|
|
154
|
-
async
|
|
155
|
-
await getStore().set({ key: "
|
|
206
|
+
async setConfig(config2) {
|
|
207
|
+
await getStore().set({ key: "webhookConfig", value: config2 });
|
|
156
208
|
},
|
|
157
209
|
async trigger(documentIds) {
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
210
|
+
const config2 = await this.getConfig();
|
|
211
|
+
if (!config2.url) {
|
|
160
212
|
return { triggered: false, error: "No webhook URL configured" };
|
|
161
213
|
}
|
|
162
|
-
if (!isAllowedWebhookUrl(
|
|
214
|
+
if (!isAllowedWebhookUrl(config2.url)) {
|
|
163
215
|
strapi.log.warn("bulk-publish: webhook URL blocked by SSRF protection");
|
|
164
216
|
return { triggered: false, error: "Webhook URL is not allowed" };
|
|
165
217
|
}
|
|
166
218
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
|
|
219
|
+
let response;
|
|
220
|
+
if (config2.preset === "gitlab") {
|
|
221
|
+
const form = buildGitlabRequest(config2, documentIds);
|
|
222
|
+
response = await fetch(config2.url, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
body: form,
|
|
225
|
+
signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS)
|
|
226
|
+
});
|
|
227
|
+
} else {
|
|
228
|
+
const { headers, body } = buildGenericRequest(config2, documentIds);
|
|
229
|
+
response = await fetch(config2.url, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers,
|
|
232
|
+
body,
|
|
233
|
+
signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS)
|
|
234
|
+
});
|
|
235
|
+
}
|
|
177
236
|
if (!response.ok) {
|
|
178
237
|
const text = await response.text();
|
|
179
|
-
|
|
180
|
-
strapi.log.warn(`bulk-publish webhook returned ${response.status}: ${truncated}`);
|
|
238
|
+
logTruncated(strapi, response.status, text);
|
|
181
239
|
return { triggered: true, error: `Webhook returned ${response.status}` };
|
|
182
240
|
}
|
|
183
241
|
return { triggered: true };
|
|
@@ -226,19 +284,44 @@ const bulkPublish = ({ strapi }) => {
|
|
|
226
284
|
};
|
|
227
285
|
},
|
|
228
286
|
async getSettings(ctx) {
|
|
229
|
-
const
|
|
230
|
-
ctx.body = { data:
|
|
287
|
+
const config2 = await webhookService().getConfig();
|
|
288
|
+
ctx.body = { data: config2 };
|
|
231
289
|
},
|
|
232
290
|
async updateSettings(ctx) {
|
|
233
|
-
const
|
|
234
|
-
if (typeof
|
|
235
|
-
return ctx.badRequest("
|
|
291
|
+
const body = ctx.request.body;
|
|
292
|
+
if (!body || typeof body !== "object") {
|
|
293
|
+
return ctx.badRequest("Request body is required");
|
|
294
|
+
}
|
|
295
|
+
const { preset, url, token, ref, variables } = body;
|
|
296
|
+
if (typeof preset !== "string" || !WEBHOOK_PRESETS.includes(preset)) {
|
|
297
|
+
return ctx.badRequest(`preset must be one of: ${WEBHOOK_PRESETS.join(", ")}`);
|
|
298
|
+
}
|
|
299
|
+
if (typeof url !== "string") {
|
|
300
|
+
return ctx.badRequest("url must be a string");
|
|
301
|
+
}
|
|
302
|
+
if (url && !isAllowedWebhookUrl(url)) {
|
|
303
|
+
return ctx.badRequest("url must be a valid public HTTP(S) URL");
|
|
304
|
+
}
|
|
305
|
+
if (typeof token !== "string") {
|
|
306
|
+
return ctx.badRequest("token must be a string");
|
|
307
|
+
}
|
|
308
|
+
if (typeof ref !== "string") {
|
|
309
|
+
return ctx.badRequest("ref must be a string");
|
|
310
|
+
}
|
|
311
|
+
if (preset === "gitlab" && !ref) {
|
|
312
|
+
return ctx.badRequest("ref is required for GitLab preset");
|
|
313
|
+
}
|
|
314
|
+
if (!Array.isArray(variables)) {
|
|
315
|
+
return ctx.badRequest("variables must be an array");
|
|
236
316
|
}
|
|
237
|
-
if (
|
|
238
|
-
|
|
317
|
+
if (!variables.every(
|
|
318
|
+
(v) => typeof v === "object" && v !== null && typeof v.key === "string" && typeof v.value === "string"
|
|
319
|
+
)) {
|
|
320
|
+
return ctx.badRequest("Each variable must have string key and value");
|
|
239
321
|
}
|
|
240
|
-
|
|
241
|
-
|
|
322
|
+
const config2 = { preset, url, token, ref, variables };
|
|
323
|
+
await webhookService().setConfig(config2);
|
|
324
|
+
ctx.body = { data: config2 };
|
|
242
325
|
}
|
|
243
326
|
};
|
|
244
327
|
};
|
package/dist/server/index.mjs
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
const register = (_args) => {
|
|
2
2
|
};
|
|
3
|
+
const WEBHOOK_PRESETS = ["generic", "gitlab"];
|
|
4
|
+
const DEFAULT_WEBHOOK_CONFIG = {
|
|
5
|
+
preset: "generic",
|
|
6
|
+
url: "",
|
|
7
|
+
token: "",
|
|
8
|
+
ref: "main",
|
|
9
|
+
variables: []
|
|
10
|
+
};
|
|
3
11
|
const bootstrap = async ({ strapi }) => {
|
|
4
12
|
strapi.log.debug("Bulk Publish plugin bootstrapped");
|
|
5
13
|
await strapi.service("admin::permission").actionProvider.registerMany([
|
|
@@ -17,17 +25,30 @@ const bootstrap = async ({ strapi }) => {
|
|
|
17
25
|
}
|
|
18
26
|
]);
|
|
19
27
|
const store = strapi.store({ type: "plugin", name: "bulk-publish" });
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
const existingConfig = await store.get({ key: "webhookConfig" });
|
|
29
|
+
if (existingConfig) return;
|
|
30
|
+
const legacyUrl = await store.get({ key: "webhookUrl" });
|
|
31
|
+
if (legacyUrl && typeof legacyUrl === "string") {
|
|
32
|
+
const migrated = {
|
|
33
|
+
...DEFAULT_WEBHOOK_CONFIG,
|
|
34
|
+
url: legacyUrl
|
|
35
|
+
};
|
|
36
|
+
await store.set({ key: "webhookConfig", value: migrated });
|
|
37
|
+
await store.set({ key: "webhookUrl", value: null });
|
|
38
|
+
strapi.log.info("bulk-publish: migrated webhookUrl to webhookConfig");
|
|
39
|
+
return;
|
|
24
40
|
}
|
|
41
|
+
const configUrl = strapi.plugin("bulk-publish").config("webhookUrl") || "";
|
|
42
|
+
const seeded = {
|
|
43
|
+
...DEFAULT_WEBHOOK_CONFIG,
|
|
44
|
+
url: typeof configUrl === "string" ? configUrl : ""
|
|
45
|
+
};
|
|
46
|
+
await store.set({ key: "webhookConfig", value: seeded });
|
|
25
47
|
};
|
|
26
48
|
const config = {
|
|
27
49
|
default: {
|
|
28
50
|
contentType: "",
|
|
29
|
-
titleField: "title"
|
|
30
|
-
webhookUrl: ""
|
|
51
|
+
titleField: "title"
|
|
31
52
|
},
|
|
32
53
|
validator: (config2) => {
|
|
33
54
|
if (!config2.contentType || typeof config2.contentType !== "string") {
|
|
@@ -38,9 +59,6 @@ const config = {
|
|
|
38
59
|
if (config2.titleField && typeof config2.titleField !== "string") {
|
|
39
60
|
throw new Error("bulk-publish: titleField must be a string");
|
|
40
61
|
}
|
|
41
|
-
if (config2.webhookUrl && typeof config2.webhookUrl !== "string") {
|
|
42
|
-
throw new Error("bulk-publish: webhookUrl must be a string");
|
|
43
|
-
}
|
|
44
62
|
}
|
|
45
63
|
};
|
|
46
64
|
const admin = {
|
|
@@ -143,40 +161,80 @@ function isAllowedWebhookUrl(urlStr) {
|
|
|
143
161
|
return false;
|
|
144
162
|
}
|
|
145
163
|
}
|
|
164
|
+
function buildGenericRequest(config2, documentIds) {
|
|
165
|
+
const payload = {
|
|
166
|
+
event: "bulk-publish",
|
|
167
|
+
posts: documentIds,
|
|
168
|
+
publishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
169
|
+
};
|
|
170
|
+
for (const { key, value } of config2.variables) {
|
|
171
|
+
if (key) payload[key] = value;
|
|
172
|
+
}
|
|
173
|
+
const headers = { "Content-Type": "application/json" };
|
|
174
|
+
if (config2.token) {
|
|
175
|
+
headers["Authorization"] = `Bearer ${config2.token}`;
|
|
176
|
+
}
|
|
177
|
+
return { headers, body: JSON.stringify(payload) };
|
|
178
|
+
}
|
|
179
|
+
function buildGitlabRequest(config2, documentIds) {
|
|
180
|
+
const form = new FormData();
|
|
181
|
+
form.append("token", config2.token);
|
|
182
|
+
form.append("ref", config2.ref || "main");
|
|
183
|
+
form.append("variables[BULK_PUBLISH_EVENT]", "bulk-publish");
|
|
184
|
+
form.append("variables[BULK_PUBLISH_POSTS]", documentIds.join(","));
|
|
185
|
+
form.append("variables[BULK_PUBLISH_DATE]", (/* @__PURE__ */ new Date()).toISOString());
|
|
186
|
+
for (const { key, value } of config2.variables) {
|
|
187
|
+
if (key) form.append(`variables[${key}]`, value);
|
|
188
|
+
}
|
|
189
|
+
return form;
|
|
190
|
+
}
|
|
191
|
+
function logTruncated(strapi, status, text) {
|
|
192
|
+
const truncated = text.length > MAX_LOG_BODY_LENGTH ? text.slice(0, MAX_LOG_BODY_LENGTH) + "..." : text;
|
|
193
|
+
strapi.log.warn(`bulk-publish webhook returned ${status}: ${truncated}`);
|
|
194
|
+
}
|
|
146
195
|
const webhook = ({ strapi }) => {
|
|
147
196
|
const getStore = () => strapi.store({ type: "plugin", name: "bulk-publish" });
|
|
148
197
|
return {
|
|
149
|
-
async
|
|
150
|
-
const
|
|
151
|
-
|
|
198
|
+
async getConfig() {
|
|
199
|
+
const stored = await getStore().get({ key: "webhookConfig" });
|
|
200
|
+
if (stored && typeof stored === "object") {
|
|
201
|
+
return stored;
|
|
202
|
+
}
|
|
203
|
+
return { ...DEFAULT_WEBHOOK_CONFIG };
|
|
152
204
|
},
|
|
153
|
-
async
|
|
154
|
-
await getStore().set({ key: "
|
|
205
|
+
async setConfig(config2) {
|
|
206
|
+
await getStore().set({ key: "webhookConfig", value: config2 });
|
|
155
207
|
},
|
|
156
208
|
async trigger(documentIds) {
|
|
157
|
-
const
|
|
158
|
-
if (!
|
|
209
|
+
const config2 = await this.getConfig();
|
|
210
|
+
if (!config2.url) {
|
|
159
211
|
return { triggered: false, error: "No webhook URL configured" };
|
|
160
212
|
}
|
|
161
|
-
if (!isAllowedWebhookUrl(
|
|
213
|
+
if (!isAllowedWebhookUrl(config2.url)) {
|
|
162
214
|
strapi.log.warn("bulk-publish: webhook URL blocked by SSRF protection");
|
|
163
215
|
return { triggered: false, error: "Webhook URL is not allowed" };
|
|
164
216
|
}
|
|
165
217
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
|
|
218
|
+
let response;
|
|
219
|
+
if (config2.preset === "gitlab") {
|
|
220
|
+
const form = buildGitlabRequest(config2, documentIds);
|
|
221
|
+
response = await fetch(config2.url, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
body: form,
|
|
224
|
+
signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS)
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
const { headers, body } = buildGenericRequest(config2, documentIds);
|
|
228
|
+
response = await fetch(config2.url, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers,
|
|
231
|
+
body,
|
|
232
|
+
signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS)
|
|
233
|
+
});
|
|
234
|
+
}
|
|
176
235
|
if (!response.ok) {
|
|
177
236
|
const text = await response.text();
|
|
178
|
-
|
|
179
|
-
strapi.log.warn(`bulk-publish webhook returned ${response.status}: ${truncated}`);
|
|
237
|
+
logTruncated(strapi, response.status, text);
|
|
180
238
|
return { triggered: true, error: `Webhook returned ${response.status}` };
|
|
181
239
|
}
|
|
182
240
|
return { triggered: true };
|
|
@@ -225,19 +283,44 @@ const bulkPublish = ({ strapi }) => {
|
|
|
225
283
|
};
|
|
226
284
|
},
|
|
227
285
|
async getSettings(ctx) {
|
|
228
|
-
const
|
|
229
|
-
ctx.body = { data:
|
|
286
|
+
const config2 = await webhookService().getConfig();
|
|
287
|
+
ctx.body = { data: config2 };
|
|
230
288
|
},
|
|
231
289
|
async updateSettings(ctx) {
|
|
232
|
-
const
|
|
233
|
-
if (typeof
|
|
234
|
-
return ctx.badRequest("
|
|
290
|
+
const body = ctx.request.body;
|
|
291
|
+
if (!body || typeof body !== "object") {
|
|
292
|
+
return ctx.badRequest("Request body is required");
|
|
293
|
+
}
|
|
294
|
+
const { preset, url, token, ref, variables } = body;
|
|
295
|
+
if (typeof preset !== "string" || !WEBHOOK_PRESETS.includes(preset)) {
|
|
296
|
+
return ctx.badRequest(`preset must be one of: ${WEBHOOK_PRESETS.join(", ")}`);
|
|
297
|
+
}
|
|
298
|
+
if (typeof url !== "string") {
|
|
299
|
+
return ctx.badRequest("url must be a string");
|
|
300
|
+
}
|
|
301
|
+
if (url && !isAllowedWebhookUrl(url)) {
|
|
302
|
+
return ctx.badRequest("url must be a valid public HTTP(S) URL");
|
|
303
|
+
}
|
|
304
|
+
if (typeof token !== "string") {
|
|
305
|
+
return ctx.badRequest("token must be a string");
|
|
306
|
+
}
|
|
307
|
+
if (typeof ref !== "string") {
|
|
308
|
+
return ctx.badRequest("ref must be a string");
|
|
309
|
+
}
|
|
310
|
+
if (preset === "gitlab" && !ref) {
|
|
311
|
+
return ctx.badRequest("ref is required for GitLab preset");
|
|
312
|
+
}
|
|
313
|
+
if (!Array.isArray(variables)) {
|
|
314
|
+
return ctx.badRequest("variables must be an array");
|
|
235
315
|
}
|
|
236
|
-
if (
|
|
237
|
-
|
|
316
|
+
if (!variables.every(
|
|
317
|
+
(v) => typeof v === "object" && v !== null && typeof v.key === "string" && typeof v.value === "string"
|
|
318
|
+
)) {
|
|
319
|
+
return ctx.badRequest("Each variable must have string key and value");
|
|
238
320
|
}
|
|
239
|
-
|
|
240
|
-
|
|
321
|
+
const config2 = { preset, url, token, ref, variables };
|
|
322
|
+
await webhookService().setConfig(config2);
|
|
323
|
+
ctx.body = { data: config2 };
|
|
241
324
|
}
|
|
242
325
|
};
|
|
243
326
|
};
|
package/package.json
CHANGED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback, useEffect } from "react";
|
|
3
|
-
import { u as useIntl } from "./index-CEh8vkxY.mjs";
|
|
4
|
-
import { Main, Flex, Loader, Box, Typography, Button, TextInput } from "@strapi/design-system";
|
|
5
|
-
import { useFetchClient, useNotification, Page } from "@strapi/strapi/admin";
|
|
6
|
-
import { P as PLUGIN_ID, p as pluginPermissions } from "./index-CBDMXg1c.mjs";
|
|
7
|
-
const SettingsPage = () => {
|
|
8
|
-
const { formatMessage } = useIntl();
|
|
9
|
-
const { get, put } = useFetchClient();
|
|
10
|
-
const { toggleNotification } = useNotification();
|
|
11
|
-
const [webhookUrl, setWebhookUrl] = useState("");
|
|
12
|
-
const [initialUrl, setInitialUrl] = useState("");
|
|
13
|
-
const [loading, setLoading] = useState(true);
|
|
14
|
-
const [saving, setSaving] = useState(false);
|
|
15
|
-
const fetchSettings = useCallback(async () => {
|
|
16
|
-
try {
|
|
17
|
-
const { data } = await get(`/${PLUGIN_ID}/settings`);
|
|
18
|
-
const url = data.data?.webhookUrl || "";
|
|
19
|
-
setWebhookUrl(url);
|
|
20
|
-
setInitialUrl(url);
|
|
21
|
-
} catch {
|
|
22
|
-
toggleNotification({
|
|
23
|
-
type: "danger",
|
|
24
|
-
message: formatMessage({ id: `${PLUGIN_ID}.notification.settings.error` })
|
|
25
|
-
});
|
|
26
|
-
} finally {
|
|
27
|
-
setLoading(false);
|
|
28
|
-
}
|
|
29
|
-
}, [get, toggleNotification, formatMessage]);
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
fetchSettings();
|
|
32
|
-
}, [fetchSettings]);
|
|
33
|
-
const handleSave = async () => {
|
|
34
|
-
try {
|
|
35
|
-
setSaving(true);
|
|
36
|
-
await put(`/${PLUGIN_ID}/settings`, { webhookUrl });
|
|
37
|
-
setInitialUrl(webhookUrl);
|
|
38
|
-
toggleNotification({
|
|
39
|
-
type: "success",
|
|
40
|
-
message: formatMessage({ id: `${PLUGIN_ID}.notification.settings.success` })
|
|
41
|
-
});
|
|
42
|
-
} catch {
|
|
43
|
-
toggleNotification({
|
|
44
|
-
type: "danger",
|
|
45
|
-
message: formatMessage({ id: `${PLUGIN_ID}.notification.settings.error` })
|
|
46
|
-
});
|
|
47
|
-
} finally {
|
|
48
|
-
setSaving(false);
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
const hasChanged = webhookUrl !== initialUrl;
|
|
52
|
-
return /* @__PURE__ */ jsx(Page.Protect, { permissions: pluginPermissions.settings, children: /* @__PURE__ */ jsx(Main, { children: loading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", paddingTop: 8, children: /* @__PURE__ */ jsx(Loader, { children: formatMessage({ id: `${PLUGIN_ID}.loading.settings` }) }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
53
|
-
/* @__PURE__ */ jsx(Box, { paddingTop: 8, paddingBottom: 4, paddingLeft: 10, paddingRight: 10, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
54
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
55
|
-
/* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: formatMessage({ id: `${PLUGIN_ID}.settings.title` }) }),
|
|
56
|
-
/* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", children: formatMessage({ id: `${PLUGIN_ID}.settings.subtitle` }) })
|
|
57
|
-
] }),
|
|
58
|
-
/* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: !hasChanged || saving, loading: saving, children: formatMessage({ id: `${PLUGIN_ID}.button.save` }) })
|
|
59
|
-
] }) }),
|
|
60
|
-
/* @__PURE__ */ jsx(Box, { paddingLeft: 10, paddingRight: 10, paddingBottom: 10, children: /* @__PURE__ */ jsx(Box, { background: "neutral0", padding: 6, shadow: "tableShadow", hasRadius: true, children: /* @__PURE__ */ jsx(
|
|
61
|
-
TextInput,
|
|
62
|
-
{
|
|
63
|
-
label: formatMessage({ id: `${PLUGIN_ID}.webhook.label` }),
|
|
64
|
-
placeholder: formatMessage({ id: `${PLUGIN_ID}.webhook.placeholder` }),
|
|
65
|
-
hint: formatMessage({ id: `${PLUGIN_ID}.webhook.hint` }),
|
|
66
|
-
name: "webhookUrl",
|
|
67
|
-
value: webhookUrl,
|
|
68
|
-
onChange: (e) => setWebhookUrl(e.target.value)
|
|
69
|
-
}
|
|
70
|
-
) }) })
|
|
71
|
-
] }) }) });
|
|
72
|
-
};
|
|
73
|
-
export {
|
|
74
|
-
SettingsPage
|
|
75
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
-
const React = require("react");
|
|
5
|
-
const index = require("./index-DkTxsEqL.js");
|
|
6
|
-
const designSystem = require("@strapi/design-system");
|
|
7
|
-
const admin = require("@strapi/strapi/admin");
|
|
8
|
-
const index$1 = require("./index-BBZKQRB2.js");
|
|
9
|
-
const SettingsPage = () => {
|
|
10
|
-
const { formatMessage } = index.useIntl();
|
|
11
|
-
const { get, put } = admin.useFetchClient();
|
|
12
|
-
const { toggleNotification } = admin.useNotification();
|
|
13
|
-
const [webhookUrl, setWebhookUrl] = React.useState("");
|
|
14
|
-
const [initialUrl, setInitialUrl] = React.useState("");
|
|
15
|
-
const [loading, setLoading] = React.useState(true);
|
|
16
|
-
const [saving, setSaving] = React.useState(false);
|
|
17
|
-
const fetchSettings = React.useCallback(async () => {
|
|
18
|
-
try {
|
|
19
|
-
const { data } = await get(`/${index$1.PLUGIN_ID}/settings`);
|
|
20
|
-
const url = data.data?.webhookUrl || "";
|
|
21
|
-
setWebhookUrl(url);
|
|
22
|
-
setInitialUrl(url);
|
|
23
|
-
} catch {
|
|
24
|
-
toggleNotification({
|
|
25
|
-
type: "danger",
|
|
26
|
-
message: formatMessage({ id: `${index$1.PLUGIN_ID}.notification.settings.error` })
|
|
27
|
-
});
|
|
28
|
-
} finally {
|
|
29
|
-
setLoading(false);
|
|
30
|
-
}
|
|
31
|
-
}, [get, toggleNotification, formatMessage]);
|
|
32
|
-
React.useEffect(() => {
|
|
33
|
-
fetchSettings();
|
|
34
|
-
}, [fetchSettings]);
|
|
35
|
-
const handleSave = async () => {
|
|
36
|
-
try {
|
|
37
|
-
setSaving(true);
|
|
38
|
-
await put(`/${index$1.PLUGIN_ID}/settings`, { webhookUrl });
|
|
39
|
-
setInitialUrl(webhookUrl);
|
|
40
|
-
toggleNotification({
|
|
41
|
-
type: "success",
|
|
42
|
-
message: formatMessage({ id: `${index$1.PLUGIN_ID}.notification.settings.success` })
|
|
43
|
-
});
|
|
44
|
-
} catch {
|
|
45
|
-
toggleNotification({
|
|
46
|
-
type: "danger",
|
|
47
|
-
message: formatMessage({ id: `${index$1.PLUGIN_ID}.notification.settings.error` })
|
|
48
|
-
});
|
|
49
|
-
} finally {
|
|
50
|
-
setSaving(false);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
const hasChanged = webhookUrl !== initialUrl;
|
|
54
|
-
return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Protect, { permissions: index$1.pluginPermissions.settings, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { children: loading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", paddingTop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: formatMessage({ id: `${index$1.PLUGIN_ID}.loading.settings` }) }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
55
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 8, paddingBottom: 4, paddingLeft: 10, paddingRight: 10, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
56
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
57
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: formatMessage({ id: `${index$1.PLUGIN_ID}.settings.title` }) }),
|
|
58
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: formatMessage({ id: `${index$1.PLUGIN_ID}.settings.subtitle` }) })
|
|
59
|
-
] }),
|
|
60
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleSave, disabled: !hasChanged || saving, loading: saving, children: formatMessage({ id: `${index$1.PLUGIN_ID}.button.save` }) })
|
|
61
|
-
] }) }),
|
|
62
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 10, paddingRight: 10, paddingBottom: 10, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", padding: 6, shadow: "tableShadow", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
63
|
-
designSystem.TextInput,
|
|
64
|
-
{
|
|
65
|
-
label: formatMessage({ id: `${index$1.PLUGIN_ID}.webhook.label` }),
|
|
66
|
-
placeholder: formatMessage({ id: `${index$1.PLUGIN_ID}.webhook.placeholder` }),
|
|
67
|
-
hint: formatMessage({ id: `${index$1.PLUGIN_ID}.webhook.hint` }),
|
|
68
|
-
name: "webhookUrl",
|
|
69
|
-
value: webhookUrl,
|
|
70
|
-
onChange: (e) => setWebhookUrl(e.target.value)
|
|
71
|
-
}
|
|
72
|
-
) }) })
|
|
73
|
-
] }) }) });
|
|
74
|
-
};
|
|
75
|
-
exports.SettingsPage = SettingsPage;
|