mirror of
https://github.com/strapi/strapi.git
synced 2025-10-28 00:22:51 +00:00
Merge branch 'master' into alpha.8.1
This commit is contained in:
commit
2e55706fdb
12
README.md
12
README.md
@ -41,7 +41,7 @@ This is the production-ready version of Strapi (v1). You should also consider th
|
||||
npm install strapi -g
|
||||
```
|
||||
|
||||
Read the [Getting started](http://strapi.io/documentation/getting-started/quick-start.html) page to create your first project using Strapi.
|
||||
Read the [Getting started](https://strapi.io/getting-started) page to create your first project using Strapi.
|
||||
|
||||
## Features
|
||||
|
||||
@ -56,7 +56,7 @@ Read the [Getting started](http://strapi.io/documentation/getting-started/quick-
|
||||
|
||||
## Philosophy ?
|
||||
|
||||
> At [Strapi](http://strapi.io), everything we do we believe in changing the status quo of web development. Our products are simple to use, user friendly and production-ready.
|
||||
> At [Strapi](https://strapi.io), everything we do we believe in changing the status quo of web development. Our products are simple to use, user friendly and production-ready.
|
||||
|
||||
Web and mobile applications needed a powerful, simple to use and production-ready API-driven solution. That's why we created Strapi, an open-source Content Management Framework (CMF) for exposing your content (data, media) accross multi-devices.
|
||||
|
||||
@ -78,8 +78,12 @@ For general help using Strapi, please refer to [the official Strapi documentatio
|
||||
|
||||
### Professional support
|
||||
|
||||
[Strapi Solutions](http://strapi.io), the company behind Strapi, provides a full range of solutions to get better results, faster. We're always looking for the next challenge: coaching, consulting, training, customization, etc. [Drop us an email](mailto:support@strapi.io) to see how we can help you.
|
||||
[Strapi Solutions](https://strapi.io), the company behind Strapi, provides a full range of solutions to get better results, faster. We're always looking for the next challenge: coaching, consulting, training, customization, etc. [Drop us an email](mailto:support@strapi.io) to see how we can help you.
|
||||
|
||||
### Migration
|
||||
|
||||
Follow our [migration guides](https://strapi.io/documentation/migration/migration-guide.html) on the website to keep your Strapi projects updated.
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](LICENSE.md) Copyright (c) 2015-2018 [Strapi Solutions](http://strapi.io/).
|
||||
[MIT License](LICENSE.md) Copyright (c) 2015-2018 [Strapi Solutions](https://strapi.io/).
|
||||
|
||||
@ -12,10 +12,10 @@ The most advanced open-source Content Management Framework to build powerful API
|
||||
|
||||
{% endcenter %}
|
||||
|
||||
## v3@alpha.7 is available!
|
||||
## v3@alpha.8 is available!
|
||||
We've been working on a major update for Strapi during the past months, rewriting the core framework and the dashboard.
|
||||
|
||||
This documentation is only related to Strapi v3@alpha.7 ([v1 documentation is still available](http://strapi.io/documentation/1.x.x)).
|
||||
This documentation is only related to Strapi v3@alpha.8 ([v1 documentation is still available](http://strapi.io/documentation/1.x.x)).
|
||||
|
||||
**[Get Started](getting-started/installation.md)**<br />
|
||||
Learn how to install Strapi and start developing your API.
|
||||
@ -36,4 +36,3 @@ Understand how to develop your own plugin.
|
||||
Learn about Strapi's API, the `strapi` object that is available in your backend.
|
||||
|
||||
**[Migration guide](migration/migration-guide.md)**<br />
|
||||
Migrate from v1 to v3@alpha.7.
|
||||
|
||||
@ -53,3 +53,4 @@
|
||||
|
||||
### Migration
|
||||
* [Migrating from v1 to v3](migration/migration-guide.md)
|
||||
* [Migrating from 3.0.0-alpha.7.4 to 3.0.0-alpha.8](migration/migration-guide-alpha-7-4-to-alpha-8.md)
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
# Migrating from 3.0.0-alpha.7.3 to 3.0.0-alpha.8
|
||||
|
||||
**Here are the major changes:**
|
||||
|
||||
- Fix deployment process
|
||||
- Setup database connection on project creation
|
||||
- Helper for table creation for SQL database
|
||||
|
||||
> Feel free to [join us on Slack](http://slack.strapi.io) and ask questions about the migration process.
|
||||
|
||||
## Getting started
|
||||
|
||||
Install Strapi `alpha.8` globally on your computer. To do so run `npm install strapi@3.0.0-alpha.8 -g`.
|
||||
|
||||
When it's done, generate a new empty project `strapi new myNewProject` (don't pay attention to the database configuration).
|
||||
|
||||
## Configurations
|
||||
|
||||
You will have to update just 1 file: `package.json`
|
||||
|
||||
- Edit the scripts section: (only the `setup` line has changed)
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"setup": "cd admin && npm run setup",
|
||||
"start": "node server.js",
|
||||
"strapi": "node_modules/strapi/bin/strapi.js",
|
||||
"lint": "node_modules/.bin/eslint api/**/*.js config/**/*.js plugins/**/*.js",
|
||||
"postinstall": "node node_modules/strapi/lib/utils/post-install.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Edit the Strapi's dependencies version: (move Strapi's dependencies to `3.0.0-alpha.8` version)
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"lodash": "4.x.x",
|
||||
"strapi": "3.0.0-alpha.8",
|
||||
"strapi-mongoose": "3.0.0-alpha.8"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Update the Admin
|
||||
|
||||
Delete your old admin folder and replace by the new one.
|
||||
|
||||
## Update the Plugins
|
||||
|
||||
Copy these 3 files `/plugins/users-permissions/config/jwt.json`, `/plugins/users-permissions/config/roles.json` and `/plugins/users-permissions/models/User.settings.json` **from your old project** and paste them in the corresponding ones in your new project. It is important to save these files.
|
||||
|
||||
Then, delete your old `plugins` folder and replace it by the new one.
|
||||
|
||||
That's all, you have now upgraded to Strapi `alpha.8`.
|
||||
@ -330,7 +330,7 @@ export default FooPage;
|
||||
|
||||
## OverlayBlocker
|
||||
|
||||
The OverlayBlocker is a React component that is very useful to block user interactions when the strapi server is restarting in order to avoid front-end errors.
|
||||
The OverlayBlocker is a React component that is very useful to block user interactions when the strapi server is restarting in order to avoid front-end errors. This component is automatically displayed when the server needs to restart. You need to disable it in order to override the current design (once disabled it won't show on the other plugins so it's really important to enable it back when the component is unmounting).
|
||||
|
||||
### Usage
|
||||
|
||||
@ -341,7 +341,7 @@ The OverlayBlocker is a React component that is very useful to block user intera
|
||||
|
||||
### Example
|
||||
|
||||
In this example we'll have a button that when clicked it will display the OverlayBlocker for 5 seconds thus 'freezes' the admin so the user can't navigate (it simulates a very long server restart).
|
||||
In this example we'll have a button that when clicked will display the OverlayBlocker for 5 seconds thus 'freezes' the admin so the user can't navigate (it simulates a very long server restart).
|
||||
|
||||
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/constants.js`.
|
||||
```js
|
||||
@ -373,6 +373,12 @@ import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
|
||||
// Actions required for disabling and enabling the OverlayBlocker
|
||||
import {
|
||||
disableGlobalOverlayBlocker,
|
||||
enableGlobalOverlayBlocker,
|
||||
} from 'actions/overlayBlocker';
|
||||
|
||||
// Design
|
||||
import Button from 'components/Button';
|
||||
import OverlayBlocker from 'components/OverlayBlocker';
|
||||
@ -395,6 +401,16 @@ import makeSelectFooPage from './selectors';
|
||||
|
||||
|
||||
export class FooPage extends React.Component {
|
||||
componentDidMount() {
|
||||
// Disable the AdminPage OverlayBlocker in order to give it a custom design (children)
|
||||
this.props.disableGlobalOverlayBlocker();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Enable the AdminPage OverlayBlocker so it is displayed when the server is restarting in the other plugins
|
||||
this.props.enableGlobalOverlayBlocker();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
@ -410,6 +426,8 @@ export class FooPage extends React.Component {
|
||||
}
|
||||
|
||||
FooPage.propTypes = {
|
||||
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
||||
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
||||
onButtonClick: PropTypes.func.isRequired,
|
||||
showOverlayBlocker: PropTypes.bool.isRequired,
|
||||
};
|
||||
@ -419,6 +437,8 @@ const mapStateToProps = makeSelectFooPage();
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
disableGlobalOverlayBlocker,
|
||||
enableGlobalOverlayBlocker,
|
||||
onButtonClick,
|
||||
},
|
||||
dispatch,
|
||||
|
||||
@ -2,6 +2,63 @@
|
||||
|
||||
Strapi provides helpers so you don't have to develop again and again the same generic functions.
|
||||
|
||||
## Auth
|
||||
|
||||
`auth.js` lets you get, set and delete data in either the browser's `localStorage` or `sessionStorage`.
|
||||
|
||||
### Methods
|
||||
|
||||
| Name | Description |
|
||||
| ---- | ----------- |
|
||||
| clear(key) | Remove the data in either `localStorage` or `sessionStorage` |
|
||||
| clearAppStorage() | Remove all data from both storage |
|
||||
| clearToken() | Remove the user's `jwt Token` in the appropriate browser's storage |
|
||||
| clearUserInfo() | Remove the user's info from storage |
|
||||
| get(key) | Get the item in the browser's storage |
|
||||
| getToken() | Get the user's `jwtToken` |
|
||||
| getUserInfo() | Get the user's infos |
|
||||
| set(value, key, isLocalStorage) | Set an item in the `sessionStorage`. If `true` is passed as the 3rd parameter it sets the value in the `localStorage` |
|
||||
| setToken(value, isLocalStorage) | Set the user's `jwtToken` in the `sessionStorage`. If `true` is passed as the 2nd parameter it sets the value in the `localStorage` |
|
||||
| setUserInfo(value, isLocalStorage) | Set the user's info in the `sessionStorage`. If `true` is passed as the 2nd parameter it sets the value in the `localStorage` |
|
||||
|
||||
|
||||
```js
|
||||
import auth from 'utils/auth';
|
||||
|
||||
// ...
|
||||
//
|
||||
auth.setToken('12345', true); // This will set 1234 in the browser's localStorage associated with the key: jwtToken
|
||||
```
|
||||
|
||||
## Colors
|
||||
|
||||
This function allows to darken a color.
|
||||
|
||||
### Usage
|
||||
|
||||
```js
|
||||
import { darken } from 'utils/colors';
|
||||
|
||||
const linkColor = darken('#f5f5f5', 1.5); // Will darken #F5F5F5 by 1.5% which gives #f2f2f2.
|
||||
```
|
||||
|
||||
## Get URL Query Parameters
|
||||
|
||||
The helpers allows to retrieve the query parameters in the URL.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
import getQueryParameters from 'utils/getQueryParameters';
|
||||
|
||||
const URL = '/create?source=users-permissions';
|
||||
const source = getQueryParameters(URL, 'source');
|
||||
|
||||
console.log(source); // users-permissions
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Request helper
|
||||
|
||||
A request helper is available to handle all requests inside a plugin.
|
||||
@ -368,7 +425,6 @@ function* defaultSaga() {
|
||||
export default defaultSaga;
|
||||
```
|
||||
|
||||
***
|
||||
***
|
||||
|
||||
### Example with server autoReload watcher
|
||||
|
||||
@ -23,7 +23,14 @@ import LanguageProvider from 'containers/LanguageProvider';
|
||||
|
||||
import App from 'containers/App';
|
||||
import { showNotification } from 'containers/NotificationProvider/actions';
|
||||
import { pluginLoaded, updatePlugin, unsetHasUserPlugin } from 'containers/App/actions';
|
||||
import {
|
||||
freezeApp,
|
||||
pluginLoaded,
|
||||
unfreezeApp,
|
||||
unsetHasUserPlugin,
|
||||
updatePlugin,
|
||||
} from 'containers/App/actions';
|
||||
|
||||
import auth from 'utils/auth';
|
||||
import configureStore from './store';
|
||||
import { translationMessages, languages } from './i18n';
|
||||
@ -175,6 +182,13 @@ const displayNotification = (message, status) => {
|
||||
store.dispatch(showNotification(message, status));
|
||||
};
|
||||
|
||||
const lockApp = () => {
|
||||
store.dispatch(freezeApp());
|
||||
};
|
||||
|
||||
const unlockApp = () => {
|
||||
store.dispatch(unfreezeApp());
|
||||
};
|
||||
|
||||
/**
|
||||
* Public Strapi object exposed to the `window` object
|
||||
@ -208,6 +222,8 @@ window.strapi = Object.assign(window.strapi || {}, {
|
||||
router: history,
|
||||
languages,
|
||||
currentLanguage: window.localStorage.getItem('strapi-admin-language') || window.navigator.language || window.navigator.userLanguage || 'en',
|
||||
lockApp,
|
||||
unlockApp,
|
||||
});
|
||||
|
||||
const dispatch = store.dispatch;
|
||||
|
||||
@ -17,7 +17,13 @@ import { Switch, Route } from 'react-router-dom';
|
||||
import { get, includes, isFunction, map, omit } from 'lodash';
|
||||
|
||||
import { pluginLoaded, updatePlugin } from 'containers/App/actions';
|
||||
import { selectHasUserPlugin, selectPlugins } from 'containers/App/selectors';
|
||||
import {
|
||||
makeSelectBlockApp,
|
||||
makeSelectShowGlobalAppBlocker,
|
||||
selectHasUserPlugin,
|
||||
selectPlugins,
|
||||
} from 'containers/App/selectors';
|
||||
|
||||
import { hideNotification } from 'containers/NotificationProvider/actions';
|
||||
|
||||
// Design
|
||||
@ -30,6 +36,7 @@ import LeftMenu from 'containers/LeftMenu';
|
||||
import ListPluginsPage from 'containers/ListPluginsPage';
|
||||
import Logout from 'components/Logout';
|
||||
import NotFoundPage from 'containers/NotFoundPage';
|
||||
import OverlayBlocker from 'components/OverlayBlocker';
|
||||
import PluginPage from 'containers/PluginPage';
|
||||
|
||||
import auth from 'utils/auth';
|
||||
@ -130,6 +137,7 @@ export class AdminPage extends React.Component { // eslint-disable-line react/pr
|
||||
</Switch>
|
||||
</Content>
|
||||
</div>
|
||||
<OverlayBlocker isOpen={this.props.blockApp && this.props.showGlobalAppBlocker} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -149,17 +157,21 @@ AdminPage.defaultProps = {
|
||||
};
|
||||
|
||||
AdminPage.propTypes = {
|
||||
blockApp: PropTypes.bool.isRequired,
|
||||
hasUserPlugin: PropTypes.bool,
|
||||
history: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
pluginLoaded: PropTypes.func.isRequired,
|
||||
plugins: PropTypes.object.isRequired,
|
||||
showGlobalAppBlocker: PropTypes.bool.isRequired,
|
||||
updatePlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
blockApp: makeSelectBlockApp(),
|
||||
hasUserPlugin: selectHasUserPlugin(),
|
||||
plugins: selectPlugins(),
|
||||
showGlobalAppBlocker: makeSelectShowGlobalAppBlocker(),
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
|
||||
@ -5,13 +5,21 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
FREEZE_APP,
|
||||
LOAD_PLUGIN,
|
||||
UPDATE_PLUGIN,
|
||||
PLUGIN_LOADED,
|
||||
PLUGIN_DELETED,
|
||||
PLUGIN_LOADED,
|
||||
UNFREEZE_APP,
|
||||
UNSET_HAS_USERS_PLUGIN,
|
||||
UPDATE_PLUGIN,
|
||||
} from './constants';
|
||||
|
||||
export function freezeApp() {
|
||||
return {
|
||||
type: FREEZE_APP,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadPlugin(newPlugin) {
|
||||
return {
|
||||
type: LOAD_PLUGIN,
|
||||
@ -19,22 +27,6 @@ export function loadPlugin(newPlugin) {
|
||||
};
|
||||
}
|
||||
|
||||
export function updatePlugin(pluginId, updatedKey, updatedValue) {
|
||||
return {
|
||||
type: UPDATE_PLUGIN,
|
||||
pluginId,
|
||||
updatedKey,
|
||||
updatedValue,
|
||||
};
|
||||
}
|
||||
|
||||
export function pluginLoaded(newPlugin) {
|
||||
return {
|
||||
type: PLUGIN_LOADED,
|
||||
plugin: newPlugin,
|
||||
};
|
||||
}
|
||||
|
||||
export function pluginDeleted(plugin) {
|
||||
return {
|
||||
type: PLUGIN_DELETED,
|
||||
@ -42,8 +34,30 @@ export function pluginDeleted(plugin) {
|
||||
};
|
||||
}
|
||||
|
||||
export function pluginLoaded(newPlugin) {
|
||||
return {
|
||||
type: PLUGIN_LOADED,
|
||||
plugin: newPlugin,
|
||||
};
|
||||
}
|
||||
|
||||
export function unfreezeApp() {
|
||||
return {
|
||||
type: UNFREEZE_APP,
|
||||
};
|
||||
}
|
||||
|
||||
export function unsetHasUserPlugin() {
|
||||
return {
|
||||
type: UNSET_HAS_USERS_PLUGIN,
|
||||
};
|
||||
}
|
||||
|
||||
export function updatePlugin(pluginId, updatedKey, updatedValue) {
|
||||
return {
|
||||
type: UPDATE_PLUGIN,
|
||||
pluginId,
|
||||
updatedKey,
|
||||
updatedValue,
|
||||
};
|
||||
}
|
||||
|
||||
@ -4,8 +4,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
export const UNSET_HAS_USERS_PLUGIN = 'app/App/UNSET_HAS_USERS_PLUGIN';
|
||||
export const FREEZE_APP = 'app/App/FREEZE_APP';
|
||||
export const LOAD_PLUGIN = 'app/App/LOAD_PLUGIN';
|
||||
export const UPDATE_PLUGIN = 'app/App/UPDATE_PLUGIN';
|
||||
export const PLUGIN_LOADED = 'app/App/PLUGIN_LOADED';
|
||||
export const PLUGIN_DELETED = 'app/App/PLUGIN_DELETED';
|
||||
export const UNFREEZE_APP = 'app/App/UNFREEZE_APP';
|
||||
export const UNSET_HAS_USERS_PLUGIN = 'app/App/UNSET_HAS_USERS_PLUGIN';
|
||||
export const UPDATE_PLUGIN = 'app/App/UPDATE_PLUGIN';
|
||||
|
||||
@ -1,24 +1,43 @@
|
||||
import { fromJS } from 'immutable';
|
||||
// Shared constants
|
||||
import {
|
||||
UPDATE_PLUGIN,
|
||||
DISABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||
ENABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||
} from 'constants/overlayBlocker';
|
||||
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
import {
|
||||
FREEZE_APP,
|
||||
PLUGIN_DELETED,
|
||||
PLUGIN_LOADED,
|
||||
UNFREEZE_APP,
|
||||
UNSET_HAS_USERS_PLUGIN,
|
||||
UPDATE_PLUGIN,
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
plugins: {},
|
||||
blockApp: false,
|
||||
hasUserPlugin: true,
|
||||
plugins: {},
|
||||
showGlobalAppBlocker: true,
|
||||
});
|
||||
|
||||
function appReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case DISABLE_GLOBAL_OVERLAY_BLOCKER:
|
||||
return state.set('showGlobalAppBlocker', false);
|
||||
case ENABLE_GLOBAL_OVERLAY_BLOCKER:
|
||||
return state.set('showGlobalAppBlocker', true);
|
||||
case FREEZE_APP:
|
||||
return state.set('blockApp', true);
|
||||
case PLUGIN_LOADED:
|
||||
return state.setIn(['plugins', action.plugin.id], fromJS(action.plugin));
|
||||
case UPDATE_PLUGIN:
|
||||
return state.setIn(['plugins', action.pluginId, action.updatedKey], fromJS(action.updatedValue));
|
||||
case PLUGIN_DELETED:
|
||||
return state.deleteIn(['plugins', action.plugin]);
|
||||
case UNFREEZE_APP:
|
||||
return state.set('blockApp', false);
|
||||
case UNSET_HAS_USERS_PLUGIN:
|
||||
return state.set('hasUserPlugin', false);
|
||||
default:
|
||||
|
||||
@ -19,8 +19,20 @@ const selectHasUserPlugin = () => createSelector(
|
||||
(appState) => appState.get('hasUserPlugin'),
|
||||
);
|
||||
|
||||
const makeSelectShowGlobalAppBlocker = () => createSelector(
|
||||
selectApp(),
|
||||
(appState) => appState.get('showGlobalAppBlocker'),
|
||||
);
|
||||
|
||||
const makeSelectBlockApp = () => createSelector(
|
||||
selectApp(),
|
||||
(appState) => appState.get('blockApp'),
|
||||
);
|
||||
|
||||
export {
|
||||
selectApp,
|
||||
selectHasUserPlugin,
|
||||
selectPlugins,
|
||||
makeSelectBlockApp,
|
||||
makeSelectShowGlobalAppBlocker,
|
||||
};
|
||||
|
||||
@ -13,6 +13,11 @@ import { bindActionCreators, compose } from 'redux';
|
||||
import cn from 'classnames';
|
||||
import { get, isUndefined, map } from 'lodash';
|
||||
|
||||
import {
|
||||
disableGlobalOverlayBlocker,
|
||||
enableGlobalOverlayBlocker,
|
||||
} from 'actions/overlayBlocker';
|
||||
|
||||
// Design
|
||||
// import Input from 'components/Input';
|
||||
import DownloadInfo from 'components/DownloadInfo';
|
||||
@ -43,12 +48,20 @@ export class InstallPluginPage extends React.Component { // eslint-disable-line
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
// Disable the AdminPage OverlayBlocker in order to give it a custom design (children)
|
||||
this.props.disableGlobalOverlayBlocker();
|
||||
|
||||
// Don't fetch the available plugins if it has already been done
|
||||
if (!this.props.didFetchPlugins) {
|
||||
this.props.getPlugins();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Enable the AdminPage OverlayBlocker so it is displayed when the server is restarting
|
||||
this.props.enableGlobalOverlayBlocker();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
@ -118,7 +131,9 @@ InstallPluginPage.propTypes = {
|
||||
availablePlugins: PropTypes.array.isRequired,
|
||||
blockApp: PropTypes.bool.isRequired,
|
||||
didFetchPlugins: PropTypes.bool.isRequired,
|
||||
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
||||
downloadPlugin: PropTypes.func.isRequired,
|
||||
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
||||
getPlugins: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
// onChange: PropTypes.func.isRequired,
|
||||
@ -130,7 +145,9 @@ const mapStateToProps = makeSelectInstallPluginPage();
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
disableGlobalOverlayBlocker,
|
||||
downloadPlugin,
|
||||
enableGlobalOverlayBlocker,
|
||||
getPlugins,
|
||||
onChange,
|
||||
},
|
||||
|
||||
@ -37,7 +37,7 @@ export class PluginPage extends React.Component { // eslint-disable-line react/p
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary key={plugin.id}>
|
||||
<ErrorBoundary key={plugin.id}>
|
||||
<Elem {...this.props} {...blockerComponentProps} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
@ -60,6 +60,9 @@
|
||||
|
||||
"components.ErrorBoundary.title": "Something wen't wrong...",
|
||||
|
||||
"components.OverlayBlocker.title": "Waiting for restart...",
|
||||
"components.OverlayBlocker.description": "You're using a feature that needs the server to restart. Please wait until the server is up.",
|
||||
|
||||
"components.ProductionBlocker.header": "This plugin is only available in development.",
|
||||
"components.ProductionBlocker.description": "For safety we have to disable this plugin in other environments.",
|
||||
|
||||
|
||||
@ -58,6 +58,9 @@
|
||||
"components.AutoReloadBlocker.header": "L'autoReload doit être activé pour ce plugin.",
|
||||
"components.AutoReloadBlocker.description": "Ouvrez le fichier suivant pour activer cette fonctionnalité.",
|
||||
|
||||
"components.OverlayBlocker.title": "Le serveur est en train de redémarrer",
|
||||
"components.OverlayBlocker.description": "Vous utilisez une fonctionnalité qui nécessite le redémarrage du server. Merci d'attendre que celui-ci ait redémarré.",
|
||||
|
||||
"components.ErrorBoundary.title": "Une erreur est survenue...",
|
||||
|
||||
"components.ProductionBlocker.header": "Ce plugin est disponible uniquement en développement.",
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
/*
|
||||
*
|
||||
* Shared OverlayBlocker actions
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
import {
|
||||
DISABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||
ENABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||
} from '../constants/overlayBlocker';
|
||||
|
||||
export function disableGlobalOverlayBlocker() {
|
||||
return {
|
||||
type: DISABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||
};
|
||||
}
|
||||
|
||||
export function enableGlobalOverlayBlocker() {
|
||||
return {
|
||||
type: ENABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||
};
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
height: 6rem;
|
||||
width: 6.5rem;
|
||||
line-height: 6rem;
|
||||
z-index: 999999;
|
||||
z-index: 999;
|
||||
text-align: center;
|
||||
background-color: #FFFFFF;
|
||||
color: #81848A;
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import cn from 'classnames';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
@ -22,11 +24,32 @@ class OverlayBlocker extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const content = this.props.children ? (
|
||||
this.props.children
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.icoContainer}>
|
||||
<i className="fa fa-refresh" />
|
||||
</div>
|
||||
<div>
|
||||
<h4>
|
||||
<FormattedMessage id="components.OverlayBlocker.title" />
|
||||
</h4>
|
||||
<p>
|
||||
<FormattedMessage id="components.OverlayBlocker.description" />
|
||||
</p>
|
||||
<div className={styles.buttonContainer}>
|
||||
<a className={cn(styles.primary, 'btn')} href="https://strapi.io/documentation/configurations/configurations.html#server" target="_blank">Read the documentation</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.isOpen) {
|
||||
return ReactDOM.createPortal(
|
||||
<div className={styles.overlay}>
|
||||
<div>
|
||||
{this.props.children}
|
||||
{content}
|
||||
</div>
|
||||
</div>,
|
||||
this.overlayContainer
|
||||
|
||||
@ -1,3 +1,35 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
> div {
|
||||
padding-top: 2.5rem;
|
||||
> h4 {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
> p {
|
||||
margin-top: -1px;
|
||||
font-size: 14px;
|
||||
color: #919BAE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icoContainer {
|
||||
padding-top: 0 !important;
|
||||
font-size: 4.2rem;
|
||||
color: #323740;
|
||||
margin-right: 20px;
|
||||
line-height: 9.3rem;
|
||||
> i {
|
||||
-webkit-animation:spin 4s linear infinite;
|
||||
-moz-animation:spin 4s linear infinite;
|
||||
animation:spin 4s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -5,12 +37,86 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1040;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
&:before {
|
||||
position: fixed;
|
||||
content: '';
|
||||
top: 6rem;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(rgba(0,0,0, 15) 0%, rgba(0,0,0,0) 100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content:'';
|
||||
position: fixed;
|
||||
top: 6rem;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 24rem;
|
||||
background: linear-gradient(#FBFBFB 20%, rgba(0, 0, 100, 0) 100%);
|
||||
box-shadow: inset 0px 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: inset 0 1px 2px 0 rgba(40, 42, 49, 0.16);
|
||||
}
|
||||
|
||||
> div {
|
||||
position: fixed;
|
||||
top: 17.5rem;
|
||||
top: 11.5rem;
|
||||
left: 50%;
|
||||
margin-left: -24rem;
|
||||
margin-left: -17.5rem;
|
||||
z-index: 1100;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
padding-top: 3.9rem;
|
||||
> a {
|
||||
height: 30px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.primary {
|
||||
min-width: 15rem;
|
||||
padding-top: 4px;
|
||||
padding-left: 1.6rem;
|
||||
padding-right: 1.6rem;
|
||||
border-radius: 0.3rem;
|
||||
border: none;
|
||||
background: linear-gradient(315deg, #0097F6 0%, #005EEA 100%);
|
||||
color: white;
|
||||
font-family: Lato;
|
||||
font-weight: 600;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
cursor: pointer;
|
||||
|
||||
> i {
|
||||
margin-right: 1.3rem;
|
||||
font-weight: 600;
|
||||
padding-top: 1px;
|
||||
}
|
||||
&:before {
|
||||
content: '\f02d';
|
||||
font-family: 'FontAwesome';
|
||||
font-weight: 600;
|
||||
font-size: 1.3rem;
|
||||
margin-right: 13px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: inset 1px 1px 3px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
|
||||
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
|
||||
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
/*
|
||||
*
|
||||
* Shared OverlayBlocker constants
|
||||
*
|
||||
*/
|
||||
|
||||
export const DISABLE_GLOBAL_OVERLAY_BLOCKER = 'strapiHelperPlugin/OverlayBlocker/DISABLE_GLOBAL_OVERLAY_BLOCKER';
|
||||
export const ENABLE_GLOBAL_OVERLAY_BLOCKER = 'strapiHelperPlugin/OverlayBlocker/ENABLE_GLOBAL_OVERLAY_BLOCKER';
|
||||
@ -1,5 +1,6 @@
|
||||
import 'whatwg-fetch';
|
||||
import auth from 'utils/auth';
|
||||
|
||||
/**
|
||||
* Parses the JSON returned by a network request
|
||||
*
|
||||
@ -86,6 +87,8 @@ function serverRestartWatcher(response) {
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// Hide the global OverlayBlocker
|
||||
strapi.unlockApp();
|
||||
resolve(response);
|
||||
})
|
||||
.catch(err => {
|
||||
@ -141,6 +144,8 @@ export default function request(url, options = {}, shouldWatchServerRestart = fa
|
||||
.then(parseJSON)
|
||||
.then((response) => {
|
||||
if (shouldWatchServerRestart) {
|
||||
// Display the global OverlayBlocker
|
||||
strapi.lockApp();
|
||||
return serverRestartWatcher(response);
|
||||
}
|
||||
|
||||
|
||||
@ -45,7 +45,11 @@ class TableList extends React.Component { // eslint-disable-line react/prefer-st
|
||||
</div>
|
||||
</li>
|
||||
{map(this.props.rowItems, (rowItem, key) => (
|
||||
<TableListRow key={key} rowItem={rowItem} onDelete={this.props.onHandleDelete} />
|
||||
<TableListRow
|
||||
key={key}
|
||||
onDelete={this.props.onHandleDelete}
|
||||
rowItem={rowItem}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -20,6 +20,7 @@ import NotFoundPage from 'containers/NotFoundPage';
|
||||
import formSaga from 'containers/Form/sagas';
|
||||
import formReducer from 'containers/Form/reducer';
|
||||
|
||||
// Other containers actions
|
||||
import { makeSelectShouldRefetchContentType } from 'containers/Form/selectors';
|
||||
|
||||
// Utils
|
||||
|
||||
@ -7,15 +7,13 @@ export function* deleteContentType(action) {
|
||||
try {
|
||||
if (action.sendRequest) {
|
||||
const requestUrl = `/content-type-builder/models/${action.itemToDelete}`;
|
||||
const response = yield call(request, requestUrl, { method: 'DELETE' }, true);
|
||||
|
||||
yield call(request, requestUrl, { method: 'DELETE' });
|
||||
|
||||
if (action.updateLeftMenu) {
|
||||
if (response.ok && action.updateLeftMenu) {
|
||||
action.updatePlugin('content-manager', 'leftMenuSections', action.leftMenuContentTypes);
|
||||
strapi.notification.success('content-type-builder.notification.success.contentTypeDeleted');
|
||||
}
|
||||
strapi.notification.success('content-type-builder.notification.success.contentTypeDeleted');
|
||||
}
|
||||
|
||||
} catch(error) {
|
||||
strapi.notification.error('content-type-builder.notification.error.message');
|
||||
}
|
||||
|
||||
@ -41,6 +41,6 @@ const selectLocationState = () => {
|
||||
export {
|
||||
selectLocationState,
|
||||
makeSelectLoading,
|
||||
makeSelectModels,
|
||||
makeSelectMenu,
|
||||
makeSelectModels,
|
||||
};
|
||||
|
||||
@ -254,6 +254,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
|
||||
this.props.contentTypeCreate(contentType);
|
||||
}
|
||||
|
||||
this.setState({ showModal: false });
|
||||
return this.props.contentTypeEdit(this.context);
|
||||
}
|
||||
|
||||
|
||||
@ -69,8 +69,8 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
|
||||
title={title}
|
||||
buttonLabel={'content-type-builder.button.contentType.add'}
|
||||
onButtonClick={this.handleButtonClick}
|
||||
rowItems={this.props.models}
|
||||
onHandleDelete={this.handleDelete}
|
||||
rowItems={this.props.models}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -76,8 +76,6 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
|
||||
if (this.props.updatedContentType !== nextProps.updatedContentType) {
|
||||
if (this.state.contentTypeTemporary && storeData.getContentType()) {
|
||||
this.props.modelFetchSucceeded({ model: storeData.getContentType() });
|
||||
} else {
|
||||
this.fetchModel(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,24 +84,24 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
if (!isEmpty(nextProps.menu)) {
|
||||
const allowedPaths = nextProps.menu.reduce((acc, current) => {
|
||||
const models = current.items.reduce((acc, current) => {
|
||||
acc.push(current.name);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
return acc.concat(models);
|
||||
}, []);
|
||||
|
||||
const shouldRedirect = allowedPaths.filter(el => el === this.props.match.params.modelName.split('&')[0]).length === 0;
|
||||
|
||||
if (shouldRedirect) {
|
||||
this.props.history.push('/404');
|
||||
}
|
||||
}
|
||||
}
|
||||
// componentWillUpdate(nextProps) {
|
||||
// if (!isEmpty(nextProps.menu)) {
|
||||
// const allowedPaths = nextProps.menu.reduce((acc, current) => {
|
||||
// const models = current.items.reduce((acc, current) => {
|
||||
// acc.push(current.name);
|
||||
//
|
||||
// return acc;
|
||||
// }, []);
|
||||
// return acc.concat(models);
|
||||
// }, []);
|
||||
//
|
||||
// const shouldRedirect = allowedPaths.filter(el => el === this.props.match.params.modelName.split('&')[0]).length === 0;
|
||||
//
|
||||
// if (shouldRedirect) {
|
||||
// this.props.history.push('/404');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.match.params.modelName !== this.props.match.params.modelName) {
|
||||
@ -347,7 +345,7 @@ ModelPage.propTypes = {
|
||||
cancelChanges: PropTypes.func.isRequired,
|
||||
checkIfTableExists: PropTypes.func.isRequired,
|
||||
deleteAttribute: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
// history: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
menu: PropTypes.array.isRequired,
|
||||
|
||||
@ -140,7 +140,6 @@ export function* submitChanges(action) {
|
||||
}
|
||||
|
||||
yield put(submitActionSucceeded());
|
||||
|
||||
yield put(resetShowButtonsProps());
|
||||
// Remove loader
|
||||
yield put(unsetButtonLoader());
|
||||
|
||||
@ -3,7 +3,7 @@ import request from 'utils/request';
|
||||
const shouldRenderCompo = (plugin) => new Promise((resolve, reject) => {
|
||||
request(`${strapi.backendURL}/content-type-builder/autoReload`)
|
||||
.then(response => {
|
||||
plugin.preventComponentRendering = !response.autoReload;
|
||||
plugin.preventComponentRendering = !response.autoReload.enabled;
|
||||
plugin.blockerComponentProps = {
|
||||
blockerComponentTitle: 'components.AutoReloadBlocker.header',
|
||||
blockerComponentDescription: 'components.AutoReloadBlocker.description',
|
||||
|
||||
@ -5,26 +5,12 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
MENU_FETCH,
|
||||
ENVIRONMENTS_FETCH,
|
||||
MENU_FETCH_SUCCEEDED,
|
||||
ENVIRONMENTS_FETCH_SUCCEEDED,
|
||||
MENU_FETCH_SUCCEEDED,
|
||||
MENU_FETCH,
|
||||
} from './constants';
|
||||
|
||||
|
||||
export function menuFetch() {
|
||||
return {
|
||||
type: MENU_FETCH,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMenuSucceeded(menu) {
|
||||
return {
|
||||
type: MENU_FETCH_SUCCEEDED,
|
||||
menu,
|
||||
};
|
||||
}
|
||||
|
||||
export function environmentsFetch() {
|
||||
return {
|
||||
type: ENVIRONMENTS_FETCH,
|
||||
@ -37,3 +23,16 @@ export function environmentsFetchSucceeded(environments) {
|
||||
environments,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMenuSucceeded(menu) {
|
||||
return {
|
||||
type: MENU_FETCH_SUCCEEDED,
|
||||
menu,
|
||||
};
|
||||
}
|
||||
|
||||
export function menuFetch() {
|
||||
return {
|
||||
type: MENU_FETCH,
|
||||
};
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
export const MENU_FETCH = 'SettingsManager/App/MENU_FETCH';
|
||||
export const ENVIRONMENTS_FETCH = 'SettingsManager/App/ENVIRONMENTS_FETCH';
|
||||
export const MENU_FETCH_SUCCEEDED = 'SettingsManager/App/MENU_FETCH_SUCCEEDED';
|
||||
export const ENVIRONMENTS_FETCH_SUCCEEDED = 'SettingsManager/App/ENVIRONMENTS_FETCH_SUCCEEDED';
|
||||
export const MENU_FETCH = 'SettingsManager/App/MENU_FETCH';
|
||||
export const MENU_FETCH_SUCCEEDED = 'SettingsManager/App/MENU_FETCH_SUCCEEDED';
|
||||
|
||||
@ -6,24 +6,23 @@
|
||||
|
||||
import { fromJS, List } from 'immutable';
|
||||
import {
|
||||
MENU_FETCH_SUCCEEDED,
|
||||
ENVIRONMENTS_FETCH_SUCCEEDED,
|
||||
MENU_FETCH_SUCCEEDED,
|
||||
} from './constants';
|
||||
|
||||
/* eslint-disable new-cap */
|
||||
const initialState = fromJS({
|
||||
sections: List(), // eslint-disable-line new-cap
|
||||
environments: List(),
|
||||
sections: List([]),
|
||||
environments: List([]),
|
||||
loading: true,
|
||||
});
|
||||
|
||||
function appReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case MENU_FETCH_SUCCEEDED:
|
||||
return state.set('sections', List(action.menu.sections)).set('loading', false);
|
||||
case ENVIRONMENTS_FETCH_SUCCEEDED:
|
||||
return state
|
||||
.set('environments', List(action.environments.environments));
|
||||
case MENU_FETCH_SUCCEEDED:
|
||||
return state.set('sections', List(action.menu.sections)).set('loading', false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -37,5 +37,10 @@ const makeSelectLoading = () => createSelector(
|
||||
(globalSate) => globalSate.get('loading'),
|
||||
);
|
||||
|
||||
export { selectLocationState, makeSelectSections, makeSelectEnvironments, makeSelectLoading };
|
||||
export {
|
||||
makeSelectEnvironments,
|
||||
makeSelectLoading,
|
||||
makeSelectSections,
|
||||
selectLocationState,
|
||||
};
|
||||
export default selectGlobalDomain;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
|
||||
import { forEach, set, map, replace } from 'lodash';
|
||||
import { call, take, put, fork, cancel, select, takeLatest } from 'redux-saga/effects';
|
||||
import request from 'utils/request';
|
||||
@ -86,7 +85,6 @@ export function* deleteLanguage(action) {
|
||||
method: 'DELETE',
|
||||
};
|
||||
const requestUrl = `/settings-manager/configurations/languages/${action.languageToDelete}`;
|
||||
|
||||
const resp = yield call(request, requestUrl, opts, true);
|
||||
|
||||
if (resp.ok) {
|
||||
@ -152,7 +150,6 @@ export function* fetchLanguages() {
|
||||
export function* postLanguage() {
|
||||
try {
|
||||
const newLanguage = yield select(makeSelectModifiedData());
|
||||
|
||||
const body = {
|
||||
name: newLanguage['language.defaultLocale'],
|
||||
};
|
||||
@ -161,12 +158,11 @@ export function* postLanguage() {
|
||||
method: 'POST',
|
||||
};
|
||||
const requestUrl = '/settings-manager/configurations/languages';
|
||||
|
||||
const resp = yield call(request, requestUrl, opts, true);
|
||||
|
||||
if (resp.ok) {
|
||||
strapi.notification.success('settings-manager.strapi.notification.success.languageAdd');
|
||||
yield put(languageActionSucceeded());
|
||||
strapi.notification.success('settings-manager.strapi.notification.success.languageAdd');
|
||||
}
|
||||
} catch(error) {
|
||||
yield put(languageActionError());
|
||||
@ -187,7 +183,6 @@ export function* postDatabase(action) {
|
||||
body,
|
||||
};
|
||||
const requestUrl = `/settings-manager/configurations/databases/${action.endPoint}`;
|
||||
|
||||
const resp = yield call(request, requestUrl, opts, true);
|
||||
|
||||
if (resp.ok) {
|
||||
@ -203,7 +198,6 @@ export function* postDatabase(action) {
|
||||
});
|
||||
|
||||
yield put(databaseActionError(formErrors));
|
||||
|
||||
strapi.notification.error('settings-manager.strapi.notification.error');
|
||||
}
|
||||
}
|
||||
@ -221,13 +215,13 @@ export function* settingsEdit(action) {
|
||||
const resp = yield call(request, requestUrl, opts, true);
|
||||
|
||||
if (resp.ok) {
|
||||
strapi.notification.success('settings-manager.strapi.notification.success.settingsEdit');
|
||||
yield put(editSettingsSucceeded());
|
||||
yield put(unsetLoader());
|
||||
strapi.notification.success('settings-manager.strapi.notification.success.settingsEdit');
|
||||
}
|
||||
} catch(error) {
|
||||
strapi.notification.error('settings-manager.strapi.notification.error');
|
||||
yield put(unsetLoader());
|
||||
strapi.notification.error('settings-manager.strapi.notification.error');
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +231,6 @@ export function* fetchSpecificDatabase(action) {
|
||||
method: 'GET',
|
||||
};
|
||||
const requestUrl = `/settings-manager/configurations/databases/${action.databaseName}/${action.endPoint}`;
|
||||
|
||||
const data = yield call(request, requestUrl, opts);
|
||||
|
||||
yield put(specificDatabaseFetchSucceeded(data));
|
||||
|
||||
@ -1,126 +0,0 @@
|
||||
import 'whatwg-fetch';
|
||||
import { startsWith } from 'lodash';
|
||||
import auth from 'utils/auth';
|
||||
|
||||
/**
|
||||
* Parses the JSON returned by a network request
|
||||
*
|
||||
* @param {object} response A response from a network request
|
||||
*
|
||||
* @return {object} The parsed JSON from the request
|
||||
*/
|
||||
function parseJSON(response) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a network request came back fine, and throws an error if not
|
||||
*
|
||||
* @param {object} response A response from a network request
|
||||
*
|
||||
* @return {object|undefined} Returns either the response, or throws an error
|
||||
*/
|
||||
function checkStatus(response) {
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return parseJSON(response).then(responseFormatted => {
|
||||
const error = new Error(response.statusText);
|
||||
error.response = response;
|
||||
error.response.payload = responseFormatted;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format query params
|
||||
*
|
||||
* @param params
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatQueryParams(params) {
|
||||
return Object.keys(params)
|
||||
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Server restart watcher
|
||||
* @param response
|
||||
* @returns {object} the response data
|
||||
*/
|
||||
function serverRestartWatcher(response) {
|
||||
return new Promise((resolve) => {
|
||||
fetch(`${strapi.backendURL}/_health`, {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Keep-Alive': false,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(() => {
|
||||
return serverRestartWatcher(response)
|
||||
.then(resolve);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a URL, returning a promise
|
||||
*
|
||||
* @param {string} url The URL we want to request
|
||||
* @param {object} [options] The options we want to pass to "fetch"
|
||||
*
|
||||
* @return {object} The response data
|
||||
*/
|
||||
export default function request(url, options, shouldWatchServerRestart = false) {
|
||||
const optionsObj = options || {};
|
||||
|
||||
// Set headers
|
||||
optionsObj.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Forwarded-Host': 'strapi',
|
||||
};
|
||||
|
||||
const token = auth.getToken();
|
||||
|
||||
if (token) {
|
||||
optionsObj.headers = Object.assign({
|
||||
'Authorization': `Bearer ${token}`,
|
||||
}, optionsObj.headers);
|
||||
}
|
||||
|
||||
// Add parameters to url
|
||||
let urlFormatted = startsWith(url, '/')
|
||||
? `${strapi.backendURL}${url}`
|
||||
: url;
|
||||
|
||||
if (optionsObj && optionsObj.params) {
|
||||
const params = formatQueryParams(optionsObj.params);
|
||||
urlFormatted = `${url}?${params}`;
|
||||
}
|
||||
|
||||
// Stringify body object
|
||||
if (optionsObj && optionsObj.body) {
|
||||
optionsObj.body = JSON.stringify(optionsObj.body);
|
||||
}
|
||||
|
||||
return fetch(urlFormatted, optionsObj)
|
||||
.then(checkStatus)
|
||||
.then(parseJSON)
|
||||
.then((response) => {
|
||||
if (shouldWatchServerRestart) {
|
||||
return serverRestartWatcher(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
}
|
||||
@ -48,7 +48,7 @@ const generateListTitle = (data, settingType) => {
|
||||
}
|
||||
};
|
||||
|
||||
function List({ data, deleteActionSucceeded, deleteData, noButton, onButtonClick, settingType }) {
|
||||
function List({ data, deleteData, noButton, onButtonClick, settingType }) {
|
||||
return (
|
||||
<div className={styles.list}>
|
||||
<div className={styles.flex}>
|
||||
@ -69,7 +69,6 @@ function List({ data, deleteActionSucceeded, deleteData, noButton, onButtonClick
|
||||
<ul className={noButton ? styles.listPadded : ''}>
|
||||
{map(data, item => (
|
||||
<ListRow
|
||||
deleteActionSucceeded={deleteActionSucceeded}
|
||||
deleteData={deleteData}
|
||||
item={item}
|
||||
key={item.name}
|
||||
@ -89,7 +88,6 @@ List.defaultProps = {
|
||||
|
||||
List.propTypes = {
|
||||
data: PropTypes.array.isRequired,
|
||||
deleteActionSucceeded: PropTypes.bool.isRequired,
|
||||
deleteData: PropTypes.func.isRequired,
|
||||
noButton: PropTypes.bool,
|
||||
onButtonClick: PropTypes.func,
|
||||
|
||||
@ -19,12 +19,6 @@ import styles from './styles.scss';
|
||||
class ListRow extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
state = { showModalDelete: false };
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.deleteActionSucceeded !== this.props.deleteActionSucceeded) {
|
||||
this.setState({ showModalDelete: false });
|
||||
}
|
||||
}
|
||||
|
||||
// Roles that can't be deleted && modified
|
||||
// Don't delete this line
|
||||
protectedRoleIDs = ['0'];
|
||||
@ -148,7 +142,10 @@ class ListRow extends React.Component { // eslint-disable-line react/prefer-stat
|
||||
}
|
||||
}
|
||||
|
||||
handleDelete = () => this.props.deleteData(this.props.item, this.props.settingType);
|
||||
handleDelete = () => {
|
||||
this.props.deleteData(this.props.item, this.props.settingType);
|
||||
this.setState({ showModalDelete: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
@ -176,7 +173,6 @@ ListRow.defaultProps = {
|
||||
};
|
||||
|
||||
ListRow.propTypes = {
|
||||
deleteActionSucceeded: PropTypes.bool.isRequired,
|
||||
deleteData: PropTypes.func.isRequired,
|
||||
item: PropTypes.object,
|
||||
settingType: PropTypes.string,
|
||||
|
||||
@ -117,7 +117,7 @@ Plugin.propTypes = {
|
||||
plugin: PropTypes.shape({
|
||||
description: PropTypes.string,
|
||||
information: PropTypes.shape({
|
||||
logo: PropTypes.string.isRequired,
|
||||
logo: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
pluginSelected: PropTypes.string.isRequired,
|
||||
|
||||
@ -5,5 +5,3 @@
|
||||
*/
|
||||
|
||||
// const selectGlobalDomain = () => state => state.get('global');
|
||||
|
||||
export {};
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
take,
|
||||
takeLatest,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import request from 'utils/request';
|
||||
|
||||
import {
|
||||
@ -98,7 +99,7 @@ export function* submit() {
|
||||
};
|
||||
|
||||
const requestURL = actionType === 'POST' ? '/users-permissions/roles' : `/users-permissions/roles/${roleId}`;
|
||||
const response = yield call(request, requestURL, opts);
|
||||
const response = yield call(request, requestURL, opts, true);
|
||||
|
||||
if (response.ok) {
|
||||
yield put(submitSucceeded());
|
||||
|
||||
@ -116,7 +116,6 @@ export class HomePage extends React.Component {
|
||||
<EditForm onChange={this.props.onChange} values={this.props.modifiedData} /> : (
|
||||
<List
|
||||
data={this.props.data}
|
||||
deleteActionSucceeded={this.props.deleteActionSucceeded}
|
||||
deleteData={this.props.deleteData}
|
||||
noButton={noButtonList}
|
||||
onButtonClick={this.handleButtonClick}
|
||||
@ -161,7 +160,6 @@ HomePage.defaultProps = {};
|
||||
|
||||
HomePage.propTypes = {
|
||||
data: PropTypes.array.isRequired,
|
||||
deleteActionSucceeded: PropTypes.bool.isRequired,
|
||||
deleteData: PropTypes.func.isRequired,
|
||||
fetchData: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
|
||||
@ -17,7 +17,6 @@ import {
|
||||
const initialState = fromJS({
|
||||
data: List([]),
|
||||
dataToDelete: Map({}),
|
||||
deleteActionSucceeded: false,
|
||||
deleteEndPoint: '',
|
||||
initialData: Map({}),
|
||||
modifiedData: Map({}),
|
||||
@ -34,8 +33,7 @@ function homePageReducer(state = initialState, action) {
|
||||
return state
|
||||
.update('data', list => list.splice(action.indexDataToDelete, 1))
|
||||
.set('deleteEndPoint', '')
|
||||
.set('dataToDelete', Map({}))
|
||||
.set('deleteActionSucceeded', !state.get('deleteActionSucceeded'));
|
||||
.set('dataToDelete', Map({}));
|
||||
case FETCH_DATA_SUCCEEDED:
|
||||
return state.set('data', List(action.data));
|
||||
case ON_CHANGE:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import { findIndex } from 'lodash';
|
||||
import { takeLatest, put, fork, take, cancel, select, call } from 'redux-saga/effects';
|
||||
|
||||
import request from 'utils/request';
|
||||
|
||||
import {
|
||||
@ -8,6 +9,7 @@ import {
|
||||
fetchDataSucceeded,
|
||||
setForm,
|
||||
} from './actions';
|
||||
|
||||
import {
|
||||
DELETE_DATA,
|
||||
FETCH_DATA,
|
||||
@ -32,8 +34,7 @@ export function* dataDelete() {
|
||||
if (indexDataToDelete !== -1) {
|
||||
const id = dataToDelete.id;
|
||||
const requestURL = `/users-permissions/${endPointAPI}/${id}`;
|
||||
// TODO watchServerRestart
|
||||
const response = yield call(request, requestURL, { method: 'DELETE' });
|
||||
const response = yield call(request, requestURL, { method: 'DELETE' }, true);
|
||||
|
||||
if (response.ok) {
|
||||
yield put(deleteDataSucceeded(indexDataToDelete));
|
||||
|
||||
@ -20,9 +20,14 @@ module.exports = {
|
||||
return ctx.badRequest(null, [{ messages: [{ id: 'Cannot be empty' }] }]);
|
||||
}
|
||||
|
||||
strapi.reload.isWatching = false;
|
||||
|
||||
try {
|
||||
await strapi.plugins['users-permissions'].services.userspermissions.createRole(ctx.request.body);
|
||||
|
||||
ctx.send({ ok: true });
|
||||
|
||||
strapi.reload();
|
||||
} catch(err) {
|
||||
ctx.badRequest(null, [{ messages: [{ id: 'An error occured' }] }]);
|
||||
}
|
||||
@ -50,9 +55,14 @@ module.exports = {
|
||||
return ctx.badRequest(null, [{ messages: [{ id: 'Unauthorized' }] }]);
|
||||
}
|
||||
|
||||
strapi.reload.isWatching = false;
|
||||
|
||||
try {
|
||||
await strapi.plugins['users-permissions'].services.userspermissions.deleteRole(role);
|
||||
return ctx.send({ ok: true });
|
||||
|
||||
ctx.send({ ok: true });
|
||||
|
||||
strapi.reload();
|
||||
} catch(err) {
|
||||
return ctx.badRequest(null, [{ messages: [{ id: 'Bad request' }] }]);
|
||||
}
|
||||
@ -139,9 +149,14 @@ module.exports = {
|
||||
return ctx.badRequest(null, [{ messages: [{ id: 'Bad request' }] }]);
|
||||
}
|
||||
|
||||
strapi.reload.isWatching = false;
|
||||
|
||||
try {
|
||||
await strapi.plugins['users-permissions'].services.userspermissions.updateRole(roleId, ctx.request.body);
|
||||
|
||||
ctx.send({ ok: true });
|
||||
|
||||
strapi.reload();
|
||||
} catch(error) {
|
||||
ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user