Initialize plugin

This commit is contained in:
Aurélien Georget 2017-01-18 11:59:46 +01:00
parent 6ece58d157
commit f7665e5a93
68 changed files with 908 additions and 2962 deletions

View File

@ -1,122 +0,0 @@
# Contributing to react-boilerplate
Love react-boilerplate and want to help? Thanks so much, there's something to do for everybody!
Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.
Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features.
## Using the issue tracker
The [issue tracker](https://github.com/mxstbr/react-boilerplate/issues) is
the preferred channel for [bug reports](#bugs), [features requests](#features)
and [submitting pull requests](#pull-requests).
<a name="bugs"></a>
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; ideally create a [reduced test case](https://css-tricks.com/reduced-test-cases/) and a live example.
A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What browser(s) and OS
experience the problem? What would you expect to be the outcome? All these details will help people to fix any potential bugs.
Example:
> Short and descriptive example bug report title
>
> A summary of the issue and the browser/OS environment in which it occurs. If
> suitable, include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> `<url>` - a link to the reduced test case
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
<a name="features"></a>
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible.
<a name="pull-requests"></a>
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**Please ask first** before embarking on any significant pull request (e.g.
implementing features, refactoring code, porting to a different language),
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.
Please adhere to the coding conventions used throughout a project (indentation,
accurate comments, etc.) and any other requirements (such as test coverage).
Since the `master` branch is what people actually use in production, we have a
`dev` branch that unstable changes get merged into first. Only when we
consider that stable we merge it into the `master` branch and release the
changes for real.
Adhering to the following process is the best way to get your work
included in the project:
1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, and configure the remotes:
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/react-boilerplate.git
# Navigate to the newly cloned directory
cd react-boilerplate
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/mxstbr/react-boilerplate.git
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout dev
git pull upstream dev
```
3. Create a new topic branch (off the `dev` branch) to contain your feature, change, or fix:
```bash
git checkout -b <topic-branch-name>
```
4. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/about-git-rebase/) feature to tidy up your commits before making them public.
5. Locally merge (or rebase) the upstream dev branch into your topic branch:
```bash
git pull [--rebase] upstream dev
```
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
**IMPORTANT**: By submitting a patch, you agree to allow the project
owners to license your work under the terms of the [MIT License](https://github.com/mxstbr/react-boilerplate/blob/master/LICENSE.md).

View File

@ -1,30 +0,0 @@
# React Boilerplate
Before opening a new issue, please take a moment to review our [**community guidelines**](https://github.com/mxstbr/react-boilerplate/blob/master/.github/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved.
Please direct redux-saga related questions to stack overflow:
http://stackoverflow.com/questions/tagged/redux-saga
For questions related to the boilerplate itself, you can also find answers on our gitter chat:
https://gitter.im/mxstbr/react-boilerplate
**Before opening a new issue, you may find an answer in already closed issues**:
https://github.com/mxstbr/react-boilerplate/issues?q=is%3Aissue+is%3Aclosed
## Issue Type
- [ ] Bug (https://github.com/mxstbr/react-boilerplate/blob/master/.github/CONTRIBUTING.md#bug-reports)
- [ ] Feature (https://github.com/mxstbr/react-boilerplate/blob/master/.github/CONTRIBUTING.md#feature-requests)
## Description
(Add images if possible)
## Steps to reproduce
(Add link to a demo on https://jsfiddle.net or similar if possible)
# Versions
- Node/NPM:
- Browser:

View File

@ -1,22 +0,0 @@
## React Boilerplate
Thank you for contributing! Please take a moment to review our [**contributing guidelines**](https://github.com/mxstbr/react-boilerplate/blob/master/.github/CONTRIBUTING.md)
to make the process easy and effective for everyone involved.
**Please open an issue** before embarking on any significant pull request, especially those that
add a new library or change existing tests, otherwise you risk spending a lot of time working
on something that might not end up being merged into the project.
Before opening a pull request, please ensure:
- [ ] You have followed our [**contributing guidelines**](https://github.com/mxstbr/react-boilerplate/blob/master/.github/CONTRIBUTING.md)
- [ ] Pull request has tests (we are going for 100% coverage!)
- [ ] Code is well-commented, linted and follows project conventions
- [ ] Documentation is updated (if necessary)
- [ ] Internal code generators and templates are updated (if necessary)
- [ ] Description explains the issue/use-case resolved and auto-closes related issues
Be kind to code reviewers, please try to keep pull requests as small and focused as possible :)
**IMPORTANT**: By submitting a patch, you agree to allow the project
owners to license your work under the terms of the [MIT License](https://github.com/mxstbr/react-boilerplate/blob/master/LICENSE.md).

View File

@ -11,4 +11,8 @@ NB: Please refers to the contributing section of the [monorepo](https://github.c
1. Create a new Strapi application: `strapi new myApp`.
2. Go to your new Strapi app `cd myApp`.
3. Create a symlink to the monorepo plugin's folder `ln -s /path/to/strapi/monorepo/packages/strapi-plugin-content-manager ./plugins/content-manager`
3. Copy the layout SASS variables folder into your project from `admin/public/app/styles/variables` to `plugins/content-manager/public/app/styles/variables`
4. Start your app `strapi start` (Don't forget to return to the root of your app's directory).
ln -s -f ./admin/public/app/styles/variables/variables.scss ./plugins/content-manager/public/app/styles/variables/variables.scss

View File

@ -1,29 +1,3 @@
{
"routes": [
{
"method": "GET",
"path": "/settings/general",
"handler": "SettingsManager.getGeneralSettings",
"config": {
"policies": []
}
},
{
"action": "updateSettings",
"method": "PUT",
"path": "/settings",
"handler": "SettingsManager.updateSettings",
"config": {
"policies": []
}
}, {
"action": "updateSettings",
"method": "GET",
"path": "/:file",
"handler": "SettingsManager.file",
"config": {
"policies": []
}
}
]
"routes": []
}

View File

@ -1,47 +0,0 @@
'use strict';
const path = require('path');
const _ = require('lodash');
const fs = require('fs');
/**
* A set of functions called "actions" for `SettingsManager`
*/
module.exports = {
getGeneralSettings: async (ctx) => {
// Pick values from `strapi.config`
const settings = _.pick(strapi.config, [
'name',
'version',
'description'
]);
ctx.body = settings;
},
updateSettings: async (ctx) => {
const data = ctx.request.body;
try {
const settingsUpdated = await strapi.plugins['settings-manager'].services.settingsservice.configurationsManager(strapi, data);
ctx.body = settingsUpdated.values;
} catch (err) {
console.log('err', err);
ctx.status = err && err.status || 400;
return ctx.body = {
message: err.msg || 'An error occurred during settings update'
};
}
},
file: async (ctx) => {
try {
const file = fs.readFileSync(path.resolve(__dirname, '..', 'public', 'build', ctx.params.file));
ctx.body = file;
} catch (err) {
ctx.body = ctx.notFound();
}
}
};

View File

@ -26,11 +26,11 @@ const pluginId = require('../package.json').name.replace(/^strapi-/i, '');
// Register the plugin
if (window.Strapi) {
window.Strapi.registerPlugin({
name: 'Settings Manager',
name: 'Content Manager',
id: pluginId,
leftMenuLink: {
label: 'Settings Manager',
to: '/settings-manager',
label: 'Content Manager',
to: '/content-manager',
},
mainComponent: App,
routes: createRoutes(store),

View File

@ -5,8 +5,6 @@
*/
import React from 'react';
import LeftMenu from 'components/LeftMenu';
import styles from './styles.scss';
class Container extends React.Component { // eslint-disable-line react/prefer-stateless-function
@ -14,12 +12,7 @@ class Container extends React.Component { // eslint-disable-line react/prefer-st
return (
<div className={styles.container}>
<div className={`row row-eq-height ${styles.containerContent}`}>
<div className={`col-lg-3 p-l-0 p-r-0 ${styles.containerLeftContent}`}>
<LeftMenu></LeftMenu>
</div>
<div className={`col-lg-9 ${styles.containerRightContent}`}>
{this.props.children}
</div>
{this.props.children}
</div>
</div>
);

View File

@ -1,48 +0,0 @@
/**
*
* LeftMenu
*
*/
import React from 'react';
import LeftMenuLink from 'components/LeftMenuLink';
import styles from './styles.scss';
class LeftMenu extends React.Component { // eslint-disable-line react/prefer-stateless-function
links = [{
value: 'general',
to: '',
}, {
value: 'languages',
to: 'languages',
}, {
value: 'databases',
to: 'databases',
}, {
value: 'security',
to: 'security',
}, {
value: 'server',
to: 'server',
}, {
value: 'advanced',
to: 'advanced',
}];
render() {
const linksElements = this.links.map((link, i) => (<LeftMenuLink key={i} link={link}></LeftMenuLink>));
return (
<div className={styles.leftMenu}>
<nav className={styles.leftMenuNav}>
<ul className={styles.leftMenuList}>
{linksElements}
</ul>
</nav>
</div>
);
}
}
export default LeftMenu;

View File

@ -1,11 +0,0 @@
.leftMenu { /* stylelint-disable */
background-color: #F7F8F9;
height: 100%;
width: 100%;
padding-top: 1.7rem;
}
.leftMenuList {
padding-left: 1rem;
padding-right: 1rem;
}

View File

@ -1,11 +0,0 @@
// import LeftMenu from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<LeftMenu />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -1,36 +0,0 @@
/**
*
* LeftMenuLink
*
*/
import React from 'react';
import { Link } from 'react-router';
import appMessages from 'containers/App/messages.json';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const messageKey = `${this.props.link.value}SectionTitle`;
return (
<li className={styles.leftMenuLink}>
<Link
className={styles.leftMenuLinkDestination}
activeClassName={styles.leftMenuLinkDestinationActive}
to={`/plugins/settings-manager/${this.props.link.to}`}
>
<FormattedMessage {...appMessages[messageKey]} />
<i className={`ion ion-arrow-right-c ${styles.leftMenuLinkIcon}`}></i>
</Link>
</li>
);
}
}
export default LeftMenuLink;
LeftMenuLink.propTypes = {
link: React.PropTypes.object,
};

View File

@ -1,44 +0,0 @@
/* Import */
@import '../../styles/variables/variables';
.leftMenuLink { /* stylelint-disable */
margin-bottom: .4rem;
}
.leftMenuLinkDestination {
display: block;
position: relative;
color: #3B3F49;
padding-left: 2rem;
padding-right: 2rem;
line-height: 3.6rem;
font-size: 1.4rem;
border-radius: 2rem;
&:hover {
color: white;
text-decoration: none;
background-color: $strapi-blue;
}
}
.leftMenuLinkDestinationActive {
color: white;
text-decoration: none;
background-color: #1C5DE7;
}
.leftMenuLinkDestinationActive .leftMenuLinkIcon {
display: block;
}
.leftMenuLinkIcon {
display: none;
float: right;
padding-top: 1.1rem;
z-index: 10;
}
.leftMenuLinkDestination:hover .leftMenuLinkIcon {
display: block;
}

View File

@ -1,11 +0,0 @@
// import LeftMenuLink from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<LeftMenuLink />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -1,27 +0,0 @@
/**
*
* RightContentSectionTitle
*
*/
import React from 'react';
import styles from './styles.scss';
class RightContentSectionTitle extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
return (
<div>
<h3 className={styles.rightContentSectionTitle}>{this.props.title}</h3>
<p className={styles.rightContentSectionSubTitle}>{this.props.description}</p>
</div>
);
}
}
RightContentSectionTitle.propTypes = {
title: React.PropTypes.string,
description: React.PropTypes.string,
};
export default RightContentSectionTitle;

View File

@ -1,14 +0,0 @@
/* Import */
@import '../../styles/variables/variables';
.rightContentSectionTitle { /* stylelint-disable */
text-transform: uppercase;
font-size: 1.4rem;
margin-top: 2.6rem;
}
.rightContentSectionSubTitle {
color: $gray-light;
font-size: 1.2rem;
margin-bottom: 1.2rem;
}

View File

@ -1,11 +0,0 @@
// import RightContentSectionTitle from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<RightContentSectionTitle />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -1,28 +0,0 @@
/**
*
* RightContentTitle
*
*/
import React from 'react';
import styles from './styles.scss';
class RightContentTitle extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
return (
<div className={styles.rightContentTitle}>
<h2 className={styles.rightContentTitleName}>{this.props.title}</h2>
<p className={styles.rightContentTitleDescription}>{this.props.description}</p>
<hr className={styles.rightContentTitleSeparator}></hr>
</div>
);
}
}
RightContentTitle.propTypes = {
title: React.PropTypes.string,
description: React.PropTypes.string,
};
export default RightContentTitle;

View File

@ -1,23 +0,0 @@
/* Import */
@import '../../styles/variables/variables';
.rightContentTitle { /* stylelint-disable */
}
.rightContentTitleName {
font-size: 1.8rem;
margin-bottom: .3rem;
}
.rightContentTitleDescription {
font-size: 1.3rem;
color: $gray-light;
margin-bottom: 1.5rem;
}
.rightContentTitleSeparator {
margin-left: -3rem;
margin-right: -3rem;
margin-bottom: 1.5rem;
}

View File

@ -1,11 +0,0 @@
// import RightContentTitle from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<RightContentTitle />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -1,55 +0,0 @@
/*
*
* AdvancedPage
*
*/
import React from 'react';
import Helmet from 'react-helmet';
import PluginHeader from 'components/PluginHeader';
import Container from 'components/Container';
import RightContentTitle from 'components/RightContentTitle';
import RightContentSectionTitle from 'components/RightContentSectionTitle';
import { injectIntl, intlShape } from 'react-intl';
import appMessages from 'containers/App/messages.json';
import messages from './messages.json';
import { define } from '../../i18n';
define(messages);
import styles from './styles.scss';
export class AdvancedPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const { formatMessage } = this.props.intl;
return (
<div className={styles.advancedPage}>
<Helmet
title="Settings Manager - Advanced"
meta={[
{ name: 'description', content: formatMessage(messages.rightSectionDescription) },
]}
/>
<div className="container">
<PluginHeader />
<Container>
<RightContentTitle
title={formatMessage(appMessages.advancedSectionTitle)}
description={formatMessage(messages.rightSectionDescription)}
/>
<RightContentSectionTitle
title={formatMessage(appMessages.comingSoon)}
description=""
/>
</Container>
</div>
</div>
);
}
}
AdvancedPage.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(AdvancedPage);

View File

@ -1,6 +0,0 @@
{
"rightSectionDescription": {
"id": "settings-manager.components.AdvancedPage.rightSectionDescription",
"defaultMessage": "Configure your advanced settings"
}
}

View File

@ -1,3 +0,0 @@
.advancedPage { /* stylelint-disable */
}

View File

@ -1,11 +0,0 @@
// import AdvancedPage from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<AdvancedPage />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -8,9 +8,6 @@
import React from 'react';
import { Provider } from 'react-redux';
import { store } from '../../app';
import appMessages from 'containers/App/messages.json';
import { define } from '../../i18n';
define(appMessages);
import '../../styles/main.scss';

View File

@ -1,30 +0,0 @@
{
"generalSectionTitle": {
"id": "settings-manager.components.App.generalSectionTitle",
"defaultMessage": "General"
},
"languagesSectionTitle": {
"id": "settings-manager.components.App.languagesSectionTitle",
"defaultMessage": "Languages"
},
"databasesSectionTitle": {
"id": "settings-manager.components.App.databasesSectionTitle",
"defaultMessage": "Databases"
},
"securitySectionTitle": {
"id": "settings-manager.components.App.securitySectionTitle",
"defaultMessage": "Security"
},
"serverSectionTitle": {
"id": "settings-manager.components.App.serverSectionTitle",
"defaultMessage": "Server"
},
"advancedSectionTitle": {
"id": "settings-manager.components.App.advancedSectionTitle",
"defaultMessage": "Advanced"
},
"comingSoon": {
"id": "settings-manager.components.App.comingSoon",
"defaultMessage": "Coming soon"
}
}

View File

@ -1,54 +0,0 @@
/*
* DatabasesPage
*/
import React from 'react';
import Helmet from 'react-helmet';
import PluginHeader from 'components/PluginHeader';
import Container from 'components/Container';
import RightContentTitle from 'components/RightContentTitle';
import RightContentSectionTitle from 'components/RightContentSectionTitle';
import { injectIntl, intlShape } from 'react-intl';
import appMessages from 'containers/App/messages.json';
import messages from './messages.json';
import { define } from '../../i18n';
define(messages);
import styles from './styles.scss';
export class DatabasesPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const { formatMessage } = this.props.intl;
return (
<div className={styles.databasesPage}>
<Helmet
title="Settings Manager - Databases"
meta={[
{ name: 'description', content: formatMessage(messages.rightSectionDescription) },
]}
/>
<div className="container">
<PluginHeader />
<Container>
<RightContentTitle
title={formatMessage(appMessages.databasesSectionTitle)}
description={formatMessage(messages.rightSectionDescription)}
/>
<RightContentSectionTitle
title={formatMessage(appMessages.comingSoon)}
description=""
/>
</Container>
</div>
</div>
);
}
}
DatabasesPage.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(DatabasesPage);

View File

@ -1,6 +0,0 @@
{
"rightSectionDescription": {
"id": "settings-manager.components.DatabasesPage.rightSectionDescription",
"defaultMessage": "Configure your databases settings"
}
}

View File

@ -1,3 +0,0 @@
.databasesPage { /* stylelint-disable */
}

View File

@ -1,11 +0,0 @@
// import DatabasesPage from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<DatabasesPage />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -3,16 +3,7 @@
*/
import {
LOAD_GENERAL_SETTINGS,
LOAD_GENERAL_SETTINGS_SUCCESS,
LOAD_GENERAL_SETTINGS_ERROR,
CHANGE_NAME,
CHANGE_DESCRIPTION,
CHANGE_VERSION,
UPDATE_GENERAL_SETTINGS,
UPDATE_GENERAL_SETTINGS_SUCCESS,
UPDATE_GENERAL_SETTINGS_ERROR,
CANCEL_GENERAL_SETTINGS,
LOAD_DEFAULT
} from './constants';
/**
@ -20,123 +11,8 @@ import {
*
* @return {object} An action object with a type of LOAD_GENERAL_SETTINGS
*/
export function loadGeneralSettings() {
export function defaultLoad() {
return {
type: LOAD_GENERAL_SETTINGS,
};
}
/**
* Dispatched when the generalSettings are loaded by the request saga
*
* @param {object} generalSettings The generalSettings data
*
* @return {object} An action object with a type of LOAD_GENERAL_SETTINGS_SUCCESS passing the generalSettings
*/
export function generalSettingsLoaded(data) {
return {
type: LOAD_GENERAL_SETTINGS_SUCCESS,
data,
};
}
/**
* Dispatched when loading the generalSettings fails
*
* @param {object} error The error
*
* @return {object} An action object with a type of LOAD_GENERAL_SETTINGS_ERROR passing the error
*/
export function generalSettingsLoadingError(error) {
return {
type: LOAD_GENERAL_SETTINGS_ERROR,
error,
};
}
/**
* Change the `name` value
*
* @return {object} An action object with a type of CHANGE_NAME
*/
export function changeName(name) {
return {
type: CHANGE_NAME,
name,
};
}
/**
* Change the `description` value
*
* @return {object} An action object with a type of CHANGE_DESCRIPTION
*/
export function changeDescription(description) {
return {
type: CHANGE_DESCRIPTION,
description,
};
}
/**
* Change the `version` value
*
* @return {object} An action object with a type of CHANGE_VERSION
*/
export function changeVersion(version) {
return {
type: CHANGE_VERSION,
version,
};
}
/**
* Update the generalSettings, this action starts the request saga
*
* @return {object} An action object with a type of UPDATE_GENERAL_SETTINGS
*/
export function updateGeneralSettings(data) {
return {
type: UPDATE_GENERAL_SETTINGS,
data,
};
}
/**
* Dispatched when the generalSettings are updated by the request saga
*
* @param {object} generalSettings The generalSettings data
*
* @return {object} An action object with a type of UPDATE_GENERAL_SETTINGS_SUCCESS passing the generalSettings
*/
export function generalSettingsUpdated(data) {
return {
type: UPDATE_GENERAL_SETTINGS_SUCCESS,
data,
};
}
/**
* Dispatched when updating the generalSettings fails
*
* @param {object} error The error
*
* @return {object} An action object with a type of UPDATE_GENERAL_SETTINGS_ERROR passing the error
*/
export function generalSettingsUpdatedError(error) {
return {
type: UPDATE_GENERAL_SETTINGS_ERROR,
error,
};
}
/**
* Dispatched when cancelling the data modifications
*
* @return {object} An action object with a type of CANCEL_GENERAL_SETTINGS passing the error
*/
export function cancelGeneralSettings() {
return {
type: CANCEL_GENERAL_SETTINGS,
type: LOAD_DEFAULT,
};
}

View File

@ -9,13 +9,4 @@
* export const YOUR_ACTION_CONSTANT = 'your-plugin/YourContainer/YOUR_ACTION_CONSTANT';
*/
export const LOAD_GENERAL_SETTINGS = 'settingsManager/HomePage/LOAD_GENERAL_SETTINGS';
export const LOAD_GENERAL_SETTINGS_SUCCESS = 'settingsManager/HomePage/LOAD_GENERAL_SETTINGS_SUCCESS';
export const LOAD_GENERAL_SETTINGS_ERROR = 'settingsManager/HomePage/LOAD_GENERAL_SETTINGS_ERROR';
export const CHANGE_NAME = 'settingsManager/HomePage/CHANGE_NAME';
export const CHANGE_DESCRIPTION = 'settingsManager/HomePage/CHANGE_DESCRIPTION';
export const CHANGE_VERSION = 'settingsManager/HomePage/CHANGE_VERSION';
export const UPDATE_GENERAL_SETTINGS = 'settingsManager/HomePage/UPDATE_GENERAL_SETTINGS';
export const UPDATE_GENERAL_SETTINGS_SUCCESS = 'settingsManager/HomePage/UPDATE_GENERAL_SETTINGS_SUCCESS';
export const UPDATE_GENERAL_SETTINGS_ERROR = 'settingsManager/HomePage/UPDATE_GENERAL_SETTINGS_ERROR';
export const CANCEL_GENERAL_SETTINGS = 'settingsManager/HomePage/CANCEL_GENERAL_SETTINGS';
export const LOAD_DEFAULT = 'contentManager/HomePage/LOAD_DEFAULT';

View File

@ -5,111 +5,31 @@
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import PluginHeader from 'components/PluginHeader';
import Container from 'components/Container';
import RightContentTitle from 'components/RightContentTitle';
import RightContentSectionTitle from 'components/RightContentSectionTitle';
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
import appMessages from 'containers/App/messages.json';
import messages from './messages.json';
import { define } from '../../i18n';
define(messages);
import { injectIntl } from 'react-intl';
import {
// selectHome,
selectLoading,
selectError,
// selectGeneralSettings,
selectName,
selectDescription,
selectVersion,
// selectLocationState,
} from './selectors';
import {
loadGeneralSettings,
changeName,
changeDescription,
changeVersion,
updateGeneralSettings,
cancelGeneralSettings,
defaultLoad,
} from './actions';
import {
selectName
} from './selectors';
import styles from './styles.scss';
export class HomePage extends React.Component {
componentDidMount() {
this.props.onPageLoad();
this.props.onDefault();
}
render() {
const { formatMessage } = this.props.intl;
return (
<div>
<div className="container">
<PluginHeader {...this.props}></PluginHeader>
// <PluginHeader {...this.props}></PluginHeader>
<Container>
<RightContentTitle
title={formatMessage(appMessages.generalSectionTitle)}
description={formatMessage(messages.rightSectionDescription)}
/>
<RightContentSectionTitle
title={formatMessage(messages.rightContentSectionTitle)}
description={formatMessage(messages.rightContentSectionDescription)}
/>
<form onSubmit={this.props.onFormSubmit}>
<div className={`form-group row ${styles.homePageRightContentFormGroup}`}>
<label htmlFor="applicationName" className="col-xs-7 col-form-label">
<FormattedMessage {...messages.nameLabel} />
</label>
<div className="col-xs-5">
<input
className="form-control"
type="text"
placeholder={formatMessage(messages.namePlaceholder)}
id="applicationName"
value={this.props.name || ''}
onChange={this.props.onChangeName}
autoFocus
/>
</div>
</div>
<div className={`form-group row ${styles.homePageRightContentFormGroup}`}>
<label htmlFor="applicationDescription" className="col-xs-7 col-form-label">
<FormattedMessage {...messages.descriptionLabel} />
</label>
<div className="col-xs-5">
<input
className="form-control"
type="text"
placeholder={formatMessage(messages.descriptionPlaceholder)}
id="applicationDescription"
value={this.props.description || ''}
onChange={this.props.onChangeDescription}
/>
</div>
</div>
<div className={`form-group row ${styles.homePageRightContentFormGroup}`}>
<label htmlFor="applicationVersion" className="col-xs-7 col-form-label">
<FormattedMessage {...messages.versionLabel} />
</label>
<div className="col-xs-5">
<input
className="form-control"
type="text"
placeholder={formatMessage(messages.versionPlaceholder)}
id="applicationVersion"
value={this.props.version || ''}
onChange={this.props.onChangeVersion}
/>
</div>
</div>
<button className="btn btn-primary hidden-xs-up" type="submit">Submit</button>
</form>
{this.props.name}
</Container>
</div>
</div>
@ -117,58 +37,17 @@ export class HomePage extends React.Component {
}
}
HomePage.propTypes = {
// changeRoute: React.PropTypes.func,
description: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.bool,
]),
// error: React.PropTypes.oneOfType([
// React.PropTypes.object,
// React.PropTypes.bool,
// ]),
intl: intlShape.isRequired,
// loading: React.PropTypes.bool,
name: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.bool,
]),
// onCancel: React.PropTypes.func,
onChangeName: React.PropTypes.func,
onChangeDescription: React.PropTypes.func,
onChangeVersion: React.PropTypes.func,
onFormSubmit: React.PropTypes.func,
onPageLoad: React.PropTypes.func,
version: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.bool,
]),
};
HomePage.propTypes = {};
export function mapDispatchToProps(dispatch) {
return {
onCancel: () => dispatch(cancelGeneralSettings()),
onChangeName: (evt) => dispatch(changeName(evt.target.value)),
onChangeDescription: (evt) => dispatch(changeDescription(evt.target.value)),
onChangeVersion: (evt) => dispatch(changeVersion(evt.target.value)),
onFormSubmit: (evt) => {
if (evt !== undefined && evt.preventDefault) evt.preventDefault();
dispatch(updateGeneralSettings());
},
onPageLoad: (evt) => {
if (evt !== undefined && evt.preventDefault) evt.preventDefault();
dispatch(loadGeneralSettings());
},
onDefault: () => dispatch(defaultLoad()),
dispatch,
};
}
const mapStateToProps = createStructuredSelector({
name: selectName(),
description: selectDescription(),
error: selectError(),
loading: selectLoading(),
version: selectVersion(),
name: selectName()
});
// Wrap the component to inject dispatch and state into it

View File

@ -1,42 +0,0 @@
{
"rightSectionDescription": {
"id": "settings-manager.components.HomePage.rightSectionDescription",
"defaultMessage": "Configure your general settings"
},
"rightContentSectionTitle": {
"id": "settings-manager.components.HomePage.rightContentSectionTitle",
"defaultMessage": "Application"
},
"rightContentSectionDescription": {
"id": "settings-manager.components.HomePage.rightContentSectionDescription",
"defaultMessage": "The general settings of your Strapi application."
},
"nameLabel": {
"id": "settings-manager.components.HomePage.nameLabel",
"defaultMessage": "Name"
},
"namePlaceholder": {
"id": "settings-manager.components.HomePage.namePlaceholder",
"defaultMessage": "My Application"
},
"descriptionLabel": {
"id": "settings-manager.components.HomePage.descriptionLabel",
"defaultMessage": "Description"
},
"descriptionPlaceholder": {
"id": "settings-manager.components.HomePage.descriptionPlaceholder",
"defaultMessage": "A Strapi application"
},
"versionLabel": {
"id": "settings-manager.components.HomePage.versionLabel",
"defaultMessage": "Version"
},
"versionPlaceholder": {
"id": "settings-manager.components.HomePage.versionPlaceholder",
"defaultMessage": "0.0.1"
},
"submit": {
"id": "settings-manager.components.HomePage.submit",
"defaultMessage": "Submit"
}
}

View File

@ -10,85 +10,19 @@
* return state.set('yourStateVariable', true);
*/
import {
LOAD_GENERAL_SETTINGS_SUCCESS,
LOAD_GENERAL_SETTINGS,
LOAD_GENERAL_SETTINGS_ERROR,
CHANGE_NAME,
CHANGE_DESCRIPTION,
CHANGE_VERSION,
UPDATE_GENERAL_SETTINGS,
UPDATE_GENERAL_SETTINGS_SUCCESS,
UPDATE_GENERAL_SETTINGS_ERROR,
CANCEL_GENERAL_SETTINGS,
} from './constants';
import { LOAD_DEFAULT } from './constants';
import { fromJS } from 'immutable';
// The initial state of the App
const initialState = fromJS({
loading: false,
error: false,
name: false,
description: false,
version: false,
backup: fromJS({
name: false,
description: false,
version: false,
}),
default: ''
});
function appReducer(state = initialState, action) {
switch (action.type) {
case LOAD_GENERAL_SETTINGS:
case LOAD_DEFAULT:
return state
.set('loading', true)
.set('error', false)
.set('name', false)
.set('description', false)
.set('version', false);
case LOAD_GENERAL_SETTINGS_SUCCESS:
return state
.set('loading', false)
.set('name', action.data.name)
.set('description', action.data.description)
.set('version', action.data.version)
.setIn(['backup', 'name'], action.data.name)
.setIn(['backup', 'description'], action.data.description)
.setIn(['backup', 'version'], action.data.version);
case LOAD_GENERAL_SETTINGS_ERROR:
return state
.set('error', action.error)
.set('loading', false);
case CHANGE_NAME:
return state
.set('name', action.name);
case CHANGE_DESCRIPTION:
return state
.set('description', action.description);
case CHANGE_VERSION:
return state
.set('version', action.version);
case UPDATE_GENERAL_SETTINGS:
return state
.set('loading', true)
.set('error', false);
case UPDATE_GENERAL_SETTINGS_SUCCESS:
return state
.set('loading', false)
.setIn(['backup', 'name'], action.data.name)
.setIn(['backup', 'description'], action.data.description)
.setIn(['backup', 'version'], action.data.version);
case UPDATE_GENERAL_SETTINGS_ERROR:
return state
.set('error', action.error)
.set('loading', false);
case CANCEL_GENERAL_SETTINGS:
return state
.set('name', state.get('backup').get('name'))
.set('description', state.get('backup').get('description'))
.set('version', state.get('backup').get('version'));
.set('name', 'Content Manager');
default:
return state;
}

View File

@ -2,111 +2,6 @@
* Set of asynchronous functions.
*/
import { take, call, put, select, fork, cancel } from 'redux-saga/effects';
import { LOCATION_CHANGE } from 'react-router-redux';
import request from 'utils/request';
import {
LOAD_GENERAL_SETTINGS,
UPDATE_GENERAL_SETTINGS,
} from 'containers/HomePage/constants';
import {
generalSettingsLoaded,
generalSettingsLoadingError,
generalSettingsUpdated,
generalSettingsUpdatedError,
} from 'containers/HomePage/actions';
import {
selectName,
selectDescription,
selectVersion,
} from 'containers/HomePage/selectors';
import { apiUrl } from '../../app';
/**
* General Settings request/response handler
*/
export function* getGeneralSettings() {
const requestURL = `${apiUrl}/settings/general`;
// Call our request helper (see 'utils/request')
const generalSettings = yield call(request, requestURL);
if (!generalSettings.err) {
yield put(generalSettingsLoaded(generalSettings.data));
} else {
yield put(generalSettingsLoadingError(generalSettings.err));
}
}
/**
* Update general settings
*/
export function* updateGeneralSettings() {
const data = {
values: {
version: yield select(selectVersion()),
name: yield select(selectName()),
description: yield select(selectDescription()),
},
type: 'general',
};
const requestURL = `${apiUrl}/settings`;
// Call our request helper (see 'utils/request')
const generalSettings = yield call(
request,
requestURL, {
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}
);
if (!generalSettings.err) {
window.Strapi.notification.success('Your settings have successfully updated.');
yield put(generalSettingsUpdated(generalSettings.data));
} else {
window.Strapi.notification.error(generalSettings.err.message || 'An error occurred during settings update.');
yield put(generalSettingsUpdatedError(generalSettings.err));
}
}
/**
* Watches for LOAD_GENERAL_SETTINGS action and calls handler
*/
export function* getGeneralSettingsWatcher() {
while (yield take(LOAD_GENERAL_SETTINGS)) {
yield call(getGeneralSettings);
}
}
/**
* Watches for UPDATE_GENERAL_SETTINGS action and calls handler
*/
export function* updateGeneralSettingsWatcher() {
while (yield take(UPDATE_GENERAL_SETTINGS)) {
yield call(updateGeneralSettings);
}
}
/**
* Root saga manages watcher lifecycle
*/
export function* generalSettingsData() {
// Fork watchers so we can continue execution
const watcher = yield fork(getGeneralSettingsWatcher);
yield fork(updateGeneralSettingsWatcher);
// Suspend execution until location changes
yield take(LOCATION_CHANGE);
yield cancel(watcher);
}
// Bootstrap sagas
export default [
generalSettingsData,
];
export default [];

View File

@ -4,64 +4,11 @@
import { createSelector } from 'reselect';
/*
* Select home state
*/
const selectHome = () => (state) => state.get('home');
const selectLoading = () => createSelector(
selectHome(),
(homeState) => homeState.get('loading')
);
const selectError = () => createSelector(
selectHome(),
(homeState) => homeState.get('error')
);
const selectGeneralSettings = () => createSelector(
selectHome(),
(homeState) => homeState.get('generalSettings')
);
const selectName = () => createSelector(
selectHome(),
(homeState) => homeState.get('name')
(state) => state.get('name')
);
const selectDescription = () => createSelector(
selectHome(),
(homeState) => homeState.get('description')
);
const selectVersion = () => createSelector(
selectHome(),
(homeState) => homeState.get('version')
);
const selectLocationState = () => {
let prevRoutingState;
let prevRoutingStateJS;
return (state) => {
const routingState = state.get('route'); // or state.route
if (!routingState.equals(prevRoutingState)) {
prevRoutingState = routingState;
prevRoutingStateJS = routingState.toJS();
}
return prevRoutingStateJS;
};
};
export {
selectHome,
selectLoading,
selectError,
selectGeneralSettings,
selectName,
selectDescription,
selectVersion,
selectLocationState,
};

View File

@ -1,54 +0,0 @@
/*
* LanguagesPage
*/
import React from 'react';
import Helmet from 'react-helmet';
import PluginHeader from 'components/PluginHeader';
import Container from 'components/Container';
import RightContentTitle from 'components/RightContentTitle';
import RightContentSectionTitle from 'components/RightContentSectionTitle';
import { injectIntl, intlShape } from 'react-intl';
import appMessages from 'containers/App/messages.json';
import messages from './messages.json';
import { define } from '../../i18n';
define(messages);
import styles from './styles.scss';
export class LanguagesPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const { formatMessage } = this.props.intl;
return (
<div className={styles.languagesPage}>
<Helmet
title="Settings Manager - Languages"
meta={[
{ name: 'description', content: formatMessage(messages.rightSectionDescription) },
]}
/>
<div className="container">
<PluginHeader />
<Container>
<RightContentTitle
title={formatMessage(appMessages.languagesSectionTitle)}
description={formatMessage(messages.rightSectionDescription)}
/>
<RightContentSectionTitle
title={formatMessage(appMessages.comingSoon)}
description=""
/>
</Container>
</div>
</div>
);
}
}
LanguagesPage.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(LanguagesPage);

View File

@ -1,6 +0,0 @@
{
"rightSectionDescription": {
"id": "settings-manager.components.LanguagesPage.rightSectionDescription",
"defaultMessage": "Configure your languages settings"
}
}

View File

@ -1,3 +0,0 @@
.languagesPage { /* stylelint-disable */
}

View File

@ -1,11 +0,0 @@
// import LanguagesPage from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<LanguagesPage />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -1,53 +0,0 @@
/*
* SecurityPage
*/
import React from 'react';
import Helmet from 'react-helmet';
import PluginHeader from 'components/PluginHeader';
import Container from 'components/Container';
import RightContentTitle from 'components/RightContentTitle';
import RightContentSectionTitle from 'components/RightContentSectionTitle';
import { injectIntl, intlShape } from 'react-intl';
import appMessages from 'containers/App/messages.json';
import messages from './messages.json';
import { define } from '../../i18n';
define(messages);
import styles from './styles.scss';
export class SecurityPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const { formatMessage } = this.props.intl;
return (
<div className={styles.securityPage}>
<Helmet
title="Settings Manager - Security"
meta={[
{ name: 'description', content: formatMessage(messages.rightSectionDescription) },
]}
/>
<div className="container">
<PluginHeader />
<Container>
<RightContentTitle
title={formatMessage(appMessages.securitySectionTitle)}
description={formatMessage(messages.rightSectionDescription)}
/>
<RightContentSectionTitle
title={formatMessage(appMessages.comingSoon)}
description=""
/>
</Container>
</div>
</div>
);
}
}
SecurityPage.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(SecurityPage);

View File

@ -1,6 +0,0 @@
{
"rightSectionDescription": {
"id": "settings-manager.components.SecurityPage.rightSectionDescription",
"defaultMessage": "Configure your security settings"
}
}

View File

@ -1,3 +0,0 @@
.securityPage { /* stylelint-disable */
}

View File

@ -1,11 +0,0 @@
// import SecurityPage from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<SecurityPage />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -1,53 +0,0 @@
/*
* ServerPage
*/
import React from 'react';
import Helmet from 'react-helmet';
import PluginHeader from 'components/PluginHeader';
import Container from 'components/Container';
import RightContentTitle from 'components/RightContentTitle';
import RightContentSectionTitle from 'components/RightContentSectionTitle';
import { injectIntl, intlShape } from 'react-intl';
import appMessages from 'containers/App/messages.json';
import messages from './messages.json';
import { define } from '../../i18n';
define(messages);
import styles from './styles.scss';
export class ServerPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const { formatMessage } = this.props.intl;
return (
<div className={styles.serverPage}>
<Helmet
title="Settings Manager - Server"
meta={[
{ name: 'description', content: formatMessage(messages.rightSectionDescription) },
]}
/>
<div className="container">
<PluginHeader />
<Container>
<RightContentTitle
title={formatMessage(appMessages.serverSectionTitle)}
description={formatMessage(messages.rightSectionDescription)}
/>
<RightContentSectionTitle
title={formatMessage(appMessages.comingSoon)}
description=""
/>
</Container>
</div>
</div>
);
}
}
ServerPage.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(ServerPage);

View File

@ -1,6 +0,0 @@
{
"rightSectionDescription": {
"id": "settings-manager.components.ServerPage.rightSectionDescription",
"defaultMessage": "Configure your server settings"
}
}

View File

@ -1,3 +0,0 @@
.serverPage { /* stylelint-disable */
}

View File

@ -1,11 +0,0 @@
// import ServerPage from '../index';
import expect from 'expect';
// import { shallow } from 'enzyme';
// import React from 'react';
describe('<ServerPage />', () => {
it('Expect to have unit tests specified', () => {
expect(true).toEqual(true);
});
});

View File

@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Allow installing the app to the homescreen -->
<meta name="mobile-web-app-capable" content="yes">
<title>Settings Manager - Strapi</title>
<title>Content Manager - Strapi</title>
</head>
<body>
<!-- Display a message if JS has been disabled on the browser. -->
@ -15,4 +15,4 @@
<!-- The app hooks into this div -->
<div id="app"></div>
</body>
</html>
</html>

View File

@ -8,10 +8,6 @@ const loadModule = (cb) => (componentModule) => {
cb(null, componentModule.default);
};
const errorLoading = (err) => {
console.error('Dynamic page loading failed', err); // eslint-disable-line no-console
};
export default function createRoutes(store) {
// Create reusable async injectors using getAsyncInjectors factory
const { injectReducer, injectSagas } = getAsyncInjectors(store); // eslint-disable-line no-unused-vars
@ -31,64 +27,6 @@ export default function createRoutes(store) {
injectSagas(sagas.default);
renderRoute(component);
},
}, {
path: '/languages',
name: 'languages',
getComponent(nextState, cb) {
const component = require('containers/LanguagesPage'); // eslint-disable-line global-require
const renderRoute = loadModule(cb);
renderRoute(component);
},
}, {
path: '/databases',
name: 'databases',
getComponent(nextState, cb) {
const component = require('containers/DatabasesPage'); // eslint-disable-line global-require
const renderRoute = loadModule(cb);
renderRoute(component);
},
}, {
path: '/security',
name: 'security',
getComponent(nextState, cb) {
const component = require('containers/SecurityPage'); // eslint-disable-line global-require
const renderRoute = loadModule(cb);
renderRoute(component);
},
}, {
path: '/server',
name: 'server',
getComponent(nextState, cb) {
const component = require('containers/ServerPage'); // eslint-disable-line global-require
const renderRoute = loadModule(cb);
renderRoute(component);
},
}, {
path: '/advanced',
name: 'advanced',
getComponent(nextState, cb) {
const component = require('containers/AdvancedPage'); // eslint-disable-line global-require
const renderRoute = loadModule(cb);
renderRoute(component);
},
}, {
path: '*',
name: 'notfound',
getComponent(nextState, cb) {
System.import('containers/NotFoundPage')
.then(loadModule(cb))
.catch(errorLoading);
},
},
];
}

View File

@ -1,2 +1,2 @@
// Import variables
@import 'variables/variables';
@import 'variables/variables.scss';

View File

@ -0,0 +1,829 @@
// Variables
//
// Copy settings from this file into the provided `_custom.scss` to override
// the Bootstrap defaults without modifying key, versioned files.
// Table of Contents
//
// Colors
// Options
// Spacing
// Body
// Links
// Grid breakpoints
// Grid containers
// Grid columns
// Fonts
// Components
@mixin _assert-ascending($map, $map-name) {
$prev-key: null;
$prev-num: null;
@each $key, $num in $map {
@if $prev-num == null {
// Do nothing
} @else if not comparable($prev-num, $num) {
@warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
} @else if $prev-num >= $num {
@warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
}
$prev-key: $key;
$prev-num: $num;
}
}
// General variable structure
//
// Variable format should follow the `$component-modifier-state-property` order.
// Colors
//
// Grayscale and brand colors for use across Bootstrap.
$gray-dark: #373a3c !default;
$gray: #55595c !default;
$gray-light: #9ca4b9 !default;
$gray-lighter: #eceeef !default;
$gray-lightest: #f7f7f9 !default;
$brand-primary: $strapi-blue !default;
$brand-success: #5cb85c !default;
$brand-info: #5bc0de !default;
$brand-warning: #f0ad4e !default;
$brand-danger: #d9534f !default;
$brand-inverse: $gray-dark !default;
// Options
//
// Quickly modify global styling by enabling or disabling optional features.
$enable-flex: false !default;
$enable-rounded: true !default;
$enable-shadows: false !default;
$enable-gradients: false !default;
$enable-transitions: false !default;
$enable-hover-media-query: false !default;
$enable-grid-classes: true !default;
$enable-print-styles: true !default;
// Spacing
//
// Control the default styling of most Bootstrap elements by modifying these
// variables. Mostly focused on spacing.
// You can add more entries to the $spacers map, should you need more variation.
$spacer: 1rem !default;
$spacer-x: $spacer !default;
$spacer-y: $spacer !default;
$spacers: (
0: (
x: 0,
y: 0
),
1: (
x: $spacer-x,
y: $spacer-y
),
2: (
x: ($spacer-x * 1.5),
y: ($spacer-y * 1.5)
),
3: (
x: ($spacer-x * 3),
y: ($spacer-y * 3)
)
) !default;
$border-width: 1px !default;
// Body
//
// Settings for the `<body>` element.
$body-bg: #fff !default;
$body-color: $gray-dark !default;
// Links
//
// Style anchor elements.
$link-color: $brand-primary !default;
$link-decoration: none !default;
$link-hover-color: $link-color !default;
$link-hover-decoration: underline !default;
// Grid breakpoints
//
// Define the minimum dimensions at which your layout will change,
// adapting to different screen sizes, for use in media queries.
$grid-breakpoints: (
xs: 0,
sm: 544px,
md: 768px,
lg: 992px,
xl: 1200px
) !default;
@include _assert-ascending($grid-breakpoints, "$grid-breakpoints");
// Grid containers
//
// Define the maximum width of `.container` for different screen sizes.
$container-max-widths: (
sm: 576px,
md: 720px,
lg: 940px,
xl: 1140px
) !default;
@include _assert-ascending($container-max-widths, "$container-max-widths");
// Grid columns
//
// Set the number of columns and specify the width of the gutters.
$grid-columns: 12 !default;
$grid-gutter-width: 30px !default;
// Typography
//
// Font, line-height, and color for body text, headings, and more.
$font-family-sans-serif: 'Lato', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
$font-family-serif: Georgia, "Times New Roman", Times, serif !default;
$font-family-monospace: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;
$font-family-base: $font-family-sans-serif !default;
// Pixel value used to responsively scale all typography. Applied to the `<html>` element.
$font-size-root: 16px !default;
$font-size-base: 1.4rem !default;
$font-size-lg: 1.6rem !default;
$font-size-sm: 1.3rem !default;
$font-size-xs: 1.1rem !default;
$line-height-base: 1.5 !default;
$font-size-h1: 2.5rem !default;
$font-size-h2: 2rem !default;
$font-size-h3: 1.75rem !default;
$font-size-h4: 1.5rem !default;
$font-size-h5: 1.25rem !default;
$font-size-h6: 1rem !default;
$display1-size: 6rem !default;
$display2-size: 5.5rem !default;
$display3-size: 4.5rem !default;
$display4-size: 3.5rem !default;
$display1-weight: 300 !default;
$display2-weight: 300 !default;
$display3-weight: 300 !default;
$display4-weight: 300 !default;
$headings-margin-bottom: ($spacer / 2) !default;
$headings-font-family: inherit !default;
$headings-font-weight: 500 !default;
$headings-line-height: 1.1 !default;
$headings-color: inherit !default;
$lead-font-size: 1.25rem !default;
$lead-font-weight: 300 !default;
$small-font-size: 80% !default;
$text-muted: $gray-light !default;
$abbr-border-color: $gray-light !default;
$blockquote-small-color: $gray-light !default;
$blockquote-font-size: ($font-size-base * 1.25) !default;
$blockquote-border-color: $gray-lighter !default;
$blockquote-border-width: .25rem !default;
$hr-border-color: rgba(0,0,0,.1) !default;
$hr-border-width: $border-width !default;
$mark-padding: .2em !default;
$dt-font-weight: bold !default;
$kbd-box-shadow: inset 0 -.1rem 0 rgba(0,0,0,.25) !default;
$nested-kbd-font-weight: bold !default;
$list-inline-padding: 5px !default;
// Components
//
// Define common padding and border radius sizes and more.
$line-height-lg: (4 / 3) !default;
$line-height-sm: 1.5 !default;
$border-radius: .4rem !default;
$border-radius-lg: .6rem !default;
$border-radius-sm: .2rem !default;
$component-active-color: #fff !default;
$component-active-bg: $brand-primary !default;
$caret-width: .3em !default;
$caret-width-lg: $caret-width !default;
// Tables
//
// Customizes the `.table` component with basic values, each used across all table variations.
$table-cell-padding: .75rem !default;
$table-sm-cell-padding: .3rem !default;
$table-bg: transparent !default;
$table-bg-accent: rgba(0,0,0,.05) !default;
$table-bg-hover: rgba(0,0,0,.075) !default;
$table-bg-active: $table-bg-hover !default;
$table-border-width: $border-width !default;
$table-border-color: $gray-lighter !default;
// Buttons
//
// For each of Bootstrap's buttons, define text, background and border color.
$btn-padding-x: 3rem !default;
$btn-padding-y: .6rem !default;
$btn-line-height: 1.25 !default;
$btn-font-weight: normal !default;
$btn-box-shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075) !default;
$btn-active-box-shadow: inset 0 3px 5px rgba(0,0,0,.125) !default;
$btn-primary-color: #fff !default;
$btn-primary-bg: $brand-primary !default;
$btn-primary-border: $btn-primary-bg !default;
$btn-secondary-color: $gray-light !default;
$btn-secondary-bg: #fff !default;
$btn-secondary-border: #ccc !default;
$btn-info-color: #fff !default;
$btn-info-bg: $brand-info !default;
$btn-info-border: $btn-info-bg !default;
$btn-success-color: #fff !default;
$btn-success-bg: $brand-success !default;
$btn-success-border: $btn-success-bg !default;
$btn-warning-color: #fff !default;
$btn-warning-bg: $brand-warning !default;
$btn-warning-border: $btn-warning-bg !default;
$btn-danger-color: #fff !default;
$btn-danger-bg: $brand-danger !default;
$btn-danger-border: $btn-danger-bg !default;
$btn-link-disabled-color: $gray-light !default;
$btn-padding-x-sm: .5rem !default;
$btn-padding-y-sm: .25rem !default;
$btn-padding-x-lg: 1.5rem !default;
$btn-padding-y-lg: .75rem !default;
$btn-block-spacing-y: .5rem !default;
$btn-toolbar-margin: .5rem !default;
// Allows for customizing button radius independently from global border radius
$btn-border-radius: $border-radius !default;
$btn-border-radius-lg: $border-radius-lg !default;
$btn-border-radius-sm: $border-radius-sm !default;
// Forms
$input-padding-x: 1.2rem !default;
$input-padding-y: .7rem !default;
$input-line-height: 1.25 !default;
$input-bg: #fff !default;
$input-bg-disabled: $gray-lighter !default;
$input-color: $gray !default;
$input-border-color: rgba(0,0,0,.15) !default;
$input-btn-border-width: $border-width !default; // For form controls and buttons
$input-box-shadow: inset 0 1px 1px rgba(0,0,0,.075) !default;
$input-border-radius: $border-radius !default;
$input-border-radius-lg: $border-radius-lg !default;
$input-border-radius-sm: $border-radius-sm !default;
$input-bg-focus: $input-bg;
$input-border-focus: $brand-primary !default;
$input-box-shadow-focus: rgba(102,175,233,.6) !default;
$input-color-focus: $input-color;
$input-color-placeholder: #858585 !default;
$input-padding-x-sm: .5rem !default;
$input-padding-y-sm: .25rem !default;
$input-padding-x-lg: 1.5rem !default;
$input-padding-y-lg: .75rem !default;
$input-height: (($font-size-base * $line-height-base) + ($input-padding-y * 2)) !default;
$input-height-lg: (($font-size-lg * $line-height-lg) + ($input-padding-y-lg * 2)) !default;
$input-height-sm: (($font-size-sm * $line-height-sm) + ($input-padding-y-sm * 2)) !default;
$form-group-margin-bottom: $spacer-y !default;
$input-group-addon-bg: $gray-lighter !default;
$input-group-addon-border-color: $input-border-color !default;
$cursor-disabled: not-allowed !default;
$custom-control-gutter: 1.5rem !default;
$custom-control-spacer-x: 1rem !default;
$custom-control-spacer-y: .25rem !default;
$custom-control-indicator-size: 1rem !default;
$custom-control-indicator-bg: #ddd !default;
$custom-control-indicator-bg-size: 50% 50% !default;
$custom-control-indicator-box-shadow: inset 0 .25rem .25rem rgba(0,0,0,.1) !default;
$custom-control-disabled-cursor: $cursor-disabled !default;
$custom-control-disabled-indicator-bg: #eee !default;
$custom-control-disabled-description-color: #767676 !default;
$custom-control-checked-indicator-color: #fff !default;
$custom-control-checked-indicator-bg: #0074d9 !default;
$custom-control-checked-indicator-box-shadow: none !default;
$custom-control-focus-indicator-box-shadow: 0 0 0 .075rem #fff, 0 0 0 .2rem #0074d9 !default;
$custom-control-active-indicator-color: #fff !default;
$custom-control-active-indicator-bg: #84c6ff !default;
$custom-control-active-indicator-box-shadow: none !default;
$custom-checkbox-radius: $border-radius !default;
$custom-checkbox-checked-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E") !default;
$custom-checkbox-indeterminate-bg: #0074d9 !default;
$custom-checkbox-indeterminate-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E") !default;
$custom-checkbox-indeterminate-box-shadow: none !default;
$custom-radio-radius: 50% !default;
$custom-radio-checked-icon: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E") !default;
$custom-select-padding-x: .75rem !default;
$custom-select-padding-y: .375rem !default;
$custom-select-indicator-padding: 1rem !default; // Extra padding to account for the presence of the background-image based indicator
$custom-select-color: $input-color !default;
$custom-select-disabled-color: $gray-light !default;
$custom-select-bg: #fff !default;
$custom-select-disabled-bg: $gray-lighter !default;
$custom-select-bg-size: 8px 10px !default; // In pixels because image dimensions
$custom-select-indicator: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23333' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") !default;
$custom-select-border-width: $input-btn-border-width !default;
$custom-select-border-color: $input-border-color !default;
$custom-select-border-radius: $border-radius !default;
$custom-select-focus-border-color: #51a7e8 !default;
$custom-select-focus-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .075), 0 0 5px rgba(81, 167, 232, .5) !default;
$custom-select-sm-padding-y: .2rem !default;
$custom-select-sm-font-size: 75% !default;
$custom-file-height: 2.5rem !default;
$custom-file-width: 14rem !default;
$custom-file-focus-box-shadow: 0 0 0 .075rem #fff, 0 0 0 .2rem #0074d9 !default;
$custom-file-padding-x: .5rem !default;
$custom-file-padding-y: 1rem !default;
$custom-file-line-height: 1.5 !default;
$custom-file-color: #555 !default;
$custom-file-bg: #fff !default;
$custom-file-border-width: $border-width !default;
$custom-file-border-color: #ddd !default;
$custom-file-border-radius: $border-radius !default;
$custom-file-box-shadow: inset 0 .2rem .4rem rgba(0,0,0,.05) !default;
$custom-file-button-color: $custom-file-color !default;
$custom-file-button-bg: #eee !default;
$custom-file-text: (
placeholder: (
en: "Choose file..."
),
button-label: (
en: "Browse"
)
) !default;
// Form validation icons
$form-icon-success: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%235cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") !default;
$form-icon-warning: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23f0ad4e' d='M4.4 5.324h-.8v-2.46h.8zm0 1.42h-.8V5.89h.8zM3.76.63L.04 7.075c-.115.2.016.425.26.426h7.397c.242 0 .372-.226.258-.426C6.726 4.924 5.47 2.79 4.253.63c-.113-.174-.39-.174-.494 0z'/%3E%3C/svg%3E") !default;
$form-icon-danger: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23d9534f' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E") !default;
// Dropdowns
//
// Dropdown menu container and contents.
$dropdown-min-width: 160px !default;
$dropdown-padding-y: 5px !default;
$dropdown-margin-top: 2px !default;
$dropdown-bg: #fff !default;
$dropdown-border-color: rgba(0,0,0,.15) !default;
$dropdown-border-width: $border-width !default;
$dropdown-divider-bg: #e5e5e5 !default;
$dropdown-box-shadow: 0 6px 12px rgba(0,0,0,.175) !default;
$dropdown-link-color: $gray-dark !default;
$dropdown-link-hover-color: darken($gray-dark, 5%) !default;
$dropdown-link-hover-bg: #f5f5f5 !default;
$dropdown-link-active-color: $component-active-color !default;
$dropdown-link-active-bg: $component-active-bg !default;
$dropdown-link-disabled-color: $gray-light !default;
$dropdown-item-padding-x: 20px !default;
$dropdown-header-color: $gray-light !default;
// Z-index master list
//
// Warning: Avoid customizing these values. They're used for a bird's eye view
// of components dependent on the z-axis and are designed to all work together.
$zindex-dropdown-backdrop: 990 !default;
$zindex-navbar: 1000 !default;
$zindex-dropdown: 1000 !default;
$zindex-popover: 1060 !default;
$zindex-tooltip: 1070 !default;
$zindex-navbar-fixed: 1030 !default;
$zindex-navbar-sticky: 1030 !default;
$zindex-modal-bg: 1040 !default;
$zindex-modal: 1050 !default;
// Navbar
$navbar-border-radius: $border-radius !default;
$navbar-padding-x: $spacer !default;
$navbar-padding-y: ($spacer / 2) !default;
$navbar-brand-padding-y: .25rem !default;
$navbar-dark-color: rgba(255,255,255,.5) !default;
$navbar-dark-hover-color: rgba(255,255,255,.75) !default;
$navbar-dark-active-color: rgba(255,255,255,1) !default;
$navbar-dark-disabled-color: rgba(255,255,255,.25) !default;
$navbar-light-color: rgba(0,0,0,.3) !default;
$navbar-light-hover-color: rgba(0,0,0,.6) !default;
$navbar-light-active-color: rgba(0,0,0,.8) !default;
$navbar-light-disabled-color: rgba(0,0,0,.15) !default;
// Navs
$nav-item-margin: .2rem !default;
$nav-item-inline-spacer: 1rem !default;
$nav-link-padding: .5em 1em !default;
$nav-link-hover-bg: $gray-lighter !default;
$nav-disabled-link-color: $gray-light !default;
$nav-disabled-link-hover-color: $gray-light !default;
$nav-disabled-link-hover-bg: transparent !default;
$nav-tabs-border-color: #ddd !default;
$nav-tabs-border-width: $border-width !default;
$nav-tabs-border-radius: $border-radius !default;
$nav-tabs-link-hover-border-color: $gray-lighter !default;
$nav-tabs-active-link-hover-color: $gray !default;
$nav-tabs-active-link-hover-bg: $body-bg !default;
$nav-tabs-active-link-hover-border-color: #ddd !default;
$nav-tabs-justified-link-border-color: #ddd !default;
$nav-tabs-justified-active-link-border-color: $body-bg !default;
$nav-pills-border-radius: $border-radius !default;
$nav-pills-active-link-color: $component-active-color !default;
$nav-pills-active-link-bg: $component-active-bg !default;
// Pagination
$pagination-padding-x: .75rem !default;
$pagination-padding-y: .5rem !default;
$pagination-padding-x-sm: .75rem !default;
$pagination-padding-y-sm: .275rem !default;
$pagination-padding-x-lg: 1.5rem !default;
$pagination-padding-y-lg: .75rem !default;
$pagination-color: $link-color !default;
$pagination-bg: #fff !default;
$pagination-border-width: $border-width !default;
$pagination-border-color: #ddd !default;
$pagination-hover-color: $link-hover-color !default;
$pagination-hover-bg: $gray-lighter !default;
$pagination-hover-border: #ddd !default;
$pagination-active-color: #fff !default;
$pagination-active-bg: $brand-primary !default;
$pagination-active-border: $brand-primary !default;
$pagination-disabled-color: $gray-light !default;
$pagination-disabled-bg: #fff !default;
$pagination-disabled-border: #ddd !default;
// Jumbotron
$jumbotron-padding: 2rem !default;
$jumbotron-bg: $gray-lighter !default;
// Form states and alerts
//
// Define colors for form feedback states and, by default, alerts.
$state-success-text: #3c763d !default;
$state-success-bg: #dff0d8 !default;
$state-success-border: darken($state-success-bg, 5%) !default;
$state-info-text: #31708f !default;
$state-info-bg: #d9edf7 !default;
$state-info-border: darken($state-info-bg, 7%) !default;
$state-warning-text: #8a6d3b !default;
$state-warning-bg: #fcf8e3 !default;
$mark-bg: $state-warning-bg !default;
$state-warning-border: darken($state-warning-bg, 5%) !default;
$state-danger-text: #a94442 !default;
$state-danger-bg: #f2dede !default;
$state-danger-border: darken($state-danger-bg, 5%) !default;
// Cards
$card-spacer-x: 1.25rem !default;
$card-spacer-y: .75rem !default;
$card-border-width: 1px !default;
$card-border-radius: $border-radius !default;
$card-border-color: rgba(0,0,0,.125) !default;
$card-border-radius-inner: $card-border-radius !default;
$card-cap-bg: #f5f5f5 !default;
$card-bg: #fff !default;
$card-link-hover-color: #fff !default;
$card-img-overlay-padding: 1.25rem !default;
$card-deck-margin: .625rem !default;
$card-columns-sm-up-column-gap: 1.25rem !default;
// Tooltips
$tooltip-max-width: 200px !default;
$tooltip-color: #fff !default;
$tooltip-bg: #000 !default;
$tooltip-opacity: .9 !default;
$tooltip-padding-y: 3px !default;
$tooltip-padding-x: 8px !default;
$tooltip-margin: 3px !default;
$tooltip-arrow-width: 5px !default;
$tooltip-arrow-color: $tooltip-bg !default;
// Popovers
$popover-inner-padding: 1px !default;
$popover-bg: #fff !default;
$popover-max-width: 276px !default;
$popover-border-width: $border-width !default;
$popover-border-color: rgba(0,0,0,.2) !default;
$popover-box-shadow: 0 5px 10px rgba(0,0,0,.2) !default;
$popover-title-bg: darken($popover-bg, 3%) !default;
$popover-title-padding-x: 14px !default;
$popover-title-padding-y: 8px !default;
$popover-content-padding-x: 14px !default;
$popover-content-padding-y: 9px !default;
$popover-arrow-width: 10px !default;
$popover-arrow-color: $popover-bg !default;
$popover-arrow-outer-width: ($popover-arrow-width + 1px) !default;
$popover-arrow-outer-color: fade-in($popover-border-color, .05) !default;
// Tags
$tag-default-bg: $gray-light !default;
$tag-primary-bg: $brand-primary !default;
$tag-success-bg: $brand-success !default;
$tag-info-bg: $brand-info !default;
$tag-warning-bg: $brand-warning !default;
$tag-danger-bg: $brand-danger !default;
$tag-color: #fff !default;
$tag-link-hover-color: #fff !default;
$tag-font-size: 75% !default;
$tag-font-weight: bold !default;
$tag-padding-x: .4em !default;
$tag-padding-y: .25em !default;
$tag-pill-padding-x: .6em !default;
// Use a higher than normal value to ensure completely rounded edges when
// customizing padding or font-size on labels.
$tag-pill-border-radius: 10rem !default;
// Modals
// Padding applied to the modal body
$modal-inner-padding: 15px !default;
$modal-dialog-margin: 10px !default;
$modal-dialog-sm-up-margin-y: 30px !default;
$modal-title-padding: 15px !default;
$modal-title-line-height: $line-height-base !default;
$modal-content-bg: #fff !default;
$modal-content-border-color: rgba(0,0,0,.2) !default;
$modal-content-border-width: $border-width !default;
$modal-content-xs-box-shadow: 0 3px 9px rgba(0,0,0,.5) !default;
$modal-content-sm-up-box-shadow: 0 5px 15px rgba(0,0,0,.5) !default;
$modal-backdrop-bg: #000 !default;
$modal-backdrop-opacity: .5 !default;
$modal-header-border-color: #e5e5e5 !default;
$modal-footer-border-color: $modal-header-border-color !default;
$modal-header-border-width: $modal-content-border-width !default;
$modal-footer-border-width: $modal-header-border-width !default;
$modal-lg: 900px !default;
$modal-md: 600px !default;
$modal-sm: 300px !default;
// Alerts
//
// Define alert colors, border radius, and padding.
$alert-padding: 15px !default;
$alert-border-radius: $border-radius !default;
$alert-link-font-weight: bold !default;
$alert-border-width: $border-width !default;
$alert-success-bg: $state-success-bg !default;
$alert-success-text: $state-success-text !default;
$alert-success-border: $state-success-border !default;
$alert-info-bg: $state-info-bg !default;
$alert-info-text: $state-info-text !default;
$alert-info-border: $state-info-border !default;
$alert-warning-bg: $state-warning-bg !default;
$alert-warning-text: $state-warning-text !default;
$alert-warning-border: $state-warning-border !default;
$alert-danger-bg: $state-danger-bg !default;
$alert-danger-text: $state-danger-text !default;
$alert-danger-border: $state-danger-border !default;
// Progress bars
$progress-bg: #eee !default;
$progress-bar-color: #0074d9 !default;
$progress-border-radius: $border-radius !default;
$progress-box-shadow: inset 0 .1rem .1rem rgba(0,0,0,.1) !default;
$progress-bar-bg: $brand-primary !default;
$progress-bar-success-bg: $brand-success !default;
$progress-bar-warning-bg: $brand-warning !default;
$progress-bar-danger-bg: $brand-danger !default;
$progress-bar-info-bg: $brand-info !default;
// List group
$list-group-bg: #fff !default;
$list-group-border-color: #ddd !default;
$list-group-border-width: $border-width !default;
$list-group-border-radius: $border-radius !default;
$list-group-hover-bg: #f5f5f5 !default;
$list-group-active-color: $component-active-color !default;
$list-group-active-bg: $component-active-bg !default;
$list-group-active-border: $list-group-active-bg !default;
$list-group-active-text-color: lighten($list-group-active-bg, 40%) !default;
$list-group-disabled-color: $gray-light !default;
$list-group-disabled-bg: $gray-lighter !default;
$list-group-disabled-text-color: $list-group-disabled-color !default;
$list-group-link-color: #555 !default;
$list-group-link-hover-color: $list-group-link-color !default;
$list-group-link-heading-color: #333 !default;
$list-group-item-padding-x: 1.25rem !default;
$list-group-item-padding-y: .75rem !default;
$list-group-item-heading-margin-bottom: 5px !default;
// Image thumbnails
$thumbnail-padding: .25rem !default;
$thumbnail-bg: $body-bg !default;
$thumbnail-border-width: $border-width !default;
$thumbnail-border-color: #ddd !default;
$thumbnail-border-radius: $border-radius !default;
$thumbnail-box-shadow: 0 1px 2px rgba(0,0,0,.075) !default;
// Figures
$figure-caption-font-size: 90% !default;
// Breadcrumbs
$breadcrumb-padding-y: .75rem !default;
$breadcrumb-padding-x: 1rem !default;
$breadcrumb-item-padding: .5rem !default;
$breadcrumb-bg: $gray-lighter !default;
$breadcrumb-divider-color: $gray-light !default;
$breadcrumb-active-color: $gray-light !default;
$breadcrumb-divider: "/" !default;
// Media objects
$media-margin-top: 15px !default;
$media-heading-margin-bottom: 5px !default;
$media-alignment-padding-x: 10px !default;
// Carousel
$carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6) !default;
$carousel-control-color: #fff !default;
$carousel-control-width: 15% !default;
$carousel-control-sm-up-size: 30px !default;
$carousel-control-opacity: .5 !default;
$carousel-control-font-size: 20px !default;
$carousel-indicators-width: 60% !default;
$carousel-indicator-size: 10px !default;
$carousel-indicator-active-size: 12px !default;
$carousel-indicator-active-bg: #fff !default;
$carousel-indicator-border-color: #fff !default;
$carousel-caption-width: 70% !default;
$carousel-caption-sm-up-width: 60% !default;
$carousel-caption-color: #fff !default;
$carousel-icon-width: 20px !default;
// Close
$close-font-weight: bold !default;
$close-color: #000 !default;
$close-text-shadow: 0 1px 0 #fff !default;
// Code
$code-font-size: 90% !default;
$code-padding-x: .4rem !default;
$code-padding-y: .2rem !default;
$code-color: #bd4147 !default;
$code-bg: #f7f7f9 !default;
$kbd-color: #fff !default;
$kbd-bg: #333 !default;
$pre-bg: #f7f7f9 !default;
$pre-color: $gray-dark !default;
$pre-border-color: #ccc !default;
$pre-scrollable-max-height: 340px !default;

View File

@ -1,2 +1,29 @@
// Import admin variables
@import "../../../../../../admin/public/app/styles/variables/variables";
// App variables
// - colors
$black: #000000;
$white: #ffffff;
$strapi-gray-light: #EFF3F6;
$strapi-gray: #32394A;
$strapi-blue-darker: #101622;
$strapi-blue-dark: #151C2E;
$strapi-blue: #1C5DE7;
$strapi-blue-light: #7E8AAA;
// - layout
// -- left menu
$left-menu-width: 24rem;
$left-menu-bg: $strapi-blue-darker;
$left-menu-link-hover: $strapi-blue-dark;
$left-menu-link-color: $strapi-blue-light;
$left-menu-title-color: $strapi-gray;
// -- header
$header-height: 6rem;
// -- content
$content-background: $strapi-gray-light;
// Import bootstrap variables
@import "variables.bootstrap";

View File

@ -1,28 +1 @@
{
"settings-manager.components.AdvancedPage.rightSectionDescription": "",
"settings-manager.components.App.advancedSectionTitle": "",
"settings-manager.components.App.comingSoon": "",
"settings-manager.components.App.databasesSectionTitle": "",
"settings-manager.components.App.generalSectionTitle": "",
"settings-manager.components.App.languagesSectionTitle": "",
"settings-manager.components.App.securitySectionTitle": "",
"settings-manager.components.App.serverSectionTitle": "",
"settings-manager.components.DatabasesPage.rightSectionDescription": "",
"settings-manager.components.HomePage.descriptionLabel": "",
"settings-manager.components.HomePage.descriptionPlaceholder": "",
"settings-manager.components.HomePage.nameLabel": "",
"settings-manager.components.HomePage.namePlaceholder": "",
"settings-manager.components.HomePage.rightContentSectionDescription": "",
"settings-manager.components.HomePage.rightContentSectionTitle": "",
"settings-manager.components.HomePage.rightSectionDescription": "",
"settings-manager.components.HomePage.submit": "",
"settings-manager.components.HomePage.versionLabel": "",
"settings-manager.components.HomePage.versionPlaceholder": "",
"settings-manager.components.LanguagesPage.rightSectionDescription": "",
"settings-manager.components.PluginHeaderActions.cancelLabel": "",
"settings-manager.components.PluginHeaderActions.saveLabel": "",
"settings-manager.components.PluginHeaderTitle.description": "",
"settings-manager.components.PluginHeaderTitle.title": "",
"settings-manager.components.SecurityPage.rightSectionDescription": "",
"settings-manager.components.ServerPage.rightSectionDescription": ""
}
{}

View File

@ -1,28 +1 @@
{
"settings-manager.components.AdvancedPage.rightSectionDescription": "",
"settings-manager.components.App.advancedSectionTitle": "",
"settings-manager.components.App.comingSoon": "",
"settings-manager.components.App.databasesSectionTitle": "",
"settings-manager.components.App.generalSectionTitle": "",
"settings-manager.components.App.languagesSectionTitle": "",
"settings-manager.components.App.securitySectionTitle": "",
"settings-manager.components.App.serverSectionTitle": "",
"settings-manager.components.DatabasesPage.rightSectionDescription": "",
"settings-manager.components.HomePage.descriptionLabel": "",
"settings-manager.components.HomePage.descriptionPlaceholder": "",
"settings-manager.components.HomePage.nameLabel": "",
"settings-manager.components.HomePage.namePlaceholder": "",
"settings-manager.components.HomePage.rightContentSectionDescription": "",
"settings-manager.components.HomePage.rightContentSectionTitle": "",
"settings-manager.components.HomePage.rightSectionDescription": "",
"settings-manager.components.HomePage.submit": "",
"settings-manager.components.HomePage.versionLabel": "",
"settings-manager.components.HomePage.versionPlaceholder": "",
"settings-manager.components.LanguagesPage.rightSectionDescription": "",
"settings-manager.components.PluginHeaderActions.cancelLabel": "",
"settings-manager.components.PluginHeaderActions.saveLabel": "",
"settings-manager.components.PluginHeaderTitle.description": "",
"settings-manager.components.PluginHeaderTitle.title": "",
"settings-manager.components.SecurityPage.rightSectionDescription": "",
"settings-manager.components.ServerPage.rightSectionDescription": ""
}
{}

View File

@ -1,28 +1 @@
{
"settings-manager.components.AdvancedPage.rightSectionDescription": "Configurez vos réglages avancés",
"settings-manager.components.App.advancedSectionTitle": "Avancés",
"settings-manager.components.App.comingSoon": "Bientôt disponible",
"settings-manager.components.App.databasesSectionTitle": "Base de données",
"settings-manager.components.App.generalSectionTitle": "Général",
"settings-manager.components.App.languagesSectionTitle": "Langages",
"settings-manager.components.App.securitySectionTitle": "Sécurité",
"settings-manager.components.App.serverSectionTitle": "Serveur",
"settings-manager.components.DatabasesPage.rightSectionDescription": "Configurez vos réglages de bases de données",
"settings-manager.components.HomePage.descriptionLabel": "Description",
"settings-manager.components.HomePage.descriptionPlaceholder": "Une application Strapi",
"settings-manager.components.HomePage.nameLabel": "Nom",
"settings-manager.components.HomePage.namePlaceholder": "monApplication",
"settings-manager.components.HomePage.rightContentSectionDescription": "Réglages généraux de votre application Strapi.",
"settings-manager.components.HomePage.rightContentSectionTitle": "Application",
"settings-manager.components.HomePage.rightSectionDescription": "Configurez vos réglages généraux",
"settings-manager.components.HomePage.submit": "Soumettre",
"settings-manager.components.HomePage.versionLabel": "Version",
"settings-manager.components.HomePage.versionPlaceholder": "0.0.1",
"settings-manager.components.LanguagesPage.rightSectionDescription": "Configurez vos réglages de langages",
"settings-manager.components.PluginHeaderActions.cancelLabel": "Annuler",
"settings-manager.components.PluginHeaderActions.saveLabel": "Sauvegarder",
"settings-manager.components.PluginHeaderTitle.description": "Mettez facilement à jour vos configurations",
"settings-manager.components.PluginHeaderTitle.title": "Gestionnaire de configurations",
"settings-manager.components.SecurityPage.rightSectionDescription": "Configurez vos réglages de sécurité",
"settings-manager.components.ServerPage.rightSectionDescription": "Configurez vos réglages serveur"
}
{}

View File

@ -22,7 +22,7 @@ module.exports = require('./webpack.base.babel')({
filename: '[name].js',
chunkFilename: '[name].[chunkhash].chunk.js',
// publicPath: 'http://localhost:1337/settings-manager/',
publicPath: '/settings-manager/',
publicPath: '/content-manager/',
},
// We use ExtractTextPlugin so we get a seperate SCSS file instead

View File

@ -1,13 +1,12 @@
{
"name": "strapi-settings-manager",
"name": "strapi-plugin-content-manager",
"version": "0.0.1",
"description": "Strapi Settings Manager",
"description": "Strapi Plugin - Content Manager",
"repository": {
"type": "git",
"url": "git://github.com/strapi/strapi-settings-manager.git"
"type": "git"
},
"engines": {
"npm": ">=3"
"npm": ">=7"
},
"author": "Strapi",
"license": "MIT",
@ -189,7 +188,9 @@
"postcss-focus": "^1.0.0",
"postcss-loader": "^1.1.1",
"postcss-reporter": "^2.0.0",
"postcss-smart-import": "^0.6.7",
"pre-commit": "1.1.3",
"precss": "^1.4.0",
"psi": "2.0.4",
"rimraf": "2.5.4",
"sass-loader": "^4.0.2",

View File

@ -0,0 +1,7 @@
module.exports = {
plugins: [
require('postcss-smart-import')(),
require('precss')(),
require('autoprefixer')()
]
};

View File

@ -1,602 +0,0 @@
'use strict';
/**
* Module dependencies
*/
// Node.js core
const path = require('path');
// Public node modules
const _ = require('lodash');
const fs = require('fs-extra');
/**
* Run resolver function
*
* @param {Function} fn
* @param {String|Object|Array|Integer} rootValue
* @param {String} attribute
* @param {Object} scope
* @param {String} parent
*
* @return {Promise*Object}
*/
exports.resolver = (fn, rootValue, attribute, scope, rootAttribute) => {
return new Promise((resolve, reject) => {
let value;
if (_.isEmpty(rootAttribute)) {
value = rootValue[attribute];
} else {
value = rootValue[rootAttribute][attribute];
}
/**
* @param {Object} rootValue: Object send by the server
* @param {Object|String|Integer|Null} value: Value of this attribute
* @param {Object} scope: Value of file
* @param {Function} cb
*/
fn(rootValue, value, scope, (err, value) => {
if (err) {
let rejection = {};
if (_.isEmpty(rootAttribute)) {
rejection = attribute;
} else {
rejection = rootAttribute + ' > ' + attribute;
}
reject({
error: 'Resolver rejection',
attribute: rejection,
msg: err
});
} else {
let response = {};
if (_.isEmpty(rootAttribute)) {
response = {
value: value,
attribute: attribute
};
} else {
response[rootAttribute] = {
value: value,
attribute: attribute
};
}
resolve(response);
}
});
});
};
/**
* Get data type
*
* @param {String|Object|Array|Integer} value
*
* @return {String}
*/
exports.getType = value => {
if (_.isArray(value)) {
return 'array';
} else if (value === parseInt(value, 10)) {
return 'integer';
} else if (_.isObject(value)) {
return 'object';
} else if (_.isBoolean(value)) {
return 'boolean';
} else if (_.isNull(value)) {
return 'null';
} else if (_.isString(value)) {
return 'string';
}
return 'Unknown type';
};
/**
* Check data type
*
* @param {String} type
* @param {String} key
* @param {String|Object|Array|Integer} key
*
* @return {Promise*Object}
*/
exports.isType = (type, key, value) => {
return new Promise((resolve, reject) => {
switch (type) {
case 'boolean':
if (!_.isBoolean(value)) {
reject({
excepted: 'boolean',
attribute: key,
error: key + ' is not a boolean'
});
}
break;
case 'null':
if (!_.isNull(value)) {
reject({
excepted: 'null',
attribute: key,
error: key + ' is not a null'
});
}
break;
case 'integer':
if (value !== parseInt(value, 10)) {
reject({
excepted: 'integer',
attribute: key,
error: key + ' is not an integer'
});
}
break;
case 'object':
if (!_.isObject(value)) {
reject({
excepted: 'object',
attribute: key,
error: key + ' is not an object'
});
}
break;
case 'array':
if (!_.isArray(value)) {
reject({
excepted: 'array',
attribute: key,
error: key + ' is not an array'
});
}
break;
case 'string':
if (!_.isString(value)) {
reject({
excepted: 'string',
attribute: key,
error: key + ' is not a string'
});
}
break;
default:
reject({
excepted: 'Unknown type',
attribute: key,
error: key + ' type is not in the schema'
});
break;
}
resolve(type);
});
};
/**
* Parse value to verify data type and resolve path
*
* @param {Object} app
* @param {Object} validations
* @param {Object} attribute
* @param {Object} value
* @param {Object} rootAttribute
*
* @return {Promise*Object}
*/
exports.parser = (app, validations, attribute, value, rootAttribute) => {
return new Promise((resolve, reject) => {
const rootPath = validations.hasOwnProperty('path') && _.isString(validations.path) ? validations.path : null;
const rootType = validations.hasOwnProperty('type') ? validations.type : reject('Schema parameters `type` is missing.');
const rootNested = validations.hasOwnProperty('nested') && _.isString(validations.nested) ? validations.nested : null;
const rootKey = validations.hasOwnProperty('key') && _.isString(validations.key) ? validations.key : null;
if (_.isArray(rootType)) {
let typeFounded = null;
const arrayOfErrors = [];
const done = _.after(_.size(rootType), function () {
if (_.isEmpty(typeFounded)) {
reject(arrayOfErrors);
} else {
const object = {};
if (!_.isEmpty(rootAttribute)) {
object[rootAttribute] = {};
object[rootAttribute][attribute] = {
type: typeFounded
};
} else {
object[attribute] = {
type: typeFounded,
path: _.isNull(rootPath) ? rootPath : path.join(app.config.appPath, rootPath)
};
// Keep nested value
if (!_.isNull(rootNested)) {
object[attribute].nested = rootNested;
}
// Keep key to write
if (!_.isNull(rootKey)) {
object[attribute].key = rootKey;
}
}
resolve(object);
}
});
_.forEach(rootType, type => {
exports.isType(type, attribute, value)
.then(type => {
typeFounded = type;
done();
})
.catch(error => {
arrayOfErrors.push(error);
done();
});
});
} else {
exports.isType(rootType, attribute, value)
.then(type => {
const object = {};
if (!_.isEmpty(rootAttribute)) {
object[rootAttribute] = {};
object[rootAttribute][attribute] = {
type: type
};
} else {
object[attribute] = {
type: type,
path: _.isNull(rootPath) ? rootPath : path.join(app.config.appPath, rootPath)
};
// Keep nested value
if (!_.isNull(rootNested)) {
object[attribute].nested = rootNested;
}
// Keep key to write
if (!_.isNull(rootKey)) {
object[attribute].key = rootKey;
}
}
resolve(object);
})
.catch(error => {
reject(error);
});
}
});
};
/**
* Parse schema, validate it, run tests and resolvers
*
* @param {Object} app
* @param {Object} schema
* @param {Object} params
*
* @return {Promise*Array}
*/
exports.parse = (app, schema, params) => {
return new Promise((resolve, reject) => {
const diff = _.difference(_.keys(schema), _.keys(params));
if (_.isEmpty(diff)) {
const arrayOfTypes = [];
let AST = {};
_.forEach(schema, (validations, attribute) => {
arrayOfTypes.push(exports.parser(app, validations, attribute, params[attribute]));
});
// Check value's type
Promise.all(arrayOfTypes)
.then(results => {
// Build AST
_.forEach(results, value => {
AST = _.merge(AST, value);
});
const arrayOfNestedType = [];
_.forEach(schema, (validations, attribute) => {
if (_.isArray(validations.type)) {
const type = AST[attribute].type;
if (type === 'object') {
_.forEach(validations.values[type], (validations, key) => {
arrayOfNestedType.push(exports.parser(app, validations, key, params[attribute][key], attribute));
});
}
}
});
// Check value's type for nested objects
return Promise.all(arrayOfNestedType);
})
.then(results => {
// Build AST
_.forEach(AST, (validation, attribute) => {
if (validation.type === 'object') {
AST[attribute].value = {};
_.forEach(results, (value) => {
if (value.hasOwnProperty(attribute)) {
AST[attribute].schema = value[attribute];
}
});
}
});
// Get values
return exports.getFiles(app, AST);
})
.then(files => {
const arrayOfResolvers = [];
_.forEach(AST, (rootValidations, rootAttribute) => {
if (rootValidations.type === 'object') {
_.forEach(rootValidations.schema, (validations, attribute) => {
if (schema[rootAttribute].values.object[attribute].hasOwnProperty('resolver') && _.isFunction(schema[rootAttribute].values.object[attribute].resolver)) {
const index = _.findIndex(files, (n) => {
return n.path === rootValidations.path;
});
const resolver = schema[rootAttribute].values.object[attribute].resolver;
// Run resolver
arrayOfResolvers.push(exports.resolver(resolver, params, attribute, (index >= 0) ? files[index].value : params, rootAttribute));
} else {
AST[rootAttribute].value[attribute] = params[rootAttribute][attribute];
}
});
}
if (schema[rootAttribute].hasOwnProperty('resolver') && _.isFunction(schema[rootAttribute].resolver)) {
const index = _.findIndex(files, n => {
return n.path === rootValidations.path;
});
const resolver = schema[rootAttribute].resolver;
// Run resolver
arrayOfResolvers.push(exports.resolver(resolver, params, rootAttribute, (index >= 0) ? files[index].value : params));
} else {
AST[rootAttribute].value = params[rootAttribute];
}
});
// Execute resolver function for each value
return Promise.all(arrayOfResolvers);
})
.then(results => {
// Detect errors
return Promise.all(results);
})
.then(arrayOfValues => {
_.forEach(arrayOfValues, (data) => {
AST[data.attribute].value = data.value;
});
resolve(AST);
})
.catch(errors => {
console.log('errors', errors);
reject(errors);
});
} else {
reject('Some attributes are missing (' + diff.toString() + ')');
}
});
};
/**
* Get configuration files values
*
* @param {Object} app
* @param {Object} AST
*
* @return {Promise*Array}
*/
exports.getFiles = (app, AST) => {
const arrayOfPromises = [];
const filesToPull = _.groupBy(AST, 'path');
// Remove null path
delete filesToPull.null;
_.forEach(filesToPull, (attributes, path) => {
arrayOfPromises.push(new Promise((resolve, reject) => {
const rootPath = path;
// TODO: normalize
// const rootPath = path.normalize(path);
fs.exists(rootPath, exists => {
if (exists) {
fs.readFile(rootPath, 'utf8', (err, file) => {
if (err) {
reject('Impossible to read `' + rootPath + '`', null);
} else {
resolve({
path: path,
value: JSON.parse(file)
});
}
});
} else {
reject('Unknown path `' + rootPath + '`', null);
}
});
}));
});
return Promise.all(arrayOfPromises);
};
/**
* Update configuration files values
*
* @param {Object} app
* @param {Object} schema
* @param {Object} AST
*
* @return {Promise*Array}
*/
exports.updateFiles = (app, schema, AST) => {
return new Promise(resolve => {
let arrayOfFiles = [];
exports.getFiles(app, AST)
.then(files => {
if (_.isEmpty(files)) {
return Promise.resolve(files);
}
// Update values
_.forEach(AST, (validations, attribute) => {
if (!schema[attribute].hasOwnProperty('update') || (schema[attribute].hasOwnProperty('update') && schema[attribute].update !== false)) {
const index = _.findIndex(files, (n) => {
return n.path === validations.path;
});
if (index >= 0) {
let rootAttribute = null;
let subAttribute = null;
const type = exports.getType(files[index].value[attribute]);
if (_.isEmpty(validations.nested) && _.isEmpty(validations.key)) {
rootAttribute = attribute;
} else if (_.isEmpty(validations.nested) && !_.isEmpty(validations.key)) {
rootAttribute = validations.key;
} else if (!_.isEmpty(validations.nested) && !_.isEmpty(validations.key)) {
rootAttribute = validations.nested;
subAttribute = validations.key;
} else {
rootAttribute = validations.nested;
subAttribute = attribute;
}
// Merge values only if the current type and destination object are object or array.
if (_.includes(['array', 'object'], type) && _.includes(['array', 'object'], validations.type) && _.isNull(subAttribute)) {
files[index].value[rootAttribute] = _.assign(files[index].value[attribute], validations.value);
} else if (_.includes(['array', 'object'], type) && _.includes(['array', 'object'], validations.type) && !_.isNull(subAttribute)) {
files[index].value[rootAttribute][subAttribute] = _.assign(files[index].value[attribute], validations.value);
} else if (_.isNull(subAttribute)) {
files[index].value[rootAttribute] = validations.value;
} else {
files[index].value[rootAttribute][subAttribute] = validations.value;
}
}
}
});
const arrayOfPromises = [];
// Generate new files
_.forEach(files, data => {
arrayOfPromises.push(exports.generateSetting(app, data.path, path.basename(data.path), data.value));
});
return Promise.all(arrayOfPromises);
})
.then(files => {
arrayOfFiles = _.union(arrayOfFiles, files);
resolve(arrayOfFiles);
})
.catch(error => {
resolve(error);
});
});
};
/**
* Generate settings file
*
* @param {Object} app
* @param {String} rootPath
* @param {String} file
* @param {Object} rootValue
*
* @return {Promise}
*/
exports.generateSetting = (app, rootPath, file, rootValue) => {
return new Promise((resolve, reject) => {
const filePath = path.resolve(strapi.config.appPath, file);
fs.writeJSON(filePath, rootValue, err => {
if (err) {
reject(err);
}
resolve({
dest: path.join(rootPath),
src: 'config'
});
});
});
};
/**
* Update general settings
*
* @param {Object} app
* @param {Object} params
*
* @return {Promise*Array}
*/
exports.configurationsManager = (app, params) => {
return new Promise((resolve, reject) => {
const rootPath = path.resolve(__dirname, 'settings', 'Schema' + _.capitalize(params.type) + '.js');
fs.exists(rootPath, exists => {
if (exists) {
if (params.hasOwnProperty('environment') && _.includes(_.keys(app.config.environments), params.environment)) {
app.currentUpdatedEnvironment = params.environment;
} else if (params.hasOwnProperty('environment') && !_.includes(_.keys(app.config.environments), params.environment)) {
reject('Unknown environment');
return;
}
const schema = require('./settings/Schema' + _.capitalize(params.type) + '.js')();
let globalValues = null;
exports.parse(app, schema, params.values)
.then(AST => {
globalValues = _.mapValues(AST, 'value');
return exports.updateFiles(app, schema, AST);
})
.then(files => {
resolve({
values: globalValues,
files: files
});
})
.catch(errors => {
console.log('errors', errors);
reject(errors);
});
} else {
reject('Unknown settings schema');
}
});
});
};

View File

@ -1,137 +0,0 @@
'use strict';
/**
* Schema general dependencies
*/
// Public node modules
const _ = require('lodash');
const validator = require('validator');
const SchemaDatabases = function(app) {
const schema = {
adapter: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
if (_.includes(['mongo', 'redis', 'arangodb', 'mysql', 'postgresql', 'sqlite', 'disk'], value)) {
return cb(null, value);
}
return cb('Unknow adapter', null);
}
},
name: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(_.deburr(value)));
}
},
host: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
if (validator.isURL(value) || validator.isIP(value)) {
return cb(null, value);
}
return cb('Invalid host', null);
}
},
port: {
type: 'integer',
path: null,
update: false
},
database: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(_.deburr(value)));
}
},
user: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(_.deburr(value)));
}
},
password: {
type: 'string',
path: null,
update: false
}
};
const schemaExtend = {
adapter: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
if (_.includes(['mongo', 'redis', 'arangodb', 'mysql', 'postgresql', 'sqlite', 'disk'], value)) {
return cb(null, value);
}
return cb('Unknow adapter', null);
}
},
name: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(_.deburr(value)));
}
},
host: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
if (validator.isURL(value) || validator.isIP(value)) {
return cb(null, value);
}
return cb('Invalid host', null);
}
},
database: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(_.deburr(value)));
}
},
filePath: {
type: 'string',
path: null,
update: false
},
fileName: {
type: 'string',
path: null,
update: false,
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(_.deburr(value)));
}
},
migrate: {
type: 'string',
path: null,
update: false
}
};
return (app.isExtend) ? schemaExtend : schema;
};
module.exports = SchemaDatabases;

View File

@ -1,124 +0,0 @@
'use strict';
/**
* Schema general dependencies
*/
// Public node modules
const _ = require('lodash');
const semver = require('semver');
// Local services
// var SocketService = require('../SocketService');
const SchemaGeneral = function() {
const schema = {
name: {
type: 'string',
path: 'package.json',
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(_.deburr(value)));
}
},
description: {
type: 'string',
path: 'package.json',
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(value));
}
},
version: {
type: 'string',
path: 'package.json',
resolver: function(rootValue, value, scope, cb) {
if (_.isNull(semver.valid(value))) {
return cb('Not valid as a semver version', null);
}
return cb(null, value);
}
},
// static: {
// type: 'boolean',
// path: 'config/general.json'
// },
// views: {
// type: ['boolean', 'object'],
// path: 'config/general.json',
// values: {
// object: {
// default: {
// type: 'string'
// },
// map: {
// type: 'object'
// }
// }
// },
// resolver: function(rootValue, value, scope, cb) {
// if (_.isObject(value)) {
// if (_.isEmpty(value.map)) {
// // Set lodash as default template engine
// value = {
// map: {
// html: 'lodash'
// },
// default: 'html'
// };
// } else if (!value.map.hasOwnProperty(value.default)) {
// value.default = _.first(_.keys(value.map));
// }
// }
//
// return cb(null, value);
// }
// },
// websockets: {
// type: 'boolean',
// path: 'config/general.json'
// },
// prefix: {
// type: 'string',
// path: 'config/general.json'
// },
// blueprints: {
// type: ['boolean', 'object'],
// path: 'config/general.json',
// values: {
// object: {
// defaultLimit: {
// type: 'integer'
// },
// populate: {
// type: 'boolean'
// }
// }
// },
// resolver: function(rootValue, value, scope, cb) {
// if (value.defaultLimit >= 0) {
// return cb(null, value);
// }
//
// return cb('Invalid default limit value', null);
// }
// },
// graphql: {
// type: 'object',
// path: 'config/general.json',
// values: {
// object: {
// enabled: {
// type: 'boolean'
// },
// route: {
// type: 'string'
// }
// }
// }
// }
};
return schema;
};
module.exports = SchemaGeneral;

View File

@ -1,24 +0,0 @@
'use strict';
/**
* Schema global variables dependencies
*/
// Public node modules
const _ = require('lodash');
const SchemaGlobal = function(app) {
const schema = {};
_.forEach(app.config.globals, function(value, key) {
schema[key] = {
type: 'boolean',
nested: 'globals',
path: 'config/globals.json'
};
});
return schema;
};
module.exports = SchemaGlobal;

View File

@ -1,100 +0,0 @@
'use strict';
/**
* Schema languages dependencies
*/
// Public node modules
const _ = require('lodash');
const path = require('path');
// Local services
const SettingService = require('../SettingsService');
// const SocketService = require('../SocketService');
const SchemaLanguages = function(app) {
const schema = {
defaultLocale: {
type: 'string',
path: 'config/i18n.json',
nested: 'i18n',
resolver: function(rootValue, value, scope, cb) {
if (_.includes(_.union(app.config.i18n.locales, rootValue.locales), value)) {
return cb(null, value);
}
return cb('This locale doesn\'t exist', null);
}
},
locales: {
update: false,
type: 'array',
path: 'config/i18n.json',
resolver: function(rootValue, value, scope, cb) {
var arrayOfFiles = [];
var localesToRemove = _.difference(app.config.i18n.locales, value);
var localesToAdd = _.difference(value, _.difference(app.config.i18n.locales, localesToRemove));
var defaultLocale = _.includes(app.config.i18n.locales, rootValue.defaultLocale) ? rootValue.defaultLocale : app.config.i18n.defaultLocale;
SettingService.getFiles(app, [{
path: path.resolve(app.config.appPath, 'config', 'locales', defaultLocale + '.json')}
])
.then(function(files) {
var arrayOfPromises = [];
_.forEach(localesToAdd, function(locale) {
var localePath = path.resolve(app.config.appPath, 'config', 'locales', locale + '.json');
arrayOfPromises.push(SettingService.generateSetting(app, localePath, locale + '.json', files[0].value));
});
// Create new locale based on default locale
return Promise.all(arrayOfPromises);
})
.then(function(files) {
arrayOfFiles = _.union(arrayOfFiles, files);
var arrayOfPathToRemove = [];
_.forEach(localesToRemove, function(locale) {
arrayOfPathToRemove.push({
path: path.resolve(app.config.appPath, 'config', 'locales', locale + '.json')
});
});
return;
// Remove locales from local machine
// return SocketService.todo({
// action: 'removeFileOrFolder',
// from: app.token,
// to: app.config.studio.appId,
// toRemove: arrayOfPathToRemove
// });
})
.then(function() {
return;
// return SocketService.zip(app.token, arrayOfFiles);
})
.then(function() {
return;
// return SocketService.todo({
// from: app.token,
// to: app.config.studio.appId,
// files: arrayOfFiles
// });
})
.then(function() {
cb(null, value);
})
.catch(function(errors) {
cb(errors, null);
});
}
}
};
return schema;
};
module.exports = SchemaLanguages;

View File

@ -1,201 +0,0 @@
'use strict';
/**
* Schema security dependencies
*/
// Public node modules
const _ = require('lodash');
const validator = require('validator');
const SchemaSecurity = function(app) {
const schema = {
session: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
values: {
object: {
key: {
type: 'string'
},
secretKeys: {
type: 'array'
},
maxAge: {
type: 'integer'
}
}
}
},
csrf: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
values: {
object: {
key: {
type: 'string'
},
secret: {
type: 'string'
}
}
}
},
csp: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
values: {
object: {
reportOnly: {
type: 'boolean'
},
reportUri: {
type: 'string'
}
}
},
resolve: function(rootValue, value, scope, cb) {
if (_.isObject(value)) {
if (validator.isURL(value.reportUri)) {
return cb(null, value);
}
return cb('ReportURI is not a valid URL', null);
}
return cb(null, value);
}
},
hsts: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
values: {
object: {
maxAge: {
type: 'integer'
},
includeSubDomains: {
type: 'boolean'
}
}
}
},
xframe: {
type: ['boolean', 'string'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json'
},
xssProtection: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
values: {
object: {
enabled: {
type: 'boolean'
},
mode: {
type: 'string'
}
}
}
},
cors: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
values: {
object: {
origin: {
type: 'boolean'
},
expose: {
type: 'array'
},
maxAge: {
type: 'integer'
},
credentials: {
type: 'boolean'
},
methods: {
type: 'array'
},
headers: {
type: 'array'
}
}
}
},
ssl: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
values: {
object: {
disabled: {
type: 'boolean'
},
trustProxy: {
type: 'boolean'
}
}
}
},
ip: {
type: 'object',
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
values: {
object: {
whiteList: {
type: 'array'
},
blackList: {
type: 'array'
}
}
},
resolver: function(rootValue, value, scope, cb) {
var arrayOfWhiteIP = [];
var arrayOfBlackIP = [];
_.forEach(value.whiteList, function(whiteListIP) {
if (!validator.isIP(whiteListIP) && !validator.isURL(whiteListIP)) {
arrayOfWhiteIP.push(whiteListIP);
}
});
_.forEach(value.blackList, function(blackListIP) {
if (!validator.isIP(blackListIP) && !validator.isURL(blackListIP)) {
arrayOfBlackIP.push(blackListIP);
}
});
if (_.isEmpty(arrayOfBlackIP) && !_.isEmpty(arrayOfWhiteIP)) {
return cb('Those whitelisted IP are invalid: ' + arrayOfWhiteIP.toString(), null);
} else if (!_.isEmpty(arrayOfBlackIP) && _.isEmpty(arrayOfWhiteIP)) {
return cb('Those blacklisted IP are invalid: ' + arrayOfBlackIP.toString(), null);
} else if (!_.isEmpty(arrayOfBlackIP) && !_.isEmpty(arrayOfWhiteIP)) {
return cb('Those blacklisted and whitelisted IP are invalid: ' + arrayOfBlackIP.toString() + arrayOfWhiteIP.toString(), null);
}
return cb(null, value);
}
},
proxy: {
type: ['boolean', 'string'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/security.json',
resolver: function(rootValue, value, scope, cb) {
if (_.isString(value)) {
if (validator.isURL(value) || validator.isIP(value)) {
return cb(null, value);
}
return cb('Invalid proxy host', null);
}
return cb(null, value);
}
}
};
return schema;
};
module.exports = SchemaSecurity;

View File

@ -1,107 +0,0 @@
'use strict';
/**
* Schema general dependencies
*/
// Public node modules
const _ = require('lodash');
const validator = require('validator');
const SchemaServer = function(app) {
const schema = {
serverHost: {
type: 'string',
path: 'config/environments/' + app.currentUpdatedEnvironment + '/server.json',
key: 'host',
resolver: function(rootValue, value, scope, cb) {
return cb(null, _.trim(value));
}
},
serverPort: {
type: ['integer', 'string'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/server.json',
key: 'port',
resolver: function(rootValue, value, scope, cb) {
if (_.isString(value) && _.isEmpty(value)) {
return cb(null, '');
} else if (value >= 0 && value <= 65535) {
return cb(null, value);
}
return cb('Invalid port number', null);
}
},
frontendHost: {
type: 'string',
path: 'config/environments/' + app.currentUpdatedEnvironment + '/server.json',
key: 'frontendUrl',
resolver: function(rootValue, value, scope, cb) {
if (validator.isURL(value) || _.isEmpty(value)) {
return cb(null, value);
}
return cb('Invalid front-end host URL', null);
}
},
reload: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/server.json',
values: {
object: {
timeout: {
type: 'integer'
},
workers: {
type: 'integer'
}
}
}
},
logger: {
type: 'boolean',
path: 'config/environments/' + app.currentUpdatedEnvironment + '/server.json'
},
parser: {
type: ['boolean', 'object'],
path: 'config/environments/' + app.currentUpdatedEnvironment + '/server.json',
values: {
object: {
encode: {
type: 'string'
},
formLimit: {
type: 'string'
},
jsonLimit: {
type: 'string'
},
strict: {
type: 'boolean'
}
}
},
resolver: function(rootValue, value, scope, cb) {
if (_.isObject(value) && !value.hasOwnProperty('extendTypes')) {
value.extendTypes = {
json: ['application/x-javascript']
};
}
return cb(null, value);
}
},
gzip: {
type: 'boolean',
path: 'config/environments/' + app.currentUpdatedEnvironment + '/server.json'
},
responseTime: {
type: 'boolean',
path: 'config/environments/' + app.currentUpdatedEnvironment + '/server.json'
}
};
return schema;
};
module.exports = SchemaServer;

View File

@ -1,33 +0,0 @@
'use strict';
/**
* Schema Dashboard & Studio variables dependencies
*/
const SchemaStudio = function() {
const schema = {
studio: {
type: 'object',
path: 'config/studio.json',
values: {
object: {
enabled: {
type: 'boolean'
},
secretKey: {
type: 'string'
}
}
},
resolver: function(rootValue, value, scope, cb) {
scope.studio = value;
return cb(null, scope.studio);
}
}
};
return schema;
};
module.exports = SchemaStudio;