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.
Files changed (36) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +27 -6
  3. package/admin/src/Hooks/useAlert.js +17 -0
  4. package/admin/src/Hooks/useCollection.js +69 -56
  5. package/admin/src/Hooks/useCredential.js +24 -20
  6. package/admin/src/components/Initializer/index.js +27 -0
  7. package/admin/src/components/PluginIcon/index.js +1 -1
  8. package/admin/src/constants.js +34 -0
  9. package/admin/src/containers/Collection/CollectionColumn.js +43 -32
  10. package/admin/src/containers/Collection/CollectionTable.js +4 -8
  11. package/admin/src/containers/Collection/CollectionTableHeader.js +19 -9
  12. package/admin/src/containers/HomePage/index.js +8 -5
  13. package/admin/src/containers/PluginHeader/index.js +2 -4
  14. package/admin/src/containers/PluginTabs/index.js +16 -10
  15. package/admin/src/containers/Settings/Credentials.js +23 -24
  16. package/admin/src/containers/Settings/PluginActions.js +1 -3
  17. package/admin/src/containers/Settings/Settings.js +1 -1
  18. package/admin/src/index.js +4 -2
  19. package/package.json +16 -13
  20. package/server/__mocks__/strapi.js +7 -5
  21. package/server/__tests__/configuration-validation.test.js +56 -21
  22. package/server/__tests__/configuration.test.js +1 -1
  23. package/server/__tests__/content-types.test.js +5 -5
  24. package/server/__tests__/meilisearch.test.js +39 -9
  25. package/server/bootstrap.js +45 -0
  26. package/server/configuration-validation.js +35 -18
  27. package/server/constants.js +13 -0
  28. package/server/controllers/reload.js +1 -2
  29. package/server/policies/isAdmin.js +1 -1
  30. package/server/routes/index.js +37 -7
  31. package/server/services/content-types/content-types.js +4 -4
  32. package/server/services/lifecycle/lifecycle.js +5 -5
  33. package/server/services/meilisearch/config.js +18 -6
  34. package/server/services/meilisearch/connector.js +54 -27
  35. package/server/services/store/credential.js +4 -7
  36. package/admin/src/containers/Collection/utils/getTrad.js +0 -5
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021-2022 Meili SAS
3
+ Copyright (c) 2021-2024 Meili SAS
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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 toc -->
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 meiliSearch instance and, a running Strapi app, let's go to the plugin page on your admin dashboard.
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 meiliSearch <!-- omit in toc -->
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 / NPM versions**:
568
+ **Node**:
547
569
 
548
- - NodeJS >= 14.10 <= 16
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
- const { error } = await request(
61
- `/${pluginId}/content-type/${contentType}`,
62
- {
63
- method: 'DELETE',
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
- if (error) {
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
- const { error } = await request(`/${pluginId}/content-type`, {
84
- method: 'POST',
85
- body: {
86
- contentType,
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
- const { error } = await request(`/${pluginId}/content-type`, {
107
- method: 'PUT',
108
- body: {
109
- contentType,
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
- const { error } = await request(`/${pluginId}/credential`, {
23
- method: 'POST',
24
- body: {
25
- apiKey: apiKey,
26
- host: host,
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
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import React from 'react'
8
- import Search from '@strapi/icons/Search'
8
+ import { Search } from '@strapi/icons'
9
9
 
10
10
  const PluginIcon = () => <Search />
11
11
 
@@ -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 { Tr, Td } from '@strapi/design-system/Table'
3
- import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox'
4
- import { Typography } from '@strapi/design-system/Typography'
5
- import { Flex } from '@strapi/design-system/Flex'
6
- import { Box } from '@strapi/design-system/Box'
7
- import { Button } from '@strapi/design-system/Button'
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
- <Td>
18
- <BaseCheckbox
19
- aria-label={`Select ${entry.collection}`}
20
- onValueChange={() => {
21
- if (entry.indexed)
22
- deleteCollection({ contentType: entry.contentType })
23
- else addCollection({ contentType: entry.contentType })
24
- }}
25
- value={entry.indexed}
26
- />
27
- </Td>
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
- <Td>
59
- <Flex>
60
- <Box paddingLeft={1}>
61
- <Button
62
- onClick={() =>
63
- updateCollection({ contentType: entry.contentType })
64
- }
65
- size="S"
66
- variant="secondary"
67
- >
68
- Update
69
- </Button>
70
- </Box>
71
- </Flex>
72
- </Td>
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/Table'
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
- lockAppWithAutoreload,
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 { Thead, Tr, Th } from '@strapi/design-system/Table'
3
- import { Typography } from '@strapi/design-system/Typography'
4
- import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden'
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
- <Th>
11
- <VisuallyHidden>INDEX</VisuallyHidden>
12
- </Th>
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
- <Th>
32
- <VisuallyHidden>Actions</VisuallyHidden>
33
- </Th>
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
- <div>
14
- <PluginHeader />
15
- <PluginTabs />
16
- </div>
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 ArrowLeft from '@strapi/icons/ArrowLeft'
3
- import { Box } from '@strapi/design-system/Box'
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
- Tabs,
3
+ Box,
5
4
  Tab,
6
5
  TabGroup,
7
- TabPanels,
8
6
  TabPanel,
9
- } from '@strapi/design-system/Tabs'
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
- <Box color="neutral800" padding={4} background="neutral0">
24
- <CollectionTable />
25
- </Box>
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
- <Box color="neutral800" padding={4} background="neutral0">
29
- <Settings />
30
- </Box>
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>