diff --git a/server/utils/hollaex-network-lib/index.js b/server/utils/hollaex-network-lib/index.js
index 063efe9ba8..dda743f21a 100644
--- a/server/utils/hollaex-network-lib/index.js
+++ b/server/utils/hollaex-network-lib/index.js
@@ -2213,19 +2213,19 @@ class HollaExNetwork {
if (isBoolean(opts.rejected)) {
data.rejected = opts.rejected;
} else {
- data.rejected = true;
+ data.rejected = false;
}
if (isBoolean(opts.dismissed)) {
data.dismissed = opts.dismissed;
} else {
- data.dismissed = true;
+ data.dismissed = false;
}
if (isBoolean(opts.waiting)) {
data.waiting = opts.waiting;
} else {
- data.waiting = true;
+ data.waiting = false;
}
if (isBoolean(opts.email)) {
@@ -2410,19 +2410,19 @@ class HollaExNetwork {
if (isBoolean(opts.rejected)) {
data.rejected = opts.rejected;
} else {
- data.rejected = true;
+ data.rejected = false;
}
if (isBoolean(opts.dismissed)) {
data.dismissed = opts.dismissed;
} else {
- data.dismissed = true;
+ data.dismissed = false;
}
if (isBoolean(opts.waiting)) {
data.waiting = opts.waiting;
} else {
- data.waiting = true;
+ data.waiting = false;
}
if (isBoolean(opts.email)) {
diff --git a/version b/version
index fad066f801..437459cd94 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-2.5.0
\ No newline at end of file
+2.5.0
diff --git a/web/public/assets/images/manual-plugin-upgrade.svg b/web/public/assets/images/manual-plugin-upgrade.svg
new file mode 100644
index 0000000000..5ea1b6157c
--- /dev/null
+++ b/web/public/assets/images/manual-plugin-upgrade.svg
@@ -0,0 +1,3 @@
+
diff --git a/web/src/config/icons/static.js b/web/src/config/icons/static.js
index f7718c14f0..2938047860 100644
--- a/web/src/config/icons/static.js
+++ b/web/src/config/icons/static.js
@@ -156,6 +156,7 @@ const icons = {
CLOCK: '/assets/images/clock.svg',
QR_CODE_SHOW: '/assets/images/mini-qr-code.svg',
QR_CODE_SCAN: '/assets/images/camera-scan.svg',
+ MANUAL_PLUGIN_UPGRADE: '/assets/images/manual-plugin-upgrade.svg',
};
export default icons;
diff --git a/web/src/containers/Admin/Deposits/utils.js b/web/src/containers/Admin/Deposits/utils.js
index d01d332f8a..1d0c6e6b53 100644
--- a/web/src/containers/Admin/Deposits/utils.js
+++ b/web/src/containers/Admin/Deposits/utils.js
@@ -92,7 +92,7 @@ export const renderResendContent = (renderData, onOpenModal) => {
!renderData.status &&
!renderData.dismissed &&
!renderData.rejected &&
- !renderData.processing
+ renderData.waiting
) {
return (
diff --git a/web/src/containers/Admin/Plugins/AddPlugin.js b/web/src/containers/Admin/Plugins/AddPlugin.js
new file mode 100644
index 0000000000..d537e117cc
--- /dev/null
+++ b/web/src/containers/Admin/Plugins/AddPlugin.js
@@ -0,0 +1,114 @@
+import React from 'react';
+import { Button, Input, Radio } from 'antd';
+import { DownloadOutlined } from '@ant-design/icons';
+
+const radioStyle = {
+ display: 'flex',
+ alignItems: 'center',
+ height: '30px',
+ lineHeight: '1.2',
+ padding: '24px 0',
+ margin: 0,
+ paddingLeft: '1px',
+ whiteSpace: 'normal',
+ letterSpacing: '-0.15px',
+};
+
+const AddThirdPartyPlugin = ({
+ handleChange,
+ thirdPartyType,
+ handleFileChange,
+ handleURL,
+ thirdPartyError,
+ handleBack,
+ thirdParty,
+ getJSONFromURL,
+ updateState,
+ handleStep,
+ handleUpdatePlugin = () => {},
+}) => {
+ return (
+
+
+ Add third party plugin
+
+
+
+
+ Upload a json file
+
+ {thirdPartyType === 'upload_json' ? (
+
+ ) : null}
+
+ Input URL path
+
+ {thirdPartyType === 'input_url' ? (
+
+ URL path
+
+
+ ) : null}
+
+ {thirdPartyError ? (
+
{thirdPartyError}
+ ) : null}
+
+
+
+
+
+
+ );
+};
+
+export default AddThirdPartyPlugin;
diff --git a/web/src/containers/Admin/Plugins/MyPlugins.js b/web/src/containers/Admin/Plugins/MyPlugins.js
index ae3523fb19..6da69b3d75 100644
--- a/web/src/containers/Admin/Plugins/MyPlugins.js
+++ b/web/src/containers/Admin/Plugins/MyPlugins.js
@@ -1,12 +1,11 @@
import React, { Component } from 'react';
-import { Button, Input, Spin, Modal, Radio, message } from 'antd';
import { Link } from 'react-router';
+import { Button, Input, Spin, Modal, message } from 'antd';
import _debounce from 'lodash/debounce';
-import { DownloadOutlined } from '@ant-design/icons';
-import axios from 'axios';
import { STATIC_ICONS } from 'config/icons';
import { addPlugin, updatePlugins } from './action';
+import AddThirdPartyPlugin from './AddPlugin';
class MyPlugins extends Component {
constructor(props) {
@@ -14,12 +13,8 @@ class MyPlugins extends Component {
this.state = {
isVisible: false,
step: 1,
- thirdPartyType: 'upload_json',
isConfirm: true,
pluginData: {},
- thirdParty: {},
- thirdPartyError: '',
- jsonURL: '',
buttonSubmitting: false,
};
}
@@ -47,23 +42,8 @@ class MyPlugins extends Component {
this.setState({
isVisible: false,
step: 1,
- thirdParty: {},
- thirdPartyError: '',
- jsonURL: '',
});
- };
-
- handleStep = (step) => {
- this.setState({ step });
- };
-
- handleChange = (e) => {
- if (e.target.value === 'upload_json') {
- this.setState({ thirdPartyType: 'upload_json' });
- } else {
- this.setState({ thirdPartyType: 'input_url' });
- }
- this.setState({ thirdPartyError: '', jsonURL: '' });
+ this.props.handleCancel();
};
handleInput = (e) => {
@@ -82,7 +62,7 @@ class MyPlugins extends Component {
handleAddPlugin = async () => {
const { restart, myPlugins } = this.props;
const body = {
- ...this.state.thirdParty,
+ ...this.props.thirdParty,
enabled: true,
};
this.setState({ buttonSubmitting: true });
@@ -136,195 +116,42 @@ class MyPlugins extends Component {
}
};
- handleURL = (e) => {
- this.setState({ jsonURL: e.target.value });
- };
-
- getJsonFromFile = async (file) => {
- return await new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = (function () {
- return function (e) {
- try {
- let json = JSON.parse(e.target.result);
- resolve(json);
- } catch (err) {
- message.error(err.toString());
- reject('Invalid format');
- }
- };
- })(file);
- reader.readAsText(file);
- });
- };
-
- handleFileChange = async (event) => {
- const file = event.target.files[0];
- if (file) {
- try {
- const res = await this.getJsonFromFile(file);
- const check = this.checkJSON(res);
- if (check) {
- this.setState({ thirdParty: res, thirdPartyError: '' });
- } else {
- this.setState({
- thirdPartyError:
- 'The file format is not correct. Please make sure it follows JSON standard',
- });
- }
- } catch (err) {
- this.setState({
- thirdPartyError:
- 'The file format is not correct. Please make sure it follows JSON standard',
- });
- }
- }
- };
-
- checkJSON = (json) => {
- if (json && json.name && json.version && json.author) {
- return true;
- } else {
- return false;
- }
- };
-
- getJSONFromURL = async () => {
- try {
- if (this.state.jsonURL) {
- const res = await axios.get(this.state.jsonURL);
- if (res.data) {
- const check = this.checkJSON(res.data);
- if (check) {
- this.setState({ thirdParty: res.data, thirdPartyError: '' });
- this.handleStep(3);
- } else {
- this.setState({
- thirdPartyError:
- 'The file format is not correct. Please make sure it follows JSON standard',
- });
- }
- }
- } else {
- this.setState({ thirdPartyError: 'Enter valid JSON file URL' });
- }
- } catch (err) {
- this.setState({
- thirdPartyError:
- 'The file format is not correct. Please make sure it follows JSON standard',
- });
- }
+ handleStep = (step) => {
+ this.setState({ step });
};
handleBack = () => {
- this.setState({ thirdParty: {}, thirdPartyError: '' });
+ this.props.handleSetBack();
this.handleStep(1);
};
renderPopup = () => {
- const radioStyle = {
- display: 'flex',
- alignItems: 'center',
- height: '30px',
- lineHeight: '1.2',
- padding: '24px 0',
- margin: 0,
- paddingLeft: '1px',
- whiteSpace: 'normal',
- letterSpacing: '-0.15px',
- };
+ const { step, isConfirm, buttonSubmitting } = this.state;
const {
- step,
+ handleChange,
+ handleFileChange,
+ handleURL,
thirdPartyType,
- isConfirm,
- thirdParty,
thirdPartyError,
- buttonSubmitting,
- } = this.state;
+ getJSONFromURL,
+ updateState,
+ thirdParty,
+ } = this.props;
switch (step) {
case 2:
return (
-
-
- Add third party plugin
-
-
-
-
- Upload a json file
-
- {thirdPartyType === 'upload_json' ? (
-
- ) : null}
-
- Input URL path
-
- {thirdPartyType === 'input_url' ? (
-
- URL path
-
-
- ) : null}
-
- {thirdPartyError ? (
-
{thirdPartyError}
- ) : null}
-
-
-
-
-
-
+
);
case 3:
return (
diff --git a/web/src/containers/Admin/Plugins/PluginConfigure.js b/web/src/containers/Admin/Plugins/PluginConfigure.js
index 4227b96a76..cf410f6746 100644
--- a/web/src/containers/Admin/Plugins/PluginConfigure.js
+++ b/web/src/containers/Admin/Plugins/PluginConfigure.js
@@ -12,7 +12,8 @@ const PluginConfigure = ({
updatePluginList,
removePlugin,
restart,
- handleRedirect
+ handleRedirect,
+ handleStep,
}) => {
const [pluginData, setPlugin] = useState({});
const [selectedNetworkPlugin, setNetworkData] = useState({});
@@ -60,6 +61,7 @@ const PluginConfigure = ({
selectedPlugin={pluginData}
requestPlugin={requestPlugin}
restart={restart}
+ handleStep={handleStep}
/>
) : (
{
}
};
-const PluginConfigureForm = ({ selectedPlugin, requestPlugin, restart }) => {
+const PluginConfigureForm = ({
+ selectedPlugin,
+ requestPlugin,
+ restart,
+ handleStep = () => {},
+}) => {
const [isLoading, setLoading] = useState(true);
const [metaData, setMetaData] = useState({});
const getMetaData = useCallback(() => {
@@ -173,22 +178,34 @@ const PluginConfigureForm = ({ selectedPlugin, requestPlugin, restart }) => {
}
return (
-
-

-
-
{metaData.name}
-
-
Version: {metaData.version}
+
+
+

+
+
{metaData.name}
+
+ Version: {metaData.version}
+
+
+
+
{renderContent(selectedPlugin, setMetaData, metaData, restart)}
diff --git a/web/src/containers/Admin/Plugins/index.css b/web/src/containers/Admin/Plugins/index.css
index b7bd599e35..9cf6dc2677 100644
--- a/web/src/containers/Admin/Plugins/index.css
+++ b/web/src/containers/Admin/Plugins/index.css
@@ -462,3 +462,20 @@
.plugin-list-container .plugin-list .plugin-list-item .premium-plugin {
color: #ff8000;
}
+
+.w-85 {
+ width: 85%;
+}
+
+.w-48 {
+ width: 48% !important;
+}
+
+.Spinner-wrapper svg {
+ color: #ffffff;
+ font-size: 28px;
+}
+
+.spinner-container {
+ padding-right: 1.25rem;
+}
diff --git a/web/src/containers/Admin/Plugins/index.js b/web/src/containers/Admin/Plugins/index.js
index 187da49255..e94953f75e 100644
--- a/web/src/containers/Admin/Plugins/index.js
+++ b/web/src/containers/Admin/Plugins/index.js
@@ -1,14 +1,21 @@
import React, { Component } from 'react';
-import { Spin, Tabs, Breadcrumb, Modal, message } from 'antd';
import { connect } from 'react-redux';
-import { RightOutlined } from '@ant-design/icons';
+import { Spin, Tabs, Breadcrumb, Modal, message, Button } from 'antd';
+import { LoadingOutlined, RightOutlined } from '@ant-design/icons';
+import axios from 'axios';
import PluginList from './PluginList';
import PluginConfigure from './PluginConfigure';
import MyPlugins from './MyPlugins';
-import { removePlugin, requestPlugins, requestMyPlugins } from './action';
+import {
+ removePlugin,
+ requestPlugins,
+ requestMyPlugins,
+ updatePlugins,
+} from './action';
import { STATIC_ICONS } from 'config/icons';
import Spinner from './Spinner';
+import AddThirdPartyPlugin from './AddPlugin';
import './index.css';
@@ -40,6 +47,11 @@ class Plugins extends Component {
tabKey: plugin ? 'my_plugin' : 'explore',
pluginCards: [],
processing: false,
+ thirdPartyType: 'upload_json',
+ thirdPartyError: '',
+ thirdParty: {},
+ step: 1,
+ jsonURL: '',
};
this.removeTimeout = null;
}
@@ -227,7 +239,7 @@ class Plugins extends Component {
};
onCancelModal = () => {
- this.setState({ isVisible: false, selectedPlugin: {} });
+ this.setState({ isVisible: false });
};
handlePluginList = (plugin) => {
@@ -276,6 +288,274 @@ class Plugins extends Component {
this.setState({ type: 'configure', isConfigure: true });
};
+ handleUpdatePlugin = () => {
+ this.handleStep(3);
+ const body = {
+ ...this.state.thirdParty,
+ };
+ updatePlugins({ name: body.name }, body)
+ .then((res) => {
+ if (res) {
+ message.success('Third party plugin updated successfully');
+ this.onCancelModal();
+ }
+ })
+ .catch((err) => {
+ const _error =
+ err.data && err.data.message ? err.data.message : err.message;
+ message.error(_error);
+ this.onCancelModal();
+ });
+ };
+
+ updateState = (thirdPartyError) => {
+ this.setState({ thirdPartyError });
+ };
+
+ handleCancel = () => {
+ this.setState({
+ thirdParty: {},
+ thirdPartyError: '',
+ jsonURL: '',
+ });
+ };
+
+ handleStep = (step) => {
+ this.setState({ step, isVisible: true });
+ };
+
+ handleURL = (e) => {
+ this.setState({ jsonURL: e.target.value });
+ };
+
+ handleChange = (e) => {
+ if (e.target.value === 'upload_json') {
+ this.setState({ thirdPartyType: 'upload_json' });
+ } else {
+ this.setState({ thirdPartyType: 'input_url' });
+ }
+ this.setState({ thirdPartyError: '', jsonURL: '' });
+ };
+
+ getJsonFromFile = async (file) => {
+ return await new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = (function () {
+ return function (e) {
+ try {
+ let json = JSON.parse(e.target.result);
+ resolve(json);
+ } catch (err) {
+ message.error(err.toString());
+ reject('Invalid format');
+ }
+ };
+ })(file);
+ reader.readAsText(file);
+ });
+ };
+
+ checkJSON = (json) => {
+ if (json && json.name && json.version && json.author) {
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ handleFileChange = async (event) => {
+ const file = event.target.files[0];
+ if (file) {
+ try {
+ const res = await this.getJsonFromFile(file);
+ const check = this.checkJSON(res);
+ if (check) {
+ this.setState({ thirdParty: res, thirdPartyError: '' });
+ } else {
+ this.setState({
+ thirdPartyError:
+ 'The file format is not correct. Please make sure it follows JSON standard',
+ });
+ }
+ } catch (err) {
+ this.setState({
+ thirdPartyError:
+ 'The file format is not correct. Please make sure it follows JSON standard',
+ });
+ }
+ }
+ };
+
+ getJSONFromURL = async () => {
+ try {
+ if (this.state.jsonURL) {
+ const res = await axios.get(this.state.jsonURL);
+ if (res.data) {
+ const check = this.checkJSON(res.data);
+ if (check) {
+ this.setState({ thirdParty: res.data, thirdPartyError: '' });
+ this.handleStep(3);
+ } else {
+ this.setState({
+ thirdPartyError:
+ 'The file format is not correct. Please make sure it follows JSON standard',
+ });
+ }
+ }
+ } else {
+ this.setState({ thirdPartyError: 'Enter valid JSON file URL' });
+ }
+ } catch (err) {
+ this.setState({
+ thirdPartyError:
+ 'The file format is not correct. Please make sure it follows JSON standard',
+ });
+ }
+ };
+
+ handleBack = () => {
+ this.handleSetBack();
+ this.handleStep(1);
+ };
+
+ handleSetBack = () => {
+ this.setState({ thirdParty: {}, thirdPartyError: '' });
+ };
+
+ renderModalContent = () => {
+ const {
+ selectedPlugin,
+ thirdPartyType,
+ thirdPartyError,
+ step,
+ thirdParty,
+ } = this.state;
+ switch (step) {
+ case 1:
+ return (
+
+
+

+
Upgrade third-party plugin
+
+
+
+ {selectedPlugin.icon ? (
+

+ ) : (
+

+ )}
+
+
+
Name: {selectedPlugin.name}
+
Current version: 1
+
+
+
+ You can upgrade this plugin to a newer version manually by
+ uploading a .json file while maintaining the current plugin's
+ configuration values.
+
+
+ Would you like to proceed with the upgrade?
+
+
+
+
+
+
+ );
+ case 2:
+ return (
+
+ );
+ case 3:
+ return (
+
+
+
+
+
+ }
+ />
+
+
Upgrading plugin
+
+
+
Please wait while the upgrade is being applied...
+
+
+
+ );
+ default:
+ return (
+
+
+
+ {selectedPlugin.icon ? (
+

+ ) : (
+

+ )}
+
+
+
{selectedPlugin.name}
+
This plugin is coming soon!
+
+
+
+ );
+ }
+ };
+
render() {
const {
loading,
@@ -291,6 +571,9 @@ class Plugins extends Component {
removePluginName,
pluginCards,
processing,
+ thirdPartyType,
+ thirdPartyError,
+ thirdParty,
} = this.state;
if (loading || this.props.pluginsLoading) {
return (
@@ -328,6 +611,7 @@ class Plugins extends Component {
removePlugin={this.removePlugin}
restart={this.handleRestart}
handleRedirect={this.handleRedirect}
+ handleStep={this.handleStep}
/>
) : (
@@ -354,35 +638,25 @@ class Plugins extends Component {
myPlugins={myPlugins}
pluginData={pluginData}
restart={this.handleRestart}
+ thirdPartyType={thirdPartyType}
+ thirdPartyError={thirdPartyError}
+ thirdParty={thirdParty}
+ handleStep={this.handleStep}
+ handleURL={this.handleURL}
+ handleChange={this.handleChange}
+ handleFileChange={this.handleFileChange}
+ handleBack={this.handleBack}
+ handleSetBack={this.handleSetBack}
+ handleCancel={this.handleCancel}
+ getJSONFromURL={this.getJSONFromURL}
+ updateState={this.updateState}
/>
)}
-
-
-
- {selectedPlugin.icon ? (
-

- ) : (
-

- )}
-
-
-
{selectedPlugin.name}
-
This plugin is coming soon!
-
-
-
+ {this.renderModalContent()}