);
8 | expect(renderedComponent.type()).toEqual('div');
9 | });
10 |
--------------------------------------------------------------------------------
/app/components/ProgressBar/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | it('should render an
tag', () => {
7 | const renderedComponent = shallow(
);
8 | expect(renderedComponent.type()).toEqual('div');
9 | });
10 |
--------------------------------------------------------------------------------
/app/components/Toggle/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * LocaleToggle
4 | *
5 | */
6 |
7 | import React from 'react';
8 |
9 | import Option from './option';
10 |
11 | function Toggle(props) {
12 | let content = (
-- );
13 |
14 | // If we have items, render them
15 | if (props.values) {
16 | content = props.values.map((value) => (
17 |
18 | ));
19 | }
20 |
21 | return (
22 |
23 |
24 | {content}
25 |
26 |
27 | );
28 | }
29 |
30 | Toggle.propTypes = {
31 | onToggle: React.PropTypes.func,
32 | values: React.PropTypes.array,
33 | value: React.PropTypes.string,
34 | messages: React.PropTypes.object,
35 | };
36 |
37 | export default Toggle;
38 |
--------------------------------------------------------------------------------
/app/components/Toggle/option.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * ToggleOption
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { injectIntl, intlShape } from 'react-intl';
9 |
10 | const Option = ({ value, message, intl }) => (
11 |
12 | {message ? intl.formatMessage(message) : value}
13 |
14 | );
15 |
16 | Option.propTypes = {
17 | value: React.PropTypes.string.isRequired,
18 | message: React.PropTypes.object,
19 | intl: intlShape.isRequired,
20 | };
21 |
22 | export default injectIntl(Option);
23 |
--------------------------------------------------------------------------------
/app/components/Toggle/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import { IntlProvider, defineMessages } from 'react-intl';
4 |
5 | import Toggle from '../index';
6 |
7 | describe('
', () => {
8 | it('should contain default text', () => {
9 | const defaultEnMessage = 'someContent';
10 | const defaultDeMessage = 'someOtherContent';
11 | const messages = defineMessages({
12 | en: {
13 | id: 'boilerplate.containers.LocaleToggle.en',
14 | defaultMessage: defaultEnMessage,
15 | },
16 | de: {
17 | id: 'boilerplate.containers.LocaleToggle.en',
18 | defaultMessage: defaultDeMessage,
19 | },
20 | });
21 | const renderedComponent = shallow(
22 |
23 |
24 |
25 | );
26 | expect(renderedComponent.contains(
)).toBe(true);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/app/components/TriggerHttpCreateForm/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerHttpCreateForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 | import { FormattedMessage } from 'react-intl';
10 | import commonMessages from 'messages';
11 |
12 | class TriggerHttpCreateForm extends React.Component { // eslint-disable-line react/prefer-stateless-function
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {
17 | urlpattern: '/',
18 | method: 'GET',
19 | };
20 | this.onChange = this.onChange.bind(this);
21 | this.onTriggerCreate = this.onTriggerCreate.bind(this);
22 | }
23 |
24 | onChange(event) {
25 | const target = event.target;
26 | this.state[target.name] = target.value;
27 | }
28 |
29 | onTriggerCreate(event) {
30 | event.preventDefault();
31 | const { onCreate } = this.props;
32 | onCreate(this.state);
33 | }
34 |
35 | render() {
36 | return (
37 |
54 | );
55 | }
56 | }
57 |
58 | TriggerHttpCreateForm.propTypes = {
59 | onCreate: React.PropTypes.func,
60 | };
61 |
62 | export default TriggerHttpCreateForm;
63 |
--------------------------------------------------------------------------------
/app/components/TriggerHttpCreateForm/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * TriggerHttpCreateForm Messages
3 | *
4 | * This contains all the text for the TriggerHttpCreateForm component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.TriggerHttpCreateForm.header',
11 | defaultMessage: 'This is the TriggerHttpCreateForm component !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/components/TriggerHttpForm/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerHttpForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 | import TriggerHttpCreateForm from 'components/TriggerHttpCreateForm';
10 | import { FormattedMessage } from 'react-intl';
11 | import commonMessages from 'messages';
12 | import messages from './messages';
13 | import Item from './item';
14 |
15 | class TriggerHttpForm extends React.Component { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | const { triggers, onRemove, onCreate } = this.props;
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {
31 | triggers.map((item, index) => (
32 | - { onRemove(item); }} />
33 | ))
34 | }
35 |
36 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 | TriggerHttpForm.propTypes = {
44 | triggers: React.PropTypes.array,
45 | onRemove: React.PropTypes.func,
46 | onCreate: React.PropTypes.func,
47 | };
48 |
49 | export default TriggerHttpForm;
50 |
--------------------------------------------------------------------------------
/app/components/TriggerHttpForm/item.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerHttpItemForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import ReactTooltip from 'react-tooltip';
9 | import { FormattedMessage } from 'react-intl';
10 | import commonMessages from 'messages';
11 | // import styled from 'styled-components';
12 |
13 |
14 | class Item extends React.Component { // eslint-disable-line react/prefer-stateless-function
15 |
16 | makeCopy(endpoint) {
17 | return () => {
18 | window.prompt('Copy to clipboard: Ctrl+C, Enter', endpoint);
19 | };
20 | }
21 |
22 | render() {
23 | const { trigger, onRemove } = this.props;
24 | const endpoint = `${window.location.origin}/proxy/router${trigger.urlpattern}`;
25 | return (
26 |
27 | {trigger.method}
28 | {trigger.urlpattern}
29 |
30 | { ' ' }
31 |
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | Item.propTypes = {
40 | trigger: React.PropTypes.object,
41 | onRemove: React.PropTypes.func,
42 | };
43 |
44 | export default Item;
45 |
--------------------------------------------------------------------------------
/app/components/TriggerHttpForm/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * TriggerHttpForm Messages
3 | *
4 | * This contains all the text for the TriggerHttpForm component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | headerhttptrigger: {
10 | id: 'app.components.TriggerHttpForm.headerhttptrigger',
11 | defaultMessage: 'Http trigger',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/components/TriggerMQCreateForm/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerMQCreateForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 | import { FormattedMessage } from 'react-intl';
10 | import commonMessages from 'messages';
11 |
12 | class TriggerMQCreateForm extends React.Component { // eslint-disable-line react/prefer-stateless-function
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {
17 | messageQueueType: 'nats-streaming',
18 | topic: '',
19 | respTopic: '',
20 | };
21 | this.onChange = this.onChange.bind(this);
22 | this.onTriggerCreate = this.onTriggerCreate.bind(this);
23 | }
24 |
25 | onChange(event) {
26 | const target = event.target;
27 | this.state[target.name] = target.value;
28 | }
29 |
30 | onTriggerCreate(event) {
31 | event.preventDefault();
32 | const { onCreate } = this.props;
33 | onCreate(this.state);
34 | }
35 |
36 | render() {
37 | return (
38 |
53 | );
54 | }
55 | }
56 |
57 | TriggerMQCreateForm.propTypes = {
58 | onCreate: React.PropTypes.func,
59 | };
60 |
61 | export default TriggerMQCreateForm;
62 |
--------------------------------------------------------------------------------
/app/components/TriggerMQCreateForm/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * TriggerMQCreateForm Messages
3 | *
4 | * This contains all the text for the TriggerMQCreateForm component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.TriggerMQCreateForm.header',
11 | defaultMessage: 'This is the TriggerMQCreateForm component !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/components/TriggerMQForm/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerMQForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 | import TriggerMQCreateForm from 'components/TriggerMQCreateForm';
10 | import { FormattedMessage } from 'react-intl';
11 | import commonMessages from 'messages';
12 | import messages from './messages';
13 | import Item from './item';
14 |
15 | class TriggerMQForm extends React.Component { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | const { triggers, onRemove, onCreate } = this.props;
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {
32 | triggers.map((item, index) => (
33 | - { onRemove(item); }} />
34 | ))
35 | }
36 |
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | TriggerMQForm.propTypes = {
45 | triggers: React.PropTypes.array,
46 | onRemove: React.PropTypes.func,
47 | onCreate: React.PropTypes.func,
48 | };
49 |
50 | export default TriggerMQForm;
51 |
--------------------------------------------------------------------------------
/app/components/TriggerMQForm/item.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerHttpItemForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import commonMessages from 'messages';
10 | // import styled from 'styled-components';
11 |
12 |
13 | class Item extends React.Component { // eslint-disable-line react/prefer-stateless-function
14 |
15 | render() {
16 | const { trigger, onRemove } = this.props;
17 | return (
18 |
19 | {trigger.messageQueueType}
20 | {trigger.topic}
21 | {trigger.respTopic}
22 |
23 | { ' ' }
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | Item.propTypes = {
31 | trigger: React.PropTypes.object,
32 | onRemove: React.PropTypes.func,
33 | };
34 |
35 | export default Item;
36 |
--------------------------------------------------------------------------------
/app/components/TriggerMQForm/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * TriggerMQForm Messages
3 | *
4 | * This contains all the text for the TriggerMQForm component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | headerhttptrigger: {
10 | id: 'app.components.TriggerMQForm.headerhttptrigger',
11 | defaultMessage: 'MQ trigger',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/components/TriggerTimerCreateForm/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerTimerCreateForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 | import { FormattedMessage } from 'react-intl';
10 | import commonMessages from 'messages';
11 |
12 | class TriggerTimerCreateForm extends React.Component { // eslint-disable-line react/prefer-stateless-function
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {
17 | cron: '',
18 | description: '',
19 | };
20 | this.onChange = this.onChange.bind(this);
21 | this.onTriggerCreate = this.onTriggerCreate.bind(this);
22 | }
23 |
24 | onChange(event) {
25 | const target = event.target;
26 | this.state[target.name] = target.value;
27 | }
28 |
29 | onTriggerCreate(event) {
30 | event.preventDefault();
31 | const { onCreate } = this.props;
32 | onCreate(this.state);
33 | }
34 |
35 | render() {
36 | return (
37 |
44 | );
45 | }
46 | }
47 |
48 | TriggerTimerCreateForm.propTypes = {
49 | onCreate: React.PropTypes.func,
50 | };
51 |
52 | export default TriggerTimerCreateForm;
53 |
--------------------------------------------------------------------------------
/app/components/TriggerTimerCreateForm/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * TriggerTimerCreateForm Messages
3 | *
4 | * This contains all the text for the TriggerTimerCreateForm component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.TriggerTimerCreateForm.header',
11 | defaultMessage: 'This is the TriggerTimerCreateForm component !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/components/TriggerTimerForm/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerTimerForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 | import TriggerTimerCreateForm from 'components/TriggerTimerCreateForm';
10 | import { FormattedMessage } from 'react-intl';
11 | import commonMessages from 'messages';
12 | import messages from './messages';
13 | import Item from './item';
14 |
15 | class TriggerTimerForm extends React.Component { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | const { triggers, onRemove, onCreate } = this.props;
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {
30 | triggers.map((item, index) => (
31 | - { onRemove(item); }} />
32 | ))
33 | }
34 |
35 |
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | TriggerTimerForm.propTypes = {
43 | triggers: React.PropTypes.array,
44 | onRemove: React.PropTypes.func,
45 | onCreate: React.PropTypes.func,
46 | };
47 |
48 | export default TriggerTimerForm;
49 |
--------------------------------------------------------------------------------
/app/components/TriggerTimerForm/item.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * TriggerHttpItemForm
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import commonMessages from 'messages';
10 | // import styled from 'styled-components';
11 |
12 |
13 | class Item extends React.Component { // eslint-disable-line react/prefer-stateless-function
14 |
15 | render() {
16 | const { trigger, onRemove } = this.props;
17 | return (
18 |
19 | {trigger.cron}
20 |
21 | { ' ' }
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | Item.propTypes = {
29 | trigger: React.PropTypes.object,
30 | onRemove: React.PropTypes.func,
31 | };
32 |
33 | export default Item;
34 |
--------------------------------------------------------------------------------
/app/components/TriggerTimerForm/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * TriggerTimerForm Messages
3 | *
4 | * This contains all the text for the TriggerTimerForm component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | headerhttptrigger: {
10 | id: 'app.components.TriggerTimerForm.headerhttptrigger',
11 | defaultMessage: 'Timer trigger',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/App/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * AppConstants
3 | * Each action has a corresponding type, which the reducer knows and picks up on.
4 | * To avoid weird typos between the reducer and the actions, we save them as
5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid
6 | * reducers accidentally picking up actions they shouldn't.
7 | *
8 | * Follow this format:
9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT';
10 | */
11 |
12 | export const DEFAULT_LOCALE = 'en';
13 |
--------------------------------------------------------------------------------
/app/containers/App/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * App.react.js
4 | *
5 | * This component is the skeleton around the actual pages, and should only
6 | * contain code that should be seen on all pages. (e.g. navigation bar)
7 | *
8 | * NOTE: while this component should technically be a stateless functional
9 | * component (SFC), hot reloading does not currently support SFCs. If hot
10 | * reloading is not a necessity for you then you can refactor it and remove
11 | * the linting exception.
12 | */
13 |
14 | import React from 'react';
15 | import { browserHistory } from 'react-router';
16 | import withProgressBar from 'components/ProgressBar';
17 | import LocaleToggle from 'containers/LocaleToggle';
18 | import { FormattedMessage } from 'react-intl';
19 | import { Navbar, Nav, NavItem } from 'react-bootstrap';
20 | import commonMessages from 'messages';
21 |
22 | export function App(props) {
23 | const onLink = (e) => {
24 | browserHistory.push(e);
25 | };
26 |
27 | return (
28 |
29 |
30 |
31 |
32 | Fission UI
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {React.Children.toArray(props.children)}
55 |
56 |
57 |
58 | );
59 | }
60 | App.propTypes = {
61 | children: React.PropTypes.node,
62 | };
63 |
64 | export default withProgressBar(App);
65 |
--------------------------------------------------------------------------------
/app/containers/App/selectors.js:
--------------------------------------------------------------------------------
1 | // makeSelectLocationState expects a plain JS object for the routing state
2 | const makeSelectLocationState = () => {
3 | let prevRoutingState;
4 | let prevRoutingStateJS;
5 |
6 | return (state) => {
7 | const routingState = state.get('route'); // or state.route
8 |
9 | if (!routingState.equals(prevRoutingState)) {
10 | prevRoutingState = routingState;
11 | prevRoutingStateJS = routingState.toJS();
12 | }
13 |
14 | return prevRoutingStateJS;
15 | };
16 | };
17 |
18 | export {
19 | makeSelectLocationState,
20 | };
21 |
--------------------------------------------------------------------------------
/app/containers/App/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import App from '../index';
5 |
6 | describe('
', () => {
7 | it('should render its children', () => {
8 | const children = (
Test );
9 | const renderedComponent = shallow(
10 |
11 | {children}
12 |
13 | );
14 | expect(renderedComponent.contains(children)).toBe(true);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/app/containers/App/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import { makeSelectLocationState } from 'containers/App/selectors';
4 |
5 | describe('makeSelectLocationState', () => {
6 | it('should select the route as a plain JS object', () => {
7 | const route = fromJS({
8 | locationBeforeTransitions: null,
9 | });
10 | const mockedState = fromJS({
11 | route,
12 | });
13 | expect(makeSelectLocationState()(mockedState)).toEqual(route.toJS());
14 | });
15 |
16 | it('should return cached js routeState for same concurrent calls', () => {
17 | const route = fromJS({
18 | locationBeforeTransitions: null,
19 | });
20 | const mockedState = fromJS({
21 | route,
22 | });
23 | const selectLocationState = makeSelectLocationState();
24 |
25 | const firstRouteStateJS = selectLocationState(mockedState);
26 | expect(selectLocationState(mockedState)).toBe(firstRouteStateJS);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkConfigListItem/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * BenchmarkConfigListItem
4 | *
5 | */
6 |
7 | import React, { PropTypes } from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import { Link } from 'react-router';
10 | import commonMessages from 'messages';
11 |
12 | export class BenchmarkConfigListItem extends React.Component { // eslint-disable-line react/prefer-stateless-function
13 | render() {
14 | const { item, onRemove } = this.props;
15 | const { name } = item.metadata;
16 | return (
17 |
18 | { name }
19 |
20 | { ' ' }
21 | { ' ' }
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
29 | BenchmarkConfigListItem.propTypes = {
30 | item: PropTypes.object,
31 | onRemove: PropTypes.func,
32 | };
33 |
34 | export default BenchmarkConfigListItem;
35 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkConfigPage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * BenchmarksListPage actions
4 | *
5 | */
6 |
7 | import {
8 | UPDATE_CONFIG_REQUEST,
9 | GET_CONFIG_REQUEST,
10 | CREATE_CONFIG_REQUEST,
11 | } from 'containers/BenchmarksPage/constants';
12 |
13 | export function updateConfigAction(config) {
14 | return {
15 | type: UPDATE_CONFIG_REQUEST,
16 | config,
17 | };
18 | }
19 | export function createConfigAction(config) {
20 | return {
21 | type: CREATE_CONFIG_REQUEST,
22 | config,
23 | };
24 | }
25 | export function getConfigAction(name) {
26 | return {
27 | type: GET_CONFIG_REQUEST,
28 | name,
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkConfigPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * BenchmarksListPage Messages
3 | *
4 | * This contains all the text for the BenchmarksListPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.BenchmarksListPage.header',
11 | defaultMessage: 'This is BenchmarksListPage container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkConfigPage/sagas.js:
--------------------------------------------------------------------------------
1 | import { takeLatest, call, put, take, cancel } from 'redux-saga/effects';
2 | import { createBenchmarkConfig, updateBenchmarkConfig, getBenchmarkConfig } from 'utils/tprapi';
3 | import { LOCATION_CHANGE } from 'react-router-redux';
4 | import {
5 | CREATE_CONFIG_REQUEST,
6 | CREATE_CONFIG_SUCCESS,
7 | CREATE_CONFIG_ERROR,
8 | GET_CONFIG_REQUEST,
9 | GET_CONFIG_SUCCESS,
10 | GET_CONFIG_ERROR,
11 | UPDATE_CONFIG_REQUEST,
12 | UPDATE_CONFIG_SUCCESS,
13 | UPDATE_CONFIG_ERROR,
14 | } from 'containers/BenchmarksPage/constants';
15 |
16 | function* createConfig(action) {
17 | try {
18 | const data = yield call(createBenchmarkConfig, action.config);
19 | yield put({ type: CREATE_CONFIG_SUCCESS, data });
20 | } catch (error) {
21 | yield put({ type: CREATE_CONFIG_ERROR, error });
22 | }
23 | }
24 |
25 | function* updateConfig(action) {
26 | try {
27 | const data = yield call(updateBenchmarkConfig, action.config);
28 | yield put({ type: UPDATE_CONFIG_SUCCESS, data });
29 | } catch (error) {
30 | yield put({ type: UPDATE_CONFIG_ERROR, error });
31 | }
32 | }
33 |
34 | function* getConfig(action) {
35 | try {
36 | const data = yield call(getBenchmarkConfig, action.name);
37 | yield put({ type: GET_CONFIG_SUCCESS, data });
38 | } catch (error) {
39 | yield put({ type: GET_CONFIG_ERROR, error });
40 | }
41 | }
42 |
43 | function makeSaga(l, f) {
44 | return function* s() {
45 | const watcher = yield takeLatest(l, f);
46 |
47 | // Suspend execution until location changes
48 | yield take(LOCATION_CHANGE);
49 | yield cancel(watcher);
50 | };
51 | }
52 |
53 | // All sagas to be loaded
54 | export default [
55 | [CREATE_CONFIG_REQUEST, createConfig],
56 | [UPDATE_CONFIG_REQUEST, updateConfig],
57 | [GET_CONFIG_REQUEST, getConfig],
58 | ].map((d) => makeSaga(d[0], d[1]));
59 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkConfigsListPage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * BenchmarksListPage actions
4 | *
5 | */
6 |
7 | import {
8 | LOAD_CONFIGS_REQUEST,
9 | UPDATE_CONFIG_REQUEST,
10 | GET_CONFIG_REQUEST,
11 | CREATE_CONFIG_REQUEST,
12 | DELETE_CONFIG_REQUEST,
13 | } from 'containers/BenchmarksPage/constants';
14 |
15 | export function removeConfigAction(config) {
16 | return {
17 | type: DELETE_CONFIG_REQUEST,
18 | config,
19 | };
20 | }
21 | export function updateConfigAction(config) {
22 | return {
23 | type: UPDATE_CONFIG_REQUEST,
24 | config,
25 | };
26 | }
27 | export function createConfigAction(config) {
28 | return {
29 | type: CREATE_CONFIG_REQUEST,
30 | config,
31 | };
32 | }
33 | export function getConfigAction(name) {
34 | return {
35 | type: GET_CONFIG_REQUEST,
36 | name,
37 | };
38 | }
39 | export function loadConfigsAction() {
40 | return {
41 | type: LOAD_CONFIGS_REQUEST,
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkConfigsListPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * BenchmarksListPage Messages
3 | *
4 | * This contains all the text for the BenchmarksListPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.BenchmarksListPage.header',
11 | defaultMessage: 'This is BenchmarksListPage container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkConfigsListPage/sagas.js:
--------------------------------------------------------------------------------
1 | import { takeLatest, call, put, take, cancel } from 'redux-saga/effects';
2 | import {
3 | getBenchmarkConfigs,
4 | removeBenchmarkConfig,
5 | createBenchmarkConfig,
6 | updateBenchmarkConfig,
7 | getBenchmarkConfig,
8 | } from 'utils/tprapi';
9 | import { LOCATION_CHANGE } from 'react-router-redux';
10 | import {
11 | LOAD_CONFIGS_REQUEST,
12 | LOAD_CONFIGS_SUCCESS,
13 | LOAD_CONFIGS_ERROR,
14 | DELETE_CONFIG_REQUEST,
15 | DELETE_CONFIG_SUCCESS,
16 | DELETE_CONFIG_ERROR,
17 | CREATE_CONFIG_REQUEST,
18 | CREATE_CONFIG_SUCCESS,
19 | CREATE_CONFIG_ERROR,
20 | GET_CONFIG_REQUEST,
21 | GET_CONFIG_SUCCESS,
22 | GET_CONFIG_ERROR,
23 | UPDATE_CONFIG_REQUEST,
24 | UPDATE_CONFIG_SUCCESS,
25 | UPDATE_CONFIG_ERROR,
26 | } from 'containers/BenchmarksPage/constants';
27 |
28 | function* loadConfigs() {
29 | try {
30 | const data = yield call(getBenchmarkConfigs);
31 | yield put({ type: LOAD_CONFIGS_SUCCESS, data: data.items });
32 | } catch (error) {
33 | yield put({ type: LOAD_CONFIGS_ERROR, error });
34 | }
35 | }
36 |
37 | function* deleteConfig(action) {
38 | try {
39 | yield call(removeBenchmarkConfig, action.config);
40 | yield put({ type: DELETE_CONFIG_SUCCESS, data: action.config });
41 | } catch (error) {
42 | yield put({ type: DELETE_CONFIG_ERROR, error });
43 | }
44 | }
45 |
46 | function* createConfig(action) {
47 | try {
48 | const data = yield call(createBenchmarkConfig, action.config);
49 | yield put({ type: CREATE_CONFIG_SUCCESS, data });
50 | } catch (error) {
51 | yield put({ type: CREATE_CONFIG_ERROR, error });
52 | }
53 | }
54 |
55 | function* updateConfig(action) {
56 | try {
57 | const data = yield call(updateBenchmarkConfig, action.config);
58 | yield put({ type: UPDATE_CONFIG_SUCCESS, data });
59 | } catch (error) {
60 | yield put({ type: UPDATE_CONFIG_ERROR, error });
61 | }
62 | }
63 |
64 | function* getConfig(action) {
65 | try {
66 | const data = yield call(getBenchmarkConfig, action.name);
67 | yield put({ type: GET_CONFIG_SUCCESS, data });
68 | } catch (error) {
69 | yield put({ type: GET_CONFIG_ERROR, error });
70 | }
71 | }
72 |
73 | function makeSaga(l, f) {
74 | return function* s() {
75 | const watcher = yield takeLatest(l, f);
76 |
77 | // Suspend execution until location changes
78 | yield take(LOCATION_CHANGE);
79 | yield cancel(watcher);
80 | };
81 | }
82 |
83 | // All sagas to be loaded
84 | export default [
85 | [LOAD_CONFIGS_REQUEST, loadConfigs],
86 | [DELETE_CONFIG_REQUEST, deleteConfig],
87 | [CREATE_CONFIG_REQUEST, createConfig],
88 | [UPDATE_CONFIG_REQUEST, updateConfig],
89 | [GET_CONFIG_REQUEST, getConfig],
90 | ].map((d) => makeSaga(d[0], d[1]));
91 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkInstanceListItem/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * BenchmarkConfigListItem
4 | *
5 | */
6 |
7 | import React, { PropTypes } from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import { Link } from 'react-router';
10 | import commonMessages from 'messages';
11 |
12 | export class BenchmarkConfigListItem extends React.Component { // eslint-disable-line react/prefer-stateless-function
13 | render() {
14 | const { item, onRun, onStop, onRemove } = this.props;
15 | const { name, labels } = item.metadata;
16 | const { status } = item.spec;
17 | return (
18 |
19 | { name }
20 | { status }
21 |
22 |
23 |
24 | { ' ' }
25 | { ' ' }
26 | { ' ' }
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | BenchmarkConfigListItem.propTypes = {
35 | item: PropTypes.object,
36 | onRemove: PropTypes.func,
37 | onRun: PropTypes.func,
38 | onStop: PropTypes.func,
39 | };
40 |
41 | export default BenchmarkConfigListItem;
42 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkInstancePage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * BenchmarksListPage actions
4 | *
5 | */
6 |
7 | import {
8 | GET_INSTANCE_REQUEST,
9 | GET_CONFIG_REQUEST,
10 | } from 'containers/BenchmarksPage/constants';
11 |
12 | export function getInstanceAction(name) {
13 | return {
14 | type: GET_INSTANCE_REQUEST,
15 | name,
16 | };
17 | }
18 |
19 | export function getConfigAction(name) {
20 | return {
21 | type: GET_CONFIG_REQUEST,
22 | name,
23 | };
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkInstancePage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * BenchmarksListPage Messages
3 | *
4 | * This contains all the text for the BenchmarksListPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.BenchmarksListPage.header',
11 | defaultMessage: 'This is BenchmarksListPage container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkInstancePage/sagas.js:
--------------------------------------------------------------------------------
1 | import { takeLatest, call, put, take, cancel } from 'redux-saga/effects';
2 | import { getBenchmarkInstance, getBenchmarkConfig } from 'utils/tprapi';
3 | import { LOCATION_CHANGE } from 'react-router-redux';
4 | import {
5 | GET_INSTANCE_REQUEST,
6 | GET_INSTANCE_SUCCESS,
7 | GET_INSTANCE_ERROR,
8 | GET_CONFIG_REQUEST,
9 | GET_CONFIG_SUCCESS,
10 | GET_CONFIG_ERROR,
11 | } from 'containers/BenchmarksPage/constants';
12 |
13 | function* getInstance(action) {
14 | try {
15 | const data = yield call(getBenchmarkInstance, action.name);
16 | yield put({ type: GET_INSTANCE_SUCCESS, data });
17 | } catch (error) {
18 | yield put({ type: GET_INSTANCE_ERROR, error });
19 | }
20 | }
21 |
22 | function* getConfig(action) {
23 | try {
24 | const data = yield call(getBenchmarkConfig, action.name);
25 | yield put({ type: GET_CONFIG_SUCCESS, data });
26 | } catch (error) {
27 | yield put({ type: GET_CONFIG_ERROR, error });
28 | }
29 | }
30 |
31 | function makeSaga(l, f) {
32 | return function* s() {
33 | const watcher = yield takeLatest(l, f);
34 |
35 | // Suspend execution until location changes
36 | yield take(LOCATION_CHANGE);
37 | yield cancel(watcher);
38 | };
39 | }
40 |
41 | // All sagas to be loaded
42 | export default [
43 | [GET_INSTANCE_REQUEST, getInstance],
44 | [GET_CONFIG_REQUEST, getConfig],
45 | ].map((d) => makeSaga(d[0], d[1]));
46 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkInstancesListPage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * BenchmarkInstancesListPage actions
4 | *
5 | */
6 |
7 | import {
8 | LOAD_INSTANCES_REQUEST,
9 | UPDATE_INSTANCE_REQUEST,
10 | GET_INSTANCE_REQUEST,
11 | CREATE_INSTANCE_REQUEST,
12 | DELETE_INSTANCE_REQUEST,
13 | } from 'containers/BenchmarksPage/constants';
14 |
15 | export function removeInstanceAction(instance) {
16 | return {
17 | type: DELETE_INSTANCE_REQUEST,
18 | instance,
19 | };
20 | }
21 | export function updateInstanceAction(ins, status) {
22 | const instance = Object.assign({}, ins);
23 | instance.spec.status = status;
24 | return {
25 | type: UPDATE_INSTANCE_REQUEST,
26 | instance,
27 | };
28 | }
29 | export function createInstanceAction(instance) {
30 | return {
31 | type: CREATE_INSTANCE_REQUEST,
32 | instance,
33 | };
34 | }
35 | export function getInstanceAction(name) {
36 | return {
37 | type: GET_INSTANCE_REQUEST,
38 | name,
39 | };
40 | }
41 | export function loadInstancesAction() {
42 | return {
43 | type: LOAD_INSTANCES_REQUEST,
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkInstancesListPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * BenchmarksListPage Messages
3 | *
4 | * This contains all the text for the BenchmarksListPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.BenchmarksListPage.header',
11 | defaultMessage: 'This is BenchmarksListPage container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/BenchmarkInstancesListPage/sagas.js:
--------------------------------------------------------------------------------
1 | import { takeLatest, call, put, take, cancel } from 'redux-saga/effects';
2 | import {
3 | getBenchmarkInstances,
4 | removeBenchmarkInstance,
5 | createBenchmarkInstance,
6 | updateBenchmarkInstance,
7 | getBenchmarkInstance,
8 | } from 'utils/tprapi';
9 | import { LOCATION_CHANGE } from 'react-router-redux';
10 | import {
11 | LOAD_INSTANCES_REQUEST,
12 | LOAD_INSTANCES_SUCCESS,
13 | LOAD_INSTANCES_ERROR,
14 | DELETE_INSTANCE_REQUEST,
15 | DELETE_INSTANCE_SUCCESS,
16 | DELETE_INSTANCE_ERROR,
17 | CREATE_INSTANCE_REQUEST,
18 | CREATE_INSTANCE_SUCCESS,
19 | CREATE_INSTANCE_ERROR,
20 | GET_INSTANCE_REQUEST,
21 | GET_INSTANCE_SUCCESS,
22 | GET_INSTANCE_ERROR,
23 | UPDATE_INSTANCE_REQUEST,
24 | UPDATE_INSTANCE_SUCCESS,
25 | UPDATE_INSTANCE_ERROR,
26 | } from 'containers/BenchmarksPage/constants';
27 |
28 | function* loadInstances() {
29 | try {
30 | const data = yield call(getBenchmarkInstances);
31 | yield put({ type: LOAD_INSTANCES_SUCCESS, data: data.items });
32 | } catch (error) {
33 | yield put({ type: LOAD_INSTANCES_ERROR, error });
34 | }
35 | }
36 |
37 | function* deleteInstance(action) {
38 | try {
39 | yield call(removeBenchmarkInstance, action.instance);
40 | yield put({ type: DELETE_INSTANCE_SUCCESS, data: action.instance });
41 | } catch (error) {
42 | yield put({ type: DELETE_INSTANCE_ERROR, error });
43 | }
44 | }
45 |
46 | function* createInstance(action) {
47 | try {
48 | const data = yield call(createBenchmarkInstance, action.instance);
49 | yield put({ type: CREATE_INSTANCE_SUCCESS, data });
50 | } catch (error) {
51 | yield put({ type: CREATE_INSTANCE_ERROR, error });
52 | }
53 | }
54 |
55 | function* updateInstance(action) {
56 | try {
57 | const data = yield call(updateBenchmarkInstance, action.instance);
58 | yield put({ type: UPDATE_INSTANCE_SUCCESS, data });
59 | } catch (error) {
60 | yield put({ type: UPDATE_INSTANCE_ERROR, error });
61 | }
62 | }
63 |
64 | function* getInstance(action) {
65 | try {
66 | const data = yield call(getBenchmarkInstance, action.name);
67 | yield put({ type: GET_INSTANCE_SUCCESS, data });
68 | } catch (error) {
69 | yield put({ type: GET_INSTANCE_ERROR, error });
70 | }
71 | }
72 |
73 | function makeSaga(l, f) {
74 | return function* s() {
75 | const watcher = yield takeLatest(l, f);
76 |
77 | // Suspend execution until location changes
78 | yield take(LOCATION_CHANGE);
79 | yield cancel(watcher);
80 | };
81 | }
82 |
83 | // All sagas to be loaded
84 | export default [
85 | [LOAD_INSTANCES_REQUEST, loadInstances],
86 | [DELETE_INSTANCE_REQUEST, deleteInstance],
87 | [CREATE_INSTANCE_REQUEST, createInstance],
88 | [UPDATE_INSTANCE_REQUEST, updateInstance],
89 | [GET_INSTANCE_REQUEST, getInstance],
90 | ].map((d) => makeSaga(d[0], d[1]));
91 |
--------------------------------------------------------------------------------
/app/containers/BenchmarksPage/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * BenchmarksPage constants
4 | *
5 | */
6 |
7 | export const LOAD_CONFIGS_REQUEST = 'app/Benchmarks/LOAD_CONFIGS_REQUEST';
8 | export const LOAD_CONFIGS_SUCCESS = 'app/Benchmarks/LOAD_CONFIGS_SUCCESS';
9 | export const LOAD_CONFIGS_ERROR = 'app/Benchmarks/LOAD_CONFIGS_ERROR';
10 | export const CREATE_CONFIG_REQUEST = 'app/Benchmarks/CREATE_CONFIG_REQUEST';
11 | export const CREATE_CONFIG_SUCCESS = 'app/Benchmarks/CREATE_CONFIG_SUCCESS';
12 | export const CREATE_CONFIG_ERROR = 'app/Benchmarks/CREATE_CONFIG_ERROR';
13 | export const GET_CONFIG_REQUEST = 'app/Benchmarks/GET_CONFIG_REQUEST';
14 | export const GET_CONFIG_SUCCESS = 'app/Benchmarks/GET_CONFIG_SUCCESS';
15 | export const GET_CONFIG_ERROR = 'app/Benchmarks/GET_CONFIG_ERROR';
16 | export const DELETE_CONFIG_REQUEST = 'app/Benchmarks/DELETE_CONFIG_REQUEST';
17 | export const DELETE_CONFIG_SUCCESS = 'app/Benchmarks/DELETE_CONFIG_SUCCESS';
18 | export const DELETE_CONFIG_ERROR = 'app/Benchmarks/DELETE_CONFIG_ERROR';
19 | export const UPDATE_CONFIG_REQUEST = 'app/Benchmarks/UPDATE_CONFIG_REQUEST';
20 | export const UPDATE_CONFIG_SUCCESS = 'app/Benchmarks/UPDATE_CONFIG_SUCCESS';
21 | export const UPDATE_CONFIG_ERROR = 'app/Benchmarks/UPDATE_CONFIG_ERROR';
22 |
23 | export const LOAD_INSTANCES_REQUEST = 'app/Benchmarks/LOAD_INSTANCES_REQUEST';
24 | export const LOAD_INSTANCES_SUCCESS = 'app/Benchmarks/LOAD_INSTANCES_SUCCESS';
25 | export const LOAD_INSTANCES_ERROR = 'app/Benchmarks/LOAD_INSTANCES_ERROR';
26 | export const CREATE_INSTANCE_REQUEST = 'app/Benchmarks/CREATE_INSTANCE_REQUEST';
27 | export const CREATE_INSTANCE_SUCCESS = 'app/Benchmarks/CREATE_INSTANCE_SUCCESS';
28 | export const CREATE_INSTANCE_ERROR = 'app/Benchmarks/CREATE_INSTANCE_ERROR';
29 | export const GET_INSTANCE_REQUEST = 'app/Benchmarks/GET_INSTANCE_REQUEST';
30 | export const GET_INSTANCE_SUCCESS = 'app/Benchmarks/GET_INSTANCE_SUCCESS';
31 | export const GET_INSTANCE_ERROR = 'app/Benchmarks/GET_INSTANCE_ERROR';
32 | export const DELETE_INSTANCE_REQUEST = 'app/Benchmarks/DELETE_INSTANCE_REQUEST';
33 | export const DELETE_INSTANCE_SUCCESS = 'app/Benchmarks/DELETE_INSTANCE_SUCCESS';
34 | export const DELETE_INSTANCE_ERROR = 'app/Benchmarks/DELETE_INSTANCE_ERROR';
35 | export const UPDATE_INSTANCE_REQUEST = 'app/Benchmarks/UPDATE_INSTANCE_REQUEST';
36 | export const UPDATE_INSTANCE_SUCCESS = 'app/Benchmarks/UPDATE_INSTANCE_SUCCESS';
37 | export const UPDATE_INSTANCE_ERROR = 'app/Benchmarks/UPDATE_INSTANCE_ERROR';
38 |
--------------------------------------------------------------------------------
/app/containers/BenchmarksPage/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * BenchmarksPage
4 | *
5 | */
6 |
7 | import React, { PropTypes } from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import messages from './messages';
10 |
11 | export class BenchmarksPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
12 | render() {
13 | return (
14 |
15 |
16 |
17 |
18 | {React.Children.toArray(this.props.children)}
19 |
20 | );
21 | }
22 | }
23 |
24 | BenchmarksPage.propTypes = {
25 | children: PropTypes.node,
26 | };
27 |
28 | export default BenchmarksPage;
29 |
--------------------------------------------------------------------------------
/app/containers/BenchmarksPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * BenchmarksPage Messages
3 | *
4 | * This contains all the text for the BenchmarksPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.BenchmarksPage.header',
11 | defaultMessage: 'Benchmarks',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/BenchmarksPage/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | /**
3 | * Direct selector to the BenchmarksPage state domain
4 | */
5 | const selectBenchmarksPageDomain = () => (state) => state.get('benchmarks');
6 |
7 | const makeSelectConfigByName = (nameWrapper) => createSelector(
8 | selectBenchmarksPageDomain(),
9 | (substate) => {
10 | const name = nameWrapper[0];
11 | const config = substate.get('configs').find((c) => c.getIn(['metadata', 'name']) === name);
12 | if (config === undefined) {
13 | return undefined;
14 | }
15 | return config.toJS();
16 | }
17 | );
18 |
19 | const makeSelectInstanceByName = (nameWrapper) => createSelector(
20 | selectBenchmarksPageDomain(),
21 | (substate) => {
22 | const name = nameWrapper[0];
23 | const ins = substate.get('instances').find((instances) => instances.getIn(['metadata', 'name']) === name);
24 | if (ins === undefined) {
25 | return undefined;
26 | }
27 | return ins.toJS();
28 | }
29 | );
30 |
31 | const makeSelectLoading = () => createSelector(
32 | selectBenchmarksPageDomain(),
33 | (substate) => substate.get('loading')
34 | );
35 |
36 | const makeSelectError = () => createSelector(
37 | selectBenchmarksPageDomain(),
38 | (substate) => substate.get('error')
39 | );
40 |
41 | const makeSelectConfigs = () => createSelector(
42 | selectBenchmarksPageDomain(),
43 | (substate) => substate.get('configs').toJS(),
44 | );
45 |
46 | const makeSelectInstances = () => createSelector(
47 | selectBenchmarksPageDomain(),
48 | (substate) => substate.get('instances').toJS(),
49 | );
50 |
51 | const makeSelectInstancesByLabels = (labelsWrapper) => createSelector(
52 | selectBenchmarksPageDomain(),
53 | (substate) => {
54 | const labels = labelsWrapper[0];
55 | return substate.get('instances').filter((instance) => {
56 | const insLabels = instance.getIn(['metadata', 'labels']);
57 | return Object.keys(labels).map((k) => insLabels.get(k) === labels[k]).reduce((a, b) => a && b);
58 | }).toJS();
59 | }
60 | );
61 |
62 | export {
63 | makeSelectConfigs,
64 | makeSelectError,
65 | makeSelectLoading,
66 | makeSelectConfigByName,
67 | makeSelectInstances,
68 | makeSelectInstanceByName,
69 | makeSelectInstancesByLabels,
70 | };
71 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentCreatePage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * EnvironmentCreatePage actions
4 | *
5 | */
6 |
7 | import {
8 | CREATE_ENVIRONMENT_REQUEST,
9 | } from 'containers/EnvironmentsPage/constants';
10 |
11 | export function createEnvironmentAction(environment) {
12 | return {
13 | type: CREATE_ENVIRONMENT_REQUEST,
14 | environment,
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentCreatePage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * EnvironmentCreatePage Messages
3 | *
4 | * This contains all the text for the EnvironmentCreatePage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.EnvironmentCreatePage.header',
11 | defaultMessage: 'This is EnvironmentCreatePage container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentCreatePage/sagas.js:
--------------------------------------------------------------------------------
1 |
2 | import { takeLatest, call, put, take, cancel } from 'redux-saga/effects';
3 | import { LOCATION_CHANGE } from 'react-router-redux';
4 | import { createEnvironment } from 'utils/api';
5 | import { CREATE_ENVIRONMENT_REQUEST, CREATE_ENVIRONMENT_SUCCESS, CREATE_ENVIRONMENT_ERROR } from 'containers/EnvironmentsPage/constants';
6 | import { browserHistory } from 'react-router';
7 |
8 | // Individual exports for testing
9 | function* createEnvironmentSagaRequest(action) {
10 | try {
11 | const data = yield call(createEnvironment, action.environment);
12 | yield put({ type: CREATE_ENVIRONMENT_SUCCESS, data });
13 | browserHistory.push('/environments');
14 | } catch (error) {
15 | yield put({ type: CREATE_ENVIRONMENT_ERROR, error });
16 | }
17 | }
18 |
19 | // Individual exports for testing
20 | export function* defaultSaga() {
21 | // See example in containers/HomePage/sagas.js
22 | const watcher = yield takeLatest(CREATE_ENVIRONMENT_REQUEST, createEnvironmentSagaRequest);
23 |
24 | // Suspend execution until location changes
25 | yield take(LOCATION_CHANGE);
26 | yield cancel(watcher);
27 | }
28 |
29 | // All sagas to be loaded
30 | export default [
31 | defaultSaga,
32 | ];
33 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentEditPage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * EnvironmentEditPage actions
4 | *
5 | */
6 |
7 | import {
8 | GET_ENVIRONMENT_REQUEST,
9 | EDIT_ENVIRONMENT_REQUEST,
10 | } from 'containers/EnvironmentsPage/constants';
11 |
12 | export function getEnvironmentAction(name) {
13 | return {
14 | type: GET_ENVIRONMENT_REQUEST,
15 | name,
16 | };
17 | }
18 |
19 | export function editEnvironmentAction(environment) {
20 | return {
21 | type: EDIT_ENVIRONMENT_REQUEST,
22 | environment,
23 | };
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentEditPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * EnvironmentEditPage Messages
3 | *
4 | * This contains all the text for the EnvironmentEditPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.EnvironmentEditPage.header',
11 | defaultMessage: 'This is EnvironmentEditPage container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentEditPage/sagas.js:
--------------------------------------------------------------------------------
1 | import { takeLatest, call, put, take, cancel } from 'redux-saga/effects';
2 | import { LOCATION_CHANGE } from 'react-router-redux';
3 | import { getEnvironment, updateEnvironment } from 'utils/api';
4 | import { GET_ENVIRONMENT_REQUEST, GET_ENVIRONMENT_SUCCESS, GET_ENVIRONMENT_ERROR, EDIT_ENVIRONMENT_REQUEST, EDIT_ENVIRONMENT_SUCCESS, EDIT_ENVIRONMENT_ERROR } from 'containers/EnvironmentsPage/constants';
5 | import { browserHistory } from 'react-router';
6 |
7 | // Individual exports for testing
8 | function* getEnvironmentSagaRequest(action) {
9 | try {
10 | const data = yield call(getEnvironment, action.name);
11 | yield put({ type: GET_ENVIRONMENT_SUCCESS, data });
12 | } catch (error) {
13 | yield put({ type: GET_ENVIRONMENT_ERROR, error });
14 | }
15 | }
16 |
17 | function* editEnvironmentSagaRequest(action) {
18 | try {
19 | const data = yield call(updateEnvironment, action.environment);
20 | yield put({ type: EDIT_ENVIRONMENT_SUCCESS, data });
21 | browserHistory.push('/environments');
22 | } catch (error) {
23 | yield put({ type: EDIT_ENVIRONMENT_ERROR, error });
24 | }
25 | }
26 |
27 | // Individual exports for testing
28 | export function* getEnvironmentSaga() {
29 | // See example in containers/HomePage/sagas.js
30 | const watcher = yield takeLatest(GET_ENVIRONMENT_REQUEST, getEnvironmentSagaRequest);
31 |
32 | // Suspend execution until location changes
33 | yield take(LOCATION_CHANGE);
34 | yield cancel(watcher);
35 | }
36 |
37 | export function* editEnvironmentSaga() {
38 | // See example in containers/HomePage/sagas.js
39 | const watcher = yield takeLatest(EDIT_ENVIRONMENT_REQUEST, editEnvironmentSagaRequest);
40 |
41 | // Suspend execution until location changes
42 | yield take(LOCATION_CHANGE);
43 | yield cancel(watcher);
44 | }
45 |
46 | // All sagas to be loaded
47 | export default [
48 | getEnvironmentSaga,
49 | editEnvironmentSaga,
50 | ];
51 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsListPage/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * EnvironmentsListPage actions
4 | *
5 | */
6 |
7 | import {
8 | LOAD_ENVIRONMENTS_REQUEST,
9 | DELETE_ENVIRONMENT_REQUEST,
10 | } from 'containers/EnvironmentsPage/constants';
11 |
12 | export function removeEnvironmentAction(environment) {
13 | return {
14 | type: DELETE_ENVIRONMENT_REQUEST,
15 | environment,
16 | };
17 | }
18 | export function loadEnvironmentAction() {
19 | return {
20 | type: LOAD_ENVIRONMENTS_REQUEST,
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsListPage/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * EnvironmentsListPage
4 | *
5 | */
6 |
7 | import React, { PropTypes } from 'react';
8 | import { connect } from 'react-redux';
9 | import { FormattedMessage } from 'react-intl';
10 | import Helmet from 'react-helmet';
11 | import { Link } from 'react-router';
12 | import { createStructuredSelector } from 'reselect';
13 | import EnvironmentsList from 'components/EnvironmentsList';
14 | import { makeSelectEnvironments, makeSelectError, makeSelectLoading } from 'containers/EnvironmentsPage/selectors';
15 | import commonMessages from 'messages';
16 | import { loadEnvironmentAction, removeEnvironmentAction } from './actions';
17 |
18 | export class EnvironmentsListPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
19 | constructor() {
20 | super();
21 | this.onRemove = this.onRemove.bind(this);
22 | }
23 |
24 | componentDidMount() {
25 | this.props.loadEnvironmentData();
26 | }
27 |
28 | onRemove(environment) {
29 | this.props.removeEnvironment(environment);
30 | }
31 |
32 | render() {
33 | const { loading, error, environments } = this.props;
34 | const environmentsListProps = {
35 | loading,
36 | error,
37 | environments,
38 | };
39 | return (
40 |
41 |
44 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | EnvironmentsListPage.propTypes = {
52 | loading: PropTypes.bool,
53 | error: PropTypes.oneOfType([
54 | PropTypes.object,
55 | PropTypes.bool,
56 | ]),
57 | environments: PropTypes.oneOfType([
58 | PropTypes.object,
59 | PropTypes.array,
60 | ]),
61 | loadEnvironmentData: PropTypes.func,
62 | removeEnvironment: PropTypes.func,
63 | };
64 |
65 | const mapStateToProps = createStructuredSelector({
66 | environments: makeSelectEnvironments(),
67 | loading: makeSelectLoading(),
68 | error: makeSelectError(),
69 | });
70 |
71 | function mapDispatchToProps(dispatch) {
72 | return {
73 | loadEnvironmentData: () => dispatch(loadEnvironmentAction()),
74 | removeEnvironment: (environment) => dispatch(removeEnvironmentAction(environment)),
75 | };
76 | }
77 |
78 | export default connect(mapStateToProps, mapDispatchToProps)(EnvironmentsListPage);
79 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsListPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * EnvironmentsListPage Messages
3 | *
4 | * This contains all the text for the EnvironmentsListPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.EnvironmentsListPage.header',
11 | defaultMessage: 'This is EnvironmentsListPage container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsListPage/sagas.js:
--------------------------------------------------------------------------------
1 | import { takeLatest, call, put, take, cancel } from 'redux-saga/effects';
2 | import { getEnvironments, removeEnvironment } from 'utils/api';
3 | import { LOCATION_CHANGE } from 'react-router-redux';
4 | import { LOAD_ENVIRONMENTS_REQUEST, LOAD_ENVIRONMENTS_SUCCESS, LOAD_ENVIRONMENTS_ERROR, DELETE_ENVIRONMENT_REQUEST, DELETE_ENVIRONMENT_SUCCESS, DELETE_ENVIRONMENT_ERROR } from 'containers/EnvironmentsPage/constants';
5 |
6 | function* loadEnvironments() {
7 | try {
8 | const data = yield call(getEnvironments);
9 | yield put({ type: LOAD_ENVIRONMENTS_SUCCESS, data });
10 | } catch (error) {
11 | yield put({ type: LOAD_ENVIRONMENTS_ERROR, error });
12 | }
13 | }
14 |
15 | function* removeEnvironmentSaga(action) {
16 | try {
17 | yield call(removeEnvironment, action.environment);
18 | yield put({ type: DELETE_ENVIRONMENT_SUCCESS, environment: action.environment });
19 | } catch (error) {
20 | yield put({ type: DELETE_ENVIRONMENT_ERROR, error });
21 | }
22 | }
23 |
24 | // Individual exports for testing
25 | export function* getAllSaga() {
26 | const watcher = yield takeLatest(LOAD_ENVIRONMENTS_REQUEST, loadEnvironments);
27 |
28 | // Suspend execution until location changes
29 | yield take(LOCATION_CHANGE);
30 | yield cancel(watcher);
31 | }
32 | export function* removeSaga() {
33 | const watcher = yield takeLatest(DELETE_ENVIRONMENT_REQUEST, removeEnvironmentSaga);
34 |
35 | // Suspend execution until location changes
36 | yield take(LOCATION_CHANGE);
37 | yield cancel(watcher);
38 | }
39 |
40 | // All sagas to be loaded
41 | export default [
42 | getAllSaga,
43 | removeSaga,
44 | ];
45 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsPage/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * EnvironmentsPage constants
4 | *
5 | */
6 |
7 | export const DEFAULT_ACTION = 'app/EnvironmentsListPage/DEFAULT_ACTION';
8 | export const LOAD_ENVIRONMENTS_REQUEST = 'app/Environments/LOAD_ENVIRONMENTS_REQUEST';
9 | export const LOAD_ENVIRONMENTS_SUCCESS = 'app/Environments/LOAD_ENVIRONMENTS_SUCCESS';
10 | export const LOAD_ENVIRONMENTS_ERROR = 'app/Environments/LOAD_ENVIRONMENTS_ERROR';
11 | export const CREATE_ENVIRONMENT_REQUEST = 'app/Environments/CREATE_ENVIRONMENT_REQUEST';
12 | export const CREATE_ENVIRONMENT_SUCCESS = 'app/Environments/CREATE_ENVIRONMENT_SUCCESS';
13 | export const CREATE_ENVIRONMENT_ERROR = 'app/Environments/CREATE_ENVIRONMENT_ERROR';
14 | export const DELETE_ENVIRONMENT_REQUEST = 'app/Environments/DELETE_ENVIRONMENT_REQUEST';
15 | export const DELETE_ENVIRONMENT_SUCCESS = 'app/Environments/DELETE_ENVIRONMENT_SUCCESS';
16 | export const DELETE_ENVIRONMENT_ERROR = 'app/Environments/DELETE_ENVIRONMENT_ERROR';
17 | export const GET_ENVIRONMENT_REQUEST = 'app/Environments/GET_ENVIRONMENT_REQUEST';
18 | export const GET_ENVIRONMENT_SUCCESS = 'app/Environments/GET_ENVIRONMENT_SUCCESS';
19 | export const GET_ENVIRONMENT_ERROR = 'app/Environments/GET_ENVIRONMENT_ERROR';
20 | export const EDIT_ENVIRONMENT_REQUEST = 'app/Environments/EDIT_ENVIRONMENT_REQUEST';
21 | export const EDIT_ENVIRONMENT_SUCCESS = 'app/Environments/EDIT_ENVIRONMENT_SUCCESS';
22 | export const EDIT_ENVIRONMENT_ERROR = 'app/Environments/EDIT_ENVIRONMENT_ERROR';
23 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsPage/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * EnvironmentsPage
4 | *
5 | */
6 |
7 | import React, { PropTypes } from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import messages from './messages';
10 |
11 | export class EnvironmentsPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
12 |
13 | render() {
14 | return (
15 |
16 |
17 |
18 |
19 | {React.Children.toArray(this.props.children)}
20 |
21 | );
22 | }
23 | }
24 |
25 | EnvironmentsPage.propTypes = {
26 | children: PropTypes.node,
27 | };
28 |
29 |
30 | export default EnvironmentsPage;
31 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * EnvironmentsPage Messages
3 | *
4 | * This contains all the text for the EnvironmentsPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.EnvironmentsPage.header',
11 | defaultMessage: 'Environments',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsPage/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * EnvironmentsPage reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 | import {
9 | LOAD_ENVIRONMENTS_REQUEST,
10 | LOAD_ENVIRONMENTS_SUCCESS,
11 | LOAD_ENVIRONMENTS_ERROR,
12 | CREATE_ENVIRONMENT_REQUEST,
13 | CREATE_ENVIRONMENT_SUCCESS,
14 | CREATE_ENVIRONMENT_ERROR,
15 | DELETE_ENVIRONMENT_SUCCESS,
16 | GET_ENVIRONMENT_SUCCESS,
17 | GET_ENVIRONMENT_REQUEST,
18 | GET_ENVIRONMENT_ERROR,
19 | EDIT_ENVIRONMENT_SUCCESS,
20 | } from './constants';
21 |
22 | const initialState = fromJS({ environments: [], loading: false, error: false });
23 |
24 | function environmentsReducer(state = initialState, action) {
25 | switch (action.type) {
26 | case EDIT_ENVIRONMENT_SUCCESS:
27 | return state.set('environments', state.get('environments').map((env) =>
28 | env.getIn(['metadata', 'name']) === action.data.metadata.name ? fromJS(action.data) : env
29 | ));
30 | case GET_ENVIRONMENT_REQUEST:
31 | return state
32 | .set('loading', true)
33 | .set('error', false);
34 | case GET_ENVIRONMENT_ERROR:
35 | return state
36 | .set('error', fromJS(action.error))
37 | .set('loading', false);
38 | case GET_ENVIRONMENT_SUCCESS:
39 | return state
40 | .update('environments', (env) => env.push(fromJS(action.data)))
41 | .set('loading', false);
42 | case CREATE_ENVIRONMENT_REQUEST:
43 | return state
44 | .set('loading', true)
45 | .set('error', false);
46 | case CREATE_ENVIRONMENT_SUCCESS:
47 | return state
48 | .update('environments', (env) => env.push(fromJS(action.data)))
49 | .set('loading', false)
50 | .set('error', false);
51 | case CREATE_ENVIRONMENT_ERROR:
52 | return state
53 | .set('loading', false)
54 | .set('error', fromJS(action.error));
55 | case DELETE_ENVIRONMENT_SUCCESS:
56 | return state.set('environments', state.get('environments').filter((e) =>
57 | e.getIn(['metadata', 'name']) !== action.environment.name
58 | ));
59 | case LOAD_ENVIRONMENTS_REQUEST:
60 | return state
61 | .set('loading', true)
62 | .set('error', false)
63 | .set('environments', fromJS([]));
64 | case LOAD_ENVIRONMENTS_ERROR:
65 | return state
66 | .set('error', fromJS(action.error))
67 | .set('loading', false);
68 | case LOAD_ENVIRONMENTS_SUCCESS:
69 | return state
70 | .set('environments', fromJS(action.data))
71 | .set('loading', false);
72 | default:
73 | return state;
74 | }
75 | }
76 |
77 | export default environmentsReducer;
78 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsPage/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the environmentEditPage state domain
5 | */
6 | const selectEnvironmentsPageDomain = () => (state) => state.get('environments');
7 |
8 |
9 | const makeSelectEnvironmentByName = () => createSelector(
10 | selectEnvironmentsPageDomain(),
11 | (substate) => (environmentName) => {
12 | const environmentFound = substate.get('environments').find((environment) => environment.getIn(['metadata', 'name']) === environmentName);
13 | if (environmentFound) {
14 | return ({ name: environmentFound.getIn(['metadata', 'name']), image: environmentFound.get('runContainerImageUrl') });
15 | }
16 | return false;
17 | }
18 | );
19 |
20 | const makeSelectLoading = () => createSelector(
21 | selectEnvironmentsPageDomain(),
22 | (substate) => substate.get('loading')
23 | );
24 |
25 | const makeSelectError = () => createSelector(
26 | selectEnvironmentsPageDomain(),
27 | (substate) => substate.get('error')
28 | );
29 |
30 | const makeSelectEnvironments = () => createSelector(
31 | selectEnvironmentsPageDomain(),
32 | (substate) => substate.get('environments').map((e) => ({ name: e.getIn(['metadata', 'name']), image: e.get('runContainerImageUrl') })).toJS()
33 | );
34 |
35 |
36 | export {
37 | makeSelectEnvironmentByName,
38 | makeSelectEnvironments,
39 | makeSelectError,
40 | makeSelectLoading,
41 | };
42 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsPage/tests/reducer.test.js:
--------------------------------------------------------------------------------
1 |
2 | import { fromJS } from 'immutable';
3 | import environmentsPageReducer from '../reducer';
4 |
5 | describe('environmentsPageReducer', () => {
6 | it('returns the initial state', () => {
7 | expect(environmentsPageReducer(undefined, {})).toEqual(fromJS({ environments: [], loading: false, error: false }));
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/app/containers/EnvironmentsPage/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 | import {
3 | makeSelectEnvironmentByName,
4 | makeSelectEnvironments,
5 | makeSelectError,
6 | makeSelectLoading,
7 | } from '../selectors';
8 |
9 | const mockedState = fromJS({
10 | environments: {
11 | environments: [
12 | {
13 | metadata: {
14 | name: 'node',
15 | uid: 'a9b76a1f-ee1f-4177-83ee-2c9a016a5da3',
16 | },
17 | runContainerImageUrl: 'fission/node-env',
18 | },
19 | {
20 | metadata: {
21 | name: 'python',
22 | uid: '830e152b-771d-4cc5-8e3e-ea323b698aa7',
23 | },
24 | runContainerImageUrl: 'fission/python-env',
25 | },
26 | ],
27 | loading: false,
28 | error: false,
29 | },
30 | });
31 |
32 | describe('makeSelectEnvironmentByName', () => {
33 | it('should select the existed python environment', () => {
34 | expect(makeSelectEnvironmentByName()(mockedState)('python'))
35 | .toEqual({ image: 'fission/python-env', name: 'python' });
36 | });
37 | it('should return false as the non-existed php environment', () => {
38 | expect(makeSelectEnvironmentByName()(mockedState)('php'))
39 | .toEqual(false);
40 | });
41 | });
42 |
43 | describe('makeSelectEnvironments', () => {
44 | it('should select the environment list', () => {
45 | expect(makeSelectEnvironments()(mockedState))
46 | .toEqual([
47 | {
48 | name: 'node',
49 | image: 'fission/node-env',
50 | },
51 | {
52 | name: 'python',
53 | image: 'fission/python-env',
54 | },
55 | ]);
56 | });
57 | it('should select the empty environment list', () => {
58 | expect(makeSelectEnvironments()(mockedState.setIn(['environments', 'environments'], fromJS([]))))
59 | .toEqual([]);
60 | });
61 | });
62 |
63 | describe('makeSelectLoading', () => {
64 | it('should select the loading', () => {
65 | expect(makeSelectLoading()(mockedState))
66 | .toEqual(false);
67 | });
68 | });
69 |
70 | describe('makeSelectError', () => {
71 | it('should select the error', () => {
72 | expect(makeSelectError()(mockedState))
73 | .toEqual(false);
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/app/containers/FunctionCreatePage/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | CREATE_FUNCTION_REQUEST,
3 | TEST_FUNCTION_REQUEST,
4 | CLEAN_TEST_FUNCTION_REQUEST,
5 | } from 'containers/FunctionsPage/constants';
6 |
7 | export function createFunctionAction(fn) {
8 | return {
9 | type: CREATE_FUNCTION_REQUEST,
10 | fn,
11 | };
12 | }
13 |
14 | export function testFunctionAction(fn) {
15 | return {
16 | type: TEST_FUNCTION_REQUEST,
17 | fn,
18 | };
19 | }
20 |
21 | export function cleanTestFunctionAction() {
22 | return {
23 | type: CLEAN_TEST_FUNCTION_REQUEST,
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/app/containers/FunctionCreatePage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * FunctionCreatePage Messages
3 | *
4 | * This contains all the text for the FunctionCreatePage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.FunctionCreatePage.header',
11 | defaultMessage: 'This is FunctionCreatePage container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/FunctionCreatePage/sagas.js:
--------------------------------------------------------------------------------
1 | import { take, call, put, cancel, takeLatest } from 'redux-saga/effects';
2 | import { delay } from 'redux-saga';
3 | import v4 from 'uuid';
4 | import { LOCATION_CHANGE } from 'react-router-redux';
5 | import { browserHistory } from 'react-router';
6 | import { postFunction, restRequest, removeFunction } from 'utils/api';
7 | import {
8 | CREATE_FUNCTION_REQUEST,
9 | CREATE_FUNCTION_SUCCESS,
10 | CREATE_FUNCTION_ERROR,
11 | TEST_FUNCTION_REQUEST,
12 | TEST_FUNCTION_SUCCESS,
13 | TEST_FUNCTION_ERROR,
14 | } from 'containers/FunctionsPage/constants';
15 |
16 | function* createFunction(action) {
17 | try {
18 | yield call(postFunction, action.fn);
19 |
20 | yield put({ type: CREATE_FUNCTION_SUCCESS, data: action.fn });
21 |
22 | // TODO the following code works, but not sure it is the best solution
23 | browserHistory.push(`/functions/edit/${action.fn.name}`);
24 | } catch (error) {
25 | yield put({ type: CREATE_FUNCTION_ERROR, error });
26 | }
27 | }
28 | function* testFunction(action) {
29 | const { fn } = action;
30 | const { method, headers, params, body, draft } = fn.test;
31 | if (draft) {
32 | fn.name = v4();
33 | }
34 | const url = `/fission-function/${fn.name}`;
35 |
36 | try {
37 | if (draft) {
38 | yield call(postFunction, fn);
39 | yield delay(4 * 1000);
40 | }
41 | const data = yield call(restRequest, url, method, headers, params, body);
42 | if (draft) {
43 | yield call(removeFunction, fn);
44 | }
45 |
46 | yield put({ type: TEST_FUNCTION_SUCCESS, data });
47 | } catch (error) {
48 | yield put({ type: TEST_FUNCTION_ERROR, error });
49 | }
50 | }
51 |
52 | export function* createFunctionSaga() {
53 | const watcher = yield takeLatest(CREATE_FUNCTION_REQUEST, createFunction);
54 |
55 | // Suspend execution until location changes
56 | yield take(LOCATION_CHANGE);
57 | yield cancel(watcher);
58 | }
59 | export function* testFunctionSaga() {
60 | const watcher = yield takeLatest(TEST_FUNCTION_REQUEST, testFunction);
61 |
62 | // Suspend execution until location changes
63 | yield take(LOCATION_CHANGE);
64 | yield cancel(watcher);
65 | }
66 |
67 | // All sagas to be loaded
68 | export default [
69 | createFunctionSaga,
70 | testFunctionSaga,
71 | ];
72 |
--------------------------------------------------------------------------------
/app/containers/FunctionEditPage/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_FUNCTION_REQUEST,
3 | LOAD_TRIGGERSHTTP_REQUEST,
4 | DELETE_TRIGGERHTTP_REQUEST,
5 | UPDATE_FUNCTION_REQUEST,
6 | CREATE_TRIGGERHTTP_REQUEST,
7 | CREATE_FUNCTION_REQUEST,
8 | CREATE_KUBEWATCHER_REQUEST,
9 | DELETE_KUBEWATCHER_REQUEST,
10 | LOAD_KUBEWATCHERS_REQUEST,
11 | LOAD_TRIGGERSTIMER_REQUEST,
12 | DELETE_TRIGGERTIMER_REQUEST,
13 | CREATE_TRIGGERTIMER_REQUEST,
14 | LOAD_TRIGGERSMQ_REQUEST,
15 | DELETE_TRIGGERMQ_REQUEST,
16 | CREATE_TRIGGERMQ_REQUEST,
17 | } from 'containers/FunctionsPage/constants';
18 |
19 |
20 | export function getFunctionAction(name) {
21 | return {
22 | type: GET_FUNCTION_REQUEST,
23 | name,
24 | };
25 | }
26 |
27 | export function loadTriggersHttpAction() {
28 | return {
29 | type: LOAD_TRIGGERSHTTP_REQUEST,
30 | };
31 | }
32 |
33 | export function deleteTriggerHttpAction(trigger) {
34 | return {
35 | type: DELETE_TRIGGERHTTP_REQUEST,
36 | trigger,
37 | };
38 | }
39 |
40 | export function updateFunctionAction(fn) {
41 | return {
42 | type: UPDATE_FUNCTION_REQUEST,
43 | fn,
44 | };
45 | }
46 |
47 | export function createTriggerHttpAction(trigger) {
48 | return {
49 | type: CREATE_TRIGGERHTTP_REQUEST,
50 | trigger,
51 | };
52 | }
53 |
54 | export function createFunctionAction(fn) {
55 | return {
56 | type: CREATE_FUNCTION_REQUEST,
57 | fn,
58 | };
59 | }
60 |
61 | export function createKubeWatcherAction(watcher) {
62 | return {
63 | type: CREATE_KUBEWATCHER_REQUEST,
64 | watcher,
65 | };
66 | }
67 |
68 | export function deleteKubeWatcherAction(watcher) {
69 | return {
70 | type: DELETE_KUBEWATCHER_REQUEST,
71 | watcher,
72 | };
73 | }
74 |
75 | export function loadKubeWatchersAction() {
76 | return {
77 | type: LOAD_KUBEWATCHERS_REQUEST,
78 | };
79 | }
80 |
81 | export function createTriggerTimerAction(trigger) {
82 | return {
83 | type: CREATE_TRIGGERTIMER_REQUEST,
84 | trigger,
85 | };
86 | }
87 |
88 | export function deleteTriggerTimerAction(trigger) {
89 | return {
90 | type: DELETE_TRIGGERTIMER_REQUEST,
91 | trigger,
92 | };
93 | }
94 |
95 | export function loadTriggersTimerAction() {
96 | return {
97 | type: LOAD_TRIGGERSTIMER_REQUEST,
98 | };
99 | }
100 |
101 |
102 | export function createTriggerMQAction(trigger) {
103 | return {
104 | type: CREATE_TRIGGERMQ_REQUEST,
105 | trigger,
106 | };
107 | }
108 |
109 | export function deleteTriggerMQAction(trigger) {
110 | return {
111 | type: DELETE_TRIGGERMQ_REQUEST,
112 | trigger,
113 | };
114 | }
115 |
116 | export function loadTriggersMQAction() {
117 | return {
118 | type: LOAD_TRIGGERSMQ_REQUEST,
119 | };
120 | }
121 |
--------------------------------------------------------------------------------
/app/containers/FunctionEditPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * LocaleToggle Messages
3 | *
4 | * This contains all the text for the LanguageToggle component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | });
10 |
--------------------------------------------------------------------------------
/app/containers/FunctionListItem/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * EnvironmentsListItem Messages
3 | *
4 | * This contains all the text for the EnvironmentsListItem component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | httptriggers: {
10 | id: 'app.containers.FunctionListItem.httptriggers',
11 | defaultMessage: 'Http Triggers',
12 | },
13 | kubewatchers: {
14 | id: 'app.containers.FunctionListItem.kubewatchers',
15 | defaultMessage: 'Kube Watchers',
16 | },
17 | timertriggers: {
18 | id: 'app.containers.FunctionListItem.timertriggers',
19 | defaultMessage: 'Timer Triggers',
20 | },
21 | mqtriggers: {
22 | id: 'app.containers.FunctionListItem.mqtriggers',
23 | defaultMessage: 'MQ Triggers',
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/app/containers/FunctionUploadPage/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | UPLOAD_FUNCTIONS_IN_BATCH_REQUEST,
3 | SET_UPLOAD_FUNCTIONS,
4 | } from 'containers/FunctionsPage/constants';
5 |
6 | export function uploadFunctionsInBatchAction(fns, isCreate) {
7 | return {
8 | type: UPLOAD_FUNCTIONS_IN_BATCH_REQUEST,
9 | fns,
10 | isCreate,
11 | };
12 | }
13 |
14 | export function setUploadFunctionsAction(fns) {
15 | return {
16 | type: SET_UPLOAD_FUNCTIONS,
17 | data: fns,
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/app/containers/FunctionUploadPage/listItem.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * FunctionUploadListItem
4 | *
5 | */
6 |
7 | import React, { PropTypes } from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import { Link } from 'react-router';
10 | import commonMessages from 'messages';
11 |
12 | export class ListItem extends React.Component { // eslint-disable-line react/prefer-stateless-function
13 | render() {
14 | const { item, onRemove } = this.props;
15 | return (
16 |
17 | { item.name }
18 | { item.environment }
19 |
20 |
21 | { item.errors.length > 0 &&
22 | (
23 | {
24 | item.errors.map((e, idx) => {e} )
25 | }
26 | )
27 | }
28 |
29 |
30 | {
31 | item.status === 'uploaded' &&
32 |
33 | }
34 |
35 |
36 |
37 | );
38 | }
39 | }
40 |
41 | ListItem.propTypes = {
42 | item: PropTypes.object,
43 | onRemove: PropTypes.func,
44 | };
45 |
46 | export default ListItem;
47 |
--------------------------------------------------------------------------------
/app/containers/FunctionUploadPage/sagas.js:
--------------------------------------------------------------------------------
1 | import { take, call, put, cancel, takeLatest } from 'redux-saga/effects';
2 | import { putFunction, postFunction } from 'utils/api';
3 | import {
4 | UPLOAD_FUNCTIONS_IN_BATCH_REQUEST,
5 | UPLOAD_SINGLE_FUNCTION_IN_BATCH_PROGRESS,
6 | UPLOAD_SINGLE_FUNCTION_IN_BATCH_ERROR,
7 | } from 'containers/FunctionsPage/constants';
8 | import { LOCATION_CHANGE } from 'react-router-redux';
9 |
10 | function* uploadFunctions(action) {
11 | const apiFunction = action.isCreate ? postFunction : putFunction;
12 | for (let i = 0; i < action.fns.length; i += 1) {
13 | const fn = action.fns[i];
14 | try {
15 | fn.status = 'processing';
16 | yield put({ type: UPLOAD_SINGLE_FUNCTION_IN_BATCH_PROGRESS, data: Object.assign({}, fn) });
17 | yield call(apiFunction, fn);
18 | fn.status = 'uploaded';
19 | yield put({ type: UPLOAD_SINGLE_FUNCTION_IN_BATCH_PROGRESS, data: Object.assign({}, fn) });
20 | } catch (error) {
21 | fn.errors = error.response ? [error.response.data] : [JSON.stringify(error)];
22 | fn.status = 'failed';
23 | yield put({ type: UPLOAD_SINGLE_FUNCTION_IN_BATCH_ERROR, data: Object.assign({}, fn) });
24 | }
25 | }
26 | }
27 |
28 | export function* uploadFunctionsSaga() {
29 | const watcher = yield takeLatest(UPLOAD_FUNCTIONS_IN_BATCH_REQUEST, uploadFunctions);
30 |
31 | // Suspend execution until location changes
32 | yield take(LOCATION_CHANGE);
33 | yield cancel(watcher);
34 | }
35 |
36 | // All sagas to be loaded
37 | export default [
38 | uploadFunctionsSaga,
39 | ];
40 |
--------------------------------------------------------------------------------
/app/containers/FunctionsListPage/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | LOAD_FUNCTIONS_REQUEST,
3 | LOAD_TRIGGERSHTTP_REQUEST,
4 | LOAD_TRIGGERSTIMER_REQUEST,
5 | LOAD_TRIGGERSMQ_REQUEST,
6 | DELETE_FUNCTION_REQUEST,
7 | LOAD_KUBEWATCHERS_REQUEST,
8 | } from 'containers/FunctionsPage/constants';
9 |
10 |
11 | export function loadFunctionAction() {
12 | return {
13 | type: LOAD_FUNCTIONS_REQUEST,
14 | };
15 | }
16 |
17 | export function loadTriggersHttpAction() {
18 | return {
19 | type: LOAD_TRIGGERSHTTP_REQUEST,
20 | };
21 | }
22 |
23 | export function loadTriggersTimerAction() {
24 | return {
25 | type: LOAD_TRIGGERSTIMER_REQUEST,
26 | };
27 | }
28 |
29 | export function loadTriggersMQAction() {
30 | return {
31 | type: LOAD_TRIGGERSMQ_REQUEST,
32 | };
33 | }
34 |
35 | export function loadKubeWatchersAction() {
36 | return {
37 | type: LOAD_KUBEWATCHERS_REQUEST,
38 | };
39 | }
40 |
41 | export function deleteFunctionAction(fn) {
42 | return {
43 | type: DELETE_FUNCTION_REQUEST,
44 | fn,
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/app/containers/FunctionsListPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * FunctionsListPage Messages
3 | *
4 | * This contains all the text for the FunctionsListPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.FunctionsListPage.header',
11 | defaultMessage: 'This is FunctionsListPage container !',
12 | },
13 | functionDeleteRelatedTriggers: {
14 | id: 'app.containers.FunctionsPage.function.delete.triggers',
15 | defaultMessage: 'The function has related triggers, delete these triggers?',
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/app/containers/FunctionsPage/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * FunctionsPage
4 | *
5 | */
6 |
7 | import React, { PropTypes } from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import messages from './messages';
10 |
11 | export class FunctionsPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
12 | render() {
13 | return (
14 |
15 |
16 |
17 |
18 | {React.Children.toArray(this.props.children)}
19 |
20 | );
21 | }
22 | }
23 |
24 | FunctionsPage.propTypes = {
25 | children: PropTypes.node,
26 | };
27 |
28 | export default FunctionsPage;
29 |
--------------------------------------------------------------------------------
/app/containers/FunctionsPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * FunctionsPage Messages
3 | *
4 | * This contains all the text for the FunctionsPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.FunctionsPage.header',
11 | defaultMessage: 'Functions',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/FunctionsPage/tests/reducer.test.js:
--------------------------------------------------------------------------------
1 |
2 | describe('functionsPageReducer', () => {
3 | it('returns the initial state', () => {
4 | expect(true).toEqual(true);
5 | });
6 |
7 | // TODO add test for some actions of function page reducer
8 | });
9 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider actions
4 | *
5 | */
6 |
7 | import {
8 | CHANGE_LOCALE,
9 | } from './constants';
10 |
11 | export function changeLocale(languageLocale) {
12 | return {
13 | type: CHANGE_LOCALE,
14 | locale: languageLocale,
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider constants
4 | *
5 | */
6 |
7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE';
8 | export const DEFAULT_LOCALE = 'en';
9 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider
4 | *
5 | * this component connects the redux state language locale to the
6 | * IntlProvider component and i18n messages (loaded from `app/translations`)
7 | */
8 |
9 | import React from 'react';
10 | import { connect } from 'react-redux';
11 | import { createSelector } from 'reselect';
12 | import { IntlProvider } from 'react-intl';
13 |
14 | import { makeSelectLocale } from './selectors';
15 |
16 | export class LanguageProvider extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
17 | render() {
18 | return (
19 |
20 | {React.Children.only(this.props.children)}
21 |
22 | );
23 | }
24 | }
25 |
26 | LanguageProvider.propTypes = {
27 | locale: React.PropTypes.string,
28 | messages: React.PropTypes.object,
29 | children: React.PropTypes.element.isRequired,
30 | };
31 |
32 |
33 | const mapStateToProps = createSelector(
34 | makeSelectLocale(),
35 | (locale) => ({ locale })
36 | );
37 |
38 | function mapDispatchToProps(dispatch) {
39 | return {
40 | dispatch,
41 | };
42 | }
43 |
44 | export default connect(mapStateToProps, mapDispatchToProps)(LanguageProvider);
45 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 |
9 | import {
10 | CHANGE_LOCALE,
11 | } from './constants';
12 | import {
13 | DEFAULT_LOCALE,
14 | } from '../App/constants'; // eslint-disable-line
15 |
16 | const initialState = fromJS({
17 | locale: DEFAULT_LOCALE,
18 | });
19 |
20 | function languageProviderReducer(state = initialState, action) {
21 | switch (action.type) {
22 | case CHANGE_LOCALE:
23 | return state
24 | .set('locale', action.locale);
25 | default:
26 | return state;
27 | }
28 | }
29 |
30 | export default languageProviderReducer;
31 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the languageToggle state domain
5 | */
6 | const selectLanguage = (state) => state.get('language');
7 |
8 | /**
9 | * Select the language locale
10 | */
11 |
12 | const makeSelectLocale = () => createSelector(
13 | selectLanguage,
14 | (languageState) => languageState.get('locale')
15 | );
16 |
17 | export {
18 | selectLanguage,
19 | makeSelectLocale,
20 | };
21 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/actions.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | changeLocale,
3 | } from '../actions';
4 |
5 | import {
6 | CHANGE_LOCALE,
7 | } from '../constants';
8 |
9 | describe('LanguageProvider actions', () => {
10 | describe('Change Local Action', () => {
11 | it('has a type of CHANGE_LOCALE', () => {
12 | const expected = {
13 | type: CHANGE_LOCALE,
14 | locale: 'de',
15 | };
16 | expect(changeLocale('de')).toEqual(expected);
17 | });
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow, mount } from 'enzyme';
3 | import { FormattedMessage, defineMessages } from 'react-intl';
4 | import { Provider } from 'react-redux';
5 | import { browserHistory } from 'react-router';
6 |
7 | import ConnectedLanguageProvider, { LanguageProvider } from '../index';
8 | import configureStore from '../../../store';
9 |
10 | import { translationMessages } from '../../../i18n';
11 |
12 | const messages = defineMessages({
13 | someMessage: {
14 | id: 'some.id',
15 | defaultMessage: 'This is some default message',
16 | en: 'This is some en message',
17 | },
18 | });
19 |
20 | describe('
', () => {
21 | it('should render its children', () => {
22 | const children = (
Test );
23 | const renderedComponent = shallow(
24 |
25 | {children}
26 |
27 | );
28 | expect(renderedComponent.contains(children)).toBe(true);
29 | });
30 | });
31 |
32 | describe('
', () => {
33 | let store;
34 |
35 | beforeAll(() => {
36 | store = configureStore({}, browserHistory);
37 | });
38 |
39 | it('should render the default language messages', () => {
40 | const renderedComponent = mount(
41 |
42 |
43 |
44 |
45 |
46 | );
47 | expect(renderedComponent.contains(
)).toBe(true);
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/reducer.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import languageProviderReducer from '../reducer';
4 | import {
5 | CHANGE_LOCALE,
6 | } from '../constants';
7 |
8 | describe('languageProviderReducer', () => {
9 | it('returns the initial state', () => {
10 | expect(languageProviderReducer(undefined, {})).toEqual(fromJS({
11 | locale: 'en',
12 | }));
13 | });
14 |
15 | it('changes the locale', () => {
16 | expect(languageProviderReducer(undefined, { type: CHANGE_LOCALE, locale: 'de' }).toJS()).toEqual({
17 | locale: 'de',
18 | });
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import {
4 | selectLanguage,
5 | } from '../selectors';
6 |
7 | describe('selectLanguage', () => {
8 | it('should select the global state', () => {
9 | const globalState = fromJS({});
10 | const mockedState = fromJS({
11 | language: globalState,
12 | });
13 | expect(selectLanguage(mockedState)).toEqual(globalState);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/Wrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Wrapper = styled.div`
4 | padding: 2px;
5 | `;
6 |
7 | export default Wrapper;
8 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageToggle
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { connect } from 'react-redux';
9 | import { createSelector } from 'reselect';
10 |
11 | import Toggle from 'components/Toggle';
12 | import Wrapper from './Wrapper';
13 | import messages from './messages';
14 | import { appLocales } from '../../i18n';
15 | import { changeLocale } from '../LanguageProvider/actions';
16 | import { makeSelectLocale } from '../LanguageProvider/selectors';
17 |
18 | export class LocaleToggle extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
19 | render() {
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | LocaleToggle.propTypes = {
29 | onLocaleToggle: React.PropTypes.func,
30 | locale: React.PropTypes.string,
31 | };
32 |
33 | const mapStateToProps = createSelector(
34 | makeSelectLocale(),
35 | (locale) => ({ locale })
36 | );
37 |
38 | export function mapDispatchToProps(dispatch) {
39 | return {
40 | onLocaleToggle: (evt) => dispatch(changeLocale(evt.target.value)),
41 | dispatch,
42 | };
43 | }
44 |
45 | export default connect(mapStateToProps, mapDispatchToProps)(LocaleToggle);
46 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * LocaleToggle Messages
3 | *
4 | * This contains all the text for the LanguageToggle component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | en: {
10 | id: 'boilerplate.containers.LocaleToggle.en',
11 | defaultMessage: 'en',
12 | },
13 | zh: {
14 | id: 'boilerplate.containers.LocaleToggle.zh',
15 | defaultMessage: 'zh',
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/tests/Wrapper.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Wrapper from '../Wrapper';
5 |
6 | describe('
', () => {
7 | it('should render an
tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('div');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import { browserHistory } from 'react-router';
4 | import { shallow, mount } from 'enzyme';
5 |
6 | import LocaleToggle, { mapDispatchToProps } from '../index';
7 | import { changeLocale } from '../../LanguageProvider/actions';
8 | import LanguageProvider from '../../LanguageProvider';
9 |
10 | import configureStore from '../../../store';
11 | import { translationMessages } from '../../../i18n';
12 |
13 | describe('
', () => {
14 | let store;
15 |
16 | beforeAll(() => {
17 | store = configureStore({}, browserHistory);
18 | });
19 |
20 | it('should render the default language messages', () => {
21 | const renderedComponent = shallow(
22 |
23 |
24 |
25 |
26 |
27 | );
28 | expect(renderedComponent.contains(
)).toBe(true);
29 | });
30 |
31 | it('should present the default `en` english language option', () => {
32 | const renderedComponent = mount(
33 |
34 |
35 |
36 |
37 |
38 | );
39 | expect(renderedComponent.contains(
en )).toBe(true);
40 | });
41 |
42 | describe('mapDispatchToProps', () => {
43 | describe('onLocaleToggle', () => {
44 | it('should be injected', () => {
45 | const dispatch = jest.fn();
46 | const result = mapDispatchToProps(dispatch);
47 | expect(result.onLocaleToggle).toBeDefined();
48 | });
49 |
50 | it('should dispatch changeLocale when called', () => {
51 | const dispatch = jest.fn();
52 | const result = mapDispatchToProps(dispatch);
53 | const locale = 'de';
54 | const evt = { target: { value: locale } };
55 | result.onLocaleToggle(evt);
56 | expect(dispatch).toHaveBeenCalledWith(changeLocale(locale));
57 | });
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NotFoundPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have a route
5 | *
6 | * NOTE: while this component should technically be a stateless functional
7 | * component (SFC), hot reloading does not currently support SFCs. If hot
8 | * reloading is not a necessity for you then you can refactor it and remove
9 | * the linting exception.
10 | */
11 |
12 | import React from 'react';
13 | import { FormattedMessage } from 'react-intl';
14 |
15 | import messages from './messages';
16 |
17 | export default class NotFound extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
18 | render() {
19 | return (
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * NotFoundPage Messages
3 | *
4 | * This contains all the text for the NotFoundPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.NotFoundPage.header',
11 | defaultMessage: 'This is NotFoundPage component!',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import { shallow } from 'enzyme';
4 |
5 | import NotFoundPage from '../index';
6 | import messages from '../messages';
7 |
8 | describe('
', () => {
9 | it('should render the page message', () => {
10 | const renderedComponent = shallow(
11 |
12 | );
13 | expect(renderedComponent.contains(
14 |
15 | )).toEqual(true);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fission/fission-ui/d14ef875f905f9f33e6f76489a7f6bbfd562b0ff/app/favicon.ico
--------------------------------------------------------------------------------
/app/global-styles.js:
--------------------------------------------------------------------------------
1 | import { injectGlobal } from 'styled-components';
2 |
3 | /* eslint no-unused-expressions: 0 */
4 | injectGlobal`
5 | body {
6 | padding-top: 20px;
7 | padding-bottom: 20px;
8 | }
9 |
10 | .navbar {
11 | margin-bottom: 20px;
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/app/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * i18n.js
3 | *
4 | * This will setup the i18n language files and locale data for your app.
5 | *
6 | */
7 | import { addLocaleData } from 'react-intl';
8 | import enLocaleData from 'react-intl/locale-data/en';
9 | import zhLocaleData from 'react-intl/locale-data/zh';
10 | import frLocaleData from 'react-intl/locale-data/fr';
11 |
12 | import { DEFAULT_LOCALE } from './containers/App/constants'; // eslint-disable-line
13 | import enTranslationMessages from './translations/en.json';
14 | import zhTranslationMessages from './translations/zh.json';
15 | import frTranslationMessages from './translations/fr.json';
16 |
17 | export const appLocales = [
18 | 'en',
19 | 'zh',
20 | 'fr',
21 | ];
22 |
23 | addLocaleData(enLocaleData);
24 | addLocaleData(zhLocaleData);
25 | addLocaleData(frLocaleData);
26 |
27 | export const formatTranslationMessages = (locale, messages) => {
28 | const defaultFormattedMessages = locale !== DEFAULT_LOCALE
29 | ? formatTranslationMessages(DEFAULT_LOCALE, enTranslationMessages)
30 | : {};
31 | return Object.keys(messages).reduce((formattedMessages, key) => {
32 | let message = messages[key];
33 | if (!message && locale !== DEFAULT_LOCALE) {
34 | message = defaultFormattedMessages[key];
35 | }
36 | return Object.assign(formattedMessages, { [key]: message });
37 | }, {});
38 | };
39 |
40 | export const translationMessages = {
41 | en: formatTranslationMessages('en', enTranslationMessages),
42 | zh: formatTranslationMessages('zh', zhTranslationMessages),
43 | fr: formatTranslationMessages('fr', frTranslationMessages),
44 | };
45 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Fission UI
12 |
13 |
14 |
15 |
If you're seeing this message, that means JavaScript has been disabled on your browser , please enable JS to make this app work.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Fission UI",
3 | "icons": [
4 | {
5 | "src": "favicon.png",
6 | "sizes": "48x48",
7 | "type": "image/png",
8 | "density": 1.0
9 | },
10 | {
11 | "src": "favicon.png",
12 | "sizes": "96x96",
13 | "type": "image/png",
14 | "density": 2.0
15 | },
16 | {
17 | "src": "favicon.png",
18 | "sizes": "144x144",
19 | "type": "image/png",
20 | "density": 3.0
21 | },
22 | {
23 | "src": "favicon.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "density": 4.0
27 | }
28 | ],
29 | "start_url": "index.html",
30 | "display": "standalone",
31 | "orientation": "portrait",
32 | "background_color": "#FFFFFF"
33 | }
34 |
--------------------------------------------------------------------------------
/app/reducers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Combine all reducers in this file and export the combined reducers.
3 | * If we were to do this in store.js, reducers wouldn't be hot reloadable.
4 | */
5 |
6 | import { combineReducers } from 'redux-immutable';
7 | import { fromJS } from 'immutable';
8 | import { LOCATION_CHANGE } from 'react-router-redux';
9 |
10 | import languageProviderReducer from 'containers/LanguageProvider/reducer';
11 |
12 | /*
13 | * routeReducer
14 | *
15 | * The reducer merges route location changes into our immutable state.
16 | * The change is necessitated by moving to react-router-redux@4
17 | *
18 | */
19 |
20 | // Initial routing state
21 | const routeInitialState = fromJS({
22 | locationBeforeTransitions: null,
23 | });
24 |
25 | /**
26 | * Merge route into the global application state
27 | */
28 | function routeReducer(state = routeInitialState, action) {
29 | switch (action.type) {
30 | /* istanbul ignore next */
31 | case LOCATION_CHANGE:
32 | return state.merge({
33 | locationBeforeTransitions: action.payload,
34 | });
35 | default:
36 | return state;
37 | }
38 | }
39 |
40 | /**
41 | * Creates the main reducer with the asynchronously loaded ones
42 | */
43 | export default function createReducer(asyncReducers) {
44 | return combineReducers({
45 | route: routeReducer,
46 | language: languageProviderReducer,
47 | ...asyncReducers,
48 | });
49 | }
50 |
--------------------------------------------------------------------------------
/app/store.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create the store with asynchronously loaded reducers
3 | */
4 |
5 | import { createStore, applyMiddleware, compose } from 'redux';
6 | import { fromJS } from 'immutable';
7 | import { routerMiddleware } from 'react-router-redux';
8 | import createSagaMiddleware from 'redux-saga';
9 | import createReducer from './reducers';
10 |
11 | const sagaMiddleware = createSagaMiddleware();
12 |
13 | export default function configureStore(initialState = {}, history) {
14 | // Create the store with two middlewares
15 | // 1. sagaMiddleware: Makes redux-sagas work
16 | // 2. routerMiddleware: Syncs the location/URL path to the state
17 | const middlewares = [
18 | sagaMiddleware,
19 | routerMiddleware(history),
20 | ];
21 |
22 | const enhancers = [
23 | applyMiddleware(...middlewares),
24 | ];
25 |
26 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose
27 | /* eslint-disable no-underscore-dangle */
28 | const composeEnhancers =
29 | process.env.NODE_ENV !== 'production' &&
30 | typeof window === 'object' &&
31 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
32 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
33 | /* eslint-enable */
34 |
35 | const store = createStore(
36 | createReducer(),
37 | fromJS(initialState),
38 | composeEnhancers(...enhancers)
39 | );
40 |
41 | // Extensions
42 | store.runSaga = sagaMiddleware.run;
43 | store.asyncReducers = {}; // Async reducer registry
44 |
45 | // Make reducers hot reloadable, see http://mxs.is/googmo
46 | /* istanbul ignore next */
47 | if (module.hot) {
48 | module.hot.accept('./reducers', () => {
49 | import('./reducers').then((reducerModule) => {
50 | const createReducers = reducerModule.default;
51 | const nextReducers = createReducers(store.asyncReducers);
52 |
53 | store.replaceReducer(nextReducers);
54 | });
55 | });
56 | }
57 |
58 | return store;
59 | }
60 |
--------------------------------------------------------------------------------
/app/tests/i18n.test.js:
--------------------------------------------------------------------------------
1 | import { DEFAULT_LOCALE } from '../containers/App/constants';
2 | import { formatTranslationMessages } from '../i18n';
3 |
4 | jest.mock('../translations/en.json', () => (
5 | {
6 | message1: 'default message',
7 | message2: 'default message 2',
8 | }
9 | ));
10 |
11 | const esTranslationMessages = {
12 | message1: 'mensaje predeterminado',
13 | message2: '',
14 | };
15 |
16 | describe('formatTranslationMessages', () => {
17 | it('should build only defaults when DEFAULT_LOCALE', () => {
18 | const result = formatTranslationMessages(DEFAULT_LOCALE, { a: 'a' });
19 |
20 | expect(result).toEqual({ a: 'a' });
21 | });
22 |
23 |
24 | it('should combine default locale and current locale when not DEFAULT_LOCALE', () => {
25 | const result = formatTranslationMessages('', esTranslationMessages);
26 |
27 | expect(result).toEqual({
28 | message1: 'mensaje predeterminado',
29 | message2: 'default message 2',
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/app/tests/store.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test store addons
3 | */
4 |
5 | import { browserHistory } from 'react-router';
6 | import configureStore from '../store';
7 |
8 | describe('configureStore', () => {
9 | let store;
10 |
11 | beforeAll(() => {
12 | store = configureStore({}, browserHistory);
13 | });
14 |
15 | describe('asyncReducers', () => {
16 | it('should contain an object for async reducers', () => {
17 | expect(typeof store.asyncReducers).toBe('object');
18 | });
19 | });
20 |
21 | describe('runSaga', () => {
22 | it('should contain a hook for `sagaMiddleware.run`', () => {
23 | expect(typeof store.runSaga).toBe('function');
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/app/translations/zh.json:
--------------------------------------------------------------------------------
1 | {
2 | "app.components.NotFoundPage.header": "抱歉,没有这个页面",
3 |
4 | "boilerplate.containers.LocaleToggle.en": "en",
5 | "boilerplate.containers.LocaleToggle.zh": "中文",
6 | "boilerplate.containers.LocaleToggle.fr": "fr",
7 |
8 | "app.messages.deploy": "部署",
9 | "app.messages.save": "保存",
10 | "app.messages.cancel": "取消",
11 | "app.messages.edit": "编辑",
12 | "app.messages.delete": "删除",
13 | "app.messages.add": "创建",
14 | "app.messages.method": "方法",
15 | "app.messages.path": "路径",
16 | "app.messages.action": "操作",
17 | "app.messages.namespace": "命名空间",
18 | "app.messages.objtype": "对象类型",
19 | "app.messages.labelselector": "标签选择器",
20 | "app.messages.functionName": "函数名称",
21 | "app.messages.environment": "环境",
22 | "app.messages.syntax": "语言",
23 | "app.messages.name": "名称",
24 | "app.messages.trigger": "触发器",
25 | "app.messages.dockerImage": "Docker镜像",
26 | "app.messages.environmentName": "环境名称",
27 | "app.messages.function": "函数",
28 | "app.messages.chooseSample": "从样例选择",
29 | "app.messages.test": "测试",
30 | "app.messages.draft": "草稿模式",
31 | "app.messages.copyEndpoint": "拷贝链接",
32 | "app.messages.language": "语言",
33 | "app.messages.history": "历史",
34 | "app.messages.clearHistory": "清空历史",
35 | "app.messages.inputError.needName": "请输入名称",
36 | "app.messages.inputError.needCode": "请输入代码",
37 | "app.messages.inputError.needEnvironment": "请指明环境",
38 | "app.messages.inputError.needDockerImage": "请指明Docker镜像",
39 | "app.messages.inputError.inputErrorNeedExtension": "请指明一个扩展名(php, js...)",
40 | "app.messages.batchUpload": "批量上传",
41 | "app.messages.dropFilesHere": "将文件拖拽至此",
42 | "app.messages.update": "更新",
43 | "app.messages.create": "创建",
44 | "app.messages.fileExt": "文件扩展名",
45 | "app.messages.upload": "上传",
46 | "app.messages.status": "状态",
47 | "app.messages.deleteUploaded": "删除已上传的",
48 | "app.messages.chooseFunctionFiles": "选择函数文件",
49 | "app.messages.uploaded": "已上传",
50 | "app.messages.processing": "处理中",
51 | "app.messages.pending": "等待中",
52 | "app.messages.failed": "失败",
53 | "app.messages.filter": "过滤器",
54 | "app.messages.response": "响应",
55 | "app.messages.noFunctionFiles": "请选择至少一个文件",
56 |
57 | "app.containers.FunctionsListPage.header": "函数列表",
58 | "app.containers.FunctionsPage.header": "函数",
59 | "app.containers.FunctionsPage.function.delete.triggers": "此函数有关联的触发器,同时删除这些触发器吗?",
60 |
61 | "app.containers.FunctionListItem.httptriggers": "Http 触发器",
62 | "app.containers.FunctionListItem.kubewatchers": "Kube 监视器",
63 |
64 | "app.containers.EnvironmentsPage.header": "环境",
65 |
66 | "app.components.TriggerHttpForm.headerhttptrigger": "Http 触发器",
67 | "app.components.KubeWatcherForm.headerkubewatcher": "Kube 监视器",
68 |
69 | "dummy": ""
70 | }
71 |
--------------------------------------------------------------------------------
/app/utils/asyncInjectors.js:
--------------------------------------------------------------------------------
1 | import conformsTo from 'lodash/conformsTo';
2 | import isEmpty from 'lodash/isEmpty';
3 | import isFunction from 'lodash/isFunction';
4 | import isObject from 'lodash/isObject';
5 | import isString from 'lodash/isString';
6 | import invariant from 'invariant';
7 | import warning from 'warning';
8 | import createReducer from 'reducers';
9 |
10 | /**
11 | * Validate the shape of redux store
12 | */
13 | export function checkStore(store) {
14 | const shape = {
15 | dispatch: isFunction,
16 | subscribe: isFunction,
17 | getState: isFunction,
18 | replaceReducer: isFunction,
19 | runSaga: isFunction,
20 | asyncReducers: isObject,
21 | };
22 | invariant(
23 | conformsTo(store, shape),
24 | '(app/utils...) asyncInjectors: Expected a valid redux store'
25 | );
26 | }
27 |
28 | /**
29 | * Inject an asynchronously loaded reducer
30 | */
31 | export function injectAsyncReducer(store, isValid) {
32 | return function injectReducer(name, asyncReducer) {
33 | if (!isValid) checkStore(store);
34 |
35 | invariant(
36 | isString(name) && !isEmpty(name) && isFunction(asyncReducer),
37 | '(app/utils...) injectAsyncReducer: Expected `asyncReducer` to be a reducer function'
38 | );
39 |
40 | if (Reflect.has(store.asyncReducers, name)) return;
41 |
42 | store.asyncReducers[name] = asyncReducer; // eslint-disable-line no-param-reassign
43 | store.replaceReducer(createReducer(store.asyncReducers));
44 | };
45 | }
46 |
47 | /**
48 | * Inject an asynchronously loaded saga
49 | */
50 | export function injectAsyncSagas(store, isValid) {
51 | return function injectSagas(sagas) {
52 | if (!isValid) checkStore(store);
53 |
54 | invariant(
55 | Array.isArray(sagas),
56 | '(app/utils...) injectAsyncSagas: Expected `sagas` to be an array of generator functions'
57 | );
58 |
59 | warning(
60 | !isEmpty(sagas),
61 | '(app/utils...) injectAsyncSagas: Received an empty `sagas` array'
62 | );
63 |
64 | sagas.map(store.runSaga);
65 | };
66 | }
67 |
68 | /**
69 | * Helper for creating injectors
70 | */
71 | export function getAsyncInjectors(store) {
72 | checkStore(store);
73 |
74 | return {
75 | injectReducer: injectAsyncReducer(store, true),
76 | injectSagas: injectAsyncSagas(store, true),
77 | };
78 | }
79 |
--------------------------------------------------------------------------------
/app/utils/confirm.js:
--------------------------------------------------------------------------------
1 | import Confirmation from 'components/Confirmation';
2 | import { createConfirmation } from 'react-confirm';
3 |
4 | const defaultConfirmation = createConfirmation(Confirmation);
5 |
6 | export function confirm(confirmation, options = {}) {
7 | return defaultConfirmation({ confirmation, ...options });
8 | }
9 |
--------------------------------------------------------------------------------
/app/utils/tprapi.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by damien on 23/02/2017.
3 | */
4 | import axios from 'axios';
5 |
6 | const basePath = '/proxy/tpr/';
7 |
8 | /**
9 | * Parses the JSON returned by a network request
10 | *
11 | * @param {object} response A response from a network request
12 | *
13 | * @return {object} The parsed JSOs from the request
14 | */
15 | function parseJSON(response) {
16 | return response.data;
17 | }
18 |
19 | /**
20 | * Checks if a network request came back fine, and throws an error if not
21 | *
22 | * @param {object} response A response from a network request
23 | *
24 | * @return {object|undefined} Returns either the response, or throws an error
25 | */
26 | function checkStatus(response) {
27 | if (response.status >= 200 && response.status < 300) {
28 | return response;
29 | }
30 |
31 | const error = new Error(response.statusText);
32 | error.response = response;
33 | throw error;
34 | }
35 |
36 | export function getBenchmarkConfigs() {
37 | return axios.get(`${basePath}benchmark/configs`)
38 | .then(checkStatus)
39 | .then(parseJSON);
40 | }
41 | export function getBenchmarkConfig(name) {
42 | return axios.get(`${basePath}benchmark/configs/${name}`)
43 | .then(checkStatus)
44 | .then(parseJSON);
45 | }
46 | export function removeBenchmarkConfig(config) {
47 | return axios.delete(`${basePath}benchmark/configs/${config.metadata.name}`)
48 | .then(checkStatus)
49 | .then(parseJSON);
50 | }
51 | export function updateBenchmarkConfig(config) {
52 | // TODO maybe remove version info
53 | return axios.put(`${basePath}benchmark/configs/${config.metadata.name}`, config)
54 | .then(checkStatus)
55 | .then(parseJSON);
56 | }
57 | export function createBenchmarkConfig(config) {
58 | return axios.post(`${basePath}benchmark/configs`, config)
59 | .then(checkStatus)
60 | .then(parseJSON);
61 | }
62 |
63 | export function getBenchmarkInstances() {
64 | return axios.get(`${basePath}benchmark/instances`)
65 | .then(checkStatus)
66 | .then(parseJSON);
67 | }
68 | export function getBenchmarkInstance(name) {
69 | return axios.get(`${basePath}benchmark/instances/${name}`)
70 | .then(checkStatus)
71 | .then(parseJSON);
72 | }
73 | export function removeBenchmarkInstance(instance) {
74 | return axios.delete(`${basePath}benchmark/instances/${instance.metadata.name}`)
75 | .then(checkStatus)
76 | .then(parseJSON);
77 | }
78 | export function updateBenchmarkInstance(instance) {
79 | // TODO maybe remove version info
80 | return axios.put(`${basePath}benchmark/instances/${instance.metadata.name}`, instance)
81 | .then(checkStatus)
82 | .then(parseJSON);
83 | }
84 | export function createBenchmarkInstance(instance) {
85 | return axios.post(`${basePath}benchmark/instances`, instance)
86 | .then(checkStatus)
87 | .then(parseJSON);
88 | }
89 |
--------------------------------------------------------------------------------
/app/utils/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by damien on 24/02/2017.
3 | */
4 |
5 | export function slug(str) {
6 | // remove accents, swap ñ for n, etc
7 | const from = 'ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;';
8 | const to = 'aaaaaeeeeeiiiiooooouuuunc------';
9 |
10 | let strClean = str.replace(/^\s+|\s+$/g, ''); // trim
11 | strClean = strClean.toLowerCase();
12 |
13 | for (let i = 0, l = from.length; i < l; i += 1) {
14 | strClean = strClean.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
15 | }
16 |
17 | strClean = strClean.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
18 | .replace(/\s+/g, '-') // collapse whitespace and replace by -
19 | .replace(/-+/g, '-'); // collapse dashes
20 |
21 | return strClean;
22 | }
23 |
24 | export function decodeBase64(s) {
25 | return atob(s);
26 | }
27 |
28 | export function encodeBase64(s) {
29 | return btoa(s);
30 | }
31 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:9.4.0
2 |
3 | WORKDIR /app
4 | COPY . /app
5 | RUN docker/build.sh
6 |
7 | FROM nginx:1.13.8
8 |
9 | COPY docker/fission-ui.conf.template /etc/nginx/conf.d/default.conf.template
10 | COPY docker/run.sh /run.sh
11 | COPY --from=0 /app/build /fission-ui
12 |
--------------------------------------------------------------------------------
/docker/README.md:
--------------------------------------------------------------------------------
1 | # Fission-ui Deployment using docker
2 |
3 | Fission-ui is a single page application, so it only contains static files and depends on fission APIs
4 | on path `/proxy/{service}`.
5 |
6 | ## Deploy with other fission components in k8s cluster
7 |
8 | The easiest way is to deploy fission-ui as an micro-service with other fission services in fission namespace.
9 |
10 | After deploying fission services, create a fission-ui service and deployment:
11 | ```
12 | $ kubectl create -f fission-ui.yaml
13 | ```
14 | You can then access the ui via `node_ip:31319` or load balance.
15 |
16 | We use Nginx to serve the static files, which are complied by `npm run build` and piped into the project build folder.
17 | It also handles api requests to the fission services inside k8s.
18 |
19 | ## Deploy a standalone fission-ui server
20 |
21 | Modify the Nginx config `fission-ui.conf`, change the fission service name to your fission service ip or domain name.
22 |
23 | ## Other features
24 | You can add other features like https, authentication by modifying Nginx config file.
25 |
--------------------------------------------------------------------------------
/docker/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | npm run prebuild && npm run build
4 |
--------------------------------------------------------------------------------
/docker/fission-ui.conf.template:
--------------------------------------------------------------------------------
1 | ##
2 | # Put this file in /etc/nginx/conf.d folder and make sure
3 | # you have a line 'include /etc/nginx/conf.d/*.conf;'
4 | # in your main nginx configuration file
5 | ##
6 |
7 | ##
8 | # HTTP configurations
9 | ##
10 |
11 | server {
12 |
13 | listen 80;
14 |
15 | # Type your domain name below
16 | server_name localhost;
17 |
18 | # Always serve index.html for any request
19 | location / {
20 | # Set path
21 | root /fission-ui/;
22 | try_files $uri /index.html;
23 | }
24 |
25 | # Do not cache sw.js, required for offline-first updates.
26 | location /sw.js {
27 | add_header Cache-Control "no-cache";
28 | proxy_cache_bypass $http_pragma;
29 | proxy_cache_revalidate on;
30 | expires off;
31 | access_log off;
32 | }
33 |
34 | location /proxy/controller/ {
35 |
36 | proxy_pass http://controller.${FISSION_NAMESPACE}/;
37 | proxy_http_version 1.1;
38 | proxy_set_header X-Forwarded-Proto https;
39 | proxy_set_header Upgrade $http_upgrade;
40 | proxy_set_header Connection 'upgrade';
41 | proxy_set_header Host $host;
42 | proxy_cache_bypass $http_upgrade;
43 | }
44 |
45 | location /proxy/router/ {
46 |
47 | proxy_pass http://router.${FISSION_NAMESPACE}/;
48 | proxy_http_version 1.1;
49 | proxy_set_header X-Forwarded-Proto https;
50 | proxy_set_header Upgrade $http_upgrade;
51 | proxy_set_header Connection 'upgrade';
52 | proxy_set_header Host $host;
53 | proxy_cache_bypass $http_upgrade;
54 | }
55 |
56 | location /proxy/tpr/benchmark/ {
57 |
58 | proxy_pass http://127.0.0.1:8001/apis/benchmark.fission.io/v1/namespaces/fission-benchmark/;
59 | proxy_http_version 1.1;
60 | proxy_set_header X-Forwarded-Proto https;
61 | proxy_set_header Upgrade $http_upgrade;
62 | proxy_set_header Connection 'upgrade';
63 | proxy_cache_bypass $http_upgrade;
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/docker/fission-ui.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | name: fission-ui
5 | namespace: fission
6 | spec:
7 | replicas: 1
8 | template:
9 | metadata:
10 | labels:
11 | svc: fission-ui
12 | spec:
13 | containers:
14 | - name: nginx
15 | image: yqf3139/fission-ui:latest
16 | env:
17 | - name: FISSION_NAMESPACE
18 | value: fission
19 | command: ["bash"]
20 | args: ["/run.sh"]
21 | - name: kubectl-proxy
22 | image: lachlanevenson/k8s-kubectl
23 | args: ["proxy", "--port", "8001", "--address", "127.0.0.1"]
24 |
25 | ---
26 | apiVersion: v1
27 | kind: Service
28 | metadata:
29 | name: fission-ui
30 | namespace: fission
31 | labels:
32 | svc: fission-ui
33 | spec:
34 | type: NodePort
35 | ports:
36 | - port: 80
37 | targetPort: 80
38 | nodePort: 31319
39 | selector:
40 | svc: fission-ui
41 |
--------------------------------------------------------------------------------
/docker/push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | user=$1
6 | tag=$2
7 | if [ -z "$user" ]
8 | then
9 | user=fission
10 | fi
11 | if [ -z "$tag" ]
12 | then
13 | tag=latest
14 | fi
15 |
16 | docker build -t fission-ui -f docker/Dockerfile .
17 | docker tag fission-ui $user/fission-ui:$tag
18 | docker push $user/fission-ui:$tag
19 |
--------------------------------------------------------------------------------
/docker/run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | envsubst '${FISSION_NAMESPACE}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;';
4 |
--------------------------------------------------------------------------------
/documentation/images/batch-upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fission/fission-ui/d14ef875f905f9f33e6f76489a7f6bbfd562b0ff/documentation/images/batch-upload.png
--------------------------------------------------------------------------------
/documentation/images/environment-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fission/fission-ui/d14ef875f905f9f33e6f76489a7f6bbfd562b0ff/documentation/images/environment-list.png
--------------------------------------------------------------------------------
/documentation/images/function-edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fission/fission-ui/d14ef875f905f9f33e6f76489a7f6bbfd562b0ff/documentation/images/function-edit.png
--------------------------------------------------------------------------------
/documentation/images/function-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fission/fission-ui/d14ef875f905f9f33e6f76489a7f6bbfd562b0ff/documentation/images/function-list.png
--------------------------------------------------------------------------------
/documentation/images/function-triggers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fission/fission-ui/d14ef875f905f9f33e6f76489a7f6bbfd562b0ff/documentation/images/function-triggers.png
--------------------------------------------------------------------------------
/internals/config.js:
--------------------------------------------------------------------------------
1 | const resolve = require('path').resolve;
2 | const pullAll = require('lodash/pullAll');
3 | const uniq = require('lodash/uniq');
4 |
5 | const ReactBoilerplate = {
6 | // This refers to the react-boilerplate version this project is based on.
7 | version: '3.4.0',
8 |
9 | /**
10 | * The DLL Plugin provides a dramatic speed increase to webpack build and hot module reloading
11 | * by caching the module metadata for all of our npm dependencies. We enable it by default
12 | * in development.
13 | *
14 | *
15 | * To disable the DLL Plugin, set this value to false.
16 | */
17 | dllPlugin: {
18 | defaults: {
19 | /**
20 | * we need to exclude dependencies which are not intended for the browser
21 | * by listing them here.
22 | */
23 | exclude: [
24 | 'chalk',
25 | 'compression',
26 | 'cross-env',
27 | 'express',
28 | 'ip',
29 | 'minimist',
30 | 'sanitize.css',
31 | ],
32 |
33 | /**
34 | * Specify any additional dependencies here. We include core-js and lodash
35 | * since a lot of our dependencies depend on them and they get picked up by webpack.
36 | */
37 | include: ['core-js', 'eventsource-polyfill', 'babel-polyfill', 'lodash'],
38 |
39 | // The path where the DLL manifest and bundle will get built
40 | path: resolve('../node_modules/react-boilerplate-dlls'),
41 | },
42 |
43 | entry(pkg) {
44 | const dependencyNames = Object.keys(pkg.dependencies);
45 | const exclude = pkg.dllPlugin.exclude || ReactBoilerplate.dllPlugin.defaults.exclude;
46 | const include = pkg.dllPlugin.include || ReactBoilerplate.dllPlugin.defaults.include;
47 | const includeDependencies = uniq(dependencyNames.concat(include));
48 |
49 | return {
50 | reactBoilerplateDeps: pullAll(includeDependencies, exclude),
51 | };
52 | },
53 | },
54 | };
55 |
56 | module.exports = ReactBoilerplate;
57 |
--------------------------------------------------------------------------------
/internals/generators/component/es6.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | class {{ properCase name }} extends React.Component { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | return (
18 |
19 | {{#if wantMessages}}
20 |
21 | {{/if}}
22 |
23 | );
24 | }
25 | }
26 |
27 | {{ properCase name }}.propTypes = {
28 |
29 | };
30 |
31 | export default {{ properCase name }};
32 |
--------------------------------------------------------------------------------
/internals/generators/component/es6.pure.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | class {{ properCase name }} extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | return (
18 |
19 | {{#if wantMessages}}
20 |
21 | {{/if}}
22 |
23 | );
24 | }
25 | }
26 |
27 | {{ properCase name }}.propTypes = {
28 |
29 | };
30 |
31 | export default {{ properCase name }};
32 |
--------------------------------------------------------------------------------
/internals/generators/component/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Generator
3 | */
4 |
5 | /* eslint strict: [0] */
6 |
7 | 'use strict';
8 |
9 | const componentExists = require('../utils/componentExists');
10 |
11 | module.exports = {
12 | description: 'Add an unconnected component',
13 | prompts: [{
14 | type: 'list',
15 | name: 'type',
16 | message: 'Select the type of component',
17 | default: 'Stateless Function',
18 | choices: () => ['Stateless Function', 'ES6 Class (Pure)', 'ES6 Class'],
19 | }, {
20 | type: 'input',
21 | name: 'name',
22 | message: 'What should it be called?',
23 | default: 'Button',
24 | validate: (value) => {
25 | if ((/.+/).test(value)) {
26 | return componentExists(value) ? 'A component or container with this name already exists' : true;
27 | }
28 |
29 | return 'The name is required';
30 | },
31 | }, {
32 | type: 'confirm',
33 | name: 'wantMessages',
34 | default: true,
35 | message: 'Do you want i18n messages (i.e. will this component use text)?',
36 | }],
37 | actions: (data) => {
38 | // Generate index.js and index.test.js
39 | let componentTemplate;
40 |
41 | switch (data.type) {
42 | case 'ES6 Class': {
43 | componentTemplate = './component/es6.js.hbs';
44 | break;
45 | }
46 | case 'ES6 Class (Pure)': {
47 | componentTemplate = './component/es6.pure.js.hbs';
48 | break;
49 | }
50 | case 'Stateless Function': {
51 | componentTemplate = './component/stateless.js.hbs';
52 | break;
53 | }
54 | default: {
55 | componentTemplate = './component/es6.js.hbs';
56 | }
57 | }
58 |
59 | const actions = [{
60 | type: 'add',
61 | path: '../../app/components/{{properCase name}}/index.js',
62 | templateFile: componentTemplate,
63 | abortOnFail: true,
64 | }, {
65 | type: 'add',
66 | path: '../../app/components/{{properCase name}}/tests/index.test.js',
67 | templateFile: './component/test.js.hbs',
68 | abortOnFail: true,
69 | }];
70 |
71 | // If they want a i18n messages file
72 | if (data.wantMessages) {
73 | actions.push({
74 | type: 'add',
75 | path: '../../app/components/{{properCase name}}/messages.js',
76 | templateFile: './component/messages.js.hbs',
77 | abortOnFail: true,
78 | });
79 | }
80 |
81 | return actions;
82 | },
83 | };
84 |
--------------------------------------------------------------------------------
/internals/generators/component/messages.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | * {{ properCase name }} Messages
3 | *
4 | * This contains all the text for the {{ properCase name }} component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.{{ properCase name }}.header',
11 | defaultMessage: 'This is the {{ properCase name}} component !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/generators/component/stateless.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | function {{ properCase name }}() {
16 | return (
17 |
18 | {{#if wantMessages}}
19 |
20 | {{/if}}
21 |
22 | );
23 | }
24 |
25 | {{ properCase name }}.propTypes = {
26 |
27 | };
28 |
29 | export default {{ properCase name }};
30 |
--------------------------------------------------------------------------------
/internals/generators/component/test.js.hbs:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | // import { shallow } from 'enzyme';
3 |
4 | // import {{ properCase name }} from '../index';
5 |
6 | describe('<{{ properCase name }} />', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/internals/generators/container/actions.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} actions
4 | *
5 | */
6 |
7 | import {
8 | DEFAULT_ACTION,
9 | } from './constants';
10 |
11 | export function defaultAction() {
12 | return {
13 | type: DEFAULT_ACTION,
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/internals/generators/container/actions.test.js.hbs:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | defaultAction,
4 | } from '../actions';
5 | import {
6 | DEFAULT_ACTION,
7 | } from '../constants';
8 |
9 | describe('{{ properCase name }} actions', () => {
10 | describe('Default Action', () => {
11 | it('has a type of DEFAULT_ACTION', () => {
12 | const expected = {
13 | type: DEFAULT_ACTION,
14 | };
15 | expect(defaultAction()).toEqual(expected);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/internals/generators/container/constants.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} constants
4 | *
5 | */
6 |
7 | export const DEFAULT_ACTION = 'app/{{ properCase name }}/DEFAULT_ACTION';
8 |
--------------------------------------------------------------------------------
/internals/generators/container/index.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{properCase name }}
4 | *
5 | */
6 |
7 | import React, { PropTypes } from 'react';
8 | import { connect } from 'react-redux';
9 | {{#if wantHeaders}}
10 | import Helmet from 'react-helmet';
11 | {{/if}}
12 | {{#if wantMessages}}
13 | import { FormattedMessage } from 'react-intl';
14 | {{/if}}
15 | {{#if wantActionsAndReducer}}
16 | import { createStructuredSelector } from 'reselect';
17 | import makeSelect{{properCase name}} from './selectors';
18 | {{/if}}
19 | {{#if wantMessages}}
20 | import messages from './messages';
21 | {{/if}}
22 |
23 | export class {{ properCase name }} extends React.{{{ component }}} { // eslint-disable-line react/prefer-stateless-function
24 | render() {
25 | return (
26 |
27 | {{#if wantHeaders}}
28 |
34 | {{/if}}
35 | {{#if wantMessages}}
36 |
37 | {{/if}}
38 |
39 | );
40 | }
41 | }
42 |
43 | {{ properCase name }}.propTypes = {
44 | dispatch: PropTypes.func.isRequired,
45 | };
46 |
47 | {{#if wantActionsAndReducer}}
48 | const mapStateToProps = createStructuredSelector({
49 | {{name}}: makeSelect{{properCase name}}(),
50 | });
51 | {{/if}}
52 |
53 | function mapDispatchToProps(dispatch) {
54 | return {
55 | dispatch,
56 | };
57 | }
58 |
59 | {{#if wantActionsAndReducer}}
60 | export default connect(mapStateToProps, mapDispatchToProps)({{ properCase name }});
61 | {{else}}
62 | export default connect(null, mapDispatchToProps)({{ properCase name }});
63 | {{/if}}
64 |
--------------------------------------------------------------------------------
/internals/generators/container/messages.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | * {{properCase name }} Messages
3 | *
4 | * This contains all the text for the {{properCase name }} component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.{{properCase name }}.header',
11 | defaultMessage: 'This is {{properCase name}} container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/generators/container/reducer.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 | import {
9 | DEFAULT_ACTION,
10 | } from './constants';
11 |
12 | const initialState = fromJS({});
13 |
14 | function {{ camelCase name }}Reducer(state = initialState, action) {
15 | switch (action.type) {
16 | case DEFAULT_ACTION:
17 | return state;
18 | default:
19 | return state;
20 | }
21 | }
22 |
23 | export default {{ camelCase name }}Reducer;
24 |
--------------------------------------------------------------------------------
/internals/generators/container/reducer.test.js.hbs:
--------------------------------------------------------------------------------
1 |
2 | import { fromJS } from 'immutable';
3 | import {{ camelCase name }}Reducer from '../reducer';
4 |
5 | describe('{{ camelCase name }}Reducer', () => {
6 | it('returns the initial state', () => {
7 | expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({}));
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/internals/generators/container/sagas.js.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select } from 'redux-saga/effects';
2 |
3 | // Individual exports for testing
4 | export function* defaultSaga() {
5 | // See example in containers/HomePage/sagas.js
6 | }
7 |
8 | // All sagas to be loaded
9 | export default [
10 | defaultSaga,
11 | ];
12 |
--------------------------------------------------------------------------------
/internals/generators/container/sagas.test.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | * Test sagas
3 | */
4 |
5 | /* eslint-disable redux-saga/yield-effects */
6 | // import { take, call, put, select } from 'redux-saga/effects';
7 | // import { defaultSaga } from '../sagas';
8 |
9 | // const generator = defaultSaga();
10 |
11 | describe('defaultSaga Saga', () => {
12 | it('Expect to have unit tests specified', () => {
13 | expect(true).toEqual(false);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/internals/generators/container/selectors.js.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the {{ camelCase name }} state domain
5 | */
6 | const select{{ properCase name }}Domain = () => (state) => state.get('{{ camelCase name }}');
7 |
8 | /**
9 | * Other specific selectors
10 | */
11 |
12 |
13 | /**
14 | * Default selector used by {{ properCase name }}
15 | */
16 |
17 | const makeSelect{{ properCase name }} = () => createSelector(
18 | select{{ properCase name }}Domain(),
19 | (substate) => substate.toJS()
20 | );
21 |
22 | export default makeSelect{{ properCase name }};
23 | export {
24 | select{{ properCase name }}Domain,
25 | };
26 |
--------------------------------------------------------------------------------
/internals/generators/container/selectors.test.js.hbs:
--------------------------------------------------------------------------------
1 | // import { fromJS } from 'immutable';
2 | // import { makeSelect{{ properCase name }}Domain } from '../selectors';
3 |
4 | // const selector = makeSelect{{ properCase name}}Domain();
5 |
6 | describe('makeSelect{{ properCase name }}Domain', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/internals/generators/container/test.js.hbs:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | // import { shallow } from 'enzyme';
3 |
4 | // import { {{ properCase name }} } from '../index';
5 |
6 | describe('<{{ properCase name }} />', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/internals/generators/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * generator/index.js
3 | *
4 | * Exports the generators so plop knows them
5 | */
6 |
7 | const fs = require('fs');
8 | const path = require('path');
9 | const componentGenerator = require('./component/index.js');
10 | const containerGenerator = require('./container/index.js');
11 | const routeGenerator = require('./route/index.js');
12 | const languageGenerator = require('./language/index.js');
13 |
14 | module.exports = (plop) => {
15 | plop.setGenerator('component', componentGenerator);
16 | plop.setGenerator('container', containerGenerator);
17 | plop.setGenerator('route', routeGenerator);
18 | plop.setGenerator('language', languageGenerator);
19 | plop.addHelper('directory', (comp) => {
20 | try {
21 | fs.accessSync(path.join(__dirname, `../../app/containers/${comp}`), fs.F_OK);
22 | return `containers/${comp}`;
23 | } catch (e) {
24 | return `components/${comp}`;
25 | }
26 | });
27 | plop.addHelper('curly', (object, open) => (open ? '{' : '}'));
28 | };
29 |
--------------------------------------------------------------------------------
/internals/generators/language/add-locale-data.hbs:
--------------------------------------------------------------------------------
1 | $1addLocaleData({{language}}LocaleData);
2 |
--------------------------------------------------------------------------------
/internals/generators/language/app-locale.hbs:
--------------------------------------------------------------------------------
1 | $1 '{{language}}',
2 |
--------------------------------------------------------------------------------
/internals/generators/language/format-translation-messages.hbs:
--------------------------------------------------------------------------------
1 | $1 {{language}}: formatTranslationMessages('{{language}}', {{language}}TranslationMessages),
2 |
--------------------------------------------------------------------------------
/internals/generators/language/intl-locale-data.hbs:
--------------------------------------------------------------------------------
1 | $1import {{language}}LocaleData from 'react-intl/locale-data/{{language}}';
2 |
--------------------------------------------------------------------------------
/internals/generators/language/polyfill-intl-locale.hbs:
--------------------------------------------------------------------------------
1 | $1 import('intl/locale-data/jsonp/{{language}}.js'),
2 |
--------------------------------------------------------------------------------
/internals/generators/language/translation-messages.hbs:
--------------------------------------------------------------------------------
1 | $1import {{language}}TranslationMessages from './translations/{{language}}.json';
2 |
--------------------------------------------------------------------------------
/internals/generators/language/translations-json.hbs:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/internals/generators/route/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Route Generator
3 | */
4 | const fs = require('fs');
5 | const path = require('path');
6 | const componentExists = require('../utils/componentExists');
7 |
8 | function reducerExists(comp) {
9 | try {
10 | fs.accessSync(path.join(__dirname, `../../../app/containers/${comp}/reducer.js`), fs.F_OK);
11 | return true;
12 | } catch (e) {
13 | return false;
14 | }
15 | }
16 |
17 | function sagasExists(comp) {
18 | try {
19 | fs.accessSync(path.join(__dirname, `../../../app/containers/${comp}/sagas.js`), fs.F_OK);
20 | return true;
21 | } catch (e) {
22 | return false;
23 | }
24 | }
25 |
26 | function trimTemplateFile(template) {
27 | // Loads the template file and trims the whitespace and then returns the content as a string.
28 | return fs.readFileSync(path.join(__dirname, `./${template}`), 'utf8').replace(/\s*$/, '');
29 | }
30 |
31 | module.exports = {
32 | description: 'Add a route',
33 | prompts: [{
34 | type: 'input',
35 | name: 'component',
36 | message: 'Which component should the route show?',
37 | validate: (value) => {
38 | if ((/.+/).test(value)) {
39 | return componentExists(value) ? true : `"${value}" doesn't exist.`;
40 | }
41 |
42 | return 'The path is required';
43 | },
44 | }, {
45 | type: 'input',
46 | name: 'path',
47 | message: 'Enter the path of the route.',
48 | default: '/about',
49 | validate: (value) => {
50 | if ((/.+/).test(value)) {
51 | return true;
52 | }
53 |
54 | return 'path is required';
55 | },
56 | }],
57 |
58 | // Add the route to the routes.js file above the error route
59 | // TODO smarter route adding
60 | actions: (data) => {
61 | const actions = [];
62 | if (reducerExists(data.component)) {
63 | data.useSagas = sagasExists(data.component); // eslint-disable-line no-param-reassign
64 | actions.push({
65 | type: 'modify',
66 | path: '../../app/routes.js',
67 | pattern: /(\s{\n\s{0,}path: '\*',)/g,
68 | template: trimTemplateFile('routeWithReducer.hbs'),
69 | });
70 | } else {
71 | actions.push({
72 | type: 'modify',
73 | path: '../../app/routes.js',
74 | pattern: /(\s{\n\s{0,}path: '\*',)/g,
75 | template: trimTemplateFile('route.hbs'),
76 | });
77 | }
78 |
79 | return actions;
80 | },
81 | };
82 |
--------------------------------------------------------------------------------
/internals/generators/route/route.hbs:
--------------------------------------------------------------------------------
1 | {
2 | path: '{{ path }}',
3 | name: '{{ camelCase component }}',
4 | getComponent(location, cb) {
5 | import('{{{directory (properCase component)}}}')
6 | .then(loadModule(cb))
7 | .catch(errorLoading);
8 | },
9 | },$1
10 |
--------------------------------------------------------------------------------
/internals/generators/route/routeWithReducer.hbs:
--------------------------------------------------------------------------------
1 | {
2 | path: '{{ path }}',
3 | name: '{{ camelCase component }}',
4 | getComponent(nextState, cb) {
5 | const importModules = Promise.all([
6 | import('containers/{{ properCase component }}/reducer'),
7 | {{#if useSagas}}
8 | import('containers/{{ properCase component }}/sagas'),
9 | {{/if}}
10 | import('containers/{{ properCase component }}'),
11 | ]);
12 |
13 | const renderRoute = loadModule(cb);
14 |
15 | importModules.then(([reducer,{{#if useSagas}} sagas,{{/if}} component]) => {
16 | injectReducer('{{ camelCase component }}', reducer.default);
17 | {{#if useSagas}}
18 | injectSagas(sagas.default);
19 | {{/if}}
20 | renderRoute(component);
21 | });
22 |
23 | importModules.catch(errorLoading);
24 | },
25 | },$1
26 |
--------------------------------------------------------------------------------
/internals/generators/utils/componentExists.js:
--------------------------------------------------------------------------------
1 | /**
2 | * componentExists
3 | *
4 | * Check whether the given component exist in either the components or containers directory
5 | */
6 |
7 | const fs = require('fs');
8 | const path = require('path');
9 | const pageComponents = fs.readdirSync(path.join(__dirname, '../../../app/components'));
10 | const pageContainers = fs.readdirSync(path.join(__dirname, '../../../app/containers'));
11 | const components = pageComponents.concat(pageContainers);
12 |
13 | function componentExists(comp) {
14 | return components.indexOf(comp) >= 0;
15 | }
16 |
17 | module.exports = componentExists;
18 |
--------------------------------------------------------------------------------
/internals/mocks/cssModule.js:
--------------------------------------------------------------------------------
1 | module.exports = 'CSS_MODULE';
2 |
--------------------------------------------------------------------------------
/internals/mocks/image.js:
--------------------------------------------------------------------------------
1 | module.exports = 'IMAGE_MOCK';
2 |
--------------------------------------------------------------------------------
/internals/scripts/analyze.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const shelljs = require('shelljs');
4 | const animateProgress = require('./helpers/progress');
5 | const chalk = require('chalk');
6 | const addCheckMark = require('./helpers/checkmark');
7 |
8 | const progress = animateProgress('Generating stats');
9 |
10 | // Generate stats.json file with webpack
11 | shelljs.exec(
12 | 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json',
13 | addCheckMark.bind(null, callback) // Output a checkmark on completion
14 | );
15 |
16 | // Called after webpack has finished generating the stats.json file
17 | function callback() {
18 | clearInterval(progress);
19 | process.stdout.write(
20 | '\n\nOpen ' + chalk.magenta('http://webpack.github.io/analyse/') + ' in your browser and upload the stats.json file!' +
21 | chalk.blue('\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n')
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/internals/scripts/clean.js:
--------------------------------------------------------------------------------
1 | require('shelljs/global');
2 | const addCheckMark = require('./helpers/checkmark.js');
3 |
4 | if (!which('git')) {
5 | echo('Sorry, this script requires git');
6 | exit(1);
7 | }
8 |
9 | if (!test('-e', 'internals/templates')) {
10 | echo('The example is deleted already.');
11 | exit(1);
12 | }
13 |
14 | process.stdout.write('Cleanup started...');
15 |
16 | // Reuse existing LanguageProvider and i18n tests
17 | mv('app/containers/LanguageProvider/tests', 'internals/templates/containers/LanguageProvider');
18 | cp('app/tests/i18n.test.js', 'internals/templates/tests/i18n.test.js');
19 |
20 | // Cleanup components/
21 | rm('-rf', 'app/components/*');
22 |
23 | // Handle containers/
24 | rm('-rf', 'app/containers');
25 | mv('internals/templates/containers', 'app');
26 |
27 | // Handle tests/
28 | mv('internals/templates/tests', 'app');
29 |
30 | // Handle translations/
31 | rm('-rf', 'app/translations')
32 | mv('internals/templates/translations', 'app');
33 |
34 | // Handle utils/
35 | rm('-rf', 'app/utils');
36 | mv('internals/templates/utils', 'app')
37 |
38 | // Replace the files in the root app/ folder
39 | cp('internals/templates/app.js', 'app/app.js');
40 | cp('internals/templates/global-styles.js', 'app/global-styles.js');
41 | cp('internals/templates/i18n.js', 'app/i18n.js');
42 | cp('internals/templates/index.html', 'app/index.html');
43 | cp('internals/templates/reducers.js', 'app/reducers.js');
44 | cp('internals/templates/routes.js', 'app/routes.js');
45 | cp('internals/templates/store.js', 'app/store.js');
46 |
47 | // Remove the templates folder
48 | rm('-rf', 'internals/templates');
49 |
50 | addCheckMark();
51 |
52 | // Commit the changes
53 | if (exec('git add . --all && git commit -qm "Remove default example"').code !== 0) {
54 | echo('\nError: Git commit failed');
55 | exit(1);
56 | }
57 |
58 | echo('\nCleanup done. Happy Coding!!!');
59 |
--------------------------------------------------------------------------------
/internals/scripts/dependencies.js:
--------------------------------------------------------------------------------
1 | // No need to build the DLL in production
2 | if (process.env.NODE_ENV === 'production') {
3 | process.exit(0);
4 | }
5 |
6 | require('shelljs/global');
7 |
8 | const path = require('path');
9 | const fs = require('fs');
10 | const exists = fs.existsSync;
11 | const writeFile = fs.writeFileSync;
12 |
13 | const defaults = require('lodash/defaultsDeep');
14 | const pkg = require(path.join(process.cwd(), 'package.json'));
15 | const config = require('../config');
16 | const dllConfig = defaults(pkg.dllPlugin, config.dllPlugin.defaults);
17 | const outputPath = path.join(process.cwd(), dllConfig.path);
18 | const dllManifestPath = path.join(outputPath, 'package.json');
19 |
20 | /**
21 | * I use node_modules/react-boilerplate-dlls by default just because
22 | * it isn't going to be version controlled and babel wont try to parse it.
23 | */
24 | mkdir('-p', outputPath);
25 |
26 | echo('Building the Webpack DLL...');
27 |
28 | /**
29 | * Create a manifest so npm install doesn't warn us
30 | */
31 | if (!exists(dllManifestPath)) {
32 | writeFile(
33 | dllManifestPath,
34 | JSON.stringify(defaults({
35 | name: 'react-boilerplate-dlls',
36 | private: true,
37 | author: pkg.author,
38 | repository: pkg.repository,
39 | version: pkg.version,
40 | }), null, 2),
41 | 'utf8'
42 | );
43 | }
44 |
45 | // the BUILDING_DLL env var is set to avoid confusing the development environment
46 | exec('cross-env BUILDING_DLL=true webpack --display-chunks --color --config internals/webpack/webpack.dll.babel.js');
47 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/checkmark.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | /**
4 | * Adds mark check symbol
5 | */
6 | function addCheckMark(callback) {
7 | process.stdout.write(chalk.green(' ✓'));
8 | if (callback) callback();
9 | }
10 |
11 | module.exports = addCheckMark;
12 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/progress.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const readline = require('readline');
4 |
5 | /**
6 | * Adds an animated progress indicator
7 | *
8 | * @param {string} message The message to write next to the indicator
9 | * @param {number} amountOfDots The amount of dots you want to animate
10 | */
11 | function animateProgress(message, amountOfDots) {
12 | if (typeof amountOfDots !== 'number') {
13 | amountOfDots = 3;
14 | }
15 |
16 | let i = 0;
17 | return setInterval(function() {
18 | readline.cursorTo(process.stdout, 0);
19 | i = (i + 1) % (amountOfDots + 1);
20 | const dots = new Array(i + 1).join('.');
21 | process.stdout.write(message + dots);
22 | }, 500);
23 | }
24 |
25 | module.exports = animateProgress;
26 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/xmark.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | /**
4 | * Adds mark cross symbol
5 | */
6 | function addXMark(callback) {
7 | process.stdout.write(chalk.red(' ✘'));
8 | if (callback) callback();
9 | }
10 |
11 | module.exports = addXMark;
12 |
--------------------------------------------------------------------------------
/internals/scripts/npmcheckversion.js:
--------------------------------------------------------------------------------
1 | const exec = require('child_process').exec;
2 | exec('npm -v', function (err, stdout, stderr) {
3 | if (err) throw err;
4 | if (parseFloat(stdout) < 3) {
5 | throw new Error('[ERROR: React Boilerplate] You need npm version @>=3');
6 | process.exit(1);
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/internals/testing/test-bundler.js:
--------------------------------------------------------------------------------
1 | // needed for regenerator-runtime
2 | // (ES7 generator support is required by redux-saga)
3 | import 'babel-polyfill';
4 |
--------------------------------------------------------------------------------
/internals/webpack/webpack.base.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * COMMON WEBPACK CONFIGURATION
3 | */
4 |
5 | const path = require('path');
6 | const webpack = require('webpack');
7 |
8 | module.exports = (options) => ({
9 | entry: options.entry,
10 | output: Object.assign({ // Compile into js/build.js
11 | path: path.resolve(process.cwd(), 'build'),
12 | publicPath: '/',
13 | }, options.output), // Merge with env dependent settings
14 | module: {
15 | loaders: [{
16 | test: /\.js$/, // Transform all .js files required somewhere with Babel
17 | loader: 'babel-loader',
18 | exclude: /node_modules/,
19 | query: options.babelQuery,
20 | }, {
21 | // Do not transform vendor's CSS with CSS-modules
22 | // The point is that they remain in global scope.
23 | // Since we require these CSS files in our JS or CSS files,
24 | // they will be a part of our compilation either way.
25 | // So, no need for ExtractTextPlugin here.
26 | test: /\.css$/,
27 | include: /node_modules/,
28 | loaders: ['style-loader', 'css-loader'],
29 | }, {
30 | test: /\.(eot|svg|ttf|woff|woff2)$/,
31 | loader: 'file-loader',
32 | }, {
33 | test: /\.(jpg|png|gif)$/,
34 | loaders: [
35 | 'file-loader',
36 | {
37 | loader: 'image-webpack-loader',
38 | query: {
39 | progressive: true,
40 | optimizationLevel: 7,
41 | interlaced: false,
42 | pngquant: {
43 | quality: '65-90',
44 | speed: 4,
45 | },
46 | },
47 | },
48 | ],
49 | }, {
50 | test: /\.html$/,
51 | loader: 'html-loader',
52 | }, {
53 | test: /\.json$/,
54 | loader: 'json-loader',
55 | }, {
56 | test: /\.(mp4|webm)$/,
57 | loader: 'url-loader',
58 | query: {
59 | limit: 10000,
60 | },
61 | }],
62 | },
63 | plugins: options.plugins.concat([
64 | new webpack.ProvidePlugin({
65 | // make fetch available
66 | fetch: 'exports-loader?self.fetch!whatwg-fetch',
67 | }),
68 |
69 | // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
70 | // inside your code for any environment checks; UglifyJS will automatically
71 | // drop any unreachable code.
72 | new webpack.DefinePlugin({
73 | 'process.env': {
74 | NODE_ENV: JSON.stringify(process.env.NODE_ENV),
75 | },
76 | }),
77 | new webpack.NamedModulesPlugin(),
78 | ]),
79 | resolve: {
80 | modules: ['app', 'node_modules'],
81 | extensions: [
82 | '.js',
83 | '.jsx',
84 | '.react.js',
85 | ],
86 | mainFields: [
87 | 'browser',
88 | 'jsnext:main',
89 | 'main',
90 | ],
91 | },
92 | devtool: options.devtool,
93 | target: 'web', // Make web variables accessible to webpack, e.g. window
94 | performance: options.performance || {},
95 | });
96 |
--------------------------------------------------------------------------------
/internals/webpack/webpack.dll.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WEBPACK DLL GENERATOR
3 | *
4 | * This profile is used to cache webpack's module
5 | * contexts for external library and framework type
6 | * dependencies which will usually not change often enough
7 | * to warrant building them from scratch every time we use
8 | * the webpack process.
9 | */
10 |
11 | const { join } = require('path');
12 | const defaults = require('lodash/defaultsDeep');
13 | const webpack = require('webpack');
14 | const pkg = require(join(process.cwd(), 'package.json'));
15 | const dllPlugin = require('../config').dllPlugin;
16 |
17 | if (!pkg.dllPlugin) { process.exit(0); }
18 |
19 | const dllConfig = defaults(pkg.dllPlugin, dllPlugin.defaults);
20 | const outputPath = join(process.cwd(), dllConfig.path);
21 |
22 | module.exports = require('./webpack.base.babel')({
23 | context: process.cwd(),
24 | entry: dllConfig.dlls ? dllConfig.dlls : dllPlugin.entry(pkg),
25 | devtool: 'eval',
26 | output: {
27 | filename: '[name].dll.js',
28 | path: outputPath,
29 | library: '[name]',
30 | },
31 | plugins: [
32 | new webpack.DllPlugin({ name: '[name]', path: join(outputPath, '[name].json') }), // eslint-disable-line no-new
33 | ],
34 | performance: {
35 | hints: false,
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/internals/webpack/webpack.prod.babel.js:
--------------------------------------------------------------------------------
1 | // Important modules this config uses
2 | const path = require('path');
3 | const webpack = require('webpack');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 | const OfflinePlugin = require('offline-plugin');
6 |
7 | module.exports = require('./webpack.base.babel')({
8 | // In production, we skip all hot-reloading stuff
9 | entry: [
10 | path.join(process.cwd(), 'app/app.js'),
11 | ],
12 |
13 | // Utilize long-term caching by adding content hashes (not compilation hashes) to compiled assets
14 | output: {
15 | filename: '[name].[chunkhash].js',
16 | chunkFilename: '[name].[chunkhash].chunk.js',
17 | },
18 |
19 | plugins: [
20 | new webpack.optimize.CommonsChunkPlugin({
21 | name: 'vendor',
22 | children: true,
23 | minChunks: 2,
24 | async: true,
25 | }),
26 |
27 | // Minify and optimize the index.html
28 | new HtmlWebpackPlugin({
29 | template: 'app/index.html',
30 | minify: {
31 | removeComments: true,
32 | collapseWhitespace: true,
33 | removeRedundantAttributes: true,
34 | useShortDoctype: true,
35 | removeEmptyAttributes: true,
36 | removeStyleLinkTypeAttributes: true,
37 | keepClosingSlash: true,
38 | minifyJS: true,
39 | minifyCSS: true,
40 | minifyURLs: true,
41 | },
42 | inject: true,
43 | }),
44 |
45 | // Put it in the end to capture all the HtmlWebpackPlugin's
46 | // assets manipulations and do leak its manipulations to HtmlWebpackPlugin
47 | new OfflinePlugin({
48 | relativePaths: false,
49 | publicPath: '/',
50 |
51 | // No need to cache .htaccess. See http://mxs.is/googmp,
52 | // this is applied before any match in `caches` section
53 | excludes: ['.htaccess'],
54 |
55 | caches: {
56 | main: [':rest:'],
57 |
58 | // All chunks marked as `additional`, loaded after main section
59 | // and do not prevent SW to install. Change to `optional` if
60 | // do not want them to be preloaded at all (cached only when first loaded)
61 | additional: ['*.chunk.js'],
62 | },
63 |
64 | // Removes warning for about `additional` section usage
65 | safeToUseOptionalCaches: true,
66 |
67 | AppCache: false,
68 | }),
69 | ],
70 |
71 | performance: {
72 | assetFilter: (assetFilename) => !(/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename)),
73 | },
74 | });
75 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | /* eslint consistent-return:0 */
2 |
3 | const express = require('express');
4 | const logger = require('./logger');
5 |
6 | const argv = require('minimist')(process.argv.slice(2));
7 | const setup = require('./middlewares/frontendMiddleware');
8 | const isDev = process.env.NODE_ENV !== 'production';
9 | const ngrok = (isDev && process.env.ENABLE_TUNNEL) || argv.tunnel ? require('ngrok') : false;
10 | const resolve = require('path').resolve;
11 | const proxy = require('http-proxy-middleware');
12 | const app = express();
13 | const appInfluxdb = express();
14 |
15 | let controllerBackend = '192.168.99.100:31313';
16 | if (process.env.FISSION_CONTROLLER !== undefined) {
17 | controllerBackend = process.env.FISSION_CONTROLLER;
18 | }
19 |
20 | let routerBackend = '192.168.99.100:31314';
21 | if (process.env.FISSION_ROUTER !== undefined) {
22 | routerBackend = process.env.FISSION_ROUTER;
23 | }
24 |
25 | let k8sBackend = '127.0.0.1:28001';
26 | if (process.env.FISSION_K8S !== undefined) {
27 | k8sBackend = process.env.FISSION_K8S;
28 | }
29 |
30 | let influxdbBackend = '192.168.99.100:31315';
31 | if (process.env.FISSION_LOGDB !== undefined) {
32 | influxdbBackend = process.env.FISSION_LOGDB;
33 | }
34 |
35 | // Setup proxy for fission APIs
36 | app.use('/proxy/controller', proxy({ target: `http://${controllerBackend}`, pathRewrite: { '^/proxy/controller': '' }, changeOrigin: true }));
37 | app.use('/proxy/router', proxy({ target: `http://${routerBackend}`, pathRewrite: { '^/proxy/router': '' }, changeOrigin: true }));
38 | app.use('/proxy/tpr/benchmark', proxy({
39 | target: `http://${k8sBackend}`,
40 | pathRewrite: { '^/proxy/tpr/benchmark': '/apis/benchmark.fission.io/v1/namespaces/fission-benchmark' },
41 | changeOrigin: true,
42 | }));
43 | appInfluxdb.use('', proxy({ target: `http://${influxdbBackend}`, changeOrigin: true }));
44 |
45 | // In production we need to pass these values in instead of relying on webpack
46 | setup(app, {
47 | outputPath: resolve(process.cwd(), 'build'),
48 | publicPath: '/',
49 | });
50 | setup(appInfluxdb, {
51 | outputPath: resolve(process.cwd(), 'build'),
52 | publicPath: '/',
53 | });
54 |
55 |
56 | // get the intended host and port number, use localhost and port 3000 if not provided
57 | const customHost = argv.host || process.env.HOST;
58 | const host = customHost || null; // Let http.Server use its default IPv6/4 host
59 | const prettyHost = customHost || 'localhost';
60 |
61 | const port = argv.port || process.env.PORT || 3000;
62 |
63 | // Start your app.
64 | app.listen(port, host, (err) => {
65 | if (err) {
66 | return logger.error(err.message);
67 | }
68 |
69 | // Connect to ngrok in dev mode
70 | if (ngrok) {
71 | ngrok.connect(port, (innerErr, url) => {
72 | if (innerErr) {
73 | return logger.error(innerErr);
74 | }
75 |
76 | logger.appStarted(port, prettyHost, url);
77 | });
78 | } else {
79 | logger.appStarted(port, prettyHost);
80 | }
81 | });
82 |
83 | appInfluxdb.listen(31315, host, (err) => {
84 | if (err) {
85 | return logger.error(err.message);
86 | }
87 |
88 | logger.appStarted(31315, prettyHost);
89 | });
90 |
--------------------------------------------------------------------------------
/server/logger.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const chalk = require('chalk');
4 | const ip = require('ip');
5 |
6 | const divider = chalk.gray('\n-----------------------------------');
7 |
8 | /**
9 | * Logger middleware, you can customize it to make messages more personal
10 | */
11 | const logger = {
12 |
13 | // Called whenever there's an error on the server we want to print
14 | error: (err) => {
15 | console.error(chalk.red(err));
16 | },
17 |
18 | // Called when express.js app starts on given port w/o errors
19 | appStarted: (port, host, tunnelStarted) => {
20 | console.log(`Server started ! ${chalk.green('✓')}`);
21 |
22 | // If the tunnel started, log that and the URL it's available at
23 | if (tunnelStarted) {
24 | console.log(`Tunnel initialised ${chalk.green('✓')}`);
25 | }
26 |
27 | console.log(`
28 | ${chalk.bold('Access URLs:')}${divider}
29 | Localhost: ${chalk.magenta(`http://${host}:${port}`)}
30 | LAN: ${chalk.magenta(`http://${ip.address()}:${port}`) +
31 | (tunnelStarted ? `\n Proxy: ${chalk.magenta(tunnelStarted)}` : '')}${divider}
32 | ${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)}
33 | `);
34 | },
35 | };
36 |
37 | module.exports = logger;
38 |
--------------------------------------------------------------------------------
/server/middlewares/frontendMiddleware.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const express = require('express');
3 | const path = require('path');
4 | const compression = require('compression');
5 | const pkg = require(path.resolve(process.cwd(), 'package.json'));
6 |
7 | // Dev middleware
8 | const addDevMiddlewares = (app, webpackConfig) => {
9 | const webpack = require('webpack');
10 | const webpackDevMiddleware = require('webpack-dev-middleware');
11 | const webpackHotMiddleware = require('webpack-hot-middleware');
12 | const compiler = webpack(webpackConfig);
13 | const middleware = webpackDevMiddleware(compiler, {
14 | noInfo: true,
15 | publicPath: webpackConfig.output.publicPath,
16 | silent: true,
17 | stats: 'errors-only',
18 | });
19 |
20 | app.use(middleware);
21 | app.use(webpackHotMiddleware(compiler));
22 |
23 | // Since webpackDevMiddleware uses memory-fs internally to store build
24 | // artifacts, we use it instead
25 | const fs = middleware.fileSystem;
26 |
27 | if (pkg.dllPlugin) {
28 | app.get(/\.dll\.js$/, (req, res) => {
29 | const filename = req.path.replace(/^\//, '');
30 | res.sendFile(path.join(process.cwd(), pkg.dllPlugin.path, filename));
31 | });
32 | }
33 |
34 | app.get('*', (req, res) => {
35 | fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => {
36 | if (err) {
37 | res.sendStatus(404);
38 | } else {
39 | res.send(file.toString());
40 | }
41 | });
42 | });
43 | };
44 |
45 | // Production middlewares
46 | const addProdMiddlewares = (app, options) => {
47 | const publicPath = options.publicPath || '/';
48 | const outputPath = options.outputPath || path.resolve(process.cwd(), 'build');
49 |
50 | // compression middleware compresses your server responses which makes them
51 | // smaller (applies also to assets). You can read more about that technique
52 | // and other good practices on official Express.js docs http://mxs.is/googmy
53 | app.use(compression());
54 | app.use(publicPath, express.static(outputPath));
55 |
56 | app.get('*', (req, res) => res.sendFile(path.resolve(outputPath, 'index.html')));
57 | };
58 |
59 | /**
60 | * Front-end middleware
61 | */
62 | module.exports = (app, options) => {
63 | const isProd = process.env.NODE_ENV === 'production';
64 |
65 | if (isProd) {
66 | addProdMiddlewares(app, options);
67 | } else {
68 | const webpackConfig = require('../../internals/webpack/webpack.dev.babel');
69 | addDevMiddlewares(app, webpackConfig);
70 | }
71 |
72 | return app;
73 | };
74 |
--------------------------------------------------------------------------------