diff --git a/admin/src/components/ConfigList/index.jsx b/admin/src/components/ConfigList/index.jsx
index 4045a26..c4df491 100644
--- a/admin/src/components/ConfigList/index.jsx
+++ b/admin/src/components/ConfigList/index.jsx
@@ -1,7 +1,8 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import { useIntl } from 'react-intl';
import { isEmpty } from 'lodash';
import { useDispatch } from 'react-redux';
+import styled from 'styled-components';
import {
Table,
@@ -12,7 +13,12 @@ import {
Typography,
Checkbox,
Loader,
+ Flex,
+ Box,
+ TextInput,
+ IconButton,
} from '@strapi/design-system';
+import { CaretDown, CaretUp, Search, Cross } from '@strapi/icons';
import ConfigDiff from '../ConfigDiff';
import FirstExport from '../FirstExport';
@@ -20,14 +26,33 @@ import NoChanges from '../NoChanges';
import ConfigListRow from './ConfigListRow';
import { setConfigPartialDiffInState } from '../../state/actions/Config';
+const SearchBox = styled(Box)`
+ width: 100%;
+
+ @media (min-width: 768px) {
+ width: 40%;
+ }
+`;
const ConfigList = ({ diff, isLoading }) => {
const [cName, setCname] = useState('');
const [rows, setRows] = useState([]);
const [checkedItems, setCheckedItems] = useState([]);
+ const [sortBy, setSortBy] = useState('configName');
+ const [sortOrder, setSortOrder] = useState('asc');
+ const [searchQuery, setSearchQuery] = useState('');
const dispatch = useDispatch();
const { formatMessage } = useIntl();
+ const handleSort = (field) => {
+ if (sortBy === field) {
+ setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
+ } else {
+ setSortBy(field);
+ setSortOrder('asc');
+ }
+ };
+
const getConfigState = (configName) => {
if (
diff.fileConfig[configName]
@@ -83,6 +108,25 @@ const ConfigList = ({ diff, isLoading }) => {
dispatch(setConfigPartialDiffInState(newPartialDiff));
}, [checkedItems]);
+ const sortedRows = useMemo(() => {
+ const rowsWithIndex = rows.map((row, originalIndex) => ({ ...row, originalIndex }));
+
+ const filteredRows = searchQuery
+ ? rowsWithIndex.filter((row) => (
+ `${row.configType}.${row.configName}`.toLowerCase().includes(searchQuery.toLowerCase())
+ ))
+ : rowsWithIndex;
+
+ return filteredRows.sort((a, b) => {
+ const aValue = (a[sortBy] || '').toLowerCase();
+ const bValue = (b[sortBy] || '').toLowerCase();
+
+ if (aValue < bValue) return sortOrder === 'asc' ? -1 : 1;
+ if (aValue > bValue) return sortOrder === 'asc' ? 1 : -1;
+ return 0;
+ });
+ }, [rows, sortBy, sortOrder, searchQuery]);
+
const renderList = () => {
if (isLoading) {
return (
@@ -103,44 +147,76 @@ const ConfigList = ({ diff, isLoading }) => {
const allChecked = checkedItems && checkedItems.every(Boolean);
const isIndeterminate = checkedItems.some(Boolean) && !allChecked;
- return (
-
-
-
-
-
- setCheckedItems(checkedItems.map(() => value))}
+ const renderSortIcon = (field) => {
+ if (sortBy !== field) return null;
+ return sortOrder === 'asc' ? : ;
+ };
+
+ return (
+
+
+ }
+ endAction={searchQuery ? (
+ setSearchQuery('')}
+ variant="ghost"
+ >
+
+
+ ) : null}
+ aria-label={formatMessage({ id: 'config-sync.ConfigList.Search' })}
+ placeholder={formatMessage({ id: 'config-sync.ConfigList.Search' })}
+ value={searchQuery}
+ onChange={(e) => setSearchQuery(e.target.value)}
+ />
+
+
+
+
+ |
+ setCheckedItems(checkedItems.map(() => value))}
+ />
+ |
+ handleSort('configName')} style={{ cursor: 'pointer' }}>
+
+ {formatMessage({ id: 'config-sync.ConfigList.ConfigName' })}
+ {renderSortIcon('configName')}
+
+ |
+ handleSort('configType')} style={{ cursor: 'pointer' }}>
+
+ {formatMessage({ id: 'config-sync.ConfigList.ConfigType' })}
+ {renderSortIcon('configType')}
+
+ |
+ handleSort('state')} style={{ cursor: 'pointer' }}>
+
+ {formatMessage({ id: 'config-sync.ConfigList.State' })}
+ {renderSortIcon('state')}
+
+ |
+
+
+
+ {sortedRows.map((row) => (
+ {
+ checkedItems[row.originalIndex] = !checkedItems[row.originalIndex];
+ setCheckedItems([...checkedItems]);
+ }}
/>
-
-
- {formatMessage({ id: 'config-sync.ConfigList.ConfigName' })}
- |
-
- {formatMessage({ id: 'config-sync.ConfigList.ConfigType' })}
- |
-
- {formatMessage({ id: 'config-sync.ConfigList.State' })}
- |
-
-
-
- {rows.map((row, index) => (
- {
- checkedItems[index] = !checkedItems[index];
- setCheckedItems([...checkedItems]);
- }}
- />
- ))}
-
-
-
+ ))}
+
+ |
+
);
};
diff --git a/admin/src/translations/en.json b/admin/src/translations/en.json
index 0e807e4..b872d96 100644
--- a/admin/src/translations/en.json
+++ b/admin/src/translations/en.json
@@ -18,6 +18,8 @@
"ConfigList.Loading": "Loading content...",
"ConfigList.SelectAll": "Select all entries",
+ "ConfigList.Search": "Search",
+ "ConfigList.ClearSearch": "Clear search",
"ConfigList.ConfigName": "Config name",
"ConfigList.ConfigType": "Config type",
"ConfigList.State": "State",
diff --git a/admin/src/translations/es.json b/admin/src/translations/es.json
index a6a6f7e..d9d7cf0 100644
--- a/admin/src/translations/es.json
+++ b/admin/src/translations/es.json
@@ -1,4 +1,4 @@
-{
+{
"popUpWarning.warning.import_1": "Si continuas todos tus ficheros de configuración locales",
"popUpWarning.warning.import_2": "se importarán a la base de datos.",
"popUpWarning.warning.export_1": "Si continuas las configuraciones de tu base de datos",
@@ -9,11 +9,17 @@
"popUpWarning.force": "Forzar",
"popUpWarning.Confirmation": "Confirmación",
+ "envWarning.production.heading": "Estás en el entorno de producción",
+ "envWarning.production.message_1": "Ten cuidado al sincronizar la configuración en producción.",
+ "envWarning.production.message_2": "Asegúrate de no sobrescribir cambios de configuración importantes al importar.",
+
"Header.Title": "Config Sync",
"Header.Description": "Gestiona las configuraciones de tu base de datos entre diferentes entornos o instancias.",
"ConfigList.Loading": "Cargando contenido...",
"ConfigList.SelectAll": "Seleccionar todas las entradas",
+ "ConfigList.Search": "Buscar",
+ "ConfigList.ClearSearch": "Borrar búsqueda",
"ConfigList.ConfigName": "Nombre",
"ConfigList.ConfigType": "Tipo",
"ConfigList.State": "Estado",
@@ -31,6 +37,7 @@
"Buttons.Import": "Importar",
"Buttons.Export": "Exportar",
+ "Buttons.DownloadConfig": "Descargar configuración",
"FirstExport.Message": "Parece ser la primera vez que se usa config-sync en este proyecto.",
"FirstExport.Button": "Hacer la exportación inicial",
@@ -38,4 +45,4 @@
"Settings.Tool.Title": "Interfaz",
"plugin.name": "Config Sync"
-}
\ No newline at end of file
+}
diff --git a/admin/src/translations/ru.json b/admin/src/translations/ru.json
index a9e19e8..9fa243d 100644
--- a/admin/src/translations/ru.json
+++ b/admin/src/translations/ru.json
@@ -9,8 +9,8 @@
"popUpWarning.force": "Принудительно",
"popUpWarning.Confirmation": "Подтверждение",
- "envWarning.production.heading": "Вы находитесь в prodoction-среде",
- "envWarning.production.message_1": "Пожалуйста, будьте осторожны при синхронизации конфигурации в prodoction-среде.",
+ "envWarning.production.heading": "Вы находитесь в производственной среде",
+ "envWarning.production.message_1": "Будьте осторожны при синхронизации конфигурации в производственной среде.",
"envWarning.production.message_2": "Убедитесь, что вы не переопределяете важные изменения конфигурации при импорте.",
"Header.Title": "Config Sync",
@@ -18,6 +18,8 @@
"ConfigList.Loading": "Загрузка содержимого...",
"ConfigList.SelectAll": "Выбрать все записи",
+ "ConfigList.Search": "Поиск",
+ "ConfigList.ClearSearch": "Очистить поиск",
"ConfigList.ConfigName": "Имя конфигурации",
"ConfigList.ConfigType": "Тип конфигурации",
"ConfigList.State": "Состояние",
diff --git a/playground/types/generated/contentTypes.d.ts b/playground/types/generated/contentTypes.d.ts
index e4d7a16..1531c93 100644
--- a/playground/types/generated/contentTypes.d.ts
+++ b/playground/types/generated/contentTypes.d.ts
@@ -26,11 +26,6 @@ export interface AdminApiToken extends Struct.CollectionTypeSchema {
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
- adminPermissions: Schema.Attribute.Relation<
- 'oneToMany',
- 'admin::permission'
- >;
- adminUserOwner: Schema.Attribute.Relation<'manyToOne', 'admin::user'>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
@@ -44,9 +39,6 @@ export interface AdminApiToken extends Struct.CollectionTypeSchema {
minLength: 1;
}>;
expiresAt: Schema.Attribute.DateTime;
- kind: Schema.Attribute.Enumeration<['content-api', 'admin']> &
- Schema.Attribute.Required &
- Schema.Attribute.DefaultTo<'content-api'>;
lastUsedAt: Schema.Attribute.DateTime;
lifespan: Schema.Attribute.BigInteger;
locale: Schema.Attribute.String & Schema.Attribute.Private;
@@ -64,6 +56,7 @@ export interface AdminApiToken extends Struct.CollectionTypeSchema {
>;
publishedAt: Schema.Attribute.DateTime;
type: Schema.Attribute.Enumeration<['read-only', 'full-access', 'custom']> &
+ Schema.Attribute.Required &
Schema.Attribute.DefaultTo<'read-only'>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
@@ -141,7 +134,6 @@ export interface AdminPermission extends Struct.CollectionTypeSchema {
minLength: 1;
}>;
actionParameters: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>;
- apiToken: Schema.Attribute.Relation<'manyToOne', 'admin::api-token'>;
conditions: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<[]>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
@@ -249,7 +241,6 @@ export interface AdminSession extends Struct.CollectionTypeSchema {
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::session'> &
Schema.Attribute.Private;
- metadata: Schema.Attribute.JSON & Schema.Attribute.Private;
origin: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Private;
@@ -394,8 +385,6 @@ export interface AdminUser extends Struct.CollectionTypeSchema {
};
};
attributes: {
- apiTokens: Schema.Attribute.Relation<'oneToMany', 'admin::api-token'> &
- Schema.Attribute.Private;
blocked: Schema.Attribute.Boolean &
Schema.Attribute.Private &
Schema.Attribute.DefaultTo;
@@ -432,8 +421,6 @@ export interface AdminUser extends Struct.CollectionTypeSchema {
publishedAt: Schema.Attribute.DateTime;
registrationToken: Schema.Attribute.String & Schema.Attribute.Private;
resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;
- resetPasswordTokenExpiresAt: Schema.Attribute.DateTime &
- Schema.Attribute.Private;
roles: Schema.Attribute.Relation<'manyToMany', 'admin::role'> &
Schema.Attribute.Private;
updatedAt: Schema.Attribute.DateTime;
@@ -996,7 +983,7 @@ export interface PluginUsersPermissionsUser
}
declare module '@strapi/strapi' {
- export namespace Public {
+ export module Public {
export interface ContentTypeSchemas {
'admin::api-token': AdminApiToken;
'admin::api-token-permission': AdminApiTokenPermission;