strapi-plugin-meilisearch 0.10.0 → 0.11.1
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/LICENSE +1 -1
- package/README.md +27 -6
- package/admin/src/Hooks/useAlert.js +17 -0
- package/admin/src/Hooks/useCollection.js +69 -56
- package/admin/src/Hooks/useCredential.js +24 -20
- package/admin/src/components/Initializer/index.js +27 -0
- package/admin/src/components/PluginIcon/index.js +1 -1
- package/admin/src/constants.js +34 -0
- package/admin/src/containers/Collection/CollectionColumn.js +43 -32
- package/admin/src/containers/Collection/CollectionTable.js +4 -8
- package/admin/src/containers/Collection/CollectionTableHeader.js +19 -9
- package/admin/src/containers/HomePage/index.js +8 -5
- package/admin/src/containers/PluginHeader/index.js +2 -4
- package/admin/src/containers/PluginTabs/index.js +16 -10
- package/admin/src/containers/Settings/Credentials.js +23 -24
- package/admin/src/containers/Settings/PluginActions.js +1 -3
- package/admin/src/containers/Settings/Settings.js +1 -1
- package/admin/src/index.js +4 -2
- package/package.json +16 -13
- package/server/__mocks__/strapi.js +7 -5
- package/server/__tests__/configuration-validation.test.js +56 -21
- package/server/__tests__/configuration.test.js +1 -1
- package/server/__tests__/content-types.test.js +5 -5
- package/server/__tests__/meilisearch.test.js +39 -9
- package/server/bootstrap.js +45 -0
- package/server/configuration-validation.js +35 -18
- package/server/constants.js +13 -0
- package/server/controllers/reload.js +1 -2
- package/server/policies/isAdmin.js +1 -1
- package/server/routes/index.js +37 -7
- package/server/services/content-types/content-types.js +4 -4
- package/server/services/lifecycle/lifecycle.js +5 -5
- package/server/services/meilisearch/config.js +18 -6
- package/server/services/meilisearch/connector.js +54 -27
- package/server/services/store/credential.js +4 -7
- package/admin/src/containers/Collection/utils/getTrad.js +0 -5
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Meilisearch is an open-source search engine. [Discover what Meilisearch is!](htt
|
|
|
28
28
|
|
|
29
29
|
Add your Strapi content-types into a Meilisearch instance. The plugin listens to modifications made on your content-types and updates Meilisearch accordingly.
|
|
30
30
|
|
|
31
|
-
## Table of Contents <!-- omit in
|
|
31
|
+
## Table of Contents <!-- omit in TOC -->
|
|
32
32
|
|
|
33
33
|
- [📖 Documentation](#-documentation)
|
|
34
34
|
- [⚡ Supercharge your Meilisearch experience](#-supercharge-your-meilisearch-experience)
|
|
@@ -106,7 +106,7 @@ To run the Docker script add both files `Dockerfile` and `docker-compose.yaml` a
|
|
|
106
106
|
|
|
107
107
|
## 🎬 Getting Started
|
|
108
108
|
|
|
109
|
-
Now that you have installed the plugin, a running
|
|
109
|
+
Now that you have installed the plugin, a running Meilisearch instance and, a running Strapi app, let's go to the plugin page on your admin dashboard.
|
|
110
110
|
|
|
111
111
|
On the left-navbar, `Meilisearch` appears under the `PLUGINS` category. If it does not, ensure that you have installed the plugin and re-build Strapi (see [installation](#-installation)).
|
|
112
112
|
|
|
@@ -211,6 +211,7 @@ Settings:
|
|
|
211
211
|
- [🤚 Filter entries](#-filter-entries)
|
|
212
212
|
- [🏗 Add Meilisearch settings](#-add-meilisearch-settings)
|
|
213
213
|
- [🔎 Entries query](#-entries-query)
|
|
214
|
+
- [🔐 Selectively index private fields](#-selectively-index-private-fields)
|
|
214
215
|
|
|
215
216
|
### 🏷 Custom index name
|
|
216
217
|
|
|
@@ -412,13 +413,34 @@ module.exports = {
|
|
|
412
413
|
|
|
413
414
|
[See resources](./resources/entries-query) for more entriesQuery examples.
|
|
414
415
|
|
|
416
|
+
### 🔐 Selectively index private fields
|
|
417
|
+
|
|
418
|
+
Private fields are sanitized by default to prevent data leaks. However, you might want to allow some of these private fields to be used for `search`, `filter` or `sort`. This is possible with the `noSanitizePrivateFields`. For example, if you have a private field called `internal_notes` in your content-type schema that you wish to include in searching, you can add it to the `noSanitizePrivateFields` array to allow it to be indexed.
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
// config/plugins.js
|
|
422
|
+
|
|
423
|
+
module.exports = {
|
|
424
|
+
meilisearch: {
|
|
425
|
+
config: {
|
|
426
|
+
restaurant: {
|
|
427
|
+
noSanitizePrivateFields: ["internal_notes"], // All attributes: ["*"]
|
|
428
|
+
settings: {
|
|
429
|
+
"searchableAttributes": ["internal_notes"],
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
415
437
|
### 🕵️♀️ Start Searching <!-- omit in toc -->
|
|
416
438
|
|
|
417
439
|
Once you have a content-type indexed in Meilisearch, you can [start searching](https://www.meilisearch.com/docs/learn/getting_started/quick_start.html#search).
|
|
418
440
|
|
|
419
441
|
To search in Meilisearch, you can use the [instant-meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch) library that integrates a whole search interface, or our [meilisearch-js](https://github.com/meilisearch/meilisearch-js) SDK.
|
|
420
442
|
|
|
421
|
-
#### ⚡️ Using Instant
|
|
443
|
+
#### ⚡️ Using Instant Meilisearch <!-- omit in toc -->
|
|
422
444
|
|
|
423
445
|
You can have a front up and running in record time with [instant-meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch).
|
|
424
446
|
|
|
@@ -543,10 +565,9 @@ If you are using [Strapi v3](https://github.com/strapi/strapi/tree/v3.6.9), plea
|
|
|
543
565
|
|
|
544
566
|
This package guarantees compatibility with [version v1.x of Meilisearch](https://github.com/meilisearch/meilisearch/releases/latest), but some features may not be present. Please check the [issues](https://github.com/meilisearch/strapi-plugin-meilisearch/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3Aenhancement) for more info.
|
|
545
567
|
|
|
546
|
-
**Node
|
|
568
|
+
**Node**:
|
|
547
569
|
|
|
548
|
-
- NodeJS >=
|
|
549
|
-
- NPM >= 6.x
|
|
570
|
+
- NodeJS >= 18
|
|
550
571
|
|
|
551
572
|
**We recommend always using the latest version of Strapi to start your new projects**.
|
|
552
573
|
|
|
@@ -14,8 +14,11 @@ export function useAlert() {
|
|
|
14
14
|
message = 'Something occured in Meilisearch',
|
|
15
15
|
link,
|
|
16
16
|
blockTransition = true,
|
|
17
|
+
title,
|
|
17
18
|
}) => {
|
|
18
19
|
toggleNotification({
|
|
20
|
+
// optional
|
|
21
|
+
title,
|
|
19
22
|
// required
|
|
20
23
|
// type: 'info|success|warning',
|
|
21
24
|
type,
|
|
@@ -32,8 +35,22 @@ export function useAlert() {
|
|
|
32
35
|
onClose: () => localStorage.setItem('STRAPI_UPDATE_NOTIF', true),
|
|
33
36
|
})
|
|
34
37
|
}
|
|
38
|
+
|
|
39
|
+
const checkForbiddenError = ({ response }) => {
|
|
40
|
+
const status = response?.payload?.error?.status
|
|
41
|
+
if (status && status === 403) {
|
|
42
|
+
handleNotification({
|
|
43
|
+
title: 'Forbidden',
|
|
44
|
+
type: 'warning',
|
|
45
|
+
message: 'You do not have permission to do this action',
|
|
46
|
+
blockTransition: false,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
return {
|
|
36
52
|
handleNotification,
|
|
53
|
+
checkForbiddenError,
|
|
37
54
|
}
|
|
38
55
|
}
|
|
39
56
|
|
|
@@ -16,7 +16,7 @@ export function useCollection() {
|
|
|
16
16
|
const [reloadNeeded, setReloadNeeded] = useState(false)
|
|
17
17
|
const [realTimeReports, setRealTimeReports] = useState(false)
|
|
18
18
|
|
|
19
|
-
const { handleNotification } = useAlert()
|
|
19
|
+
const { handleNotification, checkForbiddenError } = useAlert()
|
|
20
20
|
|
|
21
21
|
const refetchCollection = () =>
|
|
22
22
|
setRefetchIndex(prevRefetchIndex => !prevRefetchIndex)
|
|
@@ -41,7 +41,7 @@ export function useCollection() {
|
|
|
41
41
|
return collection
|
|
42
42
|
})
|
|
43
43
|
const reload = collections.find(
|
|
44
|
-
col => col.reloadNeeded === 'Reload needed'
|
|
44
|
+
col => col.reloadNeeded === 'Reload needed',
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
const isIndexing = collections.find(col => col.isIndexing === true)
|
|
@@ -57,71 +57,84 @@ export function useCollection() {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const deleteCollection = async ({ contentType }) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
try {
|
|
61
|
+
const { error } = await request(
|
|
62
|
+
`/${pluginId}/content-type/${contentType}`,
|
|
63
|
+
{
|
|
64
|
+
method: 'DELETE',
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
if (error) {
|
|
68
|
+
handleNotification({
|
|
69
|
+
type: 'warning',
|
|
70
|
+
message: error.message,
|
|
71
|
+
link: error.link,
|
|
72
|
+
})
|
|
73
|
+
} else {
|
|
74
|
+
refetchCollection()
|
|
75
|
+
handleNotification({
|
|
76
|
+
type: 'success',
|
|
77
|
+
message: 'Request to delete content-type is successful',
|
|
78
|
+
blockTransition: false,
|
|
79
|
+
})
|
|
64
80
|
}
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
handleNotification({
|
|
68
|
-
type: 'warning',
|
|
69
|
-
message: error.message,
|
|
70
|
-
link: error.link,
|
|
71
|
-
})
|
|
72
|
-
} else {
|
|
73
|
-
refetchCollection()
|
|
74
|
-
handleNotification({
|
|
75
|
-
type: 'success',
|
|
76
|
-
message: 'Request to delete content-type is successful',
|
|
77
|
-
blockTransition: false,
|
|
78
|
-
})
|
|
81
|
+
} catch (error) {
|
|
82
|
+
checkForbiddenError(error)
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
const addCollection = async ({ contentType }) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (error) {
|
|
90
|
-
handleNotification({
|
|
91
|
-
type: 'warning',
|
|
92
|
-
message: error.message,
|
|
93
|
-
link: error.link,
|
|
94
|
-
})
|
|
95
|
-
} else {
|
|
96
|
-
refetchCollection()
|
|
97
|
-
handleNotification({
|
|
98
|
-
type: 'success',
|
|
99
|
-
message: 'Request to add a content-type is successful',
|
|
100
|
-
blockTransition: false,
|
|
87
|
+
try {
|
|
88
|
+
const { error } = await request(`/${pluginId}/content-type`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: {
|
|
91
|
+
contentType,
|
|
92
|
+
},
|
|
101
93
|
})
|
|
94
|
+
if (error) {
|
|
95
|
+
handleNotification({
|
|
96
|
+
type: 'warning',
|
|
97
|
+
message: error.message,
|
|
98
|
+
link: error.link,
|
|
99
|
+
})
|
|
100
|
+
} else {
|
|
101
|
+
refetchCollection()
|
|
102
|
+
handleNotification({
|
|
103
|
+
type: 'success',
|
|
104
|
+
message: 'Request to add a content-type is successful',
|
|
105
|
+
blockTransition: false,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
checkForbiddenError(error)
|
|
102
110
|
}
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
const updateCollection = async ({ contentType }) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (error) {
|
|
113
|
-
handleNotification({
|
|
114
|
-
type: 'warning',
|
|
115
|
-
message: error.message,
|
|
116
|
-
link: error.link,
|
|
117
|
-
})
|
|
118
|
-
} else {
|
|
119
|
-
refetchCollection()
|
|
120
|
-
handleNotification({
|
|
121
|
-
type: 'success',
|
|
122
|
-
message: 'Request to update content-type is successful',
|
|
123
|
-
blockTransition: false,
|
|
114
|
+
try {
|
|
115
|
+
const { error } = await request(`/${pluginId}/content-type`, {
|
|
116
|
+
method: 'PUT',
|
|
117
|
+
body: {
|
|
118
|
+
contentType,
|
|
119
|
+
},
|
|
124
120
|
})
|
|
121
|
+
|
|
122
|
+
if (error) {
|
|
123
|
+
handleNotification({
|
|
124
|
+
type: 'warning',
|
|
125
|
+
message: error.message,
|
|
126
|
+
link: error.link,
|
|
127
|
+
})
|
|
128
|
+
} else {
|
|
129
|
+
refetchCollection()
|
|
130
|
+
handleNotification({
|
|
131
|
+
type: 'success',
|
|
132
|
+
message: 'Request to update content-type is successful',
|
|
133
|
+
blockTransition: false,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
checkForbiddenError(error)
|
|
125
138
|
}
|
|
126
139
|
}
|
|
127
140
|
|
|
@@ -13,32 +13,36 @@ export function useCredential() {
|
|
|
13
13
|
const [refetchIndex, setRefetchIndex] = useState(true)
|
|
14
14
|
const [host, setHost] = useState('')
|
|
15
15
|
const [apiKey, setApiKey] = useState('')
|
|
16
|
-
const { handleNotification } = useAlert()
|
|
16
|
+
const { handleNotification, checkForbiddenError } = useAlert()
|
|
17
17
|
|
|
18
18
|
const refetchCredentials = () =>
|
|
19
19
|
setRefetchIndex(prevRefetchIndex => !prevRefetchIndex)
|
|
20
20
|
|
|
21
21
|
const updateCredentials = async () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (error) {
|
|
30
|
-
handleNotification({
|
|
31
|
-
type: 'warning',
|
|
32
|
-
message: error.message,
|
|
33
|
-
link: error.link,
|
|
34
|
-
})
|
|
35
|
-
} else {
|
|
36
|
-
refetchCredentials()
|
|
37
|
-
handleNotification({
|
|
38
|
-
type: 'success',
|
|
39
|
-
message: 'Credentials sucessfully updated!',
|
|
40
|
-
blockTransition: false,
|
|
22
|
+
try {
|
|
23
|
+
const { error } = await request(`/${pluginId}/credential`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
body: {
|
|
26
|
+
apiKey: apiKey,
|
|
27
|
+
host: host,
|
|
28
|
+
},
|
|
41
29
|
})
|
|
30
|
+
if (error) {
|
|
31
|
+
handleNotification({
|
|
32
|
+
type: 'warning',
|
|
33
|
+
message: error.message,
|
|
34
|
+
link: error.link,
|
|
35
|
+
})
|
|
36
|
+
} else {
|
|
37
|
+
refetchCredentials()
|
|
38
|
+
handleNotification({
|
|
39
|
+
type: 'success',
|
|
40
|
+
message: 'Credentials sucessfully updated!',
|
|
41
|
+
blockTransition: false,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
checkForbiddenError(error)
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Initializer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import PropTypes from 'prop-types'
|
|
8
|
+
import { useEffect, useRef } from 'react'
|
|
9
|
+
|
|
10
|
+
import pluginId from '../../pluginId'
|
|
11
|
+
|
|
12
|
+
const Initializer = ({ setPlugin }) => {
|
|
13
|
+
const ref = useRef()
|
|
14
|
+
ref.current = setPlugin
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
ref.current(pluginId)
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Initializer.propTypes = {
|
|
24
|
+
setPlugin: PropTypes.func.isRequired,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default Initializer
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const PERMISSIONS = {
|
|
2
|
+
// This permission regards the main component (App) and is used to tell
|
|
3
|
+
// If the plugin link should be displayed in the menu
|
|
4
|
+
// And also if the plugin is accessible. This use case is found when a user types the url of the
|
|
5
|
+
// plugin directly in the browser
|
|
6
|
+
main: [
|
|
7
|
+
{ action: 'plugin::meilisearch.read', subject: null },
|
|
8
|
+
{ action: 'plugin::meilisearch.collections.create', subject: null },
|
|
9
|
+
{ action: 'plugin::meilisearch.collections.update', subject: null },
|
|
10
|
+
{ action: 'plugin::meilisearch.collections.delete', subject: null },
|
|
11
|
+
{ action: 'plugin::meilisearch.settings.edit', subject: null },
|
|
12
|
+
],
|
|
13
|
+
collections: [
|
|
14
|
+
{ action: 'plugin::meilisearch.read', subject: null },
|
|
15
|
+
{ action: 'plugin::meilisearch.collections.create', subject: null },
|
|
16
|
+
{ action: 'plugin::meilisearch.collections.update', subject: null },
|
|
17
|
+
{ action: 'plugin::meilisearch.collections.delete', subject: null },
|
|
18
|
+
],
|
|
19
|
+
settings: [
|
|
20
|
+
{ action: 'plugin::meilisearch.read', subject: null },
|
|
21
|
+
{ action: 'plugin::meilisearch.settings.edit', subject: null },
|
|
22
|
+
],
|
|
23
|
+
read: [{ action: 'plugin::meilisearch.read', subject: null }],
|
|
24
|
+
create: [{ action: 'plugin::meilisearch.collections.create', subject: null }],
|
|
25
|
+
update: [{ action: 'plugin::meilisearch.collections.update', subject: null }],
|
|
26
|
+
delete: [{ action: 'plugin::meilisearch.collections.delete', subject: null }],
|
|
27
|
+
settingsEdit: [
|
|
28
|
+
{ action: 'plugin::meilisearch.settings.edit', subject: null },
|
|
29
|
+
],
|
|
30
|
+
createAndDelete: [
|
|
31
|
+
{ action: 'plugin::meilisearch.collections.create', subject: null },
|
|
32
|
+
{ action: 'plugin::meilisearch.collections.delete', subject: null },
|
|
33
|
+
],
|
|
34
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import React, { memo } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import {
|
|
3
|
+
BaseCheckbox,
|
|
4
|
+
Box,
|
|
5
|
+
Button,
|
|
6
|
+
Flex,
|
|
7
|
+
Td,
|
|
8
|
+
Tr,
|
|
9
|
+
Typography,
|
|
10
|
+
} from '@strapi/design-system'
|
|
11
|
+
import { CheckPermissions } from '@strapi/helper-plugin'
|
|
12
|
+
import { PERMISSIONS } from '../../constants'
|
|
8
13
|
|
|
9
14
|
const CollectionColumn = ({
|
|
10
15
|
entry,
|
|
@@ -14,17 +19,19 @@ const CollectionColumn = ({
|
|
|
14
19
|
}) => {
|
|
15
20
|
return (
|
|
16
21
|
<Tr key={entry.contentType}>
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
<CheckPermissions permissions={PERMISSIONS.createAndDelete}>
|
|
23
|
+
<Td>
|
|
24
|
+
<BaseCheckbox
|
|
25
|
+
aria-label={`Select ${entry.collection}`}
|
|
26
|
+
onValueChange={() => {
|
|
27
|
+
if (entry.indexed)
|
|
28
|
+
deleteCollection({ contentType: entry.contentType })
|
|
29
|
+
else addCollection({ contentType: entry.contentType })
|
|
30
|
+
}}
|
|
31
|
+
value={entry.indexed}
|
|
32
|
+
/>
|
|
33
|
+
</Td>
|
|
34
|
+
</CheckPermissions>
|
|
28
35
|
{/* // Name */}
|
|
29
36
|
<Td>
|
|
30
37
|
<Typography textColor="neutral800">{entry.collection}</Typography>
|
|
@@ -55,21 +62,25 @@ const CollectionColumn = ({
|
|
|
55
62
|
<Td>
|
|
56
63
|
<Typography textColor="neutral800">{entry.reloadNeeded}</Typography>
|
|
57
64
|
</Td>
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
<CheckPermissions permissions={PERMISSIONS.update}>
|
|
66
|
+
<Td>
|
|
67
|
+
<Flex>
|
|
68
|
+
<Box paddingLeft={1}>
|
|
69
|
+
{entry.indexed && (
|
|
70
|
+
<Button
|
|
71
|
+
onClick={() =>
|
|
72
|
+
updateCollection({ contentType: entry.contentType })
|
|
73
|
+
}
|
|
74
|
+
size="S"
|
|
75
|
+
variant="secondary"
|
|
76
|
+
>
|
|
77
|
+
Update
|
|
78
|
+
</Button>
|
|
79
|
+
)}
|
|
80
|
+
</Box>
|
|
81
|
+
</Flex>
|
|
82
|
+
</Td>
|
|
83
|
+
</CheckPermissions>
|
|
73
84
|
</Tr>
|
|
74
85
|
)
|
|
75
86
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import React, { memo, useEffect, useState } from 'react'
|
|
2
|
-
import { Table, Tbody } from '@strapi/design-system
|
|
3
|
-
import { Box } from '@strapi/design-system/Box'
|
|
4
|
-
import { Button } from '@strapi/design-system/Button'
|
|
2
|
+
import { Box, Button, Table, Tbody } from '@strapi/design-system'
|
|
5
3
|
import { request, useAutoReloadOverlayBlocker } from '@strapi/helper-plugin'
|
|
6
4
|
import CollectionTableHeader from './CollectionTableHeader'
|
|
7
5
|
import CollectionColumn from './CollectionColumn'
|
|
@@ -17,10 +15,8 @@ const Collection = () => {
|
|
|
17
15
|
reloadNeeded,
|
|
18
16
|
refetchCollection,
|
|
19
17
|
} = useCollection()
|
|
20
|
-
const {
|
|
21
|
-
|
|
22
|
-
unlockAppWithAutoreload,
|
|
23
|
-
} = useAutoReloadOverlayBlocker()
|
|
18
|
+
const { lockAppWithAutoreload, unlockAppWithAutoreload } =
|
|
19
|
+
useAutoReloadOverlayBlocker()
|
|
24
20
|
const [reload, setReload] = useState(false)
|
|
25
21
|
|
|
26
22
|
const ROW_COUNT = 6
|
|
@@ -37,7 +33,7 @@ const Collection = () => {
|
|
|
37
33
|
{
|
|
38
34
|
method: 'GET',
|
|
39
35
|
},
|
|
40
|
-
true
|
|
36
|
+
true,
|
|
41
37
|
)
|
|
42
38
|
setReload(false)
|
|
43
39
|
} catch (err) {
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import React, { memo } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
Th,
|
|
4
|
+
Thead,
|
|
5
|
+
Tr,
|
|
6
|
+
Typography,
|
|
7
|
+
VisuallyHidden,
|
|
8
|
+
} from '@strapi/design-system'
|
|
9
|
+
import { CheckPermissions } from '@strapi/helper-plugin'
|
|
10
|
+
import { PERMISSIONS } from '../../constants'
|
|
5
11
|
|
|
6
12
|
const CollectionTableHeader = () => {
|
|
7
13
|
return (
|
|
8
14
|
<Thead>
|
|
9
15
|
<Tr>
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
16
|
+
<CheckPermissions permissions={PERMISSIONS.createAndDelete}>
|
|
17
|
+
<Th>
|
|
18
|
+
<VisuallyHidden>INDEX</VisuallyHidden>
|
|
19
|
+
</Th>
|
|
20
|
+
</CheckPermissions>
|
|
13
21
|
<Th>
|
|
14
22
|
<Typography variant="sigma">NAME</Typography>
|
|
15
23
|
</Th>
|
|
@@ -28,9 +36,11 @@ const CollectionTableHeader = () => {
|
|
|
28
36
|
<Th>
|
|
29
37
|
<Typography variant="sigma">HOOKS</Typography>
|
|
30
38
|
</Th>
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
39
|
+
<CheckPermissions permissions={PERMISSIONS.update}>
|
|
40
|
+
<Th>
|
|
41
|
+
<VisuallyHidden>Actions</VisuallyHidden>
|
|
42
|
+
</Th>
|
|
43
|
+
</CheckPermissions>
|
|
34
44
|
</Tr>
|
|
35
45
|
</Thead>
|
|
36
46
|
)
|
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
* HomePage
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
import { CheckPagePermissions } from '@strapi/helper-plugin'
|
|
7
7
|
import React, { memo } from 'react'
|
|
8
8
|
import PluginTabs from '../PluginTabs'
|
|
9
9
|
import PluginHeader from '../PluginHeader'
|
|
10
|
+
import { PERMISSIONS } from '../../constants'
|
|
10
11
|
|
|
11
12
|
const HomePage = () => {
|
|
12
13
|
return (
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
<CheckPagePermissions permissions={PERMISSIONS.main}>
|
|
15
|
+
<div>
|
|
16
|
+
<PluginHeader />
|
|
17
|
+
<PluginTabs />
|
|
18
|
+
</div>
|
|
19
|
+
</CheckPagePermissions>
|
|
17
20
|
)
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import React, { memo } from 'react'
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { Link } from '@strapi/design-system/Link'
|
|
5
|
-
import { BaseHeaderLayout } from '@strapi/design-system/Layout'
|
|
2
|
+
import { Box, Link, BaseHeaderLayout } from '@strapi/design-system'
|
|
3
|
+
import { ArrowLeft } from '@strapi/icons'
|
|
6
4
|
|
|
7
5
|
const PluginHeader = () => {
|
|
8
6
|
return (
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import React, { memo } from 'react'
|
|
2
|
-
import { Box } from '@strapi/design-system/Box'
|
|
3
2
|
import {
|
|
4
|
-
|
|
3
|
+
Box,
|
|
5
4
|
Tab,
|
|
6
5
|
TabGroup,
|
|
7
|
-
TabPanels,
|
|
8
6
|
TabPanel,
|
|
9
|
-
|
|
7
|
+
TabPanels,
|
|
8
|
+
Tabs,
|
|
9
|
+
} from '@strapi/design-system'
|
|
10
10
|
import { CollectionTable } from '../Collection'
|
|
11
11
|
import { Settings } from '../Settings'
|
|
12
|
+
import { CheckPermissions } from '@strapi/helper-plugin'
|
|
13
|
+
import { PERMISSIONS } from '../../constants'
|
|
12
14
|
|
|
13
15
|
const PluginTabs = () => {
|
|
14
16
|
return (
|
|
@@ -20,14 +22,18 @@ const PluginTabs = () => {
|
|
|
20
22
|
</Tabs>
|
|
21
23
|
<TabPanels>
|
|
22
24
|
<TabPanel>
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
|
|
25
|
+
<CheckPermissions permissions={PERMISSIONS.collections}>
|
|
26
|
+
<Box color="neutral800" padding={4} background="neutral0">
|
|
27
|
+
<CollectionTable />
|
|
28
|
+
</Box>
|
|
29
|
+
</CheckPermissions>
|
|
26
30
|
</TabPanel>
|
|
27
31
|
<TabPanel>
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
|
|
32
|
+
<CheckPermissions permissions={PERMISSIONS.settings}>
|
|
33
|
+
<Box color="neutral800" padding={4} background="neutral0">
|
|
34
|
+
<Settings />
|
|
35
|
+
</Box>
|
|
36
|
+
</CheckPermissions>
|
|
31
37
|
</TabPanel>
|
|
32
38
|
</TabPanels>
|
|
33
39
|
</TabGroup>
|