diff --git a/.eslintrc.json b/.eslintrc.json
index 4e02ad7..1f9f7c0 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -7,8 +7,10 @@
},
"extends": [
"eslint:recommended",
- "plugin:react/recommended"
+ "plugin:react/recommended",
+ "plugin:prettier/recommended"
],
+ "parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
@@ -17,9 +19,14 @@
"sourceType": "module"
},
"plugins": [
+ "prettier",
"react",
"jest"
],
"rules": {
+ "prettier/prettier": "warn",
+ "react/prop-types": "warn",
+ "no-prototype-builtins": "warn",
+ "react/no-string-refs": "warn"
}
}
diff --git a/.vimrc b/.vimrc
index cbde2c6..b8dd2cb 100644
--- a/.vimrc
+++ b/.vimrc
@@ -1,6 +1,6 @@
set autoread
set path+=.,public/**,src/**,test/**
-if has('win32')
+if has('win32') || has('win64')
let &makeprg="create_dist.cmd"
else
let &makeprg="./create_dist.sh"
diff --git a/package.json b/package.json
index 824a517..ff4d9dc 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"focus-trap-react": "^8.4.2",
"font-awesome": "^4.7.0",
"node-cron": "1.2.1",
+ "prop-types": "^15.7.2",
"randomstring": "^1.1.5",
"react": "16.14.0",
"react-checkbox-tree": "^1.6.0",
@@ -37,8 +38,11 @@
"electron-builder": "22.9.1",
"electron-webpack": "^2.8.2",
"eslint": "^7.22.0",
+ "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^24.3.2",
+ "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.23.0",
+ "prettier": "2.2.1",
"webpack": "4.44.2",
"webpack-dev-server": "3.11.1"
},
diff --git a/src/App.jsx b/src/App.jsx
index 4b862bd..5c2b9c3 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,6 @@
import React from 'react';
import './App.css';
+import AddEditHost from './containers/AddEditHost/AddEditHost';
import Box from './components/UI/Box/Box';
import Configuration from './containers/Configuration/Configuration';
import { connect } from 'react-redux';
@@ -176,6 +177,15 @@ class App extends IPCContainer {
!this.props.DismissNewReleasesAvailable &&
this.props.NewReleasesAvailable.length > 0;
+ const showAddEditHost =
+ !showDependencies &&
+ !this.props.DownloadActive &&
+ !showNewReleases &&
+ !this.props.RebootRequired &&
+ !this.props.DisplaySelectAppPlatform &&
+ !showUpgrade &&
+ this.props.DisplayAddEditHost;
+
const showSkynetImport =
!showConfig &&
!showDependencies &&
@@ -204,6 +214,10 @@ class App extends IPCContainer {
remoteSupported={remoteSupported}
/>
);
+ const addEditHostDisplay = createModalConditionally(
+ showAddEditHost,
+
+ );
const pinnedManagerDisplay = createModalConditionally(
showPinnedManager,
@@ -358,6 +372,7 @@ class App extends IPCContainer {
{upgradeDisplay}
{pinnedManagerDisplay}
{configDisplay}
+ {addEditHostDisplay}
{infoDisplay}
{confirmDisplay}
{downloadDisplay}
@@ -378,6 +393,7 @@ const mapStateToProps = (state) => {
AppBusyTransparent: state.common.AppBusyTransparent,
AppReady: state.common.AppReady,
DismissDependencies: state.install.DismissDependencies,
+ DisplayAddEditHost: state.host.DisplayAddEditHost,
DisplayConfiguration: state.mounts.DisplayConfiguration,
DisplayConfirmYesNo: state.common.DisplayConfirmYesNo,
DisplayError: state.error.DisplayError,
diff --git a/src/components/ApplicationBusy/ApplicationBusy.js b/src/components/ApplicationBusy/ApplicationBusy.js
index 709cc02..0e2b83b 100644
--- a/src/components/ApplicationBusy/ApplicationBusy.js
+++ b/src/components/ApplicationBusy/ApplicationBusy.js
@@ -2,6 +2,7 @@ import React from 'react';
import Box from '../UI/Box/Box';
import Loader from 'react-loader-spinner';
import Text from '../UI/Text/Text';
+import PropTypes from 'prop-types';
const ApplicationBusy = ({ title }) => {
return (
@@ -27,4 +28,8 @@ const ApplicationBusy = ({ title }) => {
);
};
+ApplicationBusy.propTypes = {
+ title: PropTypes.string,
+};
+
export default ApplicationBusy;
diff --git a/src/components/DependencyList/DependencyList.js b/src/components/DependencyList/DependencyList.js
index b3fbfd5..33eb089 100644
--- a/src/components/DependencyList/DependencyList.js
+++ b/src/components/DependencyList/DependencyList.js
@@ -2,6 +2,7 @@ import React from 'react';
import './DependencyList.css';
import { connect } from 'react-redux';
import * as Constants from '../../constants';
+import { createDismissDisplay } from '../../utils.jsx';
import Dependency from './Dependency/Dependency';
import Box from '../UI/Box/Box';
import { downloadItem } from '../../redux/actions/download_actions';
@@ -45,33 +46,12 @@ export default connect(
);
});
- const dismissDisplay = (
-
- );
-
return (
- {dismissDisplay}
+ {createDismissDisplay(
+ () => props.setDismissDependencies(true),
+ !props.AllowDismissDependencies
+ )}
{
+ this.props.completeAddEditHost(this.state);
+ };
+
+ render() {
+ return (
+
+ {createDismissDisplay(this.props.Close)}
+
+
+ Portal Settings
+
+
+
+
+
+
+ this.setState({ HostNameOrIp: e.target.value.trim() })
+ }
+ className={'ConfigurationItemInput'}
+ style={{ width: '100%' }}
+ type={'text'}
+ value={this.state.HostNameOrIp}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ this.setState({ AuthURL: e.target.value })}
+ className={'ConfigurationItemInput'}
+ type={'text'}
+ value={this.state.AuthURL}
+ />
+
+
+
+
+
+
+
+ {'Portal URL: ' +
+ this.state.Protocol +
+ '://' +
+ this.state.HostNameOrIp +
+ ((this.state.Protocol === 'http' && this.state.ApiPort != 80) ||
+ (this.state.Protocol === 'https' && this.state.ApiPort != 443)
+ ? ':' + this.state.ApiPort.toString()
+ : '')}
+
+
+
+
+
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ HostData: state.host.HostData,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ Close: () => dispatch(addEditHost(false)),
+ completeAddEditHost: (host_data) =>
+ dispatch(completeAddEditHost(true, host_data)),
+ };
+};
+
+AddEditHost.propTypes = {
+ Close: PropTypes.func.isRequired,
+ completeAddEditHost: PropTypes.func.isRequired,
+ HostData: PropTypes.object,
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(AddEditHost);
diff --git a/src/containers/AddMount/AddMount.js b/src/containers/AddMount/AddMount.js
index 68a2371..8dd3596 100644
--- a/src/containers/AddMount/AddMount.js
+++ b/src/containers/AddMount/AddMount.js
@@ -36,7 +36,7 @@ const default_state = {
DisplayS3: false,
HostNameOrIp: '',
Name: '',
- Port: 20000,
+ Port: 2000,
Provider: Constants.S3_PROVIDER_LIST[0],
Region: Constants.S3_REGION_PROVIDER_REGION[0],
SecretKey: '',
@@ -47,7 +47,7 @@ export default connect(
mapStateToProps,
mapDispatchToProps
)(
- class extends Component {
+ class AddMount extends Component {
state = {
...default_state,
};
diff --git a/src/containers/Configuration/Configuration.js b/src/containers/Configuration/Configuration.js
index 5cbbd1d..17163fa 100644
--- a/src/containers/Configuration/Configuration.js
+++ b/src/containers/Configuration/Configuration.js
@@ -1,6 +1,7 @@
import React from 'react';
import './Configuration.css';
import { connect } from 'react-redux';
+import { createDismissDisplay } from '../../utils.jsx';
import Box from '../../components/UI/Box/Box';
import Button from '../../components/UI/Button/Button';
import ConfigurationItem from './ConfigurationItem/ConfigurationItem';
@@ -35,6 +36,27 @@ class Configuration extends IPCContainer {
}
return itemA.value.filter((i) => !itemB.value.includes(i)).length !== 0;
}
+ if (itemA.type === 'host_list') {
+ if (itemA.value.length !== itemB.value.length) {
+ return true;
+ }
+ return (
+ itemA.value.filter((i) =>
+ itemB.value.find(
+ (j) =>
+ j.HostNameOrIp === i.HostNameOrIp &&
+ j.ApiPort === i.ApiPort &&
+ j.Protocol === i.Protocol &&
+ j.TimeoutMs === i.TimeoutMs &&
+ j.AgentString === i.AgentString &&
+ j.ApiPassword === i.ApiPassword &&
+ j.AuthURL === i.AuthURL &&
+ j.AuthUser === i.AuthUser &&
+ j.AuthPassword === i.AuthPassword
+ )
+ ).length === 0
+ );
+ }
return itemA.value !== itemB.value;
};
@@ -115,9 +137,11 @@ class Configuration extends IPCContainer {
remote: template[key] ? template[key].remote : false,
type: template[key] ? template[key].type : null,
value:
- template[key] && template[key].type === 'object'
+ template[key] &&
+ (template[key].type === 'string_array' ||
+ template[key].type === 'object')
? config[key]
- : template[key] && template[key].type === 'string_array'
+ : template[key] && template[key].type === 'host_list'
? config[key]
: config[key].toString(),
};
@@ -141,6 +165,8 @@ class Configuration extends IPCContainer {
itemList[idx].value =
target.type === 'textarea'
? target.string_array
+ : target.type === 'host_list'
+ ? target.value
: target.value.toString();
this.setState({
ItemList: itemList,
@@ -156,6 +182,8 @@ class Configuration extends IPCContainer {
itemList[idx].value =
target.type === 'textarea'
? target.string_array
+ : target.type === 'host_list'
+ ? target.value
: target.value.toString();
objectLookup[name] = itemList;
this.setState({
@@ -246,7 +274,11 @@ class Configuration extends IPCContainer {
changedItems.push({
Name: item.label,
Value:
- item.type === 'string_array' ? item.value.join(';') : item.value,
+ item.type === 'string_array'
+ ? item.value.join(';')
+ : item.type === 'host_list'
+ ? JSON.stringify(item.value)
+ : item.value,
});
}
@@ -258,6 +290,8 @@ class Configuration extends IPCContainer {
Value:
item.type === 'string_array'
? item.value.join(';')
+ : item.type === 'host_list'
+ ? JSON.stringify(item.value)
: item.value,
});
}
@@ -402,23 +436,8 @@ class Configuration extends IPCContainer {
return (
{confirmSave}
-
-
+
+ {createDismissDisplay(this.checkSaveRequired)}
{(this.props.DisplayRemoteConfiguration
? this.props.DisplayConfiguration.substr(6)
diff --git a/src/containers/Configuration/ConfigurationItem/ConfigurationItem.js b/src/containers/Configuration/ConfigurationItem/ConfigurationItem.js
index b59b359..56c20d5 100644
--- a/src/containers/Configuration/ConfigurationItem/ConfigurationItem.js
+++ b/src/containers/Configuration/ConfigurationItem/ConfigurationItem.js
@@ -7,6 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { notifyError, notifyInfo } from '../../../redux/actions/error_actions';
import settings from '../../../assets/settings';
import DropDown from '../../../components/UI/DropDown/DropDown';
+import HostList from '../../HostList/HostList';
import Password from '../../../containers/UI/Password/Password';
const mapDispatchToProps = (dispatch) => {
@@ -80,6 +81,25 @@ export default connect(
);
break;
+ case 'host_list':
+ data = (
+
+ handleChanged({
+ target: {
+ type: 'host_list',
+ value: items,
+ },
+ })
+ }
+ type={props.template.subtype}
+ value={props.value}
+ />
+ );
+ break;
+
case 'list':
data = (
{
+// return {};
+// };
+//
+const mapDispatchToProps = (dispatch) => {
+ return {
+ addEditHost: (host_data, cb) => dispatch(addEditHost(true, host_data, cb)),
+ };
+};
+
+const Host = ({ allowDelete, addEditHost, value, onChange, onDelete }) => {
+ const handleEditHost = () => {
+ addEditHost(value, (changed, host_data) => {
+ if (changed) {
+ onChange(host_data);
+ }
+ });
+ };
+
+ return (
+
+
+ {allowDelete ? (
+
+ ) : null}
+
+ {value.HostNameOrIp +
+ ((value.Protocol === 'http' && value.ApiPort === 80) ||
+ (value.Protocol === 'https' && value.ApiPort === 443)
+ ? ''
+ : ':' + value.ApiPort)}
+
+
+ );
+};
+
+Host.propTypes = {
+ allowDelete: PropTypes.bool.isRequired,
+ addEditHost: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onDelete: PropTypes.func.isRequired,
+ value: PropTypes.object.isRequired,
+};
+
+export default connect(null, mapDispatchToProps)(Host);
+// export default Host;
diff --git a/src/containers/HostList/HostList.css b/src/containers/HostList/HostList.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/containers/HostList/HostList.js b/src/containers/HostList/HostList.js
new file mode 100644
index 0000000..85826d5
--- /dev/null
+++ b/src/containers/HostList/HostList.js
@@ -0,0 +1,122 @@
+import React from 'react';
+import './HostList.css';
+import Host from './Host/Host';
+import PropTypes from 'prop-types';
+import { Component } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { confirmYesNo } from '../../redux/actions/common_actions';
+import { connect } from 'react-redux';
+import { addEditHost } from '../../redux/actions/host_actions';
+import { faPlusCircle } from '@fortawesome/free-solid-svg-icons';
+
+class HostList extends Component {
+ state = {
+ items: [],
+ };
+ // autoFocus={props.autoFocus}
+ // disabled={props.readOnly}
+ // type={props.template.subtype}
+
+ componentDidMount() {
+ this.setState({ items: this.props.value });
+ }
+
+ componentWillUnmount() {}
+
+ checkDuplicates = () => {
+ return false;
+ };
+
+ handleAddHost = () => {
+ this.props.addEditHost((changed, host_data) => {
+ if (changed) {
+ const items = [...this.state.items, host_data];
+ this.updateItems(items);
+ }
+ });
+ };
+
+ handleChanged = (host_data, index) => {
+ const items = [...this.state.items];
+ items[index] = host_data;
+ this.updateItems(items);
+ };
+
+ handleDeleted = (index) => {
+ this.props.ConfirmRemoveHost(
+ 'Delete [' + this.state.items[index].HostNameOrIp + ']?',
+ (confirmed) => {
+ if (confirmed) {
+ const items = [...this.state.items];
+ items.splice(index, 1);
+ this.updateItems(items);
+ }
+ }
+ );
+ };
+
+ updateItems = (items) => {
+ this.setState(
+ {
+ items,
+ },
+ () => {
+ this.props.onChange(this.state.items);
+ }
+ );
+ };
+
+ render() {
+ let idx = 0;
+ return (
+
+
+ {this.state.items.map((v, index) => {
+ return (
+ this.handleChanged(host_data, index)}
+ onDelete={() => this.handleDeleted(index)}
+ allowDelete={this.state.items.length > 1}
+ value={v}
+ />
+ );
+ })}
+
+
+
+ {' Add Portal '}
+
+
+ );
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ addEditHost: (cb) => dispatch(addEditHost(true, null, cb)),
+ ConfirmRemoveHost: (title, cb) => dispatch(confirmYesNo(title, cb)),
+ };
+};
+
+HostList.propTypes = {
+ addEditHost: PropTypes.func.isRequired,
+ ConfirmRemoveHost: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+ value: PropTypes.array.isRequired,
+};
+
+export default connect(null, mapDispatchToProps)(HostList);
diff --git a/src/containers/PinnedManager/PinnedManager.js b/src/containers/PinnedManager/PinnedManager.js
index e3008ab..1f0dbae 100644
--- a/src/containers/PinnedManager/PinnedManager.js
+++ b/src/containers/PinnedManager/PinnedManager.js
@@ -1,15 +1,15 @@
import React from 'react';
import './PinnedManager.css';
-import { connect } from 'react-redux';
-import IPCContainer from '../IPCContainer/IPCContainer';
-import { notifyApplicationBusy } from '../../redux/actions/common_actions';
-import { notifyError, notifyInfo } from '../../redux/actions/error_actions';
import Box from '../../components/UI/Box/Box';
-import { displayPinnedManager } from '../../redux/actions/pinned_manager_actions';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faFolder } from '@fortawesome/free-solid-svg-icons';
import Button from '../../components/UI/Button/Button';
import CheckBox from '../../components/UI/CheckBox/CheckBox';
+import IPCContainer from '../IPCContainer/IPCContainer';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { connect } from 'react-redux';
+import { displayPinnedManager } from '../../redux/actions/pinned_manager_actions';
+import { faFolder } from '@fortawesome/free-solid-svg-icons';
+import { notifyApplicationBusy } from '../../redux/actions/common_actions';
+import { notifyError, notifyInfo } from '../../redux/actions/error_actions';
const Constants = require('../../constants');
diff --git a/src/redux/actions/common_actions.js b/src/redux/actions/common_actions.js
index 592c6e8..871a6d8 100644
--- a/src/redux/actions/common_actions.js
+++ b/src/redux/actions/common_actions.js
@@ -6,11 +6,15 @@ import { getIPCRenderer } from '../../utils.jsx';
const ipcRenderer = getIPCRenderer();
let yesNoResolvers = [];
-export const confirmYesNo = (title) => {
+export const confirmYesNo = (title, cb) => {
return (dispatch) => {
- return new Promise((resolve) => {
- dispatch(handleConfirmYesNo(true, title, resolve));
- });
+ if (cb) {
+ dispatch(confirmYesNo(title)).then((confirmed) => cb(confirmed));
+ } else {
+ return new Promise((resolve) => {
+ dispatch(handleConfirmYesNo(true, title, resolve));
+ });
+ }
};
};
@@ -116,7 +120,7 @@ export const setRebootRequired = () => {
};
export const showWindow = () => {
- return (_) => {
+ return () => {
if (ipcRenderer) {
ipcRenderer.send(Constants.IPC_Show_Window);
}
diff --git a/src/redux/actions/download_actions.js b/src/redux/actions/download_actions.js
index 146551a..a5bfbdc 100644
--- a/src/redux/actions/download_actions.js
+++ b/src/redux/actions/download_actions.js
@@ -57,13 +57,13 @@ export const downloadItem = (
case Constants.INSTALL_TYPES.Upgrade:
// const info =
// this.props.LocationsLookup[this.props.AppPlatform][this.props.VersionLookup[this.props.AppPlatform][0]];
- const sha256 = null; // info.sha256;
- const signature = null; // info.sig;
+ //const sha256 = null; // info.sha256;
+ //const signature = null; // info.sig;
dispatch(
installUpgrade(
result.Destination,
- sha256,
- signature,
+ null,
+ null,
!!result.SkipVerification
)
);
diff --git a/src/redux/actions/host_actions.js b/src/redux/actions/host_actions.js
new file mode 100644
index 0000000..41306ab
--- /dev/null
+++ b/src/redux/actions/host_actions.js
@@ -0,0 +1,41 @@
+let addEditHostResolvers = [];
+
+export const addEditHost = (display, host_data, cb) => {
+ return (dispatch) => {
+ if (cb) {
+ dispatch(addEditHost(display, host_data)).then(({ changed, host_data }) =>
+ cb(changed, host_data)
+ );
+ } else {
+ return new Promise((resolve) => {
+ dispatch(handleDisplayAddEditHost(display, host_data, resolve));
+ });
+ }
+ };
+};
+
+const handleDisplayAddEditHost = (display, host_data, resolve) => {
+ return (dispatch) => {
+ if (display) {
+ addEditHostResolvers.push(resolve);
+ dispatch(displayAddEditHost(display, host_data));
+ } else {
+ dispatch(completeAddEditHost(false));
+ }
+ };
+};
+
+export const completeAddEditHost = (changed, host_data) => {
+ return (dispatch) => {
+ if (changed) {
+ addEditHostResolvers[0]({ changed, host_data });
+ }
+ addEditHostResolvers.splice(0, 1);
+ dispatch(displayAddEditHost(false));
+ };
+};
+
+export const DISPLAY_ADD_EDIT_HOST = 'host/displayAddEditHost';
+export const displayAddEditHost = (display, host_data) => {
+ return { type: DISPLAY_ADD_EDIT_HOST, payload: { display, host_data } };
+};
diff --git a/src/redux/actions/release_version_actions.js b/src/redux/actions/release_version_actions.js
index 46d465f..96e04e2 100644
--- a/src/redux/actions/release_version_actions.js
+++ b/src/redux/actions/release_version_actions.js
@@ -31,7 +31,7 @@ export const clearUIUpgrade = () => {
};
const cleanupOldReleases = (versionList) => {
- return (_) => {
+ return () => {
const ipcRenderer = getIPCRenderer();
if (ipcRenderer) {
ipcRenderer.sendSync(Constants.IPC_Cleanup_Releases + '_sync', {
diff --git a/src/redux/reducers/host_reducer.js b/src/redux/reducers/host_reducer.js
new file mode 100644
index 0000000..9284ff4
--- /dev/null
+++ b/src/redux/reducers/host_reducer.js
@@ -0,0 +1,20 @@
+import { createReducer } from '@reduxjs/toolkit';
+import * as Actions from '../actions/host_actions';
+
+export const hostReducer = createReducer(
+ {
+ DisplayAddEditHost: false,
+ Edit: false,
+ HostData: {},
+ },
+ {
+ [Actions.DISPLAY_ADD_EDIT_HOST]: (state, action) => {
+ return {
+ ...state,
+ DisplayAddEditHost: action.payload.display,
+ Edit: !!action.payload.host_data,
+ HostData: action.payload.host_data || {},
+ };
+ },
+ }
+);
diff --git a/src/redux/store/createAppStore.js b/src/redux/store/createAppStore.js
index 390d077..e26dbec 100644
--- a/src/redux/store/createAppStore.js
+++ b/src/redux/store/createAppStore.js
@@ -8,12 +8,14 @@ import { createMountReducer } from '../reducers/mount_reducer';
import { pinnedManagerReducer } from '../reducers/pinned_manager_reducer';
import { releaseVersionReducer } from '../reducers/release_version_reducer';
import { skynetReducer } from '../reducers/skynet_reducer';
+import { hostReducer } from '../reducers/host_reducer';
export default function createAppStore(platformInfo, version, state) {
const reducer = {
common: createCommonReducer(platformInfo, version),
download: downloadReducer,
error: errorReducer,
+ host: hostReducer,
install: installReducer,
mounts: createMountReducer(state),
relver: releaseVersionReducer,
diff --git a/src/utils.jsx b/src/utils.jsx
index 58a4e0e..7564810 100644
--- a/src/utils.jsx
+++ b/src/utils.jsx
@@ -24,6 +24,30 @@ export const checkNewReleases = (selectedVersion) => {
return [];
};
+export const createDismissDisplay = (closeHandler, preventClick) => {
+ return (
+
+ );
+};
+
export const createModalConditionally = (
condition,
jsx,